diff --git a/pom.xml b/pom.xml index 16016cd..6f60588 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ -LOCAL - 1.0.4 + 1.0.5 bentobox-world diff --git a/src/main/java/world/bentobox/stranger/listeners/TeamListener.java b/src/main/java/world/bentobox/stranger/listeners/TeamListener.java index d17c5b6..1b1c543 100644 --- a/src/main/java/world/bentobox/stranger/listeners/TeamListener.java +++ b/src/main/java/world/bentobox/stranger/listeners/TeamListener.java @@ -68,20 +68,28 @@ public void onTeamJoin(TeamJoinedEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onTeamKick(TeamKickEvent e) { if (!addon.inWorld(e.getIsland().getWorld()) // Not in game world - || addon.getSettings().getMemberBonus() != 0) { - resize(e.getIsland()); + || addon.getSettings().getMemberBonus() == 0) { // No resizing + return; } + resize(e.getIsland()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onTeamLeave(TeamLeaveEvent e) { if (!addon.inWorld(e.getIsland().getWorld()) // Not in game world - || addon.getSettings().getMemberBonus() != 0) { - resize(e.getIsland()); + || addon.getSettings().getMemberBonus() == 0) { // No resizing + return; } + resize(e.getIsland()); } protected int resize(Island claim) { + // Guard: never resize an island that isn't governed by StrangerRealms. + // Resizing would call setRange/setProtectionRange with StrangerRealms' distance, + // corrupting the range of islands belonging to other game modes (see #issue). + if (!addon.inWorld(claim.getWorld())) { + return 0; + } // Remove old claim from grid addon.getPlugin().getIslands().getIslandCache().getIslandGrid(claim.getWorld()).removeFromGrid(claim); diff --git a/src/test/java/world/bentobox/stranger/listeners/TeamListenerTest.java b/src/test/java/world/bentobox/stranger/listeners/TeamListenerTest.java new file mode 100644 index 0000000..d3f3809 --- /dev/null +++ b/src/test/java/world/bentobox/stranger/listeners/TeamListenerTest.java @@ -0,0 +1,125 @@ +package world.bentobox.stranger.listeners; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.UUID; + +import org.bukkit.World; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.api.events.team.TeamKickEvent; +import world.bentobox.bentobox.api.events.team.TeamLeaveEvent; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.island.IslandCache; +import world.bentobox.bentobox.managers.island.IslandGrid; +import world.bentobox.stranger.CommonTestSetup; +import world.bentobox.stranger.Settings; +import world.bentobox.stranger.StrangerRealms; + +/** + * Regression tests for TeamListener. + * + *

Background: a prior version of {@link TeamListener#onTeamKick(TeamKickEvent)} and + * {@link TeamListener#onTeamLeave(TeamLeaveEvent)} had inverted boolean logic that caused + * {@code resize()} to fire on islands that were not in StrangerRealms' world. + * That in turn called {@link Island#setRange(int)} with StrangerRealms' configured distance + * (typically 64 with defaults), corrupting the range field of other game modes' islands and + * causing {@code Island distance mismatch} crashes on the next server restart. + */ +public class TeamListenerTest extends CommonTestSetup { + + @Mock + private World otherWorld; // a world NOT governed by StrangerRealms + @Mock + private Island otherIsland; // an island in that other world (e.g. AOneBlock) + @Mock + private Settings strangerSettings; + @Mock + private IslandCache islandCache; + @Mock + private IslandGrid islandGrid; + + // Do NOT use @Mock for StrangerRealms — its static initializer touches + // BentoBox.getInstance() (NamespacedKey for the warped compass recipe), + // which fails before super.setUp() has installed the plugin singleton. + // Construct it manually after super.setUp(). + private StrangerRealms addon; + private TeamListener listener; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + addon = mock(StrangerRealms.class); + + // Addon points at the standard mocked BentoBox plugin/managers. + when(addon.getPlugin()).thenReturn(plugin); + when(addon.getIslands()).thenReturn(im); + when(addon.getSettings()).thenReturn(strangerSettings); + // Defaults from StrangerRealms config: distance 32, memberBonus 32. + when(strangerSettings.getIslandDistance()).thenReturn(32); + when(strangerSettings.getMemberBonus()).thenReturn(32); + + // CRITICAL: the kicked/leaving island is in some other game mode's world. + when(addon.inWorld(otherWorld)).thenReturn(false); + when(otherIsland.getWorld()).thenReturn(otherWorld); + // Island has two members so resize() would compute 32 + 32*(2-1) = 64. + when(otherIsland.getMemberSet()).thenReturn(ImmutableSet.of(UUID.randomUUID(), UUID.randomUUID())); + when(otherIsland.getProtectionRange()).thenReturn(50); + + // Island grid wiring (in case the guard is removed in the future and resize is reached). + when(im.getIslandCache()).thenReturn(islandCache); + when(islandCache.getIslandGrid(any())).thenReturn(islandGrid); + + listener = new TeamListener(addon); + } + + @Test + public void onTeamKick_islandInOtherGamemode_doesNotMutateRange() { + TeamKickEvent event = mock(TeamKickEvent.class); + when(event.getIsland()).thenReturn(otherIsland); + + listener.onTeamKick(event); + + // The whole point of the fix: never touch the range / protection range of an + // island that isn't in StrangerRealms' world. + verify(otherIsland, never()).setRange(anyInt()); + verify(otherIsland, never()).setProtectionRange(anyInt()); + } + + @Test + public void onTeamLeave_islandInOtherGamemode_doesNotMutateRange() { + TeamLeaveEvent event = mock(TeamLeaveEvent.class); + when(event.getIsland()).thenReturn(otherIsland); + + listener.onTeamLeave(event); + + verify(otherIsland, never()).setRange(anyInt()); + verify(otherIsland, never()).setProtectionRange(anyInt()); + } + + @Test + public void onTeamKick_memberBonusZero_doesNotMutateRange() { + // Even within StrangerRealms' own world, a zero memberBonus means resizing + // is disabled and the early-return should fire. + when(strangerSettings.getMemberBonus()).thenReturn(0); + when(addon.inWorld(otherWorld)).thenReturn(true); // pretend the island IS in stranger world + + TeamKickEvent event = mock(TeamKickEvent.class); + when(event.getIsland()).thenReturn(otherIsland); + + listener.onTeamKick(event); + + verify(otherIsland, never()).setRange(anyInt()); + verify(otherIsland, never()).setProtectionRange(anyInt()); + } +}