diff options
Diffstat (limited to 'EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving')
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); + } + } +} |