Fixed logging + new anticheat mechanism

Logging of moving violations (previously reset coordinates of event
before logging, therefore the logged information was not useful at
all).

Added check for building. Building against thin air is not permitted
in the client singleplayer game, so why should it be on a server?
This commit is contained in:
Evenprime 2011-02-21 19:23:05 +01:00
parent f338325bd9
commit 62dfce532d
9 changed files with 210 additions and 163 deletions

View File

@ -3,5 +3,5 @@ name: NoCheatPlugin
author: Evenprime author: Evenprime
main: cc.co.evenprime.bukkit.nocheat.NoCheatPlugin main: cc.co.evenprime.bukkit.nocheat.NoCheatPlugin
version: 0.5.2 version: 0.5.2a

View File

@ -0,0 +1,24 @@
package cc.co.evenprime.bukkit.nocheat;
import org.bukkit.Material;
import org.bukkit.event.block.BlockPlaceEvent;
public class BlockPlacingCheck {
public static void check(BlockPlaceEvent event) {
if(NoCheatPlugin.Permissions != null && NoCheatPlugin.Permissions.has(event.getPlayer(), "nocheat.airbuild")) {
return;
}
else if(NoCheatPlugin.Permissions == null && event.getPlayer().isOp() ) {
return;
}
if(event.getBlockAgainst().getType() == Material.AIR) {
event.setCancelled(true);
NoCheatPlugin.log.warning("NoCheatPlugin: Airbuild violation: "+event.getPlayer().getName()+" tried to place block " + event.getBlockPlaced().getType() + " against air");
}
}
}

View File

@ -6,7 +6,6 @@ import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerMoveEvent;
import cc.co.evenprime.bukkit.nocheat.NoCheatPluginPlayerListener.NoCheatPluginData;
public class MovingCheck { public class MovingCheck {
@ -134,114 +133,109 @@ public class MovingCheck {
// 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
if(!event.isCancelled()) { double xDistance = Math.abs(from.getX() - to.getX());
double xDistance = Math.abs(from.getX() - to.getX()); double zDistance = Math.abs(from.getZ() - to.getZ());
double zDistance = Math.abs(from.getZ() - to.getZ());
// How far are we off? // How far are we off?
if(xDistance > NoCheatConfiguration.movingDistanceHigh || zDistance > NoCheatConfiguration.movingDistanceHigh) { if(xDistance > NoCheatConfiguration.movingDistanceHigh || zDistance > NoCheatConfiguration.movingDistanceHigh) {
vl = vl > HEAVY ? vl : HEAVY; vl = vl > HEAVY ? vl : HEAVY;
} }
else if(xDistance > NoCheatConfiguration.movingDistanceMed || zDistance > NoCheatConfiguration.movingDistanceMed) { else if(xDistance > NoCheatConfiguration.movingDistanceMed || zDistance > NoCheatConfiguration.movingDistanceMed) {
vl = vl > NORMAL ? vl : NORMAL; vl = vl > NORMAL ? vl : NORMAL;
} }
else if(xDistance > NoCheatConfiguration.movingDistanceLow || zDistance > NoCheatConfiguration.movingDistanceLow) { else if(xDistance > NoCheatConfiguration.movingDistanceLow || zDistance > NoCheatConfiguration.movingDistanceLow) {
vl = vl > MINOR ? vl : MINOR; vl = vl > MINOR ? vl : MINOR;
}
} }
// If we didn't already cancel the event, check the vertical movement
if(!event.isCancelled()) {
// pre-calculate boundary values that are needed multiple times in the following checks // pre-calculate boundary values that are needed multiple times in the following checks
// the array each contains [lowerX, higherX, Y, lowerZ, higherZ] // the array each contains [lowerX, higherX, Y, lowerZ, higherZ]
int fromValues[] = {floor_double(from.getX() - 0.3D), (int)Math.floor(from.getX() + 0.3D), from.getBlockY(), floor_double(from.getZ() - 0.3D),(int)Math.floor(from.getZ() + 0.3D) }; int fromValues[] = {floor_double(from.getX() - 0.3D), (int)Math.floor(from.getX() + 0.3D), from.getBlockY(), floor_double(from.getZ() - 0.3D),(int)Math.floor(from.getZ() + 0.3D) };
int toValues[] = {floor_double(to.getX() - 0.3D), (int)Math.floor(to.getX() + 0.3D), to.getBlockY(), floor_double(to.getZ() - 0.3D), (int)Math.floor(to.getZ() + 0.3D) }; int toValues[] = {floor_double(to.getX() - 0.3D), (int)Math.floor(to.getX() + 0.3D), to.getBlockY(), floor_double(to.getZ() - 0.3D), (int)Math.floor(to.getZ() + 0.3D) };
// compare locations to the world to guess if the player is standing on the ground, a half-block or next to a ladder // compare locations to the world to guess if the player is standing on the ground, a half-block or next to a ladder
boolean onGroundFrom = playerIsOnGround(from.getWorld(), fromValues, from); boolean onGroundFrom = playerIsOnGround(from.getWorld(), fromValues, from);
boolean onGroundTo = playerIsOnGround(from.getWorld(), toValues, to); boolean onGroundTo = playerIsOnGround(from.getWorld(), toValues, to);
// Both locations seem to be on solid ground or at a ladder // Both locations seem to be on solid ground or at a ladder
if(onGroundFrom && onGroundTo) if(onGroundFrom && onGroundTo)
{ {
// reset jumping // reset jumping
data.phase = 0; data.phase = 0;
// Check if the player isn't 'walking' up unrealistically far in one step // Check if the player isn't 'walking' up unrealistically far in one step
// Finally found out why this can happen: // Finally found out why this can happen:
// If a player runs into a wall at an angle from above, the game tries to // If a player runs into a wall at an angle from above, the game tries to
// place him above the block he bumped into, by placing him 0.5 m above // place him above the block he bumped into, by placing him 0.5 m above
// the target block // the target block
if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) {
double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase]; double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase];
if(offset > 2D) vl = vl > HEAVY ? vl : HEAVY; if(offset > 2D) vl = vl > HEAVY ? vl : HEAVY;
else if(offset > 0.6D) vl = vl > NORMAL ? vl : NORMAL; else if(offset > 0.6D) vl = vl > NORMAL ? vl : NORMAL;
else vl = vl > MINOR ? vl : MINOR; else vl = vl > MINOR ? vl : MINOR;
}
} }
// player is starting to jump (or starting to fall down somewhere) }
else if(onGroundFrom && !onGroundTo) // player is starting to jump (or starting to fall down somewhere)
{ else if(onGroundFrom && !onGroundTo)
// reset jumping {
data.phase = 0; // reset jumping
data.phase = 0;
// Check if player isn't jumping too high // Check if player isn't jumping too high
if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) {
double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase]; double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase];
if(offset > 2D) vl = vl > HEAVY ? vl : HEAVY; if(offset > 2D) vl = vl > HEAVY ? vl : HEAVY;
else if(offset > 0.6D) vl = vl > NORMAL ? vl : NORMAL; else if(offset > 0.6D) vl = vl > NORMAL ? vl : NORMAL;
else vl = vl > MINOR ? vl : MINOR; else vl = vl > MINOR ? vl : MINOR;
}
else if(to.getY() <= from.getY()) {
// Very special case if running over a cliff and then immediately jumping.
// Some sort of "air jump", MC allows it, so we have to do so too.
}
else data.phase++; // Setup next phase of the jump
} }
// player is probably landing somewhere else if(to.getY() <= from.getY()) {
else if(!onGroundFrom && onGroundTo) // Very special case if running over a cliff and then immediately jumping.
{ // Some sort of "air jump", MC allows it, so we have to do so too.
// Check if player isn't landing to high (sounds weird, but has its use) }
if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) { else data.phase++; // Setup next phase of the jump
}
double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase]; // player is probably landing somewhere
else if(!onGroundFrom && onGroundTo)
if(offset > 2D) vl = vl > HEAVY ? vl : HEAVY; {
else if(offset > 0.6D) vl = vl > NORMAL ? vl : NORMAL; // Check if player isn't landing to high (sounds weird, but has its use)
else vl = vl > MINOR ? vl : MINOR; if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) {
}
else { double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase];
data.phase = 0; // He is on ground now, so reset the jump
} if(offset > 2D) vl = vl > HEAVY ? vl : HEAVY;
else if(offset > 0.6D) vl = vl > NORMAL ? vl : NORMAL;
else vl = vl > MINOR ? vl : MINOR;
} }
// Player is moving through air (during jumping, falling)
else { else {
// May also be at the very edge of a platform (I seem to not be able to reliably tell if that's the case) data.phase = 0; // He is on ground now, so reset the jump
if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) {
double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase];
if(offset > 2D) vl = vl > HEAVY ? vl : HEAVY;
else if(offset > 0.6D) vl = vl > NORMAL ? vl : NORMAL;
else vl = vl > MINOR ? vl : MINOR;
}
else {
data.phase++; // Enter next phase of the flight
}
}
// do a security check on the jumping phase, such that we don't get
// OutOfArrayBoundsExceptions at long air times (falling off high places)
if(!(data.phase < jumpingPhases.length)) {
data.phase = jumpingPhases.length - 1;
} }
} }
// Player is moving through air (during jumping, falling)
else {
// May also be at the very edge of a platform (I seem to not be able to reliably tell if that's the case)
if(!(to.getY() - from.getY() < jumpingPhases[data.phase])) {
double offset = (to.getY() - from.getY()) - jumpingPhases[data.phase];
if(offset > 2D) vl = vl > HEAVY ? vl : HEAVY;
else if(offset > 0.6D) vl = vl > NORMAL ? vl : NORMAL;
else vl = vl > MINOR ? vl : MINOR;
}
else {
data.phase++; // Enter next phase of the flight
}
}
// do a security check on the jumping phase, such that we don't get
// OutOfArrayBoundsExceptions at long air times (falling off high places)
if(!(data.phase < jumpingPhases.length)) {
data.phase = jumpingPhases.length - 1;
}
// Treat the violation(s) // Treat the violation(s)
switch(vl) { switch(vl) {
case MINOR: case MINOR:
@ -273,27 +267,30 @@ public class MovingCheck {
} }
else if(data.minorViolationsInARow % 2 == 0) { else if(data.minorViolationsInARow % 2 == 0) {
// now we need it // now we need it
resetPlayer(data, event);
NoCheatPlugin.log.info("NoCheatPlugin: Moving violation: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ())); NoCheatPlugin.log.info("NoCheatPlugin: Moving violation: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()));
resetPlayer(data, event);
} }
} }
protected static void normalViolation(NoCheatPluginData data, PlayerMoveEvent event) { protected static void normalViolation(NoCheatPluginData data, PlayerMoveEvent event) {
resetPlayer(data, event);
data.normalViolationsInARow++;
// Log the first violation in a row // Log the first violation in a row
if(data.normalViolationsInARow <= 1) if(data.normalViolationsInARow <= 1)
NoCheatPlugin.log.warning("NoCheatPlugin: Moving violation: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ())); NoCheatPlugin.log.warning("NoCheatPlugin: Moving violation: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()));
}
resetPlayer(data, event);
data.normalViolationsInARow++;
}
protected static void heavyViolation(NoCheatPluginData data, PlayerMoveEvent event) { protected static void heavyViolation(NoCheatPluginData data, PlayerMoveEvent event) {
if(data.heavyViolationsInARow == 0)
NoCheatPlugin.log.severe("NoCheatPlugin: Moving violation: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()));
resetPlayer(data, event); resetPlayer(data, event);
data.heavyViolationsInARow++; data.heavyViolationsInARow++;
// Log the first violation in a row // Log the first violation in a row
if(data.heavyViolationsInARow <= 1)
NoCheatPlugin.log.severe("NoCheatPlugin: Moving violation: "+event.getPlayer().getName()+" from " + String.format("(%.5f, %.5f, %.5f) to (%.5f, %.5f, %.5f)", event.getFrom().getX(), event.getFrom().getY(), event.getFrom().getZ(), event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()));
} }
protected static void legitimateMove(NoCheatPluginData data, PlayerMoveEvent event) { protected static void legitimateMove(NoCheatPluginData data, PlayerMoveEvent event) {

View File

@ -1,8 +1,11 @@
package cc.co.evenprime.bukkit.nocheat; package cc.co.evenprime.bukkit.nocheat;
import java.io.File; import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.bukkit.entity.Player;
import org.bukkit.event.Event.Priority; import org.bukkit.event.Event.Priority;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
@ -25,17 +28,39 @@ public class NoCheatPlugin extends JavaPlugin {
private final NoCheatPluginPlayerListener playerListener; private final NoCheatPluginPlayerListener playerListener;
private final NoCheatPluginVehicleListener vehicleListener; private final NoCheatPluginVehicleListener vehicleListener;
private final NoCheatPluginBlockListener blockListener;
public static Logger log; public static Logger log;
public static PermissionHandler Permissions = null; public static PermissionHandler Permissions = null;
// Store data between Events
public static Map<Player, NoCheatPluginData> playerData = new HashMap<Player, NoCheatPluginData>();
public NoCheatPlugin() { public NoCheatPlugin() {
playerListener = new NoCheatPluginPlayerListener(this); playerListener = new NoCheatPluginPlayerListener();
vehicleListener = new NoCheatPluginVehicleListener(this, playerListener); vehicleListener = new NoCheatPluginVehicleListener(playerListener);
blockListener = new NoCheatPluginBlockListener();
log = NoCheatConfiguration.logger; log = NoCheatConfiguration.logger;
} }
public static NoCheatPluginData getPlayerData(Player p) {
NoCheatPluginData data = null;
if((data = playerData.get(p)) == null ) {
synchronized(playerData) {
data = playerData.get(p);
if(data == null) {
// If we have no data for the player, create some
data = new NoCheatPluginData();
playerData.put(p, data);
}
}
}
return data;
}
public void onDisable() { public void onDisable() {
} }
@ -47,6 +72,7 @@ public class NoCheatPlugin extends JavaPlugin {
pm.registerEvent(Event.Type.PLAYER_QUIT, playerListener, Priority.Monitor, this); pm.registerEvent(Event.Type.PLAYER_QUIT, playerListener, Priority.Monitor, this);
pm.registerEvent(Event.Type.VEHICLE_EXIT, vehicleListener, Priority.Monitor, this); pm.registerEvent(Event.Type.VEHICLE_EXIT, vehicleListener, Priority.Monitor, this);
pm.registerEvent(Event.Type.VEHICLE_DAMAGE, vehicleListener, Priority.Monitor, this); pm.registerEvent(Event.Type.VEHICLE_DAMAGE, vehicleListener, Priority.Monitor, this);
pm.registerEvent(Event.Type.BLOCK_PLACED, blockListener, Priority.Low, this);
PluginDescriptionFile pdfFile = this.getDescription(); PluginDescriptionFile pdfFile = this.getDescription();
Logger.getLogger("Minecraft").info( "NoCheat version " + pdfFile.getVersion() + " is enabled!" ); Logger.getLogger("Minecraft").info( "NoCheat version " + pdfFile.getVersion() + " is enabled!" );
@ -58,14 +84,11 @@ public class NoCheatPlugin extends JavaPlugin {
public void setupPermissions() { public void setupPermissions() {
Plugin test = this.getServer().getPluginManager().getPlugin("Permissions"); Plugin test = this.getServer().getPluginManager().getPlugin("Permissions");
if(test != null) {
if(Permissions == null) { Permissions = ((Permissions)test).getHandler();
if(test != null) { } else {
Permissions = ((Permissions)test).getHandler(); log.info("Nocheat couldn't find Permissions plugin. Fallback to 'isOp()' equals 'all allowed'.");
} else { this.getServer().getPluginManager().disablePlugin(this);
log.info("Nocheat couldn't find Permissions plugin. Fallback to OP -> all allowed.");
this.getServer().getPluginManager().disablePlugin(this);
}
} }
} }

View File

@ -0,0 +1,19 @@
package cc.co.evenprime.bukkit.nocheat;
import org.bukkit.event.block.BlockListener;
import org.bukkit.event.block.BlockPlaceEvent;
public class NoCheatPluginBlockListener extends BlockListener {
public NoCheatPluginBlockListener() {
}
@Override
public void onBlockPlace(BlockPlaceEvent event) {
if(!event.isCancelled() && NoCheatConfiguration.speedhackCheckActive)
BlockPlacingCheck.check(event);
}
}

View File

@ -0,0 +1,26 @@
package cc.co.evenprime.bukkit.nocheat;
import org.bukkit.Location;
/**
* Storage for data persistence between events
*
*/
public class NoCheatPluginData {
/**
* Don't rely on any of these yet, they are likely going to
* change their name/functionality
*/
int phase = 0; // current jumpingPhase
long lastSpeedHackCheck = System.currentTimeMillis(); // timestamp of last check for speedhacks
int eventsSinceLastSpeedHackCheck = 0; // used to identify speedhacks
int ignoreNextXEvents = 0;
int minorViolationsInARow = 0;
int normalViolationsInARow = 0;
int heavyViolationsInARow = 0;
Location movingSetBackPoint = null;
NoCheatPluginData() { }
}

View File

@ -1,12 +1,8 @@
package cc.co.evenprime.bukkit.nocheat; package cc.co.evenprime.bukkit.nocheat;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Location;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerEvent; import org.bukkit.event.player.PlayerEvent;
import org.bukkit.event.player.PlayerListener; import org.bukkit.event.player.PlayerListener;
import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerMoveEvent;
@ -18,47 +14,18 @@ import org.bukkit.event.player.PlayerMoveEvent;
public class NoCheatPluginPlayerListener extends PlayerListener { public class NoCheatPluginPlayerListener extends PlayerListener {
/**
* Storage for data persistence between events
* public NoCheatPluginPlayerListener() { }
*/
public class NoCheatPluginData {
/**
* Don't rely on any of these yet, they are likely going to
* change their name/functionality
*/
int phase = 0; // current jumpingPhase
long lastSpeedHackCheck = System.currentTimeMillis(); // timestamp of last check for speedhacks
int eventsSinceLastSpeedHackCheck = 0; // used to identify speedhacks
int ignoreNextXEvents = 0;
int minorViolationsInARow = 0;
int normalViolationsInARow = 0;
int heavyViolationsInARow = 0;
Location movingSetBackPoint = null;
private NoCheatPluginData() { }
}
private final NoCheatPlugin plugin;
// Store data between Events
private static Map<Player, NoCheatPluginData> playerData = new HashMap<Player, NoCheatPluginData>();
public NoCheatPluginPlayerListener(NoCheatPlugin instance) {
plugin = instance;
}
@Override @Override
public void onPlayerQuit(PlayerEvent event) { public void onPlayerQuit(PlayerEvent event) {
playerData.remove(event.getPlayer()); NoCheatPlugin.playerData.remove(event.getPlayer());
} }
public void ingoreNextXEvents(Entity player, int count) { public void ingoreNextXEvents(Entity player, int count) {
NoCheatPluginData data = playerData.get(player); NoCheatPluginData data = NoCheatPlugin.playerData.get(player);
if(data != null) { if(data != null) {
data.ignoreNextXEvents = count; data.ignoreNextXEvents = count;
} }
@ -68,14 +35,8 @@ public class NoCheatPluginPlayerListener extends PlayerListener {
public void onPlayerMove(PlayerMoveEvent event) { public void onPlayerMove(PlayerMoveEvent event) {
// Get the player-specific data // Get the player-specific data
NoCheatPluginData data = null; NoCheatPluginData data = NoCheatPlugin.getPlayerData(event.getPlayer());
if((data = playerData.get(event.getPlayer())) == null ) {
// If we have no data for the player, create some
data = new NoCheatPluginData();
playerData.put(event.getPlayer(), data);
}
if(data.ignoreNextXEvents > 0 ) { if(data.ignoreNextXEvents > 0 ) {
data.ignoreNextXEvents--; data.ignoreNextXEvents--;
return; return;

View File

@ -12,11 +12,9 @@ import org.bukkit.event.vehicle.VehicleListener;
public class NoCheatPluginVehicleListener extends VehicleListener { public class NoCheatPluginVehicleListener extends VehicleListener {
private final NoCheatPlugin plugin;
private final NoCheatPluginPlayerListener playerListener; private final NoCheatPluginPlayerListener playerListener;
public NoCheatPluginVehicleListener(NoCheatPlugin plugin, NoCheatPluginPlayerListener playerListener) { public NoCheatPluginVehicleListener(NoCheatPluginPlayerListener playerListener) {
this.plugin = plugin;
this.playerListener = playerListener; this.playerListener = playerListener;
} }

View File

@ -2,7 +2,6 @@ package cc.co.evenprime.bukkit.nocheat;
import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerMoveEvent;
import cc.co.evenprime.bukkit.nocheat.NoCheatPluginPlayerListener.NoCheatPluginData;
public class SpeedhackCheck { public class SpeedhackCheck {