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 1d1b43a2e..9f8ec51c1 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 @@ -10,6 +10,7 @@ import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockExplodeEvent; import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; @@ -50,8 +51,8 @@ public class TNTListener extends FlagListener { // Stop TNT from being damaged if it is being caused by a visitor with a flaming arrow if (e.getEntity() instanceof Projectile projectile) { // Find out who fired it - if (projectile.getShooter() instanceof Player && projectile.getFireTicks() > 0 - && !checkIsland(e, (Player)projectile.getShooter(), e.getBlock().getLocation(), Flags.TNT_PRIMING)) { + if (projectile.getShooter() instanceof Player shooter && projectile.getFireTicks() > 0 + && !checkIsland(e, shooter, e.getBlock().getLocation(), Flags.TNT_PRIMING)) { // Remove the arrow projectile.remove(); e.setCancelled(true); @@ -82,12 +83,17 @@ public class TNTListener extends FlagListener { @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onExplosion(final EntityExplodeEvent e) { // Check world and types - if (getIWM().inWorld(e.getLocation()) && 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())); + if (!getIWM().inWorld(e.getLocation()) || !TNT_TYPES.contains(e.getEntityType())) { + return; } + if (protect(e.getLocation())) { + // This is protected as a whole, so just cancel the event + e.setCancelled(true); + } else { + // Remove any blocks from the explosion list if required + e.blockList().removeIf(b -> protect(b.getLocation())); + } } protected boolean protect(Location location) { @@ -109,4 +115,43 @@ public class TNTListener extends FlagListener { e.setCancelled(protect(e.getEntity().getLocation())); } } + + protected boolean protectBlockExplode(Location location) { + return getIslands().getProtectedIslandAt(location).map(i -> !i.isAllowed(Flags.BLOCK_EXPLODE_DAMAGE)) + .orElseGet(() -> !Flags.WORLD_BLOCK_EXPLODE_DAMAGE.isSetForWorld(location.getWorld())); + } + + /** + * Prevents block explosion from breaking blocks + * @param e - event + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onExplosion(final BlockExplodeEvent e) { + // Check world + if (!getIWM().inWorld(e.getBlock().getLocation())) { + return; + } + + if (protectBlockExplode(e.getBlock().getLocation())) { + // This is protected as a whole, so just cancel the event + e.setCancelled(true); + } else { + // Remove any blocks from the explosion list if required + e.blockList().removeIf(b -> protectBlockExplode(b.getLocation())); + } + } + + /** + * Prevents block explosion from damaging entities. + * @param e event + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onExplosion(final EntityDamageEvent e) { + // Check if this in world and a block explosion + if (getIWM().inWorld(e.getEntity().getLocation()) + && e.getCause().equals(EntityDamageEvent.DamageCause.BLOCK_EXPLOSION)) { + // Check if it is disallowed, then cancel it. + e.setCancelled(protectBlockExplode(e.getEntity().getLocation())); + } + } } diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index ec832e32c..33f41f5b1 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -392,6 +392,14 @@ 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 Block Explode from breaking blocks and damaging nearby entities. + * @since 1.19.1 + * @see TNTListener + */ + public static final Flag BLOCK_EXPLODE_DAMAGE = new Flag.Builder("BLOCK_EXPLODE_DAMAGE", Material.TNT_MINECART).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 @@ -401,6 +409,15 @@ public final class Flags { .type(Type.WORLD_SETTING) .build(); + /** + * If {@code false}, prevents Block Explode from breaking blocks and damaging nearby entities outside of island boundaries. + * @since 1.19.1 + * @see TNTListener + */ + public static final Flag WORLD_BLOCK_EXPLODE_DAMAGE = new Flag.Builder("WORLD_BLOCK_EXPLODE_DAMAGE", Material.TNT_MINECART) + .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 d5e437e12..40726d177 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -815,6 +815,12 @@ protection: name: "Barrels" description: "Toggle barrel interaction" hint: "Barrel access disabled" + BLOCK_EXPLODE_DAMAGE: + description: |- + &a Allow Bed & Respawn Anchors + &a to break blocks and damage + &a entities. + name: "Block explode damage" COMPOSTER: name: "Composters" description: "Toggle composter interaction" @@ -1320,6 +1326,12 @@ protection: description: |- &a If active, withers can &a damage blocks and players + WORLD_BLOCK_EXPLODE_DAMAGE: + description: |- + &a Allow Bed & Respawn Anchors + &a to break blocks and damage + &a entities outside of island limits. + name: "World block explode damage" WORLD_TNT_DAMAGE: description: |- &a Allow TNT and TNT minecarts