Skip to content

Commit

Permalink
Merge pull request #6314 from TownyAdvanced/feature/fallingblock-prev…
Browse files Browse the repository at this point in the history
…ention

Prevent falling blocks from being launched into towns
  • Loading branch information
LlmDl authored Nov 16, 2022
2 parents 310d13d + 8b02728 commit e5967ef
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 42 deletions.
35 changes: 18 additions & 17 deletions src/com/palmergames/bukkit/towny/listeners/TownyEntityListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,6 @@ public void onEntityInteract(EntityInteractEvent event) {

}

@SuppressWarnings("deprecation")
/**
* Handles:
* Enderman thieving protected blocks.
Expand All @@ -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)) {
Expand All @@ -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();
Expand All @@ -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<Block> 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 -> {}
}
}

Expand Down
115 changes: 90 additions & 25 deletions src/com/palmergames/bukkit/towny/listeners/TownyPaperEvents.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,127 @@
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;
import org.bukkit.event.Event;
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<Event> executor, EventPriority eventPriority, boolean ignoreCancelled) {
@SuppressWarnings("JavaReflectionMemberAccess")
private void initializeReflections() {
try {
Class<? extends Event> 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<Event> TNTPrimeEvent = (event) -> {
Entity primerEntity = null;
@SuppressWarnings("unchecked")
private <T extends Event> void registerEvent(String className, Supplier<Consumer<T>> 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<T> eventClass = (Class<T>) Class.forName(className).asSubclass(Event.class);
registerEvent(eventClass, executor.get(), eventPriority, ignoreCancelled);
} catch (ClassNotFoundException ignored) {}
}

@SuppressWarnings("unchecked")
private <T extends Event> void registerEvent(Class<T> eventClass, Consumer<T> 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<Event> 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<EntityChangeBlockEvent> 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);
};
}
}

0 comments on commit e5967ef

Please sign in to comment.