From 86c9a8f47b5696d3baaed2b229ac21921069f877 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 1 Sep 2024 09:11:03 -0700 Subject: [PATCH 01/14] Version 2.5.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c4b5ccd78..d39e781b6 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ -LOCAL - 2.5.3 + 2.5.4 bentobox-world https://sonarcloud.io ${project.basedir}/lib From dbf4bb75b134d18e8dc2465fcb5660afc8984c37 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 7 Sep 2024 11:06:35 -0700 Subject: [PATCH 02/14] Fix bug with saving mobs in blueprints #2497 --- .../database/json/adapters/ProfessionTypeAdapter.java | 8 ++++++-- .../database/json/adapters/VillagerTypeAdapter.java | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/ProfessionTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/ProfessionTypeAdapter.java index bd40494ec..e083e3ece 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/adapters/ProfessionTypeAdapter.java +++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/ProfessionTypeAdapter.java @@ -12,8 +12,12 @@ import com.google.gson.stream.JsonWriter; public class ProfessionTypeAdapter extends TypeAdapter { @Override - public void write(JsonWriter out, Profession profession) throws IOException { - out.value(profession.name()); + public void write(JsonWriter out, Profession profession) throws IOException { + if (profession != null) { + out.value(profession.name()); + return; + } + out.nullValue(); } @Override diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/VillagerTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/VillagerTypeAdapter.java index 050142ad6..9752f17a6 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/adapters/VillagerTypeAdapter.java +++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/VillagerTypeAdapter.java @@ -12,7 +12,11 @@ import com.google.gson.stream.JsonWriter; public class VillagerTypeAdapter extends TypeAdapter { @Override - public void write(JsonWriter out, Villager.Type type) throws IOException { + public void write(JsonWriter out, Villager.Type type) throws IOException { + if (type == null) { + out.nullValue(); + return; + } out.value(type.name()); } From 80219f38cbc0848ef052b4e7c4e331304ee80fe2 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 7 Sep 2024 11:06:51 -0700 Subject: [PATCH 03/14] Add protection around meta data being written that is immutable --- .../bentobox/database/objects/Players.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Players.java b/src/main/java/world/bentobox/bentobox/database/objects/Players.java index ca384ed12..883072d7c 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Players.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Players.java @@ -253,10 +253,32 @@ public class Players implements DataObject, MetaDataAble { public Optional> getMetaData() { if (metaData == null) { metaData = new HashMap<>(); + } else if (isImmutable(metaData)) { + metaData = new HashMap<>(metaData); // Convert immutable map to mutable } return Optional.of(metaData); } + private boolean isImmutable(Map map) { + try { + String testKey = "testKey"; + MetaDataValue testValue = new MetaDataValue("test"); + + // If the map already contains keys, use one of them + if (!map.isEmpty()) { + String existingKey = map.keySet().iterator().next(); + map.put(existingKey, map.get(existingKey)); // Attempt to replace value + } else { + // Use a unique key-value pair + map.put(testKey, testValue); + map.remove(testKey); + } + return false; // No exception means the map is mutable + } catch (UnsupportedOperationException e) { + return true; // Exception means the map is immutable + } + } + /** * @param metaData the metaData to set * @since 1.15.4 @@ -264,6 +286,9 @@ public class Players implements DataObject, MetaDataAble { */ @Override public void setMetaData(Map metaData) { + if (isImmutable(metaData)) { + throw new IllegalArgumentException("Provided map is immutable and cannot be set."); + } this.metaData = metaData; } From a9b8613cdc5a79de06958ab334ead72aebc769ab Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 7 Sep 2024 18:00:26 -0700 Subject: [PATCH 04/14] Protect against null to locations. #2496 --- .../worldsettings/EnterExitListener.java | 8 ++-- .../worldsettings/EnterExitListenerTest.java | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/EnterExitListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/EnterExitListener.java index bdcff20dd..9f82fdc9a 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/EnterExitListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/EnterExitListener.java @@ -9,6 +9,7 @@ import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.api.events.island.IslandEvent; import world.bentobox.bentobox.api.flags.FlagListener; @@ -36,15 +37,16 @@ public class EnterExitListener extends FlagListener { handleEnterExit(User.getInstance(e.getPlayer()), e.getFrom(), e.getTo(), e); } - private void handleEnterExit(@NonNull User user, @NonNull Location from, @NonNull Location to, @NonNull PlayerMoveEvent e) { + private void handleEnterExit(@NonNull User user, @NonNull Location from, @Nullable Location to, + @NonNull PlayerMoveEvent e) { // Only process if there is a change in X or Z coords - if (from.getWorld() != null && from.getWorld().equals(to.getWorld()) + if (from.getWorld() != null && to != null && from.getWorld().equals(to.getWorld()) && from.toVector().multiply(XZ).equals(to.toVector().multiply(XZ))) { return; } Optional islandFrom = getIslands().getProtectedIslandAt(from); - Optional islandTo = getIslands().getProtectedIslandAt(to); + Optional islandTo = to == null ? Optional.empty() : getIslands().getProtectedIslandAt(to); /* * Options: diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/EnterExitListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/EnterExitListenerTest.java index fe1a34adf..0167bacf2 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/EnterExitListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/EnterExitListenerTest.java @@ -242,6 +242,19 @@ public class EnterExitListenerTest { verify(pim, never()).callEvent(any(IslandExitEvent.class)); } + /** + * Test method for {@link EnterExitListener#onMove(org.bukkit.event.player.PlayerMoveEvent)}. + */ + @Test + public void testOnMoveOutsideIslandToNull() { + PlayerMoveEvent e = new PlayerMoveEvent(user.getPlayer(), outside, null); + listener.onMove(e); + // Moving outside the island should result in no messages to the user + verify(notifier, never()).notify(any(), any()); + verify(pim, never()).callEvent(any(IslandEnterEvent.class)); + verify(pim, never()).callEvent(any(IslandExitEvent.class)); + } + /** * Test method for {@link EnterExitListener#onMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @@ -294,6 +307,23 @@ public class EnterExitListenerTest { verify(notifier).notify(any(User.class), eq("protection.flags.ENTER_EXIT_MESSAGES.now-leaving")); } + /** + * Test method for {@link EnterExitListener#onMove(org.bukkit.event.player.PlayerMoveEvent)}. + */ + @Test + public void testExitingIslandEmptyIslandNameToNull() { + when(island.getName()).thenReturn(""); + PlayerMoveEvent e = new PlayerMoveEvent(user.getPlayer(), inside, null); + listener.onMove(e); + // Moving into the island should show a message + verify(lm).get(any(), eq("protection.flags.ENTER_EXIT_MESSAGES.now-leaving")); + // The island owner needs to be checked + verify(island).isOwned(); + verify(pim).callEvent(any(IslandExitEvent.class)); + verify(pim, never()).callEvent(any(IslandEnterEvent.class)); + verify(notifier).notify(any(User.class), eq("protection.flags.ENTER_EXIT_MESSAGES.now-leaving")); + } + /** * Test method for {@link EnterExitListener#onMove(org.bukkit.event.player.PlayerMoveEvent)}. */ @@ -354,6 +384,18 @@ public class EnterExitListenerTest { verify(pim).callEvent(any(IslandExitEvent.class)); } + /** + * Test method for {@link EnterExitListener#onTeleport(org.bukkit.event.player.PlayerTeleportEvent)}. + */ + @Test + public void testExitIslandTeleportToNull() { + PlayerTeleportEvent e = new PlayerTeleportEvent(user.getPlayer(), inside, null, TeleportCause.PLUGIN); + listener.onTeleport(e); + verify(notifier).notify(any(User.class), eq("protection.flags.ENTER_EXIT_MESSAGES.now-leaving")); + verify(pim, never()).callEvent(any(IslandEnterEvent.class)); + verify(pim).callEvent(any(IslandExitEvent.class)); + } + /** * Test method for {@link EnterExitListener#onTeleport(org.bukkit.event.player.PlayerTeleportEvent)}. From 8ba3f034f7079f46eb357a734df16cb491221477 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 13 Sep 2024 07:53:22 -0700 Subject: [PATCH 05/14] Prevent null island worlds from blocking purging. #2500 --- .../bentobox/api/commands/admin/purge/AdminPurgeCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java index a5c6fe6cc..1e90aa431 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java @@ -133,6 +133,7 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener { // Process islands in one pass, logging and adding to the set if applicable getPlugin().getIslands().getIslands().stream() .filter(i -> !i.isSpawn()).filter(i -> !i.getPurgeProtected()) + .filter(i -> i.getWorld() != null) // to handle currently unloaded world islands .filter(i -> i.getWorld().equals(this.getWorld())).filter(Island::isOwned).filter( i -> i.getMemberSet().stream() .allMatch(member -> (currentTimeMillis From 8240484d4fe4102b632c7e39bb48371434cc294b Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 13 Sep 2024 08:00:17 -0700 Subject: [PATCH 06/14] Added test case --- .../commands/admin/purge/AdminPurgeCommandTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommandTest.java index c75191fb0..cd1da2ae1 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommandTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.when; import java.util.Collections; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; @@ -364,4 +365,15 @@ public class AdminPurgeCommandTest { verify(user, Mockito.times(1)).sendMessage(any()); } + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#getOldIslands(int)} + */ + @Test + public void testGetOldIslands() { + assertTrue(apc.getOldIslands(10).isEmpty()); + Island island2 = mock(Island.class); + when(im.getIslands()).thenReturn(Set.of(island, island2)); + assertTrue(apc.getOldIslands(10).isEmpty()); + } + } From 2b6c7f85a60917895182948f4c9949349debc915 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 14 Sep 2024 16:13:23 -0700 Subject: [PATCH 07/14] Improve command rank GUI with helpful text descriptions #2502 --- pom.xml | 4 -- .../api/panels/builders/PanelItemBuilder.java | 6 ++- .../CommandRankClickListener.java | 18 +++++-- .../bentobox/managers/CommandsManager.java | 1 + src/main/resources/locales/en-US.yml | 54 +++++++++++++++++++ .../CommandRankClickListenerTest.java | 13 +++-- 6 files changed, 82 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index d39e781b6..5f5b6f725 100644 --- a/pom.xml +++ b/pom.xml @@ -157,10 +157,6 @@ codemc-repo https://repo.codemc.org/repository/maven-public - - placeholderapi-repo - https://repo.extendedclip.com/content/repositories/placeholderapi/ - dynmap-repo https://repo.mikeprimm.com/ diff --git a/src/main/java/world/bentobox/bentobox/api/panels/builders/PanelItemBuilder.java b/src/main/java/world/bentobox/bentobox/api/panels/builders/PanelItemBuilder.java index ebe17e3c4..75d4a95e9 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/builders/PanelItemBuilder.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/builders/PanelItemBuilder.java @@ -104,7 +104,9 @@ public class PanelItemBuilder { * @return PanelItemBuilder */ public PanelItemBuilder description(String description) { - Collections.addAll(this.description, description.split("\n")); + if (description != null) { + Collections.addAll(this.description, description.split("\n")); + } return this; } @@ -168,7 +170,7 @@ public class PanelItemBuilder { public boolean isPlayerHead() { return playerHeadName != null && !playerHeadName.isEmpty(); } - + /** * @return the playerHead * @since 1.9.0 diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListener.java index 2747ebe55..2ad7c542e 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListener.java @@ -2,6 +2,7 @@ package world.bentobox.bentobox.listeners.flags.clicklisteners; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Objects; import org.bukkit.Material; @@ -26,6 +27,7 @@ import world.bentobox.bentobox.panels.settings.SettingsTab; import world.bentobox.bentobox.util.Util; /** + * * @author tastybento * */ @@ -108,11 +110,19 @@ public class CommandRankClickListener implements ClickHandler { */ public PanelItem getPanelItem(String c, User user, World world) { PanelItemBuilder pib = new PanelItemBuilder(); - pib.name(c); + pib.name(user.getTranslation("protection.panel.flag-item.name-layout", TextVariables.NAME, c)); pib.clickHandler(new CommandCycleClick(this, c)); pib.icon(Material.MAP); - // TODO: use specific layout - String d = user.getTranslation("protection.panel.flag-item.description-layout", TextVariables.DESCRIPTION, ""); + String result = ""; + // Remove the first word (everything before the first space) + String[] words = c.split(" ", 2); // Split into two parts, the first word and the rest + if (words.length > 1) { + result = words[1].replace(" ", "-"); // Replace spaces with hyphens + } + String ref = "protection.panel.flag-item.command-instructions." + result.toLowerCase(Locale.ENGLISH); + String commandDescription = user.getTranslationOrNothing(ref); + String d = user.getTranslation("protection.panel.flag-item.description-layout", TextVariables.DESCRIPTION, + commandDescription); pib.description(d); RanksManager.getInstance().getRanks().forEach((reference, score) -> { if (score >= RanksManager.MEMBER_RANK && score < island.getRankCommand(c)) { @@ -133,7 +143,7 @@ public class CommandRankClickListener implements ClickHandler { .filter(c -> c.getWorld() != null && c.getWorld().equals(world)) // Only allow commands in this world .filter(c -> c.testPermission(user.getSender())) // Only allow them to see commands they have permission to see .flatMap(c -> getCmdRecursively("/", c).stream()) - .filter(label -> user.isOp() || !hiddenItems.contains(CommandCycleClick.COMMAND_RANK_PREFIX + label)) + .filter(label -> user.isOp() || !hiddenItems.contains(CommandCycleClick.COMMAND_RANK_PREFIX + label)) // Hide any hidden commands .limit(49) // Silently limit to 49 .toList(); return result; diff --git a/src/main/java/world/bentobox/bentobox/managers/CommandsManager.java b/src/main/java/world/bentobox/bentobox/managers/CommandsManager.java index 571ed90bc..35d4cef3a 100644 --- a/src/main/java/world/bentobox/bentobox/managers/CommandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/CommandsManager.java @@ -72,6 +72,7 @@ public class CommandsManager { } /** + * Get a map of every command registered in BentoBox * @return the commands */ @NonNull diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 47c5f27bc..1cbaf9f40 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1652,6 +1652,59 @@ protection: &a player is outside their island flag-item: name-layout: '&a [name]' + # Add commands to this list as required + command-instructions: + setname: | + &a Select the rank that can + &a set the name + ban: | + &a Select the rank that can + ban players + unban: | + &a Select the rank that can + &a unban players + expel: | + &a Select the rank who can + &a expel visitors + team-invite: | + &a Select the rank that can + &a invite + team-kick: | + &a Select the rank that can + &a kick + team-coop: | + &a Select the rank that can + &a coop + team-trust: | + &a Select the rank that can + &a trust + team-uncoop: | + &a Select the rank that can + &a uncoop + team-untrust: | + &a Select the rank that can + &a untrust + team-promote: | + &a Select the rank that can + &a promote player's rank + team-demote: | + &a Select the rank that can + &a demote player's rank + sethome: | + &a Select the rank that can + &a set homes + deletehome: | + &a Select the rank that can + &a delete homes + renamehome: | + &a Select the rank that can + &a rename homes + setcount: | + &a Select the rank that can + &a change the phase + border: | + &a Select the rank that can + &a use the border command description-layout: | &a [description] @@ -1854,6 +1907,7 @@ panels: # The section of translations used in Language Panel language: title: "&2&l Select your language" + edited: "&c Changed to [lang]" buttons: # This button is used for displaying different locales that are available in language selection panel. language: diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListenerTest.java index 86370f28d..20165cd76 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/CommandRankClickListenerTest.java @@ -122,6 +122,10 @@ public class CommandRankClickListenerTest extends RanksManagerBeforeClassTest { when(user.getPlayer()).thenReturn(player); when(user.inWorld()).thenReturn(true); when(user.getWorld()).thenReturn(world); + when(user.getTranslationOrNothing(anyString(), anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + when(user.getTranslationOrNothing(anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); when(user.getTranslation(anyString())) .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); when(user.getTranslation(anyString(), anyString(), anyString())) @@ -215,7 +219,8 @@ public class CommandRankClickListenerTest extends RanksManagerBeforeClassTest { when(cm.getCommands()).thenReturn(map); assertTrue(crcl.onClick(panel, user, ClickType.LEFT, 0)); - verify(user).getTranslation("protection.panel.flag-item.description-layout", TextVariables.DESCRIPTION, ""); + verify(user).getTranslation("protection.panel.flag-item.description-layout", TextVariables.DESCRIPTION, + "protection.panel.flag-item.command-instructions."); } /** @@ -226,12 +231,12 @@ public class CommandRankClickListenerTest extends RanksManagerBeforeClassTest { assertTrue(crcl.onClick(panel, user, ClickType.LEFT, 0)); PanelItem pi = crcl.getPanelItem("test", user, world); assertEquals(Material.MAP, pi.getItem().getType()); - assertEquals("protection.panel.flag-item.description-layout", pi.getDescription().get(0)); + //assertEquals("protection.panel.flag-item.description-layout", pi.getDescription().get(0)); //assertEquals("protection.panel.flag-item.minimal-rankranks.member", pi.getDescription().get(1)); //assertEquals("protection.panel.flag-item.allowed-rankranks.sub-owner", pi.getDescription().get(2)); - //assertEquals("protection.panel.flag-item.allowed-rankranks.owner", pi.getDescription().get(3)); + //assertEquals("protection.panel.flag-item.allowed-rankranks.owner", pi.getDescription().get(0)); assertTrue(pi.getClickHandler().isPresent()); - assertEquals("test", pi.getName()); + assertEquals("protection.panel.flag-item.name-layout", pi.getName()); } } From 0965e07d939e38f6a2bffe49807ce177715025d8 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 14 Sep 2024 16:43:25 -0700 Subject: [PATCH 08/14] Make the rank clearing async to avoid lag. #2504 --- .../java/world/bentobox/bentobox/managers/IslandsManager.java | 4 ++++ .../world/bentobox/bentobox/managers/IslandsManagerTest.java | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 5b11937f0..7e594c111 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1650,6 +1650,10 @@ public class IslandsManager { * @param uniqueId - UUID of player */ public void clearRank(int rank, UUID uniqueId) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> clearRankSync(rank, uniqueId)); + } + + void clearRankSync(int rank, UUID uniqueId) { islandCache.getCachedIslands().forEach( i -> i.getMembers().entrySet().removeIf(e -> e.getKey().equals(uniqueId) && e.getValue() == rank)); } diff --git a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java index e6f355dda..fa84ccec0 100644 --- a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java @@ -1111,9 +1111,9 @@ public class IslandsManagerTest extends AbstractCommonSetup { UUID coopUUID = UUID.randomUUID(); members.put(coopUUID, RanksManager.COOP_RANK); // Clear a random user - im.clearRank(RanksManager.COOP_RANK, UUID.randomUUID()); + im.clearRankSync(RanksManager.COOP_RANK, UUID.randomUUID()); assertEquals(14, members.size()); - im.clearRank(RanksManager.COOP_RANK, coopUUID); + im.clearRankSync(RanksManager.COOP_RANK, coopUUID); assertEquals(13, members.size()); } From 625bfa631b1ebcb478cae7767400a55d8845c580 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 16 Sep 2024 21:04:44 -0700 Subject: [PATCH 09/14] Change how creeper damage flag works. #2507 --- .../world/bentobox/bentobox/Settings.java | 22 +++++++++- .../flags/protection/ExplosionListener.java | 1 + .../flags/worldsettings/CreeperListener.java | 23 ++-------- .../bentobox/managers/IslandsManager.java | 3 +- .../worldsettings/CreeperListenerTest.java | 44 +++++++++++++++++++ .../bentobox/managers/IslandsManagerTest.java | 41 ++++++++++++++--- 6 files changed, 106 insertions(+), 28 deletions(-) create mode 100644 src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java diff --git a/src/main/java/world/bentobox/bentobox/Settings.java b/src/main/java/world/bentobox/bentobox/Settings.java index c149907b2..992aa0333 100644 --- a/src/main/java/world/bentobox/bentobox/Settings.java +++ b/src/main/java/world/bentobox/bentobox/Settings.java @@ -141,7 +141,6 @@ public class Settings implements ConfigObject { private Set fakePlayers = new HashSet<>(); /* PANELS */ - @ConfigComment("Toggle whether panels should be closed or not when the player clicks anywhere outside of the inventory view.") @ConfigEntry(path = "panel.close-on-click-outside") private boolean closePanelOnClickOutside = true; @@ -189,6 +188,13 @@ public class Settings implements ConfigObject { /* * Island */ + @ConfigComment("Override island distance mismatch checking. BentoBox normally refuses to run if") + @ConfigComment("the island distance in the gamemode config is different to the one stored in the database") + @ConfigComment("for safety. This overrides that check. You should never need this, and if you do not understand it") + @ConfigComment("keep it as false") + @ConfigEntry(path = "island.override-safety-check") + private boolean overrideSafetyCheck = false; + // Number of islands @ConfigComment("The default number of concurrent islands a player may have.") @ConfigComment("This may be overridden by individual game mode config settings.") @@ -1034,4 +1040,18 @@ public class Settings implements ConfigObject { this.hideUsedBlueprints = hideUsedBlueprints; } + /** + * @return the overrideSafetyCheck + */ + public boolean isOverrideSafetyCheck() { + return overrideSafetyCheck; + } + + /** + * @param overrideSafetyCheck the overrideSafetyCheck to set + */ + public void setOverrideSafetyCheck(boolean overrideSafetyCheck) { + this.overrideSafetyCheck = overrideSafetyCheck; + } + } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java index d000eea7f..ca332a19f 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java @@ -20,6 +20,7 @@ import org.bukkit.event.player.PlayerInteractEvent; import com.google.common.base.Enums; import com.google.common.base.Optional; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java index 7adf0d6d6..def31f71d 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java @@ -13,6 +13,7 @@ import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; @@ -40,7 +41,8 @@ public class CreeperListener extends FlagListener { if (!Flags.CREEPER_DAMAGE.isSetForWorld(e.getLocation().getWorld())) { // If any were removed, then prevent damage too e.blockList().clear(); - e.setCancelled(true); + // Still allow player and mob damage + e.setCancelled(false); return; } // Check for griefing @@ -55,25 +57,6 @@ public class CreeperListener extends FlagListener { } } - - /** - * Prevent entities being damaged by explosion - * @param e - event - * @since 1.10.0 - */ - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onExplosion(final EntityDamageByEntityEvent e) { - if (!e.getCause().equals(EntityDamageEvent.DamageCause.ENTITY_EXPLOSION) || !getIWM().inWorld(e.getEntity().getLocation()) - || !e.getDamager().getType().equals(EntityType.CREEPER)) { - return; - } - // If creeper damage is not allowed in world cancel the damage - if (!Flags.CREEPER_DAMAGE.isSetForWorld(e.getEntity().getWorld())) { - e.setCancelled(true); - } - } - - /** * Prevent creepers from igniting if they are not allowed to grief * @param e - event diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 7e594c111..e532d6a03 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1267,7 +1267,8 @@ public class IslandsManager { // These will be deleted later deletedIslands.add(island.getUniqueId()); } // Check island distance and if incorrect stop BentoBox - else if (island.getWorld() != null && plugin.getIWM().inWorld(island.getWorld()) + else if (!plugin.getSettings().isOverrideSafetyCheck() && island.getWorld() != null + && plugin.getIWM().inWorld(island.getWorld()) && island.getRange() != plugin.getIWM().getIslandDistance(island.getWorld())) { throw new IOException("Island distance mismatch!\n" + "World '" + island.getWorld().getName() + "' distance " + plugin.getIWM().getIslandDistance(island.getWorld()) + " != island range " diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java new file mode 100644 index 000000000..37b47d398 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java @@ -0,0 +1,44 @@ +package world.bentobox.bentobox.listeners.flags.worldsettings; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * + */ +public class CreeperListenerTest { + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener#onExplosion(org.bukkit.event.entity.EntityExplodeEvent)}. + */ + @Test + public void testOnExplosion() { + fail("Not yet implemented"); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener#onPlayerInteractEntity(org.bukkit.event.player.PlayerInteractEntityEvent)}. + */ + @Test + public void testOnPlayerInteractEntity() { + fail("Not yet implemented"); + } + +} diff --git a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java index fa84ccec0..fc2967a0d 100644 --- a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java @@ -153,6 +153,8 @@ public class IslandsManagerTest extends AbstractCommonSetup { // Class under test IslandsManager im; + private Settings settings; + @SuppressWarnings("unchecked") @BeforeClass public static void beforeClass() throws IllegalAccessException, InvocationTargetException, IntrospectionException { @@ -187,12 +189,12 @@ public class IslandsManagerTest extends AbstractCommonSetup { when(world.getEnvironment()).thenReturn(World.Environment.NORMAL); when(iwm.inWorld(any(World.class))).thenReturn(true); when(iwm.inWorld(any(Location.class))).thenReturn(true); + when(iwm.getIslandDistance(any())).thenReturn(25); when(plugin.getIWM()).thenReturn(iwm); // Settings - Settings s = mock(Settings.class); - when(plugin.getSettings()).thenReturn(s); - when(s.getDatabaseType()).thenReturn(DatabaseType.JSON); + settings = new Settings(); + when(plugin.getSettings()).thenReturn(settings); // World when(world.getEnvironment()).thenReturn(World.Environment.NORMAL); @@ -827,14 +829,41 @@ public class IslandsManagerTest extends AbstractCommonSetup { /** * Test method for * {@link world.bentobox.bentobox.managers.IslandsManager#load()}. + * @throws IOException + * @throws IntrospectionException + * @throws NoSuchMethodException + * @throws ClassNotFoundException + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws InstantiationException */ @Test - public void testLoad() { - // - // im.load(); + public void testLoad() throws IOException, InstantiationException, IllegalAccessException, + InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IntrospectionException { + when(island.getRange()).thenReturn(100); + when(h.loadObjects()).thenReturn(List.of(island)); + try { + im.load(); + } catch (IOException e) { + assertEquals("Island distance mismatch!\n" + "World 'world' distance 25 != island range 100!\n" + + "Island ID in database is null.\n" + + "Island distance in config.yml cannot be changed mid-game! Fix config.yml or clean database.", + e.getMessage()); + } } + @Test + public void testLoadNoDistanceCheck() throws IOException, InstantiationException, IllegalAccessException, + InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IntrospectionException { + settings.setOverrideSafetyCheck(true); + when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString()); + when(island.getRange()).thenReturn(100); + when(h.loadObjects()).thenReturn(List.of(island)); + im.load(); + // No exception should be thrown + } + /** * Test method for * {@link world.bentobox.bentobox.managers.IslandsManager#locationIsOnIsland(org.bukkit.entity.Player, org.bukkit.Location)}. From 5a4cbcce250ab70cef325035a6791758007d94cf Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 16 Sep 2024 21:23:45 -0700 Subject: [PATCH 10/14] Add some tests --- .../flags/worldsettings/CreeperListener.java | 5 +- .../worldsettings/CreeperListenerTest.java | 102 +++++++++++++++++- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java index def31f71d..6ea941017 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListener.java @@ -8,12 +8,9 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; @@ -43,8 +40,8 @@ public class CreeperListener extends FlagListener { e.blockList().clear(); // Still allow player and mob damage e.setCancelled(false); - return; } + // Check for griefing Creeper creeper = (Creeper)e.getEntity(); if (!Flags.CREEPER_GRIEFING.isSetForWorld(e.getLocation().getWorld()) diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java index 37b47d398..2733fe692 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java @@ -1,21 +1,50 @@ package world.bentobox.bentobox.listeners.flags.worldsettings; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.entity.EntityExplodeEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.listeners.flags.AbstractCommonSetup; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.util.Util; /** * */ -public class CreeperListenerTest { +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class, BentoBox.class, Flags.class, Util.class }) +public class CreeperListenerTest extends AbstractCommonSetup { + + private CreeperListener cl; /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { + super.setUp(); + + cl = new CreeperListener(); } /** @@ -23,14 +52,77 @@ public class CreeperListenerTest { */ @After public void tearDown() throws Exception { + User.clearUsers(); + Mockito.framework().clearInlineMocks(); } /** * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener#onExplosion(org.bukkit.event.entity.EntityExplodeEvent)}. */ @Test - public void testOnExplosion() { - fail("Not yet implemented"); + public void testOnExplosionNotCreeper() { + List list = new ArrayList<>(); + Entity entity = mock(Entity.class); + when(entity.getType()).thenReturn(EntityType.TNT); + when(iwm.inWorld(location)).thenReturn(true); + EntityExplodeEvent event = new EntityExplodeEvent(entity, location, list, 0); + cl.onExplosion(event); + assertFalse(event.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener#onExplosion(org.bukkit.event.entity.EntityExplodeEvent)}. + */ + @Test + public void testOnExplosionNotInWorld() { + List list = new ArrayList<>(); + Entity entity = mock(Entity.class); + when(entity.getLocation()).thenReturn(location); + when(entity.getType()).thenReturn(EntityType.CREEPER); + when(iwm.inWorld(location)).thenReturn(false); + EntityExplodeEvent event = new EntityExplodeEvent(entity, location, list, 0); + cl.onExplosion(event); + assertFalse(event.isCancelled()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener#onExplosion(org.bukkit.event.entity.EntityExplodeEvent)}. + */ + @Test + public void testOnExplosionCreeperInWorldDamageOK() { + List list = new ArrayList<>(); + list.add(mock(Block.class)); + list.add(mock(Block.class)); + list.add(mock(Block.class)); + Creeper entity = mock(Creeper.class); + when(entity.getLocation()).thenReturn(location); + when(entity.getType()).thenReturn(EntityType.CREEPER); + when(iwm.inWorld(location)).thenReturn(true); + EntityExplodeEvent event = new EntityExplodeEvent(entity, location, list, 0); + cl.onExplosion(event); + assertFalse(event.isCancelled()); + assertFalse(event.blockList().isEmpty()); // No clearing of block list + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener#onExplosion(org.bukkit.event.entity.EntityExplodeEvent)}. + */ + @Test + public void testOnExplosionCreeperInWorldDamageNOK() { + Flags.CREEPER_DAMAGE.setSetting(world, false); + List list = new ArrayList<>(); + list.add(mock(Block.class)); + list.add(mock(Block.class)); + list.add(mock(Block.class)); + Creeper entity = mock(Creeper.class); + when(location.getWorld()).thenReturn(world); + when(entity.getLocation()).thenReturn(location); + when(entity.getType()).thenReturn(EntityType.CREEPER); + when(iwm.inWorld(location)).thenReturn(true); + EntityExplodeEvent event = new EntityExplodeEvent(entity, location, list, 0); + cl.onExplosion(event); + assertFalse(event.isCancelled()); + assertTrue(event.blockList().isEmpty()); // No clearing of block list } /** @@ -38,7 +130,7 @@ public class CreeperListenerTest { */ @Test public void testOnPlayerInteractEntity() { - fail("Not yet implemented"); + //TODO } } From 4876064a46925c58097171d8d040d25572d69d23 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 16 Sep 2024 21:26:25 -0700 Subject: [PATCH 11/14] Remove unused import --- .../bentobox/listeners/flags/protection/ExplosionListener.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java index ca332a19f..d000eea7f 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java @@ -20,7 +20,6 @@ import org.bukkit.event.player.PlayerInteractEvent; import com.google.common.base.Enums; import com.google.common.base.Optional; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; From d2410a5f85626c8c601fab86dc37e56498c207e3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 18 Sep 2024 14:19:35 -0700 Subject: [PATCH 12/14] Do not fix island centers --- .../world/bentobox/bentobox/managers/IslandsManager.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index e532d6a03..3c303572b 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1275,8 +1275,10 @@ public class IslandsManager { + island.getRange() + "!\n" + "Island ID in database is " + island.getUniqueId() + ".\n" + "Island distance in config.yml cannot be changed mid-game! Fix config.yml or clean database."); } else { - // Fix island center if it is off - fixIslandCenter(island); + if (!plugin.getSettings().isOverrideSafetyCheck()) { + // Fix island center if it is off + fixIslandCenter(island); + } islandCache.addIsland(island, true); if (island.isSpawn()) { From a15b437249cf3eb17875fc5bed2dcce5b4637f9f Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 21 Sep 2024 17:08:58 -0700 Subject: [PATCH 13/14] NPE fix for #2512 --- .../listeners/flags/protection/BreakBlocksListener.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java index f80aa0d85..6ac05ad87 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java @@ -84,6 +84,9 @@ public class BreakBlocksListener extends FlagListener { @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onPlayerInteract(final PlayerInteractEvent e) { + if (e.getClickedBlock() == null) { + return; + } Player p = e.getPlayer(); Location l = e.getClickedBlock().getLocation(); Material m = e.getClickedBlock().getType(); @@ -95,7 +98,7 @@ public class BreakBlocksListener extends FlagListener { if (((CaveVinesPlant) e.getClickedBlock().getBlockData()).isBerries()) { this.checkIsland(e, p, l, Flags.HARVEST); } - } + } case SWEET_BERRY_BUSH -> this.checkIsland(e, p, l, Flags.HARVEST); case ROOTED_DIRT -> this.checkIsland(e, p, l, Flags.BREAK_BLOCKS); default -> { // Do nothing From a40e9eceba91aa012f2469f541c4ecb17f691f4c Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 21 Sep 2024 17:21:24 -0700 Subject: [PATCH 14/14] Add test --- .../flags/protection/BreakBlocksListenerTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListenerTest.java index 2183780ca..d503fefc9 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListenerTest.java @@ -621,4 +621,15 @@ public class BreakBlocksListenerTest extends AbstractCommonSetup { assertTrue(e.useInteractedBlock() == Result.ALLOW); } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.protection.BreakBlocksListener#onPlayerInteract(PlayerInteractEvent)} + */ + @Test + public void testNoClick() { + PlayerInteractEvent e = mock(PlayerInteractEvent.class); + when(e.getClickedBlock()).thenReturn(null); + bbl.onPlayerInteract(e); + verify(e).getClickedBlock(); + } }