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:
939
EssentialsAntiCheat/Instructions.txt
Normal file
939
EssentialsAntiCheat/Instructions.txt
Normal 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. ;)
|
9
EssentialsAntiCheat/README.txt
Normal file
9
EssentialsAntiCheat/README.txt
Normal 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)
|
||||
#
|
||||
#
|
21
EssentialsAntiCheat/pom.xml
Normal file
21
EssentialsAntiCheat/pom.xml
Normal 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>
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package com.earth2me.essentials.anticheat;
|
||||
|
||||
|
||||
public interface ConfigItem
|
||||
{
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package com.earth2me.essentials.anticheat;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Every class that is extending this has to implement an empty Constructor()
|
||||
*
|
||||
*/
|
||||
public interface DataItem
|
||||
{
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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" : "");
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
||||
}
|
@@ -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 + ".";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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()]);
|
||||
}
|
||||
}
|
@@ -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 = "";
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
@@ -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";
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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>();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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(' ');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
94
EssentialsAntiCheat/src/plugin.yml
Normal file
94
EssentialsAntiCheat/src/plugin.yml
Normal 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
|
Reference in New Issue
Block a user