diff --git a/src/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java b/src/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java index 805dbb86b4..85683505cd 100644 --- a/src/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java +++ b/src/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java @@ -459,7 +459,6 @@ public void onEntityInteract(EntityInteractEvent event) { } - @SuppressWarnings("deprecation") /** * Handles: * Enderman thieving protected blocks. @@ -470,16 +469,19 @@ public void onEntityInteract(EntityInteractEvent event) { * * @param event - onEntityChangeBlockEvent */ + @SuppressWarnings("deprecation") @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityChangeBlockEvent(EntityChangeBlockEvent event) { if (plugin.isError()) { event.setCancelled(true); return; } - if (!TownyAPI.getInstance().isTownyWorld(event.getBlock().getWorld())) - return; TownyWorld townyWorld = TownyAPI.getInstance().getTownyWorld(event.getBlock().getWorld().getName()); + + if (townyWorld == null || !townyWorld.isUsingTowny()) + return; + // Crop trampling protection done here. if (event.getBlock().getType().equals(Material.FARMLAND)) { @@ -495,16 +497,14 @@ public void onEntityChangeBlockEvent(EntityChangeBlockEvent event) { } } - // Switch over name instead of EntityType to maintain pre-1.19 compatibility. (For chest_boats.) - switch (event.getEntity().getType().name()) { - - case "ENDERMAN": + switch (event.getEntity().getType()) { + case ENDERMAN -> { if (townyWorld.isEndermanProtect()) event.setCancelled(true); - break; + } /* Protect lily pads. */ - case "BOAT", "CHEST_BOAT": + case BOAT, CHEST_BOAT -> { if (!event.getBlock().getType().equals(Material.LILY_PAD)) return; Boat boat = (Boat) event.getEntity(); @@ -514,25 +514,26 @@ public void onEntityChangeBlockEvent(EntityChangeBlockEvent event) { else if (!TownyAPI.getInstance().isWilderness(event.getBlock())) // Protect townland from non-player-ridden boats. (Maybe someone is pushing a boat?) event.setCancelled(true); - break; + } - case "RAVAGER": + case RAVAGER -> { if (townyWorld.isDisableCreatureTrample()) event.setCancelled(true); - break; + } - case "WITHER": + case WITHER -> { List allowed = TownyActionEventExecutor.filterExplodableBlocks(Collections.singletonList(event.getBlock()), event.getBlock().getType(), event.getEntity(), event); event.setCancelled(allowed.isEmpty()); - break; + } /* Protect campfires from SplashWaterBottles. Uses a destroy test. */ - case "SPLASH_POTION": + case SPLASH_POTION -> { if (event.getBlock().getType() != Material.CAMPFIRE && ((ThrownPotion) event.getEntity()).getShooter() instanceof Player) return; event.setCancelled(!TownyActionEventExecutor.canDestroy((Player) ((ThrownPotion) event.getEntity()).getShooter(), event.getBlock().getLocation(), Material.CAMPFIRE)); - break; - default: + } + + default -> {} } } diff --git a/src/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java b/src/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java index d60534f741..48099b5f25 100644 --- a/src/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java +++ b/src/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java @@ -1,11 +1,15 @@ package com.palmergames.bukkit.towny.listeners; import com.palmergames.bukkit.towny.Towny; +import com.palmergames.bukkit.towny.TownyAPI; +import com.palmergames.bukkit.towny.TownyMessaging; import com.palmergames.bukkit.towny.event.executors.TownyActionEventExecutor; import com.palmergames.bukkit.towny.utils.BorderUtil; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.event.Cancellable; @@ -13,50 +17,111 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockEvent; +import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.projectiles.BlockProjectileSource; +import org.jetbrains.annotations.ApiStatus; +import java.lang.reflect.Method; import java.util.function.Consumer; +import java.util.function.Supplier; +@ApiStatus.Internal public class TownyPaperEvents implements Listener { private final Towny plugin; + private Method getOrigin = null; + private Method getPrimerEntity = null; public TownyPaperEvents(Towny plugin) { this.plugin = plugin; } public void register() { - registerEvent("com.destroystokyo.paper.event.block.TNTPrimeEvent", TNTPrimeEvent, EventPriority.NORMAL, true); + initializeReflections(); + + if (this.getPrimerEntity != null) + registerEvent("com.destroystokyo.paper.event.block.TNTPrimeEvent", this::tntPrimeEvent, EventPriority.LOW, true); + + if (this.getOrigin != null) + registerEvent(EntityChangeBlockEvent.class, fallingBlockListener(), EventPriority.LOW, true); } - private void registerEvent(String className, Consumer executor, EventPriority eventPriority, boolean ignoreCancelled) { + @SuppressWarnings("JavaReflectionMemberAccess") + private void initializeReflections() { try { - Class eventClass = Class.forName(className).asSubclass(Event.class); - Bukkit.getPluginManager().registerEvent(eventClass, this, eventPriority, (listener, event) -> executor.accept(event), plugin, ignoreCancelled); - } catch (Exception ignored) {} + //https://jd.papermc.io/paper/1.19/org/bukkit/entity/Entity.html#getOrigin() + getOrigin = Entity.class.getMethod("getOrigin"); + TownyMessaging.sendDebugMsg("Entity#getOrigin found, using falling block listener."); + } catch (NoSuchMethodException ignored) {} + + try { + // https://jd.papermc.io/paper/1.19/com/destroystokyo/paper/event/block/TNTPrimeEvent.html#getPrimerEntity() + getPrimerEntity = Class.forName("com.destroystokyo.paper.event.block.TNTPrimeEvent").getMethod("getPrimerEntity"); + TownyMessaging.sendDebugMsg("TNTPRimeEvent#getPrimerEntity method found, using TNTPrimeEvent listener."); + } catch (ReflectiveOperationException ignored) {} } - // https://papermc.io/javadocs/paper/1.18/com/destroystokyo/paper/event/block/TNTPrimeEvent.html - private final Consumer TNTPrimeEvent = (event) -> { - Entity primerEntity = null; + @SuppressWarnings("unchecked") + private void registerEvent(String className, Supplier> executor, EventPriority eventPriority, boolean ignoreCancelled) { try { - primerEntity = (Entity) event.getClass().getDeclaredMethod("getPrimerEntity").invoke(event); - } catch (NoSuchMethodException e) { - // Should not happen, unless the getPrimerEntity method is renamed. - e.printStackTrace(); - return; - } catch (Exception ignored) {} + Class eventClass = (Class) Class.forName(className).asSubclass(Event.class); + registerEvent(eventClass, executor.get(), eventPriority, ignoreCancelled); + } catch (ClassNotFoundException ignored) {} + } + + @SuppressWarnings("unchecked") + private void registerEvent(Class eventClass, Consumer consumer, EventPriority eventPriority, boolean ignoreCancelled) { + Bukkit.getPluginManager().registerEvent(eventClass, this, eventPriority, (listener, event) -> consumer.accept((T) event), plugin, ignoreCancelled); + } + + // https://papermc.io/javadocs/paper/1.19/com/destroystokyo/paper/event/block/TNTPrimeEvent.html + private Consumer tntPrimeEvent() { + return event -> { + Entity primerEntity; + try { + primerEntity = (Entity) getPrimerEntity.invoke(event); + } catch (ReflectiveOperationException | IllegalArgumentException e) { + // Should not happen, unless the getPrimerEntity method is renamed. + e.printStackTrace(); + return; + } - if (primerEntity instanceof Projectile projectile) { - Cancellable cancellable = (Cancellable) event; - Block block = ((BlockEvent) event).getBlock(); - if (projectile.getShooter() instanceof Player player) { - // A player shot a flaming arrow at the block, use a regular destroy test. - cancellable.setCancelled(!TownyActionEventExecutor.canDestroy(player, block)); - } else if (projectile.getShooter() instanceof BlockProjectileSource bps) { - // A block (likely a dispenser) shot a flaming arrow, cancel it if plot owners aren't the same. - if (!BorderUtil.allowedMove(bps.getBlock(), block)) - cancellable.setCancelled(true); + if (primerEntity instanceof Projectile projectile) { + Cancellable cancellable = (Cancellable) event; + Block block = ((BlockEvent) event).getBlock(); + if (projectile.getShooter() instanceof Player player) { + // A player shot a flaming arrow at the block, use a regular destroy test. + cancellable.setCancelled(!TownyActionEventExecutor.canDestroy(player, block)); + } else if (projectile.getShooter() instanceof BlockProjectileSource bps) { + // A block (likely a dispenser) shot a flaming arrow, cancel it if plot owners aren't the same. + if (!BorderUtil.allowedMove(bps.getBlock(), block)) + cancellable.setCancelled(true); + } } - } + }; }; + + private Consumer fallingBlockListener() { + return event -> { + if (event.getEntityType() != EntityType.FALLING_BLOCK || !TownyAPI.getInstance().isTownyWorld(event.getEntity().getWorld())) + return; + + Location origin; + try { + origin = (Location) getOrigin.invoke(event.getEntity()); + } catch (ReflectiveOperationException | IllegalArgumentException e) { + e.printStackTrace(); + return; + } + + if (origin == null) + return; + + // If the z and x are the same then don't process allowedMove logic, since it couldn't have crossed a town boundary. + if (origin.getBlockZ() == event.getBlock().getZ() && origin.getBlockX() == event.getBlock().getX()) + return; + + if (!BorderUtil.allowedMove(origin.getBlock(), event.getBlock())) + event.setCancelled(true); + }; + } }