From 1d4fd435a9ba3c64a77193f19612aba547d3eae1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 8 Nov 2020 10:43:15 -0800 Subject: [PATCH] Added World TNT Damage Flag Enables admins to decide whether TNT should damage outside of island boundaries https://github.com/BentoBoxWorld/BentoBox/issues/1562 --- .../commands/admin/AdminSettingsCommand.java | 2 + .../flags/protection/TNTListener.java | 40 ++- .../world/bentobox/bentobox/lists/Flags.java | 9 + src/main/resources/locales/en-US.yml | 6 + .../flags/protection/TNTListenerTest.java | 327 +++++++++++------- 5 files changed, 241 insertions(+), 143 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java index 67bd4d164..8c8f2dad8 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java @@ -7,6 +7,7 @@ import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.flags.Flag; +import world.bentobox.bentobox.api.flags.Flag.Mode; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder; import world.bentobox.bentobox.api.user.User; @@ -62,6 +63,7 @@ public class AdminSettingsCommand extends CompositeCommand { @Override public boolean execute(User user, String label, List args) { + getPlayers().setFlagsDisplayMode(user.getUniqueId(), Mode.EXPERT); if (args.isEmpty()) { new TabbedPanelBuilder() .user(user) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java index 145d6497f..b91deeae2 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java @@ -1,8 +1,10 @@ package world.bentobox.bentobox.listeners.flags.protection; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; @@ -16,6 +18,7 @@ import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.player.PlayerInteractEvent; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; @@ -29,23 +32,23 @@ public class TNTListener extends FlagListener { * Contains {@link EntityType}s that generates an explosion. * @since 1.5.0 */ - private List tntTypes = Arrays.asList(EntityType.PRIMED_TNT, EntityType.MINECART_TNT); + private static final List TNT_TYPES = Collections.unmodifiableList(Arrays.asList(EntityType.PRIMED_TNT, EntityType.MINECART_TNT)); /** * Contains {@link Material}s that can be used to prime a TNT. * @since 1.5.0 */ - private List primingItems = Arrays.asList(Material.FLINT_AND_STEEL, Material.FIRE_CHARGE); + private static final List PRIMING_ITEMS = Collections.unmodifiableList(Arrays.asList(Material.FLINT_AND_STEEL, Material.FIRE_CHARGE)); /** * Protect TNT from being set light by a fire arrow * @param e - event */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onTNTDamage(EntityChangeBlockEvent e) { + public boolean onTNTDamage(EntityChangeBlockEvent e) { // Check world if (!e.getBlock().getType().equals(Material.TNT) || !getIWM().inWorld(e.getBlock().getLocation())) { - return; + return false; } // Stop TNT from being damaged if it is being caused by a visitor with a flaming arrow if (e.getEntity() instanceof Projectile) { @@ -56,8 +59,10 @@ public class TNTListener extends FlagListener { // Remove the arrow projectile.remove(); e.setCancelled(true); + return true; } } + return false; } /** @@ -69,7 +74,7 @@ public class TNTListener extends FlagListener { if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.TNT) - && primingItems.contains(e.getMaterial())) { + && PRIMING_ITEMS.contains(e.getMaterial())) { checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.TNT_PRIMING); } } @@ -80,12 +85,18 @@ public class TNTListener extends FlagListener { */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onExplosion(final EntityExplodeEvent e) { - // Remove any blocks from the explosion list if they are inside a protected area - if (tntTypes.contains(e.getEntityType()) - && e.blockList().removeIf(b -> getIslands().getProtectedIslandAt(b.getLocation()).map(i -> !i.isAllowed(Flags.TNT_DAMAGE)).orElse(false))) { - // If any were removed - e.setCancelled(true); // Seems to have no effect. + if (TNT_TYPES.contains(e.getEntityType())) { + // Remove any blocks from the explosion list if required + e.blockList().removeIf(b -> protect(b.getLocation())); + e.setCancelled(protect(e.getLocation())); + BentoBox.getInstance().logDebug(e.getEventName() + " " + e.isCancelled()); } + + } + + protected boolean protect(Location location) { + return getIslands().getProtectedIslandAt(location).map(i -> !i.isAllowed(Flags.TNT_DAMAGE)) + .orElse(!Flags.WORLD_TNT_DAMAGE.isSetForWorld(location.getWorld())); } /** @@ -95,13 +106,12 @@ public class TNTListener extends FlagListener { @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onExplosion(final EntityDamageByEntityEvent e) { // Check if this a TNT exploding - if (!e.getCause().equals(EntityDamageEvent.DamageCause.ENTITY_EXPLOSION) || !tntTypes.contains(e.getDamager().getType())) { + if (!e.getCause().equals(EntityDamageEvent.DamageCause.ENTITY_EXPLOSION) || !TNT_TYPES.contains(e.getDamager().getType())) { return; } - // Check if it is disallowed, then cancel it. - if(getIslands().getProtectedIslandAt(e.getEntity().getLocation()).map(i -> !i.isAllowed(Flags.TNT_DAMAGE)).orElse(false)) { - e.setCancelled(true); - } + e.setCancelled(protect(e.getEntity().getLocation())); + + BentoBox.getInstance().logDebug(e.getEventName() + " " + e.isCancelled()); } } diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index e0e272caa..f9aab8cd8 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -378,6 +378,15 @@ public final class Flags { public static final Flag TNT_DAMAGE = new Flag.Builder("TNT_DAMAGE", Material.TNT).type(Type.SETTING) .mode(Flag.Mode.ADVANCED).build(); + /** + * If {@code false}, prevents TNT from breaking blocks and damaging nearby entities outside of island boundaries. + * @since 1.15.3 + * @see TNTListener + */ + public static final Flag WORLD_TNT_DAMAGE = new Flag.Builder("WORLD_TNT_DAMAGE", Material.TNT) + .type(Type.WORLD_SETTING) + .build(); + /* * World Settings - they apply to every island in the game worlds. */ diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 073e2fa67..7a44ced2b 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1241,6 +1241,12 @@ protection: description: |- &a If active, withers can &a damage blocks and players + WORLD_TNT_DAMAGE: + description: |- + &a Allow TNT and TNT minecarts + &a to break blocks and damage + &a entities outside of island limits. + name: "World TNT damage" locked: "&c This island is locked!" protected: "&c Island protected: [description]." world-protected: "&c World protected: [description]." diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/TNTListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/TNTListenerTest.java index 0300c1587..b01a7c206 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/TNTListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/TNTListenerTest.java @@ -4,7 +4,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; 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.ArrayList; @@ -14,12 +17,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.logging.Logger; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.Server; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -36,14 +37,12 @@ import org.bukkit.event.block.Action; import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.SkullMeta; -import org.bukkit.plugin.PluginManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; @@ -67,51 +66,49 @@ import world.bentobox.bentobox.managers.PlayersManager; import world.bentobox.bentobox.util.Util; @RunWith(PowerMockRunner.class) -@PrepareForTest( {BentoBox.class, Flags.class, Util.class, Bukkit.class} ) +@PrepareForTest( {BentoBox.class, Util.class, Bukkit.class} ) public class TNTListenerTest { + @Mock private Location location; + @Mock private BentoBox plugin; + @Mock private Notifier notifier; + @Mock + private Block block; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private Player player; + @Mock + private IslandWorldManager iwm; + @Mock + private World world; + @Mock + private Entity entity; + + private Map worldFlags; + // Class under test + private TNTListener listener; @Before public void setUp() { // Set up plugin - plugin = mock(BentoBox.class); Whitebox.setInternalState(BentoBox.class, "instance", plugin); - Server server = mock(Server.class); - World world = mock(World.class); - when(server.getLogger()).thenReturn(Logger.getAnonymousLogger()); - when(server.getWorld("world")).thenReturn(world); - when(server.getVersion()).thenReturn("BSB_Mocking"); - - PluginManager pim = mock(PluginManager.class); - - ItemFactory itemFactory = mock(ItemFactory.class); - when(server.getItemFactory()).thenReturn(itemFactory); - - PowerMockito.mockStatic(Bukkit.class); - when(Bukkit.getServer()).thenReturn(server); - when(Bukkit.getPluginManager()).thenReturn(pim); - - SkullMeta skullMeta = mock(SkullMeta.class); - when(itemFactory.getItemMeta(any())).thenReturn(skullMeta); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); - location = mock(Location.class); + PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); when(location.getWorld()).thenReturn(world); when(location.getBlockX()).thenReturn(0); when(location.getBlockY()).thenReturn(0); when(location.getBlockZ()).thenReturn(0); - PowerMockito.mockStatic(Flags.class); FlagsManager flagsManager = new FlagsManager(plugin); when(plugin.getFlagsManager()).thenReturn(flagsManager); - // Worlds - IslandWorldManager iwm = mock(IslandWorldManager.class); when(iwm.inWorld(any(World.class))).thenReturn(true); when(iwm.inWorld(any(Location.class))).thenReturn(true); when(plugin.getIWM()).thenReturn(iwm); @@ -130,13 +127,10 @@ public class TNTListenerTest { Mockito.when(settings.getFakePlayers()).thenReturn(new HashSet<>()); // Users - //User user = mock(User.class); - ///user.setPlugin(plugin); User.setPlugin(plugin); // Locales - final - LocalesManager lm = mock(LocalesManager.class); when(plugin.getLocalesManager()).thenReturn(lm); Answer answer = invocation -> (String)Arrays.asList(invocation.getArguments()).get(1); @@ -149,34 +143,44 @@ public class TNTListenerTest { // Player name PlayersManager pm = mock(PlayersManager.class); - when(pm.getName(Mockito.any())).thenReturn("tastybento"); + when(pm.getName(any())).thenReturn("tastybento"); when(plugin.getPlayers()).thenReturn(pm); // World Settings WorldSettings ws = mock(WorldSettings.class); - when(iwm.getWorldSettings(Mockito.any())).thenReturn(ws); - Map worldFlags = new HashMap<>(); + when(iwm.getWorldSettings(any())).thenReturn(ws); + worldFlags = new HashMap<>(); when(ws.getWorldFlags()).thenReturn(worldFlags); // Island manager - IslandsManager im = mock(IslandsManager.class); when(plugin.getIslands()).thenReturn(im); - Island island = mock(Island.class); Optional optional = Optional.of(island); - when(im.getProtectedIslandAt(Mockito.any())).thenReturn(optional); + when(im.getProtectedIslandAt(any())).thenReturn(optional); // Notifier - notifier = mock(Notifier.class); when(plugin.getNotifier()).thenReturn(notifier); PowerMockito.mockStatic(Util.class); - when(Util.getWorld(Mockito.any())).thenReturn(mock(World.class)); + when(Util.getWorld(any())).thenReturn(world); // Addon - when(iwm.getAddon(Mockito.any())).thenReturn(Optional.empty()); + when(iwm.getAddon(any())).thenReturn(Optional.empty()); // Util strip spaces when(Util.stripSpaceAfterColorCodes(anyString())).thenCallRealMethod(); + + // Block + when(block.getLocation()).thenReturn(location); + when(block.getWorld()).thenReturn(world); + + // Entity + when(entity.getType()).thenReturn(EntityType.PRIMED_TNT); + when(entity.getWorld()).thenReturn(world); + + + listener = new TNTListener(); + listener.setPlugin(plugin); + } @After @@ -196,8 +200,6 @@ public class TNTListenerTest { Player player = mock(Player.class); PlayerInteractEvent e = new PlayerInteractEvent(player , action, item, clickedBlock, clickedFace); - TNTListener listener = new TNTListener(); - listener.setPlugin(plugin); listener.onTNTPriming(e); assertTrue(e.useInteractedBlock().equals(Result.DENY)); Mockito.verify(notifier).notify(Mockito.any(), Mockito.eq("protection.protected")); @@ -205,118 +207,187 @@ public class TNTListenerTest { @Test public void testOnExplosion() { - Entity entity = mock(Entity.class); - when(entity.getType()).thenReturn(EntityType.PRIMED_TNT); List list = new ArrayList<>(); - Block block = mock(Block.class); - when(block.getLocation()).thenReturn(location); list.add(block); EntityExplodeEvent e = new EntityExplodeEvent(entity, location, list, 0); - TNTListener listener = new TNTListener(); - listener.setPlugin(plugin); listener.onExplosion(e); - assertTrue(e.isCancelled()); assertTrue(list.isEmpty()); } @Test - public void testOnTNTDamage() { - // Notifier - Notifier notifier = mock(Notifier.class); - when(plugin.getNotifier()).thenReturn(notifier); + public void testOnExplosionOutsideIsland() { + Flags.WORLD_TNT_DAMAGE.setDefaultSetting(false); + assertFalse(Flags.WORLD_TNT_DAMAGE.isSetForWorld(world)); + when(im.getProtectedIslandAt(any())).thenReturn(Optional.empty()); + List list = new ArrayList<>(); + list.add(block); + EntityExplodeEvent e = new EntityExplodeEvent(entity, location, list, 0); + listener.onExplosion(e); + assertTrue(list.isEmpty()); + } - // Island - IslandsManager im = mock(IslandsManager.class); - when(plugin.getIslands()).thenReturn(im); - Island island = mock(Island.class); - when(im.getIslandAt(any())).thenReturn(Optional.of(island)); - when(im.getProtectedIslandAt(Mockito.any())).thenReturn(Optional.of(island)); + @Test + public void testOnExplosionOutsideIslandAllowed() { + Flags.WORLD_TNT_DAMAGE.setDefaultSetting(true); + assertTrue(Flags.WORLD_TNT_DAMAGE.isSetForWorld(world)); + when(im.getProtectedIslandAt(any())).thenReturn(Optional.empty()); + List list = new ArrayList<>(); + list.add(block); + EntityExplodeEvent e = new EntityExplodeEvent(entity, location, list, 0); + listener.onExplosion(e); + assertFalse(list.isEmpty()); + } + @Test + public void testOnTNTDamageInWorldTNTNotProjectile() { // Block on fire - Block block = mock(Block.class); - when(block.getLocation()).thenReturn(location); - when(block.getType()).thenReturn(Material.OBSIDIAN); - EntityChangeBlockEvent e = mock(EntityChangeBlockEvent.class); - when(e.getBlock()).thenReturn(block); - - - // TNT listener - TNTListener listener = new TNTListener(); - listener.setPlugin(plugin); - - // Obsidian is not TNT - listener.onTNTDamage(e); - assertFalse(e.isCancelled()); - // Out of world - when(block.getLocation()).thenReturn(null); - listener.onTNTDamage(e); - assertFalse(e.isCancelled()); - - // Now set to TNT when(block.getType()).thenReturn(Material.TNT); - listener.onTNTDamage(e); - assertFalse(e.isCancelled()); - - // Back in world - when(block.getLocation()).thenReturn(location); - // Entity is not a projectile - Player player = mock(Player.class); - when(e.getEntity()).thenReturn(player); - listener.onTNTDamage(e); + EntityChangeBlockEvent e = new EntityChangeBlockEvent(player, block, Material.AIR.createBlockData()); + assertFalse(listener.onTNTDamage(e)); assertFalse(e.isCancelled()); + } + @Test + public void testOnTNTDamageTNTWrongWorld() { + // Block on fire + when(block.getType()).thenReturn(Material.TNT); + // Out of world + when(iwm.inWorld(any(Location.class))).thenReturn(false); + EntityChangeBlockEvent e = new EntityChangeBlockEvent(player, block, Material.AIR.createBlockData()); + assertFalse(listener.onTNTDamage(e)); + assertFalse(e.isCancelled()); + } + @Test + public void testOnTNTDamageObsidianWrongWorld() { + // Block on fire + when(block.getType()).thenReturn(Material.OBSIDIAN); + // Out of world + when(iwm.inWorld(any(Location.class))).thenReturn(false); + EntityChangeBlockEvent e = new EntityChangeBlockEvent(player, block, Material.AIR.createBlockData()); + assertFalse(listener.onTNTDamage(e)); + assertFalse(e.isCancelled()); + } + + @Test + public void testOnTNTDamageInWorldTNTProjectileWitherSkelly() { + // Block on fire + when(block.getType()).thenReturn(Material.TNT); + // Entity is a projectile // Entity is an arrow Arrow arrow = mock(Arrow.class); // Shooter is a skeleton WitherSkeleton skeleton = mock(WitherSkeleton.class); when(arrow.getShooter()).thenReturn(skeleton); - // No fire arrow - when(arrow.getFireTicks()).thenReturn(0); - when(e.getEntity()).thenReturn(arrow); - listener.onTNTDamage(e); - assertFalse(e.isCancelled()); // Fire arrow when(arrow.getFireTicks()).thenReturn(10); - listener.onTNTDamage(e); - assertFalse(e.isCancelled()); + EntityChangeBlockEvent e = new EntityChangeBlockEvent(arrow, block, Material.AIR.createBlockData()); + assertFalse(listener.onTNTDamage(e)); + assertFalse(e.isCancelled()); + verify(arrow, never()).remove(); + } + + @Test + public void testOnTNTDamageInWorldTNTProjectilePlayerNotFireArrow() { + // Block on fire + when(block.getType()).thenReturn(Material.TNT); + // Entity is a projectile + // Entity is an arrow + Arrow arrow = mock(Arrow.class); // Shooter is a player when(arrow.getShooter()).thenReturn(player); - // No fire arrow + // Not fire arrow when(arrow.getFireTicks()).thenReturn(0); - when(e.getEntity()).thenReturn(arrow); - listener.onTNTDamage(e); + + EntityChangeBlockEvent e = new EntityChangeBlockEvent(arrow, block, Material.AIR.createBlockData()); + assertFalse(listener.onTNTDamage(e)); assertFalse(e.isCancelled()); - - // Fire arrow - when(arrow.getFireTicks()).thenReturn(10); - - /* - // Break blocks not allowed, general flag should have no effect - when(island.isAllowed(Mockito.any(), Mockito.any())).thenReturn(false); - Flags.BREAK_BLOCKS.setDefaultSetting(false); - assertTrue(listener.onTNTDamage(e)); - Flags.BREAK_BLOCKS.setDefaultSetting(true); - assertTrue(listener.onTNTDamage(e)); - - // Allow BREAK_BLOCKS spread - when(island.isAllowed(Mockito.any(), Mockito.any())).thenReturn(true); - Flags.BREAK_BLOCKS.setDefaultSetting(false); - assertFalse(listener.onTNTDamage(e)); - Flags.BREAK_BLOCKS.setDefaultSetting(true); - assertFalse(listener.onTNTDamage(e)); - - // Check with no island - when(im.getProtectedIslandAt(Matchers.any())).thenReturn(Optional.empty()); - // BREAK_BLOCKS spread is not allowed, so should be cancelled - Flags.BREAK_BLOCKS.setDefaultSetting(false); - assertTrue(listener.onTNTDamage(e)); - // BREAK_BLOCKS allowed - Flags.BREAK_BLOCKS.setDefaultSetting(true); - assertFalse(listener.onTNTDamage(e)); - */ + verify(arrow, never()).remove(); } + @Test + public void testOnTNTDamageInWorldTNTProjectilePlayerFireArrow() { + // Block on fire + when(block.getType()).thenReturn(Material.TNT); + // Entity is a projectile + // Entity is an arrow + Arrow arrow = mock(Arrow.class); + // Shooter is a player + when(arrow.getShooter()).thenReturn(player); + // Fire arrow + when(arrow.getFireTicks()).thenReturn(10); + + EntityChangeBlockEvent e = new EntityChangeBlockEvent(arrow, block, Material.AIR.createBlockData()); + assertTrue(listener.onTNTDamage(e)); + assertTrue(e.isCancelled()); + verify(arrow).remove(); + + } + + @Test + public void testOnTNTDamageInWorldTNTProjectilePlayerFireArrowAllowed() { + // Block on fire + when(block.getType()).thenReturn(Material.TNT); + // Entity is a projectile + // Entity is an arrow + Arrow arrow = mock(Arrow.class); + // Shooter is a player + when(arrow.getShooter()).thenReturn(player); + // Fire arrow + when(arrow.getFireTicks()).thenReturn(10); + // Allowed on island + when(island.isAllowed(any(), eq(Flags.TNT_PRIMING))).thenReturn(true); + + EntityChangeBlockEvent e = new EntityChangeBlockEvent(arrow, block, Material.AIR.createBlockData()); + assertFalse(listener.onTNTDamage(e)); + assertFalse(e.isCancelled()); + verify(arrow, never()).remove(); + + } + + @Test + public void testOnTNTDamageInWorldTNTProjectilePlayerFireArrowNotIsland() { + Flags.TNT_PRIMING.setSetting(world, false); + when(im.getProtectedIslandAt(any())).thenReturn(Optional.empty()); + // Block on fire + when(block.getType()).thenReturn(Material.TNT); + // Entity is a projectile + // Entity is an arrow + Arrow arrow = mock(Arrow.class); + // Shooter is a player + when(arrow.getShooter()).thenReturn(player); + // Fire arrow + when(arrow.getFireTicks()).thenReturn(10); + + EntityChangeBlockEvent e = new EntityChangeBlockEvent(arrow, block, Material.AIR.createBlockData()); + assertTrue(listener.onTNTDamage(e)); + assertTrue(e.isCancelled()); + verify(arrow).remove(); + + } + + @Test + public void testOnTNTDamageInWorldTNTProjectilePlayerFireArrowNotIslandNotAllowed() { + Flags.TNT_PRIMING.setSetting(world, true); + when(im.getProtectedIslandAt(any())).thenReturn(Optional.empty()); + // Block on fire + when(block.getType()).thenReturn(Material.TNT); + // Entity is a projectile + // Entity is an arrow + Arrow arrow = mock(Arrow.class); + // Shooter is a player + when(arrow.getShooter()).thenReturn(player); + // Fire arrow + when(arrow.getFireTicks()).thenReturn(10); + + EntityChangeBlockEvent e = new EntityChangeBlockEvent(arrow, block, Material.AIR.createBlockData()); + assertFalse(listener.onTNTDamage(e)); + assertFalse(e.isCancelled()); + verify(arrow, never()).remove(); + + } + + }