Rename + Speedhack-detection + bugfix

Renamed Project into NoCheatPlugin, because the old name doesn't fit
it anymore.
Also added a first draft of Speedhack detection. Will log suspicious
behaviour.
Fixed another bug with ladders, if a player jumps onto them and holds
itself only with his hands (metaphorically speaking).
This commit is contained in:
Evenprime 2011-02-16 23:16:09 +01:00
parent 20a67d606d
commit 79c8fd6060
3 changed files with 75 additions and 34 deletions

View File

@ -1,7 +1,7 @@
name: NoFlyPlugin name: NoCheatPlugin
author: Evenprime author: Evenprime
main: cc.co.evenprime.bukkit.nofly.NoFlyPlugin main: cc.co.evenprime.bukkit.nocheat.NoCheatPlugin
version: 0.2.1 version: 0.3

View File

@ -1,4 +1,4 @@
package cc.co.evenprime.bukkit.nofly; package cc.co.evenprime.bukkit.nocheat;
import java.io.File; import java.io.File;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -13,18 +13,18 @@ import org.bukkit.plugin.PluginManager;
/** /**
* *
* NoFlyPlugin * NoCheatPlugin
* *
* Check PLAYER_MOVE events for their plausibility and cancel them if they are implausible * Check PLAYER_MOVE events for their plausibility and cancel them if they are implausible
* *
* @author Evenprime * @author Evenprime
*/ */
public class NoFlyPlugin extends JavaPlugin { public class NoCheatPlugin extends JavaPlugin {
private final NoFlyPluginPlayerListener playerListener = new NoFlyPluginPlayerListener(this); private final NoCheatPluginPlayerListener playerListener = new NoCheatPluginPlayerListener(this);
public static final Logger log = Logger.getLogger("Minecraft"); public static final Logger log = Logger.getLogger("Minecraft");
public NoFlyPlugin(PluginLoader pluginLoader, Server instance, PluginDescriptionFile desc, File folder, File plugin, ClassLoader cLoader) { public NoCheatPlugin(PluginLoader pluginLoader, Server instance, PluginDescriptionFile desc, File folder, File plugin, ClassLoader cLoader) {
super(pluginLoader, instance, desc, folder, plugin, cLoader); super(pluginLoader, instance, desc, folder, plugin, cLoader);
} }

View File

@ -1,4 +1,4 @@
package cc.co.evenprime.bukkit.nofly; package cc.co.evenprime.bukkit.nocheat;
import java.util.HashMap; import java.util.HashMap;
@ -16,26 +16,28 @@ import org.bukkit.event.player.PlayerMoveEvent;
* @author Evenprime * @author Evenprime
*/ */
public class NoFlyPluginPlayerListener extends PlayerListener { public class NoCheatPluginPlayerListener extends PlayerListener {
/** /**
* Storage for data persistence between events * Storage for data persistence between events
* *
*/ */
public class NoFlyPluginData { public class NoCheatPluginData {
/** /**
* Don't rely on any of these yet, they are likely going to * Don't rely on any of these yet, they are likely going to
* change their name/functionality * change their name/functionality
*/ */
private int phase = 0; // current jumpingPhase private int phase = 0; // current jumpingPhase
public long previousUpdate = 0; // timestamp of last event
public int violations = 0; // number of cancelled events public int violations = 0; // number of cancelled events
private boolean lastWasInvalid = false; // used to reduce amount logging
private long lastSpeedHackCheck = System.currentTimeMillis();; // timestamp of last check for speedhacks
private int eventsSinceLastSpeedHackCheck = 0; // used to identify speedhacks
private NoFlyPluginData() { } private NoCheatPluginData() { }
} }
private final NoFlyPlugin plugin; private final NoCheatPlugin plugin;
// previously-calculated upper bound values for jumps. Minecraft is very deterministic when it comes to jumps // previously-calculated upper bound values for jumps. Minecraft is very deterministic when it comes to jumps
// Each entry represents the maximum gain in height per move event. // Each entry represents the maximum gain in height per move event.
@ -45,11 +47,14 @@ public class NoFlyPluginPlayerListener extends PlayerListener {
private static double maxX = 0.5D; private static double maxX = 0.5D;
private static double maxZ = 0.5D; private static double maxZ = 0.5D;
private static final long timeFrameForSpeedHackCheck = 2000;
private static final long eventLimitForSpeedHackCheck = 50;
// Store data between Events // Store data between Events
private static Map<String, NoFlyPluginData> playerData = new HashMap<String, NoFlyPluginData>(); private static Map<String, NoCheatPluginData> playerData = new HashMap<String, NoCheatPluginData>();
public NoFlyPluginPlayerListener(NoFlyPlugin instance) { public NoCheatPluginPlayerListener(NoCheatPlugin instance) {
plugin = instance; plugin = instance;
} }
@ -73,19 +78,35 @@ public class NoFlyPluginPlayerListener extends PlayerListener {
Location to = event.getTo(); Location to = event.getTo();
// Get the player-specific data // Get the player-specific data
NoFlyPluginData data = null; NoCheatPluginData data = null;
if((data = playerData.get(event.getPlayer().getName())) == null ) { if((data = playerData.get(event.getPlayer().getName())) == null ) {
// If we have no data for the player, create some // If we have no data for the player, create some
data = new NoFlyPluginData(); data = new NoCheatPluginData();
playerData.put(event.getPlayer().getName(), data); playerData.put(event.getPlayer().getName(), data);
} }
// Measure the time since the last move update by the player // Get the time of the server
// Not used currently, but probably will be used in future
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
//System.out.print((time - data.previousUpdate) + ",");
data.previousUpdate = time; // Is it time for a speedhack check now?
if(time > timeFrameForSpeedHackCheck + data.lastSpeedHackCheck ) {
// Yes
int limit = (int)((eventLimitForSpeedHackCheck * (time - data.lastSpeedHackCheck)) / timeFrameForSpeedHackCheck);
if(data.eventsSinceLastSpeedHackCheck > limit) {
// Probably someone is speedhacking here! Better log that
NoCheatPlugin.log.info("NoCheatPlugin: "+event.getPlayer().getDisplayName()+" probably uses a speedhack. He sent "+ data.eventsSinceLastSpeedHackCheck + " events, but only "+limit+ " were allowed in the timeframe!");
}
// Reset values for next check
data.eventsSinceLastSpeedHackCheck = 0;
data.lastSpeedHackCheck = time;
}
data.eventsSinceLastSpeedHackCheck++;
// First check the distance the player has moved horizontally // First check the distance the player has moved horizontally
// TODO: Make this check much more precise // TODO: Make this check much more precise
@ -170,27 +191,43 @@ public class NoFlyPluginPlayerListener extends PlayerListener {
/** /**
* Teleport the player back to the last valid position * Teleport the player back to the last valid position
*/ */
if(event.isCancelled()) { if(event.isCancelled() && !data.lastWasInvalid) {
// Keep count of violations // Keep count of violations
data.violations++; data.violations++;
// Log the violation
NoFlyPlugin.log.info("NoFlyPlugin: At " + data.previousUpdate + " player "+event.getPlayer().getDisplayName()+" triggered. Total Violations: "+data.violations);
NoFlyPlugin.log.info("NoFlyPlugin: He went from " + String.format("%.5f,%.5f,%.5f to %.5f,%.5f,%.5f", from.getX(), from.getY(), from.getZ(), to.getX(), to.getY(), to.getZ()));
//event.getPlayer().sendMessage("NoFlyPlugin violation "+data.violations);
// Reset the player to his old location. This prevents him from getting stuck somewhere and/or getting // Log the violation
NoCheatPlugin.log.info("NoCheatPlugin: "+event.getPlayer().getDisplayName()+" begins violating constraints. Total Violations: "+data.violations);
NoCheatPlugin.log.info("NoCheatPlugin: He tried to go from " + String.format("%.5f,%.5f,%.5f to %.5f,%.5f,%.5f", from.getX(), from.getY(), from.getZ(), to.getX(), to.getY(), to.getZ()));
data.lastWasInvalid = true;
// Reset the player to his old location. This should prevent him from getting stuck somewhere and/or getting
// out of sync with the server // out of sync with the server
event.getPlayer().teleportTo(event.getFrom()); event.getPlayer().teleportTo(event.getFrom());
// To prevent players from getting stuck in an infinite loop // To prevent players from getting stuck in an infinite loop, needs probably more testing
// TODO: Find a better solution
if(data.phase > 7) data.phase = 7; if(data.phase > 7) data.phase = 7;
} }
else if(event.isCancelled() && data.lastWasInvalid) {
data.violations++;
// Reset the player to his old location. This should prevent him from getting stuck somewhere and/or getting
// out of sync with the server
event.getPlayer().teleportTo(event.getFrom());
}
else if(!event.isCancelled() && data.lastWasInvalid) {
data.lastWasInvalid = false;
NoCheatPlugin.log.info("NoCheatPlugin: "+event.getPlayer().getDisplayName()+" stopped violating constraints. Total Violations: "+data.violations);
}
} }
/** /**
* Check the four edges of the player's approximated Bounding Box for blocks or ladders, * Check the four edges of the player's approximated Bounding Box for blocks or ladders,
* at his own height (values[2]) and below his feet (values[2]-1). * at his own height (values[2]) and below his feet (values[2]-1). Also, check at his "head"
* If there is one, the player is considered as standing on it. * for ladders.
*
* If there is one, the player is considered as standing on it/hanging to it.
* *
* Not perfect at all and will produce some false negatives. Probably will be refined * Not perfect at all and will produce some false negatives. Probably will be refined
* later. * later.
@ -204,12 +241,16 @@ public class NoFlyPluginPlayerListener extends PlayerListener {
if((w.getBlockAt(values[0], values[2]-1, values[3]).getType() != Material.AIR || if((w.getBlockAt(values[0], values[2]-1, values[3]).getType() != Material.AIR ||
w.getBlockAt(values[0], values[2]-1, values[4]).getType() != Material.AIR || w.getBlockAt(values[0], values[2]-1, values[4]).getType() != Material.AIR ||
w.getBlockAt(values[0], values[2], values[3]).getType() != Material.AIR || w.getBlockAt(values[0], values[2], values[3]).getType() != Material.AIR ||
w.getBlockAt(values[0], values[2], values[4]).getType() != Material.AIR) || w.getBlockAt(values[0], values[2], values[4]).getType() != Material.AIR ||
w.getBlockAt(values[0], values[2]+1, values[3]).getType() == Material.LADDER ||
w.getBlockAt(values[0], values[2]+1, values[4]).getType() == Material.LADDER) ||
(values[0] != values[1] && // May save some time by skipping half of the tests (values[0] != values[1] && // May save some time by skipping half of the tests
(w.getBlockAt(values[1], values[2]-1, values[3]).getType() != Material.AIR || (w.getBlockAt(values[1], values[2]-1, values[3]).getType() != Material.AIR ||
w.getBlockAt(values[1], values[2]-1, values[4]).getType() != Material.AIR || w.getBlockAt(values[1], values[2]-1, values[4]).getType() != Material.AIR ||
w.getBlockAt(values[1], values[2], values[3]).getType() != Material.AIR || w.getBlockAt(values[1], values[2], values[3]).getType() != Material.AIR ||
w.getBlockAt(values[1], values[2], values[4]).getType() != Material.AIR))) w.getBlockAt(values[1], values[2], values[4]).getType() != Material.AIR ||
w.getBlockAt(values[1], values[2]+1, values[3]).getType() == Material.LADDER ||
w.getBlockAt(values[1], values[2]+1, values[4]).getType() == Material.LADDER)))
return true; return true;
else else
return false; return false;