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 3962e63ac..7e8222cbd 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 @@ -2,7 +2,9 @@ import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Stream; import org.bukkit.Location; import org.bukkit.Material; @@ -17,7 +19,9 @@ 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 org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; @@ -27,13 +31,24 @@ * @author tastybento */ public class ExplosionListener extends FlagListener { + /** + * The Sulfur Cube entity type (Minecraft 26.2), resolved at runtime so the code still + * compiles and runs on older API versions. {@code null} when absent. + */ + private static final EntityType SULFUR_CUBE = findFirstMatchingEnum(EntityType.class, "SULFUR_CUBE"); + /** * Contains {@link EntityType}s that generates an explosion. + * A Sulfur Cube with absorbed TNT explodes as itself, not as primed TNT, so it has to be + * included here for the TNT damage flags to apply. * @since 1.5.0 */ - private static final List TNT_TYPES = List.of( + private static final List TNT_TYPES = Stream.of( findFirstMatchingEnum(EntityType.class, "PRIMED_TNT", "TNT"), - findFirstMatchingEnum(EntityType.class, "MINECART_TNT", "TNT_MINECART")); + findFirstMatchingEnum(EntityType.class, "MINECART_TNT", "TNT_MINECART"), + SULFUR_CUBE) + .filter(Objects::nonNull) + .toList(); private static > T findFirstMatchingEnum(Class enumClass, String... values) { if (enumClass == null || values == null) { @@ -87,6 +102,21 @@ public void onTNTPriming(PlayerInteractEvent e) { } } + /** + * Protect against priming a Sulfur Cube (with absorbed TNT) unless TNT priming is allowed. + * @param e - event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onSulfurCubePriming(PlayerInteractEntityEvent e) { + if (SULFUR_CUBE == null || e.getRightClicked().getType() != SULFUR_CUBE) { + return; + } + ItemStack item = e.getPlayer().getInventory().getItem(e.getHand()); + if (item != null && PRIMING_ITEMS.contains(item.getType())) { + checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.TNT_PRIMING); + } + } + /** * Prevents TNT explosion from breaking blocks * @param e - event 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 6c49f2c55..8258c5eda 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 @@ -33,7 +33,9 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.AfterEach; @@ -164,6 +166,26 @@ void testOnExplosionWrongWorld() { assertFalse(list.isEmpty()); } + @Test + void testOnSulfurCubeExplosion() { + List list = new ArrayList<>(); + list.add(block); + EntityExplodeEvent e = getExplodeEvent(sulfurCube(), location, list); + listener.onExplosion(e); + assertTrue(e.isCancelled()); + } + + @Test + void testOnSulfurCubeExplosionAllowed() { + when(island.isAllowed(Flags.TNT_DAMAGE)).thenReturn(true); + List list = new ArrayList<>(); + list.add(block); + EntityExplodeEvent e = getExplodeEvent(sulfurCube(), location, list); + listener.onExplosion(e); + assertFalse(e.isCancelled()); + assertFalse(list.isEmpty()); + } + @Test void testOnTNTDamageInWorldTNTNotProjectile() { // Block on fire @@ -323,6 +345,23 @@ void testOnEntityExplosion() { assertTrue(e.isCancelled()); } + @Test + void testOnSulfurCubeEntityExplosion() { + EntityDamageByEntityEvent e = new EntityDamageByEntityEvent(sulfurCube(), mockPlayer, + DamageCause.ENTITY_EXPLOSION, null, 20D); + listener.onExplosion(e); + assertTrue(e.isCancelled()); + } + + @Test + void testOnSulfurCubeEntityExplosionAllowed() { + when(island.isAllowed(Flags.TNT_DAMAGE)).thenReturn(true); + EntityDamageByEntityEvent e = new EntityDamageByEntityEvent(sulfurCube(), mockPlayer, + DamageCause.ENTITY_EXPLOSION, null, 20D); + listener.onExplosion(e); + assertFalse(e.isCancelled()); + } + @Test void testOnEntityExplosionOutsideIsland() { Flags.WORLD_TNT_DAMAGE.setDefaultSetting(false); @@ -354,4 +393,40 @@ void testOnEntityExplosionWrongWorld() { assertFalse(e.isCancelled()); } + @Test + void testOnSulfurCubePrimingNotAllowed() { + ItemStack flintAndSteel = new ItemStack(Material.FLINT_AND_STEEL); + when(inv.getItem(EquipmentSlot.HAND)).thenReturn(flintAndSteel); + PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, sulfurCube(), EquipmentSlot.HAND); + listener.onSulfurCubePriming(e); + assertTrue(e.isCancelled()); + } + + @Test + void testOnSulfurCubePrimingAllowed() { + ItemStack flintAndSteel = new ItemStack(Material.FLINT_AND_STEEL); + when(island.isAllowed(any(), eq(Flags.TNT_PRIMING))).thenReturn(true); + when(inv.getItem(EquipmentSlot.HAND)).thenReturn(flintAndSteel); + PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, sulfurCube(), EquipmentSlot.HAND); + listener.onSulfurCubePriming(e); + assertFalse(e.isCancelled()); + } + + @Test + void testOnSulfurCubePrimingNonPrimingItem() { + ItemStack stone = new ItemStack(Material.STONE); + when(inv.getItem(EquipmentSlot.HAND)).thenReturn(stone); + PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, sulfurCube(), EquipmentSlot.HAND); + listener.onSulfurCubePriming(e); + assertFalse(e.isCancelled()); + } + + private Entity sulfurCube() { + Entity sulfurCube = mock(Entity.class); + when(sulfurCube.getType()).thenReturn(EntityType.SULFUR_CUBE); + when(sulfurCube.getWorld()).thenReturn(world); + when(sulfurCube.getLocation()).thenReturn(location); + return sulfurCube; + } + }