419 lines
14 KiB
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);
|
|
}
|
|
}
|
|
}
|