summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--EssentialsAntiCheat/Instructions.txt939
-rw-r--r--EssentialsAntiCheat/README.txt9
-rw-r--r--EssentialsAntiCheat/pom.xml21
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/Colors.java42
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/ConfigItem.java6
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/DataItem.java11
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/EventManager.java17
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheat.java204
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatLogEvent.java78
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatPlayer.java36
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/Action.java32
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/ParameterName.java35
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionList.java86
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionWithParameters.java94
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ConsolecommandAction.java39
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/DummyAction.java26
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/LogAction.java76
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/SpecialAction.java22
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java159
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java372
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java55
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java64
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java186
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java40
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java29
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java100
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java61
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java75
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java99
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java97
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java37
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java25
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/DirectionCheck.java131
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/ReachCheck.java75
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java79
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java108
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java64
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java22
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java50
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java96
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java120
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheck.java69
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java291
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java58
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java40
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java151
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java94
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java67
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/ReachCheck.java113
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java81
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java71
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java72
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java78
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java63
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java196
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java40
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java25
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java170
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java132
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java93
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java376
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java71
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java69
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java151
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java303
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/command/CommandHandler.java163
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ActionFactory.java183
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfPaths.java142
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationCacheStore.java45
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationManager.java257
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/DefaultConfiguration.java154
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/LoggingConfig.java29
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/NoCheatConfiguration.java82
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/Permissions.java44
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/DataStore.java38
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/ExecutionHistory.java145
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PlayerManager.java80
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PreciseLocation.java51
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/SimpleLocation.java76
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/Statistics.java82
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/ActiveCheckPrinter.java66
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/LagMeasureTask.java99
-rw-r--r--EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/player/NoCheatPlayerImpl.java151
-rw-r--r--EssentialsAntiCheat/src/plugin.yml94
-rw-r--r--packager.xml1
-rw-r--r--pom.xml3
86 files changed, 8775 insertions, 1 deletions
diff --git a/EssentialsAntiCheat/Instructions.txt b/EssentialsAntiCheat/Instructions.txt
new file mode 100644
index 000000000..e7dd42630
--- /dev/null
+++ b/EssentialsAntiCheat/Instructions.txt
@@ -0,0 +1,939 @@
+
+################################################################################
+# #
+# Important files, config.yml or "clean up your stuff" #
+# #
+################################################################################
+
+ 1) The config file for NoCheat is called "config.yml" now.
+
+ 2) You can have different config files for different worlds. To achieve this,
+ copy the "config.yml" and rename the copy to "worldname_config.yml". Set-
+ tings in that file will now only affect the world with the name "worldname".
+ You may also delete all settings from that world-specific file that you
+ won't use. They'll be implicitly taken from the master "config.yml" file.
+
+ 3) If you have files named "config.txt", "default_actions.txt" or "actions.txt"
+ please delete them. They are no longer used by NoCheat and serve no purpose
+ anymore.
+
+ 4) Never change the amount of white-spaces in front of options in the config
+ file "config.yml". It will break the configuration.
+
+
+################################################################################
+# #
+# How "actions" work, an Overview #
+# #
+################################################################################
+
+ NoCheat allows to define in detail what should happen when a player fails a
+ check in form of "actions". There are 4 possible things that may be done.
+ (read on to learn in detail on how to define/modify actions):
+
+ cancel: The effects of the action "cancel" depend on the check that it is
+ used for. Usually it means to prevent something from happening,
+ e.g. stop an attack or prevent sending of a chat message.
+
+ log: Create and show/log a message. Log messages can be customized in
+ how often, when and where they are registered/shown.
+
+ cmd: Execute a command of Bukkit or another plugin as if it were typed
+ into the server console by an admin. Like logging, these can be
+ customized.
+
+ vl>X: Is meant to symbolize "violation level at least X". Used to define
+ actions that will be executed only if players reached a certain
+ violation level. Failing a check usually increases their "vl", not
+ failing checks reduces it over time. Violation levels mean different
+ things for different checks, e.g. they may describe moved distance
+ beyond the limit, number of attacks above the attack limit, sent
+ messages beyond the spam limit.
+
+
+################################################################################
+# #
+# How to customize your "actions" #
+# #
+################################################################################
+
+ 1) The "cancel" action is just the word "cancel". Read in the detailed option
+ description to find out what it does depending on the check that it is
+ assigned to.
+
+ 2) The "log" action is a string of the form "log:string:delay:repeat:target".
+
+ log: is simply used to let NoCheat know it is a log action. Don't remove
+ it from the action, or NoCheat will not know what it is and how to
+ handle it.
+
+ string: is the message that will be logged. Because there is so little
+ space here, you only give a name here and define the actual
+ log message in the "strings" section of the config file.
+
+ delay: a number declaring how many times that action initially has to be
+ executed before it really leads to logging a message. Use this for
+ situations where it's common to have false positives in checks and
+ you only want the log message to be shown if a player fails the
+ check multiple times within a minute.
+
+ repeat: a number declaring how many seconds have to pass after logging the
+ message before it will be logged again for that player. This is
+ needed to prevent "log-spam". Usually a value of 5 seconds is
+ acceptable, for rare events you can use lower values. It is very
+ recommended to at least use the value 1 (one second) here.
+
+ target: where should the message be logged to? You can use three letters
+ here. The order that you use is not important.
+ "c" means logging to console
+ "i" means logging to ingame chat and
+ "f" means logging to the log file.
+
+ 3) The "cmd" action is a string of the form "cmd:string:delay:repeat".
+
+ cmd: is simply used to let NoCheat know it is a command action. Don't
+ remove it from the action, or NoCheat will not know what it is and
+ how to handle it.
+
+ string: is the command that will be issued. Because there is so little space
+ here, you only give a name here and define the actual command in the
+ "strings" section of the config file.
+
+ delay: a number declaring how many times that action initially has to be
+ executed before it really leads to running the command in the
+ console. Use this to create e.g. a 3-strikes-law by setting it to 3.
+ Only if a player fails the check 3 times within 1 minute, the
+ command will be really run.
+
+ repeat: a number declaring how many seconds have to pass after running the
+ command before it can be run again for that player. Because many
+ commands are expensive (take time, resources), you may want to limit
+ how often they can be called.
+
+ 4) The "vl>" isn't really an action. It limits all actions that are written
+ afterwards to be only executed if the players violation level has reached
+ at least the given value. This allows to define layers of actions and
+ handle repeated or severe failing of checks different. For example the spam
+ check will only kick players if they reach a certain violation level (vl).
+
+
+################################################################################
+# #
+# Permissions #
+# #
+################################################################################
+
+
+ NoCheat only supports "SuperPerms", CraftBukkits official permission framework.
+ You'll need to use a permissions plugin that supports "SuperPerms" to use it
+ with NoCheat. Here are some I know of:
+
+ - bPermissions
+ - PermissionsEx
+ - Essentials GroupManager
+
+ I personally recommend bPermissions, but any of them will do just fine.
+
+ By default all these permissions are set to "op", which means players with
+ OP-status have all permissions, unless you change it.
+
+--------------------------------------------------------------------------------
+--------------------------- Permissions for CHECKS -----------------------------
+--------------------------------------------------------------------------------
+
+ These permission nodes are grouped the same way as the options in the config
+ file, based on the event type they belong to. The logic is, that a player
+ having one of these nodes means he will NOT be checked. Players without the
+ permission node will be checked.
+
+ Example: A player has permission "nocheat.checks.moving.morepackets". That
+ means he is allowed to use that hack/cheat because NoCheat won't check/stop it.
+
+
+------------------------ MOVING Permissions for CHECKS -------------------------
+
+ - nocheat.checks.moving.runfly
+ Allows the player to move freely. It also treats the player as if he has
+ the ".flying", ".swimming", ".sneaking" and ".nofall" permission too.
+
+ - nocheat.checks.moving.flying
+ Allows the player to fly, independent of if he is in "creative mode" or not.
+ He will be limited in speed by the config settings "flyingspeedvertical"
+ and "flyingspeedhorizontal". It also treats the player as if he has the
+ ".nofall" permission too.
+
+ - nocheat.checks.moving.swimming
+ Allows the player to swim as fast as he is allowed to walk. Normally a
+ player swims slower than he walks and NoCheat prevents faster movement in
+ water.
+
+ - nocheat.checks.moving.sneaking
+ Allows the player to sneak faster than he is allowed to walk. Normally a
+ player sneaks a lot slower than he walks and NoCheat prevents faster
+ movement while sneaking.
+
+ - nocheat.checks.moving.nofall
+ Allows the player to avoid fall damage by using hacks. Normally NoCheat
+ will keep track of a players movement and try to rectify the fall-damage
+ calculations of Minecraft in case they seem to be wrong because of players
+ tricking the server.
+
+ - nocheat.checks.moving.morepackets
+ Allows players to make a lot more movements than normally possible. Doing
+ more movements will result in faster overall movement speed and causes the
+ server to spend a lot of additional time for processing these movements.
+
+
+-------------------- BLOCKBREAK Permissions for CHECKS -------------------------
+
+ - nocheat.checks.blockbreak.reach
+ Allows the player to break blocks that are further away than usual.
+
+ - nocheat.checks.blockbreak.direction
+ Don't force players to look at the blocks that they try to destroy.
+
+ - nocheat.checks.blockbreak.noswing
+ Don't force players to swing their arm when breaking blocks.
+
+
+-------------------- BLOCKPLACE Permissions for CHECKS -------------------------
+
+ - nocheat.checks.blockplace.reach
+ Allows the player to place blocks that are further away than usual.
+
+ - nocheat.checks.blockplace.direction
+ Don't force players to look at the blocks that they try to place.
+
+
+--------------------- INVENTORY Permissions for CHECKS -------------------------
+
+ - nocheat.checks.inventory.drop
+ Don't limit the number of items that a player may drop within a short time
+
+ - nocheat.checks.inventory.instantbow
+ Don't prevent players from shooting their bows instantly without taking the
+ usual time to pull the string back
+
+ - nocheat.checks.inventory.instanteat
+ Don't prevent players from eating their food instantly without taking the
+ usual time to munch on it
+
+
+----------------------- CHAT Permissions for CHECKS ----------------------------
+
+ - nocheat.checks.chat.spam
+ Don't limit the number of messages and commands that a player may send in a
+ short timeframe
+
+ - nocheat.checks.chat.color
+ Don't filter color codes from messages that get sent by players, allowing
+ them to use colors in their messages.
+
+
+---------------------- FIGHT Permissions for CHECKS ----------------------------
+
+ - nocheat.checks.fight.direction
+ Don't force players to look at their targets while fighting
+
+ - nocheat.checks.fight.noswing
+ Don't force players to move their arms while fighting
+
+ - nocheat.checks.fight.reach
+ Don't limit the distance for fights
+
+ - nocheat.checks.fight.speed
+ Don't limit the number of attacks that the player can do per second
+
+ - nocheat.checks.fight.godmode
+ Don't prevent the player from keeping the temporary invulnerability that he
+ gets when taking damage
+
+ - nocheat.checks.fight.instantheal
+ Don't prevent the player from accellerating their health generation by
+ food saturation
+
+
+--------------------------------------------------------------------------------
+----------------------- Permissions for ADMINISTRATION -------------------------
+--------------------------------------------------------------------------------
+
+ - nocheat.admin.chatlog
+ The player will receive log messages that are directed at the "ingame chat"
+ as a normal chat message ingame.
+
+ - nocheat.admin.commands
+ The player gets access to some of the "/nocheat" commands
+
+ - nocheat.admin.reload
+ In combination with "nocheat.admin.commands", the player gets access to the
+ "/nocheat reload" command, which will cause NoCheat to reread its config
+ files.
+
+
+--------------------------------------------------------------------------------
+---------------------- Things to know about Permissions ------------------------
+--------------------------------------------------------------------------------
+
+ NoCheat defines "parent" nodes for all permissions already for you. That means
+ you can use one of the following:
+
+ - nocheat
+ - nocheat.admin
+ - nocheat.checks
+ - nocheat.checks.moving
+ - nocheat.checks.blockbreak
+ - nocheat.checks.blockplace
+ - nocheat.checks.inventory
+ - nocheat.checks.chat
+ - nocheat.checks.fight
+
+ To give a player all the permissions that start with that permission node.
+
+ Especially you don't have to and should not use ".*" anywhere when defining
+ NoCheat permissions.
+
+
+ You can exclude a specific player from getting logged by appending ".silent"
+ to the relevant permission node of the specific check. E.g.
+
+ - nocheat.checks.moving.nofall.silent
+
+ will prevent NoCheat from recording log messages for that player for the
+ "nofall" check, while still executing all other actions as usual. These silent
+ permissions won't show up elsewhere, e.g. when using the "nocheat permlist"
+ command.
+
+
+################################################################################
+# #
+# All available config settings #
+# #
+################################################################################
+
+ Here you'll find the whole list of settings that you can manipulate in the
+ config.yml file. It is further split into logical sections
+
+
+--------------------------------------------------------------------------------
+-------------------------------- LOGGING Section -------------------------------
+--------------------------------------------------------------------------------
+
+ Everything that in general has to do with controlling NoCheats logging can be
+ found at this part of the config.yml
+
+ active:
+
+ Should messages get logged at all. If you are not interested in messages,
+ set this to false and you'll hear and see (almost) nothing of NoCheat.
+
+ prefix:
+
+ Will be placed in front of many log messages. To get colors, use "&"
+ followed by a number (0-9) or a letter (A-F). E.g. "&7NC&f:" would produce
+ the letters NC in red (&7), followed by black text (&f).
+
+ filename:
+
+ The name of the logfile that NoCheat will use to log its messages. The
+ default name is "nocheat.log", but you can use a different one if you want
+ to.
+
+ file:
+
+ Should the logfile be used at all. Set to false if you don't want to use
+ the logfile. By default the logfile will be used (true).
+
+ console:
+
+ Should the server console be used to display messages. Set to false if you
+ don't want NoCheat to show messages related to checks in the console. Error
+ messages may still get displayed there though.
+
+ ingamechat:
+
+ Should NoCheat display messages in the ingame chat? Set to false if you
+ don't want NoCheat to show messages ingame. The messages will only be seen
+ by players with the permission node "nocheat.admin.chatlog" or if you don't
+ use a permissions plugin, by players who are OP.
+
+ showactivechecks:
+
+ Should NoCheat display lists of checks that are enabled for each world. Set
+ to true if you are unsure that your (multiworld) setup of the config files
+ is done correctly.
+
+ debugmessages:
+
+ Should some additional messages be displayed in the server console, e.g.
+ about NoCheat encountering lag. The displayed messages may change from
+ version to version. This is deactivated by default.
+
+
+--------------------------------------------------------------------------------
+-------------------------------- CHECKS Section --------------------------------
+--------------------------------------------------------------------------------
+
+ Everything that in has to do with the various checks that NoCheat runs on the
+ players. Use these to specify what will be done, how it will be done and what
+ happens if somebody fails checks.
+
+
+----------------------------- INVENTORY Subsection -----------------------------
+
+ Checks that at least technically have to do with the inventory or usage of
+ items can be found here.
+
+ 1) DROP:
+
+ The "inventory.drop" check. It limits how many separate items a player can
+ drop onto the ground within a specific time. Dropping a lot of separate
+ items at once can cause lag on the server, therefore this check exists.
+
+ active:
+ Should the check be enabled. Set to false if you are not interested in
+ this at all
+
+ time:
+ Over how many seconds should dropped items be counted, before the
+ counter gets reset and starts at zero again.
+
+ limit:
+ How many items may be dropped in the timeframe that is specified by
+ the "time" setting. Please consider that dying causes a player to drop
+ up to 36 separate items (stacks). Therefore this value shouldn't be
+ set below ~50.
+
+ actions:
+ What should happen when a player goes beyond the set limit. Default
+ settings log a message and kick the player from the server. The VL of
+ the drop check symbolizes how many items a player dropped beyond the
+ set limit. If the limit is 100 and he tried to drop 130, he will have a
+ Violation Level of 130 - 100 = 30.
+
+ 2) INSTANTBOW:
+
+ Players may attack extremely fast and with a fully charged bow without
+ waiting for it to be fully pulled back. This is a significant advantage in
+ PvP and PvE combat.
+
+ active:
+ Should players be checked for this behavior. Set to false if you don't
+ care about players using bows faster than normally possible.
+
+ actions:
+ What should happen if the player fails this check. Default is to stop
+ the attack ("cancel" it) and log messages. The Violation Level (VL) for
+ this check the time difference between how long it took the player to
+ fire an arrow and how long NoCheat thinks he should have taken, in
+ 1/10 seconds. Therefore a VL of 10 would mean that the player shot an
+ arrow 1 second faster than NoCheat expected. The VL gets increased with
+ every failed check and slowly decreased for every passed check.
+
+ 3) INSTANTEAT:
+
+ Players may eat various kinds of food instantly instead of waiting the
+ usual time munching on the item.
+
+ active:
+ Should players be checked for this behavior. Set to false if you don't
+ care about players eating their food faster than normally possible.
+
+ actions:
+ What should happen if the player fails this check. Default is to stop
+ the eating ("cancel" it) and log messages. The Violation Level (VL) for
+ this check the time difference between how long it took the player to
+ eat his food and how long NoCheat thinks he should have taken, in
+ 1/10 seconds. Therefore a VL of 10 would mean that the player ate his
+ food 1 second faster than NoCheat expected. The VL gets increased with
+ every failed check and slowly decreased for every passed check.
+
+
+------------------------------ MOVING Subsection -------------------------------
+
+ Checks that at least technically have to do with the player moving around or
+ impacting the world with his movement can be found here.
+
+ 1) RUNFLY:
+
+ Players may move in illegal ways (flying, running too fast) or try to
+ trick the server into thinking that they are not falling/flying by
+ cleverly manipulating the data that they send to the server.
+
+ active:
+ Should players get checked for this type of movement related hacks at
+ all. If deactivated, player may freely move around on the server, fly
+ or run really fast.
+
+ walkspeed:
+ How fast should the player be allowed to walk. Default is "100",
+ meaning 100% of normal walking speed. You will not see this option in
+ your config.yml file, because normally you shouldn't have to change the
+ walking speed of players at all (NoCheat knows when players sprint, use
+ Swiftness potions etc and will already adapt the speed based on that
+ data).
+
+ sprintspeed:
+ How fast should the player be allowed to sprint. Default is "100",
+ meaning 100% of normal sprinting speed. You will not see this option in
+ your config.yml file, because normally you shouldn't have to change the
+ sprinting speed of players at all (NoCheat knows when players sprint,
+ use Swiftness potions etc and will already adapt the speed based on
+ that data).
+
+ sneakspeed:
+ How fast should the player be allowed to sneak. Default is "100",
+ meaning 100% of normal sneaking speed. You will not see this option in
+ your config.yml file, because normally you shouldn't have to change the
+ sneaking speed of players at all (NoCheat knows when players sprint,
+ use Swiftness potions etc and will already adapt the speed based on
+ that data).
+
+ swimspeed:
+ How fast should the player be allowed to swim. Default is "100",
+ meaning 100% of normal swimming speed. You will not see this option in
+ your config.yml file, because normally you shouldn't have to change the
+ swimming speed of players at all (NoCheat knows when players sprint,
+ use Swiftness potions etc and will already adapt the speed based on
+ that data).
+
+ allowfastsneaking:
+ Should sneaking players be allowed to move as fast as normal players.
+ Set this to true, if you use plugins that enable players to do that
+ (e.g. the "Heroes" plugin or other RPG plugins tend to do that)
+
+ actions:
+ What should happen when a player sneaks/swims/walks/runs faster than
+ normally allowed or is flying. Default is to log messages (depending on
+ how severe the cheating is) and teleport the player to the last known
+ legitimate location on ground that NoCheat can remember for that player
+ ("cancel" the movement)
+
+ checknofall:
+ Should players be checked for a common type of "nofall" hack, that
+ allows them to avoid taking damage when falling. If you don't care
+ about fall damage, you can deactivate this. It gets deactivated if a
+ player is allowed to fly (see some lines below), because it doesn't
+ make sense to allow flying and then hurt players when they land.
+
+ nofallaggressivemode:
+ Enable an improved version of nofall check, that will catch additional
+ types of "nofall" hacks and deal damage to players directly. This is
+ usually safe to activate. It will only work if the "checknofall" is
+ also set to "true".
+
+ nofallactions:
+ What should happen if a player is considered to be using a "nofall"
+ hack. Default reaction is to log a message and encourage Bukkit to deal
+ fall damage anyway ("cancel" the hack). The Violation Level is the
+ fall distance in blocks that the player tried to avoid. It gets
+ increased every time that the player fails the check, and decreased
+ over time if the player doesn't fail the check.
+
+ FLYING:
+ This is an entire subsection dedicated to the "moving.flying" check.
+ It will be used instead of the "runfly" check whenever a player has
+ the right to fly.
+
+ allowflyingalways:
+ Should all players be allowed to fly always.
+
+ allowflyingincreative:
+ Should players that are set to "creative mode" be allowed to fly. If
+ they are already allowed because of "allowflyingalways" to fly, this
+ setting gets ignored.
+
+ flyingspeedlimithorizontal:
+ How many 1/100 blocks may a player fly horizontal within one "step".
+ The official "creative mode" flying reaches speeds of about 0.6
+ blocks which means a value of 60 here.
+
+ flyingspeedlimitvertical:
+ How many 1/100 blocks may a player fly vertically up within one
+ "step". A value of 100 which means 1 block seems reasonable for most
+ cases.
+
+ flyingheightlimit:
+ What is the maximum height (in blocks) that a player may reach by
+ flying, relative to the max world height he is in. Some servers
+ experience lag when players fly very, very high. This value is how
+ far above the map height a player may fly.
+
+ actions:
+ What should happen if a player flies faster/higher than defined here?
+ Default is to log messages and to prevent the player from moving
+ ("cancel" his last movement). The Violation Level (VL) of this check
+ is the distance that the player went beyond what NoCheat allowed him.
+ The VL increases with every failed check and slowly decreases for
+ every passed check.
+
+ 2) MOREPACKETS:
+
+ The morepackets check is complementary to the "runfly" check. While the
+ "runfly" check(s) limit the distance a player can move per step, this
+ "morepackets" check limits the number of "steps" a player may take per
+ second. A normal value is 20 steps per second.
+
+ active:
+ Should players be checked for this kind of cheating. If you are not
+ interested in players that cheat that way, set this to false. It is a
+ good idea to have this active, because players that cheat by sending
+ more packets than normally allowed may lag the server (each of those
+ packets has to be processed, after all).
+
+ actions:
+ What should happen if a player is considered to be cheating by taking
+ more steps per second than normal. Default is to log messages and
+ teleport the player back to a location where he was ~1 second before
+ ("cancel" his movement). The Violation Level VL is the number of
+ packets that the player sent beyond the expected amount
+
+
+---------------------------- BLOCKBREAK Subsection -----------------------------
+
+ Checks that at least technically have to do with the player breaking blocks.
+
+ 1) REACH:
+
+ Players may slightly increase the distance at which they can break
+ blocks. This check will try to identify that by comparing player and
+ block location.
+
+ active:
+ Should players be checked for this behaviour.
+
+ actions:
+ What should happen if the player is considered to cheat this way. The
+ default is to prevent him from breaking the block ("cancel" breaking)
+ and on repeated offenses to log messages about it. The Violation Level
+ (VL) is the distance in Blocks between the reach distance that NoCheat
+ allowed and what the player actually tried to use. The VL increases
+ with every failed attempt to break a block out of reach, and decreases
+ with every successful attempt.
+
+ 2) DIRECTION:
+
+ Players may break blocks without really looking at them. This is often
+ combined with breaking a lot of blocks surrounding the player at the same
+ time.
+
+ active:
+ Should players get checked for this type of hack
+
+ precision:
+ How strict should NoCheat be when comparing the players line of view
+ with the broken block location. The value represents (roughly) the
+ amount of 1/100 blocks that the player is allowed to look past the to
+ be broken block. 50 (0.5 blocks) seems a good default value.
+
+ penaltytime:
+ If a player fails this check, how long should he be prevented from
+ breaking blocks afterwards, in milliseconds. This is intended to make
+ automated destruction of blocks harder. 0.3 seconds (value 300) is the
+ default. Set to 0, if you don't want to limit players at all after
+ failing this check.
+
+ actions:
+ What should happen if a player fails this check. Default is to prevent
+ the breaking of the block ("cancel" it) and after repeated/more severe
+ offenses to log a message. The Violation Level (VL) for this check is
+ the distance in Blocks between the line of view of the player and the
+ block. It increases with every failure and decreases with every
+ successful block break.
+
+ 3) NOSWING:
+
+ Players may break blocks without moving their arm. This is confusing for
+ nearby players, as they won't see who broke the blocks.
+
+ active:
+ Should players get checked for this type of hack
+
+ actions:
+ What should happen if the player didn't swing his arm first? Default is
+ to log a message and prevent the breaking of the block ("cancel" it).
+ The Violation Level (VL) is the number of block-break attempts without
+ first swinging the arm. It increases with every failed attempt by 1 and
+ decreases with every successful attempt slowly.
+
+
+---------------------------- BLOCKPLACE Subsection -----------------------------
+
+ Checks that at least technically have to do with the player placing blocks.
+
+ 1) REACH:
+
+ Players may slightly increase the distance at which they can place
+ blocks. This check will try to identify that by comparing player and
+ block location.
+
+ active:
+ Should players be checked for this behaviour.
+
+ actions:
+ What should happen if the player is considered to cheat this way. The
+ default is to prevent him from placing the block ("cancel" placing)
+ and on repeated offenses to log messages about it. The Violation Level
+ (VL) is the distance in Blocks between the reach distance that NoCheat
+ allowed and what the player actually tried to use. The VL increases
+ with every failed attempt to place a block out of reach, and decreases
+ with every successful attempt.
+
+ 2) DIRECTION:
+
+ Players may place blocks without really looking at them. This is often
+ combined with placing a lot of blocks in a certain shape.
+
+ active:
+ Should players get checked for this type of hack
+
+ precision:
+ How strict should NoCheat be when comparing the players line of view
+ with the placed block location. The value represents (roughly) the
+ amount of 1/100 blocks that the player is allowed to look past the to
+ be placed block. 75 (0.75 blocks) seems a good default value.
+
+ penaltytime:
+ If a player fails this check, how long should he be prevented from
+ placing blocks afterwards, in milliseconds. This is intended to make
+ automated placing of blocks harder. 0.1 second (value 100) is the
+ default. Set to 0, if you don't want to limit players at all after
+ failing this check.
+
+ actions:
+ What should happen if a player fails this check. Default is to prevent
+ the placing of the block ("cancel" it) and after repeated/more severe
+ offenses to log a message. The Violation Level (VL) for this check is
+ the distance in Blocks between the line of view of the player and the
+ block. It increases with every failure and decreases with every
+ successful block placement.
+
+
+------------------------------- CHAT Subsection --------------------------------
+
+ Checks that at least technically have to do with chat or commands.
+
+ 1) COLOR:
+
+ Players may use color-codes to send colored messages. This may be used
+ to fool other players into believing they are admins or similar.
+
+ active:
+ Should player messages get checked for the use of color codes.
+
+ actions:
+ What should be done if a player sends messages with color codes.
+ Default is to log a message and prevent ("cancel") the use of the
+ color codes, by filtering them from the message. The message itself
+ will still be transmitted. The Violation Level (VL) for this check is
+ the number of messages that contained color codes. It increases with
+ each color-code message by 1 and decreases slowly with colorless
+ messages.
+
+ 2) SPAM:
+
+ Players may send a ton of messages/commands in a short time to cause
+ lag or even crash a server.
+
+ active:
+ Should player messages get checked for sending of too many messages.
+
+ whitelist:
+ A " " (whitespace) separated list of words. Messages that start with
+ these sequences will not be counted. This is ideal to exempt commands
+ from getting filtered, by e.g. adding "/help" to the list.
+
+ timeframe:
+ For how many seconds should messages and commands be counted, before
+ the counters get reset and counting starts at zero again.
+
+ messagelimit:
+ How many "normal" chat messages may be sent within the timeframe. All
+ messages that don't start with "/" are considered "normal".
+
+ commandlimit:
+ How many commands may be issued within the timeframe. Some mods (e.g.
+ TooManyItems) send a command on every mouse-click, which may cause
+ problems if this is set too low. So choose wisely. Every message that
+ starts with "/" is considered a command, even if the command doesn't
+ exist.
+
+ actions:
+ What should happen if players send more messages/commands than declared
+ by the above limits? Default is to prevent the message/command from
+ being processed ("cancel" them) and for severe cases where players send
+ a lot of messages/commands, kick them. The Violation Level (VL) is the
+ number of messages/commands that were sent beyond the specified limits.
+ It gets increased for every message/command by 1 and reset to zero when
+ the "timeframe" has passed.
+
+
+------------------------------ FIGHT Subsection --------------------------------
+
+ Checks that at least technically have to do with direct combat.
+
+ 1) DIRECTION:
+
+ Players may attack other players and creatures without really looking at
+ them. This is often combined with automatically attacking every living
+ thing within reach ("kill-aura"). This check will check if the attacker
+ looks at his target.
+
+ active:
+ Should players get checked for this type of hack
+
+ precision:
+ How strict should NoCheat be when comparing the players line of view
+ with the his target's location. The value represents (roughly) the
+ amount of 1/100 blocks that the player is allowed to look past the to
+ be attacked entity. 75 (0.75 blocks) seems a good default value.
+
+ penaltytime:
+ If a player fails this check, how long should he be prevented from
+ attacking stuff afterwards, in milliseconds. This is intended to make
+ automated attacking of enemies harder. 0.5 second (value 500) is the
+ default. Set to 0, if you don't want to limit players at all after
+ failing this check.
+
+ actions:
+ What should happen if a player fails this check. Default is to prevent
+ the attack from happening ("cancel" it) and after repeated/more severe
+ offenses to log a message. The Violation Level (VL) for this check is
+ the distance in Blocks between the line of view of the player and the
+ target. It increases with every failure and decreases with every
+ successful attack.
+
+ 2) NOSWING:
+
+ Players may attack entities without moving their arm. This is confusing
+ for nearby players, as they won't see who is attacking them or the nearby
+ creatures.
+
+ active:
+ Should players get checked for this type of hack
+
+ actions:
+ What should happen if the player didn't swing his arm first? Default is
+ to log a message and prevent the attack from happening ("cancel" it).
+ The Violation Level (VL) is the number of attacking attempts without
+ first swinging the arm. It increases with every failed attempt by 1 and
+ decreases with every successful attempt slowly.
+
+ 3) REACH:
+
+ Players may slightly increase the distance at which they can attack enemy
+ creatures/players. This check will try to identify that by comparing
+ player and target location.
+
+ active:
+ Should players be checked for this behaviour.
+
+ distance:
+ How far can the enemy be away from the attacker, in 1/100 Blocks. The
+ default value of 400, which is 4.00 blocks seems to work fine most of
+ the time. Increase if you get to many false positives to e.g. 425 or
+ 450.
+
+ penaltytime:
+ If a player fails this check, how long should he be prevented from
+ attacking stuff afterwards, in milliseconds. This is intended to make
+ automated attacking of enemies harder. 0.5 second (value 500) is the
+ default. Set to 0, if you don't want to limit players at all after
+ failing this check.
+
+ actions:
+ What should happen if the player is considered to cheat this way. The
+ default is to prevent him from attacking the target ("cancel" attack)
+ and on repeated offenses to log messages about it. The Violation Level
+ (VL) is the distance in Blocks between the reach distance that NoCheat
+ allowed and what the player actually tried to use. The VL increases
+ with every failed attempt to attack enemies out of reach, and decreases
+ with every successful attempt.
+
+ 4) SPEED:
+
+ Players may be attacking extremely fast within a short time by using
+ automated clicking or hacks. This is an advantage in many situations.
+
+ active:
+ Should players be checked for this behavior.
+
+ attacklimit:
+ How many attacks may a player start within 1 second. Consider setting
+ this to a value that's close to how fast you believe players can click
+ their mouse. The default is 15 per second.
+
+ actions:
+ What should happen if the player fails this check. Default is to stop
+ the attack ("cancel" it) and log messages. The Violation Level (VL) is
+ the number of attacks beyond the set limit. For each failed check it
+ is increased by 1 and it gets decreased for every successful attack.
+
+ 5) GODMODE:
+
+ Players may trick Bukkit into not dealing them damage when they get
+ attacked. This will try to identify and correct that behavior.
+
+ active:
+ Should players be checked for this behavior.
+
+ actions:
+ What should happen if the player fails this check. Default is to make
+ him vulnerable to the attack ("cancel" his "godmode") and log messages.
+ The Violation Level (VL) for this check is the number of ticks that the
+ player seemingly tried to stay invulnerable. A second has 20 ticks.
+ Every time the player fails the check, the VL gets increased by the
+ amount of ticks (but at most 15 per failed check), and everytime the
+ player didn't avoid taking damage it gets reduced slowly.
+
+ 6) INSTANTHEAL:
+
+ Players may trick Bukkit into regenerating their health faster when they
+ are satiated (full food bar) than normally possible. This will try to
+ identify and correct that behaviour.
+
+ active:
+ Should players be checked for this behavior.
+
+ actions:
+ What should happen if the player fails this check. Default is to not
+ allow the health regeneration ("cancel" the regeneration) and log a
+ message. The Violation LEvel (VL) for this check is the number of
+ seconds that the player tried to skip while regenerating health. It
+ gets reduced whenever the player regenerates health while obeying the
+ normal regeneration times.
+
+
+--------------------------------------------------------------------------------
+------------------------------- STRINGS Section --------------------------------
+--------------------------------------------------------------------------------
+
+ This is the section that defines various strings for "log" or "cmd" actions.
+ Each has a name (the part in front of ":") and a definition (the part behind
+ the ":"). Whenever you use a "log" or "cmd" action in one of the "actions: "
+ options of this config file, the string will be taken from this section.
+ Arbitrary many additional strings may be defined here, or existing strings
+ may be changed.
+
+ Most messages/commands use place-holders in [ ], which will be replaced at
+ runtime with relevant information. Some of these may only be available in
+ certain circumstances, only "[player]" can be used everywhere, especially
+ in "cmd" actions.
+
+
+################################################################################
+# #
+# Other noteworthy stuff, DONATIONS #
+# #
+################################################################################
+
+
+- NoCheat isn't perfect and won't prevent all forms of cheating. It's a best
+ effort approach.
+
+- NoCheat may make mistakes. Don't see everything NoCheat says or does as
+ indisputable fact that somebody cheated. It's not possible to be 100% sure
+ if somebody is cheating or not, NoCheat will try to be right most of the
+ time.
+
+Thank you for reading this file. It took hours to write it, so it's nice that
+people actually take a look at it. ;)
diff --git a/EssentialsAntiCheat/README.txt b/EssentialsAntiCheat/README.txt
new file mode 100644
index 000000000..73ad6d1e9
--- /dev/null
+++ b/EssentialsAntiCheat/README.txt
@@ -0,0 +1,9 @@
+Copyright (c) 2012 Wilfried Pasquazzo (Evenprime)
+<wilfried.pasquazzo@gmail.com>
+
+# Dual-Licensed - you may freely choose between (or use both):
+#
+# 1) GPL v3 (see LICENSE_GPL3.txt)
+# 2) MIT (see LICENSE_MIT.txt)
+#
+# \ No newline at end of file
diff --git a/EssentialsAntiCheat/pom.xml b/EssentialsAntiCheat/pom.xml
new file mode 100644
index 000000000..785a25c7a
--- /dev/null
+++ b/EssentialsAntiCheat/pom.xml
@@ -0,0 +1,21 @@
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.essentials3</groupId>
+ <artifactId>BuildAll</artifactId>
+ <version>3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>EssentialsAntiCheat</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.bukkit</groupId>
+ <artifactId>craftbukkit</artifactId>
+ <version>1.2.3-R0.2-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/Colors.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/Colors.java
new file mode 100644
index 000000000..053b177e5
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/Colors.java
@@ -0,0 +1,42 @@
+package com.earth2me.essentials.anticheat;
+
+import org.bukkit.ChatColor;
+
+
+/**
+ * Manages color codes in NoCheat
+ *
+ */
+public class Colors
+{
+ /**
+ * Replace instances of &X with a color
+ *
+ * @param text
+ * @return
+ */
+ public static String replaceColors(String text)
+ {
+ for (ChatColor c : ChatColor.values())
+ {
+ text = text.replace("&" + c.getChar(), c.toString());
+ }
+
+ return text;
+ }
+
+ /**
+ * Remove instances of &X
+ *
+ * @param text
+ * @return
+ */
+ public static String removeColors(String text)
+ {
+ for (ChatColor c : ChatColor.values())
+ {
+ text = text.replace("&" + c.getChar(), "");
+ }
+ return text;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/ConfigItem.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/ConfigItem.java
new file mode 100644
index 000000000..5fc2e3290
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/ConfigItem.java
@@ -0,0 +1,6 @@
+package com.earth2me.essentials.anticheat;
+
+
+public interface ConfigItem
+{
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/DataItem.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/DataItem.java
new file mode 100644
index 000000000..e99314108
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/DataItem.java
@@ -0,0 +1,11 @@
+package com.earth2me.essentials.anticheat;
+
+
+/**
+ *
+ * Every class that is extending this has to implement an empty Constructor()
+ *
+ */
+public interface DataItem
+{
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/EventManager.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/EventManager.java
new file mode 100644
index 000000000..8bfb5da68
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/EventManager.java
@@ -0,0 +1,17 @@
+package com.earth2me.essentials.anticheat;
+
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import java.util.List;
+import org.bukkit.event.Listener;
+
+
+public interface EventManager extends Listener
+{
+ /**
+ * Used for debug output, if checks are activated for the world-specific config that is given as a parameter
+ *
+ * @param cc The config
+ * @return A list of active/enabled checks
+ */
+ public List<String> getActiveChecks(ConfigurationCacheStore cc);
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheat.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheat.java
new file mode 100644
index 000000000..dd378bac5
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheat.java
@@ -0,0 +1,204 @@
+package com.earth2me.essentials.anticheat;
+
+import com.earth2me.essentials.anticheat.checks.WorkaroundsListener;
+import com.earth2me.essentials.anticheat.checks.blockbreak.BlockBreakCheckListener;
+import com.earth2me.essentials.anticheat.checks.blockplace.BlockPlaceCheckListener;
+import com.earth2me.essentials.anticheat.checks.chat.ChatCheckListener;
+import com.earth2me.essentials.anticheat.checks.fight.FightCheckListener;
+import com.earth2me.essentials.anticheat.checks.inventory.InventoryCheckListener;
+import com.earth2me.essentials.anticheat.checks.moving.MovingCheckListener;
+import com.earth2me.essentials.anticheat.command.CommandHandler;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.config.ConfigurationManager;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.PlayerManager;
+import com.earth2me.essentials.anticheat.debug.ActiveCheckPrinter;
+import com.earth2me.essentials.anticheat.debug.LagMeasureTask;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.plugin.java.JavaPlugin;
+
+
+/**
+ *
+ * NoCheat
+ *
+ * Check various player events for their plausibility and log/deny them/react to them based on configuration
+ */
+public class NoCheat extends JavaPlugin implements Listener
+{
+ private ConfigurationManager conf;
+ private CommandHandler commandHandler;
+ private PlayerManager players = new PlayerManager(this);
+ private List<EventManager> eventManagers = new ArrayList<EventManager>();
+ private LagMeasureTask lagMeasureTask;
+ private Logger fileLogger;
+
+ @Override
+ public void onDisable()
+ {
+ if (lagMeasureTask != null)
+ {
+ lagMeasureTask.cancel();
+ }
+
+ if (conf != null)
+ {
+ conf.cleanup();
+ }
+
+ // Just to be sure nothing gets left out
+ getServer().getScheduler().cancelTasks(this);
+ }
+
+ @Override
+ public void onEnable()
+ {
+ commandHandler = new CommandHandler(this);
+ conf = new ConfigurationManager(this, getDataFolder());
+ // Set up the event listeners
+ eventManagers.add(new MovingCheckListener(this));
+ eventManagers.add(new WorkaroundsListener());
+ eventManagers.add(new ChatCheckListener(this));
+ eventManagers.add(new BlockBreakCheckListener(this));
+ eventManagers.add(new BlockPlaceCheckListener(this));
+ eventManagers.add(new FightCheckListener(this));
+ eventManagers.add(new InventoryCheckListener(this));
+
+ // Then set up a task to monitor server lag
+ if (lagMeasureTask == null)
+ {
+ lagMeasureTask = new LagMeasureTask(this);
+ lagMeasureTask.start();
+ }
+
+ // Then print a list of active checks per world
+ ActiveCheckPrinter.printActiveChecks(this, eventManagers);
+
+ // register all listeners
+ for (EventManager eventManager : eventManagers)
+ {
+ Bukkit.getPluginManager().registerEvents(eventManager, this);
+ }
+
+ getServer().getPluginManager().registerEvents(this, this);
+ }
+
+ public ConfigurationCacheStore getConfig(Player player)
+ {
+ if (player != null)
+ {
+ return getConfig(player.getWorld());
+ }
+ else
+ {
+ return conf.getConfigurationCacheForWorld(null);
+ }
+ }
+
+ public ConfigurationCacheStore getConfig(World world)
+ {
+ if (world != null)
+ {
+ return conf.getConfigurationCacheForWorld(world.getName());
+ }
+ else
+ {
+ return conf.getConfigurationCacheForWorld(null);
+ }
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
+ {
+ boolean result = commandHandler.handleCommand(this, sender, command, label, args);
+
+ return result;
+ }
+
+ public boolean skipCheck()
+ {
+ if (lagMeasureTask != null)
+ {
+ return lagMeasureTask.skipCheck();
+ }
+ return false;
+ }
+
+ public void reloadConfiguration()
+ {
+ conf.cleanup();
+ this.conf = new ConfigurationManager(this, this.getDataFolder());
+ players.cleanDataMap();
+ }
+
+ /**
+ * Call this periodically to walk over the stored data map and remove old/unused entries
+ *
+ */
+ public void cleanDataMap()
+ {
+ players.cleanDataMap();
+ }
+
+ /**
+ * An interface method usable by other plugins to collect information about a player. It will include the plugin
+ * version, two timestamps (beginning and end of data collection for that player), and various data from checks)
+ *
+ * @param playerName a player name
+ * @return A newly created map of identifiers and corresponding values
+ */
+ public Map<String, Object> getPlayerData(String playerName)
+ {
+
+ Map<String, Object> map = players.getPlayerData(playerName);
+ map.put("nocheat.version", this.getDescription().getVersion());
+ return map;
+ }
+
+ public NoCheatPlayer getPlayer(Player player)
+ {
+ return players.getPlayer(player);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void logEvent(NoCheatLogEvent event)
+ {
+ if (event.toConsole())
+ {
+ // Console logs are not colored
+ getServer().getLogger().info(Colors.removeColors(event.getPrefix() + event.getMessage()));
+ }
+ if (event.toChat())
+ {
+ for (Player player : Bukkit.getServer().getOnlinePlayers())
+ {
+ if (player.hasPermission(Permissions.ADMIN_CHATLOG))
+ {
+ // Chat logs are potentially colored
+ player.sendMessage(Colors.replaceColors(event.getPrefix() + event.getMessage()));
+ }
+ }
+ }
+ if (event.toFile())
+ {
+ // File logs are not colored
+ fileLogger.info(Colors.removeColors(event.getMessage()));
+ }
+ }
+
+ public void setFileLogger(Logger logger)
+ {
+ this.fileLogger = logger;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatLogEvent.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatLogEvent.java
new file mode 100644
index 000000000..28193405c
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatLogEvent.java
@@ -0,0 +1,78 @@
+package com.earth2me.essentials.anticheat;
+
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+
+public class NoCheatLogEvent extends Event
+{
+ private static final HandlerList handlers = new HandlerList();
+ private String message;
+ private String prefix;
+ private boolean toConsole, toChat, toFile;
+
+ public NoCheatLogEvent(String prefix, String message, boolean toConsole, boolean toChat, boolean toFile)
+ {
+ this.prefix = prefix;
+ this.message = message;
+ this.toConsole = toConsole;
+ this.toChat = toChat;
+ this.toFile = toFile;
+ }
+
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ public void setPrefix(String prefix)
+ {
+ this.prefix = prefix;
+ }
+
+ public String getMessage()
+ {
+ return message;
+ }
+
+ public void setMessage(String message)
+ {
+ this.message = message;
+ }
+
+ public boolean toFile()
+ {
+ return toFile;
+ }
+
+ public void setToFile(boolean toFile)
+ {
+ this.toFile = toFile;
+ }
+
+ public boolean toChat()
+ {
+ return toChat;
+ }
+
+ public void setToChat(boolean toChat)
+ {
+ this.toChat = toChat;
+ }
+
+ public boolean toConsole()
+ {
+ return toConsole;
+ }
+
+ public void setToConsole(boolean toConsole)
+ {
+ this.toConsole = toConsole;
+ }
+
+ @Override
+ public HandlerList getHandlers()
+ {
+ return handlers;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatPlayer.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatPlayer.java
new file mode 100644
index 000000000..bb8ee8a7a
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatPlayer.java
@@ -0,0 +1,36 @@
+package com.earth2me.essentials.anticheat;
+
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+import com.earth2me.essentials.anticheat.data.ExecutionHistory;
+import org.bukkit.entity.Player;
+
+
+public interface NoCheatPlayer
+{
+ public boolean hasPermission(String permission);
+
+ public String getName();
+
+ public Player getPlayer();
+
+ public DataStore getDataStore();
+
+ public boolean isDead();
+
+ public boolean isSprinting();
+
+ public int getTicksLived();
+
+ public ConfigurationCacheStore getConfigurationStore();
+
+ public float getSpeedAmplifier();
+
+ public float getJumpAmplifier();
+
+ public boolean isCreative();
+
+ public ExecutionHistory getExecutionHistory();
+
+ public void dealFallDamage();
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/Action.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/Action.java
new file mode 100644
index 000000000..aa72472ff
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/Action.java
@@ -0,0 +1,32 @@
+package com.earth2me.essentials.anticheat.actions;
+
+
+/**
+ * An action gets executed as the result of a failed check. If it 'really' gets executed depends on how many executions
+ * have occurred within the last 60 seconds and how much time was between this and the previous execution
+ *
+ */
+public abstract class Action
+{
+ /**
+ * Delay in violations. An "ExecutionHistory" will use this info to make sure that there were at least "delay"
+ * attempts to execute this action before it really gets executed.
+ */
+ public final int delay;
+ /**
+ * Repeat only every "repeat" seconds. An "ExecutionHistory" will use this info to make sure that there were at
+ * least "repeat" seconds between the last execution of this action and this execution.
+ */
+ public final int repeat;
+ /**
+ * The name of the action, to identify it, e.g. in the config file
+ */
+ public final String name;
+
+ public Action(String name, int delay, int repeat)
+ {
+ this.name = name;
+ this.delay = delay;
+ this.repeat = repeat;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/ParameterName.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/ParameterName.java
new file mode 100644
index 000000000..6f2b2181e
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/ParameterName.java
@@ -0,0 +1,35 @@
+package com.earth2me.essentials.anticheat.actions;
+
+
+/**
+ * Some wildcards that are used in commands and log messages
+ */
+public enum ParameterName
+{
+ PLAYER("player"), LOCATION("location"), WORLD("world"),
+ VIOLATIONS("violations"), MOVEDISTANCE("movedistance"),
+ REACHDISTANCE("reachdistance"), FALLDISTANCE("falldistance"),
+ LOCATION_TO("locationto"), CHECK("check"), PACKETS("packets"),
+ TEXT("text"), PLACE_LOCATION("placelocation"),
+ PLACE_AGAINST("placeagainst"), BLOCK_TYPE("blocktype"), LIMIT("limit"),
+ FOOD("food"), SERVERS("servers");
+ private final String s;
+
+ private ParameterName(String s)
+ {
+ this.s = s;
+ }
+
+ public static ParameterName get(String s)
+ {
+ for (ParameterName c : ParameterName.values())
+ {
+ if (c.s.equals(s))
+ {
+ return c;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionList.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionList.java
new file mode 100644
index 000000000..25a7ba296
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionList.java
@@ -0,0 +1,86 @@
+package com.earth2me.essentials.anticheat.actions.types;
+
+import com.earth2me.essentials.anticheat.actions.Action;
+import java.util.*;
+
+
+/**
+ * A list of actions, that associates actions to tresholds. It allows to retrieve all actions that match a certain
+ * treshold
+ *
+ */
+public class ActionList
+{
+ // This is a very bad design decision, but it's also really
+ // convenient to define this here
+ public final String permissionSilent;
+
+ public ActionList(String permission)
+ {
+ this.permissionSilent = permission + ".silent";
+ }
+ // If there are no actions registered, we still return an Array. It's
+ // just empty/size=0
+ private final static Action[] emptyArray = new Action[0];
+ // The actions of this ActionList, "bundled" by treshold (violation level)
+ private final Map<Integer, Action[]> actions = new HashMap<Integer, Action[]>();
+ // The tresholds of this list
+ private final List<Integer> tresholds = new ArrayList<Integer>();
+
+ /**
+ * Add an entry to this actionList. The list will be sorted by tresholds automatically after the insertion.
+ *
+ * @param treshold The minimum violation level a player needs to have to be suspected to the given actions
+ * @param actions The actions that will be used if the player reached the accompanying treshold/violation level
+ */
+ public void setActions(Integer treshold, Action[] actions)
+ {
+
+ if (!this.tresholds.contains(treshold))
+ {
+ this.tresholds.add(treshold);
+ Collections.sort(this.tresholds);
+ }
+
+ this.actions.put(treshold, actions);
+ }
+
+ /**
+ * Get a list of actions that match the violation level. The only method that has to be called by a check
+ *
+ * @param violationLevel The violation level that should be matched.
+ * @return The array of actions whose treshold was closest to the violationLevel but not bigger
+ */
+ public Action[] getActions(double violationLevel)
+ {
+
+ Integer result = null;
+
+ for (Integer treshold : tresholds)
+ {
+ if (treshold <= violationLevel)
+ {
+ result = treshold;
+ }
+ }
+
+ if (result != null)
+ {
+ return actions.get(result);
+ }
+ else
+ {
+ return emptyArray;
+ }
+ }
+
+ /**
+ * Get a sorted list of the tresholds/violation levels that were used in this list
+ *
+ * @return The sorted list of tresholds
+ */
+ public List<Integer> getTresholds()
+ {
+ return tresholds;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionWithParameters.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionWithParameters.java
new file mode 100644
index 000000000..c07d20198
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionWithParameters.java
@@ -0,0 +1,94 @@
+package com.earth2me.essentials.anticheat.actions.types;
+
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.Action;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.checks.Check;
+import java.util.ArrayList;
+
+
+/**
+ * Action with parameters is used to
+ *
+ */
+public abstract class ActionWithParameters extends Action
+{
+ private final ArrayList<Object> messageParts;
+
+ public ActionWithParameters(String name, int delay, int repeat, String message)
+ {
+ super(name, delay, repeat);
+
+ messageParts = new ArrayList<Object>();
+
+ parseMessage(message);
+ }
+
+ private void parseMessage(String message)
+ {
+ String parts[] = message.split("\\[", 2);
+
+ // No opening braces left
+ if (parts.length != 2)
+ {
+ messageParts.add(message);
+ }
+ // Found an opening brace
+ else
+ {
+ String parts2[] = parts[1].split("\\]", 2);
+
+ // Found no matching closing brace
+ if (parts2.length != 2)
+ {
+ messageParts.add(message);
+ }
+ // Found a matching closing brace
+ else
+ {
+ ParameterName w = ParameterName.get(parts2[0]);
+
+ if (w != null)
+ {
+ // Found an existing wildcard inbetween the braces
+ messageParts.add(parts[0]);
+ messageParts.add(w);
+
+ // Go further down recursive
+ parseMessage(parts2[1]);
+ }
+ else
+ {
+ messageParts.add(message);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get a string with all the wildcards replaced with data from LogData
+ *
+ * @param data
+ * @return
+ */
+ protected String getMessage(NoCheatPlayer player, Check check)
+ {
+
+ StringBuilder log = new StringBuilder(100); // Should be big enough most
+ // of the time
+
+ for (Object part : messageParts)
+ {
+ if (part instanceof String)
+ {
+ log.append((String)part);
+ }
+ else
+ {
+ log.append(check.getParameter((ParameterName)part, player));
+ }
+ }
+
+ return log.toString();
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ConsolecommandAction.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ConsolecommandAction.java
new file mode 100644
index 000000000..5af889c16
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ConsolecommandAction.java
@@ -0,0 +1,39 @@
+package com.earth2me.essentials.anticheat.actions.types;
+
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.Check;
+
+
+/**
+ * Execute a command by imitating an admin typing the command directly into the console
+ *
+ */
+public class ConsolecommandAction extends ActionWithParameters
+{
+ public ConsolecommandAction(String name, int delay, int repeat, String command)
+ {
+ // Log messages may have color codes now
+ super(name, delay, repeat, command);
+ }
+
+ /**
+ * Fill in the placeholders ( stuff that looks like '[something]') with information, make a nice String out of it
+ * that can be directly used as a command in the console.
+ *
+ * @param player The player that is used to fill in missing data
+ * @param check The check that is used to fill in missing data
+ * @return The complete, ready to use, command
+ */
+ public String getCommand(NoCheatPlayer player, Check check)
+ {
+ return super.getMessage(player, check);
+ }
+
+ /**
+ * Convert the commands data into a string that can be used in the config files
+ */
+ public String toString()
+ {
+ return "cmd:" + name + ":" + delay + ":" + repeat;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/DummyAction.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/DummyAction.java
new file mode 100644
index 000000000..d89372144
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/DummyAction.java
@@ -0,0 +1,26 @@
+package com.earth2me.essentials.anticheat.actions.types;
+
+import com.earth2me.essentials.anticheat.actions.Action;
+
+
+/**
+ * If an action can't be parsed correctly, at least keep it stored in this form to not lose it when loading/storing the
+ * config file
+ *
+ */
+public class DummyAction extends Action
+{
+ // The original string used for this action definition
+ private final String def;
+
+ public DummyAction(String def)
+ {
+ super("dummyAction", 10000, 10000);
+ this.def = def;
+ }
+
+ public String toString()
+ {
+ return def;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/LogAction.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/LogAction.java
new file mode 100644
index 000000000..16830b8d7
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/LogAction.java
@@ -0,0 +1,76 @@
+package com.earth2me.essentials.anticheat.actions.types;
+
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.Check;
+
+
+/**
+ * Print a log message to various locations
+ *
+ */
+public class LogAction extends ActionWithParameters
+{
+ // Some flags to decide where the log message should show up, based on
+ // the config file
+ private final boolean toChat;
+ private final boolean toConsole;
+ private final boolean toFile;
+
+ public LogAction(String name, int delay, int repeat, boolean toChat, boolean toConsole, boolean toFile, String message)
+ {
+ super(name, delay, repeat, message);
+ this.toChat = toChat;
+ this.toConsole = toConsole;
+ this.toFile = toFile;
+ }
+
+ /**
+ * Parse the final log message out of various data from the player and check that triggered the action.
+ *
+ * @param player The player that is used as a source for the log message
+ * @param check The check that is used as a source for the log message
+ * @return
+ */
+ public String getLogMessage(NoCheatPlayer player, Check check)
+ {
+ return super.getMessage(player, check);
+ }
+
+ /**
+ * Should the message be shown in chat?
+ *
+ * @return true, if yes
+ */
+ public boolean toChat()
+ {
+ return toChat;
+ }
+
+ /**
+ * Should the message be shown in the console?
+ *
+ * @return true, if yes
+ */
+ public boolean toConsole()
+ {
+ return toConsole;
+ }
+
+ /**
+ * Should the message be written to the logfile?
+ *
+ * @return true, if yes
+ */
+ public boolean toFile()
+ {
+ return toFile;
+ }
+
+ /**
+ * Create the string that's used to define the action in the logfile
+ */
+ public String toString()
+ {
+ return "log:" + name + ":" + delay + ":" + repeat + ":" + (toConsole ? "c" : "") + (toChat ? "i" : "") + (toFile ? "f" : "");
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/SpecialAction.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/SpecialAction.java
new file mode 100644
index 000000000..5e4a93b3d
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/SpecialAction.java
@@ -0,0 +1,22 @@
+package com.earth2me.essentials.anticheat.actions.types;
+
+import com.earth2me.essentials.anticheat.actions.Action;
+
+
+/**
+ * Do something check-specific. Usually that is to cancel the event, undo something the player did, or do something the
+ * server should've done
+ *
+ */
+public class SpecialAction extends Action
+{
+ public SpecialAction()
+ {
+ super("cancel", 0, 0);
+ }
+
+ public String toString()
+ {
+ return "cancel";
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java
new file mode 100644
index 000000000..5482efa5a
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java
@@ -0,0 +1,159 @@
+package com.earth2me.essentials.anticheat.checks;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatLogEvent;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.Action;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.actions.types.*;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.command.CommandException;
+
+
+/**
+ * The abstract Check class, providing some basic functionality
+ *
+ */
+public abstract class Check
+{
+ private final String name;
+ // used to bundle information of multiple checks
+ private final String groupId;
+ protected final NoCheat plugin;
+
+ public Check(NoCheat plugin, String groupId, String name)
+ {
+ this.plugin = plugin;
+ this.groupId = groupId;
+ this.name = name;
+ }
+
+ /**
+ * Execute some actions for the specified player
+ *
+ * @param player
+ * @param actions
+ * @return
+ */
+ protected final boolean executeActions(NoCheatPlayer player, ActionList actionList, double violationLevel)
+ {
+
+ boolean special = false;
+
+ // Get the to be executed actions
+ Action[] actions = actionList.getActions(violationLevel);
+
+ final long time = System.currentTimeMillis() / 1000L;
+
+ // The configuration will be needed too
+ final ConfigurationCacheStore cc = player.getConfigurationStore();
+
+ for (Action ac : actions)
+ {
+ if (player.getExecutionHistory().executeAction(groupId, ac, time))
+ {
+ // The executionHistory said it really is time to execute the
+ // action, find out what it is and do what is needed
+ if (ac instanceof LogAction && !player.hasPermission(actionList.permissionSilent))
+ {
+ executeLogAction((LogAction)ac, this, player, cc);
+ }
+ else if (ac instanceof SpecialAction)
+ {
+ special = true;
+ }
+ else if (ac instanceof ConsolecommandAction)
+ {
+ executeConsoleCommand((ConsolecommandAction)ac, this, player, cc);
+ }
+ else if (ac instanceof DummyAction)
+ {
+ // nothing - it's a "DummyAction" after all
+ }
+ }
+ }
+
+ return special;
+ }
+
+ /**
+ * Collect information about the players violations
+ *
+ * @param player
+ * @param id
+ * @param vl
+ */
+ protected void incrementStatistics(NoCheatPlayer player, Id id, double vl)
+ {
+ player.getDataStore().getStatistics().increment(id, vl);
+ }
+
+ private final void executeLogAction(LogAction l, Check check, NoCheatPlayer player, ConfigurationCacheStore cc)
+ {
+
+ if (!cc.logging.active)
+ {
+ return;
+ }
+
+ // Fire one of our custom "Log" Events
+ Bukkit.getServer().getPluginManager().callEvent(new NoCheatLogEvent(cc.logging.prefix, l.getLogMessage(player, check), cc.logging.toConsole && l.toConsole(), cc.logging.toChat && l.toChat(), cc.logging.toFile && l.toFile()));
+ }
+
+ private final void executeConsoleCommand(ConsolecommandAction action, Check check, NoCheatPlayer player, ConfigurationCacheStore cc)
+ {
+ final String command = action.getCommand(player, check);
+
+ try
+ {
+ plugin.getServer().dispatchCommand(Bukkit.getConsoleSender(), command);
+ }
+ catch (CommandException e)
+ {
+ plugin.getLogger().warning("failed to execute the command '" + command + "': " + e.getMessage() + ", please check if everything is setup correct.");
+ }
+ catch (Exception e)
+ {
+ // I don't care in this case, your problem if your command fails
+ }
+ }
+
+ /**
+ * Replace a parameter for commands or log actions with an actual value. Individual checks should override this to
+ * get their own parameters handled too.
+ *
+ * @param wildcard
+ * @param player
+ * @return
+ */
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.PLAYER)
+ {
+ return player.getName();
+ }
+ else if (wildcard == ParameterName.CHECK)
+ {
+ return name;
+ }
+ else if (wildcard == ParameterName.LOCATION)
+ {
+ Location l = player.getPlayer().getLocation();
+ return String.format(Locale.US, "%.2f,%.2f,%.2f", l.getX(), l.getY(), l.getZ());
+ }
+ else if (wildcard == ParameterName.WORLD)
+ {
+ return player.getPlayer().getWorld().getName();
+ }
+ else
+ {
+ return "the Author was lazy and forgot to define " + wildcard + ".";
+ }
+
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java
new file mode 100644
index 000000000..390d2207d
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java
@@ -0,0 +1,372 @@
+package com.earth2me.essentials.anticheat.checks;
+
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import java.util.HashSet;
+import java.util.Set;
+import net.minecraft.server.Block;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.util.Vector;
+
+
+/**
+ * Some stuff that's used by different checks or just too complex to keep in other places
+ *
+ */
+public class CheckUtil
+{
+ /**
+ * Check if a player looks at a target of a specific size, with a specific precision value (roughly)
+ */
+ public static double directionCheck(final NoCheatPlayer player, final double targetX, final double targetY, final double targetZ, final double targetWidth, final double targetHeight, final double precision)
+ {
+
+ // Eye location of the player
+ final Location eyes = player.getPlayer().getEyeLocation();
+
+ final double factor = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) + Math.pow(eyes.getZ() - targetZ, 2));
+
+ // View direction of the player
+ final Vector direction = eyes.getDirection();
+
+ final double x = targetX - eyes.getX();
+ final double y = targetY - eyes.getY();
+ final double z = targetZ - eyes.getZ();
+
+ final double xPrediction = factor * direction.getX();
+ final double yPrediction = factor * direction.getY();
+ final double zPrediction = factor * direction.getZ();
+
+ double off = 0.0D;
+
+ off += Math.max(Math.abs(x - xPrediction) - (targetWidth / 2 + precision), 0.0D);
+ off += Math.max(Math.abs(z - zPrediction) - (targetWidth / 2 + precision), 0.0D);
+ off += Math.max(Math.abs(y - yPrediction) - (targetHeight / 2 + precision), 0.0D);
+
+ if (off > 1)
+ {
+ off = Math.sqrt(off);
+ }
+
+ return off;
+ }
+
+ /**
+ * Check if a player is close enough to a target, based on his eye location
+ *
+ * @param player
+ * @param targetX
+ * @param targetY
+ * @param targetZ
+ * @param limit
+ * @return
+ */
+ public static final double reachCheck(final NoCheatPlayer player, final double targetX, final double targetY, final double targetZ, final double limit)
+ {
+
+ final Location eyes = player.getPlayer().getEyeLocation();
+
+ final double distance = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) + Math.pow(eyes.getZ() - targetZ, 2));
+
+ return Math.max(distance - limit, 0.0D);
+ }
+ private final static double magic = 0.45D;
+ private final static double magic2 = 0.55D;
+ private static final int NONSOLID = 1; // 0x00000001
+ private static final int SOLID = 2; // 0x00000010
+ // All liquids are "nonsolid" too
+ private static final int LIQUID = 4 | NONSOLID; // 0x00000101
+ // All ladders are "nonsolid" and "solid" too
+ private static final int LADDER = 8 | NONSOLID | SOLID; // 0x00001011
+ // All fences are solid - fences are treated specially due
+ // to being 1.5 blocks high
+ private static final int FENCE = 16 | SOLID | NONSOLID; // 0x00010011
+ private static final int INGROUND = 128;
+ private static final int ONGROUND = 256;
+ // Until I can think of a better way to determine if a block is solid or
+ // not, this is what I'll do
+ private static final int types[];
+ private static final Set<Material> foods = new HashSet<Material>();
+
+ static
+ {
+ types = new int[256];
+
+ // Find and define properties of all other blocks
+ for (int i = 0; i < types.length; i++)
+ {
+
+ // Everything unknown is considered nonsolid and solid
+ types[i] = NONSOLID | SOLID;
+
+ if (Block.byId[i] != null)
+ {
+ if (Block.byId[i].material.isSolid())
+ {
+ // STONE, CAKE, LEAFS, ...
+ types[i] = SOLID;
+ }
+ else if (Block.byId[i].material.isLiquid())
+ {
+ // WATER, LAVA, ...
+ types[i] = LIQUID;
+ }
+ else
+ {
+ // AIR, SAPLINGS, ...
+ types[i] = NONSOLID;
+ }
+ }
+ }
+
+ // Some exceptions where the above method fails
+
+ // du'h
+ types[Material.AIR.getId()] = NONSOLID;
+
+ // Webs slow down a players fall extremely, so it makes
+ // sense to treat them as optionally solid
+ types[Material.WEB.getId()] = SOLID | NONSOLID;
+
+ // Obvious
+ types[Material.LADDER.getId()] = LADDER;
+ types[Material.WATER_LILY.getId()] = LADDER;
+ types[Material.VINE.getId()] = LADDER;
+
+ types[Material.FENCE.getId()] = FENCE;
+ types[Material.FENCE_GATE.getId()] = FENCE;
+ types[Material.NETHER_FENCE.getId()] = FENCE;
+
+ // These are sometimes solid, sometimes not
+ types[Material.IRON_FENCE.getId()] = SOLID | NONSOLID;
+ types[Material.THIN_GLASS.getId()] = SOLID | NONSOLID;
+
+ // Signs are NOT solid, despite the game claiming they are
+ types[Material.WALL_SIGN.getId()] = NONSOLID;
+ types[Material.SIGN_POST.getId()] = NONSOLID;
+
+ // (trap)doors can be solid or not
+ types[Material.WOODEN_DOOR.getId()] = SOLID | NONSOLID;
+ types[Material.IRON_DOOR_BLOCK.getId()] = SOLID | NONSOLID;
+ types[Material.TRAP_DOOR.getId()] = SOLID | NONSOLID;
+
+ // repeaters are technically half blocks
+ types[Material.DIODE_BLOCK_OFF.getId()] = SOLID | NONSOLID;
+ types[Material.DIODE_BLOCK_ON.getId()] = SOLID | NONSOLID;
+
+ // pressure plates are so slim, you can consider them
+ // nonsolid too
+ types[Material.STONE_PLATE.getId()] = SOLID | NONSOLID;
+ types[Material.WOOD_PLATE.getId()] = SOLID | NONSOLID;
+
+ // We need to know what is considered food for the instanteat check
+ foods.add(Material.APPLE);
+ foods.add(Material.BREAD);
+ foods.add(Material.COOKED_BEEF);
+ foods.add(Material.COOKED_CHICKEN);
+ foods.add(Material.COOKED_FISH);
+ foods.add(Material.COOKIE);
+ foods.add(Material.GOLDEN_APPLE);
+ foods.add(Material.GRILLED_PORK);
+ foods.add(Material.MELON);
+ foods.add(Material.MUSHROOM_SOUP);
+ foods.add(Material.PORK);
+ foods.add(Material.RAW_BEEF);
+ foods.add(Material.RAW_CHICKEN);
+ foods.add(Material.RAW_FISH);
+ foods.add(Material.ROTTEN_FLESH);
+ foods.add(Material.SPIDER_EYE);
+ }
+
+ /**
+ * Ask NoCheat what it thinks about a certain location. Is it a place where a player can safely stand, should it be
+ * considered as being inside a liquid etc.
+ *
+ * @param world The world the coordinates belong to
+ * @param location The precise location in the world
+ *
+ * @return
+ */
+ public static final int evaluateLocation(final World world, final PreciseLocation location)
+ {
+
+ final int lowerX = lowerBorder(location.x);
+ final int upperX = upperBorder(location.x);
+ final int Y = (int)location.y;
+ final int lowerZ = lowerBorder(location.z);
+ final int upperZ = upperBorder(location.z);
+
+ // Check the four borders of the players hitbox for something he could
+ // be standing on, and combine the results
+ int result = 0;
+
+ result |= evaluateSimpleLocation(world, lowerX, Y, lowerZ);
+ result |= evaluateSimpleLocation(world, upperX, Y, lowerZ);
+ result |= evaluateSimpleLocation(world, upperX, Y, upperZ);
+ result |= evaluateSimpleLocation(world, lowerX, Y, upperZ);
+
+ if (!isInGround(result))
+ {
+ // Original location: X, Z (allow standing in walls this time)
+ if (isSolid(types[world.getBlockTypeIdAt(Location.locToBlock(location.x), Location.locToBlock(location.y), Location.locToBlock(location.z))]))
+ {
+ result |= INGROUND;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Evaluate a location by only looking at a specific "column" of the map to find out if that "column" would allow a
+ * player to stand, swim etc. there
+ *
+ * @param world
+ * @param x
+ * @param y
+ * @param z
+ * @return Returns INGROUND, ONGROUND, LIQUID, combination of the three or 0
+ */
+ private static final int evaluateSimpleLocation(final World world, final int x, final int y, final int z)
+ {
+
+ // First we need to know about the block itself, the block
+ // below it and the block above it
+ final int top = types[world.getBlockTypeIdAt(x, y + 1, z)];
+ final int base = types[world.getBlockTypeIdAt(x, y, z)];
+ final int below = types[world.getBlockTypeIdAt(x, y - 1, z)];
+
+ int type = 0;
+ // Special case: Standing on a fence
+ // Behave as if there is a block on top of the fence
+ if ((below == FENCE) && base != FENCE && isNonSolid(top))
+ {
+ type = INGROUND;
+ }
+ // Special case: Fence
+ // Being a bit above a fence
+ else if (below != FENCE && isNonSolid(base) && types[world.getBlockTypeIdAt(x, y - 2, z)] == FENCE)
+ {
+ type = ONGROUND;
+ }
+ else if (isNonSolid(top))
+ {
+ // Simplest (and most likely) case:
+ // Below the player is a solid block
+ if (isSolid(below) && isNonSolid(base))
+ {
+ type = ONGROUND;
+ }
+ // Next (likely) case:
+ // There is a ladder
+ else if (isLadder(base) || isLadder(top))
+ {
+ type = ONGROUND;
+ }
+ // Next (likely) case:
+ // At least the block the player stands
+ // in is solid
+ else if (isSolid(base))
+ {
+ type = INGROUND;
+ }
+ }
+
+ // (In every case, check for water)
+ if (isLiquid(base) || isLiquid(top))
+ {
+ type |= LIQUID | INGROUND;
+ }
+
+ return type;
+ }
+
+ public static final boolean isSolid(final int value)
+ {
+ return (value & SOLID) == SOLID;
+ }
+
+ public static final boolean isLiquid(final int value)
+ {
+ return (value & LIQUID) == LIQUID;
+ }
+
+ private static final boolean isNonSolid(final int value)
+ {
+ return ((value & NONSOLID) == NONSOLID);
+ }
+
+ private static final boolean isLadder(final int value)
+ {
+ return ((value & LADDER) == LADDER);
+ }
+
+ public static final boolean isOnGround(final int fromType)
+ {
+ return (fromType & ONGROUND) == ONGROUND;
+ }
+
+ public static final boolean isInGround(final int fromType)
+ {
+ return (fromType & INGROUND) == INGROUND;
+ }
+
+ /**
+ * Personal Rounding function to determine if a player is still touching a block or not
+ *
+ * @param d1
+ * @return
+ */
+ private static final int lowerBorder(final double d1)
+ {
+
+ final double floor = Math.floor(d1);
+
+ if (floor + magic <= d1)
+ {
+ return (int)(floor);
+ }
+ else
+ {
+ return (int)(floor - 1);
+ }
+ }
+
+ /**
+ * Personal Rounding function to determine if a player is still touching a block or not
+ *
+ * @param d1
+ * @return
+ */
+ private static final int upperBorder(final double d1)
+ {
+
+ final double floor = Math.floor(d1);
+
+ if (floor + magic2 < d1)
+ {
+ return (int)(floor + 1);
+ }
+ else
+ {
+ return (int)floor;
+ }
+ }
+
+ public static int getType(final int typeId)
+ {
+ return types[typeId];
+ }
+
+ public static boolean isFood(ItemStack item)
+ {
+ if (item == null)
+ {
+ return false;
+ }
+ return foods.contains(item.getType());
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java
new file mode 100644
index 000000000..7191c0c23
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java
@@ -0,0 +1,55 @@
+package com.earth2me.essentials.anticheat.checks;
+
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import java.util.Collections;
+import java.util.List;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.event.player.PlayerToggleSprintEvent;
+
+
+/**
+ * Only place that listens to Player-teleport related events and dispatches them to relevant checks
+ *
+ */
+public class WorkaroundsListener implements Listener, EventManager
+{
+ public WorkaroundsListener()
+ {
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void playerMove(final PlayerMoveEvent event)
+ {
+ // No typo here. I really only handle cancelled events and ignore others
+ if (!event.isCancelled())
+ {
+ return;
+ }
+
+ // Fix a common mistake that other developers make (cancelling move
+ // events is crazy, rather set the target location to the from location)
+ event.setCancelled(false);
+ event.setTo(event.getFrom().clone());
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void toggleSprint(final PlayerToggleSprintEvent event)
+ {
+ // Some plugins cancel "sprinting", which makes no sense at all because
+ // it doesn't stop people from sprinting and rewards them by reducing
+ // their hunger bar as if they were walking instead of sprinting
+ if (event.isCancelled() && event.isSprinting())
+ {
+ event.setCancelled(false);
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ return Collections.emptyList();
+ }
+}
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);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java
new file mode 100644
index 000000000..e20a74ca9
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java
@@ -0,0 +1,99 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+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.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+import java.util.Locale;
+
+
+/**
+ * Abstract base class for BlockPlace checks, provides some convenience methods for access to data and config that's
+ * relevant to this checktype
+ */
+public abstract class BlockPlaceCheck extends Check
+{
+ private static final String id = "blockplace";
+
+ public BlockPlaceCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+ if (wildcard == ParameterName.PLACE_LOCATION)
+ {
+ SimpleLocation l = getData(player).blockPlaced;
+ if (l.isSet())
+ {
+ return String.format(Locale.US, "%d %d %d", l.x, l.y, l.z);
+ }
+ else
+ {
+ return "null";
+ }
+ }
+ else if (wildcard == ParameterName.PLACE_AGAINST)
+ {
+ SimpleLocation l = getData(player).blockPlacedAgainst;
+ if (l.isSet())
+ {
+ return String.format(Locale.US, "%d %d %d", l.x, l.y, l.z);
+ }
+ else
+ {
+ return "null";
+ }
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+
+ /**
+ * Get the "BlockPlaceData" object that belongs to the player. Will ensure that such a object exists and if not,
+ * create one
+ *
+ * @param player
+ * @return
+ */
+ public static BlockPlaceData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ BlockPlaceData data = base.get(id);
+ if (data == null)
+ {
+ data = new BlockPlaceData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the BlockPlaceConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static BlockPlaceConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static BlockPlaceConfig getConfig(ConfigurationCacheStore cache)
+ {
+ BlockPlaceConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new BlockPlaceConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java
new file mode 100644
index 000000000..253982bd1
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java
@@ -0,0 +1,97 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+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.BlockPlaceEvent;
+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 Block-related events and dispatching them to checks
+ *
+ */
+public class BlockPlaceCheckListener implements Listener, EventManager
+{
+ private final ReachCheck reachCheck;
+ private final DirectionCheck directionCheck;
+ private final NoCheat plugin;
+
+ public BlockPlaceCheckListener(NoCheat plugin)
+ {
+
+ this.plugin = plugin;
+
+ reachCheck = new ReachCheck(plugin);
+ directionCheck = new DirectionCheck(plugin);
+ }
+
+ /**
+ * We listen to BlockPlace events for obvious reasons
+ *
+ * @param event the BlockPlace event
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ protected void handleBlockPlaceEvent(BlockPlaceEvent event)
+ {
+
+ if (event.isCancelled() || event.getBlock() == null || event.getBlockAgainst() == null)
+ {
+ return;
+ }
+
+ boolean cancelled = false;
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final BlockPlaceConfig cc = BlockPlaceCheck.getConfig(player);
+ final BlockPlaceData data = BlockPlaceCheck.getData(player);
+
+ // Remember these locations and put them in a simpler "format"
+ data.blockPlaced.set(event.getBlock());
+ data.blockPlacedAgainst.set(event.getBlockAgainst());
+
+ // Now do the actual checks
+
+ // First the reach check
+ if (cc.reachCheck && !player.hasPermission(Permissions.BLOCKPLACE_REACH))
+ {
+ cancelled = reachCheck.check(player, data, cc);
+ }
+
+ // Second the direction check
+ if (!cancelled && cc.directionCheck && !player.hasPermission(Permissions.BLOCKPLACE_DIRECTION))
+ {
+ cancelled = directionCheck.check(player, data, cc);
+ }
+
+ // If one of the checks requested to cancel the event, do so
+ if (cancelled)
+ {
+ event.setCancelled(cancelled);
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ BlockPlaceConfig bp = BlockPlaceCheck.getConfig(cc);
+
+ if (bp.reachCheck)
+ {
+ s.add("blockplace.reach");
+ }
+ if (bp.directionCheck)
+ {
+ s.add("blockplace.direction");
+ }
+
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java
new file mode 100644
index 000000000..26c8d0f6d
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java
@@ -0,0 +1,37 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+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 "BlockPlace" 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 BlockPlaceConfig implements ConfigItem
+{
+ public final boolean reachCheck;
+ public final double reachDistance;
+ public final ActionList reachActions;
+ public final boolean directionCheck;
+ public final ActionList directionActions;
+ public final long directionPenaltyTime;
+ public final double directionPrecision;
+
+ public BlockPlaceConfig(NoCheatConfiguration data)
+ {
+
+ reachCheck = data.getBoolean(ConfPaths.BLOCKPLACE_REACH_CHECK);
+ reachDistance = 535D / 100D;
+ reachActions = data.getActionList(ConfPaths.BLOCKPLACE_REACH_ACTIONS, Permissions.BLOCKPLACE_REACH);
+
+ directionCheck = data.getBoolean(ConfPaths.BLOCKPLACE_DIRECTION_CHECK);
+ directionPenaltyTime = data.getInt(ConfPaths.BLOCKPLACE_DIRECTION_PENALTYTIME);
+ directionPrecision = ((double)data.getInt(ConfPaths.BLOCKPLACE_DIRECTION_PRECISION)) / 100D;
+ directionActions = data.getActionList(ConfPaths.BLOCKPLACE_DIRECTION_ACTIONS, Permissions.BLOCKPLACE_DIRECTION);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java
new file mode 100644
index 000000000..47ff9d58a
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java
@@ -0,0 +1,25 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import com.earth2me.essentials.anticheat.data.SimpleLocation;
+
+
+/**
+ * Player specific data for the blockbreak checks
+ *
+ */
+public class BlockPlaceData implements DataItem
+{
+ // Keep track of violation levels for the two checks
+ public double reachVL = 0.0D;
+ public double directionVL = 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 blockPlacedAgainst = new SimpleLocation();
+ public final SimpleLocation blockPlaced = new SimpleLocation();
+ // 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/blockplace/DirectionCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/DirectionCheck.java
new file mode 100644
index 000000000..8aa782d19
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/DirectionCheck.java
@@ -0,0 +1,131 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+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;
+import java.util.Locale;
+import org.bukkit.Location;
+
+
+/**
+ * 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 BlockPlaceCheck
+{
+ public DirectionCheck(NoCheat plugin)
+ {
+ super(plugin, "blockplace.direction");
+ }
+
+ public boolean check(NoCheatPlayer player, BlockPlaceData data, BlockPlaceConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final SimpleLocation blockPlaced = data.blockPlaced;
+ final SimpleLocation blockPlacedAgainst = data.blockPlacedAgainst;
+
+ // 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, blockPlacedAgainst.x + 0.5D, blockPlacedAgainst.y + 0.5D, blockPlacedAgainst.z + 0.5D, 1D, 1D, cc.directionPrecision);
+
+ // now check if the player is looking at the block from the correct side
+ double off2 = 0.0D;
+
+ // Find out against which face the player tried to build, and if he
+ // stood on the correct side of it
+ Location eyes = player.getPlayer().getEyeLocation();
+ if (blockPlaced.x > blockPlacedAgainst.x)
+ {
+ off2 = blockPlacedAgainst.x + 0.5D - eyes.getX();
+ }
+ else if (blockPlaced.x < blockPlacedAgainst.x)
+ {
+ off2 = -(blockPlacedAgainst.x + 0.5D - eyes.getX());
+ }
+ else if (blockPlaced.y > blockPlacedAgainst.y)
+ {
+ off2 = blockPlacedAgainst.y + 0.5D - eyes.getY();
+ }
+ else if (blockPlaced.y < blockPlacedAgainst.y)
+ {
+ off2 = -(blockPlacedAgainst.y + 0.5D - eyes.getY());
+ }
+ else if (blockPlaced.z > blockPlacedAgainst.z)
+ {
+ off2 = blockPlacedAgainst.z + 0.5D - eyes.getZ();
+ }
+ else if (blockPlaced.z < blockPlacedAgainst.z)
+ {
+ off2 = -(blockPlacedAgainst.z + 0.5D - eyes.getZ());
+ }
+
+ // If he wasn't on the correct side, add that to the "off" value
+ if (off2 > 0.0D)
+ {
+ off += off2;
+ }
+
+ final long time = System.currentTimeMillis();
+
+ if (off < 0.1D)
+ {
+ // Player did nothing wrong
+ // reduce violation counter to reward him
+ data.directionVL *= 0.9D;
+ }
+ else
+ {
+ // Player failed the check
+ // Increment violation counter and statistics
+ data.directionVL += off;
+ incrementStatistics(player, Id.BP_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, cc.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 + 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)
+ {
+ 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/blockplace/ReachCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/ReachCheck.java
new file mode 100644
index 000000000..6e13a9348
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/ReachCheck.java
@@ -0,0 +1,75 @@
+package com.earth2me.essentials.anticheat.checks.blockplace;
+
+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;
+import java.util.Locale;
+
+
+/**
+ * The reach check will find out if a player interacts with something that's too far away
+ *
+ */
+public class ReachCheck extends BlockPlaceCheck
+{
+ public ReachCheck(NoCheat plugin)
+ {
+ super(plugin, "blockplace.reach");
+ }
+
+ public boolean check(NoCheatPlayer player, BlockPlaceData data, BlockPlaceConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final SimpleLocation placedAgainstBlock = data.blockPlacedAgainst;
+
+ // 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, placedAgainstBlock.x + 0.5D, placedAgainstBlock.y + 0.5D, placedAgainstBlock.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.BP_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);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java
new file mode 100644
index 000000000..b1f14deec
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java
@@ -0,0 +1,79 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+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.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+
+
+/**
+ * Abstract base class for Chat checks, provides some convenience methods for access to data and config that's relevant
+ * to this checktype
+ */
+public abstract class ChatCheck extends Check
+{
+ private static final String id = "chat";
+
+ public ChatCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.TEXT)
+ // Filter colors from the players message when logging
+ {
+ return getData(player).message.replaceAll("\302\247.", "").replaceAll("\247.", "");
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+
+ /**
+ * Get the "ChatData" object that belongs to the player. Will ensure that such a object exists and if not, create
+ * one
+ *
+ * @param player
+ * @return
+ */
+ public static ChatData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ ChatData data = base.get(id);
+ if (data == null)
+ {
+ data = new ChatData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the ChatConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static ChatConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static ChatConfig getConfig(ConfigurationCacheStore cache)
+ {
+ ChatConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new ChatConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java
new file mode 100644
index 000000000..965a374aa
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java
@@ -0,0 +1,108 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+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;
+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.player.PlayerChatEvent;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+
+
+/**
+ * Central location to listen to events that are relevant for the chat checks
+ *
+ */
+public class ChatCheckListener implements Listener, EventManager
+{
+ private final SpamCheck spamCheck;
+ private final ColorCheck colorCheck;
+ private final NoCheat plugin;
+
+ public ChatCheckListener(NoCheat plugin)
+ {
+
+ this.plugin = plugin;
+
+ spamCheck = new SpamCheck(plugin);
+ colorCheck = new ColorCheck(plugin);
+ }
+
+ /**
+ * We listen to PlayerCommandPreprocess events because commands can be used for spamming too.
+ *
+ * @param event The PlayerCommandPreprocess Event
+ */
+ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
+ public void commandPreprocess(final PlayerCommandPreprocessEvent event)
+ {
+ // This type of event is derived from PlayerChatEvent, therefore
+ // just treat it like that
+ chat(event);
+ }
+
+ /**
+ * We listen to PlayerChat events for obvious reasons
+ *
+ * @param event The PlayerChat event
+ */
+ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
+ public void chat(final PlayerChatEvent event)
+ {
+ boolean cancelled = false;
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final ChatConfig cc = ChatCheck.getConfig(player);
+ final ChatData data = ChatCheck.getData(player);
+
+ // Remember the original message
+ data.message = event.getMessage();
+
+ // Now do the actual checks
+
+ // First the spam check
+ if (cc.spamCheck && !player.hasPermission(Permissions.CHAT_SPAM))
+ {
+ cancelled = spamCheck.check(player, data, cc);
+ }
+
+ // Second the color check
+ if (!cancelled && cc.colorCheck && !player.hasPermission(Permissions.CHAT_COLOR))
+ {
+ cancelled = colorCheck.check(player, data, cc);
+ }
+
+ // If one of the checks requested the event to be cancelled, do it
+ if (cancelled)
+ {
+ event.setCancelled(cancelled);
+ }
+ else
+ {
+ // In case one of the events modified the message, make sure that
+ // the new message gets used
+ event.setMessage(data.message);
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ ChatConfig c = ChatCheck.getConfig(cc);
+ if (c.spamCheck)
+ {
+ s.add("chat.spam");
+ }
+ if (c.colorCheck)
+ {
+ s.add("chat.color");
+ }
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java
new file mode 100644
index 000000000..06ad5c9fc
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java
@@ -0,0 +1,64 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+import java.util.LinkedList;
+import java.util.List;
+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 "Chat" 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 ChatConfig implements ConfigItem
+{
+ public final boolean spamCheck;
+ public final String[] spamWhitelist;
+ public final long spamTimeframe;
+ public final int spamMessageLimit;
+ public final int spamCommandLimit;
+ public final ActionList spamActions;
+ public final boolean colorCheck;
+ public final ActionList colorActions;
+
+ public ChatConfig(NoCheatConfiguration data)
+ {
+
+ spamCheck = data.getBoolean(ConfPaths.CHAT_SPAM_CHECK);
+ spamWhitelist = splitWhitelist(data.getString(ConfPaths.CHAT_SPAM_WHITELIST));
+ spamTimeframe = data.getInt(ConfPaths.CHAT_SPAM_TIMEFRAME) * 1000L;
+ spamMessageLimit = data.getInt(ConfPaths.CHAT_SPAM_MESSAGELIMIT);
+ spamCommandLimit = data.getInt(ConfPaths.CHAT_SPAM_COMMANDLIMIT);
+ spamActions = data.getActionList(ConfPaths.CHAT_SPAM_ACTIONS, Permissions.CHAT_SPAM);
+ colorCheck = data.getBoolean(ConfPaths.CHAT_COLOR_CHECK);
+ colorActions = data.getActionList(ConfPaths.CHAT_COLOR_ACTIONS, Permissions.CHAT_COLOR);
+ }
+
+ /**
+ * Convenience method to split a string into an array on every occurance of the "," character, removing all
+ * whitespaces before and after it too.
+ *
+ * @param string The string containing text seperated by ","
+ * @return An array of the seperate texts
+ */
+ private String[] splitWhitelist(String string)
+ {
+
+ List<String> strings = new LinkedList<String>();
+ string = string.trim();
+
+ for (String s : string.split(","))
+ {
+ if (s != null && s.trim().length() > 0)
+ {
+ strings.add(s.trim());
+ }
+ }
+
+ return strings.toArray(new String[strings.size()]);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java
new file mode 100644
index 000000000..b05cb2579
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java
@@ -0,0 +1,22 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+import com.earth2me.essentials.anticheat.DataItem;
+
+
+/**
+ * Player specific data for the chat checks
+ *
+ */
+public class ChatData implements DataItem
+{
+ // Keep track of the violation levels for the two checks
+ public int spamVL;
+ public int colorVL;
+ // Count messages and commands
+ public int messageCount = 0;
+ public int commandCount = 0;
+ // Remember when the last check time period started
+ public long spamLastTime = 0;
+ // Remember the last chat message or command for logging purposes
+ public String message = "";
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java
new file mode 100644
index 000000000..2468c7065
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java
@@ -0,0 +1,50 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+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;
+
+
+public class ColorCheck extends ChatCheck
+{
+ public ColorCheck(NoCheat plugin)
+ {
+ super(plugin, "chat.color");
+ }
+
+ public boolean check(NoCheatPlayer player, ChatData data, ChatConfig cc)
+ {
+
+ if (data.message.contains("\247"))
+ {
+
+ data.colorVL += 1;
+ incrementStatistics(player, Id.CHAT_COLOR, 1);
+
+ boolean filter = executeActions(player, cc.colorActions, data.colorVL);
+
+ if (filter)
+ {
+ // Remove color codes
+ data.message = data.message.replaceAll("\302\247.", "").replaceAll("\247.", "");
+ }
+ }
+
+ return false;
+ }
+
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).colorVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java
new file mode 100644
index 000000000..8bf893091
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java
@@ -0,0 +1,96 @@
+package com.earth2me.essentials.anticheat.checks.chat;
+
+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;
+import java.util.Locale;
+
+
+/**
+ * The SpamCheck will count messages and commands over a short timeframe to see if the player tried to send too many of
+ * them
+ *
+ */
+public class SpamCheck extends ChatCheck
+{
+ public SpamCheck(NoCheat plugin)
+ {
+ super(plugin, "chat.spam");
+ }
+
+ public boolean check(NoCheatPlayer player, ChatData data, ChatConfig cc)
+ {
+
+ boolean cancel = false;
+ // Maybe it's a command and on the whitelist
+ for (String s : cc.spamWhitelist)
+ {
+ if (data.message.startsWith(s))
+ {
+ // It is
+ return false;
+ }
+ }
+
+ int commandLimit = cc.spamCommandLimit;
+ int messageLimit = cc.spamMessageLimit;
+ long timeframe = cc.spamTimeframe;
+
+ final long time = System.currentTimeMillis();
+
+ // Has enough time passed? Then reset the counters
+ if (data.spamLastTime + timeframe <= time)
+ {
+ data.spamLastTime = time;
+ data.messageCount = 0;
+ data.commandCount = 0;
+ }
+ // Security check, if the system time changes
+ else if (data.spamLastTime > time)
+ {
+ data.spamLastTime = Integer.MIN_VALUE;
+ }
+
+ // Increment appropriate counter
+ if (data.message.startsWith("/"))
+ {
+ data.commandCount++;
+ }
+ else
+ {
+ data.messageCount++;
+ }
+
+ // Did the player go over the limit on at least one of the counters?
+ if (data.messageCount > messageLimit || data.commandCount > commandLimit)
+ {
+
+ // Set the vl as the number of messages above the limit and
+ // increment statistics
+ data.spamVL = Math.max(0, data.messageCount - messageLimit);
+ data.spamVL += Math.max(0, data.commandCount - commandLimit);
+ incrementStatistics(player, Id.CHAT_SPAM, 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.spamActions, data.spamVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).spamVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java
new file mode 100644
index 000000000..93ce58221
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java
@@ -0,0 +1,120 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+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.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityComplex;
+import net.minecraft.server.EntityComplexPart;
+
+
+/**
+ * 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)
+ {
+ super(plugin, "fight.direction", Permissions.FIGHT_DIRECTION);
+ }
+
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final long time = System.currentTimeMillis();
+
+ // Get the damagee (entity that got hit)
+ Entity entity = data.damagee;
+
+ // Safeguard, if entity is complex, this check will fail
+ // due to giant and hard to define hitboxes
+ if (entity instanceof EntityComplex || entity instanceof EntityComplexPart)
+ {
+ 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 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 to reward him
+ data.directionVL *= 0.80D;
+ }
+ else
+ {
+ // Player failed the check
+ // 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)
+ {
+ // 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)
+ {
+ data.directionLastViolationTime = 0;
+ }
+
+ // He is in penalty time, therefore request cancelling of the event
+ return true;
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.directionCheck;
+ }
+
+ @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/fight/FightCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheck.java
new file mode 100644
index 000000000..f8dd4e3db
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheck.java
@@ -0,0 +1,69 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+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 Fight checks, provides some convenience methods for access to data and config that's relevant
+ * to this checktype
+ */
+public abstract class FightCheck extends Check
+{
+ private static final String id = "fight";
+ public final String permission;
+
+ public FightCheck(NoCheat plugin, String name, String permission)
+ {
+ super(plugin, id, name);
+ this.permission = permission;
+ }
+
+ public abstract boolean check(NoCheatPlayer player, FightData data, FightConfig cc);
+
+ public abstract boolean isEnabled(FightConfig cc);
+
+ /**
+ * 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();
+ base.set(id, data);
+ }
+ 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)
+ {
+ config = new FightConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java
new file mode 100644
index 000000000..fc1ea160c
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java
@@ -0,0 +1,291 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.craftbukkit.entity.CraftEntity;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.Entity;
+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 com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+
+
+/**
+ * Central location to listen to events that are relevant for the fight checks
+ *
+ */
+public class FightCheckListener implements Listener, EventManager
+{
+ private final List<FightCheck> checks;
+ private final GodmodeCheck godmodeCheck;
+ private final InstanthealCheck instanthealCheck;
+ private final NoCheat plugin;
+
+ public FightCheckListener(NoCheat plugin)
+ {
+
+ 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));
+ this.checks.add(new ReachCheck(plugin));
+
+ this.godmodeCheck = new GodmodeCheck(plugin);
+ this.instanthealCheck = new InstanthealCheck(plugin);
+
+ 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;
+ }
+
+ final EntityDamageByEntityEvent e = (EntityDamageByEntityEvent)event;
+ if (!(e.getDamager() instanceof Player))
+ {
+ return;
+ }
+
+ if (e.getCause() == DamageCause.ENTITY_ATTACK)
+ {
+ normalDamage(e);
+ }
+ else if (e.getCause() == DamageCause.CUSTOM)
+ {
+ customDamage(e);
+ }
+ }
+
+ /**
+ * 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);
+
+ if (!godmodeCheck.isEnabled(cc) || player.hasPermission(godmodeCheck.permission))
+ {
+ return;
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * We listen to EntityRegainHealth events of type "Satiated" for instantheal check
+ *
+ * @param event The EntityRegainHealth Event
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void satiatedRegen(final EntityRegainHealthEvent event)
+ {
+
+ if (!(event.getEntity() instanceof Player) || event.isCancelled() || event.getRegainReason() != RegainReason.SATIATED)
+ {
+ return;
+ }
+
+ boolean cancelled = false;
+
+ NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
+ FightConfig config = FightCheck.getConfig(player);
+
+ if (!instanthealCheck.isEnabled(config) || player.hasPermission(instanthealCheck.permission))
+ {
+ return;
+ }
+
+ FightData data = FightCheck.getData(player);
+
+ cancelled = instanthealCheck.check(player, data, config);
+
+ if (cancelled)
+ {
+ event.setCancelled(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 normalDamage(final EntityDamageByEntityEvent event)
+ {
+
+ final Player damager = (Player)event.getDamager();
+
+ final NoCheatPlayer player = plugin.getPlayer(damager);
+ 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)
+ {
+ data.skipNext = false;
+ return;
+ }
+
+ boolean cancelled = false;
+
+ // 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))
+ {
+ cancelled = check.check(player, data, cc);
+ }
+ }
+
+ // 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);
+
+ // Skip the next damage event, because it is with high probability
+ // something from the Heroes plugin
+ data.skipNext = true;
+
+ 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;
+ }
+
+ 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)
+ {
+ // 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)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ FightConfig f = FightCheck.getConfig(cc);
+
+ if (f.directionCheck)
+ {
+ s.add("fight.direction");
+ }
+ if (f.noswingCheck)
+ {
+ s.add("fight.noswing");
+ }
+ if (f.reachCheck)
+ {
+ s.add("fight.reach");
+ }
+ if (f.speedCheck)
+ {
+ s.add("fight.speed");
+ }
+ if (f.godmodeCheck)
+ {
+ s.add("fight.godmode");
+ }
+ if (f.instanthealCheck)
+ {
+ s.add("fight.instantHeal");
+ }
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java
new file mode 100644
index 000000000..9a36128ae
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java
@@ -0,0 +1,58 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+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 "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 instanthealCheck;
+ public final ActionList instanthealActions;
+
+ public FightConfig(NoCheatConfiguration data)
+ {
+
+ directionCheck = data.getBoolean(ConfPaths.FIGHT_DIRECTION_CHECK);
+ directionPrecision = ((double)(data.getInt(ConfPaths.FIGHT_DIRECTION_PRECISION))) / 100D;
+ directionPenaltyTime = data.getInt(ConfPaths.FIGHT_DIRECTION_PENALTYTIME);
+ directionActions = data.getActionList(ConfPaths.FIGHT_DIRECTION_ACTIONS, Permissions.FIGHT_DIRECTION);
+ noswingCheck = data.getBoolean(ConfPaths.FIGHT_NOSWING_CHECK);
+ noswingActions = data.getActionList(ConfPaths.FIGHT_NOSWING_ACTIONS, Permissions.FIGHT_NOSWING);
+ reachCheck = data.getBoolean(ConfPaths.FIGHT_REACH_CHECK);
+ reachLimit = ((double)(data.getInt(ConfPaths.FIGHT_REACH_LIMIT))) / 100D;
+ reachPenaltyTime = data.getInt(ConfPaths.FIGHT_REACH_PENALTYTIME);
+ reachActions = data.getActionList(ConfPaths.FIGHT_REACH_ACTIONS, Permissions.FIGHT_REACH);
+ speedCheck = data.getBoolean(ConfPaths.FIGHT_SPEED_CHECK);
+ speedActions = data.getActionList(ConfPaths.FIGHT_SPEED_ACTIONS, Permissions.FIGHT_SPEED);
+ speedAttackLimit = data.getInt(ConfPaths.FIGHT_SPEED_ATTACKLIMIT);
+
+ godmodeCheck = data.getBoolean(ConfPaths.FIGHT_GODMODE_CHECK);
+ godmodeActions = data.getActionList(ConfPaths.FIGHT_GODMODE_ACTIONS, Permissions.FIGHT_GODMODE);
+
+ instanthealCheck = data.getBoolean(ConfPaths.FIGHT_INSTANTHEAL_CHECK);
+ instanthealActions = data.getActionList(ConfPaths.FIGHT_INSTANTHEAL_ACTIONS, Permissions.FIGHT_INSTANTHEAL);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java
new file mode 100644
index 000000000..9f3a5a5d4
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java
@@ -0,0 +1,40 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import net.minecraft.server.Entity;
+
+
+/**
+ * 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;
+ public double instanthealVL;
+ // 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;
+ // last time player regenerated health by satiation
+ public long instanthealLastRegenTime;
+ // three seconds buffer to smooth out lag
+ public long instanthealBuffer = 3000;
+ // 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;
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java
new file mode 100644
index 000000000..cd0fd6aaa
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java
@@ -0,0 +1,151 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics;
+import java.util.Locale;
+import net.minecraft.server.EntityPlayer;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+
+
+/**
+ * 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)
+ {
+ super(plugin, "fight.godmode", Permissions.FIGHT_GODMODE);
+ }
+
+ @Override
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancelled = false;
+
+ long time = System.currentTimeMillis();
+
+ // Check at most once a second
+ if (data.godmodeLastDamageTime + 1000L < time)
+ {
+ data.godmodeLastDamageTime = time;
+
+ // How old is the player now?
+ int age = player.getTicksLived();
+ // How much older did he get?
+ int ageDiff = Math.max(0, age - data.godmodeLastAge);
+ // Is he invulnerable?
+ int nodamageTicks = player.getPlayer().getNoDamageTicks();
+
+ if (nodamageTicks > 0 && ageDiff < 15)
+ {
+ // 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, 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
+ {
+ // Give some new points, once a second
+ data.godmodeBuffer += 15;
+ data.godmodeVL *= 0.95;
+ }
+
+ 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;
+ }
+
+ // Start age counting from a new time
+ data.godmodeLastAge = age;
+ }
+
+ return cancelled;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.godmodeCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ 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, by setting up a
+ * Bukkit task
+ *
+ * @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);
+ }
+ }
+ catch (Exception e)
+ {
+ }
+ }
+ }, 30);
+ }
+ catch (Exception e)
+ {
+ }
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java
new file mode 100644
index 000000000..33fcbfd3a
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java
@@ -0,0 +1,94 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics;
+import java.util.Locale;
+
+
+/**
+ * The instantheal Check should find out if a player tried to artificially accellerate the health regeneration by food
+ *
+ */
+public class InstanthealCheck extends FightCheck
+{
+ public InstanthealCheck(NoCheat plugin)
+ {
+ super(plugin, "fight.instantheal", Permissions.FIGHT_INSTANTHEAL);
+ }
+
+ @Override
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancelled = false;
+
+ long time = System.currentTimeMillis();
+
+ // security check if system time ran backwards
+ if (data.instanthealLastRegenTime > time)
+ {
+ data.instanthealLastRegenTime = 0;
+ return false;
+ }
+
+ long difference = time - (data.instanthealLastRegenTime + 3500L);
+
+ data.instanthealBuffer += difference;
+
+ if (data.instanthealBuffer < 0)
+ {
+ // Buffer has been fully consumed
+ // Increase vl and statistics
+ double vl = data.instanthealVL -= data.instanthealBuffer / 1000;
+ incrementStatistics(player, Statistics.Id.FI_INSTANTHEAL, vl);
+
+ data.instanthealBuffer = 0;
+
+ // 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.instanthealActions, data.instanthealVL);
+ }
+ else
+ {
+ // vl gets decreased
+ data.instanthealVL *= 0.9;
+ }
+
+ // max 2 seconds buffer
+ if (data.instanthealBuffer > 2000L)
+ {
+ data.instanthealBuffer = 2000L;
+ }
+
+ if (!cancelled)
+ {
+ // New reference time
+ data.instanthealLastRegenTime = time;
+ }
+
+ return cancelled;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.instanthealCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).instanthealVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java
new file mode 100644
index 000000000..99d7ac1fd
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java
@@ -0,0 +1,67 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * 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)
+ {
+ super(plugin, "fight.noswing", Permissions.FIGHT_NOSWING);
+ }
+
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancel = false;
+
+ // 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);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.noswingCheck;
+ }
+
+ @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/fight/ReachCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/ReachCheck.java
new file mode 100644
index 000000000..c56caed08
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/ReachCheck.java
@@ -0,0 +1,113 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+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.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityComplex;
+import net.minecraft.server.EntityComplexPart;
+
+
+/**
+ * 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)
+ {
+ super(plugin, "fight.reach", Permissions.FIGHT_REACH);
+ }
+
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final long time = System.currentTimeMillis();
+
+ // Get the width of the damagee
+ Entity entity = data.damagee;
+
+ // 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)
+ {
+ return false;
+ }
+
+ // 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 to reward him
+ data.reachVL *= 0.80D;
+ }
+ else
+ {
+ // Player failed the check
+ // 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)
+ {
+ // 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)
+ {
+ data.reachLastViolationTime = 0;
+ }
+
+ // He is in penalty time, therefore request cancelling of the event
+ return true;
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.reachCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).reachVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java
new file mode 100644
index 000000000..baa7db9c5
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java
@@ -0,0 +1,81 @@
+package com.earth2me.essentials.anticheat.checks.fight;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.actions.ParameterName;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * 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)
+ {
+ super(plugin, "fight.speed", Permissions.FIGHT_SPEED);
+ }
+
+ public boolean check(NoCheatPlayer player, FightData data, FightConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final long time = System.currentTimeMillis();
+
+ // 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);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public boolean isEnabled(FightConfig cc)
+ {
+ return cc.speedCheck;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).speedVL);
+ }
+ else if (wildcard == ParameterName.LIMIT)
+ {
+ return String.format(Locale.US, "%d", getConfig(player.getConfigurationStore()).speedAttackLimit);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java
new file mode 100644
index 000000000..2e9d030f7
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java
@@ -0,0 +1,71 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+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;
+import java.util.Locale;
+
+
+/**
+ * The DropCheck will find out if a player drops too many items within a short amount of time
+ *
+ */
+public class DropCheck extends InventoryCheck
+{
+ public DropCheck(NoCheat plugin)
+ {
+ super(plugin, "inventory.drop");
+ }
+
+ public boolean check(NoCheatPlayer player, InventoryData data, InventoryConfig cc)
+ {
+
+ boolean cancel = false;
+
+ final long time = System.currentTimeMillis();
+
+ // Has the configured time passed? If so, reset the counter
+ if (data.dropLastTime + cc.dropTimeFrame <= time)
+ {
+ data.dropLastTime = time;
+ data.dropCount = 0;
+ data.dropVL = 0;
+ }
+ // Security check, if the system time changes
+ else if (data.dropLastTime > time)
+ {
+ data.dropLastTime = Integer.MIN_VALUE;
+ }
+
+ data.dropCount++;
+
+ // The player dropped more than he should
+ if (data.dropCount > cc.dropLimit)
+ {
+ // Set vl and increment statistics
+ data.dropVL = data.dropCount - cc.dropLimit;
+ incrementStatistics(player, Id.INV_DROP, 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.dropActions, data.dropVL);
+ }
+
+ return cancel;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).dropVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java
new file mode 100644
index 000000000..7d4fcf3bb
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java
@@ -0,0 +1,72 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+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;
+import java.util.Locale;
+import org.bukkit.event.entity.EntityShootBowEvent;
+
+
+/**
+ * The InstantBowCheck will find out if a player pulled the string of his bow too fast
+ */
+public class InstantBowCheck extends InventoryCheck
+{
+ public InstantBowCheck(NoCheat plugin)
+ {
+ super(plugin, "inventory.instantbow");
+ }
+
+ public boolean check(NoCheatPlayer player, EntityShootBowEvent event, InventoryData data, InventoryConfig cc)
+ {
+
+ boolean cancelled = false;
+
+ long time = System.currentTimeMillis();
+
+ // How fast will the arrow be?
+ float bowForce = event.getForce();
+
+ // Rough estimation of how long pulling the string should've taken
+ long expectedTimeWhenStringDrawn = data.lastBowInteractTime + (int)(bowForce * bowForce * 700F);
+
+ if (expectedTimeWhenStringDrawn < time)
+ {
+ // The player was slow enough, reward him by lowering the vl
+ data.instantBowVL *= 0.90D;
+ }
+ else if (data.lastBowInteractTime > time)
+ {
+ // Security check if time ran backwards, reset
+ data.lastBowInteractTime = 0;
+ }
+ else
+ {
+ // Player was too fast, increase violation level and statistics
+ int vl = ((int)(expectedTimeWhenStringDrawn - time)) / 100;
+ data.instantBowVL += vl;
+ incrementStatistics(player, Id.INV_BOW, vl);
+
+ // 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.bowActions, data.instantBowVL);
+ }
+
+ return cancelled;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", getData(player).instantBowVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java
new file mode 100644
index 000000000..05a668dd7
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java
@@ -0,0 +1,78 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+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;
+import java.util.Locale;
+import org.bukkit.event.entity.FoodLevelChangeEvent;
+
+
+/**
+ * The InstantEatCheck will find out if a player eats his food too fast
+ */
+public class InstantEatCheck extends InventoryCheck
+{
+ public InstantEatCheck(NoCheat plugin)
+ {
+ super(plugin, "inventory.instanteat");
+ }
+
+ public boolean check(NoCheatPlayer player, FoodLevelChangeEvent event, InventoryData data, InventoryConfig cc)
+ {
+
+ // Hunger level change seems to not be the result of eating
+ if (data.foodMaterial == null || event.getFoodLevel() <= player.getPlayer().getFoodLevel())
+ {
+ return false;
+ }
+
+ boolean cancelled = false;
+
+ long time = System.currentTimeMillis();
+ // rough estimation about how long it should take to eat
+ long expectedTimeWhenEatingFinished = data.lastEatInteractTime + 700;
+
+ if (expectedTimeWhenEatingFinished < time)
+ {
+ // Acceptable, reduce VL to reward the player
+ data.instantEatVL *= 0.60D;
+ }
+ else if (data.lastEatInteractTime > time)
+ {
+ // Security test, if time ran backwards, reset
+ data.lastEatInteractTime = 0;
+ }
+ else
+ {
+ // Player was too fast, increase violation level and statistics
+ int vl = ((int)(expectedTimeWhenEatingFinished - time)) / 100;
+ data.instantEatVL += vl;
+ incrementStatistics(player, Id.INV_EAT, vl);
+
+ // 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.eatActions, data.instantEatVL);
+ }
+
+ return cancelled;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).instantEatVL);
+ }
+ else if (wildcard == ParameterName.FOOD)
+ {
+ return getData(player).foodMaterial.toString();
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java
new file mode 100644
index 000000000..ad60ffa3e
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java
@@ -0,0 +1,63 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+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 Inventory checks, provides some convenience methods for access to data and config that's
+ * relevant to this checktype
+ */
+public abstract class InventoryCheck extends Check
+{
+ private static final String id = "inventory";
+
+ public InventoryCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ /**
+ * Get the "InventoryData" object that belongs to the player. Will ensure that such a object exists and if not,
+ * create one
+ *
+ * @param player
+ * @return
+ */
+ public static InventoryData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ InventoryData data = base.get(id);
+ if (data == null)
+ {
+ data = new InventoryData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the InventoryConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static InventoryConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static InventoryConfig getConfig(ConfigurationCacheStore cache)
+ {
+ InventoryConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new InventoryConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java
new file mode 100644
index 000000000..f42a37185
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java
@@ -0,0 +1,196 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.entity.EntityShootBowEvent;
+import org.bukkit.event.entity.FoodLevelChangeEvent;
+import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+
+
+/**
+ * Central location to listen to events that are relevant for the inventory checks
+ *
+ */
+public class InventoryCheckListener implements Listener, EventManager
+{
+ private final DropCheck dropCheck;
+ private final InstantBowCheck instantBowCheck;
+ private final InstantEatCheck instantEatCheck;
+ private final NoCheat plugin;
+
+ public InventoryCheckListener(NoCheat plugin)
+ {
+
+ this.dropCheck = new DropCheck(plugin);
+ this.instantBowCheck = new InstantBowCheck(plugin);
+ this.instantEatCheck = new InstantEatCheck(plugin);
+
+ this.plugin = plugin;
+ }
+
+ /**
+ * We listen to DropItem Event for the dropCheck
+ *
+ * @param event The PlayerDropItem Event
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ protected void handlePlayerDropItemEvent(final PlayerDropItemEvent event)
+ {
+
+ if (event.isCancelled() || event.getPlayer().isDead())
+ {
+ return;
+ }
+
+ boolean cancelled = false;
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final InventoryConfig cc = InventoryCheck.getConfig(player);
+ final InventoryData data = InventoryCheck.getData(player);
+
+ // If it should be executed, do it
+ if (cc.dropCheck && !player.hasPermission(Permissions.INVENTORY_DROP))
+ {
+ cancelled = dropCheck.check(player, data, cc);
+ }
+
+ if (cancelled)
+ {
+ // Cancelling drop events is not save (in certain circumstances
+ // items will disappear completely). So don't do it and kick
+ // players instead by default
+ // event.setCancelled(true);
+ }
+ }
+
+ /**
+ * We listen to PlayerInteractEvent for the instantEat and instantBow checks
+ *
+ * @param event The PlayerInteractEvent
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void interact(final PlayerInteractEvent event)
+ {
+
+ // Only interested in right-clicks while holding an item
+ if (!event.hasItem() || !(event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK))
+ {
+ return;
+ }
+
+ NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final InventoryData data = InventoryCheck.getData(player);
+
+ if (event.getItem().getType() == Material.BOW)
+ {
+ // It was a bow, the player starts to pull the string
+ // Remember this time
+ data.lastBowInteractTime = System.currentTimeMillis();
+ }
+ else if (CheckUtil.isFood(event.getItem()))
+ {
+ // It was food, the player starts to eat some food
+ // Remember this time and the type of food
+ data.foodMaterial = event.getItem().getType();
+ data.lastEatInteractTime = System.currentTimeMillis();
+ }
+ else
+ {
+ // Nothing that we are interested in, reset data
+ data.lastBowInteractTime = 0;
+ data.lastEatInteractTime = 0;
+ data.foodMaterial = null;
+ }
+ }
+
+ /**
+ * We listen to FoodLevelChange Event because Bukkit doesn't provide a PlayerFoodEating Event (or whatever it would
+ * be called).
+ *
+ * @param event The FoodLevelChangeEvent
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void foodchanged(final FoodLevelChangeEvent event)
+ {
+ // Only if a player ate food
+ if (!event.isCancelled() && event.getEntity() instanceof Player)
+ {
+ final NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
+ final InventoryConfig cc = InventoryCheck.getConfig(player);
+ final InventoryData data = InventoryCheck.getData(player);
+
+ // Only if he should get checked
+ if (cc.eatCheck && !player.hasPermission(Permissions.INVENTORY_INSTANTEAT))
+ {
+
+ boolean cancelled = instantEatCheck.check(player, event, data, cc);
+
+ // The check requested the foodlevelchange to get cancelled
+ event.setCancelled(cancelled);
+ }
+
+ // Forget the food material, as the info is no longer needed
+ data.foodMaterial = null;
+ }
+
+ }
+
+ /**
+ * We listen to EntityShootBowEvent for the instantbow check
+ *
+ * @param event The EntityShootBowEvent
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void bowfired(final EntityShootBowEvent event)
+ {
+ // Only if a player shot the arrow
+ if (!event.isCancelled() && event.getEntity() instanceof Player)
+ {
+ final NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
+ final InventoryConfig cc = InventoryCheck.getConfig(player);
+
+ // Only if he should get checked
+ if (cc.bowCheck && !player.hasPermission(Permissions.INVENTORY_INSTANTBOW))
+ {
+ final InventoryData data = InventoryCheck.getData(player);
+ boolean cancelled = instantBowCheck.check(player, event, data, cc);
+
+ // The check requested the bowshooting to get cancelled
+ event.setCancelled(cancelled);
+ }
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ InventoryConfig i = InventoryCheck.getConfig(cc);
+ if (i.dropCheck)
+ {
+ s.add("inventory.dropCheck");
+ }
+ if (i.bowCheck)
+ {
+ s.add("inventory.instantbow");
+ }
+ if (i.eatCheck)
+ {
+ s.add("inventory.instanteat");
+ }
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java
new file mode 100644
index 000000000..44f59ff04
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java
@@ -0,0 +1,40 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+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 "Inventory" 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 InventoryConfig implements ConfigItem
+{
+ public final boolean dropCheck;
+ public final long dropTimeFrame;
+ public final int dropLimit;
+ public final ActionList dropActions;
+ public final boolean bowCheck;
+ public final ActionList bowActions;
+ public final boolean eatCheck;
+ public final ActionList eatActions;
+
+ public InventoryConfig(NoCheatConfiguration data)
+ {
+
+ dropCheck = data.getBoolean(ConfPaths.INVENTORY_DROP_CHECK);
+ dropTimeFrame = data.getInt(ConfPaths.INVENTORY_DROP_TIMEFRAME) * 1000;
+ dropLimit = data.getInt(ConfPaths.INVENTORY_DROP_LIMIT);
+ dropActions = data.getActionList(ConfPaths.INVENTORY_DROP_ACTIONS, Permissions.INVENTORY_DROP);
+
+ bowCheck = data.getBoolean(ConfPaths.INVENTORY_INSTANTBOW_CHECK);
+ bowActions = data.getActionList(ConfPaths.INVENTORY_INSTANTBOW_ACTIONS, Permissions.INVENTORY_INSTANTBOW);
+
+ eatCheck = data.getBoolean(ConfPaths.INVENTORY_INSTANTEAT_CHECK);
+ eatActions = data.getActionList(ConfPaths.INVENTORY_INSTANTEAT_ACTIONS, Permissions.INVENTORY_INSTANTEAT);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java
new file mode 100644
index 000000000..daeef8679
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java
@@ -0,0 +1,25 @@
+package com.earth2me.essentials.anticheat.checks.inventory;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import org.bukkit.Material;
+
+
+/**
+ * Player specific data for the inventory checks
+ *
+ */
+public class InventoryData implements DataItem
+{
+ // Keep track of the violation levels of the three checks
+ public int dropVL;
+ public int instantBowVL;
+ public double instantEatVL;
+ // Time and amount of dropped items
+ public long dropLastTime;
+ public int dropCount;
+ // Times when bow shootinhg and eating started
+ public long lastBowInteractTime;
+ public long lastEatInteractTime;
+ // What the player is eating
+ public Material foodMaterial;
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java
new file mode 100644
index 000000000..c96d9f9c3
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java
@@ -0,0 +1,170 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+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.PreciseLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * A check designed for people that are allowed to fly. The complement to the "RunningCheck", which is for people that
+ * aren't allowed to fly, and therefore have tighter rules to obey.
+ *
+ */
+public class FlyingCheck extends MovingCheck
+{
+ public FlyingCheck(NoCheat plugin)
+ {
+ super(plugin, "moving.flying");
+ }
+ // Determined by trial and error, the flying movement speed of the creative
+ // mode
+ private static final double creativeSpeed = 0.60D;
+
+ public PreciseLocation check(NoCheatPlayer player, MovingData data, MovingConfig ccmoving)
+ {
+
+ // The setBack is the location that players may get teleported to when
+ // they fail the check
+ final PreciseLocation setBack = data.runflySetBackPoint;
+
+ final PreciseLocation from = data.from;
+ final PreciseLocation to = data.to;
+
+ // If we have no setback, define one now
+ if (!setBack.isSet())
+ {
+ setBack.set(from);
+ }
+
+ // Used to store the location where the player gets teleported to
+ PreciseLocation newToLocation = null;
+
+ // Before doing anything, do a basic height check to determine if
+ // players are flying too high
+ int maxheight = ccmoving.flyingHeightLimit + player.getPlayer().getWorld().getMaxHeight();
+
+ if (to.y - data.vertFreedom > maxheight)
+ {
+ newToLocation = new PreciseLocation();
+ newToLocation.set(setBack);
+ newToLocation.y = maxheight - 10;
+ return newToLocation;
+ }
+
+ // Calculate some distances
+ final double yDistance = to.y - from.y;
+ final double xDistance = to.x - from.x;
+ final double zDistance = to.z - from.z;
+
+ // How far did the player move horizontally
+ final double horizontalDistance = Math.sqrt((xDistance * xDistance + zDistance * zDistance));
+
+ double resultHoriz = 0;
+ double resultVert = 0;
+ double result = 0;
+
+ // In case of creative game mode give at least 0.60 speed limit horizontal
+ double speedLimitHorizontal = player.isCreative() ? Math.max(creativeSpeed, ccmoving.flyingSpeedLimitHorizontal) : ccmoving.flyingSpeedLimitHorizontal;
+
+ // If the player is affected by potion of swiftness
+ speedLimitHorizontal *= player.getSpeedAmplifier();
+
+ // Finally, determine how far the player went beyond the set limits
+ resultHoriz = Math.max(0.0D, horizontalDistance - data.horizFreedom - speedLimitHorizontal);
+
+ boolean sprinting = player.isSprinting();
+
+ data.bunnyhopdelay--;
+
+ if (resultHoriz > 0 && sprinting)
+ {
+
+ // Try to treat it as a the "bunnyhop" problem
+ // The bunnyhop problem is that landing and immediatly jumping
+ // again leads to a player moving almost twice as far in that step
+ if (data.bunnyhopdelay <= 0 && resultHoriz < 0.4D)
+ {
+ data.bunnyhopdelay = 9;
+ resultHoriz = 0;
+ }
+ }
+
+ resultHoriz *= 100;
+
+ // Is the player affected by the "jumping" potion
+ // This is really just a very, very crude estimation and far from
+ // reality
+ double jumpAmplifier = player.getJumpAmplifier();
+ if (jumpAmplifier > data.lastJumpAmplifier)
+ {
+ data.lastJumpAmplifier = jumpAmplifier;
+ }
+
+ double speedLimitVertical = ccmoving.flyingSpeedLimitVertical * data.lastJumpAmplifier;
+
+ if (data.from.y >= data.to.y && data.lastJumpAmplifier > 0)
+ {
+ data.lastJumpAmplifier--;
+ }
+
+ // super simple, just check distance compared to max distance vertical
+ resultVert = Math.max(0.0D, yDistance - data.vertFreedom - speedLimitVertical) * 100;
+
+ result = resultHoriz + resultVert;
+
+ // The player went to far, either horizontal or vertical
+ if (result > 0)
+ {
+
+ // Increment violation counter and statistics
+ data.runflyVL += result;
+ if (resultHoriz > 0)
+ {
+ incrementStatistics(player, Id.MOV_RUNNING, resultHoriz);
+ }
+
+ if (resultVert > 0)
+ {
+ incrementStatistics(player, Id.MOV_FLYING, resultVert);
+ }
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ boolean cancel = executeActions(player, ccmoving.flyingActions, data.runflyVL);
+
+ // Was one of the actions a cancel? Then really do it
+ if (cancel)
+ {
+ newToLocation = setBack;
+ }
+ }
+
+ // Slowly reduce the violation level with each event
+ data.runflyVL *= 0.97;
+
+ // If the player did not get cancelled, define a new setback point
+ if (newToLocation == null)
+ {
+ setBack.set(to);
+ }
+
+ return newToLocation;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).runflyVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java
new file mode 100644
index 000000000..d5ae30340
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java
@@ -0,0 +1,132 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+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.PreciseLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+
+
+/**
+ * The morePacketsCheck (previously called SpeedhackCheck) will try to identify players that send more than the usual
+ * amount of move-packets to the server to be able to move faster than normal, without getting caught by the other
+ * checks (flying/running).
+ *
+ * It monitors the number of packets sent to the server within 1 second and compares it to the "legal" number of packets
+ * for that timeframe (22).
+ *
+ */
+public class MorePacketsCheck extends MovingCheck
+{
+ // 20 would be for perfect internet connections, 22 is good enough
+ private final static int packetsPerTimeframe = 22;
+
+ public MorePacketsCheck(NoCheat plugin)
+ {
+ super(plugin, "moving.morepackets");
+ }
+
+ /**
+ * 1. Players get assigned a certain amount of "free" packets as a limit initially 2. Every move packet reduces that
+ * limit by 1 3. If more than 1 second of time passed, the limit gets increased by 22 * time in seconds, up to 50
+ * and he gets a new "setback" location 4. If the player reaches limit = 0 -> teleport him back to "setback" 5. If
+ * there was a long pause (maybe lag), limit may be up to 100
+ *
+ */
+ public PreciseLocation check(NoCheatPlayer player, MovingData data, MovingConfig cc)
+ {
+
+ PreciseLocation newToLocation = null;
+
+ if (!data.morePacketsSetbackPoint.isSet())
+ {
+ data.morePacketsSetbackPoint.set(data.from);
+ }
+
+ long time = System.currentTimeMillis();
+
+ // Take a packet from the buffer
+ data.morePacketsBuffer--;
+
+ // Player used up buffer, he fails the check
+ if (data.morePacketsBuffer < 0)
+ {
+
+ data.morePacketsVL = -data.morePacketsBuffer;
+ incrementStatistics(player, Id.MOV_MOREPACKETS, 1);
+
+ data.packets = -data.morePacketsBuffer;
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ final boolean cancel = executeActions(player, cc.morePacketsActions, data.morePacketsVL);
+
+ if (cancel)
+ {
+ newToLocation = data.morePacketsSetbackPoint;
+ }
+ }
+
+ if (data.morePacketsLastTime + 1000 < time)
+ {
+ // More than 1 second elapsed, but how many?
+ double seconds = ((double)(time - data.morePacketsLastTime)) / 1000D;
+
+ // For each second, fill the buffer
+ data.morePacketsBuffer += packetsPerTimeframe * seconds;
+
+ // If there was a long pause (maybe server lag?)
+ // Allow buffer to grow up to 100
+ if (seconds > 2)
+ {
+ if (data.morePacketsBuffer > 100)
+ {
+ data.morePacketsBuffer = 100;
+ }
+ // Else only allow growth up to 50
+ }
+ else
+ {
+ if (data.morePacketsBuffer > 50)
+ {
+ data.morePacketsBuffer = 50;
+ }
+ }
+
+ // Set the new "last" time
+ data.morePacketsLastTime = time;
+
+ // Set the new "setback" location
+ if (newToLocation == null)
+ {
+ data.morePacketsSetbackPoint.set(data.from);
+ }
+ }
+ else if (data.morePacketsLastTime > time)
+ {
+ // Security check, maybe system time changed
+ data.morePacketsLastTime = time;
+ }
+
+ return newToLocation;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).morePacketsVL);
+ }
+ else if (wildcard == ParameterName.PACKETS)
+ {
+ return String.format(Locale.US, "%d", getData(player).packets);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java
new file mode 100644
index 000000000..fed104130
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java
@@ -0,0 +1,93 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+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.Check;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import java.util.Locale;
+
+
+/**
+ * Abstract base class for Moving checks, provides some convenience methods for access to data and config that's
+ * relevant to this checktype
+ */
+public abstract class MovingCheck extends Check
+{
+ private static final String id = "moving";
+
+ public MovingCheck(NoCheat plugin, String name)
+ {
+ super(plugin, id, name);
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.LOCATION)
+ {
+ PreciseLocation from = getData(player).from;
+ return String.format(Locale.US, "%.2f,%.2f,%.2f", from.x, from.y, from.z);
+ }
+ else if (wildcard == ParameterName.MOVEDISTANCE)
+ {
+ PreciseLocation from = getData(player).from;
+ PreciseLocation to = getData(player).to;
+ return String.format(Locale.US, "%.2f,%.2f,%.2f", to.x - from.x, to.y - from.y, to.z - from.z);
+ }
+ else if (wildcard == ParameterName.LOCATION_TO)
+ {
+ PreciseLocation to = getData(player).to;
+ return String.format(Locale.US, "%.2f,%.2f,%.2f", to.x, to.y, to.z);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+
+ }
+
+ /**
+ * Get the "MovingData" object that belongs to the player. Will ensure that such a object exists and if not, create
+ * one
+ *
+ * @param player
+ * @return
+ */
+ public static MovingData getData(NoCheatPlayer player)
+ {
+ DataStore base = player.getDataStore();
+ MovingData data = base.get(id);
+ if (data == null)
+ {
+ data = new MovingData();
+ base.set(id, data);
+ }
+ return data;
+ }
+
+ /**
+ * Get the MovingConfig object that belongs to the world that the player currently resides in.
+ *
+ * @param player
+ * @return
+ */
+ public static MovingConfig getConfig(NoCheatPlayer player)
+ {
+ return getConfig(player.getConfigurationStore());
+ }
+
+ public static MovingConfig getConfig(ConfigurationCacheStore cache)
+ {
+ MovingConfig config = cache.get(id);
+ if (config == null)
+ {
+ config = new MovingConfig(cache.getConfiguration());
+ cache.set(id, config);
+ }
+ return config;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java
new file mode 100644
index 000000000..3b35e0c33
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java
@@ -0,0 +1,376 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.checks.CheckUtil;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import java.util.LinkedList;
+import java.util.List;
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockPlaceEvent;
+import org.bukkit.event.player.*;
+import org.bukkit.util.Vector;
+
+
+/**
+ * Central location to listen to events that are relevant for the moving checks
+ *
+ */
+public class MovingCheckListener implements Listener, EventManager
+{
+ private final MorePacketsCheck morePacketsCheck;
+ private final FlyingCheck flyingCheck;
+ private final RunningCheck runningCheck;
+ private final NoCheat plugin;
+
+ public MovingCheckListener(NoCheat plugin)
+ {
+
+ flyingCheck = new FlyingCheck(plugin);
+ runningCheck = new RunningCheck(plugin);
+ morePacketsCheck = new MorePacketsCheck(plugin);
+
+ this.plugin = plugin;
+ }
+
+ /**
+ * A workaround for players placing blocks below them getting pushed off the block by NoCheat.
+ *
+ * It essentially moves the "setbackpoint" to the top of the newly placed block, therefore tricking NoCheat into
+ * thinking the player was already on top of that block and should be allowed to stay there
+ *
+ * @param event The BlockPlaceEvent
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void blockPlace(final BlockPlaceEvent event)
+ {
+
+ // Block wasn't placed, so we don't care
+ if (event.isCancelled())
+ {
+ return;
+ }
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final MovingConfig config = MovingCheck.getConfig(player);
+
+ // If the player is allowed to fly anyway, the workaround is not needed
+ // It's kind of expensive (looking up block types) therefore it makes
+ // sense to avoid it
+ if (config.allowFlying || !config.runflyCheck || player.hasPermission(Permissions.MOVING_FLYING) || player.hasPermission(Permissions.MOVING_RUNFLY))
+ {
+ return;
+ }
+
+ // Get the player-specific stored data that applies here
+ final MovingData data = MovingCheck.getData(player);
+
+ final Block block = event.getBlockPlaced();
+
+ if (block == null || !data.runflySetBackPoint.isSet())
+ {
+ return;
+ }
+
+ // Keep some results of "expensive calls
+ final Location l = player.getPlayer().getLocation();
+ final int playerX = l.getBlockX();
+ final int playerY = l.getBlockY();
+ final int playerZ = l.getBlockZ();
+ final int blockY = block.getY();
+
+ // Was the block below the player?
+ if (Math.abs(playerX - block.getX()) <= 1 && Math.abs(playerZ - block.getZ()) <= 1 && playerY - blockY >= 0 && playerY - blockY <= 2)
+ {
+ // yes
+ final int type = CheckUtil.getType(block.getTypeId());
+ if (CheckUtil.isSolid(type) || CheckUtil.isLiquid(type))
+ {
+ if (blockY + 1 >= data.runflySetBackPoint.y)
+ {
+ data.runflySetBackPoint.y = (blockY + 1);
+ data.jumpPhase = 0;
+ }
+ }
+ }
+ }
+
+ /**
+ * If a player gets teleported, it may have two reasons. Either it was NoCheat or another plugin. If it was NoCheat,
+ * the target location should match the "data.teleportTo" value.
+ *
+ * On teleports, reset some movement related data that gets invalid
+ *
+ * @param event The PlayerTeleportEvent
+ */
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void teleport(final PlayerTeleportEvent event)
+ {
+
+ NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+ final MovingData data = MovingCheck.getData(player);
+
+ // If it was a teleport initialized by NoCheat, do it anyway
+ // even if another plugin said "no"
+ if (data.teleportTo.isSet() && data.teleportTo.equals(event.getTo()))
+ {
+ event.setCancelled(false);
+ }
+ else
+ {
+ // Only if it wasn't NoCheat, drop data from morepackets check.
+ // If it was NoCheat, we don't want players to exploit the
+ // runfly check teleporting to get rid of the "morepackets"
+ // data.
+ data.clearMorePacketsData();
+ }
+
+ // Always drop data from runfly check, as it always loses its validity
+ // after teleports. Always!
+ data.teleportTo.reset();
+ data.clearRunFlyData();
+
+ return;
+ }
+
+ /**
+ * Just for security, if a player switches between worlds, reset the runfly and morepackets checks data, because it
+ * is definitely invalid now
+ *
+ * @param event The PlayerChangedWorldEvent
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void worldChange(final PlayerChangedWorldEvent event)
+ {
+ // Maybe this helps with people teleporting through multiverse portals having problems?
+ final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
+ data.teleportTo.reset();
+ data.clearRunFlyData();
+ data.clearMorePacketsData();
+ }
+
+ /**
+ * When a player uses a portal, all information related to the moving checks becomes invalid.
+ *
+ * @param event
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void portal(final PlayerPortalEvent event)
+ {
+ final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
+ data.clearMorePacketsData();
+ data.clearRunFlyData();
+ }
+
+ /**
+ * When a player respawns, all information related to the moving checks becomes invalid.
+ *
+ * @param event
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void respawn(final PlayerRespawnEvent event)
+ {
+ final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
+ data.clearMorePacketsData();
+ data.clearRunFlyData();
+ }
+
+ /**
+ * When a player moves, he will be checked for various suspicious behaviour.
+ *
+ * @param event The PlayerMoveEvent
+ */
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void move(final PlayerMoveEvent event)
+ {
+
+ // Don't care for vehicles
+ if (event.isCancelled() || event.getPlayer().isInsideVehicle())
+ {
+ return;
+ }
+
+ // Don't care for movements that are very high distance or to another
+ // world (such that it is very likely the event data was modified by
+ // another plugin before we got it)
+ if (!event.getFrom().getWorld().equals(event.getTo().getWorld()) || event.getFrom().distanceSquared(event.getTo()) > 400)
+ {
+ return;
+ }
+
+ final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
+
+ final MovingConfig cc = MovingCheck.getConfig(player);
+ final MovingData data = MovingCheck.getData(player);
+
+ // Advance various counters and values that change per movement
+ // tick. They are needed to decide on how fast a player may
+ // move.
+ tickVelocities(data);
+
+ // Remember locations
+ data.from.set(event.getFrom());
+ final Location to = event.getTo();
+ data.to.set(to);
+
+ PreciseLocation newTo = null;
+
+ /**
+ * RUNFLY CHECK SECTION *
+ */
+ // If the player isn't handled by runfly checks
+ if (!cc.runflyCheck || player.hasPermission(Permissions.MOVING_RUNFLY))
+ {
+ // Just because he is allowed now, doesn't mean he will always
+ // be. So forget data about the player related to moving
+ data.clearRunFlyData();
+ }
+ else if (cc.allowFlying || (player.isCreative() && cc.identifyCreativeMode) || player.hasPermission(Permissions.MOVING_FLYING))
+ {
+ // Only do the limited flying check
+ newTo = flyingCheck.check(player, data, cc);
+ }
+ else
+ {
+ // Go for the full treatment
+ newTo = runningCheck.check(player, data, cc);
+ }
+
+ /**
+ * MOREPACKETS CHECK SECTION *
+ */
+ if (!cc.morePacketsCheck || player.hasPermission(Permissions.MOVING_MOREPACKETS))
+ {
+ data.clearMorePacketsData();
+ }
+ else if (newTo == null)
+ {
+ newTo = morePacketsCheck.check(player, data, cc);
+ }
+
+ // Did one of the check(s) decide we need a new "to"-location?
+ if (newTo != null)
+ {
+ // Compose a new location based on coordinates of "newTo" and
+ // viewing direction of "event.getTo()" to allow the player to
+ // look somewhere else despite getting pulled back by NoCheat
+ event.setTo(new Location(player.getPlayer().getWorld(), newTo.x, newTo.y, newTo.z, to.getYaw(), to.getPitch()));
+
+ // remember where we send the player to
+ data.teleportTo.set(newTo);
+ }
+ }
+
+ /**
+ * Just try to estimate velocities over time Not very precise, but works good enough most of the time.
+ *
+ * @param data
+ */
+ private void tickVelocities(MovingData data)
+ {
+
+ /**
+ * ****** DO GENERAL DATA MODIFICATIONS ONCE FOR EACH EVENT ****
+ */
+ if (data.horizVelocityCounter > 0)
+ {
+ data.horizVelocityCounter--;
+ }
+ else if (data.horizFreedom > 0.001)
+ {
+ data.horizFreedom *= 0.90;
+ }
+
+ if (data.vertVelocity <= 0.1)
+ {
+ data.vertVelocityCounter--;
+ }
+ if (data.vertVelocityCounter > 0)
+ {
+ data.vertFreedom += data.vertVelocity;
+ data.vertVelocity *= 0.90;
+ }
+ else if (data.vertFreedom > 0.001)
+ {
+ // Counter has run out, now reduce the vert freedom over time
+ data.vertFreedom *= 0.93;
+ }
+ }
+
+ /**
+ * Player got a velocity packet. The server can't keep track of actual velocity values (by design), so we have to
+ * try and do that ourselves. Very rough estimates.
+ *
+ * @param event The PlayerVelocityEvent
+ */
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void velocity(final PlayerVelocityEvent event)
+ {
+ if (event.isCancelled())
+ {
+ return;
+ }
+
+ final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
+
+ final Vector v = event.getVelocity();
+
+ double newVal = v.getY();
+ if (newVal >= 0.0D)
+ {
+ data.vertVelocity += newVal;
+ data.vertFreedom += data.vertVelocity;
+ }
+
+ data.vertVelocityCounter = 50;
+
+ newVal = Math.sqrt(Math.pow(v.getX(), 2) + Math.pow(v.getZ(), 2));
+ if (newVal > 0.0D)
+ {
+ data.horizFreedom += newVal;
+ data.horizVelocityCounter = 30;
+ }
+ }
+
+ public List<String> getActiveChecks(ConfigurationCacheStore cc)
+ {
+ LinkedList<String> s = new LinkedList<String>();
+
+ MovingConfig m = MovingCheck.getConfig(cc);
+
+ if (m.runflyCheck)
+ {
+
+ if (!m.allowFlying)
+ {
+ s.add("moving.runfly");
+ if (m.sneakingCheck)
+ {
+ s.add("moving.sneaking");
+ }
+ if (m.nofallCheck)
+ {
+ s.add("moving.nofall");
+ }
+ }
+ else
+ {
+ s.add("moving.flying");
+ }
+
+ }
+ if (m.morePacketsCheck)
+ {
+ s.add("moving.morepackets");
+ }
+
+ return s;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java
new file mode 100644
index 000000000..54e2834a4
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java
@@ -0,0 +1,71 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+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 Move Checks. Every world gets one of these assigned to it.
+ *
+ */
+public class MovingConfig implements ConfigItem
+{
+ public final boolean runflyCheck;
+ public final boolean identifyCreativeMode;
+ public final double walkingSpeedLimit;
+ public final double sprintingSpeedLimit;
+ public final double jumpheight;
+ public final double swimmingSpeedLimit;
+ public final boolean sneakingCheck;
+ public final double sneakingSpeedLimit;
+ public final ActionList actions;
+ public final boolean allowFlying;
+ public final double flyingSpeedLimitVertical;
+ public final double flyingSpeedLimitHorizontal;
+ public final ActionList flyingActions;
+ public final boolean nofallCheck;
+ public final boolean nofallaggressive;
+ public final float nofallMultiplier;
+ public final ActionList nofallActions;
+ public final boolean morePacketsCheck;
+ public final ActionList morePacketsActions;
+ public final int flyingHeightLimit;
+
+ public MovingConfig(NoCheatConfiguration data)
+ {
+
+ identifyCreativeMode = data.getBoolean(ConfPaths.MOVING_RUNFLY_FLYING_ALLOWINCREATIVE);
+
+ runflyCheck = data.getBoolean(ConfPaths.MOVING_RUNFLY_CHECK);
+
+ int walkspeed = data.getInt(ConfPaths.MOVING_RUNFLY_WALKSPEED, 100);
+ int sprintspeed = data.getInt(ConfPaths.MOVING_RUNFLY_SPRINTSPEED, 100);
+ int swimspeed = data.getInt(ConfPaths.MOVING_RUNFLY_SWIMSPEED, 100);
+ int sneakspeed = data.getInt(ConfPaths.MOVING_RUNFLY_SNEAKSPEED, 100);
+ walkingSpeedLimit = (0.22 * walkspeed) / 100D;
+ sprintingSpeedLimit = (0.35 * sprintspeed) / 100D;
+ swimmingSpeedLimit = (0.18 * swimspeed) / 100D;
+ sneakingSpeedLimit = (0.14 * sneakspeed) / 100D;
+ jumpheight = ((double)135) / 100D;
+
+ sneakingCheck = !data.getBoolean(ConfPaths.MOVING_RUNFLY_ALLOWFASTSNEAKING);
+ actions = data.getActionList(ConfPaths.MOVING_RUNFLY_ACTIONS, Permissions.MOVING_RUNFLY);
+
+ allowFlying = data.getBoolean(ConfPaths.MOVING_RUNFLY_FLYING_ALLOWALWAYS);
+ flyingSpeedLimitVertical = ((double)data.getInt(ConfPaths.MOVING_RUNFLY_FLYING_SPEEDLIMITVERTICAL)) / 100D;
+ flyingSpeedLimitHorizontal = ((double)data.getInt(ConfPaths.MOVING_RUNFLY_FLYING_SPEEDLIMITHORIZONTAL)) / 100D;
+ flyingHeightLimit = data.getInt(ConfPaths.MOVING_RUNFLY_FLYING_HEIGHTLIMIT);
+ flyingActions = data.getActionList(ConfPaths.MOVING_RUNFLY_FLYING_ACTIONS, Permissions.MOVING_FLYING);
+
+ nofallCheck = data.getBoolean(ConfPaths.MOVING_RUNFLY_CHECKNOFALL);
+ nofallMultiplier = ((float)200) / 100F;
+ nofallaggressive = data.getBoolean(ConfPaths.MOVING_RUNFLY_NOFALLAGGRESSIVE);
+ nofallActions = data.getActionList(ConfPaths.MOVING_RUNFLY_NOFALLACTIONS, Permissions.MOVING_NOFALL);
+
+ morePacketsCheck = data.getBoolean(ConfPaths.MOVING_MOREPACKETS_CHECK);
+ morePacketsActions = data.getActionList(ConfPaths.MOVING_MOREPACKETS_ACTIONS, Permissions.MOVING_MOREPACKETS);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java
new file mode 100644
index 000000000..e57a59d29
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java
@@ -0,0 +1,69 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+
+
+/**
+ * Player specific data for the moving check group
+ */
+public class MovingData implements DataItem
+{
+ // Keep track of the violation levels of the checks
+ public double runflyVL;
+ public double nofallVL;
+ public double morePacketsVL;
+ // Count how long a player is in the air
+ public int jumpPhase;
+ // Remember how big the players last JumpAmplifier (potion effect) was
+ public double lastJumpAmplifier;
+ // Remember for a short time that the player was on ice and therefore
+ // should be allowed to move a bit faster
+ public int onIce;
+ // Where should a player be teleported back to when failing the check
+ public final PreciseLocation runflySetBackPoint = new PreciseLocation();
+ // Some values for estimating movement freedom
+ public double vertFreedom;
+ public double vertVelocity;
+ public int vertVelocityCounter;
+ public double horizFreedom;
+ public int horizVelocityCounter;
+ public double horizontalBuffer;
+ public int bunnyhopdelay;
+ // Keep track of estimated fall distance to compare to real fall distance
+ public float fallDistance;
+ public float lastAddedFallDistance;
+ // Keep track of when "morePackets" last time checked and how much packets
+ // a player sent and may send before failing the check
+ public long morePacketsLastTime;
+ public int packets;
+ public int morePacketsBuffer = 50;
+ // Where to teleport the player that fails the "morepackets" check
+ public final PreciseLocation morePacketsSetbackPoint = new PreciseLocation();
+ // When NoCheat does teleport the player, remember the target location to
+ // be able to distinguish "our" teleports from teleports of others
+ public final PreciseLocation teleportTo = new PreciseLocation();
+ // For logging and convenience, make copies of the events locations
+ public final PreciseLocation from = new PreciseLocation();
+ public final PreciseLocation to = new PreciseLocation();
+ // For convenience, remember if the locations are considered "on ground"
+ // by NoCheat
+ public boolean fromOnOrInGround;
+ public boolean toOnOrInGround;
+ public Id statisticCategory = Id.MOV_RUNNING;
+
+ public void clearRunFlyData()
+ {
+ runflySetBackPoint.reset();
+ jumpPhase = 0;
+ fallDistance = 0;
+ lastAddedFallDistance = 0;
+ bunnyhopdelay = 0;
+ }
+
+ public void clearMorePacketsData()
+ {
+ morePacketsSetbackPoint.reset();
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java
new file mode 100644
index 000000000..6a531e3c2
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java
@@ -0,0 +1,151 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+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;
+import java.util.Locale;
+
+
+/**
+ * A check to see if people cheat by tricking the server to not deal them fall damage.
+ *
+ */
+public class NoFallCheck extends MovingCheck
+{
+ public NoFallCheck(NoCheat plugin)
+ {
+ super(plugin, "moving.nofall");
+ }
+
+ /**
+ * Calculate if and how much the player "failed" this check.
+ *
+ */
+ public void check(NoCheatPlayer player, MovingData data, MovingConfig cc)
+ {
+
+ // If the player is serverside in creative mode, we have to stop here to
+ // avoid hurting him when he switches back to "normal" mode
+ if (player.isCreative())
+ {
+ data.fallDistance = 0F;
+ data.lastAddedFallDistance = 0F;
+ return;
+ }
+
+ // This check is pretty much always a step behind for technical reasons.
+ if (data.fromOnOrInGround)
+ {
+ // Start with zero fall distance
+ data.fallDistance = 0F;
+ }
+
+ if (cc.nofallaggressive && data.fromOnOrInGround && data.toOnOrInGround && data.from.y <= data.to.y && player.getPlayer().getFallDistance() > 3.0F)
+ {
+ data.fallDistance = player.getPlayer().getFallDistance();
+ data.nofallVL += data.fallDistance;
+ incrementStatistics(player, Id.MOV_NOFALL, data.fallDistance);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ final boolean cancel = executeActions(player, cc.nofallActions, data.nofallVL);
+ if (cancel)
+ {
+ player.dealFallDamage();
+ }
+ data.fallDistance = 0F;
+ }
+
+ // If we increased fall height before for no good reason, reduce now by
+ // the same amount
+ if (player.getPlayer().getFallDistance() > data.lastAddedFallDistance)
+ {
+ player.getPlayer().setFallDistance(player.getPlayer().getFallDistance() - data.lastAddedFallDistance);
+ }
+
+ data.lastAddedFallDistance = 0;
+
+ // We want to know if the fallDistance recorded by the game is smaller
+ // than the fall distance recorded by the plugin
+ final float difference = data.fallDistance - player.getPlayer().getFallDistance();
+
+ if (difference > 1.0F && data.toOnOrInGround && data.fallDistance > 2.0F)
+ {
+ data.nofallVL += difference;
+ incrementStatistics(player, Id.MOV_NOFALL, difference);
+
+ // Execute whatever actions are associated with this check and the
+ // violation level and find out if we should cancel the event
+ final boolean cancel = executeActions(player, cc.nofallActions, data.nofallVL);
+
+ // If "cancelled", the fall damage gets dealt in a way that's
+ // visible to other plugins
+ if (cancel)
+ {
+ // Increase the fall distance a bit :)
+ final float totalDistance = data.fallDistance + difference * (cc.nofallMultiplier - 1.0F);
+
+ player.getPlayer().setFallDistance(totalDistance);
+ }
+
+ data.fallDistance = 0F;
+ }
+
+ // Increase the fall distance that is recorded by the plugin, AND set
+ // the fall distance of the player
+ // to whatever he would get with this move event. This modifies
+ // Minecrafts fall damage calculation
+ // slightly, but that's still better than ignoring players that try to
+ // use "teleports" or "stepdown"
+ // to avoid falldamage. It is only added for big height differences
+ // anyway, as to avoid to much deviation
+ // from the original Minecraft feeling.
+
+ final double oldY = data.from.y;
+ final double newY = data.to.y;
+
+ if (oldY > newY)
+ {
+ final float dist = (float)(oldY - newY);
+ data.fallDistance += dist;
+
+ if (dist > 1.0F)
+ {
+ data.lastAddedFallDistance = dist;
+ player.getPlayer().setFallDistance(player.getPlayer().getFallDistance() + dist);
+ }
+ else
+ {
+ data.lastAddedFallDistance = 0.0F;
+ }
+ }
+ else
+ {
+ data.lastAddedFallDistance = 0.0F;
+ }
+
+ // Reduce falldamage violation level
+ data.nofallVL *= 0.95D;
+
+ return;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).nofallVL);
+ }
+ else if (wildcard == ParameterName.FALLDISTANCE)
+ {
+ return String.format(Locale.US, "%.2f", getData(player).fallDistance);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java
new file mode 100644
index 000000000..bb5444be9
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java
@@ -0,0 +1,303 @@
+package com.earth2me.essentials.anticheat.checks.moving;
+
+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.config.Permissions;
+import com.earth2me.essentials.anticheat.data.PreciseLocation;
+import com.earth2me.essentials.anticheat.data.Statistics.Id;
+import java.util.Locale;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+
+
+/**
+ * The counterpart to the FlyingCheck. People that are not allowed to fly get checked by this. It will try to identify
+ * when they are jumping, check if they aren't jumping too high or far, check if they aren't moving too fast on normal
+ * ground, while sprinting, sneaking or swimming.
+ *
+ */
+public class RunningCheck extends MovingCheck
+{
+ private final static double maxBonus = 1D;
+ // How many move events can a player have in air before he is expected to
+ // lose altitude (or eventually land somewhere)
+ private final static int jumpingLimit = 6;
+ private final NoFallCheck noFallCheck;
+
+ public RunningCheck(NoCheat plugin)
+ {
+
+ super(plugin, "moving.running");
+
+ this.noFallCheck = new NoFallCheck(plugin);
+ }
+
+ public PreciseLocation check(NoCheatPlayer player, MovingData data, MovingConfig cc)
+ {
+
+ // Some shortcuts:
+ final PreciseLocation setBack = data.runflySetBackPoint;
+ final PreciseLocation to = data.to;
+ final PreciseLocation from = data.from;
+
+ // Calculate some distances
+ final double xDistance = data.to.x - from.x;
+ final double zDistance = to.z - from.z;
+ final double horizontalDistance = Math.sqrt((xDistance * xDistance + zDistance * zDistance));
+
+ if (!setBack.isSet())
+ {
+ setBack.set(from);
+ }
+
+ // To know if a player "is on ground" is useful
+ final int fromType = CheckUtil.evaluateLocation(player.getPlayer().getWorld(), from);
+ final int toType = CheckUtil.evaluateLocation(player.getPlayer().getWorld(), to);
+
+ final boolean fromOnGround = CheckUtil.isOnGround(fromType);
+ final boolean fromInGround = CheckUtil.isInGround(fromType);
+ final boolean toOnGround = CheckUtil.isOnGround(toType);
+ final boolean toInGround = CheckUtil.isInGround(toType);
+
+ PreciseLocation newToLocation = null;
+
+ final double resultHoriz = Math.max(0.0D, checkHorizontal(player, data, CheckUtil.isLiquid(fromType) && CheckUtil.isLiquid(toType), horizontalDistance, cc));
+ final double resultVert = Math.max(0.0D, checkVertical(player, data, fromOnGround, toOnGround, cc));
+
+ final double result = (resultHoriz + resultVert) * 100;
+
+ data.jumpPhase++;
+
+ // Slowly reduce the level with each event
+ data.runflyVL *= 0.95;
+
+ // Did the player move in unexpected ways?
+ if (result > 0)
+ {
+ // Increment violation counter
+ data.runflyVL += result;
+
+ incrementStatistics(player, data.statisticCategory, result);
+
+ boolean cancel = executeActions(player, cc.actions, data.runflyVL);
+
+ // Was one of the actions a cancel? Then do it
+ if (cancel)
+ {
+ newToLocation = setBack;
+ }
+ else if (toOnGround || toInGround)
+ {
+ // In case it only gets logged, not stopped by NoCheat
+ // Update the setback location at least a bit
+ setBack.set(to);
+ data.jumpPhase = 0;
+
+ }
+ }
+ else
+ {
+ // Decide if we should create a new setBack point
+ // These are the result of a lot of bug reports, experience and
+ // trial and error
+
+ if ((toInGround && from.y >= to.y) || CheckUtil.isLiquid(toType))
+ {
+ // Yes, if the player moved down "into" the ground or into liquid
+ setBack.set(to);
+ setBack.y = Math.ceil(setBack.y);
+ data.jumpPhase = 0;
+ }
+ else if (toOnGround && (from.y >= to.y || setBack.y <= Math.floor(to.y)))
+ {
+ // Yes, if the player moved down "onto" the ground and the new
+ // setback point is higher up than the old or at least at the
+ // same height
+ setBack.set(to);
+ setBack.y = Math.floor(setBack.y);
+ data.jumpPhase = 0;
+ }
+ else if (fromOnGround || fromInGround || toOnGround || toInGround)
+ {
+ // The player at least touched the ground somehow
+ data.jumpPhase = 0;
+ }
+ }
+
+ /**
+ * ******* EXECUTE THE NOFALL CHECK *******************
+ */
+ final boolean checkNoFall = cc.nofallCheck && !player.hasPermission(Permissions.MOVING_NOFALL);
+
+ if (checkNoFall && newToLocation == null)
+ {
+ data.fromOnOrInGround = fromOnGround || fromInGround;
+ data.toOnOrInGround = toOnGround || toInGround;
+ noFallCheck.check(player, data, cc);
+ }
+
+ return newToLocation;
+ }
+
+ /**
+ * Calculate how much the player failed this check
+ *
+ */
+ private double checkHorizontal(final NoCheatPlayer player, final MovingData data, final boolean isSwimming, final double totalDistance, final MovingConfig cc)
+ {
+
+ // How much further did the player move than expected??
+ double distanceAboveLimit = 0.0D;
+
+ // A player is considered sprinting if the flag is set and if he has
+ // enough food level (configurable)
+ final boolean sprinting = player.isSprinting() && (player.getPlayer().getFoodLevel() > 5);
+
+ double limit = 0.0D;
+
+ Id statisticsCategory = null;
+
+ // Player on ice? Give him higher max speed
+ Block b = player.getPlayer().getLocation().getBlock();
+ if (b.getType() == Material.ICE || b.getRelative(0, -1, 0).getType() == Material.ICE)
+ {
+ data.onIce = 20;
+ }
+ else if (data.onIce > 0)
+ {
+ data.onIce--;
+ }
+
+ if (cc.sneakingCheck && player.getPlayer().isSneaking() && !player.hasPermission(Permissions.MOVING_SNEAKING))
+ {
+ limit = cc.sneakingSpeedLimit;
+ statisticsCategory = Id.MOV_SNEAKING;
+ }
+ else if (isSwimming && !player.hasPermission(Permissions.MOVING_SWIMMING))
+ {
+ limit = cc.swimmingSpeedLimit;
+ statisticsCategory = Id.MOV_SWIMMING;
+ }
+ else if (!sprinting)
+ {
+ limit = cc.walkingSpeedLimit;
+ statisticsCategory = Id.MOV_RUNNING;
+ }
+ else
+ {
+ limit = cc.sprintingSpeedLimit;
+ statisticsCategory = Id.MOV_RUNNING;
+ }
+
+ if (data.onIce > 0)
+ {
+ limit *= 2.5;
+ }
+
+ // Taken directly from Minecraft code, should work
+ limit *= player.getSpeedAmplifier();
+
+ distanceAboveLimit = totalDistance - limit - data.horizFreedom;
+
+ data.bunnyhopdelay--;
+
+ // Did he go too far?
+ if (distanceAboveLimit > 0 && sprinting)
+ {
+
+ // Try to treat it as a the "bunnyhop" problem
+ if (data.bunnyhopdelay <= 0 && distanceAboveLimit > 0.05D && distanceAboveLimit < 0.4D)
+ {
+ data.bunnyhopdelay = 9;
+ distanceAboveLimit = 0;
+ }
+ }
+
+ if (distanceAboveLimit > 0)
+ {
+ // Try to consume the "buffer"
+ distanceAboveLimit -= data.horizontalBuffer;
+ data.horizontalBuffer = 0;
+
+ // Put back the "overconsumed" buffer
+ if (distanceAboveLimit < 0)
+ {
+ data.horizontalBuffer = -distanceAboveLimit;
+ }
+ }
+ // He was within limits, give the difference as buffer
+ else
+ {
+ data.horizontalBuffer = Math.min(maxBonus, data.horizontalBuffer - distanceAboveLimit);
+ }
+
+ if (distanceAboveLimit > 0)
+ {
+ data.statisticCategory = statisticsCategory;
+ }
+
+ return distanceAboveLimit;
+ }
+
+ /**
+ * Calculate if and how much the player "failed" this check.
+ *
+ */
+ private double checkVertical(final NoCheatPlayer player, final MovingData data, final boolean fromOnGround, final boolean toOnGround, final MovingConfig cc)
+ {
+
+ // How much higher did the player move than expected??
+ double distanceAboveLimit = 0.0D;
+
+ // Potion effect "Jump"
+ double jumpAmplifier = player.getJumpAmplifier();
+ if (jumpAmplifier > data.lastJumpAmplifier)
+ {
+ data.lastJumpAmplifier = jumpAmplifier;
+ }
+
+ double limit = data.vertFreedom + cc.jumpheight;
+
+ limit *= data.lastJumpAmplifier;
+
+ if (data.jumpPhase > jumpingLimit + data.lastJumpAmplifier)
+ {
+ limit -= (data.jumpPhase - jumpingLimit) * 0.15D;
+ }
+
+ distanceAboveLimit = data.to.y - data.runflySetBackPoint.y - limit;
+
+ if (distanceAboveLimit > 0)
+ {
+ data.statisticCategory = Id.MOV_FLYING;
+ }
+
+ if (toOnGround || fromOnGround)
+ {
+ data.lastJumpAmplifier = 0;
+ }
+
+ return distanceAboveLimit;
+ }
+
+ @Override
+ public String getParameter(ParameterName wildcard, NoCheatPlayer player)
+ {
+
+ if (wildcard == ParameterName.CHECK)
+ // Workaround for something until I find a better way to do it
+ {
+ return getData(player).statisticCategory.toString();
+ }
+ else if (wildcard == ParameterName.VIOLATIONS)
+ {
+ return String.format(Locale.US, "%d", (int)getData(player).runflyVL);
+ }
+ else
+ {
+ return super.getParameter(wildcard, player);
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/command/CommandHandler.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/command/CommandHandler.java
new file mode 100644
index 000000000..075d64c73
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/command/CommandHandler.java
@@ -0,0 +1,163 @@
+package com.earth2me.essentials.anticheat.command;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.config.Permissions;
+import java.util.*;
+import java.util.Map.Entry;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.permissions.Permission;
+
+
+/**
+ * Handle all NoCheat related commands in a common place
+ */
+public class CommandHandler
+{
+ private final List<Permission> perms;
+
+ public CommandHandler(NoCheat plugin)
+ {
+ // Make a copy to allow sorting
+ perms = new LinkedList<Permission>(plugin.getDescription().getPermissions());
+
+ // Sort NoCheats permission by name and parent-child relation with
+ // a custom sorting method
+ Collections.sort(perms, new Comparator<Permission>()
+ {
+ public int compare(Permission o1, Permission o2)
+ {
+
+ String name1 = o1.getName();
+ String name2 = o2.getName();
+
+ if (name1.equals(name2))
+ {
+ return 0;
+ }
+
+ if (name1.startsWith(name2))
+ {
+ return 1;
+ }
+
+ if (name2.startsWith(name1))
+ {
+ return -1;
+ }
+
+ return name1.compareTo(name2);
+ }
+ });
+ }
+
+ /**
+ * Handle a command that is directed at NoCheat
+ *
+ * @param plugin
+ * @param sender
+ * @param command
+ * @param label
+ * @param args
+ * @return
+ */
+ public boolean handleCommand(NoCheat plugin, CommandSender sender, Command command, String label, String[] args)
+ {
+
+ boolean result = false;
+ // Not our command, how did it get here?
+ if (!command.getName().equalsIgnoreCase("nocheat") || args.length == 0)
+ {
+ result = false;
+ }
+ else if (args[0].equalsIgnoreCase("permlist") && args.length >= 2)
+ {
+ // permlist command was used
+ result = handlePermlistCommand(plugin, sender, args);
+
+ }
+ else if (args[0].equalsIgnoreCase("reload"))
+ {
+ // reload command was used
+ result = handleReloadCommand(plugin, sender);
+ }
+ else if (args[0].equalsIgnoreCase("playerinfo") && args.length >= 2)
+ {
+ // playerinfo command was used
+ result = handlePlayerInfoCommand(plugin, sender, args);
+ }
+
+ return result;
+ }
+
+ private boolean handlePlayerInfoCommand(NoCheat plugin, CommandSender sender, String[] args)
+ {
+
+ Map<String, Object> map = plugin.getPlayerData(args[1]);
+ String filter = "";
+
+ if (args.length > 2)
+ {
+ filter = args[2];
+ }
+
+ sender.sendMessage("PlayerInfo for " + args[1]);
+ for (Entry<String, Object> entry : map.entrySet())
+ {
+ if (entry.getKey().contains(filter))
+ {
+ sender.sendMessage(entry.getKey() + ": " + entry.getValue());
+ }
+ }
+ return true;
+ }
+
+ private boolean handlePermlistCommand(NoCheat plugin, CommandSender sender, String[] args)
+ {
+
+ // Get the player by name
+ Player player = plugin.getServer().getPlayerExact(args[1]);
+ if (player == null)
+ {
+ sender.sendMessage("Unknown player: " + args[1]);
+ return true;
+ }
+
+ // Should permissions be filtered by prefix?
+ String prefix = "";
+ if (args.length == 3)
+ {
+ prefix = args[2];
+ }
+
+ sender.sendMessage("Player " + player.getName() + " has the permission(s):");
+
+ for (Permission permission : perms)
+ {
+ if (permission.getName().startsWith(prefix))
+ {
+ sender.sendMessage(permission.getName() + ": " + player.hasPermission(permission));
+ }
+ }
+ return true;
+ }
+
+ private boolean handleReloadCommand(NoCheat plugin, CommandSender sender)
+ {
+
+ // Players need a special permission for this
+ if (!(sender instanceof Player) || sender.hasPermission(Permissions.ADMIN_RELOAD))
+ {
+ sender.sendMessage("[NoCheat] Reloading configuration");
+ plugin.reloadConfiguration();
+ sender.sendMessage("[NoCheat] Configuration reloaded");
+ }
+ else
+ {
+ sender.sendMessage("You lack the " + Permissions.ADMIN_RELOAD + " permission to use 'reload'");
+ }
+
+ return true;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ActionFactory.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ActionFactory.java
new file mode 100644
index 000000000..5e07661c1
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ActionFactory.java
@@ -0,0 +1,183 @@
+package com.earth2me.essentials.anticheat.config;
+
+import com.earth2me.essentials.anticheat.actions.Action;
+import com.earth2me.essentials.anticheat.actions.types.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Helps with creating Actions out of text string definitions
+ *
+ */
+public class ActionFactory
+{
+ private static final Map<String, Object> lib = new HashMap<String, Object>();
+
+ public ActionFactory(Map<String, Object> library)
+ {
+ lib.putAll(library);
+ }
+
+ public Action createAction(String actionDefinition)
+ {
+
+ actionDefinition = actionDefinition.toLowerCase();
+
+ if (actionDefinition.equals("cancel"))
+ {
+ return new SpecialAction();
+ }
+
+ if (actionDefinition.startsWith("log:"))
+ {
+ return parseLogAction(actionDefinition.split(":", 2)[1]);
+ }
+
+ if (actionDefinition.startsWith("cmd:"))
+ {
+ return parseCmdAction(actionDefinition.split(":", 2)[1]);
+ }
+
+ throw new IllegalArgumentException("NoCheat doesn't understand action '" + actionDefinition + "' at all");
+ }
+
+ private Action parseCmdAction(String definition)
+ {
+ String[] parts = definition.split(":");
+ String name = parts[0];
+ Object command = lib.get(parts[0]);
+ int delay = 0;
+ int repeat = 1;
+
+ if (command == null)
+ {
+ throw new IllegalArgumentException("NoCheat doesn't know command '" + name + "'. Have you forgotten to define it?");
+ }
+
+ if (parts.length > 1)
+ {
+ try
+ {
+ delay = Integer.parseInt(parts[1]);
+ repeat = Integer.parseInt(parts[2]);
+ }
+ catch (Exception e)
+ {
+ // TODO
+ System.out.println("NoCheat couldn't parse details of command '" + definition + "', will use default values instead.");
+ delay = 0;
+ repeat = 1;
+ }
+ }
+
+ return new ConsolecommandAction(name, delay, repeat, command.toString());
+ }
+
+ private Action parseLogAction(String definition)
+ {
+ String[] parts = definition.split(":");
+ String name = parts[0];
+ Object message = lib.get(parts[0]);
+ int delay = 0;
+ int repeat = 1;
+ boolean toConsole = true;
+ boolean toFile = true;
+ boolean toChat = true;
+
+ if (message == null)
+ {
+ throw new IllegalArgumentException("NoCheat doesn't know log message '" + name + "'. Have you forgotten to define it?");
+ }
+
+ try
+ {
+ delay = Integer.parseInt(parts[1]);
+ repeat = Integer.parseInt(parts[2]);
+ toConsole = parts[3].contains("c");
+ toChat = parts[3].contains("i");
+ toFile = parts[3].contains("f");
+ }
+ catch (Exception e)
+ {
+ System.out.println("NoCheat couldn't parse details of log action '" + definition + "', will use default values instead.");
+ e.printStackTrace();
+ delay = 0;
+ repeat = 1;
+ toConsole = true;
+ toFile = true;
+ toChat = true;
+ }
+
+ return new LogAction(name, delay, repeat, toChat, toConsole, toFile, message.toString());
+ }
+
+ public Action[] createActions(String... definitions)
+ {
+ List<Action> actions = new ArrayList<Action>();
+
+ for (String def : definitions)
+ {
+ if (def.length() == 0)
+ {
+ continue;
+ }
+ try
+ {
+ actions.add(createAction(def));
+ }
+ catch (IllegalArgumentException e)
+ {
+ System.out.println(e.getMessage());
+ actions.add(new DummyAction(def));
+ }
+ }
+
+ return actions.toArray(new Action[actions.size()]);
+ }
+
+ public ActionList createActionList(String definition, String permission)
+ {
+ ActionList list = new ActionList(permission);
+
+ boolean first = true;
+
+ for (String s : definition.split("vl>"))
+ {
+ s = s.trim();
+
+ if (s.length() == 0)
+ {
+ first = false;
+ continue;
+ }
+
+ try
+ {
+ Integer vl;
+ String def;
+ if (first)
+ {
+ first = false;
+ vl = 0;
+ def = s;
+ }
+ else
+ {
+ String[] listEntry = s.split("\\s+", 2);
+ vl = Integer.parseInt(listEntry[0]);
+ def = listEntry[1];
+ }
+ list.setActions(vl, createActions(def.split("\\s+")));
+ }
+ catch (Exception e)
+ {
+ System.out.println("NoCheat couldn't parse action definition 'vl:" + s + "'");
+ }
+ }
+
+ return list;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfPaths.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfPaths.java
new file mode 100644
index 000000000..66473633d
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfPaths.java
@@ -0,0 +1,142 @@
+package com.earth2me.essentials.anticheat.config;
+
+/**
+ * Paths for the configuration options Making everything final static prevents accidentially modifying any of these
+ *
+ */
+public abstract class ConfPaths
+{
+ // TODO
+ private final static String LOGGING = "logging.";
+ public final static String LOGGING_ACTIVE = LOGGING + "active";
+ public final static String LOGGING_PREFIX = LOGGING + "prefix";
+ public final static String LOGGING_FILENAME = LOGGING + "filename";
+ public final static String LOGGING_LOGTOFILE = LOGGING + "file";
+ public final static String LOGGING_LOGTOCONSOLE = LOGGING + "console";
+ public final static String LOGGING_LOGTOINGAMECHAT = LOGGING + "ingamechat";
+ public final static String LOGGING_SHOWACTIVECHECKS = LOGGING + "showactivechecks";
+ public final static String LOGGING_DEBUGMESSAGES = LOGGING + "debugmessages";
+
+ private final static String CHECKS = "checks.";
+
+ private final static String INVENTORY = CHECKS + "inventory.";
+
+ private final static String INVENTORY_DROP = INVENTORY + "drop.";
+ public final static String INVENTORY_DROP_CHECK = INVENTORY_DROP + "active";
+ public final static String INVENTORY_DROP_TIMEFRAME = INVENTORY_DROP + "time";
+ public final static String INVENTORY_DROP_LIMIT = INVENTORY_DROP + "limit";
+ public final static String INVENTORY_DROP_ACTIONS = INVENTORY_DROP + "actions";
+
+ private static final String INVENTORY_INSTANTBOW = INVENTORY + "instantbow.";
+ public final static String INVENTORY_INSTANTBOW_CHECK = INVENTORY_INSTANTBOW + "active";
+ public static final String INVENTORY_INSTANTBOW_ACTIONS = INVENTORY_INSTANTBOW + "actions";
+
+ private static final String INVENTORY_INSTANTEAT = INVENTORY + "instanteat.";
+ public final static String INVENTORY_INSTANTEAT_CHECK = INVENTORY_INSTANTEAT + "active";
+ public static final String INVENTORY_INSTANTEAT_ACTIONS = INVENTORY_INSTANTEAT + "actions";
+
+ private final static String MOVING = CHECKS + "moving.";
+
+ private final static String MOVING_RUNFLY = MOVING + "runfly.";
+ public final static String MOVING_RUNFLY_CHECK = MOVING_RUNFLY + "active";
+
+ // These four are not automatically shown in the config
+ public final static String MOVING_RUNFLY_WALKSPEED = MOVING_RUNFLY + "walkspeed";
+ public final static String MOVING_RUNFLY_SNEAKSPEED = MOVING_RUNFLY + "sneakspeed";
+ public final static String MOVING_RUNFLY_SWIMSPEED = MOVING_RUNFLY + "swimspeed";
+ public final static String MOVING_RUNFLY_SPRINTSPEED = MOVING_RUNFLY + "sprintspeed";
+
+ public final static String MOVING_RUNFLY_ALLOWFASTSNEAKING = MOVING_RUNFLY + "allowfastsneaking";
+ public final static String MOVING_RUNFLY_ACTIONS = MOVING_RUNFLY + "actions";
+
+ public final static String MOVING_RUNFLY_CHECKNOFALL = MOVING_RUNFLY + "checknofall";
+ public final static String MOVING_RUNFLY_NOFALLAGGRESSIVE = MOVING_RUNFLY + "nofallaggressivemode";
+ public final static String MOVING_RUNFLY_NOFALLACTIONS = MOVING_RUNFLY + "nofallactions";
+
+ private final static String MOVING_RUNFLY_FLYING = MOVING_RUNFLY + "flying.";
+ public final static String MOVING_RUNFLY_FLYING_ALLOWALWAYS = MOVING_RUNFLY_FLYING + "allowflyingalways";
+ public final static String MOVING_RUNFLY_FLYING_ALLOWINCREATIVE = MOVING_RUNFLY_FLYING + "allowflyingincreative";
+ public final static String MOVING_RUNFLY_FLYING_SPEEDLIMITVERTICAL = MOVING_RUNFLY_FLYING + "flyingspeedlimitvertical";
+ public final static String MOVING_RUNFLY_FLYING_SPEEDLIMITHORIZONTAL = MOVING_RUNFLY_FLYING + "flyingspeedlimithorizontal";
+ public final static String MOVING_RUNFLY_FLYING_HEIGHTLIMIT = MOVING_RUNFLY_FLYING + "flyingheightlimit";
+ public final static String MOVING_RUNFLY_FLYING_ACTIONS = MOVING_RUNFLY_FLYING + "actions";
+
+ private final static String MOVING_MOREPACKETS = MOVING + "morepackets.";
+ public final static String MOVING_MOREPACKETS_CHECK = MOVING_MOREPACKETS + "active";
+ public final static String MOVING_MOREPACKETS_ACTIONS = MOVING_MOREPACKETS + "actions";
+
+ private final static String BLOCKBREAK = CHECKS + "blockbreak.";
+
+ private final static String BLOCKBREAK_REACH = BLOCKBREAK + "reach.";
+ public final static String BLOCKBREAK_REACH_CHECK = BLOCKBREAK_REACH + "active";
+ public final static String BLOCKBREAK_REACH_ACTIONS = BLOCKBREAK_REACH + "actions";
+
+ private final static String BLOCKBREAK_DIRECTION = BLOCKBREAK + "direction.";
+ public final static String BLOCKBREAK_DIRECTION_CHECK = BLOCKBREAK_DIRECTION + "active";
+ public final static String BLOCKBREAK_DIRECTION_PRECISION = BLOCKBREAK_DIRECTION + "precision";
+ public final static String BLOCKBREAK_DIRECTION_PENALTYTIME = BLOCKBREAK_DIRECTION + "penaltytime";
+ public final static String BLOCKBREAK_DIRECTION_ACTIONS = BLOCKBREAK_DIRECTION + "actions";
+
+ private final static String BLOCKBREAK_NOSWING = BLOCKBREAK + "noswing.";
+ public static final String BLOCKBREAK_NOSWING_CHECK = BLOCKBREAK_NOSWING + "active";
+ public static final String BLOCKBREAK_NOSWING_ACTIONS = BLOCKBREAK_NOSWING + "actions";
+
+ private final static String BLOCKPLACE = CHECKS + "blockplace.";
+
+ private final static String BLOCKPLACE_REACH = BLOCKPLACE + "reach.";
+ public final static String BLOCKPLACE_REACH_CHECK = BLOCKPLACE_REACH + "active";
+ public final static String BLOCKPLACE_REACH_ACTIONS = BLOCKPLACE_REACH + "actions";
+
+ private final static String BLOCKPLACE_DIRECTION = BLOCKPLACE + "direction.";
+ public final static String BLOCKPLACE_DIRECTION_CHECK = BLOCKPLACE_DIRECTION + "active";
+ public final static String BLOCKPLACE_DIRECTION_PRECISION = BLOCKPLACE_DIRECTION + "precision";
+ public final static String BLOCKPLACE_DIRECTION_PENALTYTIME = BLOCKPLACE_DIRECTION + "penaltytime";
+ public final static String BLOCKPLACE_DIRECTION_ACTIONS = BLOCKPLACE_DIRECTION + "actions";
+
+ private final static String CHAT = CHECKS + "chat.";
+
+ private final static String CHAT_COLOR = CHAT + "color.";
+ public final static String CHAT_COLOR_CHECK = CHAT_COLOR + "active";
+ public final static String CHAT_COLOR_ACTIONS = CHAT_COLOR + "actions";
+
+ private final static String CHAT_SPAM = CHAT + "spam.";
+ public final static String CHAT_SPAM_CHECK = CHAT_SPAM + "active";
+ public final static String CHAT_SPAM_WHITELIST = CHAT_SPAM + "whitelist";
+ public final static String CHAT_SPAM_TIMEFRAME = CHAT_SPAM + "timeframe";
+ public final static String CHAT_SPAM_MESSAGELIMIT = CHAT_SPAM + "messagelimit";
+ public final static String CHAT_SPAM_COMMANDLIMIT = CHAT_SPAM + "commandlimit";
+ public final static String CHAT_SPAM_ACTIONS = CHAT_SPAM + "actions";
+
+ private final static String FIGHT = CHECKS + "fight.";
+
+ private final static String FIGHT_DIRECTION = FIGHT + "direction.";
+ public final static String FIGHT_DIRECTION_CHECK = FIGHT_DIRECTION + "active";
+ public final static String FIGHT_DIRECTION_PRECISION = FIGHT_DIRECTION + "precision";
+ public final static String FIGHT_DIRECTION_PENALTYTIME = FIGHT_DIRECTION + "penaltytime";
+ public final static String FIGHT_DIRECTION_ACTIONS = FIGHT_DIRECTION + "actions";
+
+ private final static String FIGHT_NOSWING = FIGHT + "noswing.";
+ public final static String FIGHT_NOSWING_CHECK = FIGHT_NOSWING + "active";
+ public final static String FIGHT_NOSWING_ACTIONS = FIGHT_NOSWING + "actions";
+
+ private final static String FIGHT_REACH = FIGHT + "reach.";
+ public static final String FIGHT_REACH_CHECK = FIGHT_REACH + "active";
+ public static final String FIGHT_REACH_LIMIT = FIGHT_REACH + "distance";
+ public static final String FIGHT_REACH_PENALTYTIME = FIGHT_REACH + "penaltytime";
+ public static final String FIGHT_REACH_ACTIONS = FIGHT_REACH + "actions";
+
+ private final static String FIGHT_SPEED = FIGHT + "speed.";
+ public final static String FIGHT_SPEED_CHECK = FIGHT_SPEED + "active";
+ public final static String FIGHT_SPEED_ATTACKLIMIT = FIGHT_SPEED + "attacklimit";
+ public final static String FIGHT_SPEED_ACTIONS = FIGHT_SPEED + "actions";
+
+ private final static String FIGHT_GODMODE = FIGHT + "godmode.";
+ public static final String FIGHT_GODMODE_CHECK = FIGHT_GODMODE + "active";
+ public final static String FIGHT_GODMODE_ACTIONS = FIGHT_GODMODE + "actions";
+
+ private final static String FIGHT_INSTANTHEAL = FIGHT + "instantheal.";
+ public static final String FIGHT_INSTANTHEAL_CHECK = FIGHT_INSTANTHEAL + "active";
+ public final static String FIGHT_INSTANTHEAL_ACTIONS = FIGHT_INSTANTHEAL + "actions";
+
+ public final static String STRINGS = "strings";
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationCacheStore.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationCacheStore.java
new file mode 100644
index 000000000..fa404d5ea
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationCacheStore.java
@@ -0,0 +1,45 @@
+package com.earth2me.essentials.anticheat.config;
+
+import com.earth2me.essentials.anticheat.ConfigItem;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * A class to keep all configurables of the plugin associated with a world
+ *
+ */
+public class ConfigurationCacheStore
+{
+ public final LoggingConfig logging;
+ private final Map<String, ConfigItem> configMap = new HashMap<String, ConfigItem>();
+ private final NoCheatConfiguration data;
+
+ /**
+ * Instantiate a config cache and populate it with the data of a Config tree (and its parent tree)
+ */
+ public ConfigurationCacheStore(NoCheatConfiguration data)
+ {
+
+ logging = new LoggingConfig(data);
+
+ this.data = data;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends ConfigItem> T get(String id)
+ {
+ return (T)configMap.get(id);
+ }
+
+ public void set(String id, ConfigItem config)
+ {
+
+ configMap.put(id, config);
+ }
+
+ public NoCheatConfiguration getConfiguration()
+ {
+ return this.data;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationManager.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationManager.java
new file mode 100644
index 000000000..283ad88d2
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationManager.java
@@ -0,0 +1,257 @@
+package com.earth2me.essentials.anticheat.config;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.*;
+
+
+/**
+ * Central location for everything that's described in the configuration file(s)
+ *
+ */
+public class ConfigurationManager
+{
+ private final static String configFileName = "config.yml";
+ private final Map<String, ConfigurationCacheStore> worldnameToConfigCacheMap = new HashMap<String, ConfigurationCacheStore>();
+ private FileHandler fileHandler;
+ private final NoCheat plugin;
+
+
+ private static class LogFileFormatter extends Formatter
+ {
+ private final SimpleDateFormat date;
+
+ public LogFileFormatter()
+ {
+ date = new SimpleDateFormat("yy.MM.dd HH:mm:ss");
+ }
+
+ @Override
+ public String format(LogRecord record)
+ {
+ StringBuilder builder = new StringBuilder();
+ Throwable ex = record.getThrown();
+
+ builder.append(date.format(record.getMillis()));
+ builder.append(" [");
+ builder.append(record.getLevel().getLocalizedName().toUpperCase());
+ builder.append("] ");
+ builder.append(record.getMessage());
+ builder.append('\n');
+
+ if (ex != null)
+ {
+ StringWriter writer = new StringWriter();
+ ex.printStackTrace(new PrintWriter(writer));
+ builder.append(writer);
+ }
+
+ return builder.toString();
+ }
+ }
+
+ public ConfigurationManager(NoCheat plugin, File rootConfigFolder)
+ {
+
+ this.plugin = plugin;
+
+ // Setup the real configuration
+ initializeConfig(rootConfigFolder);
+
+ }
+
+ /**
+ * Read the configuration file and assign either standard values or whatever is declared in the file
+ *
+ * @param configurationFile
+ */
+ private void initializeConfig(File rootConfigFolder)
+ {
+
+ // First try to obtain and parse the global config file
+ NoCheatConfiguration root = new NoCheatConfiguration();
+ root.setDefaults(new DefaultConfiguration());
+ root.options().copyDefaults(true);
+ root.options().copyHeader(true);
+
+ File globalConfigFile = getGlobalConfigFile(rootConfigFolder);
+
+ if (globalConfigFile.exists())
+ {
+ try
+ {
+ root.load(globalConfigFile);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ try
+ {
+ root.save(globalConfigFile);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ root.regenerateActionLists();
+
+ // Create a corresponding Configuration Cache
+ // put the global config on the config map
+ worldnameToConfigCacheMap.put(null, new ConfigurationCacheStore(root));
+
+ plugin.setFileLogger(setupFileLogger(new File(rootConfigFolder, root.getString(ConfPaths.LOGGING_FILENAME))));
+
+ // Try to find world-specific config files
+ Map<String, File> worldFiles = getWorldSpecificConfigFiles(rootConfigFolder);
+
+ for (Entry<String, File> worldEntry : worldFiles.entrySet())
+ {
+
+ File worldConfigFile = worldEntry.getValue();
+
+ NoCheatConfiguration world = new NoCheatConfiguration();
+ world.setDefaults(root);
+
+ try
+ {
+ world.load(worldConfigFile);
+
+ worldnameToConfigCacheMap.put(worldEntry.getKey(), new ConfigurationCacheStore(world));
+
+ // write the config file back to disk immediately
+ world.save(worldConfigFile);
+
+ }
+ catch (Exception e)
+ {
+ plugin.getLogger().warning("Couldn't load world-specific config for " + worldEntry.getKey());
+ e.printStackTrace();
+ }
+
+ world.regenerateActionLists();
+ }
+ }
+
+ private static File getGlobalConfigFile(File rootFolder)
+ {
+
+ File globalConfig = new File(rootFolder, configFileName);
+
+ return globalConfig;
+ }
+
+ private static Map<String, File> getWorldSpecificConfigFiles(File rootFolder)
+ {
+
+ HashMap<String, File> files = new HashMap<String, File>();
+
+ if (rootFolder.isDirectory())
+ {
+ for (File f : rootFolder.listFiles())
+ {
+ if (f.isFile())
+ {
+ String filename = f.getName();
+ if (filename.matches(".+_" + configFileName + "$"))
+ {
+ // Get the first part = world name
+ String worldname = filename.substring(0, filename.length() - (configFileName.length() + 1));
+ files.put(worldname, f);
+ }
+ }
+ }
+ }
+ return files;
+ }
+
+ private Logger setupFileLogger(File logfile)
+ {
+
+ Logger l = Logger.getAnonymousLogger();
+ l.setLevel(Level.INFO);
+ // Ignore parent's settings
+ l.setUseParentHandlers(false);
+ for (Handler h : l.getHandlers())
+ {
+ l.removeHandler(h);
+ }
+
+ if (fileHandler != null)
+ {
+ fileHandler.close();
+ l.removeHandler(fileHandler);
+ fileHandler = null;
+ }
+
+ try
+ {
+ try
+ {
+ logfile.getParentFile().mkdirs();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ fileHandler = new FileHandler(logfile.getCanonicalPath(), true);
+ fileHandler.setLevel(Level.ALL);
+ fileHandler.setFormatter(new LogFileFormatter());
+
+ l.addHandler(fileHandler);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ return l;
+ }
+
+ /**
+ * Reset the loggers and flush and close the fileHandlers to be able to use them next time without problems
+ */
+ public void cleanup()
+ {
+ fileHandler.flush();
+ fileHandler.close();
+ Logger l = Logger.getLogger("NoCheat");
+ l.removeHandler(fileHandler);
+ fileHandler = null;
+ }
+
+ /**
+ * Get the cache of the specified world, or the default cache, if no cache exists for that world.
+ *
+ * @param worldname
+ * @return
+ */
+ public ConfigurationCacheStore getConfigurationCacheForWorld(String worldname)
+ {
+
+ ConfigurationCacheStore cache = worldnameToConfigCacheMap.get(worldname);
+
+ if (cache != null)
+ {
+ return cache;
+ }
+ else
+ {
+ // Enter a reference to the cache under the new name
+ // to be faster in looking it up later
+ cache = worldnameToConfigCacheMap.get(null);
+ worldnameToConfigCacheMap.put(worldname, cache);
+
+ return cache;
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/DefaultConfiguration.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/DefaultConfiguration.java
new file mode 100644
index 000000000..fd61cac5d
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/DefaultConfiguration.java
@@ -0,0 +1,154 @@
+package com.earth2me.essentials.anticheat.config;
+
+
+/**
+ * These are the default settings for NoCheat. They will be used in addition to/in replacement of configurations given
+ * in the config.yml file
+ *
+ */
+public class DefaultConfiguration extends NoCheatConfiguration
+{
+ public DefaultConfiguration()
+ {
+
+ super();
+
+ this.options().header("Main configuration file for NoCheat. Read \"Instructions.txt\"");
+
+ /**
+ * LOGGING *
+ */
+ set(ConfPaths.LOGGING_ACTIVE, true);
+ set(ConfPaths.LOGGING_SHOWACTIVECHECKS, false);
+ set(ConfPaths.LOGGING_DEBUGMESSAGES, false);
+ set(ConfPaths.LOGGING_PREFIX, "&4NC&f: ");
+ set(ConfPaths.LOGGING_FILENAME, "nocheat.log");
+ set(ConfPaths.LOGGING_LOGTOFILE, true);
+ set(ConfPaths.LOGGING_LOGTOCONSOLE, true);
+ set(ConfPaths.LOGGING_LOGTOINGAMECHAT, true);
+
+ /**
+ * * INVENTORY **
+ */
+ set(ConfPaths.INVENTORY_DROP_CHECK, true);
+ set(ConfPaths.INVENTORY_DROP_TIMEFRAME, 20);
+ set(ConfPaths.INVENTORY_DROP_LIMIT, 100);
+ set(ConfPaths.INVENTORY_DROP_ACTIONS, "log:drop:0:1:cif cmd:kick");
+
+ set(ConfPaths.INVENTORY_INSTANTBOW_CHECK, true);
+ set(ConfPaths.INVENTORY_INSTANTBOW_ACTIONS, "log:ibow:2:5:if cancel");
+
+ set(ConfPaths.INVENTORY_INSTANTEAT_CHECK, true);
+ set(ConfPaths.INVENTORY_INSTANTEAT_ACTIONS, "log:ieat:2:5:if cancel");
+
+ /**
+ * * MOVING **
+ */
+ set(ConfPaths.MOVING_RUNFLY_CHECK, true);
+ set(ConfPaths.MOVING_RUNFLY_ALLOWFASTSNEAKING, false);
+ set(ConfPaths.MOVING_RUNFLY_ACTIONS, "log:moveshort:3:5:f cancel vl>100 log:moveshort:0:5:if cancel vl>400 log:movelong:0:5:cif cancel");
+
+ set(ConfPaths.MOVING_RUNFLY_CHECKNOFALL, true);
+ set(ConfPaths.MOVING_RUNFLY_NOFALLAGGRESSIVE, true);
+ set(ConfPaths.MOVING_RUNFLY_NOFALLACTIONS, "log:nofall:0:5:cif cancel");
+
+ set(ConfPaths.MOVING_RUNFLY_FLYING_ALLOWALWAYS, false);
+ set(ConfPaths.MOVING_RUNFLY_FLYING_ALLOWINCREATIVE, true);
+ set(ConfPaths.MOVING_RUNFLY_FLYING_SPEEDLIMITHORIZONTAL, 60);
+ set(ConfPaths.MOVING_RUNFLY_FLYING_SPEEDLIMITVERTICAL, 100);
+ set(ConfPaths.MOVING_RUNFLY_FLYING_HEIGHTLIMIT, 128);
+ set(ConfPaths.MOVING_RUNFLY_FLYING_ACTIONS, "log:moveshort:3:5:f cancel vl>100 log:moveshort:0:5:if cancel vl>400 log:movelong:0:5:cif cancel");
+
+ set(ConfPaths.MOVING_MOREPACKETS_CHECK, true);
+ set(ConfPaths.MOVING_MOREPACKETS_ACTIONS, "log:morepackets:3:2:if cancel vl>20 log:morepackets:0:2:if cancel");
+
+ /**
+ * * BLOCKBREAK **
+ */
+ set(ConfPaths.BLOCKBREAK_REACH_CHECK, true);
+ set(ConfPaths.BLOCKBREAK_REACH_ACTIONS, "cancel vl>5 log:bbreach:0:2:if cancel");
+
+ set(ConfPaths.BLOCKBREAK_DIRECTION_CHECK, true);
+ set(ConfPaths.BLOCKBREAK_DIRECTION_PRECISION, 50);
+ set(ConfPaths.BLOCKBREAK_DIRECTION_PENALTYTIME, 300);
+ set(ConfPaths.BLOCKBREAK_DIRECTION_ACTIONS, "cancel vl>10 log:bbdirection:0:5:if cancel");
+
+ set(ConfPaths.BLOCKBREAK_NOSWING_CHECK, true);
+ set(ConfPaths.BLOCKBREAK_NOSWING_ACTIONS, "log:bbnoswing:3:2:if cancel");
+
+ /**
+ * * BLOCKPLACE **
+ */
+ set(ConfPaths.BLOCKPLACE_REACH_CHECK, true);
+ set(ConfPaths.BLOCKPLACE_REACH_ACTIONS, "cancel vl>5 log:bpreach:0:2:if cancel");
+
+ set(ConfPaths.BLOCKPLACE_DIRECTION_CHECK, true);
+ set(ConfPaths.BLOCKPLACE_DIRECTION_PRECISION, 75);
+ set(ConfPaths.BLOCKPLACE_DIRECTION_PENALTYTIME, 100);
+ set(ConfPaths.BLOCKPLACE_DIRECTION_ACTIONS, "cancel vl>10 log:bpdirection:0:3:if cancel");
+
+ /**
+ * * CHAT **
+ */
+ set(ConfPaths.CHAT_COLOR_CHECK, true);
+ set(ConfPaths.CHAT_COLOR_ACTIONS, "log:color:0:1:if cancel");
+
+ set(ConfPaths.CHAT_SPAM_CHECK, true);
+ set(ConfPaths.CHAT_SPAM_WHITELIST, "");
+ set(ConfPaths.CHAT_SPAM_TIMEFRAME, 3);
+ set(ConfPaths.CHAT_SPAM_MESSAGELIMIT, 3);
+ set(ConfPaths.CHAT_SPAM_COMMANDLIMIT, 12);
+ set(ConfPaths.CHAT_SPAM_ACTIONS, "log:spam:0:3:if cancel vl>30 log:spam:0:3:cif cancel cmd:kick");
+
+ /**
+ * * FIGHT **
+ */
+ set(ConfPaths.FIGHT_DIRECTION_CHECK, true);
+ set(ConfPaths.FIGHT_DIRECTION_PRECISION, 75);
+ set(ConfPaths.FIGHT_DIRECTION_PENALTYTIME, 500);
+ set(ConfPaths.FIGHT_DIRECTION_ACTIONS, "cancel vl>5 log:fdirection:3:5:f cancel vl>20 log:fdirection:0:5:if cancel vl>50 log:fdirection:0:5:cif cancel");
+
+ set(ConfPaths.FIGHT_NOSWING_CHECK, true);
+ set(ConfPaths.FIGHT_NOSWING_ACTIONS, "log:fnoswing:0:5:cif cancel");
+
+ set(ConfPaths.FIGHT_REACH_CHECK, true);
+ set(ConfPaths.FIGHT_REACH_LIMIT, 400);
+ set(ConfPaths.FIGHT_REACH_PENALTYTIME, 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_ATTACKLIMIT, 15);
+ set(ConfPaths.FIGHT_SPEED_ACTIONS, "log:fspeed:0:5:if cancel");
+
+ set(ConfPaths.FIGHT_GODMODE_CHECK, true);
+ set(ConfPaths.FIGHT_GODMODE_ACTIONS, "log:fgod:2:5:if cancel");
+
+ set(ConfPaths.FIGHT_INSTANTHEAL_CHECK, true);
+ set(ConfPaths.FIGHT_INSTANTHEAL_ACTIONS, "log:fheal:1:1:if cancel");
+
+ set(ConfPaths.STRINGS + ".drop", "[player] failed [check]: Tried to drop more items than allowed. VL [violations]");
+ set(ConfPaths.STRINGS + ".moveshort", "[player] failed [check]. VL [violations]");
+ set(ConfPaths.STRINGS + ".movelong", "[player] in [world] at [location] moving to [locationto] over distance [movedistance] failed check [check]. Total violation level so far [violations]");
+ set(ConfPaths.STRINGS + ".nofall", "[player] failed [check]: tried to avoid fall damage for ~[falldistance] blocks. VL [violations]");
+ set(ConfPaths.STRINGS + ".morepackets", "[player] failed [check]: Sent [packets] more packets than expected. Total violation level [violations]");
+ set(ConfPaths.STRINGS + ".bbreach", "[player] failed [check]: tried to interact with a block over distance [reachdistance]. VL [violations]");
+ set(ConfPaths.STRINGS + ".bbdirection", "[player] failed [check]: tried to interact with a block out of line of sight. VL [violations]");
+ set(ConfPaths.STRINGS + ".bbnoswing", "[player] failed [check]: Didn't swing arm. VL [violations]");
+ set(ConfPaths.STRINGS + ".bpreach", "[player] failed [check]: tried to interact with a block over distance [reachdistance]. VL [violations]");
+ set(ConfPaths.STRINGS + ".bpdirection", "[player] failed [check]: tried to interact with a block out of line of sight. VL [violations]");
+ set(ConfPaths.STRINGS + ".color", "[player] failed [check]: Sent colored chat message '[text]'. VL [violations]");
+ set(ConfPaths.STRINGS + ".spam", "[player] failed [check]: Last sent message '[text]'. VL [violations]");
+ set(ConfPaths.STRINGS + ".fdirection", "[player] failed [check]: tried to interact with a block out of line of sight. VL [violations]");
+ set(ConfPaths.STRINGS + ".freach", "[player] failed [check]: tried to attack entity out of reach. VL [violations]");
+ set(ConfPaths.STRINGS + ".fspeed", "[player] failed [check]: tried to attack more than [limit] times per second. VL [violations]");
+ set(ConfPaths.STRINGS + ".fnoswing", "[player] failed [check]: Didn't swing arm. VL [violations]");
+ set(ConfPaths.STRINGS + ".fgod", "[player] failed [check]: Avoided taking damage or lagging. VL [violations]");
+ set(ConfPaths.STRINGS + ".fheal", "[player] failed [check]: Tried to regenerate health faster than normal. VL [violations]");
+ set(ConfPaths.STRINGS + ".ibow", "[player] failed [check]: Fires bow to fast. VL [violations]");
+ set(ConfPaths.STRINGS + ".ieat", "[player] failed [check]: Eats food [food] too fast. VL [violations]");
+ set(ConfPaths.STRINGS + ".kick", "kick [player]");
+
+ // Update internal factory based on all the new entries to the "actions" section
+ regenerateActionLists();
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/LoggingConfig.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/LoggingConfig.java
new file mode 100644
index 000000000..9875c7715
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/LoggingConfig.java
@@ -0,0 +1,29 @@
+package com.earth2me.essentials.anticheat.config;
+
+
+/**
+ * Configurations specific for logging. Every world gets one of these.
+ *
+ */
+public class LoggingConfig
+{
+ public final boolean active;
+ public final boolean showactivechecks;
+ public final boolean toFile;
+ public final boolean toConsole;
+ public final boolean toChat;
+ public final String prefix;
+ public final boolean debugmessages;
+
+ public LoggingConfig(NoCheatConfiguration data)
+ {
+
+ active = data.getBoolean(ConfPaths.LOGGING_ACTIVE);
+ showactivechecks = data.getBoolean(ConfPaths.LOGGING_SHOWACTIVECHECKS);
+ debugmessages = data.getBoolean(ConfPaths.LOGGING_DEBUGMESSAGES);
+ prefix = data.getString(ConfPaths.LOGGING_PREFIX);
+ toFile = data.getBoolean(ConfPaths.LOGGING_LOGTOFILE);
+ toConsole = data.getBoolean(ConfPaths.LOGGING_LOGTOCONSOLE);
+ toChat = data.getBoolean(ConfPaths.LOGGING_LOGTOINGAMECHAT);
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/NoCheatConfiguration.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/NoCheatConfiguration.java
new file mode 100644
index 000000000..e137ff480
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/NoCheatConfiguration.java
@@ -0,0 +1,82 @@
+package com.earth2me.essentials.anticheat.config;
+
+import com.earth2me.essentials.anticheat.actions.Action;
+import com.earth2me.essentials.anticheat.actions.types.ActionList;
+import java.lang.reflect.Field;
+import org.bukkit.configuration.MemorySection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.yaml.snakeyaml.DumperOptions;
+
+
+public class NoCheatConfiguration extends YamlConfiguration
+{
+ private ActionFactory factory;
+
+ @Override
+ public String saveToString()
+ {
+ // Some reflection wizardry to avoid having a lot of
+ // linebreaks in the yml file, and get a "footer" into the file
+ try
+ {
+ Field op;
+ op = YamlConfiguration.class.getDeclaredField("yamlOptions");
+ op.setAccessible(true);
+ DumperOptions options = (DumperOptions)op.get(this);
+ options.setWidth(200);
+ }
+ catch (Exception e)
+ {
+ }
+
+ String result = super.saveToString();
+
+ return result;
+ }
+
+ /**
+ * Do this after reading new data
+ */
+ public void regenerateActionLists()
+ {
+ factory = new ActionFactory(((MemorySection)this.get(ConfPaths.STRINGS)).getValues(false));
+ }
+
+ /**
+ * A convenience method to get action lists from the config
+ *
+ * @param path
+ * @return
+ */
+ public ActionList getActionList(String path, String permission)
+ {
+
+ String value = this.getString(path);
+ return factory.createActionList(value, permission);
+ }
+
+ /**
+ * Savely store ActionLists back into the yml file
+ *
+ * @param path
+ * @param list
+ */
+ public void set(String path, ActionList list)
+ {
+ StringBuilder string = new StringBuilder();
+
+ for (int treshold : list.getTresholds())
+ {
+ if (treshold > 0)
+ {
+ string.append(" vl>").append(treshold);
+ }
+ for (Action action : list.getActions(treshold))
+ {
+ string.append(" ").append(action);
+ }
+ }
+
+ set(path, string.toString().trim());
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/Permissions.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/Permissions.java
new file mode 100644
index 000000000..ab55d7b48
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/Permissions.java
@@ -0,0 +1,44 @@
+package com.earth2me.essentials.anticheat.config;
+
+
+/**
+ * The various permission nodes used by NoCheat
+ *
+ */
+public class Permissions
+{
+ private static final String NOCHEAT = "nocheat";
+ private static final String ADMIN = NOCHEAT + ".admin";
+ private static final String CHECKS = NOCHEAT + ".checks";
+ public static final String MOVING = CHECKS + ".moving";
+ public static final String MOVING_RUNFLY = MOVING + ".runfly";
+ public static final String MOVING_SWIMMING = MOVING + ".swimming";
+ public static final String MOVING_SNEAKING = MOVING + ".sneaking";
+ public static final String MOVING_FLYING = MOVING + ".flying";
+ public static final String MOVING_NOFALL = MOVING + ".nofall";
+ public static final String MOVING_MOREPACKETS = MOVING + ".morepackets";
+ public static final String BLOCKBREAK = CHECKS + ".blockbreak";
+ public static final String BLOCKBREAK_REACH = BLOCKBREAK + ".reach";
+ public static final String BLOCKBREAK_DIRECTION = BLOCKBREAK + ".direction";
+ public static final String BLOCKBREAK_NOSWING = BLOCKBREAK + ".noswing";
+ public static final String BLOCKPLACE = CHECKS + ".blockplace";
+ public static final String BLOCKPLACE_REACH = BLOCKPLACE + ".reach";
+ public static final String BLOCKPLACE_DIRECTION = BLOCKPLACE + ".direction";
+ public static final String CHAT = CHECKS + ".chat";
+ public static final String CHAT_SPAM = CHAT + ".spam";
+ public static final String CHAT_COLOR = CHAT + ".color";
+ public static final String FIGHT = CHECKS + ".fight";
+ public static final String FIGHT_DIRECTION = FIGHT + ".direction";
+ public static final String FIGHT_NOSWING = FIGHT + ".noswing";
+ public static final String FIGHT_REACH = FIGHT + ".reach";
+ public static final String FIGHT_SPEED = FIGHT + ".speed";
+ public static final String FIGHT_GODMODE = FIGHT + ".godmode";
+ public static final String FIGHT_INSTANTHEAL = FIGHT + ".instantheal";
+ public static final String ADMIN_CHATLOG = ADMIN + ".chatlog";
+ public static final String ADMIN_COMMANDS = ADMIN + ".commands";
+ public static final String ADMIN_RELOAD = ADMIN + ".reload";
+ public static final String INVENTORY = CHECKS + ".inventory";
+ public static final String INVENTORY_DROP = INVENTORY + ".drop";
+ public static final String INVENTORY_INSTANTBOW = INVENTORY + ".instantbow";
+ public static final String INVENTORY_INSTANTEAT = INVENTORY + ".instanteat";
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/DataStore.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/DataStore.java
new file mode 100644
index 000000000..1f5b6ea71
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/DataStore.java
@@ -0,0 +1,38 @@
+package com.earth2me.essentials.anticheat.data;
+
+import com.earth2me.essentials.anticheat.DataItem;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class DataStore
+{
+ private final Map<String, DataItem> dataMap = new HashMap<String, DataItem>();
+ private final Statistics statistics = new Statistics();
+ private final long timestamp = System.currentTimeMillis();
+
+ @SuppressWarnings("unchecked")
+ public <T extends DataItem> T get(String id)
+ {
+ return (T)dataMap.get(id);
+ }
+
+ public void set(String id, DataItem data)
+ {
+ dataMap.put(id, data);
+ }
+
+ public Map<String, Object> collectData()
+ {
+ Map<String, Object> map = statistics.get();
+ map.put("nocheat.starttime", timestamp);
+ map.put("nocheat.endtime", System.currentTimeMillis());
+
+ return map;
+ }
+
+ public Statistics getStatistics()
+ {
+ return statistics;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/ExecutionHistory.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/ExecutionHistory.java
new file mode 100644
index 000000000..da57c3c50
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/ExecutionHistory.java
@@ -0,0 +1,145 @@
+package com.earth2me.essentials.anticheat.data;
+
+import com.earth2me.essentials.anticheat.actions.Action;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Store amount of action executions for last 60 seconds for various actions
+ *
+ */
+public class ExecutionHistory
+{
+ private static class ExecutionHistoryEntry
+ {
+ private final int executionTimes[];
+ private long lastExecution = 0;
+ private int totalEntries = 0;
+ private long lastClearedTime = 0;
+
+ private ExecutionHistoryEntry(int monitoredTimeFrame)
+ {
+ this.executionTimes = new int[monitoredTimeFrame];
+ }
+
+ /**
+ * Remember an execution at the specific time
+ */
+ private void addCounter(long time)
+ {
+ // clear out now outdated values from the array
+ if (time - lastClearedTime > 0)
+ {
+ // Clear the next few fields of the array
+ clearTimes(lastClearedTime + 1, time - lastClearedTime);
+ lastClearedTime = time + 1;
+ }
+
+ executionTimes[(int)(time % executionTimes.length)]++;
+ totalEntries++;
+ }
+
+ /**
+ * Clean parts of the array
+ *
+ * @param start
+ * @param length
+ */
+ private void clearTimes(long start, long length)
+ {
+
+ if (length <= 0)
+ {
+ return; // nothing to do (yet)
+ }
+ if (length > executionTimes.length)
+ {
+ length = executionTimes.length;
+ }
+
+ int j = (int)start % executionTimes.length;
+
+ for (int i = 0; i < length; i++)
+ {
+ if (j == executionTimes.length)
+ {
+ j = 0;
+ }
+
+ totalEntries -= executionTimes[j];
+ executionTimes[j] = 0;
+
+ j++;
+ }
+ }
+
+ public int getCounter()
+ {
+ return totalEntries;
+ }
+
+ public long getLastExecution()
+ {
+ return lastExecution;
+ }
+
+ public void setLastExecution(long time)
+ {
+ this.lastExecution = time;
+ }
+ }
+ // Store data between Events
+ // time + action + action-counter
+ private final Map<String, Map<Action, ExecutionHistoryEntry>> executionHistories;
+
+ public ExecutionHistory()
+ {
+ executionHistories = new HashMap<String, Map<Action, ExecutionHistoryEntry>>();
+ }
+
+ /**
+ * Returns true, if the action should be executed, because all time criteria have been met. Will add a entry with
+ * the time to a list which will influence further requests, so only use once and remember the result
+ *
+ * @param check
+ * @param action
+ * @param time a time IN SECONDS
+ * @return
+ */
+ public boolean executeAction(String check, Action action, long time)
+ {
+
+ Map<Action, ExecutionHistoryEntry> executionHistory = executionHistories.get(check);
+
+ if (executionHistory == null)
+ {
+ executionHistory = new HashMap<Action, ExecutionHistoryEntry>();
+ executionHistories.put(check, executionHistory);
+ }
+
+ ExecutionHistoryEntry entry = executionHistory.get(action);
+
+ if (entry == null)
+ {
+ entry = new ExecutionHistoryEntry(60);
+ executionHistory.put(action, entry);
+ }
+
+ // update entry
+ entry.addCounter(time);
+
+ if (entry.getCounter() > action.delay)
+ {
+ // Execute action?
+ if (entry.getLastExecution() <= time - action.repeat)
+ {
+ // Execute action!
+ entry.setLastExecution(time);
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PlayerManager.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PlayerManager.java
new file mode 100644
index 000000000..7a13628c6
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PlayerManager.java
@@ -0,0 +1,80 @@
+package com.earth2me.essentials.anticheat.data;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.player.NoCheatPlayerImpl;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.bukkit.entity.Player;
+
+
+/**
+ * Provide secure access to player-specific data objects for various checks or check groups.
+ */
+public class PlayerManager
+{
+ // Store data between Events
+ private final Map<String, NoCheatPlayerImpl> players;
+ private final NoCheat plugin;
+
+ public PlayerManager(NoCheat plugin)
+ {
+ this.players = new HashMap<String, NoCheatPlayerImpl>();
+ this.plugin = plugin;
+ }
+
+ /**
+ * Get a data object of the specified class. If none is stored yet, create one.
+ */
+ public NoCheatPlayer getPlayer(Player player)
+ {
+
+ NoCheatPlayerImpl p = this.players.get(player.getName().toLowerCase());
+
+ if (p == null)
+ {
+ p = new NoCheatPlayerImpl(player, plugin);
+ this.players.put(player.getName().toLowerCase(), p);
+ }
+
+ p.setLastUsedTime(System.currentTimeMillis());
+ p.refresh(player);
+
+ return p;
+ }
+
+ public void cleanDataMap()
+ {
+ long time = System.currentTimeMillis();
+ List<String> removals = new ArrayList<String>(5);
+
+ for (Entry<String, NoCheatPlayerImpl> e : this.players.entrySet())
+ {
+ if (e.getValue().shouldBeRemoved(time))
+ {
+ removals.add(e.getKey());
+ }
+ }
+
+ for (String key : removals)
+ {
+ this.players.remove(key);
+ }
+ }
+
+ public Map<String, Object> getPlayerData(String playerName)
+ {
+
+ NoCheatPlayer player = this.players.get(playerName.toLowerCase());
+
+ if (player != null)
+ {
+ return player.getDataStore().collectData();
+ }
+
+ return new HashMap<String, Object>();
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PreciseLocation.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PreciseLocation.java
new file mode 100644
index 000000000..5cf828c96
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PreciseLocation.java
@@ -0,0 +1,51 @@
+package com.earth2me.essentials.anticheat.data;
+
+import org.bukkit.Location;
+
+
+/**
+ * A class to store x,y,z triple data, instead of using bukkits Location objects, which can't be easily recycled
+ *
+ */
+public final class PreciseLocation
+{
+ public double x;
+ public double y;
+ public double z;
+
+ public PreciseLocation()
+ {
+ reset();
+ }
+
+ public final void set(Location location)
+ {
+ x = location.getX();
+ y = location.getY();
+ z = location.getZ();
+ }
+
+ public final void set(PreciseLocation location)
+ {
+ x = location.x;
+ y = location.y;
+ z = location.z;
+ }
+
+ public final boolean isSet()
+ {
+ return x != Double.MAX_VALUE;
+ }
+
+ public final void reset()
+ {
+ x = Double.MAX_VALUE;
+ y = Double.MAX_VALUE;
+ z = Double.MAX_VALUE;
+ }
+
+ public final boolean equals(Location location)
+ {
+ return location.getX() == x && location.getY() == y && location.getZ() == z;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/SimpleLocation.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/SimpleLocation.java
new file mode 100644
index 000000000..34923051e
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/SimpleLocation.java
@@ -0,0 +1,76 @@
+package com.earth2me.essentials.anticheat.data;
+
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+
+
+/**
+ * To avoid constantly creating and referencing "Location" objects, which in turn reference a whole lot of other
+ * unnecessary stuff, rather use our own "Location" object which is easily reusable.
+ *
+ */
+public final class SimpleLocation
+{
+ public int x;
+ public int y;
+ public int z;
+
+ public SimpleLocation()
+ {
+ reset();
+ }
+
+ @Override
+ public final boolean equals(Object object)
+ {
+ if (!(object instanceof SimpleLocation))
+ {
+ return false;
+ }
+
+ SimpleLocation simpleLocation = (SimpleLocation)object;
+
+ if (!isSet() && !simpleLocation.isSet())
+ {
+ return true;
+ }
+ else if (!isSet() || !simpleLocation.isSet())
+ {
+ return false;
+ }
+
+ return simpleLocation.x == x && simpleLocation.y == y && simpleLocation.z == z;
+ }
+
+ @Override
+ public final int hashCode()
+ {
+ return x * 1000000 + y * 1000 + z;
+ }
+
+ public final void set(Block block)
+ {
+ x = block.getX();
+ y = block.getY();
+ z = block.getZ();
+ }
+
+ public final void setLocation(Location location)
+ {
+ x = location.getBlockX();
+ y = location.getBlockY();
+ z = location.getBlockZ();
+ }
+
+ public final boolean isSet()
+ {
+ return x != Integer.MAX_VALUE;
+ }
+
+ public final void reset()
+ {
+ x = Integer.MAX_VALUE;
+ y = Integer.MAX_VALUE;
+ z = Integer.MAX_VALUE;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/Statistics.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/Statistics.java
new file mode 100644
index 000000000..9c83e97d5
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/Statistics.java
@@ -0,0 +1,82 @@
+package com.earth2me.essentials.anticheat.data;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+
+public class Statistics
+{
+ public enum Id
+ {
+ BB_DIRECTION("blockbreak.direction"), BB_NOSWING("blockbreak.noswing"),
+ BB_REACH("blockbreak.reach"), BP_DIRECTION("blockplace.direction"),
+ BP_REACH("blockplace.reach"), CHAT_COLOR("chat.color"),
+ CHAT_SPAM("chat.spam"), FI_DIRECTION("fight.direction"),
+ FI_NOSWING("fight.noswing"), FI_REACH("fight.reach"),
+ FI_SPEED("fight.speed"), INV_DROP("inventory.drop"),
+ INV_BOW("inventory.instantbow"), INV_EAT("inventory.instanteat"),
+ MOV_RUNNING("moving.running"), MOV_FLYING("moving.flying"),
+ MOV_MOREPACKETS("moving.morepackets"), MOV_NOFALL("moving.nofall"),
+ MOV_SNEAKING("moving.sneaking"), MOV_SWIMMING("moving.swimming"),
+ FI_GODMODE("fight.godmode"), FI_INSTANTHEAL("fight.instantheal");
+ private final String name;
+
+ private Id(String name)
+ {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ return this.name;
+ }
+ }
+ private final Map<Id, Double> statisticVLs = new HashMap<Id, Double>(Id.values().length);
+ private final Map<Id, Integer> statisticFails = new HashMap<Id, Integer>(Id.values().length);
+
+ public Statistics()
+ {
+ // Initialize statistic values
+ for (Id id : Id.values())
+ {
+ statisticVLs.put(id, 0D);
+ statisticFails.put(id, 0);
+ }
+ }
+
+ public void increment(Id id, double vl)
+ {
+ Double stored = statisticVLs.get(id);
+ if (stored == null)
+ {
+ stored = 0D;
+ }
+ statisticVLs.put(id, stored + vl);
+
+ Integer failed = statisticFails.get(id);
+ if (failed == null)
+ {
+ failed = 0;
+ }
+ statisticFails.put(id, failed + 1);
+ }
+
+ public Map<String, Object> get()
+ {
+ Map<String, Object> map = new TreeMap<String, Object>();
+
+ for (Entry<Id, Double> entry : statisticVLs.entrySet())
+ {
+ map.put(entry.getKey().toString() + ".vl", entry.getValue().intValue());
+ }
+
+ for (Entry<Id, Integer> entry : statisticFails.entrySet())
+ {
+ map.put(entry.getKey().toString() + ".failed", entry.getValue());
+ }
+
+ return map;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/ActiveCheckPrinter.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/ActiveCheckPrinter.java
new file mode 100644
index 000000000..295acb9ef
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/ActiveCheckPrinter.java
@@ -0,0 +1,66 @@
+package com.earth2me.essentials.anticheat.debug;
+
+import com.earth2me.essentials.anticheat.EventManager;
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import java.util.List;
+import org.bukkit.World;
+
+
+/**
+ * Prints the list of active checks per world on startup, if requested
+ *
+ */
+public class ActiveCheckPrinter
+{
+ public static void printActiveChecks(NoCheat plugin, List<EventManager> eventManagers)
+ {
+
+ boolean introPrinted = false;
+
+ // Print active checks for NoCheat, if needed.
+ for (World world : plugin.getServer().getWorlds())
+ {
+
+ StringBuilder line = new StringBuilder(" ").append(world.getName()).append(": ");
+
+ int length = line.length();
+
+ ConfigurationCacheStore cc = plugin.getConfig(world);
+
+ if (!cc.logging.showactivechecks)
+ {
+ continue;
+ }
+
+ for (EventManager em : eventManagers)
+ {
+ if (em.getActiveChecks(cc).isEmpty())
+ {
+ continue;
+ }
+
+ for (String active : em.getActiveChecks(cc))
+ {
+ line.append(active).append(' ');
+ }
+
+ if (!introPrinted)
+ {
+ plugin.getLogger().info("Active Checks: ");
+ introPrinted = true;
+ }
+
+ plugin.getServer().getLogger().info(line.toString());
+
+ line = new StringBuilder(length);
+
+ for (int i = 0; i < length; i++)
+ {
+ line.append(' ');
+ }
+ }
+
+ }
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/LagMeasureTask.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/LagMeasureTask.java
new file mode 100644
index 000000000..4acb5a5f2
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/LagMeasureTask.java
@@ -0,0 +1,99 @@
+package com.earth2me.essentials.anticheat.debug;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import org.bukkit.World;
+
+
+/**
+ * A task running in the background that measures tick time vs. real time
+ *
+ */
+public class LagMeasureTask implements Runnable
+{
+ private int ingameseconds = 1;
+ private long lastIngamesecondTime = System.currentTimeMillis();
+ private long lastIngamesecondDuration = 2000L;
+ private boolean skipCheck = false;
+ private int lagMeasureTaskId = -1;
+ private final NoCheat plugin;
+
+ public LagMeasureTask(NoCheat plugin)
+ {
+ this.plugin = plugin;
+ }
+
+ public void start()
+ {
+ // start measuring with a delay of 10 seconds
+ lagMeasureTaskId = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, this, 20, 20);
+ }
+
+ public void run()
+ {
+ try
+ {
+ boolean oldStatus = skipCheck;
+ // If the previous second took to long, skip checks during
+ // this second
+ skipCheck = lastIngamesecondDuration > 2000;
+
+ if (plugin.getConfig((World)null).logging.debugmessages)
+ {
+ if (oldStatus != skipCheck && skipCheck)
+ {
+ plugin.getLogger().warning("detected server lag, some checks will not work.");
+ }
+ else if (oldStatus != skipCheck && !skipCheck)
+ {
+ plugin.getLogger().info("server lag seems to have stopped, reenabling checks.");
+ }
+ }
+
+ long time = System.currentTimeMillis();
+ lastIngamesecondDuration = time - lastIngamesecondTime;
+ if (lastIngamesecondDuration < 1000)
+ {
+ lastIngamesecondDuration = 1000;
+ }
+ else if (lastIngamesecondDuration > 3600000)
+ {
+ lastIngamesecondDuration = 3600000; // top limit of 1
+ // hour per "second"
+ }
+ lastIngamesecondTime = time;
+ ingameseconds++;
+
+ // Check if some data is outdated now and let it be removed
+ if (ingameseconds % 62 == 0)
+ {
+ plugin.cleanDataMap();
+ }
+ }
+ catch (Exception e)
+ {
+ // Just prevent this thread from dying for whatever reason
+ }
+
+ }
+
+ public void cancel()
+ {
+ if (lagMeasureTaskId != -1)
+ {
+ try
+ {
+ plugin.getServer().getScheduler().cancelTask(lagMeasureTaskId);
+ }
+ catch (Exception e)
+ {
+ plugin.getLogger().warning("Couldn't cancel LagMeasureTask: " + e.getMessage());
+ }
+ lagMeasureTaskId = -1;
+ }
+ }
+
+ public boolean skipCheck()
+ {
+ return skipCheck;
+ }
+}
diff --git a/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/player/NoCheatPlayerImpl.java b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/player/NoCheatPlayerImpl.java
new file mode 100644
index 000000000..07f66b375
--- /dev/null
+++ b/EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/player/NoCheatPlayerImpl.java
@@ -0,0 +1,151 @@
+package com.earth2me.essentials.anticheat.player;
+
+import com.earth2me.essentials.anticheat.NoCheat;
+import com.earth2me.essentials.anticheat.NoCheatPlayer;
+import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
+import com.earth2me.essentials.anticheat.data.DataStore;
+import com.earth2me.essentials.anticheat.data.ExecutionHistory;
+import net.minecraft.server.EntityPlayer;
+import net.minecraft.server.MobEffectList;
+import org.bukkit.GameMode;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+
+
+public class NoCheatPlayerImpl implements NoCheatPlayer
+{
+ private Player player;
+ private final NoCheat plugin;
+ private final DataStore data;
+ private ConfigurationCacheStore config;
+ private long lastUsedTime;
+ private final ExecutionHistory history;
+
+ public NoCheatPlayerImpl(Player player, NoCheat plugin)
+ {
+
+ this.player = player;
+ this.plugin = plugin;
+ this.data = new DataStore();
+ this.history = new ExecutionHistory();
+
+ this.lastUsedTime = System.currentTimeMillis();
+ }
+
+ public void refresh(Player player)
+ {
+ this.player = player;
+ this.config = plugin.getConfig(player);
+ }
+
+ public boolean isDead()
+ {
+ return this.player.getHealth() <= 0 || this.player.isDead();
+ }
+
+ public boolean hasPermission(String permission)
+ {
+ return player.hasPermission(permission);
+ }
+
+ public DataStore getDataStore()
+ {
+ return data;
+ }
+
+ public ConfigurationCacheStore getConfigurationStore()
+ {
+ return config;
+ }
+
+ public Player getPlayer()
+ {
+ return player;
+ }
+
+ public String getName()
+ {
+ return player.getName();
+ }
+
+ public int getTicksLived()
+ {
+ return player.getTicksLived();
+ }
+
+ public float getSpeedAmplifier()
+ {
+ EntityPlayer ep = ((CraftPlayer)player).getHandle();
+ if (ep.hasEffect(MobEffectList.FASTER_MOVEMENT))
+ {
+ // Taken directly from Minecraft code, should work
+ return 1.0F + 0.2F * (float)(ep.getEffect(MobEffectList.FASTER_MOVEMENT).getAmplifier() + 1); // TODO
+ }
+ else
+ {
+ return 1.0F;
+ }
+ }
+
+ @Override
+ public float getJumpAmplifier()
+ {
+ EntityPlayer ep = ((CraftPlayer)player).getHandle();
+ if (ep.hasEffect(MobEffectList.JUMP))
+ {
+ int amp = ep.getEffect(MobEffectList.JUMP).getAmplifier();
+ // Very rough estimates only
+ // TODO
+ if (amp > 20)
+ {
+ return 1.5F * (float)(ep.getEffect(MobEffectList.JUMP).getAmplifier() + 1);
+ }
+ else
+ {
+ return 1.2F * (float)(ep.getEffect(MobEffectList.JUMP).getAmplifier() + 1);
+ }
+ }
+ else
+ {
+ return 1.0F;
+ }
+ }
+
+ public boolean isSprinting()
+ {
+ return player.isSprinting();
+ }
+
+ public void setLastUsedTime(long currentTimeInMilliseconds)
+ {
+ this.lastUsedTime = currentTimeInMilliseconds;
+ }
+
+ public boolean shouldBeRemoved(long currentTimeInMilliseconds)
+ {
+ if (lastUsedTime > currentTimeInMilliseconds)
+ {
+ // Should never happen, but if it does, fix it somewhat
+ lastUsedTime = currentTimeInMilliseconds;
+ }
+ return lastUsedTime + 60000L < currentTimeInMilliseconds;
+ }
+
+ public boolean isCreative()
+ {
+ return player.getGameMode() == GameMode.CREATIVE;
+ }
+
+ @Override
+ public ExecutionHistory getExecutionHistory()
+ {
+ return history;
+ }
+
+ @Override
+ public void dealFallDamage()
+ {
+ EntityPlayer p = ((CraftPlayer)player).getHandle();
+ p.b(0D, true);
+ }
+}
diff --git a/EssentialsAntiCheat/src/plugin.yml b/EssentialsAntiCheat/src/plugin.yml
new file mode 100644
index 000000000..d96fea0f0
--- /dev/null
+++ b/EssentialsAntiCheat/src/plugin.yml
@@ -0,0 +1,94 @@
+name: EssentialsAntiCheat
+main: com.earth2me.essentials.anticheat.NoCheat
+# Note to developers: This next line cannot change, or the automatic versioning system will break.
+version: TeamCity
+website: http://tiny.cc/EssentialsWiki
+description: Detect and Fight the exploitation of various Flaws/Bugs in Minecraft.
+authors: [Evenprime, md_5]
+commands:
+ nocheat:
+ description: NoCheat command(s)
+ permission: nocheat.admin.commands
+ usage: |
+ /<command> permlist player [permission]: list NoCheat permissions of player, optionally only if beginning with [permission]
+ /<command> playerinfo player: show the collected data NoCheat collected about a player
+ /<command> reload: fast reload of NoCheats configuration file(s) - needs additional permissions
+
+permissions:
+ nocheat:
+ description: Allow a player to bypass all checks and give him all admin permissions
+ children:
+ nocheat.admin:
+ description: Give a player all admin rights
+ children:
+ nocheat.admin.chatlog:
+ description: Player can see NoCheats log messages in the ingame chat
+ nocheat.admin.commands:
+ description: allow use of the "nocheat" commands (may be given to players to allow them to check statistics)
+ nocheat.admin.reload:
+ description: allow access to the special "nocheat reload" command (only intended for the actual server administrator)
+ nocheat.checks:
+ description: Allow the player to bypass all checks
+ children:
+ nocheat.checks.moving:
+ description: Allow the player to bypass all moving related checks
+ children:
+ nocheat.checks.moving.runfly:
+ description: Allow a player to move as free and as fast as he wants (ignores flying, swimming and sneaking settings)
+ nocheat.checks.moving.flying:
+ description: Allow a player to fly, but only within given speed limits (ignores swimming and sneaking settings)
+ nocheat.checks.moving.swimming:
+ description: Allow a player to move through water without slowdown
+ nocheat.checks.moving.sneaking:
+ description: Allow a player to sneak without slowdown
+ nocheat.checks.moving.nofall:
+ description: Allow a player to cheat and not take fall damage at all
+ nocheat.checks.moving.morepackets:
+ description: Allow a player to send more move-event-packets than normal, causing him to move faster than normal
+ nocheat.checks.blockbreak:
+ description: Allow the player to bypass all blockbreak checks
+ children:
+ nocheat.checks.blockbreak.reach:
+ description: Allow a player to break blocks at maximum range (about 6-7 blocks, in creative mode unlimited)
+ nocheat.checks.blockbreak.direction:
+ description: Allow a player to break blocks that are not in front of them
+ nocheat.checks.blockbreak.noswing:
+ description: Allow a player to break blocks without swinging their arm
+ nocheat.checks.blockplace:
+ description: Allow the player to bypass all blockplace checks
+ children:
+ nocheat.checks.blockplace.reach:
+ description: Allow a player to place blocks at maximum range (about 6-7 blocks)
+ nocheat.checks.blockplace.direction:
+ description: Allow a player to place blocks outside their line of view
+ nocheat.checks.chat:
+ description: Allow the player to bypass all chat checks
+ children:
+ nocheat.checks.chat.spam:
+ description: Allow a player to send an infinite amount of chat messages
+ nocheat.checks.chat.color:
+ description: Allow a player to send colored chat messages
+ nocheat.checks.fight:
+ description: Allow the player to bypass all fight checks
+ children:
+ nocheat.checks.fight.direction:
+ description: Allow a player to attack players and monster even if they are not in his field of view
+ nocheat.checks.fight.noswing:
+ description: Allow a player to fight without swinging their arm
+ nocheat.checks.fight.reach:
+ description: Allow a player to fight over bigger distances than usual
+ nocheat.checks.fight.speed:
+ description: Allow a player to attack faster than usual
+ nocheat.checks.fight.godmode:
+ description: Allow a player to not take damage by exploiting a design flaw in Minecraft
+ nocheat.checks.fight.instantheal:
+ description: Allow a player to artificially speed up his health regeneration
+ nocheat.checks.inventory:
+ description: Allow the player to bypass all inventory checks
+ children:
+ nocheat.checks.inventory.drop:
+ description: Allow a player to drop more items in a short timeframe than the defined limit
+ nocheat.checks.inventory.instanteat:
+ description: Allow a player to eat food faster than normally possible
+ nocheat.checks.inventory.instantbow:
+ description: Allow a player to charge his bow faster than usual
diff --git a/packager.xml b/packager.xml
index 75937728f..cc86ec046 100644
--- a/packager.xml
+++ b/packager.xml
@@ -12,6 +12,7 @@
<flattenmapper/>
</copy>
<move file="jars/Essentials-3.0-SNAPSHOT.jar" tofile="jars/Essentials.jar"/>
+ <move file="jars/EssentialsAntiCheat-3.0-SNAPSHOT.jar" tofile="jars/EssentialsAntiCheat.jar"/>
<move file="jars/EssentialsChat-3.0-SNAPSHOT.jar" tofile="jars/EssentialsChat.jar"/>
<move file="jars/EssentialsProtect-3.0-SNAPSHOT.jar" tofile="jars/EssentialsProtect.jar"/>
<move file="jars/EssentialsGeoIP-3.0-SNAPSHOT.jar" tofile="jars/EssentialsGeoIP.jar"/>
diff --git a/pom.xml b/pom.xml
index e27de1e90..827872d91 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
<modules>
<module>Essentials</module>
<module>Essentials2Compat</module>
+ <module>EssentialsAntiCheat</module>
<module>EssentialsChat</module>
<module>EssentialsGeoIP</module>
<module>EssentialsGroupBridge</module>
@@ -34,7 +35,7 @@
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
- <version>1.2.3-R0.1-SNAPSHOT</version>
+ <version>1.2.3-R0.2-SNAPSHOT</version>
</dependency>
</dependencies>