summaryrefslogtreecommitdiffstats
path: root/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks
diff options
context:
space:
mode:
Diffstat (limited to 'EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks')
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java159
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java372
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java55
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java64
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java186
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java40
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java29
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java100
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java61
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java75
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java99
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java97
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java37
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java25
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/DirectionCheck.java131
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/ReachCheck.java75
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java79
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java108
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java64
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java22
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java50
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java96
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java120
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheck.java69
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java291
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java58
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java40
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java151
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java94
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java67
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/ReachCheck.java113
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java81
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java71
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java72
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java78
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java63
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java196
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java40
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java25
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java170
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java132
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java93
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java376
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java71
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java69
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java151
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java303
47 files changed, 5018 insertions, 0 deletions
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java
new file mode 100644
index 000000000..5482efa5a
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java
@@ -0,0 +1,159 @@
+package com.earth2me.essentials.anticheat.checks;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatLogEvent;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.Action;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.actions.types.*;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.command.CommandException;
+
+
+/**
+ * The abstract Check class, providing some basic functionality
+ *
+ */
+public abstract class Check
+{
+ private final String name;
+ // used to bundle information of multiple checks
+ private final String groupId;
+ protected final NoCheat plugin;
+
+ public Check(NoCheat plugin, String groupId, String name)
+ {
+ this.plugin = plugin;
+ this.groupId = groupId;
+ this.name = name;
+ }
+
+ /**
+ * Execute some actions for the specified player
+ *
+ * @param player
+ * @param actions
+ * @return
+ */
+ protected final boolean executeActions(NoCheatPlayer player, ActionList actionList, double violationLevel)
+ {
+
+ boolean special = false;
+
+ // Get the to be executed actions
+ Action[] actions = actionList.getActions(violationLevel);
+
+ final long time = System.currentTimeMillis() / 1000L;
+
+ // The configuration will be needed too
+ final ConfigurationCacheStore cc = player.getConfigurationStore();
+
+ for (Action ac : actions)
+ {
+ if (player.getExecutionHistory().executeAction(groupId, ac, time))
+ {
+ // The executionHistory said it really is time to execute the
+ // action, find out what it is and do what is needed
+ if (ac instanceof LogAction && !player.hasPermission(actionList.permissionSilent))
+ {
+ executeLogAction((LogAction)ac, this, player, cc);
+ }
+ else if (ac instanceof SpecialAction)
+ {
+ special = true;
+ }
+ else if (ac instanceof ConsolecommandAction)
+ {
+ executeConsoleCommand((ConsolecommandAction)ac, this, player, cc);
+ }
+ else if (ac instanceof DummyAction)
+ {
+ // nothing - it's a "DummyAction" after all
+ }
+ }
+ }
+
+ return special;
+ }
+
+ /**
+ * Collect information about the players violations
+ *
+ * @param player
+ * @param id
+ * @param vl
+ */
+ protected void incrementStatistics(NoCheatPlayer player, Id id, double vl)
+ {
+ player.getDataStore().getStatistics().increment(id, vl);
+ }
+
+ private final void executeLogAction(LogAction l, Check check, NoCheatPlayer player, ConfigurationCacheStore cc)
+ {
+
+ if (!cc.logging.active)
+ {
+ return;
+ }
+
+ // Fire one of our custom "Log" Events
+ Bukkit.getServer().getPluginManager().callEvent(new NoCheatLogEvent(cc.logging.prefix, l.getLogMessage(player, check), cc.logging.toConsole && l.toConsole(), cc.logging.toChat && l.toChat(), cc.logging.toFile && l.toFile()));
+ }
+
+ private final void executeConsoleCommand(ConsolecommandAction action, Check check, NoCheatPlayer player, ConfigurationCacheStore cc)
+ {
+ final String command = action.getCommand(player, check);
+
+ try
+ {
+ plugin.getServer().dispatchCommand(Bukkit.getConsoleSender(), command);
+ }
+ catch (CommandException e)
+ {
+ plugin.getLogger().warning("failed to execute the command '" + command + "': " + e.getMessage() + ", please check if everything is setup correct.");
+ }
+ catch (Exception e)
+ {
+ // I don't care in this case, your problem if your command fails
+ }
+ }
+
+ /**
+ * Replace a parameter for commands or log actions with an actual value. Individual checks should override this to
+ * get their own parameters handled too.
+ *
+ * @param wildcard
+ * @param player
+ * @return
+ */
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.PLAYER)
+ {
+ return player.getName();
+ }
+ else if (wildcard == ParameterName.CHECK)
+ {
+ return name;
+ }
+ else if (wildcard == ParameterName.LOCATION)
+ {
+ Location l = player.getPlayer().getLocation();
+ return String.format(Locale.US, "%.2f,%.2f,%.2f", l.getX(), l.getY(), l.getZ());
+ }
+ else if (wildcard == ParameterName.WORLD)
+ {
+ return player.getPlayer().getWorld().getName();
+ }
+ else
+ {
+ return "the Author was lazy and forgot to define " + wildcard + ".";
+ }
+
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java
new file mode 100644
index 000000000..390d2207d
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java
@@ -0,0 +1,372 @@
+package com.earth2me.essentials.anticheat.checks;
+
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import java.util.HashSet;
+import java.util.Set;
+import net.minecraft.server.Block;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.util.Vector;
+
+
+/**
+ * Some stuff that's used by different checks or just too complex to keep in other places
+ *
+ */
+public class CheckUtil
+{
+ /**
+ * Check if a player looks at a target of a specific size, with a specific precision value (roughly)
+ */
+ public static double directionCheck(final NoCheatPlayer player, final double targetX, final double targetY, final double targetZ, final double targetWidth, final double targetHeight, final double precision)
+ {
+
+ // Eye location of the player
+ final Location eyes = player.getPlayer().getEyeLocation();
+
+ final double factor = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) + Math.pow(eyes.getZ() - targetZ, 2));
+
+ // View direction of the player
+ final Vector direction = eyes.getDirection();
+
+ final double x = targetX - eyes.getX();
+ final double y = targetY - eyes.getY();
+ final double z = targetZ - eyes.getZ();
+
+ final double xPrediction = factor * direction.getX();
+ final double yPrediction = factor * direction.getY();
+ final double zPrediction = factor * direction.getZ();
+
+ double off = 0.0D;
+
+ off += Math.max(Math.abs(x - xPrediction) - (targetWidth / 2 + precision), 0.0D);
+ off += Math.max(Math.abs(z - zPrediction) - (targetWidth / 2 + precision), 0.0D);
+ off += Math.max(Math.abs(y - yPrediction) - (targetHeight / 2 + precision), 0.0D);
+
+ if (off > 1)
+ {
+ off = Math.sqrt(off);
+ }
+
+ return off;
+ }
+
+ /**
+ * Check if a player is close enough to a target, based on his eye location
+ *
+ * @param player
+ * @param targetX
+ * @param targetY
+ * @param targetZ
+ * @param limit
+ * @return
+ */
+ public static final double reachCheck(final NoCheatPlayer player, final double targetX, final double targetY, final double targetZ, final double limit)
+ {
+
+ final Location eyes = player.getPlayer().getEyeLocation();
+
+ final double distance = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) + Math.pow(eyes.getZ() - targetZ, 2));
+
+ return Math.max(distance - limit, 0.0D);
+ }
+ private final static double magic = 0.45D;
+ private final static double magic2 = 0.55D;
+ private static final int NONSOLID = 1; // 0x00000001
+ private static final int SOLID = 2; // 0x00000010
+ // All liquids are "nonsolid" too
+ private static final int LIQUID = 4 | NONSOLID; // 0x00000101
+ // All ladders are "nonsolid" and "solid" too
+ private static final int LADDER = 8 | NONSOLID | SOLID; // 0x00001011
+ // All fences are solid - fences are treated specially due
+ // to being 1.5 blocks high
+ private static final int FENCE = 16 | SOLID | NONSOLID; // 0x00010011
+ private static final int INGROUND = 128;
+ private static final int ONGROUND = 256;
+ // 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 final int types[];
+ private static final Set<Material> foods = new HashSet<Material>();
+
+ static
+ {
+ types = new int[256];
+
+ // Find and define properties of all other blocks
+ for (int i = 0; i < types.length; i++)
+ {
+
+ // Everything unknown is considered nonsolid and solid
+ types[i] = NONSOLID | SOLID;
+
+ if (Block.byId[i] != null)
+ {
+ if (Block.byId[i].material.isSolid())
+ {
+ // STONE, CAKE, LEAFS, ...
+ types[i] = SOLID;
+ }
+ else if (Block.byId[i].material.isLiquid())
+ {
+ // WATER, LAVA, ...
+ types[i] = LIQUID;
+ }
+ else
+ {
+ // AIR, SAPLINGS, ...
+ types[i] = NONSOLID;
+ }
+ }
+ }
+
+ // Some exceptions where the above method fails
+
+ // du'h
+ types[Material.AIR.getId()] = NONSOLID;
+
+ // Webs slow down a players fall extremely, so it makes
+ // sense to treat them as optionally solid
+ types[Material.WEB.getId()] = SOLID | NONSOLID;
+
+ // Obvious
+ types[Material.LADDER.getId()] = LADDER;
+ types[Material.WATER_LILY.getId()] = LADDER;
+ types[Material.VINE.getId()] = LADDER;
+
+ types[Material.FENCE.getId()] = FENCE;
+ types[Material.FENCE_GATE.getId()] = FENCE;
+ types[Material.NETHER_FENCE.getId()] = FENCE;
+
+ // These are sometimes solid, sometimes not
+ types[Material.IRON_FENCE.getId()] = SOLID | NONSOLID;
+ types[Material.THIN_GLASS.getId()] = SOLID | NONSOLID;
+
+ // Signs are NOT solid, despite the game claiming they are
+ types[Material.WALL_SIGN.getId()] = NONSOLID;
+ types[Material.SIGN_POST.getId()] = NONSOLID;
+
+ // (trap)doors can be solid or not
+ types[Material.WOODEN_DOOR.getId()] = SOLID | NONSOLID;
+ types[Material.IRON_DOOR_BLOCK.getId()] = SOLID | NONSOLID;
+ types[Material.TRAP_DOOR.getId()] = SOLID | NONSOLID;
+
+ // repeaters are technically half blocks
+ types[Material.DIODE_BLOCK_OFF.getId()] = SOLID | NONSOLID;
+ types[Material.DIODE_BLOCK_ON.getId()] = SOLID | NONSOLID;
+
+ // pressure plates are so slim, you can consider them
+ // nonsolid too
+ types[Material.STONE_PLATE.getId()] = SOLID | NONSOLID;
+ types[Material.WOOD_PLATE.getId()] = SOLID | NONSOLID;
+
+ // We need to know what is considered food for the instanteat check
+ foods.add(Material.APPLE);
+ foods.add(Material.BREAD);
+ foods.add(Material.COOKED_BEEF);
+ foods.add(Material.COOKED_CHICKEN);
+ foods.add(Material.COOKED_FISH);
+ foods.add(Material.COOKIE);
+ foods.add(Material.GOLDEN_APPLE);
+ foods.add(Material.GRILLED_PORK);
+ foods.add(Material.MELON);
+ foods.add(Material.MUSHROOM_SOUP);
+ foods.add(Material.PORK);
+ foods.add(Material.RAW_BEEF);
+ foods.add(Material.RAW_CHICKEN);
+ foods.add(Material.RAW_FISH);
+ foods.add(Material.ROTTEN_FLESH);
+ foods.add(Material.SPIDER_EYE);
+ }
+
+ /**
+ * Ask NoCheat what it thinks about a certain location. Is it a place where a player can safely stand, should it be
+ * considered as being inside a liquid etc.
+ *
+ * @param world The world the coordinates belong to
+ * @param location The precise location in the world
+ *
+ * @return
+ */
+ public static final int evaluateLocation(final World world, final PreciseLocation location)
+ {
+
+ final int lowerX = lowerBorder(location.x);
+ final int upperX = upperBorder(location.x);
+ final int Y = (int)location.y;
+ final int lowerZ = lowerBorder(location.z);
+ final int upperZ = upperBorder(location.z);
+
+ // Check the four borders of the players hitbox for something he could
+ // be standing on, and combine the results
+ int result = 0;
+
+ result |= evaluateSimpleLocation(world, lowerX, Y, lowerZ);
+ result |= evaluateSimpleLocation(world, upperX, Y, lowerZ);
+ result |= evaluateSimpleLocation(world, upperX, Y, upperZ);
+ result |= evaluateSimpleLocation(world, lowerX, Y, upperZ);
+
+ if (!isInGround(result))
+ {
+ // Original location: X, Z (allow standing in walls this time)
+ if (isSolid(types[world.getBlockTypeIdAt(Location.locToBlock(location.x), Location.locToBlock(location.y), Location.locToBlock(location.z))]))
+ {
+ result |= INGROUND;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Evaluate a location by only looking at a specific "column" of the map to find out if that "column" would allow a
+ * player to stand, swim etc. there
+ *
+ * @param world
+ * @param x
+ * @param y
+ * @param z
+ * @return Returns INGROUND, ONGROUND, LIQUID, combination of the three or 0
+ */
+ private static final int evaluateSimpleLocation(final World world, final int x, final int y, final int z)
+ {
+
+ // First we need to know about the block itself, the block
+ // below it and the block above it
+ final int top = types[world.getBlockTypeIdAt(x, y + 1, z)];
+ final int base = types[world.getBlockTypeIdAt(x, y, z)];
+ final int below = types[world.getBlockTypeIdAt(x, y - 1, z)];
+
+ int type = 0;
+ // Special case: Standing on a fence
+ // Behave as if there is a block on top of the fence
+ if ((below == FENCE) && base != FENCE && isNonSolid(top))
+ {
+ type = INGROUND;
+ }
+ // Special case: Fence
+ // Being a bit above a fence
+ else if (below != FENCE && isNonSolid(base) && types[world.getBlockTypeIdAt(x, y - 2, z)] == FENCE)
+ {
+ type = ONGROUND;
+ }
+ else if (isNonSolid(top))
+ {
+ // Simplest (and most likely) case:
+ // Below the player is a solid block
+ if (isSolid(below) && isNonSolid(base))
+ {
+ type = ONGROUND;
+ }
+ // Next (likely) case:
+ // There is a ladder
+ else if (isLadder(base) || isLadder(top))
+ {
+ type = ONGROUND;
+ }
+ // Next (likely) case:
+ // At least the block the player stands
+ // in is solid
+ else if (isSolid(base))
+ {
+ type = INGROUND;
+ }
+ }
+
+ // (In every case, check for water)
+ if (isLiquid(base) || isLiquid(top))
+ {
+ type |= LIQUID | INGROUND;
+ }
+
+ return type;
+ }
+
+ public static final boolean isSolid(final int value)
+ {
+ return (value & SOLID) == SOLID;
+ }
+
+ public static final boolean isLiquid(final int value)
+ {
+ return (value & LIQUID) == LIQUID;
+ }
+
+ private static final boolean isNonSolid(final int value)
+ {
+ return ((value & NONSOLID) == NONSOLID);
+ }
+
+ private static final boolean isLadder(final int value)
+ {
+ return ((value & LADDER) == LADDER);
+ }
+
+ public static final boolean isOnGround(final int fromType)
+ {
+ return (fromType & ONGROUND) == ONGROUND;
+ }
+
+ public static final boolean isInGround(final int fromType)
+ {
+ return (fromType & INGROUND) == INGROUND;
+ }
+
+ /**
+ * Personal Rounding function to determine if a player is still touching a block or not
+ *
+ * @param d1
+ * @return
+ */
+ private static final int lowerBorder(final double d1)
+ {
+
+ final double floor = Math.floor(d1);
+
+ if (floor + magic <= d1)
+ {
+ return (int)(floor);
+ }
+ else
+ {
+ return (int)(floor - 1);
+ }
+ }
+
+ /**
+ * Personal Rounding function to determine if a player is still touching a block or not
+ *
+ * @param d1
+ * @return
+ */
+ private static final int upperBorder(final double d1)
+ {
+
+ final double floor = Math.floor(d1);
+
+ if (floor + magic2 < d1)
+ {
+ return (int)(floor + 1);
+ }
+ else
+ {
+ return (int)floor;
+ }
+ }
+
+ public static int getType(final int typeId)
+ {
+ return types[typeId];
+ }
+
+ public static boolean isFood(ItemStack item)
+ {
+ if (item == null)
+ {
+ return false;
+ }
+ return foods.contains(item.getType());
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java
new file mode 100644
index 000000000..7191c0c23
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java
@@ -0,0 +1,55 @@
+package com.earth2me.essentials.anticheat.checks;
+
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import java.util.Collections;
+import java.util.List;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.event.player.PlayerToggleSprintEvent;
+
+
+/**
+ * Only place that listens to Player-teleport related events and dispatches them to relevant checks
+ *
+ */
+public class WorkaroundsListener implements Listener, EventManager
+{
+ public WorkaroundsListener()
+ {
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void playerMove(final PlayerMoveEvent event)
+ {
+ // No typo here. I really only handle cancelled events and ignore others
+ if (!event.isCancelled())
+ {
+ return;
+ }
+
+ // Fix a common mistake that other developers make (cancelling move
+ // events is crazy, rather set the target location to the from location)
+ event.setCancelled(false);
+ event.setTo(event.getFrom().clone());
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void toggleSprint(final PlayerToggleSprintEvent event)
+ {
+ // Some plugins cancel "sprinting", which makes no sense at all because
+ // it doesn't stop people from sprinting and rewards them by reducing
+ // their hunger bar as if they were walking instead of sprinting
+ if (event.isCancelled() && event.isSprinting())
+ {
+ event.setCancelled(false);
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ return Collections.emptyList();
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java
new file mode 100644
index 000000000..6e349c85c
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java
@@ -0,0 +1,64 @@
+package com.earth2me.essentials.anticheat.checks.blockbreak;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+
+
+/**
+ * Abstract base class for BlockBreakChecks. Provides some static convenience methods for retrieving data and config
+ * objects for players
+ *
+ */
+public abstract class BlockBreakCheck extends Check
+{
+ private static final String id = "blockbreak";
+
+ public BlockBreakCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ /**
+ * Get the "BlockBreakData" object that belongs to the player. Will ensure that such a object exists and if not,
+ * create one
+ *
+ * @param player
+ * @return
+ */
+ public static BlockBreakData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ BlockBreakData data = base.get(id);
+ if (data == null)
+ {
+ data = new BlockBreakData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the BlockBreakConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static BlockBreakConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static BlockBreakConfig getConfig(ConfigurationCacheStore cache)
+ {
+ BlockBreakConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new BlockBreakConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java
new file mode 100644
index 000000000..2e56adb68
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java
@@ -0,0 +1,186 @@
+package com.earth2me.essentials.anticheat.checks.blockbreak;
+
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockDamageEvent;
+import org.bukkit.event.player.PlayerAnimationEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.config.Permissions;
+
+
+/**
+ * Central location to listen to events that are relevant for the blockbreak checks
+ *
+ */
+public class BlockBreakCheckListener implements Listener, EventManager
+{
+ private final NoswingCheck noswingCheck;
+ private final ReachCheck reachCheck;
+ private final DirectionCheck directionCheck;
+ private final NoCheat plugin;
+
+ public BlockBreakCheckListener(NoCheat plugin)
+ {
+
+ noswingCheck = new NoswingCheck(plugin);
+ reachCheck = new ReachCheck(plugin);
+ directionCheck = new DirectionCheck(plugin);
+
+ this.plugin = plugin;
+ }
+
+ /**
+ * We listen to blockBreak events for obvious reasons
+ *
+ * @param event The blockbreak event
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void blockBreak(final BlockBreakEvent event)
+ {
+
+ if (event.isCancelled())
+ {
+ return;
+ }
+
+ boolean cancelled = false;
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final BlockBreakConfig cc = BlockBreakCheck.getConfig(player);
+ final BlockBreakData data = BlockBreakCheck.getData(player);
+
+ // Remember the location of the block that will be broken
+ data.brokenBlockLocation.set(event.getBlock());
+
+ // Only if the block got damaged directly before, do the check(s)
+ if (!data.brokenBlockLocation.equals(data.lastDamagedBlock))
+ {
+ // Something caused a blockbreak event that's not from the player
+ // Don't check it at all
+ data.lastDamagedBlock.reset();
+ return;
+ }
+
+ // Now do the actual checks, if still needed. It's a good idea to make
+ // computationally cheap checks first, because it may save us from
+ // doing the computationally expensive checks.
+
+ // First NoSwing: Did the arm of the player move before breaking this
+ // block?
+ if (cc.noswingCheck && !player.hasPermission(Permissions.BLOCKBREAK_NOSWING))
+ {
+ cancelled = noswingCheck.check(player, data, cc);
+ }
+
+ // Second Reach: Is the block really in reach distance
+ if (!cancelled && cc.reachCheck && !player.hasPermission(Permissions.BLOCKBREAK_REACH))
+ {
+ cancelled = reachCheck.check(player, data, cc);
+ }
+
+ // Third Direction: Did the player look at the block at all
+ if (!cancelled && cc.directionCheck && !player.hasPermission(Permissions.BLOCKBREAK_DIRECTION))
+ {
+ cancelled = directionCheck.check(player, data, cc);
+ }
+
+ // At least one check failed and demanded to cancel the event
+ if (cancelled)
+ {
+ event.setCancelled(cancelled);
+ }
+ }
+
+ /**
+ * We listen to BlockDamage events to grab the information if it has been an "insta-break". That info may come in
+ * handy later.
+ *
+ * @param event The BlockDamage event
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void blockHit(final BlockDamageEvent event)
+ {
+
+ if (event.isCancelled())
+ {
+ return;
+ }
+
+ NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ BlockBreakData data = BlockBreakCheck.getData(player);
+
+ // Only interested in insta-break events here
+ if (event.getInstaBreak())
+ {
+ // Remember this location. We handle insta-breaks slightly
+ // different in some of the blockbreak checks.
+ data.instaBrokenBlockLocation.set(event.getBlock());
+ }
+
+ }
+
+ /**
+ * We listen to BlockInteract events to be (at least in many cases) able to distinguish between blockbreak events
+ * that were triggered by players actually digging and events that were artificially created by plugins.
+ *
+ * @param event
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void blockInteract(final PlayerInteractEvent event)
+ {
+
+ if (event.getClickedBlock() == null)
+ {
+ return;
+ }
+
+ NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ BlockBreakData data = BlockBreakCheck.getData(player);
+ // Remember this location. Only blockbreakevents for this specific
+ // block will be handled at all
+ data.lastDamagedBlock.set(event.getClickedBlock());
+ }
+
+ /**
+ * We listen to PlayerAnimationEvent because it is (currently) equivalent to "player swings arm" and we want to
+ * check if he did that between blockbreaks.
+ *
+ * @param event The PlayerAnimation Event
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void armSwing(final PlayerAnimationEvent event)
+ {
+ // Just set a flag to true when the arm was swung
+ BlockBreakCheck.getData(plugin.getPlayer(event.getPlayer())).armswung = true;
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ BlockBreakConfig bb = BlockBreakCheck.getConfig(cc);
+
+ if (bb.directionCheck)
+ {
+ s.add("blockbreak.direction");
+ }
+ if (bb.reachCheck)
+ {
+ s.add("blockbreak.reach");
+ }
+ if (bb.noswingCheck)
+ {
+ s.add("blockbreak.noswing");
+ }
+
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java
new file mode 100644
index 000000000..aed4a6a08
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java
@@ -0,0 +1,40 @@
+package com.earth2me.essentials.anticheat.checks.blockbreak;
+
+import com.earth2me.essentials.anticheat.ConfigItem;
+import com.earth2me.essentials.anticheat.actions.types.ActionList;
+import com.earth2me.essentials.anticheat.config.ConfPaths;
+import com.earth2me.essentials.anticheat.config.NoCheatConfiguration;
+import com.earth2me.essentials.anticheat.config.Permissions;
+
+
+/**
+ * Configurations specific for the "BlockBreak" checks Every world gets one of these assigned to it, or if a world
+ * doesn't get it's own, it will use the "global" version
+ *
+ */
+public class BlockBreakConfig implements ConfigItem
+{
+ public final boolean reachCheck;
+ public final double reachDistance;
+ public final ActionList reachActions;
+ public final boolean directionCheck;
+ public final ActionList directionActions;
+ public final double directionPrecision;
+ public final long directionPenaltyTime;
+ public final boolean noswingCheck;
+ public final ActionList noswingActions;
+
+ public BlockBreakConfig(NoCheatConfiguration data)
+ {
+
+ reachCheck = data.getBoolean(ConfPaths.BLOCKBREAK_REACH_CHECK);
+ reachDistance = 535D / 100D;
+ reachActions = data.getActionList(ConfPaths.BLOCKBREAK_REACH_ACTIONS, Permissions.BLOCKBREAK_REACH);
+ directionCheck = data.getBoolean(ConfPaths.BLOCKBREAK_DIRECTION_CHECK);
+ directionPrecision = ((double)data.getInt(ConfPaths.BLOCKBREAK_DIRECTION_PRECISION)) / 100D;
+ directionPenaltyTime = data.getInt(ConfPaths.BLOCKBREAK_DIRECTION_PENALTYTIME);
+ directionActions = data.getActionList(ConfPaths.BLOCKBREAK_DIRECTION_ACTIONS, Permissions.BLOCKBREAK_DIRECTION);
+ noswingCheck = data.getBoolean(ConfPaths.BLOCKBREAK_NOSWING_CHECK);
+ noswingActions = data.getActionList(ConfPaths.BLOCKBREAK_NOSWING_ACTIONS, Permissions.BLOCKBREAK_NOSWING);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java
new file mode 100644
index 000000000..dcf39adfc
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java
@@ -0,0 +1,29 @@
+package com.earth2me.essentials.anticheat.checks.blockbreak;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+
+
+/**
+ * Player specific data for the blockbreak checks
+ *
+ */
+public class BlockBreakData implements DataItem
+{
+ // Keep track of violation levels for the three checks
+ public double reachVL = 0.0D;
+ public double directionVL = 0.0D;
+ public double noswingVL = 0.0D;
+ // Used for the penalty time feature of the direction check
+ public long directionLastViolationTime = 0;
+ // Have a nicer/simpler way to work with block locations instead of
+ // Bukkits own "Location" class
+ public final SimpleLocation instaBrokenBlockLocation = new SimpleLocation();
+ public final SimpleLocation brokenBlockLocation = new SimpleLocation();
+ public final SimpleLocation lastDamagedBlock = new SimpleLocation();
+ // indicate if the player swung his arm since he got checked last time
+ public boolean armswung = true;
+ // For logging, remember the reachDistance that was calculated in the
+ // reach check
+ public double reachDistance;
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java
new file mode 100644
index 000000000..d0c7b10f8
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java
@@ -0,0 +1,100 @@
+package com.earth2me.essentials.anticheat.checks.blockbreak;
+
+import java.util.Locale;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+
+
+/**
+ * The DirectionCheck will find out if a player tried to interact with something that's not in his field of view.
+ *
+ */
+public class DirectionCheck extends BlockBreakCheck
+{
+ public DirectionCheck(NoCheat plugin)
+ {
+ super(plugin, "blockbreak.direction");
+ }
+
+ public boolean check(final NoCheatPlayer player, final BlockBreakData data, final BlockBreakConfig ccblockbreak)
+ {
+
+ final SimpleLocation brokenBlock = data.brokenBlockLocation;
+ boolean cancel = false;
+
+ // How far "off" is the player with his aim. We calculate from the
+ // players eye location and view direction to the center of the target
+ // block. If the line of sight is more too far off, "off" will be
+ // bigger than 0
+ double off = CheckUtil.directionCheck(player, brokenBlock.x + 0.5D, brokenBlock.y + 0.5D, brokenBlock.z + 0.5D, 1D, 1D, ccblockbreak.directionPrecision);
+
+ final long time = System.currentTimeMillis();
+
+ if (off < 0.1D)
+ {
+ // Player did likely nothing wrong
+ // reduce violation counter to reward him
+ data.directionVL *= 0.9D;
+ }
+ else
+ {
+ // Player failed the check
+ // Increment violation counter
+ if (data.instaBrokenBlockLocation.equals(brokenBlock))
+ {
+ // Instabreak block failures are very common, so don't be as
+ // hard on people failing them
+ off /= 5;
+ }
+
+ // Add to the overall violation level of the check and add to
+ // statistics
+ data.directionVL += off;
+ incrementStatistics(player, Id.BB_DIRECTION, off);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, ccblockbreak.directionActions, data.directionVL);
+
+ if (cancel)
+ {
+ // if we should cancel, remember the current time too
+ data.directionLastViolationTime = time;
+ }
+ }
+
+ // If the player is still in penalty time, cancel the event anyway
+ if (data.directionLastViolationTime + ccblockbreak.directionPenaltyTime > time)
+ {
+ // A saveguard to avoid people getting stuck in penalty time
+ // indefinitely in case the system time of the server gets changed
+ if (data.directionLastViolationTime > time)
+ {
+ data.directionLastViolationTime = 0;
+ }
+
+ // He is in penalty time, therefore request cancelling of the event
+ return true;
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).directionVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java
new file mode 100644
index 000000000..af53c419f
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java
@@ -0,0 +1,61 @@
+package com.earth2me.essentials.anticheat.checks.blockbreak;
+
+import java.util.Locale;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+
+
+/**
+ * We require that the player moves his arm between blockbreaks, this is what gets checked here.
+ *
+ */
+public class NoswingCheck extends BlockBreakCheck
+{
+ public NoswingCheck(NoCheat plugin)
+ {
+ super(plugin, "blockbreak.noswing");
+ }
+
+ public boolean check(NoCheatPlayer player, BlockBreakData data, BlockBreakConfig cc)
+ {
+
+ boolean cancel = false;
+
+ // did he swing his arm before
+ if (data.armswung)
+ {
+ // "consume" the flag
+ data.armswung = false;
+ // reward with lowering of the violation level
+ data.noswingVL *= 0.90D;
+ }
+ else
+ {
+ // he failed, increase vl and statistics
+ data.noswingVL += 1;
+ incrementStatistics(player, Id.BB_NOSWING, 1);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.noswingActions, data.noswingVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).noswingVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java
new file mode 100644
index 000000000..b764eedcb
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java
@@ -0,0 +1,75 @@
+package com.earth2me.essentials.anticheat.checks.blockbreak;
+
+import java.util.Locale;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+
+
+/**
+ * The reach check will find out if a player interacts with something that's too far away
+ *
+ */
+public class ReachCheck extends BlockBreakCheck
+{
+ public ReachCheck(NoCheat plugin)
+ {
+ super(plugin, "blockbreak.reach");
+ }
+
+ public boolean check(NoCheatPlayer player, BlockBreakData data, BlockBreakConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final SimpleLocation brokenBlock = data.brokenBlockLocation;
+
+ // Distance is calculated from eye location to center of targeted block
+ // If the player is further away from his target than allowed, the
+ // difference will be assigned to "distance"
+ final double distance = CheckUtil.reachCheck(player, brokenBlock.x + 0.5D, brokenBlock.y + 0.5D, brokenBlock.z + 0.5D, player.isCreative() ? cc.reachDistance + 2 : cc.reachDistance);
+
+ if (distance <= 0D)
+ {
+ // Player passed the check, reward him
+ data.reachVL *= 0.9D;
+ }
+ else
+ {
+ // He failed, increment violation level and statistics
+ data.reachVL += distance;
+ incrementStatistics(player, Id.BB_REACH, distance);
+
+ // Remember how much further than allowed he tried to reach for
+ // logging, if necessary
+ data.reachDistance = distance;
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.reachActions, data.reachVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).reachVL);
+ }
+ else if (wildcard == ParameterName.REACHDISTANCE)
+ {
+ return String.format(Locale.US, "%.2f", getData(player).reachDistance);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java
new file mode 100644
index 000000000..e20a74ca9
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java
@@ -0,0 +1,99 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+import java.util.Locale;
+
+
+/**
+ * Abstract base class for BlockPlace checks, provides some convenience methods for access to data and config that's
+ * relevant to this checktype
+ */
+public abstract class BlockPlaceCheck extends Check
+{
+ private static final String id = "blockplace";
+
+ public BlockPlaceCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+ if (wildcard == ParameterName.PLACE_LOCATION)
+ {
+ SimpleLocation l = getData(player).blockPlaced;
+ if (l.isSet())
+ {
+ return String.format(Locale.US, "%d %d %d", l.x, l.y, l.z);
+ }
+ else
+ {
+ return "null";
+ }
+ }
+ else if (wildcard == ParameterName.PLACE_AGAINST)
+ {
+ SimpleLocation l = getData(player).blockPlacedAgainst;
+ if (l.isSet())
+ {
+ return String.format(Locale.US, "%d %d %d", l.x, l.y, l.z);
+ }
+ else
+ {
+ return "null";
+ }
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+
+ /**
+ * Get the "BlockPlaceData" object that belongs to the player. Will ensure that such a object exists and if not,
+ * create one
+ *
+ * @param player
+ * @return
+ */
+ public static BlockPlaceData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ BlockPlaceData data = base.get(id);
+ if (data == null)
+ {
+ data = new BlockPlaceData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the BlockPlaceConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static BlockPlaceConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static BlockPlaceConfig getConfig(ConfigurationCacheStore cache)
+ {
+ BlockPlaceConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new BlockPlaceConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java
new file mode 100644
index 000000000..253982bd1
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java
@@ -0,0 +1,97 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockPlaceEvent;
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.config.Permissions;
+
+
+/**
+ * Central location to listen to Block-related events and dispatching them to checks
+ *
+ */
+public class BlockPlaceCheckListener implements Listener, EventManager
+{
+ private final ReachCheck reachCheck;
+ private final DirectionCheck directionCheck;
+ private final NoCheat plugin;
+
+ public BlockPlaceCheckListener(NoCheat plugin)
+ {
+
+ this.plugin = plugin;
+
+ reachCheck = new ReachCheck(plugin);
+ directionCheck = new DirectionCheck(plugin);
+ }
+
+ /**
+ * We listen to BlockPlace events for obvious reasons
+ *
+ * @param event the BlockPlace event
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ protected void handleBlockPlaceEvent(BlockPlaceEvent event)
+ {
+
+ if (event.isCancelled() || event.getBlock() == null || event.getBlockAgainst() == null)
+ {
+ return;
+ }
+
+ boolean cancelled = false;
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final BlockPlaceConfig cc = BlockPlaceCheck.getConfig(player);
+ final BlockPlaceData data = BlockPlaceCheck.getData(player);
+
+ // Remember these locations and put them in a simpler "format"
+ data.blockPlaced.set(event.getBlock());
+ data.blockPlacedAgainst.set(event.getBlockAgainst());
+
+ // Now do the actual checks
+
+ // First the reach check
+ if (cc.reachCheck && !player.hasPermission(Permissions.BLOCKPLACE_REACH))
+ {
+ cancelled = reachCheck.check(player, data, cc);
+ }
+
+ // Second the direction check
+ if (!cancelled && cc.directionCheck && !player.hasPermission(Permissions.BLOCKPLACE_DIRECTION))
+ {
+ cancelled = directionCheck.check(player, data, cc);
+ }
+
+ // If one of the checks requested to cancel the event, do so
+ if (cancelled)
+ {
+ event.setCancelled(cancelled);
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ BlockPlaceConfig bp = BlockPlaceCheck.getConfig(cc);
+
+ if (bp.reachCheck)
+ {
+ s.add("blockplace.reach");
+ }
+ if (bp.directionCheck)
+ {
+ s.add("blockplace.direction");
+ }
+
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java
new file mode 100644
index 000000000..26c8d0f6d
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java
@@ -0,0 +1,37 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+import com.earth2me.essentials.anticheat.ConfigItem;
+import com.earth2me.essentials.anticheat.actions.types.ActionList;
+import com.earth2me.essentials.anticheat.config.ConfPaths;
+import com.earth2me.essentials.anticheat.config.NoCheatConfiguration;
+import com.earth2me.essentials.anticheat.config.Permissions;
+
+
+/**
+ * Configurations specific for the "BlockPlace" checks Every world gets one of these assigned to it, or if a world
+ * doesn't get it's own, it will use the "global" version
+ *
+ */
+public class BlockPlaceConfig implements ConfigItem
+{
+ public final boolean reachCheck;
+ public final double reachDistance;
+ public final ActionList reachActions;
+ public final boolean directionCheck;
+ public final ActionList directionActions;
+ public final long directionPenaltyTime;
+ public final double directionPrecision;
+
+ public BlockPlaceConfig(NoCheatConfiguration data)
+ {
+
+ reachCheck = data.getBoolean(ConfPaths.BLOCKPLACE_REACH_CHECK);
+ reachDistance = 535D / 100D;
+ reachActions = data.getActionList(ConfPaths.BLOCKPLACE_REACH_ACTIONS, Permissions.BLOCKPLACE_REACH);
+
+ directionCheck = data.getBoolean(ConfPaths.BLOCKPLACE_DIRECTION_CHECK);
+ directionPenaltyTime = data.getInt(ConfPaths.BLOCKPLACE_DIRECTION_PENALTYTIME);
+ directionPrecision = ((double)data.getInt(ConfPaths.BLOCKPLACE_DIRECTION_PRECISION)) / 100D;
+ directionActions = data.getActionList(ConfPaths.BLOCKPLACE_DIRECTION_ACTIONS, Permissions.BLOCKPLACE_DIRECTION);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java
new file mode 100644
index 000000000..47ff9d58a
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java
@@ -0,0 +1,25 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+
+
+/**
+ * Player specific data for the blockbreak checks
+ *
+ */
+public class BlockPlaceData implements DataItem
+{
+ // Keep track of violation levels for the two checks
+ public double reachVL = 0.0D;
+ public double directionVL = 0.0D;
+ // Used for the penalty time feature of the direction check
+ public long directionLastViolationTime = 0;
+ // Have a nicer/simpler way to work with block locations instead of
+ // Bukkits own "Location" class
+ public final SimpleLocation blockPlacedAgainst = new SimpleLocation();
+ public final SimpleLocation blockPlaced = new SimpleLocation();
+ // For logging, remember the reachDistance that was calculated in the
+ // reach check
+ public double reachdistance;
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/DirectionCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/DirectionCheck.java
new file mode 100644
index 000000000..8aa782d19
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/DirectionCheck.java
@@ -0,0 +1,131 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import org.bukkit.Location;
+
+
+/**
+ * The DirectionCheck will find out if a player tried to interact with something that's not in his field of view.
+ *
+ */
+public class DirectionCheck extends BlockPlaceCheck
+{
+ public DirectionCheck(NoCheat plugin)
+ {
+ super(plugin, "blockplace.direction");
+ }
+
+ public boolean check(NoCheatPlayer player, BlockPlaceData data, BlockPlaceConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final SimpleLocation blockPlaced = data.blockPlaced;
+ final SimpleLocation blockPlacedAgainst = data.blockPlacedAgainst;
+
+ // How far "off" is the player with his aim. We calculate from the
+ // players eye location and view direction to the center of the target
+ // block. If the line of sight is more too far off, "off" will be
+ // bigger than 0
+ double off = CheckUtil.directionCheck(player, blockPlacedAgainst.x + 0.5D, blockPlacedAgainst.y + 0.5D, blockPlacedAgainst.z + 0.5D, 1D, 1D, cc.directionPrecision);
+
+ // now check if the player is looking at the block from the correct side
+ double off2 = 0.0D;
+
+ // Find out against which face the player tried to build, and if he
+ // stood on the correct side of it
+ Location eyes = player.getPlayer().getEyeLocation();
+ if (blockPlaced.x > blockPlacedAgainst.x)
+ {
+ off2 = blockPlacedAgainst.x + 0.5D - eyes.getX();
+ }
+ else if (blockPlaced.x < blockPlacedAgainst.x)
+ {
+ off2 = -(blockPlacedAgainst.x + 0.5D - eyes.getX());
+ }
+ else if (blockPlaced.y > blockPlacedAgainst.y)
+ {
+ off2 = blockPlacedAgainst.y + 0.5D - eyes.getY();
+ }
+ else if (blockPlaced.y < blockPlacedAgainst.y)
+ {
+ off2 = -(blockPlacedAgainst.y + 0.5D - eyes.getY());
+ }
+ else if (blockPlaced.z > blockPlacedAgainst.z)
+ {
+ off2 = blockPlacedAgainst.z + 0.5D - eyes.getZ();
+ }
+ else if (blockPlaced.z < blockPlacedAgainst.z)
+ {
+ off2 = -(blockPlacedAgainst.z + 0.5D - eyes.getZ());
+ }
+
+ // If he wasn't on the correct side, add that to the "off" value
+ if (off2 > 0.0D)
+ {
+ off += off2;
+ }
+
+ final long time = System.currentTimeMillis();
+
+ if (off < 0.1D)
+ {
+ // Player did nothing wrong
+ // reduce violation counter to reward him
+ data.directionVL *= 0.9D;
+ }
+ else
+ {
+ // Player failed the check
+ // Increment violation counter and statistics
+ data.directionVL += off;
+ incrementStatistics(player, Id.BP_DIRECTION, off);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.directionActions, data.directionVL);
+
+ if (cancel)
+ {
+ // if we should cancel, remember the current time too
+ data.directionLastViolationTime = time;
+ }
+ }
+
+ // If the player is still in penalty time, cancel the event anyway
+ if (data.directionLastViolationTime + cc.directionPenaltyTime > time)
+ {
+ // A safeguard to avoid people getting stuck in penalty time
+ // indefinitely in case the system time of the server gets changed
+ if (data.directionLastViolationTime > time)
+ {
+ data.directionLastViolationTime = 0;
+ }
+
+ // He is in penalty time, therefore request cancelling of the event
+ return true;
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).directionVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/ReachCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/ReachCheck.java
new file mode 100644
index 000000000..6e13a9348
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/ReachCheck.java
@@ -0,0 +1,75 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * The reach check will find out if a player interacts with something that's too far away
+ *
+ */
+public class ReachCheck extends BlockPlaceCheck
+{
+ public ReachCheck(NoCheat plugin)
+ {
+ super(plugin, "blockplace.reach");
+ }
+
+ public boolean check(NoCheatPlayer player, BlockPlaceData data, BlockPlaceConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final SimpleLocation placedAgainstBlock = data.blockPlacedAgainst;
+
+ // Distance is calculated from eye location to center of targeted block
+ // If the player is further away from his target than allowed, the
+ // difference will be assigned to "distance"
+ final double distance = CheckUtil.reachCheck(player, placedAgainstBlock.x + 0.5D, placedAgainstBlock.y + 0.5D, placedAgainstBlock.z + 0.5D, player.isCreative() ? cc.reachDistance + 2 : cc.reachDistance);
+
+ if (distance <= 0D)
+ {
+ // Player passed the check, reward him
+ data.reachVL *= 0.9D;
+ }
+ else
+ {
+ // He failed, increment violation level and statistics
+ data.reachVL += distance;
+ incrementStatistics(player, Id.BP_REACH, distance);
+
+ // Remember how much further than allowed he tried to reach for
+ // logging, if necessary
+ data.reachdistance = distance;
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.reachActions, data.reachVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).reachVL);
+ }
+ else if (wildcard == ParameterName.REACHDISTANCE)
+ {
+ return String.format(Locale.US, "%.2f", getData(player).reachdistance);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java
new file mode 100644
index 000000000..b1f14deec
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java
@@ -0,0 +1,79 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+
+
+/**
+ * Abstract base class for Chat checks, provides some convenience methods for access to data and config that's relevant
+ * to this checktype
+ */
+public abstract class ChatCheck extends Check
+{
+ private static final String id = "chat";
+
+ public ChatCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.TEXT)
+ // Filter colors from the players message when logging
+ {
+ return getData(player).message.replaceAll("\302\247.", "").replaceAll("\247.", "");
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+
+ /**
+ * Get the "ChatData" object that belongs to the player. Will ensure that such a object exists and if not, create
+ * one
+ *
+ * @param player
+ * @return
+ */
+ public static ChatData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ ChatData data = base.get(id);
+ if (data == null)
+ {
+ data = new ChatData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the ChatConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static ChatConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static ChatConfig getConfig(ConfigurationCacheStore cache)
+ {
+ ChatConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new ChatConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java
new file mode 100644
index 000000000..965a374aa
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java
@@ -0,0 +1,108 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerChatEvent;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+
+
+/**
+ * Central location to listen to events that are relevant for the chat checks
+ *
+ */
+public class ChatCheckListener implements Listener, EventManager
+{
+ private final SpamCheck spamCheck;
+ private final ColorCheck colorCheck;
+ private final NoCheat plugin;
+
+ public ChatCheckListener(NoCheat plugin)
+ {
+
+ this.plugin = plugin;
+
+ spamCheck = new SpamCheck(plugin);
+ colorCheck = new ColorCheck(plugin);
+ }
+
+ /**
+ * We listen to PlayerCommandPreprocess events because commands can be used for spamming too.
+ *
+ * @param event The PlayerCommandPreprocess Event
+ */
+ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
+ public void commandPreprocess(final PlayerCommandPreprocessEvent event)
+ {
+ // This type of event is derived from PlayerChatEvent, therefore
+ // just treat it like that
+ chat(event);
+ }
+
+ /**
+ * We listen to PlayerChat events for obvious reasons
+ *
+ * @param event The PlayerChat event
+ */
+ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
+ public void chat(final PlayerChatEvent event)
+ {
+ boolean cancelled = false;
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final ChatConfig cc = ChatCheck.getConfig(player);
+ final ChatData data = ChatCheck.getData(player);
+
+ // Remember the original message
+ data.message = event.getMessage();
+
+ // Now do the actual checks
+
+ // First the spam check
+ if (cc.spamCheck && !player.hasPermission(Permissions.CHAT_SPAM))
+ {
+ cancelled = spamCheck.check(player, data, cc);
+ }
+
+ // Second the color check
+ if (!cancelled && cc.colorCheck && !player.hasPermission(Permissions.CHAT_COLOR))
+ {
+ cancelled = colorCheck.check(player, data, cc);
+ }
+
+ // If one of the checks requested the event to be cancelled, do it
+ if (cancelled)
+ {
+ event.setCancelled(cancelled);
+ }
+ else
+ {
+ // In case one of the events modified the message, make sure that
+ // the new message gets used
+ event.setMessage(data.message);
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ ChatConfig c = ChatCheck.getConfig(cc);
+ if (c.spamCheck)
+ {
+ s.add("chat.spam");
+ }
+ if (c.colorCheck)
+ {
+ s.add("chat.color");
+ }
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java
new file mode 100644
index 000000000..06ad5c9fc
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java
@@ -0,0 +1,64 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+import java.util.LinkedList;
+import java.util.List;
+import com.earth2me.essentials.anticheat.ConfigItem;
+import com.earth2me.essentials.anticheat.actions.types.ActionList;
+import com.earth2me.essentials.anticheat.config.ConfPaths;
+import com.earth2me.essentials.anticheat.config.NoCheatConfiguration;
+import com.earth2me.essentials.anticheat.config.Permissions;
+
+
+/**
+ * Configurations specific for the "Chat" checks Every world gets one of these assigned to it, or if a world doesn't get
+ * it's own, it will use the "global" version
+ *
+ */
+public class ChatConfig implements ConfigItem
+{
+ public final boolean spamCheck;
+ public final String[] spamWhitelist;
+ public final long spamTimeframe;
+ public final int spamMessageLimit;
+ public final int spamCommandLimit;
+ public final ActionList spamActions;
+ public final boolean colorCheck;
+ public final ActionList colorActions;
+
+ public ChatConfig(NoCheatConfiguration data)
+ {
+
+ spamCheck = data.getBoolean(ConfPaths.CHAT_SPAM_CHECK);
+ spamWhitelist = splitWhitelist(data.getString(ConfPaths.CHAT_SPAM_WHITELIST));
+ spamTimeframe = data.getInt(ConfPaths.CHAT_SPAM_TIMEFRAME) * 1000L;
+ spamMessageLimit = data.getInt(ConfPaths.CHAT_SPAM_MESSAGELIMIT);
+ spamCommandLimit = data.getInt(ConfPaths.CHAT_SPAM_COMMANDLIMIT);
+ spamActions = data.getActionList(ConfPaths.CHAT_SPAM_ACTIONS, Permissions.CHAT_SPAM);
+ colorCheck = data.getBoolean(ConfPaths.CHAT_COLOR_CHECK);
+ colorActions = data.getActionList(ConfPaths.CHAT_COLOR_ACTIONS, Permissions.CHAT_COLOR);
+ }
+
+ /**
+ * Convenience method to split a string into an array on every occurance of the "," character, removing all
+ * whitespaces before and after it too.
+ *
+ * @param string The string containing text seperated by ","
+ * @return An array of the seperate texts
+ */
+ private String[] splitWhitelist(String string)
+ {
+
+ List<String> strings = new LinkedList<String>();
+ string = string.trim();
+
+ for (String s : string.split(","))
+ {
+ if (s != null && s.trim().length() > 0)
+ {
+ strings.add(s.trim());
+ }
+ }
+
+ return strings.toArray(new String[strings.size()]);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java
new file mode 100644
index 000000000..b05cb2579
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java
@@ -0,0 +1,22 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+import com.earth2me.essentials.anticheat.DataItem;
+
+
+/**
+ * Player specific data for the chat checks
+ *
+ */
+public class ChatData implements DataItem
+{
+ // Keep track of the violation levels for the two checks
+ public int spamVL;
+ public int colorVL;
+ // Count messages and commands
+ public int messageCount = 0;
+ public int commandCount = 0;
+ // Remember when the last check time period started
+ public long spamLastTime = 0;
+ // Remember the last chat message or command for logging purposes
+ public String message = "";
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java
new file mode 100644
index 000000000..2468c7065
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java
@@ -0,0 +1,50 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+import java.util.Locale;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+
+
+public class ColorCheck extends ChatCheck
+{
+ public ColorCheck(NoCheat plugin)
+ {
+ super(plugin, "chat.color");
+ }
+
+ public boolean check(NoCheatPlayer player, ChatData data, ChatConfig cc)
+ {
+
+ if (data.message.contains("\247"))
+ {
+
+ data.colorVL += 1;
+ incrementStatistics(player, Id.CHAT_COLOR, 1);
+
+ boolean filter = executeActions(player, cc.colorActions, data.colorVL);
+
+ if (filter)
+ {
+ // Remove color codes
+ data.message = data.message.replaceAll("\302\247.", "").replaceAll("\247.", "");
+ }
+ }
+
+ return false;
+ }
+
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).colorVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java
new file mode 100644
index 000000000..8bf893091
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java
@@ -0,0 +1,96 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * The SpamCheck will count messages and commands over a short timeframe to see if the player tried to send too many of
+ * them
+ *
+ */
+public class SpamCheck extends ChatCheck
+{
+ public SpamCheck(NoCheat plugin)
+ {
+ super(plugin, "chat.spam");
+ }
+
+ public boolean check(NoCheatPlayer player, ChatData data, ChatConfig cc)
+ {
+
+ boolean cancel = false;
+ // Maybe it's a command and on the whitelist
+ for (String s : cc.spamWhitelist)
+ {
+ if (data.message.startsWith(s))
+ {
+ // It is
+ return false;
+ }
+ }
+
+ int commandLimit = cc.spamCommandLimit;
+ int messageLimit = cc.spamMessageLimit;
+ long timeframe = cc.spamTimeframe;
+
+ final long time = System.currentTimeMillis();
+
+ // Has enough time passed? Then reset the counters
+ if (data.spamLastTime + timeframe <= time)
+ {
+ data.spamLastTime = time;
+ data.messageCount = 0;
+ data.commandCount = 0;
+ }
+ // Security check, if the system time changes
+ else if (data.spamLastTime > time)
+ {
+ data.spamLastTime = Integer.MIN_VALUE;
+ }
+
+ // Increment appropriate counter
+ if (data.message.startsWith("/"))
+ {
+ data.commandCount++;
+ }
+ else
+ {
+ data.messageCount++;
+ }
+
+ // Did the player go over the limit on at least one of the counters?
+ if (data.messageCount > messageLimit || data.commandCount > commandLimit)
+ {
+
+ // Set the vl as the number of messages above the limit and
+ // increment statistics
+ data.spamVL = Math.max(0, data.messageCount - messageLimit);
+ data.spamVL += Math.max(0, data.commandCount - commandLimit);
+ incrementStatistics(player, Id.CHAT_SPAM, 1);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.spamActions, data.spamVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).spamVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java
new file mode 100644
index 000000000..93ce58221
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java
@@ -0,0 +1,120 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityComplex;
+import net.minecraft.server.EntityComplexPart;
+
+
+/**
+ * The DirectionCheck will find out if a player tried to interact with something that's not in his field of view.
+ *
+ */
+public class DirectionCheck extends FightCheck
+{
+ public DirectionCheck(NoCheat plugin)
+ {
+ super(plugin, "fight.direction", Permissions.FIGHT_DIRECTION);
+ }
+
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final long time = System.currentTimeMillis();
+
+ // Get the damagee (entity that got hit)
+ Entity entity = data.damagee;
+
+ // Safeguard, if entity is complex, this check will fail
+ // due to giant and hard to define hitboxes
+ if (entity instanceof EntityComplex || entity instanceof EntityComplexPart)
+ {
+ return false;
+ }
+
+ // Find out how wide the entity is
+ final float width = entity.length > entity.width ? entity.length : entity.width;
+ // entity.height is broken and will always be 0, therefore
+ // calculate height instead based on boundingBox
+ final double height = entity.boundingBox.e - entity.boundingBox.b;
+
+ // How far "off" is the player with his aim. We calculate from the
+ // players eye location and view direction to the center of the target
+ // entity. If the line of sight is more too far off, "off" will be
+ // bigger than 0
+ final double off = CheckUtil.directionCheck(player, entity.locX, entity.locY + (height / 2D), entity.locZ, width, height, cc.directionPrecision);
+
+ if (off < 0.1D)
+ {
+ // Player did probably nothing wrong
+ // reduce violation counter to reward him
+ data.directionVL *= 0.80D;
+ }
+ else
+ {
+ // Player failed the check
+ // Increment violation counter and statistics, but only if there
+ // wasn't serious lag
+ if (!plugin.skipCheck())
+ {
+ double sqrt = Math.sqrt(off);
+ data.directionVL += sqrt;
+ incrementStatistics(player, Id.FI_DIRECTION, sqrt);
+ }
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.directionActions, data.directionVL);
+
+ if (cancel)
+ {
+ // if we should cancel, remember the current time too
+ data.directionLastViolationTime = time;
+ }
+ }
+
+ // If the player is still in penalty time, cancel the event anyway
+ if (data.directionLastViolationTime + cc.directionPenaltyTime > time)
+ {
+ // A safeguard to avoid people getting stuck in penalty time
+ // indefinitely in case the system time of the server gets changed
+ if (data.directionLastViolationTime > time)
+ {
+ data.directionLastViolationTime = 0;
+ }
+
+ // He is in penalty time, therefore request cancelling of the event
+ return true;
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.directionCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).directionVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheck.java
new file mode 100644
index 000000000..f8dd4e3db
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheck.java
@@ -0,0 +1,69 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+
+
+/**
+ * Abstract base class for Fight checks, provides some convenience methods for access to data and config that's relevant
+ * to this checktype
+ */
+public abstract class FightCheck extends Check
+{
+ private static final String id = "fight";
+ public final String permission;
+
+ public FightCheck(NoCheat plugin, String name, String permission)
+ {
+ super(plugin, id, name);
+ this.permission = permission;
+ }
+
+ public abstract boolean check(NoCheatPlayer player, FightData data, FightConfig cc);
+
+ public abstract boolean isEnabled(FightConfig cc);
+
+ /**
+ * Get the "FightData" object that belongs to the player. Will ensure that such a object exists and if not, create
+ * one
+ *
+ * @param player
+ * @return
+ */
+ public static FightData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ FightData data = base.get(id);
+ if (data == null)
+ {
+ data = new FightData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the FightConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static FightConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static FightConfig getConfig(ConfigurationCacheStore cache)
+ {
+ FightConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new FightConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java
new file mode 100644
index 000000000..fc1ea160c
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java
@@ -0,0 +1,291 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.craftbukkit.entity.CraftEntity;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.entity.EntityRegainHealthEvent;
+import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason;
+import org.bukkit.event.player.PlayerAnimationEvent;
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+
+
+/**
+ * Central location to listen to events that are relevant for the fight checks
+ *
+ */
+public class FightCheckListener implements Listener, EventManager
+{
+ private final List<FightCheck> checks;
+ private final GodmodeCheck godmodeCheck;
+ private final InstanthealCheck instanthealCheck;
+ private final NoCheat plugin;
+
+ public FightCheckListener(NoCheat plugin)
+ {
+
+ this.checks = new ArrayList<FightCheck>(4);
+
+ // Keep these in a list, because they can be executed in a bundle
+ this.checks.add(new SpeedCheck(plugin));
+ this.checks.add(new NoswingCheck(plugin));
+ this.checks.add(new DirectionCheck(plugin));
+ this.checks.add(new ReachCheck(plugin));
+
+ this.godmodeCheck = new GodmodeCheck(plugin);
+ this.instanthealCheck = new InstanthealCheck(plugin);
+
+ this.plugin = plugin;
+ }
+
+ /**
+ * We listen to EntityDamage events for obvious reasons
+ *
+ * @param event The EntityDamage Event
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void entityDamage(final EntityDamageEvent event)
+ {
+
+ // Filter some unwanted events right now
+ if (event.isCancelled() || !(event instanceof EntityDamageByEntityEvent))
+ {
+ return;
+ }
+
+ final EntityDamageByEntityEvent e = (EntityDamageByEntityEvent)event;
+ if (!(e.getDamager() instanceof Player))
+ {
+ return;
+ }
+
+ if (e.getCause() == DamageCause.ENTITY_ATTACK)
+ {
+ normalDamage(e);
+ }
+ else if (e.getCause() == DamageCause.CUSTOM)
+ {
+ customDamage(e);
+ }
+ }
+
+ /**
+ * We listen to EntityDamage events (again) for obvious reasons
+ *
+ * @param event The EntityDamage Event
+ */
+ @EventHandler(priority = EventPriority.LOW)
+ public void entityDamageForGodmodeCheck(final EntityDamageEvent event)
+ {
+
+ if (event.isCancelled())
+ {
+ return;
+ }
+
+ // Filter unwanted events right here
+ final Entity entity = event.getEntity();
+ if (!(entity instanceof Player) || entity.isDead())
+ {
+ return;
+ }
+
+ NoCheatPlayer player = plugin.getPlayer((Player)entity);
+ FightConfig cc = FightCheck.getConfig(player);
+
+ if (!godmodeCheck.isEnabled(cc) || player.hasPermission(godmodeCheck.permission))
+ {
+ return;
+ }
+
+ FightData data = FightCheck.getData(player);
+
+ // Run the godmode check on the attacked player
+ boolean cancelled = godmodeCheck.check(plugin.getPlayer((Player)entity), data, cc);
+
+ // It requested to "cancel" the players invulnerability, so set his
+ // noDamageTicks to 0
+ if (cancelled)
+ {
+ // Remove the invulnerability from the player
+ player.getPlayer().setNoDamageTicks(0);
+ }
+ }
+
+ /**
+ * We listen to EntityRegainHealth events of type "Satiated" for instantheal check
+ *
+ * @param event The EntityRegainHealth Event
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void satiatedRegen(final EntityRegainHealthEvent event)
+ {
+
+ if (!(event.getEntity() instanceof Player) || event.isCancelled() || event.getRegainReason() != RegainReason.SATIATED)
+ {
+ return;
+ }
+
+ boolean cancelled = false;
+
+ NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
+ FightConfig config = FightCheck.getConfig(player);
+
+ if (!instanthealCheck.isEnabled(config) || player.hasPermission(instanthealCheck.permission))
+ {
+ return;
+ }
+
+ FightData data = FightCheck.getData(player);
+
+ cancelled = instanthealCheck.check(player, data, config);
+
+ if (cancelled)
+ {
+ event.setCancelled(true);
+ }
+ }
+
+ /**
+ * A player attacked something with DamageCause ENTITY_ATTACK. That's most likely what we want to really check.
+ *
+ * @param event The EntityDamageByEntityEvent
+ */
+ private void normalDamage(final EntityDamageByEntityEvent event)
+ {
+
+ final Player damager = (Player)event.getDamager();
+
+ final NoCheatPlayer player = plugin.getPlayer(damager);
+ final FightConfig cc = FightCheck.getConfig(player);
+ final FightData data = FightCheck.getData(player);
+
+ // For some reason we decided to skip this event anyway
+ if (data.skipNext)
+ {
+ data.skipNext = false;
+ return;
+ }
+
+ boolean cancelled = false;
+
+ // Get the attacked entity and remember it
+ data.damagee = ((CraftEntity)event.getEntity()).getHandle();
+
+ // Run through the four main checks
+ for (FightCheck check : checks)
+ {
+ // If it should be executed, do it
+ if (!cancelled && check.isEnabled(cc) && !player.hasPermission(check.permission))
+ {
+ cancelled = check.check(player, data, cc);
+ }
+ }
+
+ // Forget the attacked entity (to allow garbage collecting etc.
+ data.damagee = null;
+
+ // One of the checks requested the event to be cancelled, so do it
+ if (cancelled)
+ {
+ event.setCancelled(cancelled);
+ }
+ }
+
+ /**
+ * There is an unofficial agreement that if a plugin wants an attack to not get checked by NoCheat, it either has to
+ * use a Damage type different from ENTITY_ATTACK or fire an event with damage type CUSTOM and damage 0 directly
+ * before the to-be-ignored event.
+ *
+ * @param event The EntityDamageByEntityEvent
+ */
+ private void customDamage(final EntityDamageByEntityEvent event)
+ {
+
+ final Player damager = (Player)event.getDamager();
+ final NoCheatPlayer player = plugin.getPlayer(damager);
+
+ final FightData data = FightCheck.getData(player);
+
+ // Skip the next damage event, because it is with high probability
+ // something from the Heroes plugin
+ data.skipNext = true;
+
+ return;
+ }
+
+ /**
+ * We listen to death events to prevent a very specific method of doing godmode.
+ *
+ * @param event The EntityDeathEvent
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ protected void death(final EntityDeathEvent event)
+ {
+ // Only interested in dying players
+ if (!(event.getEntity() instanceof CraftPlayer))
+ {
+ return;
+ }
+
+ godmodeCheck.death((CraftPlayer)event.getEntity());
+ }
+
+ /**
+ * We listen to PlayerAnimationEvent because it is used for arm swinging
+ *
+ * @param event The PlayerAnimationEvent
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ protected void armSwing(final PlayerAnimationEvent event)
+ {
+ // Set a flag telling us that the arm has been swung
+ FightCheck.getData(plugin.getPlayer(event.getPlayer())).armswung = true;
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ FightConfig f = FightCheck.getConfig(cc);
+
+ if (f.directionCheck)
+ {
+ s.add("fight.direction");
+ }
+ if (f.noswingCheck)
+ {
+ s.add("fight.noswing");
+ }
+ if (f.reachCheck)
+ {
+ s.add("fight.reach");
+ }
+ if (f.speedCheck)
+ {
+ s.add("fight.speed");
+ }
+ if (f.godmodeCheck)
+ {
+ s.add("fight.godmode");
+ }
+ if (f.instanthealCheck)
+ {
+ s.add("fight.instantHeal");
+ }
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java
new file mode 100644
index 000000000..9a36128ae
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java
@@ -0,0 +1,58 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.ConfigItem;
+import com.earth2me.essentials.anticheat.actions.types.ActionList;
+import com.earth2me.essentials.anticheat.config.ConfPaths;
+import com.earth2me.essentials.anticheat.config.NoCheatConfiguration;
+import com.earth2me.essentials.anticheat.config.Permissions;
+
+
+/**
+ * Configurations specific for the "Fight" checks Every world gets one of these assigned to it, or if a world doesn't
+ * get it's own, it will use the "global" version
+ *
+ */
+public class FightConfig implements ConfigItem
+{
+ public final boolean directionCheck;
+ public final double directionPrecision;
+ public final ActionList directionActions;
+ public final long directionPenaltyTime;
+ public final boolean noswingCheck;
+ public final ActionList noswingActions;
+ public final boolean reachCheck;
+ public final double reachLimit;
+ public final long reachPenaltyTime;
+ public final ActionList reachActions;
+ public final int speedAttackLimit;
+ public final ActionList speedActions;
+ public final boolean speedCheck;
+ public final boolean godmodeCheck;
+ public final ActionList godmodeActions;
+ public final boolean instanthealCheck;
+ public final ActionList instanthealActions;
+
+ public FightConfig(NoCheatConfiguration data)
+ {
+
+ directionCheck = data.getBoolean(ConfPaths.FIGHT_DIRECTION_CHECK);
+ directionPrecision = ((double)(data.getInt(ConfPaths.FIGHT_DIRECTION_PRECISION))) / 100D;
+ directionPenaltyTime = data.getInt(ConfPaths.FIGHT_DIRECTION_PENALTYTIME);
+ directionActions = data.getActionList(ConfPaths.FIGHT_DIRECTION_ACTIONS, Permissions.FIGHT_DIRECTION);
+ noswingCheck = data.getBoolean(ConfPaths.FIGHT_NOSWING_CHECK);
+ noswingActions = data.getActionList(ConfPaths.FIGHT_NOSWING_ACTIONS, Permissions.FIGHT_NOSWING);
+ reachCheck = data.getBoolean(ConfPaths.FIGHT_REACH_CHECK);
+ reachLimit = ((double)(data.getInt(ConfPaths.FIGHT_REACH_LIMIT))) / 100D;
+ reachPenaltyTime = data.getInt(ConfPaths.FIGHT_REACH_PENALTYTIME);
+ reachActions = data.getActionList(ConfPaths.FIGHT_REACH_ACTIONS, Permissions.FIGHT_REACH);
+ speedCheck = data.getBoolean(ConfPaths.FIGHT_SPEED_CHECK);
+ speedActions = data.getActionList(ConfPaths.FIGHT_SPEED_ACTIONS, Permissions.FIGHT_SPEED);
+ speedAttackLimit = data.getInt(ConfPaths.FIGHT_SPEED_ATTACKLIMIT);
+
+ godmodeCheck = data.getBoolean(ConfPaths.FIGHT_GODMODE_CHECK);
+ godmodeActions = data.getActionList(ConfPaths.FIGHT_GODMODE_ACTIONS, Permissions.FIGHT_GODMODE);
+
+ instanthealCheck = data.getBoolean(ConfPaths.FIGHT_INSTANTHEAL_CHECK);
+ instanthealActions = data.getActionList(ConfPaths.FIGHT_INSTANTHEAL_ACTIONS, Permissions.FIGHT_INSTANTHEAL);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java
new file mode 100644
index 000000000..9f3a5a5d4
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java
@@ -0,0 +1,40 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import net.minecraft.server.Entity;
+
+
+/**
+ * Player specific data for the fight checks
+ *
+ */
+public class FightData implements DataItem
+{
+ // Keep track of the violation levels of the checks
+ public double directionVL;
+ public double noswingVL;
+ public double reachVL;
+ public int speedVL;
+ public double godmodeVL;
+ public double instanthealVL;
+ // For checks that have penalty time
+ public long directionLastViolationTime;
+ public long reachLastViolationTime;
+ // godmode check needs to know these
+ public long godmodeLastDamageTime;
+ public int godmodeLastAge;
+ public int godmodeBuffer = 40;
+ // last time player regenerated health by satiation
+ public long instanthealLastRegenTime;
+ // three seconds buffer to smooth out lag
+ public long instanthealBuffer = 3000;
+ // While handling an event, use this to keep the attacked entity
+ public Entity damagee;
+ // The player swung his arm
+ public boolean armswung = true;
+ // For some reason the next event should be ignored
+ public boolean skipNext = false;
+ // Keep track of time and amount of attacks
+ public long speedTime;
+ public int speedAttackCount;
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java
new file mode 100644
index 000000000..cd0fd6aaa
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java
@@ -0,0 +1,151 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics;
+import java.util.Locale;
+import net.minecraft.server.EntityPlayer;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+
+
+/**
+ * The Godmode Check will find out if a player tried to stay invulnerable after being hit or after dying
+ *
+ */
+public class GodmodeCheck extends FightCheck
+{
+ public GodmodeCheck(NoCheat plugin)
+ {
+ super(plugin, "fight.godmode", Permissions.FIGHT_GODMODE);
+ }
+
+ @Override
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancelled = false;
+
+ long time = System.currentTimeMillis();
+
+ // Check at most once a second
+ if (data.godmodeLastDamageTime + 1000L < time)
+ {
+ data.godmodeLastDamageTime = time;
+
+ // How old is the player now?
+ int age = player.getTicksLived();
+ // How much older did he get?
+ int ageDiff = Math.max(0, age - data.godmodeLastAge);
+ // Is he invulnerable?
+ int nodamageTicks = player.getPlayer().getNoDamageTicks();
+
+ if (nodamageTicks > 0 && ageDiff < 15)
+ {
+ // He is invulnerable and didn't age fast enough, that costs
+ // some points
+ data.godmodeBuffer -= (15 - ageDiff);
+
+ // Still points left?
+ if (data.godmodeBuffer <= 0)
+ {
+ // No, that means VL and statistics increased
+ data.godmodeVL -= data.godmodeBuffer;
+ incrementStatistics(player, Statistics.Id.FI_GODMODE, -data.godmodeBuffer);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancelled = executeActions(player, cc.godmodeActions, data.godmodeVL);
+ }
+ }
+ else
+ {
+ // Give some new points, once a second
+ data.godmodeBuffer += 15;
+ data.godmodeVL *= 0.95;
+ }
+
+ if (data.godmodeBuffer < 0)
+ {
+ // Can't have less than 0
+ data.godmodeBuffer = 0;
+ }
+ else if (data.godmodeBuffer > 30)
+ {
+ // And 30 is enough for simple lag situations
+ data.godmodeBuffer = 30;
+ }
+
+ // Start age counting from a new time
+ data.godmodeLastAge = age;
+ }
+
+ return cancelled;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.godmodeCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).godmodeVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+
+ /**
+ * If a player apparently died, make sure he really dies after some time if he didn't already, by setting up a
+ * Bukkit task
+ *
+ * @param player The player
+ */
+ public void death(CraftPlayer player)
+ {
+ // First check if the player is really dead (e.g. another plugin could
+ // have just fired an artificial event)
+ if (player.getHealth() <= 0 && player.isDead())
+ {
+ try
+ {
+ final EntityPlayer entity = player.getHandle();
+
+ // Schedule a task to be executed in roughly 1.5 seconds
+ Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ // Check again if the player should be dead, and
+ // if the game didn't mark him as dead
+ if (entity.getHealth() <= 0 && !entity.dead)
+ {
+ // Artifically "kill" him
+ entity.deathTicks = 19;
+ entity.a(true);
+ }
+ }
+ catch (Exception e)
+ {
+ }
+ }
+ }, 30);
+ }
+ catch (Exception e)
+ {
+ }
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java
new file mode 100644
index 000000000..33fcbfd3a
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java
@@ -0,0 +1,94 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics;
+import java.util.Locale;
+
+
+/**
+ * The instantheal Check should find out if a player tried to artificially accellerate the health regeneration by food
+ *
+ */
+public class InstanthealCheck extends FightCheck
+{
+ public InstanthealCheck(NoCheat plugin)
+ {
+ super(plugin, "fight.instantheal", Permissions.FIGHT_INSTANTHEAL);
+ }
+
+ @Override
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancelled = false;
+
+ long time = System.currentTimeMillis();
+
+ // security check if system time ran backwards
+ if (data.instanthealLastRegenTime > time)
+ {
+ data.instanthealLastRegenTime = 0;
+ return false;
+ }
+
+ long difference = time - (data.instanthealLastRegenTime + 3500L);
+
+ data.instanthealBuffer += difference;
+
+ if (data.instanthealBuffer < 0)
+ {
+ // Buffer has been fully consumed
+ // Increase vl and statistics
+ double vl = data.instanthealVL -= data.instanthealBuffer / 1000;
+ incrementStatistics(player, Statistics.Id.FI_INSTANTHEAL, vl);
+
+ data.instanthealBuffer = 0;
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancelled = executeActions(player, cc.instanthealActions, data.instanthealVL);
+ }
+ else
+ {
+ // vl gets decreased
+ data.instanthealVL *= 0.9;
+ }
+
+ // max 2 seconds buffer
+ if (data.instanthealBuffer > 2000L)
+ {
+ data.instanthealBuffer = 2000L;
+ }
+
+ if (!cancelled)
+ {
+ // New reference time
+ data.instanthealLastRegenTime = time;
+ }
+
+ return cancelled;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.instanthealCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).instanthealVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java
new file mode 100644
index 000000000..99d7ac1fd
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java
@@ -0,0 +1,67 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * We require that the player moves his arm between attacks, this is what gets checked here.
+ *
+ */
+public class NoswingCheck extends FightCheck
+{
+ public NoswingCheck(NoCheat plugin)
+ {
+ super(plugin, "fight.noswing", Permissions.FIGHT_NOSWING);
+ }
+
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancel = false;
+
+ // did he swing his arm before?
+ if (data.armswung)
+ {
+ // Yes, reward him with reduction of his vl
+ data.armswung = false;
+ data.noswingVL *= 0.90D;
+ }
+ else
+ {
+ // No, increase vl and statistics
+ data.noswingVL += 1;
+ incrementStatistics(player, Id.FI_NOSWING, 1);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.noswingActions, data.noswingVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.noswingCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).noswingVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/ReachCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/ReachCheck.java
new file mode 100644
index 000000000..c56caed08
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/ReachCheck.java
@@ -0,0 +1,113 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityComplex;
+import net.minecraft.server.EntityComplexPart;
+
+
+/**
+ * The reach check will find out if a player interacts with something that's too far away
+ *
+ */
+public class ReachCheck extends FightCheck
+{
+ public ReachCheck(NoCheat plugin)
+ {
+ super(plugin, "fight.reach", Permissions.FIGHT_REACH);
+ }
+
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final long time = System.currentTimeMillis();
+
+ // Get the width of the damagee
+ Entity entity = data.damagee;
+
+ // Safeguard, if entity is Giant or Ender Dragon, this check will fail
+ // due to giant and hard to define hitboxes
+ if (entity instanceof EntityComplex || entity instanceof EntityComplexPart)
+ {
+ return false;
+ }
+
+ // Distance is calculated from eye location to center of targeted
+ // If the player is further away from his target than allowed, the
+ // difference will be assigned to "distance"
+ final double off = CheckUtil.reachCheck(player, entity.locX, entity.locY + 1.0D, entity.locZ, cc.reachLimit);
+
+ if (off < 0.1D)
+ {
+ // Player did probably nothing wrong
+ // reduce violation counter to reward him
+ data.reachVL *= 0.80D;
+ }
+ else
+ {
+ // Player failed the check
+ // Increment violation counter and statistics
+ // This is influenced by lag, so don't do it if there was lag
+ if (!plugin.skipCheck())
+ {
+ double sqrt = Math.sqrt(off);
+ data.reachVL += sqrt;
+ incrementStatistics(player, Id.FI_REACH, sqrt);
+ }
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.reachActions, data.reachVL);
+
+ if (cancel)
+ {
+ // if we should cancel, remember the current time too
+ data.reachLastViolationTime = time;
+ }
+ }
+
+ // If the player is still in penalty time, cancel the event anyway
+ if (data.reachLastViolationTime + cc.reachPenaltyTime > time)
+ {
+ // A safeguard to avoid people getting stuck in penalty time
+ // indefinitely in case the system time of the server gets changed
+ if (data.reachLastViolationTime > time)
+ {
+ data.reachLastViolationTime = 0;
+ }
+
+ // He is in penalty time, therefore request cancelling of the event
+ return true;
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.reachCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).reachVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java
new file mode 100644
index 000000000..baa7db9c5
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java
@@ -0,0 +1,81 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * The speed check will find out if a player interacts with something that's too far away
+ *
+ */
+public class SpeedCheck extends FightCheck
+{
+ public SpeedCheck(NoCheat plugin)
+ {
+ super(plugin, "fight.speed", Permissions.FIGHT_SPEED);
+ }
+
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final long time = System.currentTimeMillis();
+
+ // Check if one second has passed and reset counters and vl in that case
+ if (data.speedTime + 1000L <= time)
+ {
+ data.speedTime = time;
+ data.speedAttackCount = 0;
+ data.speedVL = 0;
+ }
+
+ // count the attack
+ data.speedAttackCount++;
+
+ // too many attacks
+ if (data.speedAttackCount > cc.speedAttackLimit)
+ {
+ // if there was lag, don't count it towards statistics and vl
+ if (!plugin.skipCheck())
+ {
+ data.speedVL += 1;
+ incrementStatistics(player, Id.FI_SPEED, 1);
+ }
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.speedActions, data.speedVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.speedCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).speedVL);
+ }
+ else if (wildcard == ParameterName.LIMIT)
+ {
+ return String.format(Locale.US, "%d", getConfig(player.getConfigurationStore()).speedAttackLimit);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java
new file mode 100644
index 000000000..2e9d030f7
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java
@@ -0,0 +1,71 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * The DropCheck will find out if a player drops too many items within a short amount of time
+ *
+ */
+public class DropCheck extends InventoryCheck
+{
+ public DropCheck(NoCheat plugin)
+ {
+ super(plugin, "inventory.drop");
+ }
+
+ public boolean check(NoCheatPlayer player, InventoryData data, InventoryConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final long time = System.currentTimeMillis();
+
+ // Has the configured time passed? If so, reset the counter
+ if (data.dropLastTime + cc.dropTimeFrame <= time)
+ {
+ data.dropLastTime = time;
+ data.dropCount = 0;
+ data.dropVL = 0;
+ }
+ // Security check, if the system time changes
+ else if (data.dropLastTime > time)
+ {
+ data.dropLastTime = Integer.MIN_VALUE;
+ }
+
+ data.dropCount++;
+
+ // The player dropped more than he should
+ if (data.dropCount > cc.dropLimit)
+ {
+ // Set vl and increment statistics
+ data.dropVL = data.dropCount - cc.dropLimit;
+ incrementStatistics(player, Id.INV_DROP, 1);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancel = executeActions(player, cc.dropActions, data.dropVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).dropVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java
new file mode 100644
index 000000000..7d4fcf3bb
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java
@@ -0,0 +1,72 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import org.bukkit.event.entity.EntityShootBowEvent;
+
+
+/**
+ * The InstantBowCheck will find out if a player pulled the string of his bow too fast
+ */
+public class InstantBowCheck extends InventoryCheck
+{
+ public InstantBowCheck(NoCheat plugin)
+ {
+ super(plugin, "inventory.instantbow");
+ }
+
+ public boolean check(NoCheatPlayer player, EntityShootBowEvent event, InventoryData data, InventoryConfig cc)
+ {
+
+ boolean cancelled = false;
+
+ long time = System.currentTimeMillis();
+
+ // How fast will the arrow be?
+ float bowForce = event.getForce();
+
+ // Rough estimation of how long pulling the string should've taken
+ long expectedTimeWhenStringDrawn = data.lastBowInteractTime + (int)(bowForce * bowForce * 700F);
+
+ if (expectedTimeWhenStringDrawn < time)
+ {
+ // The player was slow enough, reward him by lowering the vl
+ data.instantBowVL *= 0.90D;
+ }
+ else if (data.lastBowInteractTime > time)
+ {
+ // Security check if time ran backwards, reset
+ data.lastBowInteractTime = 0;
+ }
+ else
+ {
+ // Player was too fast, increase violation level and statistics
+ int vl = ((int)(expectedTimeWhenStringDrawn - time)) / 100;
+ data.instantBowVL += vl;
+ incrementStatistics(player, Id.INV_BOW, vl);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancelled = executeActions(player, cc.bowActions, data.instantBowVL);
+ }
+
+ return cancelled;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).instantBowVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java
new file mode 100644
index 000000000..05a668dd7
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java
@@ -0,0 +1,78 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import org.bukkit.event.entity.FoodLevelChangeEvent;
+
+
+/**
+ * The InstantEatCheck will find out if a player eats his food too fast
+ */
+public class InstantEatCheck extends InventoryCheck
+{
+ public InstantEatCheck(NoCheat plugin)
+ {
+ super(plugin, "inventory.instanteat");
+ }
+
+ public boolean check(NoCheatPlayer player, FoodLevelChangeEvent event, InventoryData data, InventoryConfig cc)
+ {
+
+ // Hunger level change seems to not be the result of eating
+ if (data.foodMaterial == null || event.getFoodLevel() <= player.getPlayer().getFoodLevel())
+ {
+ return false;
+ }
+
+ boolean cancelled = false;
+
+ long time = System.currentTimeMillis();
+ // rough estimation about how long it should take to eat
+ long expectedTimeWhenEatingFinished = data.lastEatInteractTime + 700;
+
+ if (expectedTimeWhenEatingFinished < time)
+ {
+ // Acceptable, reduce VL to reward the player
+ data.instantEatVL *= 0.60D;
+ }
+ else if (data.lastEatInteractTime > time)
+ {
+ // Security test, if time ran backwards, reset
+ data.lastEatInteractTime = 0;
+ }
+ else
+ {
+ // Player was too fast, increase violation level and statistics
+ int vl = ((int)(expectedTimeWhenEatingFinished - time)) / 100;
+ data.instantEatVL += vl;
+ incrementStatistics(player, Id.INV_EAT, vl);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ cancelled = executeActions(player, cc.eatActions, data.instantEatVL);
+ }
+
+ return cancelled;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).instantEatVL);
+ }
+ else if (wildcard == ParameterName.FOOD)
+ {
+ return getData(player).foodMaterial.toString();
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java
new file mode 100644
index 000000000..ad60ffa3e
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java
@@ -0,0 +1,63 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+
+
+/**
+ * Abstract base class for Inventory checks, provides some convenience methods for access to data and config that's
+ * relevant to this checktype
+ */
+public abstract class InventoryCheck extends Check
+{
+ private static final String id = "inventory";
+
+ public InventoryCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ /**
+ * Get the "InventoryData" object that belongs to the player. Will ensure that such a object exists and if not,
+ * create one
+ *
+ * @param player
+ * @return
+ */
+ public static InventoryData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ InventoryData data = base.get(id);
+ if (data == null)
+ {
+ data = new InventoryData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the InventoryConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static InventoryConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static InventoryConfig getConfig(ConfigurationCacheStore cache)
+ {
+ InventoryConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new InventoryConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java
new file mode 100644
index 000000000..f42a37185
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java
@@ -0,0 +1,196 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.entity.EntityShootBowEvent;
+import org.bukkit.event.entity.FoodLevelChangeEvent;
+import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+
+
+/**
+ * Central location to listen to events that are relevant for the inventory checks
+ *
+ */
+public class InventoryCheckListener implements Listener, EventManager
+{
+ private final DropCheck dropCheck;
+ private final InstantBowCheck instantBowCheck;
+ private final InstantEatCheck instantEatCheck;
+ private final NoCheat plugin;
+
+ public InventoryCheckListener(NoCheat plugin)
+ {
+
+ this.dropCheck = new DropCheck(plugin);
+ this.instantBowCheck = new InstantBowCheck(plugin);
+ this.instantEatCheck = new InstantEatCheck(plugin);
+
+ this.plugin = plugin;
+ }
+
+ /**
+ * We listen to DropItem Event for the dropCheck
+ *
+ * @param event The PlayerDropItem Event
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ protected void handlePlayerDropItemEvent(final PlayerDropItemEvent event)
+ {
+
+ if (event.isCancelled() || event.getPlayer().isDead())
+ {
+ return;
+ }
+
+ boolean cancelled = false;
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final InventoryConfig cc = InventoryCheck.getConfig(player);
+ final InventoryData data = InventoryCheck.getData(player);
+
+ // If it should be executed, do it
+ if (cc.dropCheck && !player.hasPermission(Permissions.INVENTORY_DROP))
+ {
+ cancelled = dropCheck.check(player, data, cc);
+ }
+
+ if (cancelled)
+ {
+ // Cancelling drop events is not save (in certain circumstances
+ // items will disappear completely). So don't do it and kick
+ // players instead by default
+ // event.setCancelled(true);
+ }
+ }
+
+ /**
+ * We listen to PlayerInteractEvent for the instantEat and instantBow checks
+ *
+ * @param event The PlayerInteractEvent
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void interact(final PlayerInteractEvent event)
+ {
+
+ // Only interested in right-clicks while holding an item
+ if (!event.hasItem() || !(event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK))
+ {
+ return;
+ }
+
+ NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final InventoryData data = InventoryCheck.getData(player);
+
+ if (event.getItem().getType() == Material.BOW)
+ {
+ // It was a bow, the player starts to pull the string
+ // Remember this time
+ data.lastBowInteractTime = System.currentTimeMillis();
+ }
+ else if (CheckUtil.isFood(event.getItem()))
+ {
+ // It was food, the player starts to eat some food
+ // Remember this time and the type of food
+ data.foodMaterial = event.getItem().getType();
+ data.lastEatInteractTime = System.currentTimeMillis();
+ }
+ else
+ {
+ // Nothing that we are interested in, reset data
+ data.lastBowInteractTime = 0;
+ data.lastEatInteractTime = 0;
+ data.foodMaterial = null;
+ }
+ }
+
+ /**
+ * We listen to FoodLevelChange Event because Bukkit doesn't provide a PlayerFoodEating Event (or whatever it would
+ * be called).
+ *
+ * @param event The FoodLevelChangeEvent
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void foodchanged(final FoodLevelChangeEvent event)
+ {
+ // Only if a player ate food
+ if (!event.isCancelled() && event.getEntity() instanceof Player)
+ {
+ final NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
+ final InventoryConfig cc = InventoryCheck.getConfig(player);
+ final InventoryData data = InventoryCheck.getData(player);
+
+ // Only if he should get checked
+ if (cc.eatCheck && !player.hasPermission(Permissions.INVENTORY_INSTANTEAT))
+ {
+
+ boolean cancelled = instantEatCheck.check(player, event, data, cc);
+
+ // The check requested the foodlevelchange to get cancelled
+ event.setCancelled(cancelled);
+ }
+
+ // Forget the food material, as the info is no longer needed
+ data.foodMaterial = null;
+ }
+
+ }
+
+ /**
+ * We listen to EntityShootBowEvent for the instantbow check
+ *
+ * @param event The EntityShootBowEvent
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void bowfired(final EntityShootBowEvent event)
+ {
+ // Only if a player shot the arrow
+ if (!event.isCancelled() && event.getEntity() instanceof Player)
+ {
+ final NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
+ final InventoryConfig cc = InventoryCheck.getConfig(player);
+
+ // Only if he should get checked
+ if (cc.bowCheck && !player.hasPermission(Permissions.INVENTORY_INSTANTBOW))
+ {
+ final InventoryData data = InventoryCheck.getData(player);
+ boolean cancelled = instantBowCheck.check(player, event, data, cc);
+
+ // The check requested the bowshooting to get cancelled
+ event.setCancelled(cancelled);
+ }
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ InventoryConfig i = InventoryCheck.getConfig(cc);
+ if (i.dropCheck)
+ {
+ s.add("inventory.dropCheck");
+ }
+ if (i.bowCheck)
+ {
+ s.add("inventory.instantbow");
+ }
+ if (i.eatCheck)
+ {
+ s.add("inventory.instanteat");
+ }
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java
new file mode 100644
index 000000000..44f59ff04
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java
@@ -0,0 +1,40 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.ConfigItem;
+import com.earth2me.essentials.anticheat.actions.types.ActionList;
+import com.earth2me.essentials.anticheat.config.ConfPaths;
+import com.earth2me.essentials.anticheat.config.NoCheatConfiguration;
+import com.earth2me.essentials.anticheat.config.Permissions;
+
+
+/**
+ * Configurations specific for the "Inventory" checks Every world gets one of these assigned to it, or if a world
+ * doesn't get it's own, it will use the "global" version
+ *
+ */
+public class InventoryConfig implements ConfigItem
+{
+ public final boolean dropCheck;
+ public final long dropTimeFrame;
+ public final int dropLimit;
+ public final ActionList dropActions;
+ public final boolean bowCheck;
+ public final ActionList bowActions;
+ public final boolean eatCheck;
+ public final ActionList eatActions;
+
+ public InventoryConfig(NoCheatConfiguration data)
+ {
+
+ dropCheck = data.getBoolean(ConfPaths.INVENTORY_DROP_CHECK);
+ dropTimeFrame = data.getInt(ConfPaths.INVENTORY_DROP_TIMEFRAME) * 1000;
+ dropLimit = data.getInt(ConfPaths.INVENTORY_DROP_LIMIT);
+ dropActions = data.getActionList(ConfPaths.INVENTORY_DROP_ACTIONS, Permissions.INVENTORY_DROP);
+
+ bowCheck = data.getBoolean(ConfPaths.INVENTORY_INSTANTBOW_CHECK);
+ bowActions = data.getActionList(ConfPaths.INVENTORY_INSTANTBOW_ACTIONS, Permissions.INVENTORY_INSTANTBOW);
+
+ eatCheck = data.getBoolean(ConfPaths.INVENTORY_INSTANTEAT_CHECK);
+ eatActions = data.getActionList(ConfPaths.INVENTORY_INSTANTEAT_ACTIONS, Permissions.INVENTORY_INSTANTEAT);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java
new file mode 100644
index 000000000..daeef8679
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java
@@ -0,0 +1,25 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import org.bukkit.Material;
+
+
+/**
+ * Player specific data for the inventory checks
+ *
+ */
+public class InventoryData implements DataItem
+{
+ // Keep track of the violation levels of the three checks
+ public int dropVL;
+ public int instantBowVL;
+ public double instantEatVL;
+ // Time and amount of dropped items
+ public long dropLastTime;
+ public int dropCount;
+ // Times when bow shootinhg and eating started
+ public long lastBowInteractTime;
+ public long lastEatInteractTime;
+ // What the player is eating
+ public Material foodMaterial;
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java
new file mode 100644
index 000000000..c96d9f9c3
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java
@@ -0,0 +1,170 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * A check designed for people that are allowed to fly. The complement to the "RunningCheck", which is for people that
+ * aren't allowed to fly, and therefore have tighter rules to obey.
+ *
+ */
+public class FlyingCheck extends MovingCheck
+{
+ public FlyingCheck(NoCheat plugin)
+ {
+ super(plugin, "moving.flying");
+ }
+ // Determined by trial and error, the flying movement speed of the creative
+ // mode
+ private static final double creativeSpeed = 0.60D;
+
+ public PreciseLocation check(NoCheatPlayer player, MovingData data, MovingConfig ccmoving)
+ {
+
+ // The setBack is the location that players may get teleported to when
+ // they fail the check
+ final PreciseLocation setBack = data.runflySetBackPoint;
+
+ final PreciseLocation from = data.from;
+ final PreciseLocation to = data.to;
+
+ // If we have no setback, define one now
+ if (!setBack.isSet())
+ {
+ setBack.set(from);
+ }
+
+ // Used to store the location where the player gets teleported to
+ PreciseLocation newToLocation = null;
+
+ // Before doing anything, do a basic height check to determine if
+ // players are flying too high
+ int maxheight = ccmoving.flyingHeightLimit + player.getPlayer().getWorld().getMaxHeight();
+
+ if (to.y - data.vertFreedom > maxheight)
+ {
+ newToLocation = new PreciseLocation();
+ newToLocation.set(setBack);
+ newToLocation.y = maxheight - 10;
+ return newToLocation;
+ }
+
+ // Calculate some distances
+ final double yDistance = to.y - from.y;
+ final double xDistance = to.x - from.x;
+ final double zDistance = to.z - from.z;
+
+ // How far did the player move horizontally
+ final double horizontalDistance = Math.sqrt((xDistance * xDistance + zDistance * zDistance));
+
+ double resultHoriz = 0;
+ double resultVert = 0;
+ double result = 0;
+
+ // In case of creative game mode give at least 0.60 speed limit horizontal
+ double speedLimitHorizontal = player.isCreative() ? Math.max(creativeSpeed, ccmoving.flyingSpeedLimitHorizontal) : ccmoving.flyingSpeedLimitHorizontal;
+
+ // If the player is affected by potion of swiftness
+ speedLimitHorizontal *= player.getSpeedAmplifier();
+
+ // Finally, determine how far the player went beyond the set limits
+ resultHoriz = Math.max(0.0D, horizontalDistance - data.horizFreedom - speedLimitHorizontal);
+
+ boolean sprinting = player.isSprinting();
+
+ data.bunnyhopdelay--;
+
+ if (resultHoriz > 0 && sprinting)
+ {
+
+ // Try to treat it as a the "bunnyhop" problem
+ // The bunnyhop problem is that landing and immediatly jumping
+ // again leads to a player moving almost twice as far in that step
+ if (data.bunnyhopdelay <= 0 && resultHoriz < 0.4D)
+ {
+ data.bunnyhopdelay = 9;
+ resultHoriz = 0;
+ }
+ }
+
+ resultHoriz *= 100;
+
+ // Is the player affected by the "jumping" potion
+ // This is really just a very, very crude estimation and far from
+ // reality
+ double jumpAmplifier = player.getJumpAmplifier();
+ if (jumpAmplifier > data.lastJumpAmplifier)
+ {
+ data.lastJumpAmplifier = jumpAmplifier;
+ }
+
+ double speedLimitVertical = ccmoving.flyingSpeedLimitVertical * data.lastJumpAmplifier;
+
+ if (data.from.y >= data.to.y && data.lastJumpAmplifier > 0)
+ {
+ data.lastJumpAmplifier--;
+ }
+
+ // super simple, just check distance compared to max distance vertical
+ resultVert = Math.max(0.0D, yDistance - data.vertFreedom - speedLimitVertical) * 100;
+
+ result = resultHoriz + resultVert;
+
+ // The player went to far, either horizontal or vertical
+ if (result > 0)
+ {
+
+ // Increment violation counter and statistics
+ data.runflyVL += result;
+ if (resultHoriz > 0)
+ {
+ incrementStatistics(player, Id.MOV_RUNNING, resultHoriz);
+ }
+
+ if (resultVert > 0)
+ {
+ incrementStatistics(player, Id.MOV_FLYING, resultVert);
+ }
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ boolean cancel = executeActions(player, ccmoving.flyingActions, data.runflyVL);
+
+ // Was one of the actions a cancel? Then really do it
+ if (cancel)
+ {
+ newToLocation = setBack;
+ }
+ }
+
+ // Slowly reduce the violation level with each event
+ data.runflyVL *= 0.97;
+
+ // If the player did not get cancelled, define a new setback point
+ if (newToLocation == null)
+ {
+ setBack.set(to);
+ }
+
+ return newToLocation;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).runflyVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java
new file mode 100644
index 000000000..d5ae30340
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java
@@ -0,0 +1,132 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * The morePacketsCheck (previously called SpeedhackCheck) will try to identify players that send more than the usual
+ * amount of move-packets to the server to be able to move faster than normal, without getting caught by the other
+ * checks (flying/running).
+ *
+ * It monitors the number of packets sent to the server within 1 second and compares it to the "legal" number of packets
+ * for that timeframe (22).
+ *
+ */
+public class MorePacketsCheck extends MovingCheck
+{
+ // 20 would be for perfect internet connections, 22 is good enough
+ private final static int packetsPerTimeframe = 22;
+
+ public MorePacketsCheck(NoCheat plugin)
+ {
+ super(plugin, "moving.morepackets");
+ }
+
+ /**
+ * 1. Players get assigned a certain amount of "free" packets as a limit initially 2. Every move packet reduces that
+ * limit by 1 3. If more than 1 second of time passed, the limit gets increased by 22 * time in seconds, up to 50
+ * and he gets a new "setback" location 4. If the player reaches limit = 0 -> teleport him back to "setback" 5. If
+ * there was a long pause (maybe lag), limit may be up to 100
+ *
+ */
+ public PreciseLocation check(NoCheatPlayer player, MovingData data, MovingConfig cc)
+ {
+
+ PreciseLocation newToLocation = null;
+
+ if (!data.morePacketsSetbackPoint.isSet())
+ {
+ data.morePacketsSetbackPoint.set(data.from);
+ }
+
+ long time = System.currentTimeMillis();
+
+ // Take a packet from the buffer
+ data.morePacketsBuffer--;
+
+ // Player used up buffer, he fails the check
+ if (data.morePacketsBuffer < 0)
+ {
+
+ data.morePacketsVL = -data.morePacketsBuffer;
+ incrementStatistics(player, Id.MOV_MOREPACKETS, 1);
+
+ data.packets = -data.morePacketsBuffer;
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ final boolean cancel = executeActions(player, cc.morePacketsActions, data.morePacketsVL);
+
+ if (cancel)
+ {
+ newToLocation = data.morePacketsSetbackPoint;
+ }
+ }
+
+ if (data.morePacketsLastTime + 1000 < time)
+ {
+ // More than 1 second elapsed, but how many?
+ double seconds = ((double)(time - data.morePacketsLastTime)) / 1000D;
+
+ // For each second, fill the buffer
+ data.morePacketsBuffer += packetsPerTimeframe * seconds;
+
+ // If there was a long pause (maybe server lag?)
+ // Allow buffer to grow up to 100
+ if (seconds > 2)
+ {
+ if (data.morePacketsBuffer > 100)
+ {
+ data.morePacketsBuffer = 100;
+ }
+ // Else only allow growth up to 50
+ }
+ else
+ {
+ if (data.morePacketsBuffer > 50)
+ {
+ data.morePacketsBuffer = 50;
+ }
+ }
+
+ // Set the new "last" time
+ data.morePacketsLastTime = time;
+
+ // Set the new "setback" location
+ if (newToLocation == null)
+ {
+ data.morePacketsSetbackPoint.set(data.from);
+ }
+ }
+ else if (data.morePacketsLastTime > time)
+ {
+ // Security check, maybe system time changed
+ data.morePacketsLastTime = time;
+ }
+
+ return newToLocation;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).morePacketsVL);
+ }
+ else if (wildcard == ParameterName.PACKETS)
+ {
+ return String.format(Locale.US, "%d", getData(player).packets);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java
new file mode 100644
index 000000000..fed104130
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java
@@ -0,0 +1,93 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import java.util.Locale;
+
+
+/**
+ * Abstract base class for Moving checks, provides some convenience methods for access to data and config that's
+ * relevant to this checktype
+ */
+public abstract class MovingCheck extends Check
+{
+ private static final String id = "moving";
+
+ public MovingCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.LOCATION)
+ {
+ PreciseLocation from = getData(player).from;
+ return String.format(Locale.US, "%.2f,%.2f,%.2f", from.x, from.y, from.z);
+ }
+ else if (wildcard == ParameterName.MOVEDISTANCE)
+ {
+ PreciseLocation from = getData(player).from;
+ PreciseLocation to = getData(player).to;
+ return String.format(Locale.US, "%.2f,%.2f,%.2f", to.x - from.x, to.y - from.y, to.z - from.z);
+ }
+ else if (wildcard == ParameterName.LOCATION_TO)
+ {
+ PreciseLocation to = getData(player).to;
+ return String.format(Locale.US, "%.2f,%.2f,%.2f", to.x, to.y, to.z);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+
+ }
+
+ /**
+ * Get the "MovingData" object that belongs to the player. Will ensure that such a object exists and if not, create
+ * one
+ *
+ * @param player
+ * @return
+ */
+ public static MovingData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ MovingData data = base.get(id);
+ if (data == null)
+ {
+ data = new MovingData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the MovingConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static MovingConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static MovingConfig getConfig(ConfigurationCacheStore cache)
+ {
+ MovingConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new MovingConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java
new file mode 100644
index 000000000..3b35e0c33
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java
@@ -0,0 +1,376 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockPlaceEvent;
+import org.bukkit.event.player.*;
+import org.bukkit.util.Vector;
+
+
+/**
+ * Central location to listen to events that are relevant for the moving checks
+ *
+ */
+public class MovingCheckListener implements Listener, EventManager
+{
+ private final MorePacketsCheck morePacketsCheck;
+ private final FlyingCheck flyingCheck;
+ private final RunningCheck runningCheck;
+ private final NoCheat plugin;
+
+ public MovingCheckListener(NoCheat plugin)
+ {
+
+ flyingCheck = new FlyingCheck(plugin);
+ runningCheck = new RunningCheck(plugin);
+ morePacketsCheck = new MorePacketsCheck(plugin);
+
+ this.plugin = plugin;
+ }
+
+ /**
+ * A workaround for players placing blocks below them getting pushed off the block by NoCheat.
+ *
+ * It essentially moves the "setbackpoint" to the top of the newly placed block, therefore tricking NoCheat into
+ * thinking the player was already on top of that block and should be allowed to stay there
+ *
+ * @param event The BlockPlaceEvent
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void blockPlace(final BlockPlaceEvent event)
+ {
+
+ // Block wasn't placed, so we don't care
+ if (event.isCancelled())
+ {
+ return;
+ }
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final MovingConfig config = MovingCheck.getConfig(player);
+
+ // If the player is allowed to fly anyway, the workaround is not needed
+ // It's kind of expensive (looking up block types) therefore it makes
+ // sense to avoid it
+ if (config.allowFlying || !config.runflyCheck || player.hasPermission(Permissions.MOVING_FLYING) || player.hasPermission(Permissions.MOVING_RUNFLY))
+ {
+ return;
+ }
+
+ // Get the player-specific stored data that applies here
+ final MovingData data = MovingCheck.getData(player);
+
+ final Block block = event.getBlockPlaced();
+
+ if (block == null || !data.runflySetBackPoint.isSet())
+ {
+ return;
+ }
+
+ // Keep some results of "expensive calls
+ final Location l = player.getPlayer().getLocation();
+ final int playerX = l.getBlockX();
+ final int playerY = l.getBlockY();
+ final int playerZ = l.getBlockZ();
+ final int blockY = block.getY();
+
+ // Was the block below the player?
+ if (Math.abs(playerX - block.getX()) <= 1 && Math.abs(playerZ - block.getZ()) <= 1 && playerY - blockY >= 0 && playerY - blockY <= 2)
+ {
+ // yes
+ final int type = CheckUtil.getType(block.getTypeId());
+ if (CheckUtil.isSolid(type) || CheckUtil.isLiquid(type))
+ {
+ if (blockY + 1 >= data.runflySetBackPoint.y)
+ {
+ data.runflySetBackPoint.y = (blockY + 1);
+ data.jumpPhase = 0;
+ }
+ }
+ }
+ }
+
+ /**
+ * If a player gets teleported, it may have two reasons. Either it was NoCheat or another plugin. If it was NoCheat,
+ * the target location should match the "data.teleportTo" value.
+ *
+ * On teleports, reset some movement related data that gets invalid
+ *
+ * @param event The PlayerTeleportEvent
+ */
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void teleport(final PlayerTeleportEvent event)
+ {
+
+ NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final MovingData data = MovingCheck.getData(player);
+
+ // If it was a teleport initialized by NoCheat, do it anyway
+ // even if another plugin said "no"
+ if (data.teleportTo.isSet() && data.teleportTo.equals(event.getTo()))
+ {
+ event.setCancelled(false);
+ }
+ else
+ {
+ // Only if it wasn't NoCheat, drop data from morepackets check.
+ // If it was NoCheat, we don't want players to exploit the
+ // runfly check teleporting to get rid of the "morepackets"
+ // data.
+ data.clearMorePacketsData();
+ }
+
+ // Always drop data from runfly check, as it always loses its validity
+ // after teleports. Always!
+ data.teleportTo.reset();
+ data.clearRunFlyData();
+
+ return;
+ }
+
+ /**
+ * Just for security, if a player switches between worlds, reset the runfly and morepackets checks data, because it
+ * is definitely invalid now
+ *
+ * @param event The PlayerChangedWorldEvent
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void worldChange(final PlayerChangedWorldEvent event)
+ {
+ // Maybe this helps with people teleporting through multiverse portals having problems?
+ final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
+ data.teleportTo.reset();
+ data.clearRunFlyData();
+ data.clearMorePacketsData();
+ }
+
+ /**
+ * When a player uses a portal, all information related to the moving checks becomes invalid.
+ *
+ * @param event
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void portal(final PlayerPortalEvent event)
+ {
+ final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
+ data.clearMorePacketsData();
+ data.clearRunFlyData();
+ }
+
+ /**
+ * When a player respawns, all information related to the moving checks becomes invalid.
+ *
+ * @param event
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void respawn(final PlayerRespawnEvent event)
+ {
+ final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
+ data.clearMorePacketsData();
+ data.clearRunFlyData();
+ }
+
+ /**
+ * When a player moves, he will be checked for various suspicious behaviour.
+ *
+ * @param event The PlayerMoveEvent
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void move(final PlayerMoveEvent event)
+ {
+
+ // Don't care for vehicles
+ if (event.isCancelled() || event.getPlayer().isInsideVehicle())
+ {
+ return;
+ }
+
+ // Don't care for movements that are very high distance or to another
+ // world (such that it is very likely the event data was modified by
+ // another plugin before we got it)
+ if (!event.getFrom().getWorld().equals(event.getTo().getWorld()) || event.getFrom().distanceSquared(event.getTo()) > 400)
+ {
+ return;
+ }
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+
+ final MovingConfig cc = MovingCheck.getConfig(player);
+ final MovingData data = MovingCheck.getData(player);
+
+ // Advance various counters and values that change per movement
+ // tick. They are needed to decide on how fast a player may
+ // move.
+ tickVelocities(data);
+
+ // Remember locations
+ data.from.set(event.getFrom());
+ final Location to = event.getTo();
+ data.to.set(to);
+
+ PreciseLocation newTo = null;
+
+ /**
+ * RUNFLY CHECK SECTION *
+ */
+ // If the player isn't handled by runfly checks
+ if (!cc.runflyCheck || player.hasPermission(Permissions.MOVING_RUNFLY))
+ {
+ // Just because he is allowed now, doesn't mean he will always
+ // be. So forget data about the player related to moving
+ data.clearRunFlyData();
+ }
+ else if (cc.allowFlying || (player.isCreative() && cc.identifyCreativeMode) || player.hasPermission(Permissions.MOVING_FLYING))
+ {
+ // Only do the limited flying check
+ newTo = flyingCheck.check(player, data, cc);
+ }
+ else
+ {
+ // Go for the full treatment
+ newTo = runningCheck.check(player, data, cc);
+ }
+
+ /**
+ * MOREPACKETS CHECK SECTION *
+ */
+ if (!cc.morePacketsCheck || player.hasPermission(Permissions.MOVING_MOREPACKETS))
+ {
+ data.clearMorePacketsData();
+ }
+ else if (newTo == null)
+ {
+ newTo = morePacketsCheck.check(player, data, cc);
+ }
+
+ // Did one of the check(s) decide we need a new "to"-location?
+ if (newTo != null)
+ {
+ // Compose a new location based on coordinates of "newTo" and
+ // viewing direction of "event.getTo()" to allow the player to
+ // look somewhere else despite getting pulled back by NoCheat
+ event.setTo(new Location(player.getPlayer().getWorld(), newTo.x, newTo.y, newTo.z, to.getYaw(), to.getPitch()));
+
+ // remember where we send the player to
+ data.teleportTo.set(newTo);
+ }
+ }
+
+ /**
+ * Just try to estimate velocities over time Not very precise, but works good enough most of the time.
+ *
+ * @param data
+ */
+ private void tickVelocities(MovingData data)
+ {
+
+ /**
+ * ****** DO GENERAL DATA MODIFICATIONS ONCE FOR EACH EVENT ****
+ */
+ if (data.horizVelocityCounter > 0)
+ {
+ data.horizVelocityCounter--;
+ }
+ else if (data.horizFreedom > 0.001)
+ {
+ data.horizFreedom *= 0.90;
+ }
+
+ if (data.vertVelocity <= 0.1)
+ {
+ data.vertVelocityCounter--;
+ }
+ if (data.vertVelocityCounter > 0)
+ {
+ data.vertFreedom += data.vertVelocity;
+ data.vertVelocity *= 0.90;
+ }
+ else if (data.vertFreedom > 0.001)
+ {
+ // Counter has run out, now reduce the vert freedom over time
+ data.vertFreedom *= 0.93;
+ }
+ }
+
+ /**
+ * Player got a velocity packet. The server can't keep track of actual velocity values (by design), so we have to
+ * try and do that ourselves. Very rough estimates.
+ *
+ * @param event The PlayerVelocityEvent
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void velocity(final PlayerVelocityEvent event)
+ {
+ if (event.isCancelled())
+ {
+ return;
+ }
+
+ final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
+
+ final Vector v = event.getVelocity();
+
+ double newVal = v.getY();
+ if (newVal >= 0.0D)
+ {
+ data.vertVelocity += newVal;
+ data.vertFreedom += data.vertVelocity;
+ }
+
+ data.vertVelocityCounter = 50;
+
+ newVal = Math.sqrt(Math.pow(v.getX(), 2) + Math.pow(v.getZ(), 2));
+ if (newVal > 0.0D)
+ {
+ data.horizFreedom += newVal;
+ data.horizVelocityCounter = 30;
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ MovingConfig m = MovingCheck.getConfig(cc);
+
+ if (m.runflyCheck)
+ {
+
+ if (!m.allowFlying)
+ {
+ s.add("moving.runfly");
+ if (m.sneakingCheck)
+ {
+ s.add("moving.sneaking");
+ }
+ if (m.nofallCheck)
+ {
+ s.add("moving.nofall");
+ }
+ }
+ else
+ {
+ s.add("moving.flying");
+ }
+
+ }
+ if (m.morePacketsCheck)
+ {
+ s.add("moving.morepackets");
+ }
+
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java
new file mode 100644
index 000000000..54e2834a4
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java
@@ -0,0 +1,71 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.ConfigItem;
+import com.earth2me.essentials.anticheat.actions.types.ActionList;
+import com.earth2me.essentials.anticheat.config.ConfPaths;
+import com.earth2me.essentials.anticheat.config.NoCheatConfiguration;
+import com.earth2me.essentials.anticheat.config.Permissions;
+
+
+/**
+ * Configurations specific for the Move Checks. Every world gets one of these assigned to it.
+ *
+ */
+public class MovingConfig implements ConfigItem
+{
+ public final boolean runflyCheck;
+ public final boolean identifyCreativeMode;
+ public final double walkingSpeedLimit;
+ public final double sprintingSpeedLimit;
+ public final double jumpheight;
+ public final double swimmingSpeedLimit;
+ public final boolean sneakingCheck;
+ public final double sneakingSpeedLimit;
+ public final ActionList actions;
+ public final boolean allowFlying;
+ public final double flyingSpeedLimitVertical;
+ public final double flyingSpeedLimitHorizontal;
+ public final ActionList flyingActions;
+ public final boolean nofallCheck;
+ public final boolean nofallaggressive;
+ public final float nofallMultiplier;
+ public final ActionList nofallActions;
+ public final boolean morePacketsCheck;
+ public final ActionList morePacketsActions;
+ public final int flyingHeightLimit;
+
+ public MovingConfig(NoCheatConfiguration data)
+ {
+
+ identifyCreativeMode = data.getBoolean(ConfPaths.MOVING_RUNFLY_FLYING_ALLOWINCREATIVE);
+
+ runflyCheck = data.getBoolean(ConfPaths.MOVING_RUNFLY_CHECK);
+
+ int walkspeed = data.getInt(ConfPaths.MOVING_RUNFLY_WALKSPEED, 100);
+ int sprintspeed = data.getInt(ConfPaths.MOVING_RUNFLY_SPRINTSPEED, 100);
+ int swimspeed = data.getInt(ConfPaths.MOVING_RUNFLY_SWIMSPEED, 100);
+ int sneakspeed = data.getInt(ConfPaths.MOVING_RUNFLY_SNEAKSPEED, 100);
+ walkingSpeedLimit = (0.22 * walkspeed) / 100D;
+ sprintingSpeedLimit = (0.35 * sprintspeed) / 100D;
+ swimmingSpeedLimit = (0.18 * swimspeed) / 100D;
+ sneakingSpeedLimit = (0.14 * sneakspeed) / 100D;
+ jumpheight = ((double)135) / 100D;
+
+ sneakingCheck = !data.getBoolean(ConfPaths.MOVING_RUNFLY_ALLOWFASTSNEAKING);
+ actions = data.getActionList(ConfPaths.MOVING_RUNFLY_ACTIONS, Permissions.MOVING_RUNFLY);
+
+ allowFlying = data.getBoolean(ConfPaths.MOVING_RUNFLY_FLYING_ALLOWALWAYS);
+ flyingSpeedLimitVertical = ((double)data.getInt(ConfPaths.MOVING_RUNFLY_FLYING_SPEEDLIMITVERTICAL)) / 100D;
+ flyingSpeedLimitHorizontal = ((double)data.getInt(ConfPaths.MOVING_RUNFLY_FLYING_SPEEDLIMITHORIZONTAL)) / 100D;
+ flyingHeightLimit = data.getInt(ConfPaths.MOVING_RUNFLY_FLYING_HEIGHTLIMIT);
+ flyingActions = data.getActionList(ConfPaths.MOVING_RUNFLY_FLYING_ACTIONS, Permissions.MOVING_FLYING);
+
+ nofallCheck = data.getBoolean(ConfPaths.MOVING_RUNFLY_CHECKNOFALL);
+ nofallMultiplier = ((float)200) / 100F;
+ nofallaggressive = data.getBoolean(ConfPaths.MOVING_RUNFLY_NOFALLAGGRESSIVE);
+ nofallActions = data.getActionList(ConfPaths.MOVING_RUNFLY_NOFALLACTIONS, Permissions.MOVING_NOFALL);
+
+ morePacketsCheck = data.getBoolean(ConfPaths.MOVING_MOREPACKETS_CHECK);
+ morePacketsActions = data.getActionList(ConfPaths.MOVING_MOREPACKETS_ACTIONS, Permissions.MOVING_MOREPACKETS);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java
new file mode 100644
index 000000000..e57a59d29
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java
@@ -0,0 +1,69 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+
+
+/**
+ * Player specific data for the moving check group
+ */
+public class MovingData implements DataItem
+{
+ // Keep track of the violation levels of the checks
+ public double runflyVL;
+ public double nofallVL;
+ public double morePacketsVL;
+ // Count how long a player is in the air
+ public int jumpPhase;
+ // Remember how big the players last JumpAmplifier (potion effect) was
+ public double lastJumpAmplifier;
+ // Remember for a short time that the player was on ice and therefore
+ // should be allowed to move a bit faster
+ public int onIce;
+ // Where should a player be teleported back to when failing the check
+ public final PreciseLocation runflySetBackPoint = new PreciseLocation();
+ // Some values for estimating movement freedom
+ public double vertFreedom;
+ public double vertVelocity;
+ public int vertVelocityCounter;
+ public double horizFreedom;
+ public int horizVelocityCounter;
+ public double horizontalBuffer;
+ public int bunnyhopdelay;
+ // Keep track of estimated fall distance to compare to real fall distance
+ public float fallDistance;
+ public float lastAddedFallDistance;
+ // Keep track of when "morePackets" last time checked and how much packets
+ // a player sent and may send before failing the check
+ public long morePacketsLastTime;
+ public int packets;
+ public int morePacketsBuffer = 50;
+ // Where to teleport the player that fails the "morepackets" check
+ public final PreciseLocation morePacketsSetbackPoint = new PreciseLocation();
+ // When NoCheat does teleport the player, remember the target location to
+ // be able to distinguish "our" teleports from teleports of others
+ public final PreciseLocation teleportTo = new PreciseLocation();
+ // For logging and convenience, make copies of the events locations
+ public final PreciseLocation from = new PreciseLocation();
+ public final PreciseLocation to = new PreciseLocation();
+ // For convenience, remember if the locations are considered "on ground"
+ // by NoCheat
+ public boolean fromOnOrInGround;
+ public boolean toOnOrInGround;
+ public Id statisticCategory = Id.MOV_RUNNING;
+
+ public void clearRunFlyData()
+ {
+ runflySetBackPoint.reset();
+ jumpPhase = 0;
+ fallDistance = 0;
+ lastAddedFallDistance = 0;
+ bunnyhopdelay = 0;
+ }
+
+ public void clearMorePacketsData()
+ {
+ morePacketsSetbackPoint.reset();
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java
new file mode 100644
index 000000000..6a531e3c2
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java
@@ -0,0 +1,151 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * A check to see if people cheat by tricking the server to not deal them fall damage.
+ *
+ */
+public class NoFallCheck extends MovingCheck
+{
+ public NoFallCheck(NoCheat plugin)
+ {
+ super(plugin, "moving.nofall");
+ }
+
+ /**
+ * Calculate if and how much the player "failed" this check.
+ *
+ */
+ public void check(NoCheatPlayer player, MovingData data, MovingConfig cc)
+ {
+
+ // If the player is serverside in creative mode, we have to stop here to
+ // avoid hurting him when he switches back to "normal" mode
+ if (player.isCreative())
+ {
+ data.fallDistance = 0F;
+ data.lastAddedFallDistance = 0F;
+ return;
+ }
+
+ // This check is pretty much always a step behind for technical reasons.
+ if (data.fromOnOrInGround)
+ {
+ // Start with zero fall distance
+ data.fallDistance = 0F;
+ }
+
+ if (cc.nofallaggressive && data.fromOnOrInGround && data.toOnOrInGround && data.from.y <= data.to.y && player.getPlayer().getFallDistance() > 3.0F)
+ {
+ data.fallDistance = player.getPlayer().getFallDistance();
+ data.nofallVL += data.fallDistance;
+ incrementStatistics(player, Id.MOV_NOFALL, data.fallDistance);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ final boolean cancel = executeActions(player, cc.nofallActions, data.nofallVL);
+ if (cancel)
+ {
+ player.dealFallDamage();
+ }
+ data.fallDistance = 0F;
+ }
+
+ // If we increased fall height before for no good reason, reduce now by
+ // the same amount
+ if (player.getPlayer().getFallDistance() > data.lastAddedFallDistance)
+ {
+ player.getPlayer().setFallDistance(player.getPlayer().getFallDistance() - data.lastAddedFallDistance);
+ }
+
+ data.lastAddedFallDistance = 0;
+
+ // We want to know if the fallDistance recorded by the game is smaller
+ // than the fall distance recorded by the plugin
+ final float difference = data.fallDistance - player.getPlayer().getFallDistance();
+
+ if (difference > 1.0F && data.toOnOrInGround && data.fallDistance > 2.0F)
+ {
+ data.nofallVL += difference;
+ incrementStatistics(player, Id.MOV_NOFALL, difference);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ final boolean cancel = executeActions(player, cc.nofallActions, data.nofallVL);
+
+ // If "cancelled", the fall damage gets dealt in a way that's
+ // visible to other plugins
+ if (cancel)
+ {
+ // Increase the fall distance a bit :)
+ final float totalDistance = data.fallDistance + difference * (cc.nofallMultiplier - 1.0F);
+
+ player.getPlayer().setFallDistance(totalDistance);
+ }
+
+ data.fallDistance = 0F;
+ }
+
+ // Increase the fall distance that is recorded by the plugin, AND set
+ // the fall distance of the player
+ // to whatever he would get with this move event. This modifies
+ // Minecrafts fall damage calculation
+ // slightly, but that's still better than ignoring players that try to
+ // use "teleports" or "stepdown"
+ // to avoid falldamage. It is only added for big height differences
+ // anyway, as to avoid to much deviation
+ // from the original Minecraft feeling.
+
+ final double oldY = data.from.y;
+ final double newY = data.to.y;
+
+ if (oldY > newY)
+ {
+ final float dist = (float)(oldY - newY);
+ data.fallDistance += dist;
+
+ if (dist > 1.0F)
+ {
+ data.lastAddedFallDistance = dist;
+ player.getPlayer().setFallDistance(player.getPlayer().getFallDistance() + dist);
+ }
+ else
+ {
+ data.lastAddedFallDistance = 0.0F;
+ }
+ }
+ else
+ {
+ data.lastAddedFallDistance = 0.0F;
+ }
+
+ // Reduce falldamage violation level
+ data.nofallVL *= 0.95D;
+
+ return;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).nofallVL);
+ }
+ else if (wildcard == ParameterName.FALLDISTANCE)
+ {
+ return String.format(Locale.US, "%.2f", getData(player).fallDistance);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java
new file mode 100644
index 000000000..bb5444be9
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java
@@ -0,0 +1,303 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+
+
+/**
+ * The counterpart to the FlyingCheck. People that are not allowed to fly get checked by this. It will try to identify
+ * when they are jumping, check if they aren't jumping too high or far, check if they aren't moving too fast on normal
+ * ground, while sprinting, sneaking or swimming.
+ *
+ */
+public class RunningCheck extends MovingCheck
+{
+ private final static double maxBonus = 1D;
+ // How many move events can a player have in air before he is expected to
+ // lose altitude (or eventually land somewhere)
+ private final static int jumpingLimit = 6;
+ private final NoFallCheck noFallCheck;
+
+ public RunningCheck(NoCheat plugin)
+ {
+
+ super(plugin, "moving.running");
+
+ this.noFallCheck = new NoFallCheck(plugin);
+ }
+
+ public PreciseLocation check(NoCheatPlayer player, MovingData data, MovingConfig cc)
+ {
+
+ // Some shortcuts:
+ final PreciseLocation setBack = data.runflySetBackPoint;
+ final PreciseLocation to = data.to;
+ final PreciseLocation from = data.from;
+
+ // Calculate some distances
+ final double xDistance = data.to.x - from.x;
+ final double zDistance = to.z - from.z;
+ final double horizontalDistance = Math.sqrt((xDistance * xDistance + zDistance * zDistance));
+
+ if (!setBack.isSet())
+ {
+ setBack.set(from);
+ }
+
+ // To know if a player "is on ground" is useful
+ final int fromType = CheckUtil.evaluateLocation(player.getPlayer().getWorld(), from);
+ final int toType = CheckUtil.evaluateLocation(player.getPlayer().getWorld(), to);
+
+ final boolean fromOnGround = CheckUtil.isOnGround(fromType);
+ final boolean fromInGround = CheckUtil.isInGround(fromType);
+ final boolean toOnGround = CheckUtil.isOnGround(toType);
+ final boolean toInGround = CheckUtil.isInGround(toType);
+
+ PreciseLocation newToLocation = null;
+
+ final double resultHoriz = Math.max(0.0D, checkHorizontal(player, data, CheckUtil.isLiquid(fromType) && CheckUtil.isLiquid(toType), horizontalDistance, cc));
+ final double resultVert = Math.max(0.0D, checkVertical(player, data, fromOnGround, toOnGround, cc));
+
+ final double result = (resultHoriz + resultVert) * 100;
+
+ data.jumpPhase++;
+
+ // Slowly reduce the level with each event
+ data.runflyVL *= 0.95;
+
+ // Did the player move in unexpected ways?
+ if (result > 0)
+ {
+ // Increment violation counter
+ data.runflyVL += result;
+
+ incrementStatistics(player, data.statisticCategory, result);
+
+ boolean cancel = executeActions(player, cc.actions, data.runflyVL);
+
+ // Was one of the actions a cancel? Then do it
+ if (cancel)
+ {
+ newToLocation = setBack;
+ }
+ else if (toOnGround || toInGround)
+ {
+ // In case it only gets logged, not stopped by NoCheat
+ // Update the setback location at least a bit
+ setBack.set(to);
+ data.jumpPhase = 0;
+
+ }
+ }
+ else
+ {
+ // Decide if we should create a new setBack point
+ // These are the result of a lot of bug reports, experience and
+ // trial and error
+
+ if ((toInGround && from.y >= to.y) || CheckUtil.isLiquid(toType))
+ {
+ // Yes, if the player moved down "into" the ground or into liquid
+ setBack.set(to);
+ setBack.y = Math.ceil(setBack.y);
+ data.jumpPhase = 0;
+ }
+ else if (toOnGround && (from.y >= to.y || setBack.y <= Math.floor(to.y)))
+ {
+ // Yes, if the player moved down "onto" the ground and the new
+ // setback point is higher up than the old or at least at the
+ // same height
+ setBack.set(to);
+ setBack.y = Math.floor(setBack.y);
+ data.jumpPhase = 0;
+ }
+ else if (fromOnGround || fromInGround || toOnGround || toInGround)
+ {
+ // The player at least touched the ground somehow
+ data.jumpPhase = 0;
+ }
+ }
+
+ /**
+ * ******* EXECUTE THE NOFALL CHECK *******************
+ */
+ final boolean checkNoFall = cc.nofallCheck && !player.hasPermission(Permissions.MOVING_NOFALL);
+
+ if (checkNoFall && newToLocation == null)
+ {
+ data.fromOnOrInGround = fromOnGround || fromInGround;
+ data.toOnOrInGround = toOnGround || toInGround;
+ noFallCheck.check(player, data, cc);
+ }
+
+ return newToLocation;
+ }
+
+ /**
+ * Calculate how much the player failed this check
+ *
+ */
+ private double checkHorizontal(final NoCheatPlayer player, final MovingData data, final boolean isSwimming, final double totalDistance, final MovingConfig cc)
+ {
+
+ // How much further did the player move than expected??
+ double distanceAboveLimit = 0.0D;
+
+ // A player is considered sprinting if the flag is set and if he has
+ // enough food level (configurable)
+ final boolean sprinting = player.isSprinting() && (player.getPlayer().getFoodLevel() > 5);
+
+ double limit = 0.0D;
+
+ Id statisticsCategory = null;
+
+ // Player on ice? Give him higher max speed
+ Block b = player.getPlayer().getLocation().getBlock();
+ if (b.getType() == Material.ICE || b.getRelative(0, -1, 0).getType() == Material.ICE)
+ {
+ data.onIce = 20;
+ }
+ else if (data.onIce > 0)
+ {
+ data.onIce--;
+ }
+
+ if (cc.sneakingCheck && player.getPlayer().isSneaking() && !player.hasPermission(Permissions.MOVING_SNEAKING))
+ {
+ limit = cc.sneakingSpeedLimit;
+ statisticsCategory = Id.MOV_SNEAKING;
+ }
+ else if (isSwimming && !player.hasPermission(Permissions.MOVING_SWIMMING))
+ {
+ limit = cc.swimmingSpeedLimit;
+ statisticsCategory = Id.MOV_SWIMMING;
+ }
+ else if (!sprinting)
+ {
+ limit = cc.walkingSpeedLimit;
+ statisticsCategory = Id.MOV_RUNNING;
+ }
+ else
+ {
+ limit = cc.sprintingSpeedLimit;
+ statisticsCategory = Id.MOV_RUNNING;
+ }
+
+ if (data.onIce > 0)
+ {
+ limit *= 2.5;
+ }
+
+ // Taken directly from Minecraft code, should work
+ limit *= player.getSpeedAmplifier();
+
+ distanceAboveLimit = totalDistance - limit - data.horizFreedom;
+
+ data.bunnyhopdelay--;
+
+ // Did he go too far?
+ if (distanceAboveLimit > 0 && sprinting)
+ {
+
+ // Try to treat it as a the "bunnyhop" problem
+ if (data.bunnyhopdelay <= 0 && distanceAboveLimit > 0.05D && distanceAboveLimit < 0.4D)
+ {
+ data.bunnyhopdelay = 9;
+ distanceAboveLimit = 0;
+ }
+ }
+
+ if (distanceAboveLimit > 0)
+ {
+ // Try to consume the "buffer"
+ distanceAboveLimit -= data.horizontalBuffer;
+ data.horizontalBuffer = 0;
+
+ // Put back the "overconsumed" buffer
+ if (distanceAboveLimit < 0)
+ {
+ data.horizontalBuffer = -distanceAboveLimit;
+ }
+ }
+ // He was within limits, give the difference as buffer
+ else
+ {
+ data.horizontalBuffer = Math.min(maxBonus, data.horizontalBuffer - distanceAboveLimit);
+ }
+
+ if (distanceAboveLimit > 0)
+ {
+ data.statisticCategory = statisticsCategory;
+ }
+
+ return distanceAboveLimit;
+ }
+
+ /**
+ * Calculate if and how much the player "failed" this check.
+ *
+ */
+ private double checkVertical(final NoCheatPlayer player, final MovingData data, final boolean fromOnGround, final boolean toOnGround, final MovingConfig cc)
+ {
+
+ // How much higher did the player move than expected??
+ double distanceAboveLimit = 0.0D;
+
+ // Potion effect "Jump"
+ double jumpAmplifier = player.getJumpAmplifier();
+ if (jumpAmplifier > data.lastJumpAmplifier)
+ {
+ data.lastJumpAmplifier = jumpAmplifier;
+ }
+
+ double limit = data.vertFreedom + cc.jumpheight;
+
+ limit *= data.lastJumpAmplifier;
+
+ if (data.jumpPhase > jumpingLimit + data.lastJumpAmplifier)
+ {
+ limit -= (data.jumpPhase - jumpingLimit) * 0.15D;
+ }
+
+ distanceAboveLimit = data.to.y - data.runflySetBackPoint.y - limit;
+
+ if (distanceAboveLimit > 0)
+ {
+ data.statisticCategory = Id.MOV_FLYING;
+ }
+
+ if (toOnGround || fromOnGround)
+ {
+ data.lastJumpAmplifier = 0;
+ }
+
+ return distanceAboveLimit;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.CHECK)
+ // Workaround for something until I find a better way to do it
+ {
+ return getData(player).statisticCategory.toString();
+ }
+ else if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).runflyVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}