From f67baf6501db9055a373f1bf46b90308dae4051c Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 14 Nov 2019 17:44:56 -0800 Subject: [PATCH] Add protection for firework explosions from crossbows. https://github.com/BentoBoxWorld/BentoBox/issues/1027 --- .../flags/protection/HurtingListener.java | 62 +++++--- .../listeners/flags/settings/PVPListener.java | 48 ++++-- .../flags/protection/HurtingListenerTest.java | 3 - .../flags/settings/PVPListenerTest.java | 147 ++++++++++++++++-- 4 files changed, 209 insertions(+), 51 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java index 61ac70d16..1f16ff55e 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java @@ -1,10 +1,14 @@ package world.bentobox.bentobox.listeners.flags.protection; import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; import org.bukkit.Material; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Firework; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Parrot; import org.bukkit.entity.Player; @@ -14,6 +18,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.LingeringPotionSplashEvent; import org.bukkit.event.entity.PotionSplashEvent; import org.bukkit.event.player.PlayerFishEvent; @@ -35,7 +40,8 @@ import world.bentobox.bentobox.versions.ServerCompatibility; */ public class HurtingListener extends FlagListener { - private HashMap thrownPotions = new HashMap<>(); + private Map thrownPotions = new HashMap<>(); + private Map firedFireworks = new WeakHashMap<>(); /** * Handles mob and monster protection @@ -173,23 +179,43 @@ public class HurtingListener extends FlagListener { public void onLingeringPotionDamage(final EntityDamageByEntityEvent e) { if (e.getCause().equals(DamageCause.ENTITY_ATTACK) && thrownPotions.containsKey(e.getDamager().getEntityId())) { Player attacker = thrownPotions.get(e.getDamager().getEntityId()); - // Self damage - if (attacker == null || attacker.equals(e.getEntity())) { - return; - } - Entity entity = e.getEntity(); - // Monsters being hurt - if (Util.isHostileEntity(entity)) { - checkIsland(e, attacker, entity.getLocation(), Flags.HURT_MONSTERS); - } - // Mobs being hurt - if (Util.isPassiveEntity(entity)) { - checkIsland(e, attacker, entity.getLocation(), Flags.HURT_ANIMALS); - } - // Villagers being hurt - if (entity instanceof Villager) { - checkIsland(e, attacker, entity.getLocation(), Flags.HURT_VILLAGERS); - } + processDamage(e, attacker); + } + } + + private void processDamage(EntityDamageByEntityEvent e, Player attacker) { + // Self damage + if (attacker == null || attacker.equals(e.getEntity())) { + return; + } + Entity entity = e.getEntity(); + // Monsters being hurt + if (Util.isHostileEntity(entity)) { + checkIsland(e, attacker, entity.getLocation(), Flags.HURT_MONSTERS); + } + // Mobs being hurt + if (Util.isPassiveEntity(entity)) { + checkIsland(e, attacker, entity.getLocation(), Flags.HURT_ANIMALS); + } + // Villagers being hurt + if (entity instanceof Villager) { + checkIsland(e, attacker, entity.getLocation(), Flags.HURT_VILLAGERS); + } + + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled=true) + public void onFireworkDamage(final EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Firework && firedFireworks.containsKey(e.getDamager())) { + processDamage(e, (Player)firedFireworks.get(e.getDamager())); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled=true) + public void onPlayerShootEvent(final EntityShootBowEvent e) { + // Only care about players shooting fireworks + if (e.getEntityType().equals(EntityType.PLAYER) && (e.getProjectile() instanceof Firework)) { + firedFireworks.put(e.getProjectile(), e.getEntity()); } } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java index a62fb0c5e..8ce05bb1a 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java @@ -1,11 +1,14 @@ package world.bentobox.bentobox.listeners.flags.settings; import java.util.HashMap; +import java.util.Map; import java.util.UUID; +import java.util.WeakHashMap; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.entity.Entity; +import org.bukkit.entity.Firework; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; @@ -16,6 +19,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.entity.AreaEffectCloudApplyEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.LingeringPotionSplashEvent; import org.bukkit.event.entity.PotionSplashEvent; import org.bukkit.event.player.PlayerFishEvent; @@ -32,7 +36,8 @@ import world.bentobox.bentobox.lists.Flags; */ public class PVPListener extends FlagListener { - private HashMap thrownPotions = new HashMap<>(); + private Map thrownPotions = new HashMap<>(); + private Map firedFireworks = new WeakHashMap<>(); /** * This method protects players from PVP if it is not allowed and from @@ -79,23 +84,31 @@ public class PVPListener extends FlagListener { } else if (damager instanceof Projectile) { // Find out who fired the arrow Projectile p = (Projectile) damager; - Entity entity =(Entity)p.getShooter(); - if (entity instanceof Player) { - // Allow self damage - if (hurtEntity.equals(entity)) { - return; - } - User user = User.getInstance((Player)p.getShooter()); - if (!checkIsland((Event)e, (Player)entity, damager.getLocation(), flag)) { - damager.setFireTicks(0); - hurtEntity.setFireTicks(0); - user.notify(Flags.PVP_OVERWORLD.getHintReference()); - e.setCancelled(true); - } + Entity shooter =(Entity)p.getShooter(); + if (shooter instanceof Player) { + processDamage(e, damager, (Player)shooter, hurtEntity, flag); } + } else if (damager instanceof Firework && firedFireworks.containsKey(damager)) { + Player shooter = firedFireworks.get(damager); + processDamage(e, damager, shooter, hurtEntity, flag); } } + private void processDamage(Cancellable e, Entity damager, Player shooter, Entity hurtEntity, Flag flag) { + // Allow self damage + if (hurtEntity.equals(shooter)) { + return; + } + User user = User.getInstance(shooter); + if (!checkIsland((Event)e, shooter, damager.getLocation(), flag)) { + damager.setFireTicks(0); + hurtEntity.setFireTicks(0); + user.notify(Flags.PVP_OVERWORLD.getHintReference()); + e.setCancelled(true); + } + + } + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onFishing(PlayerFishEvent e) { if (e.getCaught() instanceof Player && getPlugin().getIWM().inWorld(e.getCaught().getLocation())) { @@ -191,4 +204,11 @@ public class PVPListener extends FlagListener { } } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled=true) + public void onPlayerShootFireworkEvent(final EntityShootBowEvent e) { + // Only care about players shooting fireworks + if (e.getEntity() instanceof Player && (e.getProjectile() instanceof Firework)) { + firedFireworks.put(e.getProjectile(), (Player)e.getEntity()); + } + } } diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListenerTest.java index 405ed0912..e6b3e0a22 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListenerTest.java @@ -1,6 +1,3 @@ -/** - * - */ package world.bentobox.bentobox.listeners.flags.protection; import static org.junit.Assert.assertFalse; diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/settings/PVPListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/settings/PVPListenerTest.java index 2257d0314..d671d2e53 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/settings/PVPListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/settings/PVPListenerTest.java @@ -3,13 +3,13 @@ package world.bentobox.bentobox.listeners.flags.settings; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.times; import java.util.ArrayList; import java.util.Collections; @@ -22,10 +22,14 @@ import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.World; import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Arrow; import org.bukkit.entity.Creeper; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Firework; import org.bukkit.entity.FishHook; import org.bukkit.entity.LingeringPotion; import org.bukkit.entity.LivingEntity; @@ -37,7 +41,9 @@ import org.bukkit.entity.Zombie; import org.bukkit.event.entity.AreaEffectCloudApplyEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityDamageEvent.DamageModifier; +import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.LingeringPotionSplashEvent; import org.bukkit.event.entity.PotionSplashEvent; import org.bukkit.event.player.PlayerFishEvent; @@ -49,6 +55,7 @@ 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; @@ -87,15 +94,25 @@ import world.bentobox.bentobox.util.Util; @PrepareForTest({BentoBox.class, Util.class, Bukkit.class }) public class PVPListenerTest { + @Mock private IslandWorldManager iwm; + @Mock private IslandsManager im; + @Mock private Island island; + @Mock private Player player; + @Mock private Player player2; + @Mock private Location loc; + @Mock private Zombie zombie; + @Mock private Creeper creeper; + @Mock private World world; + @Mock private Notifier notifier; /** @@ -109,7 +126,6 @@ public class PVPListenerTest { // Make sure you set the plung for the User class otherwise it'll use an old object User.setPlugin(plugin); // Island World Manager - iwm = mock(IslandWorldManager.class); when(iwm.inWorld(any(World.class))).thenReturn(true); when(iwm.inWorld(any(Location.class))).thenReturn(true); when(iwm.getPermissionPrefix(Mockito.any())).thenReturn("bskyblock."); @@ -120,23 +136,23 @@ public class PVPListenerTest { Panel panel = mock(Panel.class); when(panel.getInventory()).thenReturn(mock(Inventory.class)); - // Sometimes use Mockito.withSettings().verboseLogging() - player = mock(Player.class); - UUID uuid = UUID.randomUUID(); - when(player.getUniqueId()).thenReturn(uuid); - world = mock(World.class); + // World when(world.getEnvironment()).thenReturn(World.Environment.NORMAL); - when(player.getWorld()).thenReturn(world); - loc = mock(Location.class); + // Location when(loc.getWorld()).thenReturn(world); + // Sometimes use Mockito.withSettings().verboseLogging() + // Player + UUID uuid = UUID.randomUUID(); + when(player.getUniqueId()).thenReturn(uuid); when(player.getLocation()).thenReturn(loc); + when(player.getWorld()).thenReturn(world); User.getInstance(player); // Sometimes use Mockito.withSettings().verboseLogging() - player2 = mock(Player.class); + // Player 2 UUID uuid2 = UUID.randomUUID(); when(player2.getUniqueId()).thenReturn(uuid2); @@ -144,9 +160,11 @@ public class PVPListenerTest { when(player2.getLocation()).thenReturn(loc); User.getInstance(player2); + // Util PowerMockito.mockStatic(Util.class); when(Util.getWorld(any())).thenReturn(mock(World.class)); + // Flags Manager FlagsManager fm = mock(FlagsManager.class); Flag flag = mock(Flag.class); when(flag.isSetForWorld(any())).thenReturn(false); @@ -156,7 +174,7 @@ public class PVPListenerTest { when(fm.getFlag(Mockito.anyString())).thenReturn(Optional.of(flag)); when(plugin.getFlagsManager()).thenReturn(fm); - im = mock(IslandsManager.class); + // Island Manager // Default is that player in on their island when(im.userIsOnIsland(any(), any())).thenReturn(true); island = mock(Island.class); @@ -182,12 +200,12 @@ public class PVPListenerTest { when(placeholdersManager.replacePlaceholders(any(), any())).thenAnswer(answer); // Create some entities - zombie = mock(Zombie.class); when(zombie.getWorld()).thenReturn(world); when(zombie.getUniqueId()).thenReturn(UUID.randomUUID()); - creeper = mock(Creeper.class); + when(zombie.getType()).thenReturn(EntityType.ZOMBIE); when(creeper.getWorld()).thenReturn(world); when(creeper.getUniqueId()).thenReturn(UUID.randomUUID()); + when(creeper.getType()).thenReturn(EntityType.CREEPER); // Scheduler BukkitScheduler sch = mock(BukkitScheduler.class); @@ -204,7 +222,6 @@ public class PVPListenerTest { when(iwm.getAddon(any())).thenReturn(opGma); // Notifier - notifier = mock(Notifier.class); when(plugin.getNotifier()).thenReturn(notifier); // Addon @@ -1000,4 +1017,102 @@ public class PVPListenerTest { assertFalse(ae.getAffectedEntities().contains(player2)); verify(notifier).notify(any(), eq(Flags.INVINCIBLE_VISITORS.getHintReference())); } + + /** + * Test method for {@link PVPListener#onPlayerShootFireworkEvent(org.bukkit.event.entity.EntityShootBowEvent)}. + */ + @Test + public void testOnPlayerShootFireworkEventNotPlayer() { + PVPListener listener = new PVPListener(); + ItemStack bow = new ItemStack(Material.CROSSBOW); + Firework firework = mock(Firework.class); + when(firework.getEntityId()).thenReturn(123); + EntityShootBowEvent e = new EntityShootBowEvent(creeper, bow, firework, 0); + listener.onPlayerShootFireworkEvent(e); + + // Now damage + EntityDamageByEntityEvent en = new EntityDamageByEntityEvent(firework, player, DamageCause.ENTITY_ATTACK, 0); + listener.onEntityDamage(en); + assertFalse(en.isCancelled()); + } + + /** + * Test method for {@link PVPListener#onPlayerShootFireworkEvent(org.bukkit.event.entity.EntityShootBowEvent)}. + */ + @Test + public void testOnPlayerShootFireworkEventNotFirework() { + PVPListener listener = new PVPListener(); + ItemStack bow = new ItemStack(Material.CROSSBOW); + Arrow arrow = mock(Arrow.class); + EntityShootBowEvent e = new EntityShootBowEvent(creeper, bow, arrow, 0); + listener.onPlayerShootFireworkEvent(e); + // Now damage + EntityDamageByEntityEvent en = new EntityDamageByEntityEvent(arrow, player, DamageCause.ENTITY_ATTACK, 0); + listener.onEntityDamage(en); + assertFalse(en.isCancelled()); + } + + /** + * Test method for {@link PVPListener#onPlayerShootFireworkEvent(org.bukkit.event.entity.EntityShootBowEvent)}. + */ + @Test + public void testOnPlayerShootFireworkEventNoPVPSelfDamage() { + // Disallow PVP + when(island.isAllowed(any())).thenReturn(false); + PVPListener listener = new PVPListener(); + ItemStack bow = new ItemStack(Material.CROSSBOW); + Firework firework = mock(Firework.class); + when(firework.getEntityId()).thenReturn(123); + when(firework.getLocation()).thenReturn(loc); + EntityShootBowEvent e = new EntityShootBowEvent(player, bow, firework, 0); + listener.onPlayerShootFireworkEvent(e); + + // Now damage + EntityDamageByEntityEvent en = new EntityDamageByEntityEvent(firework, player, DamageCause.ENTITY_EXPLOSION, 0); + listener.onEntityDamage(en); + assertFalse(en.isCancelled()); + } + + /** + * Test method for {@link PVPListener#onPlayerShootFireworkEvent(org.bukkit.event.entity.EntityShootBowEvent)}. + */ + @Test + public void testOnPlayerShootFireworkEventNoPVP() { + // Disallow PVP + when(island.isAllowed(any())).thenReturn(false); + PVPListener listener = new PVPListener(); + ItemStack bow = new ItemStack(Material.CROSSBOW); + Firework firework = mock(Firework.class); + when(firework.getEntityId()).thenReturn(123); + when(firework.getLocation()).thenReturn(loc); + EntityShootBowEvent e = new EntityShootBowEvent(player, bow, firework, 0); + listener.onPlayerShootFireworkEvent(e); + + // Now damage + EntityDamageByEntityEvent en = new EntityDamageByEntityEvent(firework, player2, DamageCause.ENTITY_EXPLOSION, 0); + listener.onEntityDamage(en); + assertTrue(en.isCancelled()); + } + + /** + * Test method for {@link PVPListener#onPlayerShootFireworkEvent(org.bukkit.event.entity.EntityShootBowEvent)}. + */ + @Test + public void testOnPlayerShootFireworkEventPVPAllowed() { + // Allow PVP + when(island.isAllowed(any())).thenReturn(true); + PVPListener listener = new PVPListener(); + ItemStack bow = new ItemStack(Material.CROSSBOW); + Firework firework = mock(Firework.class); + when(firework.getEntityId()).thenReturn(123); + when(firework.getLocation()).thenReturn(loc); + EntityShootBowEvent e = new EntityShootBowEvent(player, bow, firework, 0); + listener.onPlayerShootFireworkEvent(e); + + // Now damage + EntityDamageByEntityEvent en = new EntityDamageByEntityEvent(firework, player2, DamageCause.ENTITY_EXPLOSION, 0); + listener.onEntityDamage(en); + assertFalse(en.isCancelled()); + } + }