1
0
mirror of https://github.com/essentials/Essentials.git synced 2025-09-24 21:31:32 +02:00

Initial formatted and slightly tweaked version of @evenprime 's NoCheat. Will be intergrated into the main Essentials as soon as possible

This commit is contained in:
md_5
2012-03-12 10:39:36 +11:00
parent 2640905846
commit 78f4820876
86 changed files with 8775 additions and 1 deletions

View File

@@ -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. ;)

View File

@@ -0,0 +1,9 @@
Copyright (c) 2012 Wilfried Pasquazzo (Evenprime)
<wilfried.pasquazzo@gmail.com>
# Dual-Licensed - you may freely choose between (or use both):
#
# 1) GPL v3 (see LICENSE_GPL3.txt)
# 2) MIT (see LICENSE_MIT.txt)
#
#

View File

@@ -0,0 +1,21 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.essentials3</groupId>
<artifactId>BuildAll</artifactId>
<version>3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>EssentialsAntiCheat</artifactId>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.2.3-R0.2-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@@ -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;
}
}

View File

@@ -0,0 +1,6 @@
package com.earth2me.essentials.anticheat;
public interface ConfigItem
{
}

View File

@@ -0,0 +1,11 @@
package com.earth2me.essentials.anticheat;
/**
*
* Every class that is extending this has to implement an empty Constructor()
*
*/
public interface DataItem
{
}

View File

@@ -0,0 +1,17 @@
package com.earth2me.essentials.anticheat;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import java.util.List;
import org.bukkit.event.Listener;
public interface EventManager extends Listener
{
/**
* Used for debug output, if checks are activated for the world-specific config that is given as a parameter
*
* @param cc The config
* @return A list of active/enabled checks
*/
public List<String> getActiveChecks(ConfigurationCacheStore cc);
}

View File

@@ -0,0 +1,204 @@
package com.earth2me.essentials.anticheat;
import com.earth2me.essentials.anticheat.checks.WorkaroundsListener;
import com.earth2me.essentials.anticheat.checks.blockbreak.BlockBreakCheckListener;
import com.earth2me.essentials.anticheat.checks.blockplace.BlockPlaceCheckListener;
import com.earth2me.essentials.anticheat.checks.chat.ChatCheckListener;
import com.earth2me.essentials.anticheat.checks.fight.FightCheckListener;
import com.earth2me.essentials.anticheat.checks.inventory.InventoryCheckListener;
import com.earth2me.essentials.anticheat.checks.moving.MovingCheckListener;
import com.earth2me.essentials.anticheat.command.CommandHandler;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import com.earth2me.essentials.anticheat.config.ConfigurationManager;
import com.earth2me.essentials.anticheat.config.Permissions;
import com.earth2me.essentials.anticheat.data.PlayerManager;
import com.earth2me.essentials.anticheat.debug.ActiveCheckPrinter;
import com.earth2me.essentials.anticheat.debug.LagMeasureTask;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
/**
*
* NoCheat
*
* Check various player events for their plausibility and log/deny them/react to them based on configuration
*/
public class NoCheat extends JavaPlugin implements Listener
{
private ConfigurationManager conf;
private CommandHandler commandHandler;
private PlayerManager players = new PlayerManager(this);
private List<EventManager> eventManagers = new ArrayList<EventManager>();
private LagMeasureTask lagMeasureTask;
private Logger fileLogger;
@Override
public void onDisable()
{
if (lagMeasureTask != null)
{
lagMeasureTask.cancel();
}
if (conf != null)
{
conf.cleanup();
}
// Just to be sure nothing gets left out
getServer().getScheduler().cancelTasks(this);
}
@Override
public void onEnable()
{
commandHandler = new CommandHandler(this);
conf = new ConfigurationManager(this, getDataFolder());
// Set up the event listeners
eventManagers.add(new MovingCheckListener(this));
eventManagers.add(new WorkaroundsListener());
eventManagers.add(new ChatCheckListener(this));
eventManagers.add(new BlockBreakCheckListener(this));
eventManagers.add(new BlockPlaceCheckListener(this));
eventManagers.add(new FightCheckListener(this));
eventManagers.add(new InventoryCheckListener(this));
// Then set up a task to monitor server lag
if (lagMeasureTask == null)
{
lagMeasureTask = new LagMeasureTask(this);
lagMeasureTask.start();
}
// Then print a list of active checks per world
ActiveCheckPrinter.printActiveChecks(this, eventManagers);
// register all listeners
for (EventManager eventManager : eventManagers)
{
Bukkit.getPluginManager().registerEvents(eventManager, this);
}
getServer().getPluginManager().registerEvents(this, this);
}
public ConfigurationCacheStore getConfig(Player player)
{
if (player != null)
{
return getConfig(player.getWorld());
}
else
{
return conf.getConfigurationCacheForWorld(null);
}
}
public ConfigurationCacheStore getConfig(World world)
{
if (world != null)
{
return conf.getConfigurationCacheForWorld(world.getName());
}
else
{
return conf.getConfigurationCacheForWorld(null);
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
{
boolean result = commandHandler.handleCommand(this, sender, command, label, args);
return result;
}
public boolean skipCheck()
{
if (lagMeasureTask != null)
{
return lagMeasureTask.skipCheck();
}
return false;
}
public void reloadConfiguration()
{
conf.cleanup();
this.conf = new ConfigurationManager(this, this.getDataFolder());
players.cleanDataMap();
}
/**
* Call this periodically to walk over the stored data map and remove old/unused entries
*
*/
public void cleanDataMap()
{
players.cleanDataMap();
}
/**
* An interface method usable by other plugins to collect information about a player. It will include the plugin
* version, two timestamps (beginning and end of data collection for that player), and various data from checks)
*
* @param playerName a player name
* @return A newly created map of identifiers and corresponding values
*/
public Map<String, Object> getPlayerData(String playerName)
{
Map<String, Object> map = players.getPlayerData(playerName);
map.put("nocheat.version", this.getDescription().getVersion());
return map;
}
public NoCheatPlayer getPlayer(Player player)
{
return players.getPlayer(player);
}
@EventHandler(priority = EventPriority.MONITOR)
public void logEvent(NoCheatLogEvent event)
{
if (event.toConsole())
{
// Console logs are not colored
getServer().getLogger().info(Colors.removeColors(event.getPrefix() + event.getMessage()));
}
if (event.toChat())
{
for (Player player : Bukkit.getServer().getOnlinePlayers())
{
if (player.hasPermission(Permissions.ADMIN_CHATLOG))
{
// Chat logs are potentially colored
player.sendMessage(Colors.replaceColors(event.getPrefix() + event.getMessage()));
}
}
}
if (event.toFile())
{
// File logs are not colored
fileLogger.info(Colors.removeColors(event.getMessage()));
}
}
public void setFileLogger(Logger logger)
{
this.fileLogger = logger;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,86 @@
package com.earth2me.essentials.anticheat.actions.types;
import com.earth2me.essentials.anticheat.actions.Action;
import java.util.*;
/**
* A list of actions, that associates actions to tresholds. It allows to retrieve all actions that match a certain
* treshold
*
*/
public class ActionList
{
// This is a very bad design decision, but it's also really
// convenient to define this here
public final String permissionSilent;
public ActionList(String permission)
{
this.permissionSilent = permission + ".silent";
}
// If there are no actions registered, we still return an Array. It's
// just empty/size=0
private final static Action[] emptyArray = new Action[0];
// The actions of this ActionList, "bundled" by treshold (violation level)
private final Map<Integer, Action[]> actions = new HashMap<Integer, Action[]>();
// The tresholds of this list
private final List<Integer> tresholds = new ArrayList<Integer>();
/**
* Add an entry to this actionList. The list will be sorted by tresholds automatically after the insertion.
*
* @param treshold The minimum violation level a player needs to have to be suspected to the given actions
* @param actions The actions that will be used if the player reached the accompanying treshold/violation level
*/
public void setActions(Integer treshold, Action[] actions)
{
if (!this.tresholds.contains(treshold))
{
this.tresholds.add(treshold);
Collections.sort(this.tresholds);
}
this.actions.put(treshold, actions);
}
/**
* Get a list of actions that match the violation level. The only method that has to be called by a check
*
* @param violationLevel The violation level that should be matched.
* @return The array of actions whose treshold was closest to the violationLevel but not bigger
*/
public Action[] getActions(double violationLevel)
{
Integer result = null;
for (Integer treshold : tresholds)
{
if (treshold <= violationLevel)
{
result = treshold;
}
}
if (result != null)
{
return actions.get(result);
}
else
{
return emptyArray;
}
}
/**
* Get a sorted list of the tresholds/violation levels that were used in this list
*
* @return The sorted list of tresholds
*/
public List<Integer> getTresholds()
{
return tresholds;
}
}

View File

@@ -0,0 +1,94 @@
package com.earth2me.essentials.anticheat.actions.types;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.actions.Action;
import com.earth2me.essentials.anticheat.actions.ParameterName;
import com.earth2me.essentials.anticheat.checks.Check;
import java.util.ArrayList;
/**
* Action with parameters is used to
*
*/
public abstract class ActionWithParameters extends Action
{
private final ArrayList<Object> messageParts;
public ActionWithParameters(String name, int delay, int repeat, String message)
{
super(name, delay, repeat);
messageParts = new ArrayList<Object>();
parseMessage(message);
}
private void parseMessage(String message)
{
String parts[] = message.split("\\[", 2);
// No opening braces left
if (parts.length != 2)
{
messageParts.add(message);
}
// Found an opening brace
else
{
String parts2[] = parts[1].split("\\]", 2);
// Found no matching closing brace
if (parts2.length != 2)
{
messageParts.add(message);
}
// Found a matching closing brace
else
{
ParameterName w = ParameterName.get(parts2[0]);
if (w != null)
{
// Found an existing wildcard inbetween the braces
messageParts.add(parts[0]);
messageParts.add(w);
// Go further down recursive
parseMessage(parts2[1]);
}
else
{
messageParts.add(message);
}
}
}
}
/**
* Get a string with all the wildcards replaced with data from LogData
*
* @param data
* @return
*/
protected String getMessage(NoCheatPlayer player, Check check)
{
StringBuilder log = new StringBuilder(100); // Should be big enough most
// of the time
for (Object part : messageParts)
{
if (part instanceof String)
{
log.append((String)part);
}
else
{
log.append(check.getParameter((ParameterName)part, player));
}
}
return log.toString();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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" : "");
}
}

View File

@@ -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";
}
}

View File

@@ -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 + ".";
}
}
}

View File

@@ -0,0 +1,372 @@
package com.earth2me.essentials.anticheat.checks;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.data.PreciseLocation;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.server.Block;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
/**
* Some stuff that's used by different checks or just too complex to keep in other places
*
*/
public class CheckUtil
{
/**
* Check if a player looks at a target of a specific size, with a specific precision value (roughly)
*/
public static double directionCheck(final NoCheatPlayer player, final double targetX, final double targetY, final double targetZ, final double targetWidth, final double targetHeight, final double precision)
{
// Eye location of the player
final Location eyes = player.getPlayer().getEyeLocation();
final double factor = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) + Math.pow(eyes.getZ() - targetZ, 2));
// View direction of the player
final Vector direction = eyes.getDirection();
final double x = targetX - eyes.getX();
final double y = targetY - eyes.getY();
final double z = targetZ - eyes.getZ();
final double xPrediction = factor * direction.getX();
final double yPrediction = factor * direction.getY();
final double zPrediction = factor * direction.getZ();
double off = 0.0D;
off += Math.max(Math.abs(x - xPrediction) - (targetWidth / 2 + precision), 0.0D);
off += Math.max(Math.abs(z - zPrediction) - (targetWidth / 2 + precision), 0.0D);
off += Math.max(Math.abs(y - yPrediction) - (targetHeight / 2 + precision), 0.0D);
if (off > 1)
{
off = Math.sqrt(off);
}
return off;
}
/**
* Check if a player is close enough to a target, based on his eye location
*
* @param player
* @param targetX
* @param targetY
* @param targetZ
* @param limit
* @return
*/
public static final double reachCheck(final NoCheatPlayer player, final double targetX, final double targetY, final double targetZ, final double limit)
{
final Location eyes = player.getPlayer().getEyeLocation();
final double distance = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) + Math.pow(eyes.getZ() - targetZ, 2));
return Math.max(distance - limit, 0.0D);
}
private final static double magic = 0.45D;
private final static double magic2 = 0.55D;
private static final int NONSOLID = 1; // 0x00000001
private static final int SOLID = 2; // 0x00000010
// All liquids are "nonsolid" too
private static final int LIQUID = 4 | NONSOLID; // 0x00000101
// All ladders are "nonsolid" and "solid" too
private static final int LADDER = 8 | NONSOLID | SOLID; // 0x00001011
// All fences are solid - fences are treated specially due
// to being 1.5 blocks high
private static final int FENCE = 16 | SOLID | NONSOLID; // 0x00010011
private static final int INGROUND = 128;
private static final int ONGROUND = 256;
// Until I can think of a better way to determine if a block is solid or
// not, this is what I'll do
private static final int types[];
private static final Set<Material> foods = new HashSet<Material>();
static
{
types = new int[256];
// Find and define properties of all other blocks
for (int i = 0; i < types.length; i++)
{
// Everything unknown is considered nonsolid and solid
types[i] = NONSOLID | SOLID;
if (Block.byId[i] != null)
{
if (Block.byId[i].material.isSolid())
{
// STONE, CAKE, LEAFS, ...
types[i] = SOLID;
}
else if (Block.byId[i].material.isLiquid())
{
// WATER, LAVA, ...
types[i] = LIQUID;
}
else
{
// AIR, SAPLINGS, ...
types[i] = NONSOLID;
}
}
}
// Some exceptions where the above method fails
// du'h
types[Material.AIR.getId()] = NONSOLID;
// Webs slow down a players fall extremely, so it makes
// sense to treat them as optionally solid
types[Material.WEB.getId()] = SOLID | NONSOLID;
// Obvious
types[Material.LADDER.getId()] = LADDER;
types[Material.WATER_LILY.getId()] = LADDER;
types[Material.VINE.getId()] = LADDER;
types[Material.FENCE.getId()] = FENCE;
types[Material.FENCE_GATE.getId()] = FENCE;
types[Material.NETHER_FENCE.getId()] = FENCE;
// These are sometimes solid, sometimes not
types[Material.IRON_FENCE.getId()] = SOLID | NONSOLID;
types[Material.THIN_GLASS.getId()] = SOLID | NONSOLID;
// Signs are NOT solid, despite the game claiming they are
types[Material.WALL_SIGN.getId()] = NONSOLID;
types[Material.SIGN_POST.getId()] = NONSOLID;
// (trap)doors can be solid or not
types[Material.WOODEN_DOOR.getId()] = SOLID | NONSOLID;
types[Material.IRON_DOOR_BLOCK.getId()] = SOLID | NONSOLID;
types[Material.TRAP_DOOR.getId()] = SOLID | NONSOLID;
// repeaters are technically half blocks
types[Material.DIODE_BLOCK_OFF.getId()] = SOLID | NONSOLID;
types[Material.DIODE_BLOCK_ON.getId()] = SOLID | NONSOLID;
// pressure plates are so slim, you can consider them
// nonsolid too
types[Material.STONE_PLATE.getId()] = SOLID | NONSOLID;
types[Material.WOOD_PLATE.getId()] = SOLID | NONSOLID;
// We need to know what is considered food for the instanteat check
foods.add(Material.APPLE);
foods.add(Material.BREAD);
foods.add(Material.COOKED_BEEF);
foods.add(Material.COOKED_CHICKEN);
foods.add(Material.COOKED_FISH);
foods.add(Material.COOKIE);
foods.add(Material.GOLDEN_APPLE);
foods.add(Material.GRILLED_PORK);
foods.add(Material.MELON);
foods.add(Material.MUSHROOM_SOUP);
foods.add(Material.PORK);
foods.add(Material.RAW_BEEF);
foods.add(Material.RAW_CHICKEN);
foods.add(Material.RAW_FISH);
foods.add(Material.ROTTEN_FLESH);
foods.add(Material.SPIDER_EYE);
}
/**
* Ask NoCheat what it thinks about a certain location. Is it a place where a player can safely stand, should it be
* considered as being inside a liquid etc.
*
* @param world The world the coordinates belong to
* @param location The precise location in the world
*
* @return
*/
public static final int evaluateLocation(final World world, final PreciseLocation location)
{
final int lowerX = lowerBorder(location.x);
final int upperX = upperBorder(location.x);
final int Y = (int)location.y;
final int lowerZ = lowerBorder(location.z);
final int upperZ = upperBorder(location.z);
// Check the four borders of the players hitbox for something he could
// be standing on, and combine the results
int result = 0;
result |= evaluateSimpleLocation(world, lowerX, Y, lowerZ);
result |= evaluateSimpleLocation(world, upperX, Y, lowerZ);
result |= evaluateSimpleLocation(world, upperX, Y, upperZ);
result |= evaluateSimpleLocation(world, lowerX, Y, upperZ);
if (!isInGround(result))
{
// Original location: X, Z (allow standing in walls this time)
if (isSolid(types[world.getBlockTypeIdAt(Location.locToBlock(location.x), Location.locToBlock(location.y), Location.locToBlock(location.z))]))
{
result |= INGROUND;
}
}
return result;
}
/**
* Evaluate a location by only looking at a specific "column" of the map to find out if that "column" would allow a
* player to stand, swim etc. there
*
* @param world
* @param x
* @param y
* @param z
* @return Returns INGROUND, ONGROUND, LIQUID, combination of the three or 0
*/
private static final int evaluateSimpleLocation(final World world, final int x, final int y, final int z)
{
// First we need to know about the block itself, the block
// below it and the block above it
final int top = types[world.getBlockTypeIdAt(x, y + 1, z)];
final int base = types[world.getBlockTypeIdAt(x, y, z)];
final int below = types[world.getBlockTypeIdAt(x, y - 1, z)];
int type = 0;
// Special case: Standing on a fence
// Behave as if there is a block on top of the fence
if ((below == FENCE) && base != FENCE && isNonSolid(top))
{
type = INGROUND;
}
// Special case: Fence
// Being a bit above a fence
else if (below != FENCE && isNonSolid(base) && types[world.getBlockTypeIdAt(x, y - 2, z)] == FENCE)
{
type = ONGROUND;
}
else if (isNonSolid(top))
{
// Simplest (and most likely) case:
// Below the player is a solid block
if (isSolid(below) && isNonSolid(base))
{
type = ONGROUND;
}
// Next (likely) case:
// There is a ladder
else if (isLadder(base) || isLadder(top))
{
type = ONGROUND;
}
// Next (likely) case:
// At least the block the player stands
// in is solid
else if (isSolid(base))
{
type = INGROUND;
}
}
// (In every case, check for water)
if (isLiquid(base) || isLiquid(top))
{
type |= LIQUID | INGROUND;
}
return type;
}
public static final boolean isSolid(final int value)
{
return (value & SOLID) == SOLID;
}
public static final boolean isLiquid(final int value)
{
return (value & LIQUID) == LIQUID;
}
private static final boolean isNonSolid(final int value)
{
return ((value & NONSOLID) == NONSOLID);
}
private static final boolean isLadder(final int value)
{
return ((value & LADDER) == LADDER);
}
public static final boolean isOnGround(final int fromType)
{
return (fromType & ONGROUND) == ONGROUND;
}
public static final boolean isInGround(final int fromType)
{
return (fromType & INGROUND) == INGROUND;
}
/**
* Personal Rounding function to determine if a player is still touching a block or not
*
* @param d1
* @return
*/
private static final int lowerBorder(final double d1)
{
final double floor = Math.floor(d1);
if (floor + magic <= d1)
{
return (int)(floor);
}
else
{
return (int)(floor - 1);
}
}
/**
* Personal Rounding function to determine if a player is still touching a block or not
*
* @param d1
* @return
*/
private static final int upperBorder(final double d1)
{
final double floor = Math.floor(d1);
if (floor + magic2 < d1)
{
return (int)(floor + 1);
}
else
{
return (int)floor;
}
}
public static int getType(final int typeId)
{
return types[typeId];
}
public static boolean isFood(ItemStack item)
{
if (item == null)
{
return false;
}
return foods.contains(item.getType());
}
}

View File

@@ -0,0 +1,55 @@
package com.earth2me.essentials.anticheat.checks;
import com.earth2me.essentials.anticheat.EventManager;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import java.util.Collections;
import java.util.List;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerToggleSprintEvent;
/**
* Only place that listens to Player-teleport related events and dispatches them to relevant checks
*
*/
public class WorkaroundsListener implements Listener, EventManager
{
public WorkaroundsListener()
{
}
@EventHandler(priority = EventPriority.HIGHEST)
public void playerMove(final PlayerMoveEvent event)
{
// No typo here. I really only handle cancelled events and ignore others
if (!event.isCancelled())
{
return;
}
// Fix a common mistake that other developers make (cancelling move
// events is crazy, rather set the target location to the from location)
event.setCancelled(false);
event.setTo(event.getFrom().clone());
}
@EventHandler(priority = EventPriority.HIGHEST)
public void toggleSprint(final PlayerToggleSprintEvent event)
{
// Some plugins cancel "sprinting", which makes no sense at all because
// it doesn't stop people from sprinting and rewards them by reducing
// their hunger bar as if they were walking instead of sprinting
if (event.isCancelled() && event.isSprinting())
{
event.setCancelled(false);
}
}
public List<String> getActiveChecks(ConfigurationCacheStore cc)
{
return Collections.emptyList();
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,186 @@
package com.earth2me.essentials.anticheat.checks.blockbreak;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.player.PlayerAnimationEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import com.earth2me.essentials.anticheat.EventManager;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import com.earth2me.essentials.anticheat.config.Permissions;
/**
* Central location to listen to events that are relevant for the blockbreak checks
*
*/
public class BlockBreakCheckListener implements Listener, EventManager
{
private final NoswingCheck noswingCheck;
private final ReachCheck reachCheck;
private final DirectionCheck directionCheck;
private final NoCheat plugin;
public BlockBreakCheckListener(NoCheat plugin)
{
noswingCheck = new NoswingCheck(plugin);
reachCheck = new ReachCheck(plugin);
directionCheck = new DirectionCheck(plugin);
this.plugin = plugin;
}
/**
* We listen to blockBreak events for obvious reasons
*
* @param event The blockbreak event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void blockBreak(final BlockBreakEvent event)
{
if (event.isCancelled())
{
return;
}
boolean cancelled = false;
final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
final BlockBreakConfig cc = BlockBreakCheck.getConfig(player);
final BlockBreakData data = BlockBreakCheck.getData(player);
// Remember the location of the block that will be broken
data.brokenBlockLocation.set(event.getBlock());
// Only if the block got damaged directly before, do the check(s)
if (!data.brokenBlockLocation.equals(data.lastDamagedBlock))
{
// Something caused a blockbreak event that's not from the player
// Don't check it at all
data.lastDamagedBlock.reset();
return;
}
// Now do the actual checks, if still needed. It's a good idea to make
// computationally cheap checks first, because it may save us from
// doing the computationally expensive checks.
// First NoSwing: Did the arm of the player move before breaking this
// block?
if (cc.noswingCheck && !player.hasPermission(Permissions.BLOCKBREAK_NOSWING))
{
cancelled = noswingCheck.check(player, data, cc);
}
// Second Reach: Is the block really in reach distance
if (!cancelled && cc.reachCheck && !player.hasPermission(Permissions.BLOCKBREAK_REACH))
{
cancelled = reachCheck.check(player, data, cc);
}
// Third Direction: Did the player look at the block at all
if (!cancelled && cc.directionCheck && !player.hasPermission(Permissions.BLOCKBREAK_DIRECTION))
{
cancelled = directionCheck.check(player, data, cc);
}
// At least one check failed and demanded to cancel the event
if (cancelled)
{
event.setCancelled(cancelled);
}
}
/**
* We listen to BlockDamage events to grab the information if it has been an "insta-break". That info may come in
* handy later.
*
* @param event The BlockDamage event
*/
@EventHandler(priority = EventPriority.MONITOR)
public void blockHit(final BlockDamageEvent event)
{
if (event.isCancelled())
{
return;
}
NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
BlockBreakData data = BlockBreakCheck.getData(player);
// Only interested in insta-break events here
if (event.getInstaBreak())
{
// Remember this location. We handle insta-breaks slightly
// different in some of the blockbreak checks.
data.instaBrokenBlockLocation.set(event.getBlock());
}
}
/**
* We listen to BlockInteract events to be (at least in many cases) able to distinguish between blockbreak events
* that were triggered by players actually digging and events that were artificially created by plugins.
*
* @param event
*/
@EventHandler(priority = EventPriority.MONITOR)
public void blockInteract(final PlayerInteractEvent event)
{
if (event.getClickedBlock() == null)
{
return;
}
NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
BlockBreakData data = BlockBreakCheck.getData(player);
// Remember this location. Only blockbreakevents for this specific
// block will be handled at all
data.lastDamagedBlock.set(event.getClickedBlock());
}
/**
* We listen to PlayerAnimationEvent because it is (currently) equivalent to "player swings arm" and we want to
* check if he did that between blockbreaks.
*
* @param event The PlayerAnimation Event
*/
@EventHandler(priority = EventPriority.MONITOR)
public void armSwing(final PlayerAnimationEvent event)
{
// Just set a flag to true when the arm was swung
BlockBreakCheck.getData(plugin.getPlayer(event.getPlayer())).armswung = true;
}
public List<String> getActiveChecks(ConfigurationCacheStore cc)
{
LinkedList<String> s = new LinkedList<String>();
BlockBreakConfig bb = BlockBreakCheck.getConfig(cc);
if (bb.directionCheck)
{
s.add("blockbreak.direction");
}
if (bb.reachCheck)
{
s.add("blockbreak.reach");
}
if (bb.noswingCheck)
{
s.add("blockbreak.noswing");
}
return s;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,97 @@
package com.earth2me.essentials.anticheat.checks.blockplace;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import com.earth2me.essentials.anticheat.EventManager;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import com.earth2me.essentials.anticheat.config.Permissions;
/**
* Central location to listen to Block-related events and dispatching them to checks
*
*/
public class BlockPlaceCheckListener implements Listener, EventManager
{
private final ReachCheck reachCheck;
private final DirectionCheck directionCheck;
private final NoCheat plugin;
public BlockPlaceCheckListener(NoCheat plugin)
{
this.plugin = plugin;
reachCheck = new ReachCheck(plugin);
directionCheck = new DirectionCheck(plugin);
}
/**
* We listen to BlockPlace events for obvious reasons
*
* @param event the BlockPlace event
*/
@EventHandler(priority = EventPriority.LOWEST)
protected void handleBlockPlaceEvent(BlockPlaceEvent event)
{
if (event.isCancelled() || event.getBlock() == null || event.getBlockAgainst() == null)
{
return;
}
boolean cancelled = false;
final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
final BlockPlaceConfig cc = BlockPlaceCheck.getConfig(player);
final BlockPlaceData data = BlockPlaceCheck.getData(player);
// Remember these locations and put them in a simpler "format"
data.blockPlaced.set(event.getBlock());
data.blockPlacedAgainst.set(event.getBlockAgainst());
// Now do the actual checks
// First the reach check
if (cc.reachCheck && !player.hasPermission(Permissions.BLOCKPLACE_REACH))
{
cancelled = reachCheck.check(player, data, cc);
}
// Second the direction check
if (!cancelled && cc.directionCheck && !player.hasPermission(Permissions.BLOCKPLACE_DIRECTION))
{
cancelled = directionCheck.check(player, data, cc);
}
// If one of the checks requested to cancel the event, do so
if (cancelled)
{
event.setCancelled(cancelled);
}
}
public List<String> getActiveChecks(ConfigurationCacheStore cc)
{
LinkedList<String> s = new LinkedList<String>();
BlockPlaceConfig bp = BlockPlaceCheck.getConfig(cc);
if (bp.reachCheck)
{
s.add("blockplace.reach");
}
if (bp.directionCheck)
{
s.add("blockplace.direction");
}
return s;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,108 @@
package com.earth2me.essentials.anticheat.checks.chat;
import com.earth2me.essentials.anticheat.EventManager;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import com.earth2me.essentials.anticheat.config.Permissions;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
/**
* Central location to listen to events that are relevant for the chat checks
*
*/
public class ChatCheckListener implements Listener, EventManager
{
private final SpamCheck spamCheck;
private final ColorCheck colorCheck;
private final NoCheat plugin;
public ChatCheckListener(NoCheat plugin)
{
this.plugin = plugin;
spamCheck = new SpamCheck(plugin);
colorCheck = new ColorCheck(plugin);
}
/**
* We listen to PlayerCommandPreprocess events because commands can be used for spamming too.
*
* @param event The PlayerCommandPreprocess Event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void commandPreprocess(final PlayerCommandPreprocessEvent event)
{
// This type of event is derived from PlayerChatEvent, therefore
// just treat it like that
chat(event);
}
/**
* We listen to PlayerChat events for obvious reasons
*
* @param event The PlayerChat event
*/
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void chat(final PlayerChatEvent event)
{
boolean cancelled = false;
final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
final ChatConfig cc = ChatCheck.getConfig(player);
final ChatData data = ChatCheck.getData(player);
// Remember the original message
data.message = event.getMessage();
// Now do the actual checks
// First the spam check
if (cc.spamCheck && !player.hasPermission(Permissions.CHAT_SPAM))
{
cancelled = spamCheck.check(player, data, cc);
}
// Second the color check
if (!cancelled && cc.colorCheck && !player.hasPermission(Permissions.CHAT_COLOR))
{
cancelled = colorCheck.check(player, data, cc);
}
// If one of the checks requested the event to be cancelled, do it
if (cancelled)
{
event.setCancelled(cancelled);
}
else
{
// In case one of the events modified the message, make sure that
// the new message gets used
event.setMessage(data.message);
}
}
public List<String> getActiveChecks(ConfigurationCacheStore cc)
{
LinkedList<String> s = new LinkedList<String>();
ChatConfig c = ChatCheck.getConfig(cc);
if (c.spamCheck)
{
s.add("chat.spam");
}
if (c.colorCheck)
{
s.add("chat.color");
}
return s;
}
}

View File

@@ -0,0 +1,64 @@
package com.earth2me.essentials.anticheat.checks.chat;
import java.util.LinkedList;
import java.util.List;
import com.earth2me.essentials.anticheat.ConfigItem;
import com.earth2me.essentials.anticheat.actions.types.ActionList;
import com.earth2me.essentials.anticheat.config.ConfPaths;
import com.earth2me.essentials.anticheat.config.NoCheatConfiguration;
import com.earth2me.essentials.anticheat.config.Permissions;
/**
* Configurations specific for the "Chat" checks Every world gets one of these assigned to it, or if a world doesn't get
* it's own, it will use the "global" version
*
*/
public class ChatConfig implements ConfigItem
{
public final boolean spamCheck;
public final String[] spamWhitelist;
public final long spamTimeframe;
public final int spamMessageLimit;
public final int spamCommandLimit;
public final ActionList spamActions;
public final boolean colorCheck;
public final ActionList colorActions;
public ChatConfig(NoCheatConfiguration data)
{
spamCheck = data.getBoolean(ConfPaths.CHAT_SPAM_CHECK);
spamWhitelist = splitWhitelist(data.getString(ConfPaths.CHAT_SPAM_WHITELIST));
spamTimeframe = data.getInt(ConfPaths.CHAT_SPAM_TIMEFRAME) * 1000L;
spamMessageLimit = data.getInt(ConfPaths.CHAT_SPAM_MESSAGELIMIT);
spamCommandLimit = data.getInt(ConfPaths.CHAT_SPAM_COMMANDLIMIT);
spamActions = data.getActionList(ConfPaths.CHAT_SPAM_ACTIONS, Permissions.CHAT_SPAM);
colorCheck = data.getBoolean(ConfPaths.CHAT_COLOR_CHECK);
colorActions = data.getActionList(ConfPaths.CHAT_COLOR_ACTIONS, Permissions.CHAT_COLOR);
}
/**
* Convenience method to split a string into an array on every occurance of the "," character, removing all
* whitespaces before and after it too.
*
* @param string The string containing text seperated by ","
* @return An array of the seperate texts
*/
private String[] splitWhitelist(String string)
{
List<String> strings = new LinkedList<String>();
string = string.trim();
for (String s : string.split(","))
{
if (s != null && s.trim().length() > 0)
{
strings.add(s.trim());
}
}
return strings.toArray(new String[strings.size()]);
}
}

View File

@@ -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 = "";
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,291 @@
package com.earth2me.essentials.anticheat.checks.fight;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason;
import org.bukkit.event.player.PlayerAnimationEvent;
import com.earth2me.essentials.anticheat.EventManager;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
/**
* Central location to listen to events that are relevant for the fight checks
*
*/
public class FightCheckListener implements Listener, EventManager
{
private final List<FightCheck> checks;
private final GodmodeCheck godmodeCheck;
private final InstanthealCheck instanthealCheck;
private final NoCheat plugin;
public FightCheckListener(NoCheat plugin)
{
this.checks = new ArrayList<FightCheck>(4);
// Keep these in a list, because they can be executed in a bundle
this.checks.add(new SpeedCheck(plugin));
this.checks.add(new NoswingCheck(plugin));
this.checks.add(new DirectionCheck(plugin));
this.checks.add(new ReachCheck(plugin));
this.godmodeCheck = new GodmodeCheck(plugin);
this.instanthealCheck = new InstanthealCheck(plugin);
this.plugin = plugin;
}
/**
* We listen to EntityDamage events for obvious reasons
*
* @param event The EntityDamage Event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void entityDamage(final EntityDamageEvent event)
{
// Filter some unwanted events right now
if (event.isCancelled() || !(event instanceof EntityDamageByEntityEvent))
{
return;
}
final EntityDamageByEntityEvent e = (EntityDamageByEntityEvent)event;
if (!(e.getDamager() instanceof Player))
{
return;
}
if (e.getCause() == DamageCause.ENTITY_ATTACK)
{
normalDamage(e);
}
else if (e.getCause() == DamageCause.CUSTOM)
{
customDamage(e);
}
}
/**
* We listen to EntityDamage events (again) for obvious reasons
*
* @param event The EntityDamage Event
*/
@EventHandler(priority = EventPriority.LOW)
public void entityDamageForGodmodeCheck(final EntityDamageEvent event)
{
if (event.isCancelled())
{
return;
}
// Filter unwanted events right here
final Entity entity = event.getEntity();
if (!(entity instanceof Player) || entity.isDead())
{
return;
}
NoCheatPlayer player = plugin.getPlayer((Player)entity);
FightConfig cc = FightCheck.getConfig(player);
if (!godmodeCheck.isEnabled(cc) || player.hasPermission(godmodeCheck.permission))
{
return;
}
FightData data = FightCheck.getData(player);
// Run the godmode check on the attacked player
boolean cancelled = godmodeCheck.check(plugin.getPlayer((Player)entity), data, cc);
// It requested to "cancel" the players invulnerability, so set his
// noDamageTicks to 0
if (cancelled)
{
// Remove the invulnerability from the player
player.getPlayer().setNoDamageTicks(0);
}
}
/**
* We listen to EntityRegainHealth events of type "Satiated" for instantheal check
*
* @param event The EntityRegainHealth Event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void satiatedRegen(final EntityRegainHealthEvent event)
{
if (!(event.getEntity() instanceof Player) || event.isCancelled() || event.getRegainReason() != RegainReason.SATIATED)
{
return;
}
boolean cancelled = false;
NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
FightConfig config = FightCheck.getConfig(player);
if (!instanthealCheck.isEnabled(config) || player.hasPermission(instanthealCheck.permission))
{
return;
}
FightData data = FightCheck.getData(player);
cancelled = instanthealCheck.check(player, data, config);
if (cancelled)
{
event.setCancelled(true);
}
}
/**
* A player attacked something with DamageCause ENTITY_ATTACK. That's most likely what we want to really check.
*
* @param event The EntityDamageByEntityEvent
*/
private void normalDamage(final EntityDamageByEntityEvent event)
{
final Player damager = (Player)event.getDamager();
final NoCheatPlayer player = plugin.getPlayer(damager);
final FightConfig cc = FightCheck.getConfig(player);
final FightData data = FightCheck.getData(player);
// For some reason we decided to skip this event anyway
if (data.skipNext)
{
data.skipNext = false;
return;
}
boolean cancelled = false;
// Get the attacked entity and remember it
data.damagee = ((CraftEntity)event.getEntity()).getHandle();
// Run through the four main checks
for (FightCheck check : checks)
{
// If it should be executed, do it
if (!cancelled && check.isEnabled(cc) && !player.hasPermission(check.permission))
{
cancelled = check.check(player, data, cc);
}
}
// Forget the attacked entity (to allow garbage collecting etc.
data.damagee = null;
// One of the checks requested the event to be cancelled, so do it
if (cancelled)
{
event.setCancelled(cancelled);
}
}
/**
* There is an unofficial agreement that if a plugin wants an attack to not get checked by NoCheat, it either has to
* use a Damage type different from ENTITY_ATTACK or fire an event with damage type CUSTOM and damage 0 directly
* before the to-be-ignored event.
*
* @param event The EntityDamageByEntityEvent
*/
private void customDamage(final EntityDamageByEntityEvent event)
{
final Player damager = (Player)event.getDamager();
final NoCheatPlayer player = plugin.getPlayer(damager);
final FightData data = FightCheck.getData(player);
// Skip the next damage event, because it is with high probability
// something from the Heroes plugin
data.skipNext = true;
return;
}
/**
* We listen to death events to prevent a very specific method of doing godmode.
*
* @param event The EntityDeathEvent
*/
@EventHandler(priority = EventPriority.MONITOR)
protected void death(final EntityDeathEvent event)
{
// Only interested in dying players
if (!(event.getEntity() instanceof CraftPlayer))
{
return;
}
godmodeCheck.death((CraftPlayer)event.getEntity());
}
/**
* We listen to PlayerAnimationEvent because it is used for arm swinging
*
* @param event The PlayerAnimationEvent
*/
@EventHandler(priority = EventPriority.MONITOR)
protected void armSwing(final PlayerAnimationEvent event)
{
// Set a flag telling us that the arm has been swung
FightCheck.getData(plugin.getPlayer(event.getPlayer())).armswung = true;
}
public List<String> getActiveChecks(ConfigurationCacheStore cc)
{
LinkedList<String> s = new LinkedList<String>();
FightConfig f = FightCheck.getConfig(cc);
if (f.directionCheck)
{
s.add("fight.direction");
}
if (f.noswingCheck)
{
s.add("fight.noswing");
}
if (f.reachCheck)
{
s.add("fight.reach");
}
if (f.speedCheck)
{
s.add("fight.speed");
}
if (f.godmodeCheck)
{
s.add("fight.godmode");
}
if (f.instanthealCheck)
{
s.add("fight.instantHeal");
}
return s;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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)
{
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,196 @@
package com.earth2me.essentials.anticheat.checks.inventory;
import com.earth2me.essentials.anticheat.EventManager;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.checks.CheckUtil;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import com.earth2me.essentials.anticheat.config.Permissions;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityShootBowEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
/**
* Central location to listen to events that are relevant for the inventory checks
*
*/
public class InventoryCheckListener implements Listener, EventManager
{
private final DropCheck dropCheck;
private final InstantBowCheck instantBowCheck;
private final InstantEatCheck instantEatCheck;
private final NoCheat plugin;
public InventoryCheckListener(NoCheat plugin)
{
this.dropCheck = new DropCheck(plugin);
this.instantBowCheck = new InstantBowCheck(plugin);
this.instantEatCheck = new InstantEatCheck(plugin);
this.plugin = plugin;
}
/**
* We listen to DropItem Event for the dropCheck
*
* @param event The PlayerDropItem Event
*/
@EventHandler(priority = EventPriority.LOWEST)
protected void handlePlayerDropItemEvent(final PlayerDropItemEvent event)
{
if (event.isCancelled() || event.getPlayer().isDead())
{
return;
}
boolean cancelled = false;
final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
final InventoryConfig cc = InventoryCheck.getConfig(player);
final InventoryData data = InventoryCheck.getData(player);
// If it should be executed, do it
if (cc.dropCheck && !player.hasPermission(Permissions.INVENTORY_DROP))
{
cancelled = dropCheck.check(player, data, cc);
}
if (cancelled)
{
// Cancelling drop events is not save (in certain circumstances
// items will disappear completely). So don't do it and kick
// players instead by default
// event.setCancelled(true);
}
}
/**
* We listen to PlayerInteractEvent for the instantEat and instantBow checks
*
* @param event The PlayerInteractEvent
*/
@EventHandler(priority = EventPriority.LOWEST)
public void interact(final PlayerInteractEvent event)
{
// Only interested in right-clicks while holding an item
if (!event.hasItem() || !(event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK))
{
return;
}
NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
final InventoryData data = InventoryCheck.getData(player);
if (event.getItem().getType() == Material.BOW)
{
// It was a bow, the player starts to pull the string
// Remember this time
data.lastBowInteractTime = System.currentTimeMillis();
}
else if (CheckUtil.isFood(event.getItem()))
{
// It was food, the player starts to eat some food
// Remember this time and the type of food
data.foodMaterial = event.getItem().getType();
data.lastEatInteractTime = System.currentTimeMillis();
}
else
{
// Nothing that we are interested in, reset data
data.lastBowInteractTime = 0;
data.lastEatInteractTime = 0;
data.foodMaterial = null;
}
}
/**
* We listen to FoodLevelChange Event because Bukkit doesn't provide a PlayerFoodEating Event (or whatever it would
* be called).
*
* @param event The FoodLevelChangeEvent
*/
@EventHandler(priority = EventPriority.LOWEST)
public void foodchanged(final FoodLevelChangeEvent event)
{
// Only if a player ate food
if (!event.isCancelled() && event.getEntity() instanceof Player)
{
final NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
final InventoryConfig cc = InventoryCheck.getConfig(player);
final InventoryData data = InventoryCheck.getData(player);
// Only if he should get checked
if (cc.eatCheck && !player.hasPermission(Permissions.INVENTORY_INSTANTEAT))
{
boolean cancelled = instantEatCheck.check(player, event, data, cc);
// The check requested the foodlevelchange to get cancelled
event.setCancelled(cancelled);
}
// Forget the food material, as the info is no longer needed
data.foodMaterial = null;
}
}
/**
* We listen to EntityShootBowEvent for the instantbow check
*
* @param event The EntityShootBowEvent
*/
@EventHandler(priority = EventPriority.LOWEST)
public void bowfired(final EntityShootBowEvent event)
{
// Only if a player shot the arrow
if (!event.isCancelled() && event.getEntity() instanceof Player)
{
final NoCheatPlayer player = plugin.getPlayer((Player)event.getEntity());
final InventoryConfig cc = InventoryCheck.getConfig(player);
// Only if he should get checked
if (cc.bowCheck && !player.hasPermission(Permissions.INVENTORY_INSTANTBOW))
{
final InventoryData data = InventoryCheck.getData(player);
boolean cancelled = instantBowCheck.check(player, event, data, cc);
// The check requested the bowshooting to get cancelled
event.setCancelled(cancelled);
}
}
}
public List<String> getActiveChecks(ConfigurationCacheStore cc)
{
LinkedList<String> s = new LinkedList<String>();
InventoryConfig i = InventoryCheck.getConfig(cc);
if (i.dropCheck)
{
s.add("inventory.dropCheck");
}
if (i.bowCheck)
{
s.add("inventory.instantbow");
}
if (i.eatCheck)
{
s.add("inventory.instanteat");
}
return s;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,376 @@
package com.earth2me.essentials.anticheat.checks.moving;
import com.earth2me.essentials.anticheat.EventManager;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.checks.CheckUtil;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import com.earth2me.essentials.anticheat.config.Permissions;
import com.earth2me.essentials.anticheat.data.PreciseLocation;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.*;
import org.bukkit.util.Vector;
/**
* Central location to listen to events that are relevant for the moving checks
*
*/
public class MovingCheckListener implements Listener, EventManager
{
private final MorePacketsCheck morePacketsCheck;
private final FlyingCheck flyingCheck;
private final RunningCheck runningCheck;
private final NoCheat plugin;
public MovingCheckListener(NoCheat plugin)
{
flyingCheck = new FlyingCheck(plugin);
runningCheck = new RunningCheck(plugin);
morePacketsCheck = new MorePacketsCheck(plugin);
this.plugin = plugin;
}
/**
* A workaround for players placing blocks below them getting pushed off the block by NoCheat.
*
* It essentially moves the "setbackpoint" to the top of the newly placed block, therefore tricking NoCheat into
* thinking the player was already on top of that block and should be allowed to stay there
*
* @param event The BlockPlaceEvent
*/
@EventHandler(priority = EventPriority.MONITOR)
public void blockPlace(final BlockPlaceEvent event)
{
// Block wasn't placed, so we don't care
if (event.isCancelled())
{
return;
}
final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
final MovingConfig config = MovingCheck.getConfig(player);
// If the player is allowed to fly anyway, the workaround is not needed
// It's kind of expensive (looking up block types) therefore it makes
// sense to avoid it
if (config.allowFlying || !config.runflyCheck || player.hasPermission(Permissions.MOVING_FLYING) || player.hasPermission(Permissions.MOVING_RUNFLY))
{
return;
}
// Get the player-specific stored data that applies here
final MovingData data = MovingCheck.getData(player);
final Block block = event.getBlockPlaced();
if (block == null || !data.runflySetBackPoint.isSet())
{
return;
}
// Keep some results of "expensive calls
final Location l = player.getPlayer().getLocation();
final int playerX = l.getBlockX();
final int playerY = l.getBlockY();
final int playerZ = l.getBlockZ();
final int blockY = block.getY();
// Was the block below the player?
if (Math.abs(playerX - block.getX()) <= 1 && Math.abs(playerZ - block.getZ()) <= 1 && playerY - blockY >= 0 && playerY - blockY <= 2)
{
// yes
final int type = CheckUtil.getType(block.getTypeId());
if (CheckUtil.isSolid(type) || CheckUtil.isLiquid(type))
{
if (blockY + 1 >= data.runflySetBackPoint.y)
{
data.runflySetBackPoint.y = (blockY + 1);
data.jumpPhase = 0;
}
}
}
}
/**
* If a player gets teleported, it may have two reasons. Either it was NoCheat or another plugin. If it was NoCheat,
* the target location should match the "data.teleportTo" value.
*
* On teleports, reset some movement related data that gets invalid
*
* @param event The PlayerTeleportEvent
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void teleport(final PlayerTeleportEvent event)
{
NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
final MovingData data = MovingCheck.getData(player);
// If it was a teleport initialized by NoCheat, do it anyway
// even if another plugin said "no"
if (data.teleportTo.isSet() && data.teleportTo.equals(event.getTo()))
{
event.setCancelled(false);
}
else
{
// Only if it wasn't NoCheat, drop data from morepackets check.
// If it was NoCheat, we don't want players to exploit the
// runfly check teleporting to get rid of the "morepackets"
// data.
data.clearMorePacketsData();
}
// Always drop data from runfly check, as it always loses its validity
// after teleports. Always!
data.teleportTo.reset();
data.clearRunFlyData();
return;
}
/**
* Just for security, if a player switches between worlds, reset the runfly and morepackets checks data, because it
* is definitely invalid now
*
* @param event The PlayerChangedWorldEvent
*/
@EventHandler(priority = EventPriority.MONITOR)
public void worldChange(final PlayerChangedWorldEvent event)
{
// Maybe this helps with people teleporting through multiverse portals having problems?
final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
data.teleportTo.reset();
data.clearRunFlyData();
data.clearMorePacketsData();
}
/**
* When a player uses a portal, all information related to the moving checks becomes invalid.
*
* @param event
*/
@EventHandler(priority = EventPriority.MONITOR)
public void portal(final PlayerPortalEvent event)
{
final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
data.clearMorePacketsData();
data.clearRunFlyData();
}
/**
* When a player respawns, all information related to the moving checks becomes invalid.
*
* @param event
*/
@EventHandler(priority = EventPriority.MONITOR)
public void respawn(final PlayerRespawnEvent event)
{
final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
data.clearMorePacketsData();
data.clearRunFlyData();
}
/**
* When a player moves, he will be checked for various suspicious behaviour.
*
* @param event The PlayerMoveEvent
*/
@EventHandler(priority = EventPriority.LOWEST)
public void move(final PlayerMoveEvent event)
{
// Don't care for vehicles
if (event.isCancelled() || event.getPlayer().isInsideVehicle())
{
return;
}
// Don't care for movements that are very high distance or to another
// world (such that it is very likely the event data was modified by
// another plugin before we got it)
if (!event.getFrom().getWorld().equals(event.getTo().getWorld()) || event.getFrom().distanceSquared(event.getTo()) > 400)
{
return;
}
final NoCheatPlayer player = plugin.getPlayer(event.getPlayer());
final MovingConfig cc = MovingCheck.getConfig(player);
final MovingData data = MovingCheck.getData(player);
// Advance various counters and values that change per movement
// tick. They are needed to decide on how fast a player may
// move.
tickVelocities(data);
// Remember locations
data.from.set(event.getFrom());
final Location to = event.getTo();
data.to.set(to);
PreciseLocation newTo = null;
/**
* RUNFLY CHECK SECTION *
*/
// If the player isn't handled by runfly checks
if (!cc.runflyCheck || player.hasPermission(Permissions.MOVING_RUNFLY))
{
// Just because he is allowed now, doesn't mean he will always
// be. So forget data about the player related to moving
data.clearRunFlyData();
}
else if (cc.allowFlying || (player.isCreative() && cc.identifyCreativeMode) || player.hasPermission(Permissions.MOVING_FLYING))
{
// Only do the limited flying check
newTo = flyingCheck.check(player, data, cc);
}
else
{
// Go for the full treatment
newTo = runningCheck.check(player, data, cc);
}
/**
* MOREPACKETS CHECK SECTION *
*/
if (!cc.morePacketsCheck || player.hasPermission(Permissions.MOVING_MOREPACKETS))
{
data.clearMorePacketsData();
}
else if (newTo == null)
{
newTo = morePacketsCheck.check(player, data, cc);
}
// Did one of the check(s) decide we need a new "to"-location?
if (newTo != null)
{
// Compose a new location based on coordinates of "newTo" and
// viewing direction of "event.getTo()" to allow the player to
// look somewhere else despite getting pulled back by NoCheat
event.setTo(new Location(player.getPlayer().getWorld(), newTo.x, newTo.y, newTo.z, to.getYaw(), to.getPitch()));
// remember where we send the player to
data.teleportTo.set(newTo);
}
}
/**
* Just try to estimate velocities over time Not very precise, but works good enough most of the time.
*
* @param data
*/
private void tickVelocities(MovingData data)
{
/**
* ****** DO GENERAL DATA MODIFICATIONS ONCE FOR EACH EVENT ****
*/
if (data.horizVelocityCounter > 0)
{
data.horizVelocityCounter--;
}
else if (data.horizFreedom > 0.001)
{
data.horizFreedom *= 0.90;
}
if (data.vertVelocity <= 0.1)
{
data.vertVelocityCounter--;
}
if (data.vertVelocityCounter > 0)
{
data.vertFreedom += data.vertVelocity;
data.vertVelocity *= 0.90;
}
else if (data.vertFreedom > 0.001)
{
// Counter has run out, now reduce the vert freedom over time
data.vertFreedom *= 0.93;
}
}
/**
* Player got a velocity packet. The server can't keep track of actual velocity values (by design), so we have to
* try and do that ourselves. Very rough estimates.
*
* @param event The PlayerVelocityEvent
*/
@EventHandler(priority = EventPriority.MONITOR)
public void velocity(final PlayerVelocityEvent event)
{
if (event.isCancelled())
{
return;
}
final MovingData data = MovingCheck.getData(plugin.getPlayer(event.getPlayer()));
final Vector v = event.getVelocity();
double newVal = v.getY();
if (newVal >= 0.0D)
{
data.vertVelocity += newVal;
data.vertFreedom += data.vertVelocity;
}
data.vertVelocityCounter = 50;
newVal = Math.sqrt(Math.pow(v.getX(), 2) + Math.pow(v.getZ(), 2));
if (newVal > 0.0D)
{
data.horizFreedom += newVal;
data.horizVelocityCounter = 30;
}
}
public List<String> getActiveChecks(ConfigurationCacheStore cc)
{
LinkedList<String> s = new LinkedList<String>();
MovingConfig m = MovingCheck.getConfig(cc);
if (m.runflyCheck)
{
if (!m.allowFlying)
{
s.add("moving.runfly");
if (m.sneakingCheck)
{
s.add("moving.sneaking");
}
if (m.nofallCheck)
{
s.add("moving.nofall");
}
}
else
{
s.add("moving.flying");
}
}
if (m.morePacketsCheck)
{
s.add("moving.morepackets");
}
return s;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,163 @@
package com.earth2me.essentials.anticheat.command;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.config.Permissions;
import java.util.*;
import java.util.Map.Entry;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
/**
* Handle all NoCheat related commands in a common place
*/
public class CommandHandler
{
private final List<Permission> perms;
public CommandHandler(NoCheat plugin)
{
// Make a copy to allow sorting
perms = new LinkedList<Permission>(plugin.getDescription().getPermissions());
// Sort NoCheats permission by name and parent-child relation with
// a custom sorting method
Collections.sort(perms, new Comparator<Permission>()
{
public int compare(Permission o1, Permission o2)
{
String name1 = o1.getName();
String name2 = o2.getName();
if (name1.equals(name2))
{
return 0;
}
if (name1.startsWith(name2))
{
return 1;
}
if (name2.startsWith(name1))
{
return -1;
}
return name1.compareTo(name2);
}
});
}
/**
* Handle a command that is directed at NoCheat
*
* @param plugin
* @param sender
* @param command
* @param label
* @param args
* @return
*/
public boolean handleCommand(NoCheat plugin, CommandSender sender, Command command, String label, String[] args)
{
boolean result = false;
// Not our command, how did it get here?
if (!command.getName().equalsIgnoreCase("nocheat") || args.length == 0)
{
result = false;
}
else if (args[0].equalsIgnoreCase("permlist") && args.length >= 2)
{
// permlist command was used
result = handlePermlistCommand(plugin, sender, args);
}
else if (args[0].equalsIgnoreCase("reload"))
{
// reload command was used
result = handleReloadCommand(plugin, sender);
}
else if (args[0].equalsIgnoreCase("playerinfo") && args.length >= 2)
{
// playerinfo command was used
result = handlePlayerInfoCommand(plugin, sender, args);
}
return result;
}
private boolean handlePlayerInfoCommand(NoCheat plugin, CommandSender sender, String[] args)
{
Map<String, Object> map = plugin.getPlayerData(args[1]);
String filter = "";
if (args.length > 2)
{
filter = args[2];
}
sender.sendMessage("PlayerInfo for " + args[1]);
for (Entry<String, Object> entry : map.entrySet())
{
if (entry.getKey().contains(filter))
{
sender.sendMessage(entry.getKey() + ": " + entry.getValue());
}
}
return true;
}
private boolean handlePermlistCommand(NoCheat plugin, CommandSender sender, String[] args)
{
// Get the player by name
Player player = plugin.getServer().getPlayerExact(args[1]);
if (player == null)
{
sender.sendMessage("Unknown player: " + args[1]);
return true;
}
// Should permissions be filtered by prefix?
String prefix = "";
if (args.length == 3)
{
prefix = args[2];
}
sender.sendMessage("Player " + player.getName() + " has the permission(s):");
for (Permission permission : perms)
{
if (permission.getName().startsWith(prefix))
{
sender.sendMessage(permission.getName() + ": " + player.hasPermission(permission));
}
}
return true;
}
private boolean handleReloadCommand(NoCheat plugin, CommandSender sender)
{
// Players need a special permission for this
if (!(sender instanceof Player) || sender.hasPermission(Permissions.ADMIN_RELOAD))
{
sender.sendMessage("[NoCheat] Reloading configuration");
plugin.reloadConfiguration();
sender.sendMessage("[NoCheat] Configuration reloaded");
}
else
{
sender.sendMessage("You lack the " + Permissions.ADMIN_RELOAD + " permission to use 'reload'");
}
return true;
}
}

View File

@@ -0,0 +1,183 @@
package com.earth2me.essentials.anticheat.config;
import com.earth2me.essentials.anticheat.actions.Action;
import com.earth2me.essentials.anticheat.actions.types.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helps with creating Actions out of text string definitions
*
*/
public class ActionFactory
{
private static final Map<String, Object> lib = new HashMap<String, Object>();
public ActionFactory(Map<String, Object> library)
{
lib.putAll(library);
}
public Action createAction(String actionDefinition)
{
actionDefinition = actionDefinition.toLowerCase();
if (actionDefinition.equals("cancel"))
{
return new SpecialAction();
}
if (actionDefinition.startsWith("log:"))
{
return parseLogAction(actionDefinition.split(":", 2)[1]);
}
if (actionDefinition.startsWith("cmd:"))
{
return parseCmdAction(actionDefinition.split(":", 2)[1]);
}
throw new IllegalArgumentException("NoCheat doesn't understand action '" + actionDefinition + "' at all");
}
private Action parseCmdAction(String definition)
{
String[] parts = definition.split(":");
String name = parts[0];
Object command = lib.get(parts[0]);
int delay = 0;
int repeat = 1;
if (command == null)
{
throw new IllegalArgumentException("NoCheat doesn't know command '" + name + "'. Have you forgotten to define it?");
}
if (parts.length > 1)
{
try
{
delay = Integer.parseInt(parts[1]);
repeat = Integer.parseInt(parts[2]);
}
catch (Exception e)
{
// TODO
System.out.println("NoCheat couldn't parse details of command '" + definition + "', will use default values instead.");
delay = 0;
repeat = 1;
}
}
return new ConsolecommandAction(name, delay, repeat, command.toString());
}
private Action parseLogAction(String definition)
{
String[] parts = definition.split(":");
String name = parts[0];
Object message = lib.get(parts[0]);
int delay = 0;
int repeat = 1;
boolean toConsole = true;
boolean toFile = true;
boolean toChat = true;
if (message == null)
{
throw new IllegalArgumentException("NoCheat doesn't know log message '" + name + "'. Have you forgotten to define it?");
}
try
{
delay = Integer.parseInt(parts[1]);
repeat = Integer.parseInt(parts[2]);
toConsole = parts[3].contains("c");
toChat = parts[3].contains("i");
toFile = parts[3].contains("f");
}
catch (Exception e)
{
System.out.println("NoCheat couldn't parse details of log action '" + definition + "', will use default values instead.");
e.printStackTrace();
delay = 0;
repeat = 1;
toConsole = true;
toFile = true;
toChat = true;
}
return new LogAction(name, delay, repeat, toChat, toConsole, toFile, message.toString());
}
public Action[] createActions(String... definitions)
{
List<Action> actions = new ArrayList<Action>();
for (String def : definitions)
{
if (def.length() == 0)
{
continue;
}
try
{
actions.add(createAction(def));
}
catch (IllegalArgumentException e)
{
System.out.println(e.getMessage());
actions.add(new DummyAction(def));
}
}
return actions.toArray(new Action[actions.size()]);
}
public ActionList createActionList(String definition, String permission)
{
ActionList list = new ActionList(permission);
boolean first = true;
for (String s : definition.split("vl>"))
{
s = s.trim();
if (s.length() == 0)
{
first = false;
continue;
}
try
{
Integer vl;
String def;
if (first)
{
first = false;
vl = 0;
def = s;
}
else
{
String[] listEntry = s.split("\\s+", 2);
vl = Integer.parseInt(listEntry[0]);
def = listEntry[1];
}
list.setActions(vl, createActions(def.split("\\s+")));
}
catch (Exception e)
{
System.out.println("NoCheat couldn't parse action definition 'vl:" + s + "'");
}
}
return list;
}
}

View File

@@ -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";
}

View File

@@ -0,0 +1,45 @@
package com.earth2me.essentials.anticheat.config;
import com.earth2me.essentials.anticheat.ConfigItem;
import java.util.HashMap;
import java.util.Map;
/**
* A class to keep all configurables of the plugin associated with a world
*
*/
public class ConfigurationCacheStore
{
public final LoggingConfig logging;
private final Map<String, ConfigItem> configMap = new HashMap<String, ConfigItem>();
private final NoCheatConfiguration data;
/**
* Instantiate a config cache and populate it with the data of a Config tree (and its parent tree)
*/
public ConfigurationCacheStore(NoCheatConfiguration data)
{
logging = new LoggingConfig(data);
this.data = data;
}
@SuppressWarnings("unchecked")
public <T extends ConfigItem> T get(String id)
{
return (T)configMap.get(id);
}
public void set(String id, ConfigItem config)
{
configMap.put(id, config);
}
public NoCheatConfiguration getConfiguration()
{
return this.data;
}
}

View File

@@ -0,0 +1,257 @@
package com.earth2me.essentials.anticheat.config;
import com.earth2me.essentials.anticheat.NoCheat;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.*;
/**
* Central location for everything that's described in the configuration file(s)
*
*/
public class ConfigurationManager
{
private final static String configFileName = "config.yml";
private final Map<String, ConfigurationCacheStore> worldnameToConfigCacheMap = new HashMap<String, ConfigurationCacheStore>();
private FileHandler fileHandler;
private final NoCheat plugin;
private static class LogFileFormatter extends Formatter
{
private final SimpleDateFormat date;
public LogFileFormatter()
{
date = new SimpleDateFormat("yy.MM.dd HH:mm:ss");
}
@Override
public String format(LogRecord record)
{
StringBuilder builder = new StringBuilder();
Throwable ex = record.getThrown();
builder.append(date.format(record.getMillis()));
builder.append(" [");
builder.append(record.getLevel().getLocalizedName().toUpperCase());
builder.append("] ");
builder.append(record.getMessage());
builder.append('\n');
if (ex != null)
{
StringWriter writer = new StringWriter();
ex.printStackTrace(new PrintWriter(writer));
builder.append(writer);
}
return builder.toString();
}
}
public ConfigurationManager(NoCheat plugin, File rootConfigFolder)
{
this.plugin = plugin;
// Setup the real configuration
initializeConfig(rootConfigFolder);
}
/**
* Read the configuration file and assign either standard values or whatever is declared in the file
*
* @param configurationFile
*/
private void initializeConfig(File rootConfigFolder)
{
// First try to obtain and parse the global config file
NoCheatConfiguration root = new NoCheatConfiguration();
root.setDefaults(new DefaultConfiguration());
root.options().copyDefaults(true);
root.options().copyHeader(true);
File globalConfigFile = getGlobalConfigFile(rootConfigFolder);
if (globalConfigFile.exists())
{
try
{
root.load(globalConfigFile);
}
catch (Exception e)
{
e.printStackTrace();
}
}
try
{
root.save(globalConfigFile);
}
catch (Exception e)
{
e.printStackTrace();
}
root.regenerateActionLists();
// Create a corresponding Configuration Cache
// put the global config on the config map
worldnameToConfigCacheMap.put(null, new ConfigurationCacheStore(root));
plugin.setFileLogger(setupFileLogger(new File(rootConfigFolder, root.getString(ConfPaths.LOGGING_FILENAME))));
// Try to find world-specific config files
Map<String, File> worldFiles = getWorldSpecificConfigFiles(rootConfigFolder);
for (Entry<String, File> worldEntry : worldFiles.entrySet())
{
File worldConfigFile = worldEntry.getValue();
NoCheatConfiguration world = new NoCheatConfiguration();
world.setDefaults(root);
try
{
world.load(worldConfigFile);
worldnameToConfigCacheMap.put(worldEntry.getKey(), new ConfigurationCacheStore(world));
// write the config file back to disk immediately
world.save(worldConfigFile);
}
catch (Exception e)
{
plugin.getLogger().warning("Couldn't load world-specific config for " + worldEntry.getKey());
e.printStackTrace();
}
world.regenerateActionLists();
}
}
private static File getGlobalConfigFile(File rootFolder)
{
File globalConfig = new File(rootFolder, configFileName);
return globalConfig;
}
private static Map<String, File> getWorldSpecificConfigFiles(File rootFolder)
{
HashMap<String, File> files = new HashMap<String, File>();
if (rootFolder.isDirectory())
{
for (File f : rootFolder.listFiles())
{
if (f.isFile())
{
String filename = f.getName();
if (filename.matches(".+_" + configFileName + "$"))
{
// Get the first part = world name
String worldname = filename.substring(0, filename.length() - (configFileName.length() + 1));
files.put(worldname, f);
}
}
}
}
return files;
}
private Logger setupFileLogger(File logfile)
{
Logger l = Logger.getAnonymousLogger();
l.setLevel(Level.INFO);
// Ignore parent's settings
l.setUseParentHandlers(false);
for (Handler h : l.getHandlers())
{
l.removeHandler(h);
}
if (fileHandler != null)
{
fileHandler.close();
l.removeHandler(fileHandler);
fileHandler = null;
}
try
{
try
{
logfile.getParentFile().mkdirs();
}
catch (Exception e)
{
e.printStackTrace();
}
fileHandler = new FileHandler(logfile.getCanonicalPath(), true);
fileHandler.setLevel(Level.ALL);
fileHandler.setFormatter(new LogFileFormatter());
l.addHandler(fileHandler);
}
catch (Exception e)
{
e.printStackTrace();
}
return l;
}
/**
* Reset the loggers and flush and close the fileHandlers to be able to use them next time without problems
*/
public void cleanup()
{
fileHandler.flush();
fileHandler.close();
Logger l = Logger.getLogger("NoCheat");
l.removeHandler(fileHandler);
fileHandler = null;
}
/**
* Get the cache of the specified world, or the default cache, if no cache exists for that world.
*
* @param worldname
* @return
*/
public ConfigurationCacheStore getConfigurationCacheForWorld(String worldname)
{
ConfigurationCacheStore cache = worldnameToConfigCacheMap.get(worldname);
if (cache != null)
{
return cache;
}
else
{
// Enter a reference to the cache under the new name
// to be faster in looking it up later
cache = worldnameToConfigCacheMap.get(null);
worldnameToConfigCacheMap.put(worldname, cache);
return cache;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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";
}

View File

@@ -0,0 +1,38 @@
package com.earth2me.essentials.anticheat.data;
import com.earth2me.essentials.anticheat.DataItem;
import java.util.HashMap;
import java.util.Map;
public class DataStore
{
private final Map<String, DataItem> dataMap = new HashMap<String, DataItem>();
private final Statistics statistics = new Statistics();
private final long timestamp = System.currentTimeMillis();
@SuppressWarnings("unchecked")
public <T extends DataItem> T get(String id)
{
return (T)dataMap.get(id);
}
public void set(String id, DataItem data)
{
dataMap.put(id, data);
}
public Map<String, Object> collectData()
{
Map<String, Object> map = statistics.get();
map.put("nocheat.starttime", timestamp);
map.put("nocheat.endtime", System.currentTimeMillis());
return map;
}
public Statistics getStatistics()
{
return statistics;
}
}

View File

@@ -0,0 +1,145 @@
package com.earth2me.essentials.anticheat.data;
import com.earth2me.essentials.anticheat.actions.Action;
import java.util.HashMap;
import java.util.Map;
/**
* Store amount of action executions for last 60 seconds for various actions
*
*/
public class ExecutionHistory
{
private static class ExecutionHistoryEntry
{
private final int executionTimes[];
private long lastExecution = 0;
private int totalEntries = 0;
private long lastClearedTime = 0;
private ExecutionHistoryEntry(int monitoredTimeFrame)
{
this.executionTimes = new int[monitoredTimeFrame];
}
/**
* Remember an execution at the specific time
*/
private void addCounter(long time)
{
// clear out now outdated values from the array
if (time - lastClearedTime > 0)
{
// Clear the next few fields of the array
clearTimes(lastClearedTime + 1, time - lastClearedTime);
lastClearedTime = time + 1;
}
executionTimes[(int)(time % executionTimes.length)]++;
totalEntries++;
}
/**
* Clean parts of the array
*
* @param start
* @param length
*/
private void clearTimes(long start, long length)
{
if (length <= 0)
{
return; // nothing to do (yet)
}
if (length > executionTimes.length)
{
length = executionTimes.length;
}
int j = (int)start % executionTimes.length;
for (int i = 0; i < length; i++)
{
if (j == executionTimes.length)
{
j = 0;
}
totalEntries -= executionTimes[j];
executionTimes[j] = 0;
j++;
}
}
public int getCounter()
{
return totalEntries;
}
public long getLastExecution()
{
return lastExecution;
}
public void setLastExecution(long time)
{
this.lastExecution = time;
}
}
// Store data between Events
// time + action + action-counter
private final Map<String, Map<Action, ExecutionHistoryEntry>> executionHistories;
public ExecutionHistory()
{
executionHistories = new HashMap<String, Map<Action, ExecutionHistoryEntry>>();
}
/**
* Returns true, if the action should be executed, because all time criteria have been met. Will add a entry with
* the time to a list which will influence further requests, so only use once and remember the result
*
* @param check
* @param action
* @param time a time IN SECONDS
* @return
*/
public boolean executeAction(String check, Action action, long time)
{
Map<Action, ExecutionHistoryEntry> executionHistory = executionHistories.get(check);
if (executionHistory == null)
{
executionHistory = new HashMap<Action, ExecutionHistoryEntry>();
executionHistories.put(check, executionHistory);
}
ExecutionHistoryEntry entry = executionHistory.get(action);
if (entry == null)
{
entry = new ExecutionHistoryEntry(60);
executionHistory.put(action, entry);
}
// update entry
entry.addCounter(time);
if (entry.getCounter() > action.delay)
{
// Execute action?
if (entry.getLastExecution() <= time - action.repeat)
{
// Execute action!
entry.setLastExecution(time);
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,80 @@
package com.earth2me.essentials.anticheat.data;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.NoCheatPlayer;
import com.earth2me.essentials.anticheat.player.NoCheatPlayerImpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.bukkit.entity.Player;
/**
* Provide secure access to player-specific data objects for various checks or check groups.
*/
public class PlayerManager
{
// Store data between Events
private final Map<String, NoCheatPlayerImpl> players;
private final NoCheat plugin;
public PlayerManager(NoCheat plugin)
{
this.players = new HashMap<String, NoCheatPlayerImpl>();
this.plugin = plugin;
}
/**
* Get a data object of the specified class. If none is stored yet, create one.
*/
public NoCheatPlayer getPlayer(Player player)
{
NoCheatPlayerImpl p = this.players.get(player.getName().toLowerCase());
if (p == null)
{
p = new NoCheatPlayerImpl(player, plugin);
this.players.put(player.getName().toLowerCase(), p);
}
p.setLastUsedTime(System.currentTimeMillis());
p.refresh(player);
return p;
}
public void cleanDataMap()
{
long time = System.currentTimeMillis();
List<String> removals = new ArrayList<String>(5);
for (Entry<String, NoCheatPlayerImpl> e : this.players.entrySet())
{
if (e.getValue().shouldBeRemoved(time))
{
removals.add(e.getKey());
}
}
for (String key : removals)
{
this.players.remove(key);
}
}
public Map<String, Object> getPlayerData(String playerName)
{
NoCheatPlayer player = this.players.get(playerName.toLowerCase());
if (player != null)
{
return player.getDataStore().collectData();
}
return new HashMap<String, Object>();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,82 @@
package com.earth2me.essentials.anticheat.data;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
public class Statistics
{
public enum Id
{
BB_DIRECTION("blockbreak.direction"), BB_NOSWING("blockbreak.noswing"),
BB_REACH("blockbreak.reach"), BP_DIRECTION("blockplace.direction"),
BP_REACH("blockplace.reach"), CHAT_COLOR("chat.color"),
CHAT_SPAM("chat.spam"), FI_DIRECTION("fight.direction"),
FI_NOSWING("fight.noswing"), FI_REACH("fight.reach"),
FI_SPEED("fight.speed"), INV_DROP("inventory.drop"),
INV_BOW("inventory.instantbow"), INV_EAT("inventory.instanteat"),
MOV_RUNNING("moving.running"), MOV_FLYING("moving.flying"),
MOV_MOREPACKETS("moving.morepackets"), MOV_NOFALL("moving.nofall"),
MOV_SNEAKING("moving.sneaking"), MOV_SWIMMING("moving.swimming"),
FI_GODMODE("fight.godmode"), FI_INSTANTHEAL("fight.instantheal");
private final String name;
private Id(String name)
{
this.name = name;
}
public String toString()
{
return this.name;
}
}
private final Map<Id, Double> statisticVLs = new HashMap<Id, Double>(Id.values().length);
private final Map<Id, Integer> statisticFails = new HashMap<Id, Integer>(Id.values().length);
public Statistics()
{
// Initialize statistic values
for (Id id : Id.values())
{
statisticVLs.put(id, 0D);
statisticFails.put(id, 0);
}
}
public void increment(Id id, double vl)
{
Double stored = statisticVLs.get(id);
if (stored == null)
{
stored = 0D;
}
statisticVLs.put(id, stored + vl);
Integer failed = statisticFails.get(id);
if (failed == null)
{
failed = 0;
}
statisticFails.put(id, failed + 1);
}
public Map<String, Object> get()
{
Map<String, Object> map = new TreeMap<String, Object>();
for (Entry<Id, Double> entry : statisticVLs.entrySet())
{
map.put(entry.getKey().toString() + ".vl", entry.getValue().intValue());
}
for (Entry<Id, Integer> entry : statisticFails.entrySet())
{
map.put(entry.getKey().toString() + ".failed", entry.getValue());
}
return map;
}
}

View File

@@ -0,0 +1,66 @@
package com.earth2me.essentials.anticheat.debug;
import com.earth2me.essentials.anticheat.EventManager;
import com.earth2me.essentials.anticheat.NoCheat;
import com.earth2me.essentials.anticheat.config.ConfigurationCacheStore;
import java.util.List;
import org.bukkit.World;
/**
* Prints the list of active checks per world on startup, if requested
*
*/
public class ActiveCheckPrinter
{
public static void printActiveChecks(NoCheat plugin, List<EventManager> eventManagers)
{
boolean introPrinted = false;
// Print active checks for NoCheat, if needed.
for (World world : plugin.getServer().getWorlds())
{
StringBuilder line = new StringBuilder(" ").append(world.getName()).append(": ");
int length = line.length();
ConfigurationCacheStore cc = plugin.getConfig(world);
if (!cc.logging.showactivechecks)
{
continue;
}
for (EventManager em : eventManagers)
{
if (em.getActiveChecks(cc).isEmpty())
{
continue;
}
for (String active : em.getActiveChecks(cc))
{
line.append(active).append(' ');
}
if (!introPrinted)
{
plugin.getLogger().info("Active Checks: ");
introPrinted = true;
}
plugin.getServer().getLogger().info(line.toString());
line = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
line.append(' ');
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,94 @@
name: EssentialsAntiCheat
main: com.earth2me.essentials.anticheat.NoCheat
# Note to developers: This next line cannot change, or the automatic versioning system will break.
version: TeamCity
website: http://tiny.cc/EssentialsWiki
description: Detect and Fight the exploitation of various Flaws/Bugs in Minecraft.
authors: [Evenprime, md_5]
commands:
nocheat:
description: NoCheat command(s)
permission: nocheat.admin.commands
usage: |
/<command> permlist player [permission]: list NoCheat permissions of player, optionally only if beginning with [permission]
/<command> playerinfo player: show the collected data NoCheat collected about a player
/<command> reload: fast reload of NoCheats configuration file(s) - needs additional permissions
permissions:
nocheat:
description: Allow a player to bypass all checks and give him all admin permissions
children:
nocheat.admin:
description: Give a player all admin rights
children:
nocheat.admin.chatlog:
description: Player can see NoCheats log messages in the ingame chat
nocheat.admin.commands:
description: allow use of the "nocheat" commands (may be given to players to allow them to check statistics)
nocheat.admin.reload:
description: allow access to the special "nocheat reload" command (only intended for the actual server administrator)
nocheat.checks:
description: Allow the player to bypass all checks
children:
nocheat.checks.moving:
description: Allow the player to bypass all moving related checks
children:
nocheat.checks.moving.runfly:
description: Allow a player to move as free and as fast as he wants (ignores flying, swimming and sneaking settings)
nocheat.checks.moving.flying:
description: Allow a player to fly, but only within given speed limits (ignores swimming and sneaking settings)
nocheat.checks.moving.swimming:
description: Allow a player to move through water without slowdown
nocheat.checks.moving.sneaking:
description: Allow a player to sneak without slowdown
nocheat.checks.moving.nofall:
description: Allow a player to cheat and not take fall damage at all
nocheat.checks.moving.morepackets:
description: Allow a player to send more move-event-packets than normal, causing him to move faster than normal
nocheat.checks.blockbreak:
description: Allow the player to bypass all blockbreak checks
children:
nocheat.checks.blockbreak.reach:
description: Allow a player to break blocks at maximum range (about 6-7 blocks, in creative mode unlimited)
nocheat.checks.blockbreak.direction:
description: Allow a player to break blocks that are not in front of them
nocheat.checks.blockbreak.noswing:
description: Allow a player to break blocks without swinging their arm
nocheat.checks.blockplace:
description: Allow the player to bypass all blockplace checks
children:
nocheat.checks.blockplace.reach:
description: Allow a player to place blocks at maximum range (about 6-7 blocks)
nocheat.checks.blockplace.direction:
description: Allow a player to place blocks outside their line of view
nocheat.checks.chat:
description: Allow the player to bypass all chat checks
children:
nocheat.checks.chat.spam:
description: Allow a player to send an infinite amount of chat messages
nocheat.checks.chat.color:
description: Allow a player to send colored chat messages
nocheat.checks.fight:
description: Allow the player to bypass all fight checks
children:
nocheat.checks.fight.direction:
description: Allow a player to attack players and monster even if they are not in his field of view
nocheat.checks.fight.noswing:
description: Allow a player to fight without swinging their arm
nocheat.checks.fight.reach:
description: Allow a player to fight over bigger distances than usual
nocheat.checks.fight.speed:
description: Allow a player to attack faster than usual
nocheat.checks.fight.godmode:
description: Allow a player to not take damage by exploiting a design flaw in Minecraft
nocheat.checks.fight.instantheal:
description: Allow a player to artificially speed up his health regeneration
nocheat.checks.inventory:
description: Allow the player to bypass all inventory checks
children:
nocheat.checks.inventory.drop:
description: Allow a player to drop more items in a short timeframe than the defined limit
nocheat.checks.inventory.instanteat:
description: Allow a player to eat food faster than normally possible
nocheat.checks.inventory.instantbow:
description: Allow a player to charge his bow faster than usual