From 78f4820876f42f7b50bf88f64afd45bee939e4e4 Mon Sep 17 00:00:00 2001 From: md_5 Date: Mon, 12 Mar 2012 10:39:36 +1100 Subject: Initial formatted and slightly tweaked version of @evenprime 's NoCheat. Will be intergrated into the main Essentials as soon as possible --- EssentialsAntiCheat/Instructions.txt | 939 +++++++++++++++++++++ EssentialsAntiCheat/README.txt | 9 + EssentialsAntiCheat/pom.xml | 21 + .../com/earth2me/essentials/anticheat/Colors.java | 42 + .../earth2me/essentials/anticheat/ConfigItem.java | 6 + .../earth2me/essentials/anticheat/DataItem.java | 11 + .../essentials/anticheat/EventManager.java | 17 + .../com/earth2me/essentials/anticheat/NoCheat.java | 204 +++++ .../essentials/anticheat/NoCheatLogEvent.java | 78 ++ .../essentials/anticheat/NoCheatPlayer.java | 36 + .../essentials/anticheat/actions/Action.java | 32 + .../anticheat/actions/ParameterName.java | 35 + .../anticheat/actions/types/ActionList.java | 86 ++ .../actions/types/ActionWithParameters.java | 94 +++ .../actions/types/ConsolecommandAction.java | 39 + .../anticheat/actions/types/DummyAction.java | 26 + .../anticheat/actions/types/LogAction.java | 76 ++ .../anticheat/actions/types/SpecialAction.java | 22 + .../essentials/anticheat/checks/Check.java | 159 ++++ .../essentials/anticheat/checks/CheckUtil.java | 372 ++++++++ .../anticheat/checks/WorkaroundsListener.java | 55 ++ .../checks/blockbreak/BlockBreakCheck.java | 64 ++ .../checks/blockbreak/BlockBreakCheckListener.java | 186 ++++ .../checks/blockbreak/BlockBreakConfig.java | 40 + .../checks/blockbreak/BlockBreakData.java | 29 + .../checks/blockbreak/DirectionCheck.java | 100 +++ .../anticheat/checks/blockbreak/NoswingCheck.java | 61 ++ .../anticheat/checks/blockbreak/ReachCheck.java | 75 ++ .../checks/blockplace/BlockPlaceCheck.java | 99 +++ .../checks/blockplace/BlockPlaceCheckListener.java | 97 +++ .../checks/blockplace/BlockPlaceConfig.java | 37 + .../checks/blockplace/BlockPlaceData.java | 25 + .../checks/blockplace/DirectionCheck.java | 131 +++ .../anticheat/checks/blockplace/ReachCheck.java | 75 ++ .../anticheat/checks/chat/ChatCheck.java | 79 ++ .../anticheat/checks/chat/ChatCheckListener.java | 108 +++ .../anticheat/checks/chat/ChatConfig.java | 64 ++ .../essentials/anticheat/checks/chat/ChatData.java | 22 + .../anticheat/checks/chat/ColorCheck.java | 50 ++ .../anticheat/checks/chat/SpamCheck.java | 96 +++ .../anticheat/checks/fight/DirectionCheck.java | 120 +++ .../anticheat/checks/fight/FightCheck.java | 69 ++ .../anticheat/checks/fight/FightCheckListener.java | 291 +++++++ .../anticheat/checks/fight/FightConfig.java | 58 ++ .../anticheat/checks/fight/FightData.java | 40 + .../anticheat/checks/fight/GodmodeCheck.java | 151 ++++ .../anticheat/checks/fight/InstanthealCheck.java | 94 +++ .../anticheat/checks/fight/NoswingCheck.java | 67 ++ .../anticheat/checks/fight/ReachCheck.java | 113 +++ .../anticheat/checks/fight/SpeedCheck.java | 81 ++ .../anticheat/checks/inventory/DropCheck.java | 71 ++ .../checks/inventory/InstantBowCheck.java | 72 ++ .../checks/inventory/InstantEatCheck.java | 78 ++ .../anticheat/checks/inventory/InventoryCheck.java | 63 ++ .../checks/inventory/InventoryCheckListener.java | 196 +++++ .../checks/inventory/InventoryConfig.java | 40 + .../anticheat/checks/inventory/InventoryData.java | 25 + .../anticheat/checks/moving/FlyingCheck.java | 170 ++++ .../anticheat/checks/moving/MorePacketsCheck.java | 132 +++ .../anticheat/checks/moving/MovingCheck.java | 93 ++ .../checks/moving/MovingCheckListener.java | 376 +++++++++ .../anticheat/checks/moving/MovingConfig.java | 71 ++ .../anticheat/checks/moving/MovingData.java | 69 ++ .../anticheat/checks/moving/NoFallCheck.java | 151 ++++ .../anticheat/checks/moving/RunningCheck.java | 303 +++++++ .../anticheat/command/CommandHandler.java | 163 ++++ .../essentials/anticheat/config/ActionFactory.java | 183 ++++ .../essentials/anticheat/config/ConfPaths.java | 142 ++++ .../anticheat/config/ConfigurationCacheStore.java | 45 + .../anticheat/config/ConfigurationManager.java | 257 ++++++ .../anticheat/config/DefaultConfiguration.java | 154 ++++ .../essentials/anticheat/config/LoggingConfig.java | 29 + .../anticheat/config/NoCheatConfiguration.java | 82 ++ .../essentials/anticheat/config/Permissions.java | 44 + .../essentials/anticheat/data/DataStore.java | 38 + .../anticheat/data/ExecutionHistory.java | 145 ++++ .../essentials/anticheat/data/PlayerManager.java | 80 ++ .../essentials/anticheat/data/PreciseLocation.java | 51 ++ .../essentials/anticheat/data/SimpleLocation.java | 76 ++ .../essentials/anticheat/data/Statistics.java | 82 ++ .../anticheat/debug/ActiveCheckPrinter.java | 66 ++ .../essentials/anticheat/debug/LagMeasureTask.java | 99 +++ .../anticheat/player/NoCheatPlayerImpl.java | 151 ++++ EssentialsAntiCheat/src/plugin.yml | 94 +++ packager.xml | 1 + pom.xml | 3 +- 86 files changed, 8775 insertions(+), 1 deletion(-) create mode 100644 EssentialsAntiCheat/Instructions.txt create mode 100644 EssentialsAntiCheat/README.txt create mode 100644 EssentialsAntiCheat/pom.xml create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/Colors.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/ConfigItem.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/DataItem.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/EventManager.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheat.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatLogEvent.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/NoCheatPlayer.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/Action.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/ParameterName.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionList.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ActionWithParameters.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/ConsolecommandAction.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/DummyAction.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/LogAction.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/actions/types/SpecialAction.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/Check.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/CheckUtil.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/WorkaroundsListener.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakCheckListener.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakConfig.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/BlockBreakData.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/DirectionCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/NoswingCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockbreak/ReachCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceCheckListener.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceConfig.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/BlockPlaceData.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/DirectionCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/blockplace/ReachCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatCheckListener.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatConfig.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ChatData.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/ColorCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/chat/SpamCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/DirectionCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightCheckListener.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightConfig.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/FightData.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/GodmodeCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/InstanthealCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/NoswingCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/ReachCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/fight/SpeedCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/DropCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantBowCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InstantEatCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryCheckListener.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryConfig.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/inventory/InventoryData.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/FlyingCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MorePacketsCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingCheckListener.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingConfig.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/MovingData.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/NoFallCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/checks/moving/RunningCheck.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/command/CommandHandler.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ActionFactory.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfPaths.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationCacheStore.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/ConfigurationManager.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/DefaultConfiguration.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/LoggingConfig.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/NoCheatConfiguration.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/config/Permissions.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/DataStore.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/ExecutionHistory.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PlayerManager.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/PreciseLocation.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/SimpleLocation.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/data/Statistics.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/ActiveCheckPrinter.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/debug/LagMeasureTask.java create mode 100644 EssentialsAntiCheat/src/com/earth2me/essentials/anticheat/player/NoCheatPlayerImpl.java create mode 100644 EssentialsAntiCheat/src/plugin.yml 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) + + +# 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 @@ + + + 4.0.0 + + + net.essentials3 + BuildAll + 3.0-SNAPSHOT + ../pom.xml + + + EssentialsAntiCheat + + + org.bukkit + craftbukkit + 1.2.3-R0.2-SNAPSHOT + + + 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 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 eventManagers = new ArrayList(); + 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 getPlayerData(String playerName) + { + + Map 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 actions = new HashMap(); + // The tresholds of this list + private final List tresholds = new ArrayList(); + + /** + * 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 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 messageParts; + + public ActionWithParameters(String name, int delay, int repeat, String message) + { + super(name, delay, repeat); + + messageParts = new ArrayList(); + + 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 foods = new HashSet(); + + 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 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 getActiveChecks(ConfigurationCacheStore cc) + { + LinkedList s = new LinkedList(); + + 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 getActiveChecks(ConfigurationCacheStore cc) + { + LinkedList s = new LinkedList(); + + 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 getActiveChecks(ConfigurationCacheStore cc) + { + LinkedList s = new LinkedList(); + + 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 strings = new LinkedList(); + 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 checks; + private final GodmodeCheck godmodeCheck; + private final InstanthealCheck instanthealCheck; + private final NoCheat plugin; + + public FightCheckListener(NoCheat plugin) + { + + this.checks = new ArrayList(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 getActiveChecks(ConfigurationCacheStore cc) + { + LinkedList s = new LinkedList(); + + 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 getActiveChecks(ConfigurationCacheStore cc) + { + LinkedList s = new LinkedList(); + + 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 getActiveChecks(ConfigurationCacheStore cc) + { + LinkedList s = new LinkedList(); + + 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 perms; + + public CommandHandler(NoCheat plugin) + { + // Make a copy to allow sorting + perms = new LinkedList(plugin.getDescription().getPermissions()); + + // Sort NoCheats permission by name and parent-child relation with + // a custom sorting method + Collections.sort(perms, new Comparator() + { + 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 map = plugin.getPlayerData(args[1]); + String filter = ""; + + if (args.length > 2) + { + filter = args[2]; + } + + sender.sendMessage("PlayerInfo for " + args[1]); + for (Entry 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 lib = new HashMap(); + + public ActionFactory(Map 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 actions = new ArrayList(); + + 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 configMap = new HashMap(); + 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 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 worldnameToConfigCacheMap = new HashMap(); + 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 worldFiles = getWorldSpecificConfigFiles(rootConfigFolder); + + for (Entry 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 getWorldSpecificConfigFiles(File rootFolder) + { + + HashMap files = new HashMap(); + + 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 dataMap = new HashMap(); + private final Statistics statistics = new Statistics(); + private final long timestamp = System.currentTimeMillis(); + + @SuppressWarnings("unchecked") + public T get(String id) + { + return (T)dataMap.get(id); + } + + public void set(String id, DataItem data) + { + dataMap.put(id, data); + } + + public Map collectData() + { + Map 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> executionHistories; + + public ExecutionHistory() + { + executionHistories = new HashMap>(); + } + + /** + * 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 executionHistory = executionHistories.get(check); + + if (executionHistory == null) + { + executionHistory = new HashMap(); + 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 players; + private final NoCheat plugin; + + public PlayerManager(NoCheat plugin) + { + this.players = new HashMap(); + 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 removals = new ArrayList(5); + + for (Entry e : this.players.entrySet()) + { + if (e.getValue().shouldBeRemoved(time)) + { + removals.add(e.getKey()); + } + } + + for (String key : removals) + { + this.players.remove(key); + } + } + + public Map getPlayerData(String playerName) + { + + NoCheatPlayer player = this.players.get(playerName.toLowerCase()); + + if (player != null) + { + return player.getDataStore().collectData(); + } + + return new HashMap(); + } +} 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 statisticVLs = new HashMap(Id.values().length); + private final Map statisticFails = new HashMap(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 get() + { + Map map = new TreeMap(); + + for (Entry entry : statisticVLs.entrySet()) + { + map.put(entry.getKey().toString() + ".vl", entry.getValue().intValue()); + } + + for (Entry 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 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: | + / permlist player [permission]: list NoCheat permissions of player, optionally only if beginning with [permission] + / playerinfo player: show the collected data NoCheat collected about a player + / 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 @@ + diff --git a/pom.xml b/pom.xml index e27de1e90..827872d91 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ Essentials Essentials2Compat + EssentialsAntiCheat EssentialsChat EssentialsGeoIP EssentialsGroupBridge @@ -34,7 +35,7 @@ org.bukkit bukkit - 1.2.3-R0.1-SNAPSHOT + 1.2.3-R0.2-SNAPSHOT -- cgit v1.2.3