Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent falling blocks from being launched into towns #6314

Merged
merged 1 commit into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
};
}
}