diff options
Diffstat (limited to 'EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak')
7 files changed, 555 insertions, 0 deletions
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java new file mode 100644 index 000000000..6e349c85c --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java @@ -0,0 +1,64 @@ +package com.earth2me.essentials.anticheat.checks.blockbreak; + +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.checks.Check; +import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore; +import com.earth2me.essentials.anticheat.data.DataStore; + + +/** + * Abstract base class for BlockBreakChecks. Provides some static convenience methods for retrieving data and config + * objects for players + * + */ +public abstract class BlockBreakCheck extends Check +{ + private static final String id = "blockbreak"; + + public BlockBreakCheck(NoCheat plugin, String name) + { + super(plugin, id, name); + } + + /** + * Get the "BlockBreakData" object that belongs to the player. Will ensure that such a object exists and if not, + * create one + * + * @param player + * @return + */ + public static BlockBreakData getData(NoCheatPlayer player) + { + DataStore base = player.getDataStore(); + BlockBreakData data = base.get(id); + if (data == null) + { + data = new BlockBreakData(); + base.set(id, data); + } + return data; + } + + /** + * Get the BlockBreakConfig object that belongs to the world that the player currently resides in. + * + * @param player + * @return + */ + public static BlockBreakConfig getConfig(NoCheatPlayer player) + { + return getConfig(player.getConfigurationStore()); + } + + public static BlockBreakConfig getConfig(ConfigurationCacheStore cache) + { + BlockBreakConfig config = cache.get(id); + if (config == null) + { + config = new BlockBreakConfig(cache.getConfiguration()); + cache.set(id, config); + } + return config; + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java new file mode 100644 index 000000000..2e56adb68 --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java @@ -0,0 +1,186 @@ +package com.earth2me.essentials.anticheat.checks.blockbreak; + +import java.util.LinkedList; +import java.util.List; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.player.PlayerAnimationEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import com.earth2me.essentials.anticheat.EventManager; +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore; +import com.earth2me.essentials.anticheat.config.Permissions; + + +/** + * Central location to listen to events that are relevant for the blockbreak checks + * + */ +public class BlockBreakCheckListener implements Listener, EventManager +{ + private final NoswingCheck noswingCheck; + private final ReachCheck reachCheck; + private final DirectionCheck directionCheck; + private final NoCheat plugin; + + public BlockBreakCheckListener(NoCheat plugin) + { + + noswingCheck = new NoswingCheck(plugin); + reachCheck = new ReachCheck(plugin); + directionCheck = new DirectionCheck(plugin); + + this.plugin = plugin; + } + + /** + * We listen to blockBreak events for obvious reasons + * + * @param event The blockbreak event + */ + @EventHandler(priority = EventPriority.LOWEST) + public void blockBreak(final BlockBreakEvent event) + { + + if (event.isCancelled()) + { + return; + } + + boolean cancelled = false; + + final NoCheatPlayer player = plugin.getPlayer(event.getPlayer()); + final BlockBreakConfig cc = BlockBreakCheck.getConfig(player); + final BlockBreakData data = BlockBreakCheck.getData(player); + + // Remember the location of the block that will be broken + data.brokenBlockLocation.set(event.getBlock()); + + // Only if the block got damaged directly before, do the check(s) + if (!data.brokenBlockLocation.equals(data.lastDamagedBlock)) + { + // Something caused a blockbreak event that's not from the player + // Don't check it at all + data.lastDamagedBlock.reset(); + return; + } + + // Now do the actual checks, if still needed. It's a good idea to make + // computationally cheap checks first, because it may save us from + // doing the computationally expensive checks. + + // First NoSwing: Did the arm of the player move before breaking this + // block? + if (cc.noswingCheck && !player.hasPermission(Permissions.BLOCKBREAK_NOSWING)) + { + cancelled = noswingCheck.check(player, data, cc); + } + + // Second Reach: Is the block really in reach distance + if (!cancelled && cc.reachCheck && !player.hasPermission(Permissions.BLOCKBREAK_REACH)) + { + cancelled = reachCheck.check(player, data, cc); + } + + // Third Direction: Did the player look at the block at all + if (!cancelled && cc.directionCheck && !player.hasPermission(Permissions.BLOCKBREAK_DIRECTION)) + { + cancelled = directionCheck.check(player, data, cc); + } + + // At least one check failed and demanded to cancel the event + if (cancelled) + { + event.setCancelled(cancelled); + } + } + + /** + * We listen to BlockDamage events to grab the information if it has been an "insta-break". That info may come in + * handy later. + * + * @param event The BlockDamage event + */ + @EventHandler(priority = EventPriority.MONITOR) + public void blockHit(final BlockDamageEvent event) + { + + if (event.isCancelled()) + { + return; + } + + NoCheatPlayer player = plugin.getPlayer(event.getPlayer()); + BlockBreakData data = BlockBreakCheck.getData(player); + + // Only interested in insta-break events here + if (event.getInstaBreak()) + { + // Remember this location. We handle insta-breaks slightly + // different in some of the blockbreak checks. + data.instaBrokenBlockLocation.set(event.getBlock()); + } + + } + + /** + * We listen to BlockInteract events to be (at least in many cases) able to distinguish between blockbreak events + * that were triggered by players actually digging and events that were artificially created by plugins. + * + * @param event + */ + @EventHandler(priority = EventPriority.MONITOR) + public void blockInteract(final PlayerInteractEvent event) + { + + if (event.getClickedBlock() == null) + { + return; + } + + NoCheatPlayer player = plugin.getPlayer(event.getPlayer()); + BlockBreakData data = BlockBreakCheck.getData(player); + // Remember this location. Only blockbreakevents for this specific + // block will be handled at all + data.lastDamagedBlock.set(event.getClickedBlock()); + } + + /** + * We listen to PlayerAnimationEvent because it is (currently) equivalent to "player swings arm" and we want to + * check if he did that between blockbreaks. + * + * @param event The PlayerAnimation Event + */ + @EventHandler(priority = EventPriority.MONITOR) + public void armSwing(final PlayerAnimationEvent event) + { + // Just set a flag to true when the arm was swung + BlockBreakCheck.getData(plugin.getPlayer(event.getPlayer())).armswung = true; + } + + public List<String> getActiveChecks(ConfigurationCacheStore cc) + { + LinkedList<String> s = new LinkedList<String>(); + + BlockBreakConfig bb = BlockBreakCheck.getConfig(cc); + + if (bb.directionCheck) + { + s.add("blockbreak.direction"); + } + if (bb.reachCheck) + { + s.add("blockbreak.reach"); + } + if (bb.noswingCheck) + { + s.add("blockbreak.noswing"); + } + + return s; + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java new file mode 100644 index 000000000..aed4a6a08 --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java @@ -0,0 +1,40 @@ +package com.earth2me.essentials.anticheat.checks.blockbreak; + +import com.earth2me.essentials.anticheat.ConfigItem; +import com.earth2me.essentials.anticheat.actions.types.ActionList; +import com.earth2me.essentials.anticheat.config.ConfPaths; +import com.earth2me.essentials.anticheat.config.NoCheatConfiguration; +import com.earth2me.essentials.anticheat.config.Permissions; + + +/** + * Configurations specific for the "BlockBreak" 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 BlockBreakConfig implements ConfigItem +{ + public final boolean reachCheck; + public final double reachDistance; + public final ActionList reachActions; + public final boolean directionCheck; + public final ActionList directionActions; + public final double directionPrecision; + public final long directionPenaltyTime; + public final boolean noswingCheck; + public final ActionList noswingActions; + + public BlockBreakConfig(NoCheatConfiguration data) + { + + reachCheck = data.getBoolean(ConfPaths.BLOCKBREAK_REACH_CHECK); + reachDistance = 535D / 100D; + reachActions = data.getActionList(ConfPaths.BLOCKBREAK_REACH_ACTIONS, Permissions.BLOCKBREAK_REACH); + directionCheck = data.getBoolean(ConfPaths.BLOCKBREAK_DIRECTION_CHECK); + directionPrecision = ((double)data.getInt(ConfPaths.BLOCKBREAK_DIRECTION_PRECISION)) / 100D; + directionPenaltyTime = data.getInt(ConfPaths.BLOCKBREAK_DIRECTION_PENALTYTIME); + directionActions = data.getActionList(ConfPaths.BLOCKBREAK_DIRECTION_ACTIONS, Permissions.BLOCKBREAK_DIRECTION); + noswingCheck = data.getBoolean(ConfPaths.BLOCKBREAK_NOSWING_CHECK); + noswingActions = data.getActionList(ConfPaths.BLOCKBREAK_NOSWING_ACTIONS, Permissions.BLOCKBREAK_NOSWING); + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java new file mode 100644 index 000000000..dcf39adfc --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java @@ -0,0 +1,29 @@ +package com.earth2me.essentials.anticheat.checks.blockbreak; + +import com.earth2me.essentials.anticheat.DataItem; +import com.earth2me.essentials.anticheat.data.SimpleLocation; + + +/** + * Player specific data for the blockbreak checks + * + */ +public class BlockBreakData implements DataItem +{ + // Keep track of violation levels for the three checks + public double reachVL = 0.0D; + public double directionVL = 0.0D; + public double noswingVL = 0.0D; + // Used for the penalty time feature of the direction check + public long directionLastViolationTime = 0; + // Have a nicer/simpler way to work with block locations instead of + // Bukkits own "Location" class + public final SimpleLocation instaBrokenBlockLocation = new SimpleLocation(); + public final SimpleLocation brokenBlockLocation = new SimpleLocation(); + public final SimpleLocation lastDamagedBlock = new SimpleLocation(); + // indicate if the player swung his arm since he got checked last time + public boolean armswung = true; + // For logging, remember the reachDistance that was calculated in the + // reach check + public double reachDistance; +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java new file mode 100644 index 000000000..d0c7b10f8 --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java @@ -0,0 +1,100 @@ +package com.earth2me.essentials.anticheat.checks.blockbreak; + +import java.util.Locale; +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.actions.ParameterName; +import com.earth2me.essentials.anticheat.checks.CheckUtil; +import com.earth2me.essentials.anticheat.data.SimpleLocation; +import com.earth2me.essentials.anticheat.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 BlockBreakCheck +{ + public DirectionCheck(NoCheat plugin) + { + super(plugin, "blockbreak.direction"); + } + + public boolean check(final NoCheatPlayer player, final BlockBreakData data, final BlockBreakConfig ccblockbreak) + { + + final SimpleLocation brokenBlock = data.brokenBlockLocation; + boolean cancel = false; + + // 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 + // block. If the line of sight is more too far off, "off" will be + // bigger than 0 + double off = CheckUtil.directionCheck(player, brokenBlock.x + 0.5D, brokenBlock.y + 0.5D, brokenBlock.z + 0.5D, 1D, 1D, ccblockbreak.directionPrecision); + + final long time = System.currentTimeMillis(); + + if (off < 0.1D) + { + // Player did likely nothing wrong + // reduce violation counter to reward him + data.directionVL *= 0.9D; + } + else + { + // Player failed the check + // Increment violation counter + if (data.instaBrokenBlockLocation.equals(brokenBlock)) + { + // Instabreak block failures are very common, so don't be as + // hard on people failing them + off /= 5; + } + + // Add to the overall violation level of the check and add to + // statistics + data.directionVL += off; + incrementStatistics(player, Id.BB_DIRECTION, off); + + // Execute whatever actions are associated with this check and the + // violation level and find out if we should cancel the event + cancel = executeActions(player, ccblockbreak.directionActions, data.directionVL); + + if (cancel) + { + // 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 + ccblockbreak.directionPenaltyTime > time) + { + // A saveguard 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; + } + + // He is in penalty time, therefore request cancelling of the event + return true; + } + + return cancel; + } + + @Override + public String getParameter(ParameterName wildcard, NoCheatPlayer player) + { + + if (wildcard == ParameterName.VIOLATIONS) + { + return String.format(Locale.US, "%d", (int)getData(player).directionVL); + } + else + { + return super.getParameter(wildcard, player); + } + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java new file mode 100644 index 000000000..af53c419f --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java @@ -0,0 +1,61 @@ +package com.earth2me.essentials.anticheat.checks.blockbreak; + +import java.util.Locale; +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.actions.ParameterName; +import com.earth2me.essentials.anticheat.data.Statistics.Id; + + +/** + * We require that the player moves his arm between blockbreaks, this is what gets checked here. + * + */ +public class NoswingCheck extends BlockBreakCheck +{ + public NoswingCheck(NoCheat plugin) + { + super(plugin, "blockbreak.noswing"); + } + + public boolean check(NoCheatPlayer player, BlockBreakData data, BlockBreakConfig cc) + { + + boolean cancel = false; + + // did he swing his arm before + if (data.armswung) + { + // "consume" the flag + data.armswung = false; + // reward with lowering of the violation level + data.noswingVL *= 0.90D; + } + else + { + // he failed, increase vl and statistics + data.noswingVL += 1; + incrementStatistics(player, Id.BB_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); + } + + return cancel; + } + + @Override + public String getParameter(ParameterName wildcard, NoCheatPlayer player) + { + + if (wildcard == ParameterName.VIOLATIONS) + { + return String.format(Locale.US, "%d", (int)getData(player).noswingVL); + } + else + { + return super.getParameter(wildcard, player); + } + } +} diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java new file mode 100644 index 000000000..b764eedcb --- /dev/null +++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java @@ -0,0 +1,75 @@ +package com.earth2me.essentials.anticheat.checks.blockbreak; + +import java.util.Locale; +import com.earth2me.essentials.anticheat.NoCheat; +import com.earth2me.essentials.anticheat.NoCheatPlayer; +import com.earth2me.essentials.anticheat.actions.ParameterName; +import com.earth2me.essentials.anticheat.checks.CheckUtil; +import com.earth2me.essentials.anticheat.data.SimpleLocation; +import com.earth2me.essentials.anticheat.data.Statistics.Id; + + +/** + * The reach check will find out if a player interacts with something that's too far away + * + */ +public class ReachCheck extends BlockBreakCheck +{ + public ReachCheck(NoCheat plugin) + { + super(plugin, "blockbreak.reach"); + } + + public boolean check(NoCheatPlayer player, BlockBreakData data, BlockBreakConfig cc) + { + + boolean cancel = false; + + final SimpleLocation brokenBlock = data.brokenBlockLocation; + + // Distance is calculated from eye location to center of targeted block + // If the player is further away from his target than allowed, the + // difference will be assigned to "distance" + final double distance = CheckUtil.reachCheck(player, brokenBlock.x + 0.5D, brokenBlock.y + 0.5D, brokenBlock.z + 0.5D, player.isCreative() ? cc.reachDistance + 2 : cc.reachDistance); + + if (distance <= 0D) + { + // Player passed the check, reward him + data.reachVL *= 0.9D; + } + else + { + // He failed, increment violation level and statistics + data.reachVL += distance; + incrementStatistics(player, Id.BB_REACH, distance); + + // Remember how much further than allowed he tried to reach for + // logging, if necessary + data.reachDistance = distance; + + // 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); + } + + return cancel; + } + + @Override + public String getParameter(ParameterName wildcard, NoCheatPlayer player) + { + + if (wildcard == ParameterName.VIOLATIONS) + { + return String.format(Locale.US, "%d", (int)getData(player).reachVL); + } + else if (wildcard == ParameterName.REACHDISTANCE) + { + return String.format(Locale.US, "%.2f", getData(player).reachDistance); + } + else + { + return super.getParameter(wildcard, player); + } + } +} |