diff --git a/pom.xml b/pom.xml index c4b5ccd78..5f5b6f725 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 @@ -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/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 void setHideUsedBlueprints(boolean hideUsedBlueprints) { 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/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 @@ Set getOldIslands(int days) { // 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 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 PanelItemBuilder description(String... description) { * @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 PanelItem.ClickHandler getClickHandler() { public boolean isPlayerHead() { return playerHeadName != null && !playerHeadName.isEmpty(); } - + /** * @return the playerHead * @since 1.9.0 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 @@ 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 @@ 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()); } 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 void addToPendingKick(World world) 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 Optional> getMetaData() { */ @Override public void setMetaData(Map metaData) { + if (isImmutable(metaData)) { + throw new IllegalArgumentException("Provided map is immutable and cannot be set."); + } this.metaData = metaData; } 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 @@ 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.util.Util; /** + * * @author tastybento * */ @@ -108,11 +110,19 @@ private void openPanel(User user, String panelName, World world) { */ 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 @@ private List getCommands(World world, User user) { .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/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 void onBreakHanging(final HangingBreakByEntityEvent e) { @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 void onPlayerInteract(final PlayerInteractEvent e) 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 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..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,8 +8,6 @@ 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; @@ -40,9 +38,10 @@ public void onExplosion(final EntityExplodeEvent e) { if (!Flags.CREEPER_DAMAGE.isSetForWorld(e.getLocation().getWorld())) { // If any were removed, then prevent damage too e.blockList().clear(); - e.setCancelled(true); - return; + // Still allow player and mob damage + e.setCancelled(false); } + // Check for griefing Creeper creeper = (Creeper)e.getEntity(); if (!Flags.CREEPER_GRIEFING.isSetForWorld(e.getLocation().getWorld()) @@ -55,25 +54,6 @@ public void onExplosion(final EntityExplodeEvent e) { } } - - /** - * 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/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.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 void onTeleport(PlayerTeleportEvent e) { 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/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 CompositeCommand getCommand(@NonNull String command) { } /** + * Get a map of every command registered in BentoBox * @return the commands */ @NonNull diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 5b11937f0..3c303572b 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1267,15 +1267,18 @@ public void load() throws IOException { // 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 " + 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()) { @@ -1650,6 +1653,10 @@ public void clearArea(Location loc) { * @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/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/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 java.util.Collections; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; @@ -364,4 +365,15 @@ public void testSetUser() { 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()); + } + } 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 void setUp() throws Exception { 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 void testOnClickTooManyCommands() { 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 void testGetPanelItem() { 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()); } } 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 void testLeftClickHopper() { 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(); + } } 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..2733fe692 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/CreeperListenerTest.java @@ -0,0 +1,136 @@ +package world.bentobox.bentobox.listeners.flags.worldsettings; + +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; + +/** + * + */ +@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(); + } + + /** + * @throws java.lang.Exception + */ + @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 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 + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener#onPlayerInteractEntity(org.bukkit.event.player.PlayerInteractEntityEvent)}. + */ + @Test + public void testOnPlayerInteractEntity() { + //TODO + } + +} 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 void testOnMoveOutsideIsland() { 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 void testExitingIslandEmptyIslandName() { 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 void testExitIslandTeleport() { 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)}. diff --git a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java index e6f355dda..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 void setUp() throws Exception { 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,12 +829,39 @@ public void testIsAtSpawn() { /** * 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 } /** @@ -1111,9 +1140,9 @@ public void testClearRank() { 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()); }