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
main: cc.co.evenprime.bukkit.nofly.NoFlyPlugin
version: 0.2.1
main: cc.co.evenprime.bukkit.nocheat.NoCheatPlugin
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.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
*
* @author Evenprime
*/
public class NoFlyPlugin extends JavaPlugin {
private final NoFlyPluginPlayerListener playerListener = new NoFlyPluginPlayerListener(this);
public class NoCheatPlugin extends JavaPlugin {
private final NoCheatPluginPlayerListener playerListener = new NoCheatPluginPlayerListener(this);
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);
}

View File

@ -1,4 +1,4 @@
package cc.co.evenprime.bukkit.nofly;
package cc.co.evenprime.bukkit.nocheat;
import java.util.HashMap;
@ -16,26 +16,28 @@ import org.bukkit.event.player.PlayerMoveEvent;
* @author Evenprime
*/
public class NoFlyPluginPlayerListener extends PlayerListener {
public class NoCheatPluginPlayerListener extends PlayerListener {
/**
* 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
* change their name/functionality
*/
private int phase = 0; // current jumpingPhase
public long previousUpdate = 0; // timestamp of last event
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
// 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 maxZ = 0.5D;
private static final long timeFrameForSpeedHackCheck = 2000;
private static final long eventLimitForSpeedHackCheck = 50;
// 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;
}
@ -73,19 +78,35 @@ public class NoFlyPluginPlayerListener extends PlayerListener {
Location to = event.getTo();
// Get the player-specific data
NoFlyPluginData data = null;
NoCheatPluginData data = null;
if((data = playerData.get(event.getPlayer().getName())) == null ) {
// If we have no data for the player, create some
data = new NoFlyPluginData();
data = new NoCheatPluginData();
playerData.put(event.getPlayer().getName(), data);
}
// Measure the time since the last move update by the player
// Not used currently, but probably will be used in future
// Get the time of the server
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
// 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
*/
if(event.isCancelled()) {
if(event.isCancelled() && !data.lastWasInvalid) {
// Keep count of 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);
// 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 prevents him from getting stuck somewhere and/or getting
// Reset the player to his old location. This should prevent him from getting stuck somewhere and/or getting
// out of sync with the server
event.getPlayer().teleportTo(event.getFrom());
// To prevent players from getting stuck in an infinite loop
if(data.phase > 7) data.phase = 7;
// To prevent players from getting stuck in an infinite loop, needs probably more testing
// TODO: Find a better solution
if(data.phase > 7) data.phase = 7;
}
else if(event.isCancelled() && data.lastWasInvalid) {
data.violations++;
// Reset the player to his old location. This should prevent him from getting stuck somewhere and/or getting
// out of sync with the server
event.getPlayer().teleportTo(event.getFrom());
}
else if(!event.isCancelled() && data.lastWasInvalid) {
data.lastWasInvalid = false;
NoCheatPlugin.log.info("NoCheatPlugin: "+event.getPlayer().getDisplayName()+" stopped violating constraints. Total Violations: "+data.violations);
}
}
/**
* Check the four edges of the player's approximated Bounding Box for blocks or ladders,
* at his own height (values[2]) and below his feet (values[2]-1).
* If there is one, the player is considered as standing on it.
* at his own height (values[2]) and below his feet (values[2]-1). Also, check at his "head"
* for ladders.
*
* If there is one, the player is considered as standing on it/hanging to it.
*
* Not perfect at all and will produce some false negatives. Probably will be refined
* later.
@ -204,12 +241,16 @@ public class NoFlyPluginPlayerListener extends PlayerListener {
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], 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
(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], 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;
else
return false;