From b31f47614964e453c605d038842a7dac355497d4 Mon Sep 17 00:00:00 2001 From: Evenprime Date: Sat, 19 Feb 2011 19:47:57 +0100 Subject: [PATCH] Partly commit of reorganization of plugin structure. --- plugin.yml | 2 +- .../evenprime/bukkit/nocheat/MovingCheck.java | 362 +++++++++++++++++ .../bukkit/nocheat/NoCheatConfiguration.java | 12 +- .../nocheat/NoCheatPluginPlayerListener.java | 363 +----------------- .../bukkit/nocheat/SpeedhackCheck.java | 44 +++ 5 files changed, 431 insertions(+), 352 deletions(-) create mode 100644 src/cc/co/evenprime/bukkit/nocheat/MovingCheck.java create mode 100644 src/cc/co/evenprime/bukkit/nocheat/SpeedhackCheck.java diff --git a/plugin.yml b/plugin.yml index a289a39e..8341e0e1 100644 --- a/plugin.yml +++ b/plugin.yml @@ -3,5 +3,5 @@ name: NoCheatPlugin author: Evenprime main: cc.co.evenprime.bukkit.nocheat.NoCheatPlugin -version: 0.5.1 +version: 0.5.1a diff --git a/src/cc/co/evenprime/bukkit/nocheat/MovingCheck.java b/src/cc/co/evenprime/bukkit/nocheat/MovingCheck.java new file mode 100644 index 00000000..56618d19 --- /dev/null +++ b/src/cc/co/evenprime/bukkit/nocheat/MovingCheck.java @@ -0,0 +1,362 @@ +package cc.co.evenprime.bukkit.nocheat; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.event.player.PlayerMoveEvent; + +import cc.co.evenprime.bukkit.nocheat.NoCheatPluginPlayerListener.NoCheatPluginData; + +public class MovingCheck { + + // previously-calculated upper bound values for jumps. Minecraft is very deterministic when it comes to jumps + // Each entry represents the maximum gain in height per move event. + private static double jumpingPhases[] = new double[]{ 0.501D, 0.34D, 0.26D, 0.17D, 0.09D, 0.02D, 0.00D, -0.07D, -0.15D, -0.22D, -0.29D, -0.36D, -0.43D, -0.50D }; + + + public enum BlockType { + SOLID, NONSOLID, LADDER, LIQUID, UNKNOWN; + } + + // Until I can think of a better way to determine if a block is solid or not, this is what I'll do + private static BlockType types[] = new BlockType[256]; + static { + + for(int i = 0; i < types.length; i++) { + types[i] = BlockType.UNKNOWN; + } + + types[Material.AIR.getId()] = BlockType.NONSOLID; + types[Material.STONE.getId()] = BlockType.SOLID; + types[Material.GRASS.getId()] = BlockType.SOLID; + types[Material.DIRT.getId()] = BlockType.SOLID; + types[Material.COBBLESTONE.getId()] = BlockType.SOLID; + types[Material.WOOD.getId()] = BlockType.SOLID; + types[Material.SAPLING.getId()] = BlockType.NONSOLID; + types[Material.BEDROCK.getId()] = BlockType.SOLID; + types[Material.WATER.getId()] = BlockType.LIQUID; + types[Material.STATIONARY_WATER.getId()] = BlockType.LIQUID; + types[Material.LAVA.getId()] = BlockType.LIQUID; + types[Material.STATIONARY_LAVA.getId()] = BlockType.LIQUID; + types[Material.SAND.getId()] = BlockType.SOLID; + types[Material.GRAVEL.getId()] = BlockType.SOLID; + types[Material.GOLD_ORE.getId()] = BlockType.SOLID; + types[Material.IRON_ORE.getId()] = BlockType.SOLID; + types[Material.COAL_ORE.getId()] = BlockType.SOLID; + types[Material.LOG.getId()] = BlockType.SOLID; + types[Material.LEAVES.getId()] = BlockType.SOLID; + types[Material.SPONGE.getId()] = BlockType.SOLID; + types[Material.GLASS.getId()] = BlockType.SOLID; + types[Material.LAPIS_ORE.getId()] = BlockType.SOLID; + types[Material.LAPIS_BLOCK.getId()] = BlockType.SOLID; + types[Material.DISPENSER.getId()] = BlockType.SOLID; + types[Material.SANDSTONE.getId()] = BlockType.SOLID; + types[Material.NOTE_BLOCK.getId()]= BlockType.SOLID; + types[Material.WOOL.getId()]= BlockType.SOLID; + types[Material.YELLOW_FLOWER.getId()]= BlockType.NONSOLID; + types[Material.RED_ROSE.getId()]= BlockType.NONSOLID; + types[Material.BROWN_MUSHROOM.getId()]= BlockType.NONSOLID; + types[Material.RED_MUSHROOM.getId()]= BlockType.NONSOLID; + types[Material.GOLD_BLOCK.getId()]= BlockType.SOLID; + types[Material.IRON_BLOCK.getId()]= BlockType.SOLID; + types[Material.DOUBLE_STEP.getId()]= BlockType.UNKNOWN; + types[Material.STEP.getId()]= BlockType.UNKNOWN; + types[Material.BRICK.getId()]= BlockType.SOLID; + types[Material.TNT.getId()]= BlockType.SOLID; + types[Material.BOOKSHELF.getId()]= BlockType.SOLID; + types[Material.MOSSY_COBBLESTONE.getId()] = BlockType.SOLID; + types[Material.OBSIDIAN.getId()]= BlockType.SOLID; + types[Material.TORCH.getId()]= BlockType.NONSOLID; + types[Material.FIRE.getId()]= BlockType.NONSOLID; + types[Material.MOB_SPAWNER.getId()]= BlockType.SOLID; + types[Material.WOOD_STAIRS.getId()]= BlockType.UNKNOWN; + types[Material.CHEST.getId()]= BlockType.SOLID; + types[Material.REDSTONE_WIRE.getId()]= BlockType.NONSOLID; + types[Material.DIAMOND_ORE.getId()]= BlockType.SOLID; + types[Material.DIAMOND_BLOCK.getId()]= BlockType.SOLID; + types[Material.WORKBENCH.getId()]= BlockType.SOLID; + types[Material.CROPS.getId()]= BlockType.NONSOLID; + types[Material.SOIL.getId()]= BlockType.SOLID; + types[Material.FURNACE.getId()]= BlockType.SOLID; + types[Material.BURNING_FURNACE.getId()]= BlockType.SOLID; + types[Material.SIGN_POST.getId()]= BlockType.NONSOLID; + types[Material.WOODEN_DOOR.getId()]= BlockType.NONSOLID; + types[Material.LADDER.getId()]= BlockType.LADDER; + types[Material.RAILS.getId()]= BlockType.NONSOLID; + types[Material.COBBLESTONE_STAIRS.getId()]= BlockType.UNKNOWN; + types[Material.WALL_SIGN.getId()]= BlockType.NONSOLID; + types[Material.LEVER.getId()]= BlockType.NONSOLID; + types[Material.STONE_PLATE.getId()]= BlockType.UNKNOWN; + types[Material.IRON_DOOR_BLOCK.getId()]= BlockType.NONSOLID; + types[Material.WOOD_PLATE.getId()]= BlockType.NONSOLID; + types[Material.REDSTONE_ORE.getId()]= BlockType.SOLID; + types[Material.GLOWING_REDSTONE_ORE.getId()]= BlockType.SOLID; + types[Material.REDSTONE_TORCH_OFF.getId()]= BlockType.NONSOLID; + types[Material.REDSTONE_TORCH_ON.getId()]= BlockType.NONSOLID; + types[Material.STONE_BUTTON.getId()]= BlockType.NONSOLID; + types[Material.SNOW.getId()]= BlockType.UNKNOWN; + types[Material.ICE.getId()]= BlockType.UNKNOWN; + types[Material.SNOW_BLOCK.getId()]= BlockType.SOLID; + types[Material.CACTUS.getId()]= BlockType.SOLID; + types[Material.CLAY.getId()]= BlockType.SOLID; + types[Material.SUGAR_CANE_BLOCK.getId()]= BlockType.NONSOLID; + types[Material.JUKEBOX.getId()]= BlockType.SOLID; + types[Material.FENCE.getId()]= BlockType.UNKNOWN; + types[Material.PUMPKIN.getId()]= BlockType.SOLID; + types[Material.NETHERRACK.getId()]= BlockType.SOLID; + types[Material.SOUL_SAND.getId()]= BlockType.UNKNOWN; + types[Material.GLOWSTONE.getId()]= BlockType.SOLID; + types[Material.PORTAL.getId()]= BlockType.NONSOLID; + types[Material.JACK_O_LANTERN.getId()]= BlockType.SOLID; + types[Material.CAKE_BLOCK.getId()]= BlockType.UNKNOWN; + } + + public static void check(NoCheatPluginData data, PlayerMoveEvent event) { + + // Get the two locations of the event + Location from = event.getFrom(); + Location to = event.getTo(); + + if(NoCheatPlugin.Permissions != null && NoCheatPlugin.Permissions.has(event.getPlayer(), "nocheat.moving")) { + return; + } + else if(NoCheatPlugin.Permissions == null && event.getPlayer().isOp() ) { + return; + } + + // First check the distance the player has moved horizontally + // TODO: Make this check much more precise + if(!event.isCancelled()) { + double xDistance = Math.abs(from.getX() - to.getX()); + double zDistance = Math.abs(from.getZ() - to.getZ()); + + // How far are we off? + if(xDistance > NoCheatConfiguration.movingDistanceHigh || zDistance > NoCheatConfiguration.movingDistanceHigh) { + heavyViolation(data, event); + } + else if(xDistance > NoCheatConfiguration.movingDistanceMed || zDistance > NoCheatConfiguration.movingDistanceMed) { + normalViolation(data, event); + } + else if(xDistance > NoCheatConfiguration.movingDistanceLow || zDistance > NoCheatConfiguration.movingDistanceLow) { + minorViolation(data, event); + } + } + + // If we didn't already cancel the event, check the vertical movement + if(!event.isCancelled()) { + + // pre-calculate boundary values that are needed multiple times in the following checks + // the array each contains [lowerX, higherX, Y, lowerZ, higherZ] + int fromValues[] = {floor_double(from.getX() - 0.3D), (int)Math.floor(from.getX() + 0.3D), from.getBlockY(), floor_double(from.getZ() - 0.3D),(int)Math.floor(from.getZ() + 0.3D) }; + int toValues[] = {floor_double(to.getX() - 0.3D), (int)Math.floor(to.getX() + 0.3D), to.getBlockY(), floor_double(to.getZ() - 0.3D), (int)Math.floor(to.getZ() + 0.3D) }; + + // compare locations to the world to guess if the player is standing on the ground, a half-block or next to a ladder + boolean onGroundFrom = playerIsOnGround(from.getWorld(), fromValues, from); + boolean onGroundTo = playerIsOnGround(from.getWorld(), toValues, to); + + // Both locations seem to be on solid ground or at a ladder + if(onGroundFrom && onGroundTo) + { + // reset jumping + data.phase = 0; + + // Check if the player isn't 'walking' up unrealistically far in one step + // Finally found out why this can happen: + // If a player runs into a wall at an angle from above, the game tries to + // place him above the block he bumped into, by placing him 0.5 m above + // the target block + if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { + + double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase]; + + if(offset > 2D) heavyViolation(data, event); + else if(offset > 0.6D) normalViolation(data, event); + else minorViolation(data, event); + } + } + // player is starting to jump (or starting to fall down somewhere) + else if(onGroundFrom && !onGroundTo) + { + // reset jumping + data.phase = 0; + + // Check if player isn't jumping too high + if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { + + double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase]; + + if(offset > 2D) heavyViolation(data, event); + else if(offset > 0.6D) normalViolation(data, event); + else minorViolation(data, event); + } + else if(to.getY() <= from.getY()) { + // Very special case if running over a cliff and then immediately jumping. + // Some sort of "air jump", MC allows it, so we have to do so too. + } + else data.phase++; // Setup next phase of the jump + } + // player is probably landing somewhere + else if(!onGroundFrom && onGroundTo) + { + // Check if player isn't landing to high (sounds weird, but has its use) + if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { + + double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase]; + + if(offset > 2D) heavyViolation(data, event); + else if(offset > 0.6D) normalViolation(data, event); + else minorViolation(data, event); + } + else { + data.phase = 0; // He is on ground now, so reset the jump + } + } + // Player is moving through air (during jumping, falling) + else { + // May also be at the very edge of a platform (I seem to not be able to reliably tell if that's the case + if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { + + double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase]; + + if(offset > 2D) heavyViolation(data, event); + else if(offset > 0.6D) normalViolation(data, event); + else minorViolation(data, event); + } + else { + data.phase++; // Enter next phase of the flight + } + } + + // do a security check on the jumping phase, such that we don't get + // OutOfArrayBoundsExceptions at long air times (falling off high places) + if(!(data.phase < jumpingPhases.length)) { + data.phase = jumpingPhases.length - 1; + } + } + + if(!event.isCancelled()) { + if(data.movingViolationsDirty) { + combineViolationCount(data); + } + data.movingSetBackPoint = null; + } + + } + + + private static void minorViolation(NoCheatPluginData data, PlayerMoveEvent event) { + data.movingViolations[0]++; + data.movingViolationsDirty = true; + if(data.movingViolations[0] == 0) { + // Store the source location for later use + data.movingSetBackPoint = event.getFrom().clone(); + } + if(data.movingViolations[0] > 2) { + // now we need it + resetPlayer(data, event); + NoCheatPlugin.log.info("NoCheatPlugin: Moving: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ())); + } + } + + private static void normalViolation(NoCheatPluginData data, PlayerMoveEvent event) { + data.movingViolations[1]++; + data.movingViolationsDirty = true; + resetPlayer(data, event); + + NoCheatPlugin.log.warning("NoCheatPlugin: Moving: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ())); + + } + + private static void heavyViolation(NoCheatPluginData data, PlayerMoveEvent event) { + data.movingViolations[2]++; + data.movingViolationsDirty = true; + resetPlayer(data, event); + // Log the violation + NoCheatPlugin.log.severe("NoCheatPlugin: Moving: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ())); + + } + + private static void combineViolationCount(NoCheatPluginData data) { + + data.movingViolationsTotal[0] += data.movingViolations[0]; + data.movingViolationsTotal[1] += data.movingViolations[1]; + data.movingViolationsTotal[2] += data.movingViolations[2]; + + data.movingViolations[0] = 0; + data.movingViolations[1] = 0; + data.movingViolations[2] = 0; + + data.movingViolationsDirty = false; + } + + private static void resetPlayer(NoCheatPluginData data, PlayerMoveEvent event) { + + event.setCancelled(true); + + if(data.movingSetBackPoint != null) { + event.getPlayer().teleportTo(data.movingSetBackPoint); + data.movingSetBackPoint = null; + } + else + event.getPlayer().teleportTo(event.getFrom()); + + combineViolationCount(data); + } + + + /** + * Check the four edges of the player's approximated Bounding Box for blocks or ladders, + * at his own height (values[2]) and below his feet (values[2]-1). Also, check at his "head" + * for ladders. + * + * If there is one, the player is considered as standing on it/hanging to it. + * + * Not perfect at all and will produce some false negatives. Probably will be refined + * later. + * + * @param w The world the coordinates belong to + * @param values The coordinates [lowerX, higherX, Y, lowerZ, higherZ] + * @return + */ + private static boolean playerIsOnGround(World w, int values[], Location l) { + + // Completely revamped collision detection + // What it does: + // Check the blocks below the player. If they aren't not solid (sic!) and the blocks directly above + // them aren't solid, The player is considered to be standing on the lower block + // Plus the player can hang onto a ladder that is one field above him + + // Check the four borders of the players hitbox for something he could be standing on + if(types[w.getBlockTypeIdAt(values[0], values[2]-1, values[3])] != BlockType.NONSOLID || + types[w.getBlockTypeIdAt(values[1], values[2]-1, values[3])] != BlockType.NONSOLID || + types[w.getBlockTypeIdAt(values[0], values[2]-1, values[4])] != BlockType.NONSOLID || + types[w.getBlockTypeIdAt(values[1], values[2]-1, values[4])] != BlockType.NONSOLID ) + return true; + // Check if he is hanging onto a ladder + else if(types[w.getBlockTypeIdAt(l.getBlockX(), l.getBlockY(), l.getBlockZ())] == BlockType.LADDER || + types[w.getBlockTypeIdAt(l.getBlockX(), l.getBlockY()+1, l.getBlockZ())] == BlockType.LADDER) + return true; + // check if he is standing "in" an block that's potentially solid (we give him the benefit of a doubt and see that as a legit move) + // If it is not legit, the MC server already has a safeguard against that (You'll get "xy moved wrongly" on the console in that case) + else if(types[w.getBlockTypeIdAt(values[0], values[2], values[3])] != BlockType.NONSOLID || + types[w.getBlockTypeIdAt(values[1], values[2], values[3])] != BlockType.NONSOLID|| + types[w.getBlockTypeIdAt(values[0], values[2], values[4])] != BlockType.NONSOLID || + types[w.getBlockTypeIdAt(values[1], values[2], values[4])] != BlockType.NONSOLID) + return true; + // check if his head is "stuck" in an block that's potentially solid (we give him the benefit of a doubt and see that as a legit move) + // If it is not legit, the MC server already has a safeguard against that (You'll get "xy moved wrongly" on the console in that case) + else if(types[w.getBlockTypeIdAt(values[0], values[2]+1, values[3])] != BlockType.NONSOLID || + types[w.getBlockTypeIdAt(values[1], values[2]+1, values[3])] != BlockType.NONSOLID || + types[w.getBlockTypeIdAt(values[0], values[2]+1, values[4])] != BlockType.NONSOLID || + types[w.getBlockTypeIdAt(values[1], values[2]+1, values[4])] != BlockType.NONSOLID) + return true; + else + return false; + } + + public static int floor_double(double d) + { + int i = (int)d; + return d > (double)i ? i : i - 1; + } +} diff --git a/src/cc/co/evenprime/bukkit/nocheat/NoCheatConfiguration.java b/src/cc/co/evenprime/bukkit/nocheat/NoCheatConfiguration.java index 6fd4dab9..481f786d 100644 --- a/src/cc/co/evenprime/bukkit/nocheat/NoCheatConfiguration.java +++ b/src/cc/co/evenprime/bukkit/nocheat/NoCheatConfiguration.java @@ -16,13 +16,17 @@ public class NoCheatConfiguration { public static final String loggerName = "cc.co.evenprime.bukkit.nocheat"; public static final Logger logger = Logger.getLogger(loggerName); - public static boolean speedhackActive = true; - public static boolean movingActive = true; + public static boolean speedhackCheckActive = true; + public static boolean movingCheckActive = true; public static int speedhackInterval = 2000; public static int speedhackLow = 60; public static int speedhackMed = 90; public static int speedhackHigh = 120; + public static double movingDistanceLow = 0.5D; + public static double movingDistanceMed = 1.0D; + public static double movingDistanceHigh = 5.0D; + private static ConsoleHandler ch = null; private static FileHandler fh = null; @@ -60,8 +64,8 @@ public class NoCheatConfiguration { } } - speedhackActive = c.getBoolean("active.speedhack", true); - movingActive = c.getBoolean("active.moving", true); + speedhackCheckActive = c.getBoolean("active.speedhack", true); + movingCheckActive = c.getBoolean("active.moving", true); speedhackInterval = c.getInt("speedhack.interval", 2000); speedhackLow = c.getInt("speedhack.limits.low", 60); diff --git a/src/cc/co/evenprime/bukkit/nocheat/NoCheatPluginPlayerListener.java b/src/cc/co/evenprime/bukkit/nocheat/NoCheatPluginPlayerListener.java index ba044fc5..4ebf60d5 100644 --- a/src/cc/co/evenprime/bukkit/nocheat/NoCheatPluginPlayerListener.java +++ b/src/cc/co/evenprime/bukkit/nocheat/NoCheatPluginPlayerListener.java @@ -5,8 +5,6 @@ import java.util.HashMap; import java.util.Map; import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerEvent; @@ -20,11 +18,6 @@ import org.bukkit.event.player.PlayerMoveEvent; public class NoCheatPluginPlayerListener extends PlayerListener { - - public enum BlockType { - SOLID, NONSOLID, LADDER, LIQUID, UNKNOWN; - } - /** * Storage for data persistence between events * @@ -35,122 +28,21 @@ public class NoCheatPluginPlayerListener extends PlayerListener { * Don't rely on any of these yet, they are likely going to * change their name/functionality */ - private int phase = 0; // current jumpingPhase - public int violations = 0; // number of cancelled events - private boolean lastWasInvalid = false; // used to reduce amount logging - private long lastSpeedHackCheck = System.currentTimeMillis();; // timestamp of last check for speedhacks - private int eventsSinceLastSpeedHackCheck = 0; // used to identify speedhacks - private int ignoreNextXEvents = 0; + int phase = 0; // current jumpingPhase + long lastSpeedHackCheck = System.currentTimeMillis(); // timestamp of last check for speedhacks + int eventsSinceLastSpeedHackCheck = 0; // used to identify speedhacks + int ignoreNextXEvents = 0; + + int movingViolations[] = {0,0,0}; + int movingViolationsTotal[] = {0,0,0}; + boolean movingViolationsDirty = false; + Location movingSetBackPoint = null; private NoCheatPluginData() { } } private final NoCheatPlugin plugin; - // previously-calculated upper bound values for jumps. Minecraft is very deterministic when it comes to jumps - // Each entry represents the maximum gain in height per move event. - private static double jumpingPhases[] = new double[]{ 0.501D, 0.34D, 0.26D, 0.17D, 0.09D, 0.02D, 0.00D, -0.07D, -0.15D, -0.22D, -0.29D, -0.36D, -0.43D, -0.50D }; - - - - // Until I can think of a better way to determine if a block is solid or not, this is what I'll do - private static BlockType types[] = new BlockType[256]; - static { - - for(int i = 0; i < types.length; i++) { - types[i] = BlockType.UNKNOWN; - } - - types[Material.AIR.getId()] = BlockType.NONSOLID; - types[Material.STONE.getId()] = BlockType.SOLID; - types[Material.GRASS.getId()] = BlockType.SOLID; - types[Material.DIRT.getId()] = BlockType.SOLID; - types[Material.COBBLESTONE.getId()] = BlockType.SOLID; - types[Material.WOOD.getId()] = BlockType.SOLID; - types[Material.SAPLING.getId()] = BlockType.NONSOLID; - types[Material.BEDROCK.getId()] = BlockType.SOLID; - types[Material.WATER.getId()] = BlockType.LIQUID; - types[Material.STATIONARY_WATER.getId()] = BlockType.LIQUID; - types[Material.LAVA.getId()] = BlockType.LIQUID; - types[Material.STATIONARY_LAVA.getId()] = BlockType.LIQUID; - types[Material.SAND.getId()] = BlockType.SOLID; - types[Material.GRAVEL.getId()] = BlockType.SOLID; - types[Material.GOLD_ORE.getId()] = BlockType.SOLID; - types[Material.IRON_ORE.getId()] = BlockType.SOLID; - types[Material.COAL_ORE.getId()] = BlockType.SOLID; - types[Material.LOG.getId()] = BlockType.SOLID; - types[Material.LEAVES.getId()] = BlockType.SOLID; - types[Material.SPONGE.getId()] = BlockType.SOLID; - types[Material.GLASS.getId()] = BlockType.SOLID; - types[Material.LAPIS_ORE.getId()] = BlockType.SOLID; - types[Material.LAPIS_BLOCK.getId()] = BlockType.SOLID; - types[Material.DISPENSER.getId()] = BlockType.SOLID; - types[Material.SANDSTONE.getId()] = BlockType.SOLID; - types[Material.NOTE_BLOCK.getId()]= BlockType.SOLID; - types[Material.WOOL.getId()]= BlockType.SOLID; - types[Material.YELLOW_FLOWER.getId()]= BlockType.NONSOLID; - types[Material.RED_ROSE.getId()]= BlockType.NONSOLID; - types[Material.BROWN_MUSHROOM.getId()]= BlockType.NONSOLID; - types[Material.RED_MUSHROOM.getId()]= BlockType.NONSOLID; - types[Material.GOLD_BLOCK.getId()]= BlockType.SOLID; - types[Material.IRON_BLOCK.getId()]= BlockType.SOLID; - types[Material.DOUBLE_STEP.getId()]= BlockType.UNKNOWN; - types[Material.STEP.getId()]= BlockType.UNKNOWN; - types[Material.BRICK.getId()]= BlockType.SOLID; - types[Material.TNT.getId()]= BlockType.SOLID; - types[Material.BOOKSHELF.getId()]= BlockType.SOLID; - types[Material.MOSSY_COBBLESTONE.getId()] = BlockType.SOLID; - types[Material.OBSIDIAN.getId()]= BlockType.SOLID; - types[Material.TORCH.getId()]= BlockType.NONSOLID; - types[Material.FIRE.getId()]= BlockType.NONSOLID; - types[Material.MOB_SPAWNER.getId()]= BlockType.SOLID; - types[Material.WOOD_STAIRS.getId()]= BlockType.UNKNOWN; - types[Material.CHEST.getId()]= BlockType.SOLID; - types[Material.REDSTONE_WIRE.getId()]= BlockType.NONSOLID; - types[Material.DIAMOND_ORE.getId()]= BlockType.SOLID; - types[Material.DIAMOND_BLOCK.getId()]= BlockType.SOLID; - types[Material.WORKBENCH.getId()]= BlockType.SOLID; - types[Material.CROPS.getId()]= BlockType.NONSOLID; - types[Material.SOIL.getId()]= BlockType.SOLID; - types[Material.FURNACE.getId()]= BlockType.SOLID; - types[Material.BURNING_FURNACE.getId()]= BlockType.SOLID; - types[Material.SIGN_POST.getId()]= BlockType.NONSOLID; - types[Material.WOODEN_DOOR.getId()]= BlockType.NONSOLID; - types[Material.LADDER.getId()]= BlockType.LADDER; - types[Material.RAILS.getId()]= BlockType.NONSOLID; - types[Material.COBBLESTONE_STAIRS.getId()]= BlockType.UNKNOWN; - types[Material.WALL_SIGN.getId()]= BlockType.NONSOLID; - types[Material.LEVER.getId()]= BlockType.NONSOLID; - types[Material.STONE_PLATE.getId()]= BlockType.UNKNOWN; - types[Material.IRON_DOOR_BLOCK.getId()]= BlockType.NONSOLID; - types[Material.WOOD_PLATE.getId()]= BlockType.NONSOLID; - types[Material.REDSTONE_ORE.getId()]= BlockType.SOLID; - types[Material.GLOWING_REDSTONE_ORE.getId()]= BlockType.SOLID; - types[Material.REDSTONE_TORCH_OFF.getId()]= BlockType.NONSOLID; - types[Material.REDSTONE_TORCH_ON.getId()]= BlockType.NONSOLID; - types[Material.STONE_BUTTON.getId()]= BlockType.NONSOLID; - types[Material.SNOW.getId()]= BlockType.UNKNOWN; - types[Material.ICE.getId()]= BlockType.UNKNOWN; - types[Material.SNOW_BLOCK.getId()]= BlockType.SOLID; - types[Material.CACTUS.getId()]= BlockType.SOLID; - types[Material.CLAY.getId()]= BlockType.SOLID; - types[Material.SUGAR_CANE_BLOCK.getId()]= BlockType.NONSOLID; - types[Material.JUKEBOX.getId()]= BlockType.SOLID; - types[Material.FENCE.getId()]= BlockType.UNKNOWN; - types[Material.PUMPKIN.getId()]= BlockType.SOLID; - types[Material.NETHERRACK.getId()]= BlockType.SOLID; - types[Material.SOUL_SAND.getId()]= BlockType.UNKNOWN; - types[Material.GLOWSTONE.getId()]= BlockType.SOLID; - types[Material.PORTAL.getId()]= BlockType.NONSOLID; - types[Material.JACK_O_LANTERN.getId()]= BlockType.SOLID; - types[Material.CAKE_BLOCK.getId()]= BlockType.UNKNOWN; - - } - - // Very rough estimates - private static double maxX = 0.5D; - private static double maxZ = 0.5D; - // Store data between Events private static Map playerData = new HashMap(); @@ -185,239 +77,16 @@ public class NoCheatPluginPlayerListener extends PlayerListener { playerData.put(event.getPlayer(), data); } - // If someone cancelled the event already, ignore it - // If the player is inside a vehicle, ignore it for now - if(event.isCancelled()) { - data.phase = 0; - return; - } - - if(data.ignoreNextXEvents > 0 ) { + if(data.ignoreNextXEvents > 0 ) { data.ignoreNextXEvents--; return; } - - // Get the two locations of the event - Location from = event.getFrom(); - Location to = event.getTo(); - - - // Get the time of the server - long time = System.currentTimeMillis(); - - boolean ignoreSpeedhackCheck = false; - if(!NoCheatConfiguration.speedhackActive) ignoreSpeedhackCheck = true; - if(NoCheatPlugin.Permissions != null && NoCheatPlugin.Permissions.has(event.getPlayer(), "nocheat.speedhack")) { - ignoreSpeedhackCheck = true; - } - else if(NoCheatPlugin.Permissions == null && event.getPlayer().isOp() ) { - ignoreSpeedhackCheck = true; - } - - if(!ignoreSpeedhackCheck){ - - // Is it time for a speedhack check now? - if(time > NoCheatConfiguration.speedhackInterval + data.lastSpeedHackCheck ) { - // Yes - - int limitLow = (int)((NoCheatConfiguration.speedhackLow * (time - data.lastSpeedHackCheck)) / NoCheatConfiguration.speedhackInterval); - int limitMed = (int)((NoCheatConfiguration.speedhackMed * (time - data.lastSpeedHackCheck)) / NoCheatConfiguration.speedhackInterval); - int limitHigh = (int)((NoCheatConfiguration.speedhackHigh * (time - data.lastSpeedHackCheck)) / NoCheatConfiguration.speedhackInterval); - - if(data.eventsSinceLastSpeedHackCheck > limitHigh) - NoCheatPlugin.log.severe("NoCheatPlugin: "+event.getPlayer().getName()+" sent "+ data.eventsSinceLastSpeedHackCheck + " move events, but only "+limitLow+ " were allowed. Speedhack?"); - else if(data.eventsSinceLastSpeedHackCheck > limitMed) - NoCheatPlugin.log.warning("NoCheatPlugin: "+event.getPlayer().getName()+" sent "+ data.eventsSinceLastSpeedHackCheck + " move events, but only "+limitLow+ " were allowed. Speedhack?"); - else if(data.eventsSinceLastSpeedHackCheck > limitLow) - NoCheatPlugin.log.info("NoCheatPlugin: "+event.getPlayer().getName()+" sent "+ data.eventsSinceLastSpeedHackCheck + " move events, but only "+limitLow+ " were allowed. Speedhack?"); - // Reset values for next check - data.eventsSinceLastSpeedHackCheck = 0; - data.lastSpeedHackCheck = time; - } - - data.eventsSinceLastSpeedHackCheck++; - } - - boolean ignoreMovingCheck = false; - if(!NoCheatConfiguration.movingActive) ignoreMovingCheck = true; - else if(NoCheatPlugin.Permissions != null && NoCheatPlugin.Permissions.has(event.getPlayer(), "nocheat.moving")) { - ignoreMovingCheck = true; - } - else if(NoCheatPlugin.Permissions == null && event.getPlayer().isOp() ) { - ignoreMovingCheck = true; - } - - if(!ignoreMovingCheck){ - // First check the distance the player has moved horizontally - // TODO: Make this check much more precise - if(!event.isCancelled()) { - double xDistance = Math.abs(from.getX() - to.getX()); - double zDistance = Math.abs(from.getZ() - to.getZ()); - - if(xDistance > maxX || zDistance > maxZ) { - event.setCancelled(true); - } - } - - // If we didn't already cancel the event, check the vertical movement - if(!event.isCancelled()) { - - // pre-calculate boundary values that are needed multiple times in the following checks - // the array each contains [lowerX, higherX, Y, lowerZ, higherZ] - int fromValues[] = {floor_double(from.getX() - 0.3D), (int)Math.floor(from.getX() + 0.3D), from.getBlockY(), floor_double(from.getZ() - 0.3D),(int)Math.floor(from.getZ() + 0.3D) }; - int toValues[] = {floor_double(to.getX() - 0.3D), (int)Math.floor(to.getX() + 0.3D), to.getBlockY(), floor_double(to.getZ() - 0.3D), (int)Math.floor(to.getZ() + 0.3D) }; - - // compare locations to the world to guess if the player is standing on the ground, a half-block or next to a ladder - boolean onGroundFrom = playerIsOnGround(from.getWorld(), fromValues, from); - boolean onGroundTo = playerIsOnGround(from.getWorld(), toValues, to); - - // Both locations seem to be on solid ground or at a ladder - if(onGroundFrom && onGroundTo) - { - // reset jumping - data.phase = 0; - - // Check if the player isn't 'walking' up unrealistically far in one step - // Finally found out why this can happen: - // If a player runs into a wall at an angle from above, the game tries to - // place him above the block he bumped into, by placing him 0.5 m above - // the target block - if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { - event.setCancelled(true); - } - } - // player is starting to jump (or starting to fall down somewhere) - else if(onGroundFrom && !onGroundTo) - { - // reset jumping - data.phase = 0; - - // Check if player isn't jumping too high - if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { - event.setCancelled(true); - } - else if(to.getY() <= from.getY()) { - // Very special case if running over a cliff and then immediately jumping. - // Some sort of "air jump", MC allows it, so we have to do so too. - } - else data.phase++; // Setup next phase of the jump - } - // player is probably landing somewhere - else if(!onGroundFrom && onGroundTo) - { - // Check if player isn't landing to high (sounds weird, but has its use) - if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { - event.setCancelled(true); - } - else { - data.phase = 0; // He is on ground now, so reset the jump - } - } - // Player is moving through air (during jumping, falling) - else { - // May also be at the very edge of a platform (I seem to not be able to reliably tell if that's the case - if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { - event.setCancelled(true); - } - else { - data.phase++; // Enter next phase of the flight - } - } - - // do a security check on the jumping phase, such that we don't get - // OutOfArrayBoundsExceptions at long air times (falling off high places) - if(!(data.phase < jumpingPhases.length)) { - data.phase = jumpingPhases.length - 1; - } - } - - if(event.isCancelled() && !data.lastWasInvalid) { - // Keep count of violations - data.violations++; - - // Log the violation - NoCheatPlugin.log.info("NoCheatPlugin: "+event.getPlayer().getName()+" begins violating constraints. Total Violations: "+data.violations); - NoCheatPlugin.log.info("NoCheatPlugin: Moving from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", from.getX(), from.getY(), from.getZ(), to.getX(), to.getY(), to.getZ())); - - data.lastWasInvalid = true; - - // Reset the player to his old location. This should prevent him from getting stuck somewhere and/or getting - // out of sync with the server - event.getPlayer().teleportTo(event.getFrom()); - - // To prevent players from getting stuck in an infinite loop, needs probably more testing - // TODO: Find a better solution - if(data.phase > 7) { - data.phase = 7; - } - } - else if(event.isCancelled() && data.lastWasInvalid) { - data.violations++; - - // Reset the player to his old location. This should prevent him from getting stuck somewhere and/or getting - // out of sync with the server - event.getPlayer().teleportTo(event.getFrom()); - } - else if(!event.isCancelled() && data.lastWasInvalid) { - data.lastWasInvalid = false; - NoCheatPlugin.log.info("NoCheatPlugin: "+event.getPlayer().getName()+" stopped violating constraints. Total Violations: "+data.violations); - } - } - } - - /** - * Check the four edges of the player's approximated Bounding Box for blocks or ladders, - * at his own height (values[2]) and below his feet (values[2]-1). Also, check at his "head" - * for ladders. - * - * If there is one, the player is considered as standing on it/hanging to it. - * - * Not perfect at all and will produce some false negatives. Probably will be refined - * later. - * - * @param w The world the coordinates belong to - * @param values The coordinates [lowerX, higherX, Y, lowerZ, higherZ] - * @return - */ - private boolean playerIsOnGround(World w, int values[], Location l) { - - // Completely revamped collision detection - // What it does: - // Check the blocks below the player. If they aren't not solid (sic!) and the blocks directly above - // them aren't solid, The player is considered to be standing on the lower block - // Plus the player can hang onto a ladder that is one field above him - // Check the four borders of the players hitbox for something he could be standing on - if(types[w.getBlockTypeIdAt(values[0], values[2]-1, values[3])] != BlockType.NONSOLID || - types[w.getBlockTypeIdAt(values[1], values[2]-1, values[3])] != BlockType.NONSOLID || - types[w.getBlockTypeIdAt(values[0], values[2]-1, values[4])] != BlockType.NONSOLID || - types[w.getBlockTypeIdAt(values[1], values[2]-1, values[4])] != BlockType.NONSOLID ) - return true; - // Check if he is hanging onto a ladder - else if(types[w.getBlockTypeIdAt(l.getBlockX(), l.getBlockY(), l.getBlockZ())] == BlockType.LADDER || - types[w.getBlockTypeIdAt(l.getBlockX(), l.getBlockY()+1, l.getBlockZ())] == BlockType.LADDER) - return true; - // check if he is standing "in" an block that's potentially solid (we give him the benefit of a doubt and see that as a legit move) - // If it is not legit, the MC server already has a safeguard against that (You'll get "xy moved wrongly" on the console in that case) - else if(types[w.getBlockTypeIdAt(values[0], values[2], values[3])] != BlockType.NONSOLID || - types[w.getBlockTypeIdAt(values[1], values[2], values[3])] != BlockType.NONSOLID|| - types[w.getBlockTypeIdAt(values[0], values[2], values[4])] != BlockType.NONSOLID || - types[w.getBlockTypeIdAt(values[1], values[2], values[4])] != BlockType.NONSOLID) - return true; - // check if his head is "stuck" in an block that's potentially solid (we give him the benefit of a doubt and see that as a legit move) - // If it is not legit, the MC server already has a safeguard against that (You'll get "xy moved wrongly" on the console in that case) - else if(types[w.getBlockTypeIdAt(values[0], values[2]+1, values[3])] != BlockType.NONSOLID || - types[w.getBlockTypeIdAt(values[1], values[2]+1, values[3])] != BlockType.NONSOLID || - types[w.getBlockTypeIdAt(values[0], values[2]+1, values[4])] != BlockType.NONSOLID || - types[w.getBlockTypeIdAt(values[1], values[2]+1, values[4])] != BlockType.NONSOLID) - return true; - else - return false; - } - - public static int floor_double(double d) - { - int i = (int)d; - return d > (double)i ? i : i - 1; + if(!event.isCancelled() && NoCheatConfiguration.speedhackCheckActive) + SpeedhackCheck.check(data, event); + + if(!event.isCancelled() && NoCheatConfiguration.movingCheckActive) + MovingCheck.check(data, event); + } } diff --git a/src/cc/co/evenprime/bukkit/nocheat/SpeedhackCheck.java b/src/cc/co/evenprime/bukkit/nocheat/SpeedhackCheck.java new file mode 100644 index 00000000..760cd8bb --- /dev/null +++ b/src/cc/co/evenprime/bukkit/nocheat/SpeedhackCheck.java @@ -0,0 +1,44 @@ +package cc.co.evenprime.bukkit.nocheat; + +import org.bukkit.event.player.PlayerMoveEvent; + +import cc.co.evenprime.bukkit.nocheat.NoCheatPluginPlayerListener.NoCheatPluginData; + +public class SpeedhackCheck { + + public static void check(NoCheatPluginData data, PlayerMoveEvent event) { + + // Get the time of the server + long time = System.currentTimeMillis(); + + + if(NoCheatPlugin.Permissions != null && NoCheatPlugin.Permissions.has(event.getPlayer(), "nocheat.speedhack")) { + return; + } + else if(NoCheatPlugin.Permissions == null && event.getPlayer().isOp() ) { + return; + } + + // Is it time for a speedhack check now? + if(time > NoCheatConfiguration.speedhackInterval + data.lastSpeedHackCheck ) { + // Yes + // TODO: Needs some better handling for server lag + + int limitLow = (int)((NoCheatConfiguration.speedhackLow * (time - data.lastSpeedHackCheck)) / NoCheatConfiguration.speedhackInterval); + int limitMed = (int)((NoCheatConfiguration.speedhackMed * (time - data.lastSpeedHackCheck)) / NoCheatConfiguration.speedhackInterval); + int limitHigh = (int)((NoCheatConfiguration.speedhackHigh * (time - data.lastSpeedHackCheck)) / NoCheatConfiguration.speedhackInterval); + + if(data.eventsSinceLastSpeedHackCheck > limitHigh) + NoCheatPlugin.log.severe("NoCheatPlugin: "+event.getPlayer().getName()+" sent "+ data.eventsSinceLastSpeedHackCheck + " move events, but only "+limitLow+ " were allowed. Speedhack?"); + else if(data.eventsSinceLastSpeedHackCheck > limitMed) + NoCheatPlugin.log.warning("NoCheatPlugin: "+event.getPlayer().getName()+" sent "+ data.eventsSinceLastSpeedHackCheck + " move events, but only "+limitLow+ " were allowed. Speedhack?"); + else if(data.eventsSinceLastSpeedHackCheck > limitLow) + NoCheatPlugin.log.info("NoCheatPlugin: "+event.getPlayer().getName()+" sent "+ data.eventsSinceLastSpeedHackCheck + " move events, but only "+limitLow+ " were allowed. Speedhack?"); + // Reset values for next check + data.eventsSinceLastSpeedHackCheck = 0; + data.lastSpeedHackCheck = time; + } + + data.eventsSinceLastSpeedHackCheck++; + } +}