summaryrefslogtreecommitdiffstats
path: root/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving
diff options
context:
space:
mode:
Diffstat (limited to 'EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving')
-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
8 files changed, 1365 insertions, 0 deletions
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);
+ }
+ }
+}