Even more documentation

This commit is contained in:
Evenprime 2012-03-01 19:27:33 +01:00
parent b742875ead
commit 32d09868d9
10 changed files with 196 additions and 49 deletions

View File

@ -82,7 +82,7 @@ public class DirectionCheck extends BlockPlaceCheck {
// If the player is still in penalty time, cancel the event anyway
if(data.directionLastViolationTime + cc.directionPenaltyTime > time) {
// A saveguard to avoid people getting stuck in penalty time
// A safeguard to avoid people getting stuck in penalty time
// indefinitely in case the system time of the server gets changed
if(data.directionLastViolationTime > time) {
data.directionLastViolationTime = 0;

View File

@ -11,6 +11,11 @@ import cc.co.evenprime.bukkit.nocheat.checks.CheckUtil;
import cc.co.evenprime.bukkit.nocheat.config.Permissions;
import cc.co.evenprime.bukkit.nocheat.data.Statistics.Id;
/**
* The DirectionCheck will find out if a player tried to interact with something
* that's not in his field of view.
*
*/
public class DirectionCheck extends FightCheck {
public DirectionCheck(NoCheat plugin) {
@ -23,7 +28,7 @@ public class DirectionCheck extends FightCheck {
final long time = System.currentTimeMillis();
// Get the width of the damagee
// Get the damagee (entity that got hit)
Entity entity = data.damagee;
// Safeguard, if entity is complex, this check will fail
@ -32,40 +37,51 @@ public class DirectionCheck extends FightCheck {
return false;
}
// Find out how wide the entity is
final float width = entity.length > entity.width ? entity.length : entity.width;
// entity.height is broken and will always be 0, therefore calculate height instead
// entity.height is broken and will always be 0, therefore
// calculate height instead based on boundingBox
final double height = entity.boundingBox.e - entity.boundingBox.b;
// How far "off" is the player with his aim. We calculate from the
// players eye location and view direction to the center of the target
// entity. If the line of sight is more too far off, "off" will be
// bigger than 0
final double off = CheckUtil.directionCheck(player, entity.locX, entity.locY + (height / 2D), entity.locZ, width, height, cc.directionPrecision);
if(off < 0.1D) {
// Player did probably nothing wrong
// reduce violation counter
// reduce violation counter to reward him
data.directionVL *= 0.80D;
} else {
// Player failed the check
// Increment violation counter
// Increment violation counter and statistics, but only if there
// wasn't serious lag
if(!plugin.skipCheck()) {
double sqrt = Math.sqrt(off);
data.directionVL += sqrt;
incrementStatistics(player, Id.FI_DIRECTION, sqrt);
}
// Execute whatever actions are associated with this check and the
// violation level and find out if we should cancel the event
cancel = executeActions(player, cc.directionActions, data.directionVL);
if(cancel) {
// Needed to calculate penalty times
// if we should cancel, remember the current time too
data.directionLastViolationTime = time;
}
}
// If the player is still in penalty time, cancel the event anyway
if(data.directionLastViolationTime + cc.directionPenaltyTime > time) {
// A safeguard to avoid people getting stuck in penalty time
// indefinitely in case the system time of the server gets changed
if(data.directionLastViolationTime > time) {
System.out.println("Nocheat noted that your time ran backwards for " + (data.directionLastViolationTime - time) + " ms");
// Security check for server time changed situations
data.directionLastViolationTime = 0;
}
// He is in penalty time, therefore request cancelling of the event
return true;
}
@ -81,7 +97,7 @@ public class DirectionCheck extends FightCheck {
public String getParameter(ParameterName wildcard, NoCheatPlayer player) {
if(wildcard == ParameterName.VIOLATIONS)
return String.format(Locale.US, "%d", (int) getData(player.getDataStore()).directionVL);
return String.format(Locale.US, "%d", (int) getData(player).directionVL);
else
return super.getParameter(wildcard, player);
}

View File

@ -24,7 +24,15 @@ public abstract class FightCheck extends Check {
public abstract boolean isEnabled(FightConfig cc);
public static FightData getData(DataStore base) {
/**
* Get the "FightData" object that belongs to the player. Will ensure
* that such a object exists and if not, create one
*
* @param player
* @return
*/
public static FightData getData(NoCheatPlayer player) {
DataStore base = player.getDataStore();
FightData data = base.get(id);
if(data == null) {
data = new FightData();
@ -33,6 +41,17 @@ public abstract class FightCheck extends Check {
return data;
}
/**
* Get the FightConfig object that belongs to the world that the player
* currently resides in.
*
* @param player
* @return
*/
public static FightConfig getConfig(NoCheatPlayer player) {
return getConfig(player.getConfigurationStore());
}
public static FightConfig getConfig(ConfigurationCacheStore cache) {
FightConfig config = cache.get(id);
if(config == null) {

View File

@ -19,8 +19,12 @@ import cc.co.evenprime.bukkit.nocheat.EventManager;
import cc.co.evenprime.bukkit.nocheat.NoCheat;
import cc.co.evenprime.bukkit.nocheat.NoCheatPlayer;
import cc.co.evenprime.bukkit.nocheat.config.ConfigurationCacheStore;
import cc.co.evenprime.bukkit.nocheat.config.Permissions;
/**
* Central location to listen to events that are
* relevant for the fight checks
*
*/
public class FightCheckListener implements Listener, EventManager {
private final List<FightCheck> checks;
@ -30,7 +34,9 @@ public class FightCheckListener implements Listener, EventManager {
public FightCheckListener(NoCheat plugin) {
this.checks = new ArrayList<FightCheck>(3);
this.checks = new ArrayList<FightCheck>(4);
// Keep these in a list, because they can be executed in a bundle
this.checks.add(new SpeedCheck(plugin));
this.checks.add(new NoswingCheck(plugin));
this.checks.add(new DirectionCheck(plugin));
@ -41,9 +47,14 @@ public class FightCheckListener implements Listener, EventManager {
this.plugin = plugin;
}
/**
* We listen to EntityDamage events for obvious reasons
* @param event The EntityDamage Event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void entityDamage(final EntityDamageEvent event) {
// Filter some unwanted events right now
if(event.isCancelled() || !(event instanceof EntityDamageByEntityEvent))
return;
@ -59,44 +70,55 @@ public class FightCheckListener implements Listener, EventManager {
}
}
/**
* We listen to EntityDamage events (again) for obvious reasons
* @param event The EntityDamage Event
*/
@EventHandler(priority = EventPriority.LOW)
public void entityDamageForGodmodeCheck(final EntityDamageEvent event) {
if(event.isCancelled())
return;
// Filter unwanted events right here
final Entity entity = event.getEntity();
if(!(entity instanceof Player) || entity.isDead()) {
return;
}
NoCheatPlayer player = plugin.getPlayer((Player) entity);
FightConfig cc = FightCheck.getConfig(player.getConfigurationStore());
FightConfig cc = FightCheck.getConfig(player);
if(!godmodeCheck.isEnabled(cc) || player.hasPermission(godmodeCheck.permission)) {
return;
}
FightData data = FightCheck.getData(player.getDataStore());
FightData data = FightCheck.getData(player);
// Run the godmode check on the attacked player
boolean cancelled = godmodeCheck.check(plugin.getPlayer((Player) entity), data, cc);
// It requested to "cancel" the players invulnerability, so set his
// noDamageTicks to 0
if(cancelled) {
// Remove the invulnerability from the player
player.getPlayer().setNoDamageTicks(0);
}
}
/**
* A player attacked something with DamageCause ENTITY_ATTACK. That's most
* likely what we want to really check.
*
* @param event The EntityDamageByEntityEvent
*/
private void normalDamage(final EntityDamageByEntityEvent event) {
final Player damager = (Player) event.getDamager();
final NoCheatPlayer player = plugin.getPlayer(damager);
final FightConfig cc = FightCheck.getConfig(player.getConfigurationStore());
if(!cc.damageChecks || player.hasPermission(Permissions.FIGHT)) {
return;
}
final FightData data = FightCheck.getData(player.getDataStore());
final FightConfig cc = FightCheck.getConfig(player);
final FightData data = FightCheck.getData(player);
// For some reason we decided to skip this event anyway
if(data.skipNext) {
@ -106,9 +128,10 @@ public class FightCheckListener implements Listener, EventManager {
boolean cancelled = false;
// Get the attacked entity
// Get the attacked entity and remember it
data.damagee = ((CraftEntity) event.getEntity()).getHandle();
// Run through the four main checks
for(FightCheck check : checks) {
// If it should be executed, do it
if(!cancelled && check.isEnabled(cc) && !player.hasPermission(check.permission)) {
@ -116,18 +139,27 @@ public class FightCheckListener implements Listener, EventManager {
}
}
// Forget the attacked entity (to allow garbage collecting etc.
data.damagee = null;
// One of the checks requested the event to be cancelled, so do it
if(cancelled)
event.setCancelled(cancelled);
}
/**
* There is an unofficial agreement that if a plugin wants an attack to
* not get checked by NoCheat, it either has to use a Damage type different
* from ENTITY_ATTACK or fire an event with damage type CUSTOM and damage
* 0 directly before the to-be-ignored event.
* @param event The EntityDamageByEntityEvent
*/
private void customDamage(final EntityDamageByEntityEvent event) {
final Player damager = (Player) event.getDamager();
final NoCheatPlayer player = plugin.getPlayer(damager);
final FightData data = FightCheck.getData(player.getDataStore());
final FightData data = FightCheck.getData(player);
// Skip the next damage event, because it is with high probability
// something from the Heroes plugin
@ -136,8 +168,15 @@ public class FightCheckListener implements Listener, EventManager {
return;
}
/**
* We listen to death events to prevent a very specific method of doing
* godmode.
*
* @param event The EntityDeathEvent
*/
@EventHandler(priority = EventPriority.MONITOR)
protected void death(final EntityDeathEvent event) {
// Only interested in dying players
if(!(event.getEntity() instanceof CraftPlayer)) {
return;
}
@ -145,9 +184,14 @@ public class FightCheckListener implements Listener, EventManager {
godmodeCheck.death((CraftPlayer) event.getEntity());
}
/**
* We listen to PlayerAnimationEvent because it is used for arm swinging
* @param event The PlayerAnimationEvent
*/
@EventHandler(priority = EventPriority.MONITOR)
protected void armSwing(final PlayerAnimationEvent event) {
FightCheck.getData(plugin.getPlayer(event.getPlayer()).getDataStore()).armswung = true;
// Set a flag telling us that the arm has been swung
FightCheck.getData(plugin.getPlayer(event.getPlayer())).armswung = true;
}
public List<String> getActiveChecks(ConfigurationCacheStore cc) {

View File

@ -6,26 +6,34 @@ import cc.co.evenprime.bukkit.nocheat.config.ConfPaths;
import cc.co.evenprime.bukkit.nocheat.config.NoCheatConfiguration;
import cc.co.evenprime.bukkit.nocheat.config.Permissions;
/**
* Configurations specific for the "Fight" checks
* Every world gets one of these assigned to it, or if a world doesn't get
* it's own, it will use the "global" version
*
*/
public class FightConfig implements ConfigItem {
public final boolean directionCheck;
public final double directionPrecision;
public final ActionList directionActions;
public final long directionPenaltyTime;
public final boolean noswingCheck;
public final ActionList noswingActions;
public final boolean reachCheck;
public final double reachLimit;
public final long reachPenaltyTime;
public final ActionList reachActions;
public final int speedAttackLimit;
public final ActionList speedActions;
public final boolean speedCheck;
public final boolean godmodeCheck;
public final ActionList godmodeActions;
public final boolean damageChecks;
public FightConfig(NoCheatConfiguration data) {
directionCheck = data.getBoolean(ConfPaths.FIGHT_DIRECTION_CHECK);
@ -44,7 +52,5 @@ public class FightConfig implements ConfigItem {
godmodeCheck = data.getBoolean(ConfPaths.FIGHT_GODMODE_CHECK);
godmodeActions = data.getActionList(ConfPaths.FIGHT_GODMODE_ACTIONS, Permissions.FIGHT_GODMODE);
damageChecks = directionCheck || noswingCheck || reachCheck || speedCheck;
}
}

View File

@ -3,24 +3,38 @@ package cc.co.evenprime.bukkit.nocheat.checks.fight;
import net.minecraft.server.Entity;
import cc.co.evenprime.bukkit.nocheat.DataItem;
/**
* Player specific data for the fight checks
*
*/
public class FightData implements DataItem {
// Keep track of the violation levels of the checks
public double directionVL;
public double noswingVL;
public double reachVL;
public int speedVL;
public double godmodeVL;
// For checks that have penalty time
public long directionLastViolationTime;
public long reachLastViolationTime;
// godmode check needs to know these
public long godmodeLastDamageTime;
public int godmodeLastAge;
public int godmodeBuffer = 40;
// While handling an event, use this to keep the attacked entity
public Entity damagee;
// The player swung his arm
public boolean armswung = true;
// For some reason the next event should be ignored
public boolean skipNext = false;
// Keep track of time and amount of attacks
public long speedTime;
public int speedAttackCount;

View File

@ -10,6 +10,11 @@ import cc.co.evenprime.bukkit.nocheat.actions.ParameterName;
import cc.co.evenprime.bukkit.nocheat.config.Permissions;
import cc.co.evenprime.bukkit.nocheat.data.Statistics;
/**
* The Godmode Check will find out if a player tried to stay invulnerable after
* being hit or after dying
*
*/
public class GodmodeCheck extends FightCheck {
public GodmodeCheck(NoCheat plugin) {
@ -22,6 +27,7 @@ public class GodmodeCheck extends FightCheck {
boolean cancelled = false;
long time = System.currentTimeMillis();
// Check at most once a second
if(data.godmodeLastDamageTime + 1000L < time) {
data.godmodeLastDamageTime = time;
@ -34,14 +40,18 @@ public class GodmodeCheck extends FightCheck {
int nodamageTicks = player.getPlayer().getNoDamageTicks();
if(nodamageTicks > 0 && ageDiff < 15) {
// He is invulnerable and didn't age fast enough, that costs some points
// He is invulnerable and didn't age fast enough, that costs
// some points
data.godmodeBuffer -= (15 - ageDiff);
// Still points left?
if(data.godmodeBuffer <= 0) {
// No
// No, that means VL and statistics increased
data.godmodeVL -= data.godmodeBuffer;
incrementStatistics(player, Statistics.Id.FI_GODMODE, -data.godmodeBuffer);
// Execute whatever actions are associated with this check and the
// violation level and find out if we should cancel the event
cancelled = executeActions(player, cc.godmodeActions, data.godmodeVL);
}
} else {
@ -51,8 +61,10 @@ public class GodmodeCheck extends FightCheck {
}
if(data.godmodeBuffer < 0) {
// Can't have less than 0
data.godmodeBuffer = 0;
} else if(data.godmodeBuffer > 30) {
// And 30 is enough for simple lag situations
data.godmodeBuffer = 30;
}
@ -72,27 +84,33 @@ public class GodmodeCheck extends FightCheck {
public String getParameter(ParameterName wildcard, NoCheatPlayer player) {
if(wildcard == ParameterName.VIOLATIONS)
return String.format(Locale.US, "%d", (int) getData(player.getDataStore()).godmodeVL);
return String.format(Locale.US, "%d", (int) getData(player).godmodeVL);
else
return super.getParameter(wildcard, player);
}
/**
* If a player apparently died, make sure he really dies after some time
* if he didn't already.
* if he didn't already, by setting up a Bukkit task
*
* @param player
* @param player The player
*/
public void death(CraftPlayer player) {
// First check if the player is really dead (e.g. another plugin could
// have just fired an artificial event)
if(player.getHealth() <= 0 && player.isDead()) {
try {
final EntityPlayer entity = player.getHandle();
// Schedule a task to be executed in roughly 1.5 seconds
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
public void run() {
try {
// Check again if the player should be dead, and
// if the game didn't mark him as dead
if(entity.getHealth() <= 0 && !entity.dead) {
// Artifically "kill" him
entity.deathTicks = 19;
entity.a(true);
}

View File

@ -7,6 +7,11 @@ import cc.co.evenprime.bukkit.nocheat.actions.ParameterName;
import cc.co.evenprime.bukkit.nocheat.config.Permissions;
import cc.co.evenprime.bukkit.nocheat.data.Statistics.Id;
/**
* We require that the player moves his arm between attacks, this is
* what gets checked here.
*
*/
public class NoswingCheck extends FightCheck {
public NoswingCheck(NoCheat plugin) {
@ -19,12 +24,16 @@ public class NoswingCheck extends FightCheck {
// did he swing his arm before?
if(data.armswung) {
// Yes, reward him with reduction of his vl
data.armswung = false;
data.noswingVL *= 0.90D;
} else {
// No, increase vl and statistics
data.noswingVL += 1;
incrementStatistics(player, Id.FI_NOSWING, 1);
// Execute whatever actions are associated with this check and the
// violation level and find out if we should cancel the event
cancel = executeActions(player, cc.noswingActions, data.noswingVL);
}
@ -36,10 +45,11 @@ public class NoswingCheck extends FightCheck {
return cc.noswingCheck;
}
@Override
public String getParameter(ParameterName wildcard, NoCheatPlayer player) {
if(wildcard == ParameterName.VIOLATIONS)
return String.format(Locale.US, "%d", (int) getData(player.getDataStore()).noswingVL);
return String.format(Locale.US, "%d", (int) getData(player).noswingVL);
else
return super.getParameter(wildcard, player);
}

View File

@ -4,7 +4,6 @@ import java.util.Locale;
import net.minecraft.server.Entity;
import net.minecraft.server.EntityComplex;
import net.minecraft.server.EntityComplexPart;
import net.minecraft.server.EntityGiantZombie;
import cc.co.evenprime.bukkit.nocheat.NoCheat;
import cc.co.evenprime.bukkit.nocheat.NoCheatPlayer;
import cc.co.evenprime.bukkit.nocheat.actions.ParameterName;
@ -12,6 +11,11 @@ import cc.co.evenprime.bukkit.nocheat.checks.CheckUtil;
import cc.co.evenprime.bukkit.nocheat.config.Permissions;
import cc.co.evenprime.bukkit.nocheat.data.Statistics.Id;
/**
* The reach check will find out if a player interacts with something that's
* too far away
*
*/
public class ReachCheck extends FightCheck {
public ReachCheck(NoCheat plugin) {
@ -29,45 +33,48 @@ public class ReachCheck extends FightCheck {
// Safeguard, if entity is Giant or Ender Dragon, this check will fail
// due to giant and hard to define hitboxes
if(entity instanceof EntityComplex || entity instanceof EntityComplexPart || entity instanceof EntityGiantZombie) {
if(entity instanceof EntityComplex || entity instanceof EntityComplexPart) {
return false;
}
// height = 2.0D as minecraft doesn't store the height of entities,
// and that should be enough. Because entityLocations are always set
// to center bottom of the hitbox, increase "y" location by 1/2
// height to get the "center" of the hitbox
// Distance is calculated from eye location to center of targeted
// If the player is further away from his target than allowed, the
// difference will be assigned to "distance"
final double off = CheckUtil.reachCheck(player, entity.locX, entity.locY + 1.0D, entity.locZ, cc.reachLimit);
if(off < 0.1D) {
// Player did probably nothing wrong
// reduce violation counter
// reduce violation counter to reward him
data.reachVL *= 0.80D;
} else {
// Player failed the check
// Increment violation counter
// This is influenced by lag, so don't do it if there was server lag
// Increment violation counter and statistics
// This is influenced by lag, so don't do it if there was lag
if(!plugin.skipCheck()) {
double sqrt = Math.sqrt(off);
data.reachVL += sqrt;
incrementStatistics(player, Id.FI_REACH, sqrt);
}
// Execute whatever actions are associated with this check and the
// violation level and find out if we should cancel the event
cancel = executeActions(player, cc.reachActions, data.reachVL);
if(cancel) {
// Needed to calculate penalty times
// if we should cancel, remember the current time too
data.reachLastViolationTime = time;
}
}
// If the player is still in penalty time, cancel the event anyway
if(data.reachLastViolationTime + cc.reachPenaltyTime > time) {
// A safeguard to avoid people getting stuck in penalty time
// indefinitely in case the system time of the server gets changed
if(data.reachLastViolationTime > time) {
System.out.println("Nocheat noted that your time ran backwards for " + (data.reachLastViolationTime - time) + " ms");
// Security check for server time changed situations
data.reachLastViolationTime = 0;
}
// He is in penalty time, therefore request cancelling of the event
return true;
}
@ -79,10 +86,11 @@ public class ReachCheck extends FightCheck {
return cc.reachCheck;
}
@Override
public String getParameter(ParameterName wildcard, NoCheatPlayer player) {
if(wildcard == ParameterName.VIOLATIONS)
return String.format(Locale.US, "%d", (int) getData(player.getDataStore()).reachVL);
return String.format(Locale.US, "%d", (int) getData(player).reachVL);
else
return super.getParameter(wildcard, player);
}

View File

@ -7,6 +7,11 @@ import cc.co.evenprime.bukkit.nocheat.actions.ParameterName;
import cc.co.evenprime.bukkit.nocheat.config.Permissions;
import cc.co.evenprime.bukkit.nocheat.data.Statistics.Id;
/**
* The speed check will find out if a player interacts with something that's
* too far away
*
*/
public class SpeedCheck extends FightCheck {
public SpeedCheck(NoCheat plugin) {
@ -19,20 +24,26 @@ public class SpeedCheck extends FightCheck {
final long time = System.currentTimeMillis();
if(data.speedTime + 1000 <= time) {
// Check if one second has passed and reset counters and vl in that case
if(data.speedTime + 1000L <= time) {
data.speedTime = time;
data.speedAttackCount = 0;
data.speedVL = 0;
}
// count the attack
data.speedAttackCount++;
// too many attacks
if(data.speedAttackCount > cc.speedAttackLimit) {
// if there was lag, don't count it towards statistics and vl
if(!plugin.skipCheck()) {
data.speedVL += 1;
incrementStatistics(player, Id.FI_SPEED, 1);
}
// Execute whatever actions are associated with this check and the
// violation level and find out if we should cancel the event
cancel = executeActions(player, cc.speedActions, data.speedVL);
}
@ -44,10 +55,11 @@ public class SpeedCheck extends FightCheck {
return cc.speedCheck;
}
@Override
public String getParameter(ParameterName wildcard, NoCheatPlayer player) {
if(wildcard == ParameterName.VIOLATIONS)
return String.format(Locale.US, "%d", (int) getData(player.getDataStore()).speedVL);
return String.format(Locale.US, "%d", (int) getData(player).speedVL);
else if(wildcard == ParameterName.LIMIT)
return String.format(Locale.US, "%d", (int) getConfig(player.getConfigurationStore()).speedAttackLimit);
else