Plex-FAWE/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/listener/ChunkListener.java

419 lines
14 KiB
Java

package com.boydti.fawe.bukkit.listener;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.bukkit.FaweBukkit;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.util.FaweTimer;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.TaskManager;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBurnEvent;
import org.bukkit.event.block.BlockCanBuildEvent;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.block.BlockExpEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockFadeEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockGrowEvent;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.BlockRedstoneEvent;
import org.bukkit.event.block.LeavesDecayEvent;
import org.bukkit.event.block.NotePlayEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.inventory.FurnaceBurnEvent;
import org.bukkit.event.inventory.FurnaceSmeltEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.util.Vector;
import org.slf4j.Logger;
import static org.slf4j.LoggerFactory.getLogger;
public abstract class ChunkListener implements Listener {
private final Logger logger = getLogger(ChunkListener.class);
protected int rateLimit = 0;
protected Location lastCancelPos;
private int[] badLimit = new int[]{Settings.IMP.TICK_LIMITER.PHYSICS_MS,
Settings.IMP.TICK_LIMITER.FALLING, Settings.IMP.TICK_LIMITER.ITEMS};
public ChunkListener() {
if (Settings.IMP.TICK_LIMITER.ENABLED) {
PluginManager plm = Bukkit.getPluginManager();
Plugin plugin = Fawe.<FaweBukkit>imp().getPlugin();
plm.registerEvents(this, plugin);
TaskManager.IMP.repeat(() -> {
Location tmpLoc = lastCancelPos;
if (tmpLoc != null) {
logger.debug("[FAWE Tick Limiter] Detected and cancelled physics lag source at "
+ tmpLoc);
}
rateLimit--;
physicsFreeze = false;
itemFreeze = false;
lastZ = Integer.MIN_VALUE;
physSkip = 0;
physCancelPair = Long.MIN_VALUE;
physCancel = false;
lastCancelPos = null;
counter.clear();
for (Long2ObjectMap.Entry<Boolean> entry : badChunks.long2ObjectEntrySet()) {
long key = entry.getLongKey();
int x = MathMan.unpairIntX(key);
int z = MathMan.unpairIntY(key);
counter.put(key, badLimit);
}
badChunks.clear();
}, Settings.IMP.TICK_LIMITER.INTERVAL);
}
}
protected abstract int getDepth(Exception ex);
protected abstract StackTraceElement getElement(Exception ex, int index);
public static boolean physicsFreeze = false;
public static boolean itemFreeze = false;
protected final Long2ObjectOpenHashMap<Boolean> badChunks = new Long2ObjectOpenHashMap<>();
private Long2ObjectOpenHashMap<int[]> counter = new Long2ObjectOpenHashMap<>();
private int lastX = Integer.MIN_VALUE;
private int lastZ = Integer.MIN_VALUE;
private int[] lastCount;
public int[] getCount(int cx, int cz) {
if (lastX == cx && lastZ == cz) {
return lastCount;
}
lastX = cx;
lastZ = cz;
long pair = MathMan.pairInt(cx, cz);
int[] tmp = lastCount = counter.get(pair);
if (tmp == null) {
lastCount = tmp = new int[3];
counter.put(pair, tmp);
}
return tmp;
}
public void cleanup(Chunk chunk) {
for (Entity entity : chunk.getEntities()) {
if (entity.getType() == EntityType.DROPPED_ITEM) {
entity.remove();
}
}
}
protected int physSkip;
protected boolean physCancel;
protected long physCancelPair;
protected long physStart;
protected long physTick;
public final void reset() {
physSkip = 0;
physStart = System.currentTimeMillis();
physCancel = false;
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockExplodeEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockBurnEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockCanBuildEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockDamageEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockDispenseEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockExpEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockFadeEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockFromToEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockGrowEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockIgniteEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockPlaceEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(FurnaceBurnEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(FurnaceSmeltEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(LeavesDecayEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(NotePlayEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(SignChangeEvent event) {
reset();
}
@EventHandler(priority = EventPriority.LOWEST)
public void event(BlockRedstoneEvent event) {
reset();
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPhysics(BlockPhysicsEvent event) {
if (physicsFreeze) {
event.setCancelled(true);
return;
}
if (physCancel) {
Block block = event.getBlock();
long pair = MathMan.pairInt(block.getX() >> 4, block.getZ() >> 4);
if (physCancelPair == pair) {
event.setCancelled(true);
return;
}
if (badChunks.containsKey(pair)) {
physCancelPair = pair;
event.setCancelled(true);
return;
}
} else {
if ((++physSkip & 1023) != 0) {
return;
}
FaweTimer timer = Fawe.get().getTimer();
if (timer.getTick() != physTick) {
physTick = timer.getTick();
physStart = System.currentTimeMillis();
return;
} else if (System.currentTimeMillis() - physStart
< Settings.IMP.TICK_LIMITER.PHYSICS_MS) {
return;
}
}
Exception e = new Exception();
int depth = getDepth(e);
if (depth >= 256) {
if (containsSetAir(e, event)) {
Block block = event.getBlock();
int cx = block.getX() >> 4;
int cz = block.getZ() >> 4;
physCancelPair = MathMan.pairInt(cx, cz);
if (rateLimit <= 0) {
rateLimit = 20;
lastCancelPos = block.getLocation();
}
cancelNearby(cx, cz);
event.setCancelled(true);
physCancel = true;
return;
}
}
physSkip = 1;
physCancel = false;
}
protected boolean containsSetAir(Exception e, BlockPhysicsEvent event) {
for (int frame = 25; frame < 35; frame++) {
StackTraceElement elem = getElement(e, frame);
if (elem != null) {
String methodName = elem.getMethodName();
// setAir | setTypeAndData (hacky, but this needs to be efficient)
if (methodName.charAt(0) == 's' && methodName.length() == 6
|| methodName.length() == 14) {
return true;
}
}
}
return false;
}
protected void cancelNearby(int cx, int cz) {
cancel(cx, cz);
cancel(cx + 1, cz);
cancel(cx - 1, cz);
cancel(cx, cz + 1);
cancel(cx, cz - 1);
cancel(cx - 1, cz - 1);
cancel(cx - 1, cz + 1);
cancel(cx + 1, cz - 1);
cancel(cx + 1, cz + 1);
}
private void cancel(int cx, int cz) {
long key = MathMan.pairInt(cx, cz);
badChunks.put(key, (Boolean) true);
counter.put(key, badLimit);
int[] count = getCount(cx, cz);
count[0] = Integer.MAX_VALUE;
count[1] = Integer.MAX_VALUE;
count[2] = Integer.MAX_VALUE;
}
// Falling
@EventHandler(priority = EventPriority.LOWEST)
public void onBlockChange(EntityChangeBlockEvent event) {
if (physicsFreeze) {
event.setCancelled(true);
return;
}
Block block = event.getBlock();
int x = block.getX();
int z = block.getZ();
int cx = x >> 4;
int cz = z >> 4;
int[] count = getCount(cx, cz);
if (count[1] >= Settings.IMP.TICK_LIMITER.FALLING) {
event.setCancelled(true);
return;
}
if (event.getEntityType() == EntityType.FALLING_BLOCK) {
if (++count[1] >= Settings.IMP.TICK_LIMITER.FALLING) {
// Only cancel falling blocks when it's lagging
if (Fawe.get().getTimer().getTPS() < 18) {
cancelNearby(cx, cz);
if (rateLimit <= 0) {
rateLimit = 20;
lastCancelPos = block.getLocation();
}
event.setCancelled(true);
} else {
count[1] = 0;
}
}
}
}
/**
* Prevent firework from loading chunks.
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onChunkLoad(ChunkLoadEvent event) {
if (!Settings.IMP.TICK_LIMITER.FIREWORKS_LOAD_CHUNKS) {
Chunk chunk = event.getChunk();
Entity[] entities = chunk.getEntities();
World world = chunk.getWorld();
Exception e = new Exception();
int start = 14;
int end = 22;
int depth = Math.min(end, getDepth(e));
for (int frame = start; frame < depth; frame++) {
StackTraceElement elem = getElement(e, frame);
if (elem == null) {
return;
}
String className = elem.getClassName();
int len = className.length();
if (len > 15 && className.charAt(len - 15) == 'E' && className
.endsWith("EntityFireworks")) {
for (Entity ent : world.getEntities()) {
if (ent.getType() == EntityType.FIREWORK) {
Vector velocity = ent.getVelocity();
double vertical = Math.abs(velocity.getY());
if (Math.abs(velocity.getX()) > vertical
|| Math.abs(velocity.getZ()) > vertical) {
logger.warn(
"[FAWE `tick-limiter`] Detected and cancelled rogue FireWork at "
+ ent.getLocation());
ent.remove();
}
}
}
}
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onItemSpawn(ItemSpawnEvent event) {
if (physicsFreeze) {
event.setCancelled(true);
return;
}
Location loc = event.getLocation();
int cx = loc.getBlockX() >> 4;
int cz = loc.getBlockZ() >> 4;
int[] count = getCount(cx, cz);
if (count[2] >= Settings.IMP.TICK_LIMITER.ITEMS) {
event.setCancelled(true);
return;
}
if (++count[2] >= Settings.IMP.TICK_LIMITER.ITEMS) {
cleanup(loc.getChunk());
cancelNearby(cx, cz);
if (rateLimit <= 0) {
rateLimit = 20;
logger.warn(
"[FAWE `tick-limiter`] Detected and cancelled item lag source at " + loc);
}
event.setCancelled(true);
}
}
}