From e706558cc18d7ee34eafc4e01b044919ca1e7234 Mon Sep 17 00:00:00 2001 From: NeatMonster Date: Sun, 5 Aug 2012 18:12:51 +0200 Subject: [PATCH] [Development] Fight checks rewritten, now they need testing and improvements. --- .../nocheatplus/actions/ParameterName.java | 1 + .../nocheatplus/checks/fight/FightConfig.java | 8 + .../nocheatplus/checks/fight/FightData.java | 7 + .../checks/fight/FightListener.java | 181 ++++++++++++++++++ .../nocheatplus/checks/fight/Speed.java | 104 ++++++++++ .../nocheatplus/config/ConfPaths.java | 5 + .../nocheatplus/config/DefaultConfig.java | 5 + .../nocheatplus/players/Permissions.java | 1 + 8 files changed, 312 insertions(+) create mode 100644 src/fr/neatmonster/nocheatplus/checks/fight/Speed.java diff --git a/src/fr/neatmonster/nocheatplus/actions/ParameterName.java b/src/fr/neatmonster/nocheatplus/actions/ParameterName.java index 16587dd9..2b17862c 100644 --- a/src/fr/neatmonster/nocheatplus/actions/ParameterName.java +++ b/src/fr/neatmonster/nocheatplus/actions/ParameterName.java @@ -21,6 +21,7 @@ package fr.neatmonster.nocheatplus.actions; * Some wildcards that are used in commands and log messages. */ public enum ParameterName { + ATTACKS_LIMIT("attackslimit"), CHECK("check"), DISTANCE("distance"), FALLDISTANCE("falldistance"), diff --git a/src/fr/neatmonster/nocheatplus/checks/fight/FightConfig.java b/src/fr/neatmonster/nocheatplus/checks/fight/FightConfig.java index 9b5a8fe9..51344a0a 100644 --- a/src/fr/neatmonster/nocheatplus/checks/fight/FightConfig.java +++ b/src/fr/neatmonster/nocheatplus/checks/fight/FightConfig.java @@ -81,6 +81,10 @@ public class FightConfig { public final long reachPenalty; public final ActionList reachActions; + public final boolean speedCheck; + public final int speedLimit; + public final ActionList speedActions; + /** * Instantiates a new fight configuration. * @@ -117,5 +121,9 @@ public class FightConfig { reachCheck = data.getBoolean(ConfPaths.FIGHT_REACH_CHECK); reachPenalty = data.getLong(ConfPaths.FIGHT_REACH_PENALTY); reachActions = data.getActionList(ConfPaths.FIGHT_REACH_ACTIONS, Permissions.FIGHT_REACH); + + speedCheck = data.getBoolean(ConfPaths.FIGHT_SPEED_CHECK); + speedLimit = data.getInt(ConfPaths.FIGHT_SPEED_LIMIT); + speedActions = data.getActionList(ConfPaths.FIGHT_SPEED_ACTIONS, Permissions.FIGHT_SPEED); } } diff --git a/src/fr/neatmonster/nocheatplus/checks/fight/FightData.java b/src/fr/neatmonster/nocheatplus/checks/fight/FightData.java index dde10187..19281b18 100644 --- a/src/fr/neatmonster/nocheatplus/checks/fight/FightData.java +++ b/src/fr/neatmonster/nocheatplus/checks/fight/FightData.java @@ -47,6 +47,9 @@ public class FightData { public double knockbackVL; public double noSwingVL; public double reachVL; + public double speedVL; + + public boolean skipNext; // Data of the angle check. public TreeMap angleHits = new TreeMap(); @@ -72,4 +75,8 @@ public class FightData { // Data of the reach check. public long reachLastViolationTime; + // Data of the speed check. + public int speedAttacks; + public long speedTime; + } diff --git a/src/fr/neatmonster/nocheatplus/checks/fight/FightListener.java b/src/fr/neatmonster/nocheatplus/checks/fight/FightListener.java index 1b05ad8b..433da23d 100644 --- a/src/fr/neatmonster/nocheatplus/checks/fight/FightListener.java +++ b/src/fr/neatmonster/nocheatplus/checks/fight/FightListener.java @@ -1,6 +1,20 @@ package fr.neatmonster.nocheatplus.checks.fight; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; +import org.bukkit.event.player.PlayerAnimationEvent; +import org.bukkit.event.player.PlayerToggleSprintEvent; + +import fr.neatmonster.nocheatplus.checks.moving.MovingConfig; /* * MM""""""""`M oo dP dP M""MMMMMMMM oo dP @@ -16,5 +30,172 @@ import org.bukkit.event.Listener; * Central location to listen to events that are relevant for the fight checks. */ public class FightListener implements Listener { + private final Angle angle = new Angle(); + private final Critical critical = new Critical(); + private final Direction direction = new Direction(); + private final GodMode godMode = new GodMode(); + private final InstantHeal instantHeal = new InstantHeal(); + private final Knockback knockback = new Knockback(); + private final NoSwing noSwing = new NoSwing(); + private final Reach reach = new Reach(); + private final Speed speed = new Speed(); + /** + * There is an unofficial agreement that if a plugin wants an attack to not get checked by NoCheatPlus, 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 event + */ + private void handleCustomDamage(final EntityDamageByEntityEvent event) { + if (event.getDamager() instanceof Player) + // Skip the next damage event, because it is with high probability something from the Heroes plugin. + FightData.getData((Player) event.getDamager()).skipNext = true; + } + + /** + * A player attacked something with DamageCause ENTITY_ATTACK. That's most likely what we want to really check. + * + * @param event + * The EntityDamageByEntityEvent + */ + private void handleNormalDamage(final EntityDamageByEntityEvent event) { + final Player player = (Player) event.getDamager(); + FightConfig.getConfig(player); + final FightData data = FightData.getData(player); + + // For some reason we decided to skip this event anyway. + if (data.skipNext) { + data.skipNext = false; + return; + } + + boolean cancelled = false; + + // Get the attacked entity. + final net.minecraft.server.Entity damaged = ((CraftEntity) event.getEntity()).getHandle(); + + // Run through the main checks. + if (angle.isEnabled(player) && angle.check(player)) + cancelled = true; + + if (!cancelled && critical.isEnabled(player) && critical.check(player)) + cancelled = true; + + if (!cancelled && direction.isEnabled(player) && direction.check(player, damaged)) + cancelled = true; + + if (!cancelled && knockback.isEnabled(player) && knockback.check(player)) + cancelled = true; + + if (!cancelled && noSwing.isEnabled(player) && noSwing.check(player)) + cancelled = true; + + if (!cancelled && reach.isEnabled(player) && reach.check(player, damaged)) + cancelled = true; + + if (!cancelled && speed.isEnabled(player) && speed.check(player)) + cancelled = true; + + if (!cancelled && !MovingConfig.getConfig(player).survivalFlyAllowFastBlocking && player.isBlocking()) + cancelled = true; + + // One of the checks requested the event to be cancelled, so do it. + if (cancelled) + event.setCancelled(cancelled); + } + + /** + * We listen to EntityDamage events for obvious reasons. + * + * @param event + * the event + */ + @EventHandler( + ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityDamage(final EntityDamageEvent event) { + // Filter some unwanted events right now. + if (event instanceof EntityDamageByEntityEvent) { + final EntityDamageByEntityEvent e = (EntityDamageByEntityEvent) event; + if (e.getDamager() instanceof Player) + if (e.getCause() == DamageCause.ENTITY_ATTACK) + handleNormalDamage(e); + else if (e.getCause() == DamageCause.CUSTOM) + handleCustomDamage(e); + } + } + + /** + * We listen to EntityDamage events (again) for obvious reasons. + * + * @param event + * the event + */ + @EventHandler( + ignoreCancelled = true, priority = EventPriority.LOW) + public void onEntityDamage_(final EntityDamageEvent event) { + // Filter unwanted events right here. + if (event.getEntity() instanceof Player && !event.getEntity().isDead()) { + final Player player = (Player) event.getEntity(); + if (godMode.isEnabled(player) && godMode.check(player)) + // It requested to "cancel" the players invulnerability, so set his noDamageTicks to 0. + player.setNoDamageTicks(0); + } + } + + /** + * We listen to death events to prevent a very specific method of doing godmode. + * + * @param event + * the event + */ + @EventHandler( + priority = EventPriority.MONITOR) + protected void onEntityDeathEvent(final EntityDeathEvent event) { + // Only interested in dying players. + if (event.getEntity() instanceof Player) + godMode.death((Player) event.getEntity()); + } + + /** + * We listen to EntityRegainHealth events of type "satiated" for InstantHeal check. + * + * @param event + * the event + */ + @EventHandler( + ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityRegainHealth(final EntityRegainHealthEvent event) { + if (event.getEntity() instanceof Player && event.getRegainReason() == RegainReason.SATIATED) { + final Player player = (Player) event.getEntity(); + if (instantHeal.isEnabled(player) && instantHeal.check(player)) + event.setCancelled(true); + } + } + + /** + * We listen to PlayerAnimation events because it is used for arm swinging. + * + * @param event + * the event + */ + @EventHandler( + priority = EventPriority.MONITOR) + protected void onPlayerAnimation(final PlayerAnimationEvent event) { + // Set a flag telling us that the arm has been swung. + FightData.getData(event.getPlayer()).noSwingArmSwung = true; + } + + /** + * We listen to the PlayerToggleSprint events for the Knockback check. + * + * @param event + * the event + */ + @EventHandler( + ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerToggleSprint(final PlayerToggleSprintEvent event) { + FightData.getData(event.getPlayer()).knockbackSprintTime = System.currentTimeMillis(); + } } diff --git a/src/fr/neatmonster/nocheatplus/checks/fight/Speed.java b/src/fr/neatmonster/nocheatplus/checks/fight/Speed.java new file mode 100644 index 00000000..d9cf7128 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/checks/fight/Speed.java @@ -0,0 +1,104 @@ +package fr.neatmonster.nocheatplus.checks.fight; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import fr.neatmonster.nocheatplus.actions.ParameterName; +import fr.neatmonster.nocheatplus.checks.Check; +import fr.neatmonster.nocheatplus.checks.CheckEvent; +import fr.neatmonster.nocheatplus.players.Permissions; +import fr.neatmonster.nocheatplus.utilities.LagMeasureTask; + +/* + * MP""""""`MM dP + * M mmmmm..M 88 + * M. `YM 88d888b. .d8888b. .d8888b. .d888b88 + * MMMMMMM. M 88' `88 88ooood8 88ooood8 88' `88 + * M. .MMM' M 88. .88 88. ... 88. ... 88. .88 + * Mb. .dM 88Y888P' `88888P' `88888P' `88888P8 + * MMMMMMMMMMM 88 + * dP + */ +/** + * The Speed check is used to detect players who are attacking entities too quickly. + */ +public class Speed extends Check { + + /** + * The event triggered by this check. + */ + public class SpeedEvent extends CheckEvent { + + /** + * Instantiates a new speed event. + * + * @param player + * the player + */ + public SpeedEvent(final Player player) { + super(player); + } + } + + /** + * Checks a player. + * + * @param player + * the player + * @return true, if successful + */ + public boolean check(final Player player) { + final FightConfig cc = FightConfig.getConfig(player); + final FightData data = FightData.getData(player); + + boolean cancel = false; + + // Has one second passed? Reset counters and violation level in that case. + if (data.speedTime + 1000L <= System.currentTimeMillis()) { + data.speedTime = System.currentTimeMillis(); + data.speedAttacks = 0; + data.speedVL = 0D; + } + + // Count the attack. + data.speedAttacks++; + + // Too many attacks? + if (data.speedAttacks > cc.speedLimit) { + // If there was lag, don't count it towards violation level. + if (!LagMeasureTask.skipCheck()) + data.speedVL += 1; + + // Dispatch a speed event (API). + final SpeedEvent e = new SpeedEvent(player); + Bukkit.getPluginManager().callEvent(e); + + // Execute whatever actions are associated with this check and the violation level and find out if we should + // cancel the event. + cancel = !e.isCancelled() && executeActions(player, cc.speedActions, data.speedVL); + } + + return cancel; + } + + /* (non-Javadoc) + * @see fr.neatmonster.nocheatplus.checks.Check#getParameter(fr.neatmonster.nocheatplus.actions.ParameterName, org.bukkit.entity.Player) + */ + @Override + public String getParameter(final ParameterName wildcard, final Player player) { + if (wildcard == ParameterName.VIOLATIONS) + return String.valueOf(Math.round(FightData.getData(player).speedVL)); + else if (wildcard == ParameterName.ATTACKS_LIMIT) + return String.valueOf(Math.round(FightConfig.getConfig(player).speedLimit)); + else + return super.getParameter(wildcard, player); + } + + /* (non-Javadoc) + * @see fr.neatmonster.nocheatplus.checks.Check#isEnabled(org.bukkit.entity.Player) + */ + @Override + protected boolean isEnabled(final Player player) { + return !player.hasPermission(Permissions.FIGHT_SPEED) && FightConfig.getConfig(player).speedCheck; + } +} diff --git a/src/fr/neatmonster/nocheatplus/config/ConfPaths.java b/src/fr/neatmonster/nocheatplus/config/ConfPaths.java index 43e22861..71a079cc 100644 --- a/src/fr/neatmonster/nocheatplus/config/ConfPaths.java +++ b/src/fr/neatmonster/nocheatplus/config/ConfPaths.java @@ -235,6 +235,11 @@ public abstract class ConfPaths { public static final String FIGHT_REACH_PENALTY = FIGHT_REACH + "penalty"; public static final String FIGHT_REACH_ACTIONS = FIGHT_REACH + "actions"; + private static final String FIGHT_SPEED = FIGHT + "speed."; + public static final String FIGHT_SPEED_CHECK = FIGHT_SPEED + "active"; + public static final String FIGHT_SPEED_LIMIT = FIGHT_SPEED + "limit"; + public static final String FIGHT_SPEED_ACTIONS = FIGHT_SPEED + "actions"; + /* * e e ,e, * d8b d8b e88 88e Y8b Y888P " 888 8e e88 888 diff --git a/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java b/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java index c9a50a60..16586394 100644 --- a/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java +++ b/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java @@ -208,6 +208,10 @@ public class DefaultConfig extends ConfigFile { set(ConfPaths.FIGHT_REACH_PENALTY, 500); set(ConfPaths.FIGHT_REACH_ACTIONS, "cancel vl>10 log:freach:2:5:if cancel"); + set(ConfPaths.FIGHT_SPEED_CHECK, true); + set(ConfPaths.FIGHT_SPEED_LIMIT, 15); + set(ConfPaths.FIGHT_SPEED_ACTIONS, "log:fspeed:0:5:if cancel"); + /* * e e ,e, * d8b d8b e88 88e Y8b Y888P " 888 8e e88 888 @@ -277,6 +281,7 @@ public class DefaultConfig extends ConfigFile { set(ConfPaths.STRINGS + ".flylong", start + "tried to move from [locationfrom] to [locationto] over a distance of [distance] block(s)" + end); set(ConfPaths.STRINGS + ".freach", start + "tried to attack entity out of reach" + end); + set(ConfPaths.STRINGS + ".fspeed", start + "tried to attack more than [attackslimit] times per second" + end); set(ConfPaths.STRINGS + ".godmode", start + "avoided taking damage or lagging" + end); set(ConfPaths.STRINGS + ".instantheal", start + "tried to regenerate health faster than normal" + end); set(ConfPaths.STRINGS + ".kick", "kick [player]"); diff --git a/src/fr/neatmonster/nocheatplus/players/Permissions.java b/src/fr/neatmonster/nocheatplus/players/Permissions.java index 510c93b0..ad76ff98 100644 --- a/src/fr/neatmonster/nocheatplus/players/Permissions.java +++ b/src/fr/neatmonster/nocheatplus/players/Permissions.java @@ -121,6 +121,7 @@ public class Permissions { public static final String FIGHT_KNOCKBACK = FIGHT + ".knockback"; public static final String FIGHT_NOSWING = FIGHT + ".noswing"; public static final String FIGHT_REACH = FIGHT + ".reach"; + public static final String FIGHT_SPEED = FIGHT + ".speed"; /* * e e ,e,