Skip to content
ShadowMC

Configuration

ShadowBlocker

An anticheat that shows its work instead of shouting.

Premium v1.0.1

Where the files are

plugins/ShadowBlocker/

The plugin creates them on first start and updates them by itself on new versions, keeping your changes.

Applying changes

/shadowblocker reload

Reloads the files without restarting the server.

What’s inside

7 files · 314 settings

Start with: config.yml

Settings

The files that decide how the plugin behaves. Start here.

config.yml

General settings
94 settings
plugins/ShadowBlocker/config.yml
File notes

This file decides HOW the plugin behaves. The thresholds of the individual checks live in checks.yml instead, one by one, with what they detect and what can produce a false positive written next to them.

Every value here already has the right number for a normal server: you can copy the jar in and never open this file. When you do need to change something, the comment above each key says WHAT it does and, above all, WHY it is the way it is.

A wrong value never stops the plugin: it is replaced with the default one and the console gets the path and what was expected there.

On update your files are moved to the new version automatically: every value you changed (and every threshold you tuned in checks.yml) is carried over, new options arrive with their comments, the old files are kept in backup/<date>/, and if something needs your attention you will find PORTING-<version>.txt in the plugin folder.

Durations are written in plain text: 500ms, 20t (ticks), 30s, 5m, 2h30m, 7d. A number with no unit means seconds.

config-version1

How ShadowBlocker decides

Every check follows the same ladder, and each step is harder to reach than the one below it:

Keep reading

1. OVERSHOOT. A check measures something (a speed, a distance, a

time) and compares it with what Minecraft allows in that exact
situation, plus margins for latency. Going over is NOT a
violation yet. Nobody sees it, except a staff member who is
watching that player with /shadowblocker debug <player>.

2. VIOLATION. The overshoots have repeated more than the check's

'buffer' (checks.yml). The violation is written to the evidence
log with everything around it, and adds 'weight' points to that
check. The points drop by 'decay' every minute.

3. ALERT. Only when the points of that check reach its

'alert-threshold' does the staff get a message in chat. One odd
moment - a lag spike, a bugged block - never reaches this step.

4. SETBACK. For the movement checks marked 'setback: true', at the

same moment the player is moved back to the last spot where they
were standing, clean. It undoes the impossible movement and is
not a punishment (see setbacks below).

5. PUNISHMENT. Only with punishments.enabled: true (off by default),

and only with the points of checks that reached their own
'punish-threshold'. The checks that only corroborate (clicks,
aim, walls, packet order...) have punish-threshold 0: on their
own they never punish anybody.

Before step 1 even happens, a check does not run at all while an exemption window is open: teleports (any plugin), respawns, world changes, knockback and velocity from plugins, explosions, pistons, vehicles, elytra, riptide, server lag, connection hiccups, blocks refused by a protection plugin, chunks the client has not received. Every window is lengthened by the player's ping. See exemptions.

A false positive? do this

1. Look at what was measured:

/shadowblocker violations <player>     the latest evidence
/shadowblocker evidence <number>       one of them in full:
                                       value vs limit, blocks
                                       around, effects, ping
/shadowblocker player <player>         points of every check
                                       and the alert thresholds

2. Watch it happen again, live:

/shadowblocker debug <player>          every overshoot, with
                                       value, limit and buffer
                                       (same command to stop;
                                       from the console it goes
                                       to the log)

3. Stop it while you investigate:

/shadowblocker exempt <player> 10m movement
                           suspends a family (or 'all') for a
                           while, and closes on its own
/shadowblocker check <id> off          one check, until restart
exemptions.disabled-worlds             every check, one world
disabled-worlds in checks.yml          one check, one world
integrations.shadowregions.suspend     one region
integrations.shadowgames.suspend       one arena
/shadowblocker clear <player>          wipes their points (the
                                       evidence stays)

4. Fix it for good: raise that check's 'buffer' or

'alert-threshold' in checks.yml (not the limit), or set
engine.sensitivity to relaxed.

5. Report it to us with the evidence file: /shadowblocker export

<player> writes every piece of evidence to one file, which is
exactly what we need to reproduce it.

The most important thing in the whole file

Freshly installed, ShadowBlocker PUNISHES NOBODY. It detects everything, alerts the staff, writes the evidence, and stops there.

Keep reading

That is not timidity: it is the only honest thing to do. An anticheat that has just been switched on does not know your server yet - it does not know you have a grappling hook plugin, that your arena has double jump, that half of your players connect from 250 ms away. A plugin that starts banning before it knows those things bans the wrong people, and a single video of a false positive costs more than ten sales.

THE RIGHT WAY:

1. install it and leave it alone for a week;
2. now and then look at /shadowblocker violations and ask yourself
   whether what you see makes sense;
3. where it does not, raise that check's buffer in checks.yml
   (the buffer matters far more than the threshold);
4. ONLY THEN set punishments.enabled to true.

In the meantime the staff punishes by hand from the buttons in the alerts, which work from the first minute.

punishments3 keys
enabledfalse

false = observation mode. See the paragraph above.

jail-cell""

The ShadowEssentials cell used by the JAIL action. Empty = the first available one. If you do not have ShadowEssentials, the JAIL action does nothing and the console says why.

punishments.ladder4 keys

The ladder: when the score reaches this much, do that.

Keep reading

Every step fires ONCE per play session. The order you write them in does not matter: they are sorted by score on their own.

Available actions:

alert    only an alert to the staff
log      a line in the console, more visible
ticket   opens a case in the ShadowControl panel with the evidence
kick     kicks from the server
mute     mutes             (needs ShadowEssentials)
jail     sends to jail     (needs ShadowEssentials)
ban      bans              (needs ShadowEssentials)
command  runs a command from the console, with %player% and %reason%

The scores below are POINTS THAT COUNT FOR PUNISHMENTS: only the points of checks that have reached their own punish-threshold in checks.yml. With the default values, 60 points are about eight violations of a movement check close together; the checks that only corroborate (clicks, aim, walls, packet order) never count. Points drop by each check's 'decay' every minute.

MIND the permanent ban: there is none here, and that is deliberate. A ban given automatically must be undoable by a staff member who spots the mistake, and a permanent one handed out at night with nobody watching is the fastest way to lose an honest player forever.

punishments.ladder.first2 keys
score60
actionlog
punishments.ladder.second2 keys
score120
actionticket
punishments.ladder.third3 keys
score200
actionkick
reason"Disallowed behaviour"
punishments.ladder.fourth4 keys
score350
actionban
duration"7d"
reason"Disallowed behaviour detected"

The engine

engine5 keys
sensitivity"balanced"

How strict the whole plugin is, in one word:

Keep reading
relaxed    every buffer and every alert/punish threshold x1.5.
           Slower to alert, even fewer false positives. For
           servers with many high-ping or Bedrock players, or
           with plugins that move players in unusual ways.
balanced   checks.yml exactly as written. The default.
strict     x0.75: alerts sooner. Only after a week of alerts that
           you have looked at and found correct.

It multiplies the values of checks.yml at load time, so you can keep your own tuning there and still switch preset.

decay-per-minute10.0

How many points a player's score drops by every minute, for the checks that do not set their own 'decay' in checks.yml (every shipped check does) and for reports sent by other plugins.

Keep reading

The score has to come down, otherwise it does not describe a player: it describes their career. Someone who has played for two years and picked up four violations from as many hiccups of the network would end up, on some ordinary day, above a ban threshold - because of four moments spread over two years. The score answers one question, and the right one: IS THIS PERSON CHEATING NOW?

The decay is linear and not exponential, on purpose: "it drops by ten a minute" can be worked out in your head, and a parameter nobody can predict is a parameter nobody tunes.

With 10 a minute and the default scores, someone who reaches 120 points is clean again in twelve minutes. 0 = the score never drops. Do not do that.

buffer-decay0.25

How much a check's buffer drops by when a sample is fine.

The buffer is how many times in a row a check has to go over before it really counts. It goes up by 1 on every overshoot and down by this value on every clean sample.

It MUST be less than 1, and it must not be 0. If one clean tick reset the buffer, cheating intermittently - half a second on, half a second off - would be enough never to cross any threshold. That is literally how "pulsing" cheats are built.

snapshot-period-ticks1

How many ticks apart the world around a player is photographed.

Keep reading

1 = every tick, and that is what it takes to measure well. The snapshot is taken ONLY for players who have moved since last time: somebody standing still in a base costs one comparison and no more.

Raising it to 2 halves the cost and makes the measurements less precise: the exemptions for teleports and knockback arrive one tick later, so a few more false positives. Only touch this if you have measured a problem, not "just in case".

engine.packets1 key
enabledtrue

Read the packets where they arrive, instead of where the server uses them.

Keep reading

It is the difference between an anticheat that measures the player's time and one that measures how hard the server was working. It is needed for:

- the packet checks (order, rate, fields);
- the precise measurement of how long a dig took;
- not costing the server any ticks.

If you switch it off, movement is read from the game's events: it costs more, it measures worse, and the packet checks stay off. The plugin tells you so in the console.

It switches itself off, saying so, on a server where it cannot attach: that is not an error to report, it is a different server.

Alerts to the staff

alerts3 keys
cooldown"3s"

How long to wait before repeating the alert of the same check for the same player.

A cheat produces violations in bursts: with no brake, one alert for each of them fills every staff member's chat and makes them switch alerts off forever - which makes the plugin useless exactly when it is working. In the meantime the score carries on rising in silence.

to-consoletrue

Write the alerts in the console as well.

The console is the only staff member who is always there, and it is the one read by whoever opens the logs the next day. If you have hundreds of violations a day and your logs are getting heavy, switch it off: the evidence stays in the log all the same.

log-quiet-violationstrue

Also write one "[flag]" line in the console for the violations that have NOT reached their check's alert-threshold yet (nobody in chat sees them). It is how you see what the plugin is thinking without disturbing the staff, and it is what to read when tuning. Switch it off if your console is busy.

Setbacks

For the movement checks with 'setback: true' in checks.yml (fly, speed, jump, climb, phase): when a violation reaches the check's alert-threshold, the player is moved back to the last spot where they were standing on real ground, outside blocks, with no movement violation in the last second.

Keep reading

It is the cheapest answer when we are wrong: an honest player ends up a few blocks back and keeps playing. A cheater loses what the cheat gave them. It is NOT a punishment and works in observation mode too.

setbacks4 keys
enabledtrue

false = never move anybody; the alerts still arrive.

max-distance48.0

A spot further than this (in blocks) is not a correction any more, it would be a teleport: nothing is done.

max-age"30s"

A spot older than this is stale: nothing is done.

cooldown"1s"

At most one setback per player this often. The teleport opens its own exemption window anyway.

The evidence log

Every violation is saved with everything that was needed to judge it around it: position, velocity, blocks around, what was in hand, effects, ping, open exemptions. That is what lets a staff member DECIDE instead of trusting.

evidence3 keys
memory-cap500

How many pieces of evidence to keep in memory, ready for /shadowblocker violations.

In the free version the real cap is 50 and this value does not count. With a licence the evidence also goes to the database, and this number is only the fast window.

keep-for"30d"

How long to keep the evidence on the database.

A month is long enough to answer "had we already told them?", which is the question that makes a history useful. Beyond that it is weight. Zero = forever.

write-filestrue

Also write a text file for every serious violation, inside evidence/, organised by day.

It is there to attach to an appeal, and to let the panel read it without the panel having to know anything about our database.

The cases

This is the feature this plugin exists for: a serious violation becomes, on its own, something somebody has to look at, with the evidence attached and a summary in readable words.

requests3 keys
score120

From which score upwards a case is opened.

0 = never. Raise it if your staff receives too many cases: three real ones a day are better than thirty of which nobody looks at any.

cooldown"5m"

At most one case per player how often.

It is there to keep the staff list readable: twenty cases for the same person cancel out the value of the feature.

ai-summarytrue

Ask the ShadowControl assistant for a two-line summary.

Optional twice over: it needs ShadowControl with the assistant set up, and it needs our licence. The summary we write ourselves ALWAYS arrives, and arrives immediately; this one is added afterwards, when it comes back. The case never waits for a call out to the internet.

The exemptions

The most important paragraph after the one about punishments.

Keep reading

Latency and teleports are the number one cause of false positives in every anticheat ever written. Each of these windows suspends the checks for a moment after an event that makes it impossible to measure anything sensible.

The default values are GENEROUS on purpose. Lowering them does not catch you more cheaters - somebody cheating does it all evening, not for the two seconds after a teleport - it only catches you more innocent people.

To find out whether a window is staying open too long on somebody:

/shadowblocker exempt <player>
exemptions7 keys
lag-tps18.0

Below how many ticks per second (one-minute average) the server is considered to be struggling: the movement, combat and packet checks are suspended for everybody while it lasts.

When the ticks take twice as long, every time-based measurement doubles. 18 means "more than 10% slow". Lower it (15-16) only if your server lives permanently around 18-19 TPS and you still want detection there.

lag-spike"200ms"

A single server tick longer than this opens the lag window for everybody at once. The one-minute average above does not notice a one-second freeze (a world save, a garbage collection, a chunk being generated); this does. A normal tick is 50ms. 0 = off.

connection-gap"300ms"

A silence of a player's connection longer than this, followed by a burst of everything that was queued, opens the "connection" window for that player (movement, combat and packet checks). A client in game sends a packet every 50ms, always. 0 = off.

ping-extension1.0

Every window below is lengthened by the player's ping (the highest of the last seconds) multiplied by this. A two-second window is generous at 50ms and tight at 900ms: this is what makes it fair for everybody. 0 = off.

max-ping-extension"2s"

The most a window can be lengthened by the ping.

disabled-worlds[ ]

Worlds where NO check runs at all (creative build worlds, lobbies with parkour plugins...). To switch off one check in one world use disabled-worlds inside that check in checks.yml.

exemptions.windows20 keys
join"6s"

Just joined: the client is loading chunks and sending inconsistent positions.

teleport"3s"

THE NUMBER ONE CAUSE. Between the moment the server moves somebody and the moment the client confirms it, movements arrive that start from the old place: standing still they look like flight, running they look like two hundred blocks a second.

world-change"6s"

Like a teleport, plus the chunk loading.

portal"6s"
respawn"4s"
velocity"2s"

The server sent a velocity to the client: knockback, an arrow, a wind charge, a plugin's spring plate. For a few ticks the player moves faster than any formula, and it has nothing to do with what they pressed.

explosion"3s"
piston"2s"
vehicle"2500ms"

Got on or off a vehicle: the physics change mid-movement.

gliding"2500ms"
riptide"4s"
bounce"2s"
gamemode"3s"
flight"3s"
effect"1500ms"

An effect that changes movement has just arrived or just ended.

lag"4s"

The server struggled. This one applies to everybody at once.

chunk"3s"

A chunk not loaded yet under their feet.

plugin"2s"

Another plugin moved or changed the player. It is also the window used by the public API when a minigame asks for the checks to be suspended.

connection"3s"

The player's connection stopped and then delivered everything at once (see connection-gap above). The silence itself is added to this window.

ghost-block"1500ms"

A block placement or break was cancelled by another plugin (a protection, an arena): for a moment the client sees a block the server does not have, or the other way round.

Bedrock players (Geyser / Floodgate)

A Bedrock player does not run the Java client: the bridge translates their movement, hits and digs into Java packets, and the translation does not follow the Java client's physics and timing. The checks written for Java would flag them for playing from a phone.

bedrock4 keys
detecttrue

Recognise Bedrock players: Floodgate's or Geyser's API when they are installed, otherwise the client name. false = treat everybody as Java.

detect-by-uuidtrue

Also recognise them by the Floodgate UUID (first half all zeros). Leave it on unless you use a UUID scheme of your own.

exempt-families[movement, combat, packet]

The families of checks that do NOT run on Bedrock players. Families: movement, combat, interaction, packet

exempt-checks[fastbreak, visibility]

Single checks that do not run on them either (from the other families). Dig timing and line of sight are translated too loosely by the bridges to be judged.

The integrations

All optional. The plugin starts and protects a server where nothing else of the suite is installed; when a piece switches off because another plugin is missing, the console says which one and what went off with it.

integrations4 keys
integrations.shadowcontrol2 keys
reportstrue

Write requests.yml, which the panel reads.

report-every"1m"
integrations.shadowgames1 key
suspend[ ]

Suspend certain checks inside arenas.

A minigame with double jump, grappling hooks or launch pads has nothing to do with Minecraft's physics, and insisting in there flags exactly the people who are enjoying themselves.

It is written: "game:arena = family,family"

or:             "game:* = family"   for every arena of a game

Families: movement, combat, interaction, packet

Examples (commented out: switch them on if you have those games):

suspend:
  - "bedwars:* = movement"
  - "parkour:* = movement,packet"
  - "skywars:arena3 = movement"
integrations.shadowregions1 key
suspend[ ]

As above, but by region. It is there for built events, where a staff member fires people into the air for fun.

It is written: "regionname = family,family"

suspend:
  - "event_arena = movement,combat"
integrations.other-anticheat2 keys
raise-buffersfalse

What to do if the server already has another anticheat.

Keep reading

Two anticheats together do not protect more: they get in each other's way. Each one corrects movement in its own way and the other reads those corrections as if they were the player, and the result is false positives on both sides - with the blame always seeming to fall on the newer one.

The right thing is to keep only one. If you are migrating and have two of them for a week, set this to true: it raises the buffers of every check, detection becomes slower and false positives far fewer.

extra-buffer6

Where the evidence ends up

Out of the box: an H2 file inside the plugin folder. Nothing to install, no user to create, no password.

The database is ONLY for the archive: detection, the score, the alerts and the latest evidence in memory all work without it. If it cannot be opened, the plugin starts anyway - an anticheat that does not start is an unprotected server.

storage5 keys
table-prefix"sb_"

The table prefix. Change it if several plugins share the same database.

storage.mysql7 keys
enabledfalse

Shared MySQL is part of the Premium version.

It is needed on a network: without it the score and the evidence start from zero on every server switch, which is also the first trick somebody cheating on a network learns.

host"127.0.0.1"
port3306
database"shadowblocker"
username"root"
password""
parameters"useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&characterEncoding=utf8"
storage
fallback-to-localtrue

If MySQL does not answer, fall back to the local file instead of not starting. Leave it true: the evidence matters, but not as much as having a protected server.

storage.h21 key
auto-serverfalse

Lets another process open the same H2 file. Only needed if you want to look at the database while the server is running.

storage.pool5 keys
maximum-pool-size4

Small on purpose: no queries are made inside the game here. Evidence is written asynchronously and read when a staff member opens a list.

minimum-idle1
connection-timeout-ms10000
idle-timeout-ms600000
max-lifetime-ms1800000

Licence

ShadowBlocker is a single jar with two versions inside it.

Keep reading

With no key it runs the FREE version, and the free one has ALL THE DETECTION: every movement, combat, interaction and packet check, the score, the adjustable thresholds, the explanation of every violation, observation mode, the alerts, the exemptions, both languages. No check is behind a payment, and that is not generosity: an anticheat that protects half the players has a hole, and the hole is found first by whoever is cheating.

What you pay for is everything AROUND the detection:

- automatic punishments (the free one alerts, the staff decides);
- the evidence log on the database, searchable and with no expiry;
- the cases on ShadowControl with the assistant's summary;
- suspension by arena and by region;
- shared MySQL across a network;
- exporting the evidence for an appeal.

The plugin NEVER switches itself off over a licence problem. The worst case is going back to free, writing in the console why. In an anticheat that counts twice over: switching off would mean leaving the server unprotected exactly when nobody notices.

Usually nothing here needs touching: the key is activated in game with

/shadowblocker license claim <purchase-code>
/shadowblocker license activate <key>

and is written to license.yml, which lives outside this file on purpose: a config gets sent to support, a licence does not.

license4 keys

THE KEY LIVES IN license.yml (same folder as this file), or in the central plugins/ShadowControl/licenses.yml for the whole suite. The 'key' below is only a fallback for deploy systems that generate config.yml: leave it empty.

The key does NOT live here: it lives in license.yml, in this folder, written at first start (or in plugins/ShadowControl/licenses.yml for the whole suite). Only if you would rather keep it here instead of in license.yml. The SHADOWBLOCKER_LICENSE environment variable and the -Dshadowblocker.license=... system property work too.

grace-days7

Days of grace before going back to free when a subscription expires or when the server address does not match the licence. A renewal has almost always already started, and an address that does not match is far more often a NAT than a thief.

server-ip""

The address this server is known by, if you know it. Behind a proxy a machine never sees its own domain.

resolve-public-iptrue

One single call, at startup, to the licence service (activation-url + /ip) to find out what public address this server has: behind NAT it is the only way to recognise it. The service answers with the address it sees, and nothing about the server is sent. Only when a licence is installed: a server without a key asks nobody. false = never ask.

activation-url'https://shadow-licences.riccardobotins.workers.dev'

Activation service. This is the BASE address: the plugin appends /claim and /check itself. Empty = completely offline verification.

Messages and interface

messages3 keys
default-locale"en_US"

The language used when the player's own one is not there. The plugin ships en_US and it_IT; a file you add to lang/ is picked up too.

Everything a person reads goes through lang/*.yml. Everything else - command names, the keys in this file, the units written to the database, the console - is English everywhere and does not change with this setting.

follow-client-localetrue

Follow the language set in each player's client.

auto-updatetrue

Move lang/*.yml to the new version when the plugin is updated: new texts arrive, the texts you changed stay yours, and the previous file is kept in backup/<date>/. With false the files in lang/ are never rewritten, and a text missing from them is read from the copy inside the jar.

Keep reading

This applies ONLY to the languages. config.yml, checks.yml and menus/ are always moved to the new version the same way: what you changed is carried over, and what cannot be carried is listed in PORTING-<version>.txt.

gui1 key
click-delay"150ms"

How long to wait between two clicks in the same window. It is there against accidental double clicks, not against cheats.

compatibility1 key
section-color-codestrue

Accept the section character as well as the ampersand in colours. Useful if you paste in text coming from an older plugin.

Diagnostics

logging1 key
log-sqlfalse

Write every SQL statement in the console. Useful for an afternoon, then switch it back off.

metrics1 key
enabledtrue

bStats: how many servers use this plugin and which version they are on.

Keep reading

No data of any player leaves: no name, no UUID, no address, no coordinate, no violation. What is sent, every 30 minutes: a random server id (serverUuid, not derived from anything), the plugin version, the Minecraft/Paper and Java versions, the operating system and core count, the online mode, how many players are online (a number only) and one chart, the licence tier (free/premium/creator). Nothing is sent while the plugin waits for a licence.

It serves one question only: which Minecraft version can stop being supported. Without that number the answer is an opinion.

false switches off this plugin only. enabled: false in plugins/bStats/config.yml switches off every bStats plugin on this server at once.

checks.yml

The anti-cheat checks
211 settings
plugins/ShadowBlocker/checks.yml
File notes

One check per section. Each one switches on, switches off and tunes on its own, and each one has WHAT IT DETECTS and WHAT CAN PRODUCE A FALSE POSITIVE written above it. Those are the same two sentences you see in game with /shadowblocker check <name> and in the checks screen: one source, written next to the code it describes.

The keys they all have

enabled           whether the check runs.
Keep reading
weight            how many points ONE violation adds to this
                  check (and to the player's score).
buffer            HOW MANY TIMES IN A ROW the check has to go over
                  its limit before it counts as a violation at all.
alert-threshold   how many points of THIS check are needed before
                  the staff gets an alert. Below it, the violation
                  is only written to the evidence log (and, if
                  alerts.log-quiet-violations is on, one line in
                  the console): nobody is bothered for one odd
                  moment. With weight 8 and alert-threshold 16 it
                  takes two violations close together.
punish-threshold  from how many points of THIS check upwards its
                  points count for AUTOMATIC punishments (the
                  ladder in config.yml, off by default).
                  0 = NEVER. The checks that only corroborate
                  (click rhythm, packet order, walls, aim...) are
                  at 0: on their own they never punish anybody.
decay             how many points this check loses per minute.
                  One violation of fly (8 points) is forgotten in
                  one minute at 8 per minute.
setback           (movement checks only) when the alert threshold
                  is reached, move the player back to the last spot
                  where they were standing, clean. It is not a
                  punishment: the impossible movement is simply
                  undone. See setbacks in config.yml.

The global "sensitivity" in config.yml (relaxed / balanced / strict) multiplies every buffer and every threshold at once, so you rarely need to touch these numbers one by one.

The buffer is the parameter that matters, and almost nobody looks at it

If you have a false positive, nine times out of ten the answer is RAISE THE BUFFER (or the alert-threshold), not raise the limit.

Keep reading

The limit says "what is impossible". The buffer says "how many times the impossible has to happen before we believe it". Raising the limit makes the check blind to real cheating too; raising the buffer only makes it slower, and somebody cheating keeps doing it for many ticks in a row.

The buffer drops by engine.buffer-decay (0.25) on every clean sample and rises by 1 on every overshoot: somebody cheating intermittently still accumulates.

To see what a check sees, live, while a player does something:

/shadowblocker debug <player>

Every overshoot is printed with the measured value, the limit and the buffer - before it becomes a violation.

disabled-worlds

The list of worlds where that check does not run. It is there for the build world, for the minigame world, for the one where a plugin moves people its own way. Better than switching a check off everywhere because of a single world. (To switch EVERY check off in a world, use exemptions.disabled-worlds in config.yml.)

config-version1

On update this file is moved to the new version automatically: every threshold you tuned is carried over, new checks arrive with their comments, the old file is kept in backup/<date>/, and anything that needs your attention is listed in PORTING-<version>.txt.

checks19 keys

Movement

Every movement check measures the CLIENT'S own ticks (the client closes each tick with a packet since 1.21.2), ignores the moves that arrive between a teleport and the client's confirmation (any teleport: /tp, /rtp, elevators, portals, plugins), opens a window when the server pushes the player (knockback, explosions, wind charges, launch pads, velocity set by plugins) and reads the player's REAL attributes (gravity, jump strength, movement speed, step height, safe fall distance, scale) instead of the vanilla constants. The vertical checks only judge a move when the world snapshot describes exactly the point the move starts from.

checks.fly14 keys
DETECTS: every tick's upward motion compared with what the game's
  gravity allows, and players who hang in mid-air without rising or
  falling. The formula is the game's real one, with the player's
  gravity and air-drag attributes:
  next_dy = (current_dy - gravity) * drag
Keep reading

FALSE POSITIVES: anything that changes the physics - water, ladders,

cobwebs, slime, honey (also on its side), powder snow, bubble
columns, scaffolding, elytra, riptide, levitation, slow falling,
vehicles, pistons, wind charges, knockback, teleports, chunks the
client has not received yet. They are ALL already excluded. What
is left are plugins that move players their own way without a
teleport or a velocity (grappling hooks, lifts): if you have one,
either raise 'tolerance' or switch the check off in the worlds
where it runs.
enabledtrue
weight8
buffer4
disabled-worlds[ ]
alert-threshold16
punish-threshold40

40 = five violations close together before it can count for an automatic punishment: flying is never a one-tick event.

decay8
setbacktrue
tolerance0.03

How much is allowed beyond the prediction, in blocks per tick. 0.03 is a tenth of the difference between a jump and a slow flight.

ping-allowance-per-100ms0.01

How much to widen the tolerance every 100 ms of latency, and the ceiling.

max-ping-allowance0.06
jump-grace-ticks2

For how many ticks after leaving the ground a jump's push (0.42 blocks, or the player's jump strength) is allowed anyway.

Keep reading

It is needed because the world snapshot is one tick behind: there is an instant where we see "not on the ground any more" while the previous movement was still zero, and without this margin EVERY JUMP of EVERY PLAYER would be a violation.

Do NOT raise it. Every extra tick is a tick in which a flight rising by 0.42 is indistinguishable from a jump: at 20 it would become invisible.

hover-tolerance0.005

Below how much vertical movement counts as "still in the air".

hover-ticks14

For how many ticks in a row. In mid-air it is impossible by construction: even at terminal velocity you drop 3.92 per tick.

checks.speed17 keys
DETECTS: the AVERAGE horizontal speed over a short window, and
  separately the instant displacements that have no explanation.
  The average is what is looked at because a single fast tick is
  normal (the tick of a sprint-jump moves 0.6 blocks), while a
  speed cheat stays on.
  The limit is multiplied by the player's REAL movement_speed
  attribute (which already contains Speed, Soul Speed and anything
  a plugin added), by 'ice-multiplier' on ice, slime or slippery
  ground (and for 2 seconds after leaving them), and by Depth
  Strider in water.
Keep reading

FALSE POSITIVES: plugins that push players without a velocity or a

teleport (grappling hooks, dashes done by moving the player
directly) and custom mounts. Speed effects and attributes, ice,
slime, dolphins, knockback, teleports, elytra, riptide, horses and
boats are already accounted for. If a plugin grants speed in some
other way, raise 'base' instead of switching the check off:
switched off it does not even see somebody moving at ten blocks
per tick.
enabledtrue
weight6
buffer5
disabled-worlds[ ]
alert-threshold18
punish-threshold36
decay8
setbacktrue
base0.45

The honest maximum AVERAGE on normal ground, in blocks per tick. Walking 0.215 - sprinting 0.28 - sprint-jumping on flat ground averages 0.365, and the worst honest case is sprint-jumping under a ceiling 2.5 blocks high (the jump is cut short and the sprint push comes every six ticks): 0.42. Simulated with the game's own 26.2 movement code. 0.45 stays above that on purpose. Values below 0.43 flag honest players who sprint-jump in low tunnels.

speed-effect-per-level0.20

How much each level of Speed adds, as a fraction. Only used when the movement_speed attribute cannot be read: the attribute already includes the effect.

ice-multiplier2.6

Ice, slime and slippery ground (friction attribute) are the worst case of all: on blue ice sprint-jumping gets close to 0.48 per tick. 2.6 is generous on purpose; it also covers the momentum carried for two seconds after leaving the ice.

depth-strider-per-level0.15
soul-speed-multiplier1.30
ping-allowance-per-100ms0.02
max-ping-allowance0.10
instant-multiplier5.0

How many times over the threshold a single tick has to be to be flagged straight away, without waiting for the average. It is there for jumps through space, not for speed.

min-samples10

How many samples are needed before an average means anything.

checks.jump11 keys
DETECTS: how far somebody has risen since the normal physics last
  started again (the ground, or the moment they left water, a ladder,
  a bubble column, an elytra flight...). It sees the cheats that
  respect gravity tick by tick but restart it: double jump, high
  jump, mid-air bounce. A jump with no effects rises 1.2522 blocks;
  the allowed height uses the player's jump strength and gravity,
  and the upward speed they had when the normal physics started.
Keep reading

FALSE POSITIVES: slime, beds, bubble columns, wind charges, pistons,

horses, knockback and teleports are already excluded. A plugin
that applies an upward push WITHOUT going through Bukkit's
velocity API is not: in that case switch the check off in that
world, or raise 'tolerance' until it covers the highest push that
plugin gives.
enabledtrue
weight8
buffer3
disabled-worlds[ ]
alert-threshold16
punish-threshold40
decay8
setbacktrue
base-height1.35

The maximum height of a jump with no effects, with a little margin. The same margin (base-height minus 1.2522) is added on top of the height computed from the player's real jump strength and gravity.

per-jump-level0.80

How much each level of Jump Boost adds.

tolerance0.10
checks.climb10 keys
DETECTS: players who keep rising against a wall long after a jump's
  ascent would be over, with no ladder or vine anywhere.
  It is the cheat known out there as "spider".
Keep reading

FALSE POSITIVES: a climbable block invented by a plugin, which

Minecraft does not consider climbable. Real ladders, vines,
scaffolding and powder snow are already excluded, and a jump with
Jump Boost (or a stronger jump attribute) gets the extra ticks of
ascent it really has. If you have blocks like that, switch the
check off in that world.
enabledtrue
weight7
buffer6
disabled-worlds[ ]
alert-threshold21
punish-threshold42
decay8
setbacktrue
air-ticks8

From how many ticks in the air onwards it looks, at least. A jump's ascent lasts six ticks; with Jump Boost or a stronger jump the real number of rising ticks plus two is used when it is higher.

min-rise0.02

Below how much rise it does not look.

checks.phase10 keys
DETECTS: players whose box overlaps the real collision shape of a
  solid block for many ticks in a row WHILE STILL MOVING, which is
  how a wall gets walked through.
Keep reading

FALSE POSITIVES: the most delicate check in the plugin. Doors and

trapdoors closing on someone, pistons, blocks placed on their head,
chunks the client has not received yet, blocks refused by a
protection plugin, vehicles inside walls. That is why it takes
MANY ticks in a row AND movement in progress: someone who ends up
in there by accident stands still and is pushed out at once.
Walking on soul sand, mud or farmland is NOT a problem any more:
the check compares with the block's real shape.
Raise it further and you will see nobody; lower it and you will see
everybody.
enabledtrue
weight5
buffer8
disabled-worlds[ ]
alert-threshold15
punish-threshold0

0 = never on its own: it corroborates the other checks.

decay10
setbacktrue

Moving the player back out of the wall is the right answer here.

ticks12

How many ticks inside the block before saying anything.

min-movement0.02

Below how much movement it counts as "stuck" rather than "walking through". You get pulled out of being stuck, you do not walk through it.

checks.nofall10 keys
DETECTS: the "I am on the ground" flag sent by the client, checked
  against what the server sees at that exact spot (blocks, or
  entities you can stand on such as boats and shulkers) after a fall
  we measured ourselves. In Minecraft fall damage is worked out by
  the CLIENT: a modified client that keeps that flag always true
  never takes damage.
Keep reading

FALSE POSITIVES: a block the client sees but the server refused (a

placement cancelled by a protection plugin opens a short window),
chunks the client has not received yet, and plugins that reset the
fall their own way (lifts, feathers, parachutes). The claim is
verified with the world snapshot taken AFTER the landing, never
with the one from the tick before.
NOTE: this check only works with the packet layer switched on.
  Without it, the client's flag never reaches us and the check has
  nothing to compare.
enabledtrue
weight10
buffer4
disabled-worlds[ ]
alert-threshold20
punish-threshold50
decay6
setbackfalse

A setback would not undo anything useful here: the fall is over.

min-fall-distance3.0

From how many blocks of fall onwards. Below three the damage is zero anyway, so there is nothing to gain by lying. The player's safe_fall_distance attribute is used when it is higher.

margin1.0

Blocks added on top of that, for the rounding of the fall we measure ourselves.

Combat

Detects: the distance between the attacker's eye and the target's

HITBOX. The range is the player's entity_interaction_range

attribute (3.0), or the weapon's own range (spears reach 4.5).
Latency is compensated by looking at where a player target ACTUALLY
was inside the attacker's ping window plus the client's
interpolation, taking the position most favourable to the accused;
for mobs, by how fast they were moving.
Keep reading

FALSE POSITIVES: latency, already compensated (the ping used is the

highest of the last seconds, not the average). Riding on either
side gets an extra margin; the ender dragon is not measured. What
is left are plugins that teleport during a fight. If you must
change something, raise 'tolerance' one tenth at a time and watch
what stops being flagged: if everything stops, it was too high.
checks.reach15 keys
enabledtrue
weight7
buffer5
disabled-worlds[ ]
alert-threshold21
punish-threshold42
decay8
max-distance3.0

The game's range in survival. The player's attribute or the weapon range are used instead when they are longer.

tolerance0.40

The fixed margin: it covers the widening of the hitboxes and the floating-point rounding.

ping-allowance-per-100ms0.15
max-ping-allowance0.60
compensation-window-ms1000

How far back to look in the trail of the target's positions, at most. The real window is the attacker's ping + interpolation-ms + one tick; this is only the ceiling, so that somebody with five seconds of latency does not get five seconds of positions to pick the most convenient one from.

interpolation-ms150

How long the client takes to show an entity at its new position (it slides it over a few ticks). Part of the compensation window.

max-velocity-compensation1.5

For mobs (which have no position trail): at most how many blocks of their own movement during the delay are given as margin.

mounted-tolerance1.0

Extra margin when the attacker or the target is riding something.

Detects: three things. Clicks per second (the least useful), how

REGULAR the intervals are (the most useful), and how many intervals

are exactly identical. Only real clicks count: the arm swings of
mining and of right-click actions (placing blocks, throwing,
using tools, feeding animals) are recognised and left out.
Keep reading

FALSE POSITIVES: drag clicking and butterfly clicking go past twenty

clicks per second WITH no program at all - that is why 'max-cps' is
high and counts for little. A mouse with a faulty double click can
generate identical pairs: if a player is flagged only here and
never anywhere else, look at their mouse before their intentions.
This check alerts the staff and never punishes on its own.
checks.clicks12 keys
enabledtrue
weight5
buffer4
disabled-worlds[ ]
alert-threshold15
punish-threshold0

0 = never on its own: the click rhythm is the weakest clue there is.

decay10
max-cps30

The absurd number. High on purpose: a low threshold here catches the good players (drag clicking goes past 25), not the cheats.

window-seconds8

Only the clicks of the last this-many seconds are analysed: a judgement given at a hit looks at how the player clicks NOW.

min-samples40

Below how many samples nothing is ever said. On five clicks everything looks regular.

min-deviation-ms8.0

The minimum standard deviation of the intervals, in milliseconds. A person stays in the tens of milliseconds even at full speed.

max-identical-share0.55

What fraction of the intervals may be identical to the most frequent one. The 26.x client processes clicks once per tick, so honest intervals cluster on multiples of 50 ms: this is high on purpose.

checks.angle9 keys
DETECTS: the angle between where they were looking and the NEAREST
  point of the target's box at the moment of the hit. Minecraft's
  client does not even let you try to hit somebody you are not
  looking at: it is not "unlikely", it is something a normal client
  cannot send.
Keep reading

FALSE POSITIVES: tight melee, where one tick of delay is worth tens

of degrees - below 'min-distance' nothing is looked at. Big mobs
hit on a corner are fine (the nearest point of the box is used),
non-living targets and the ender dragon are skipped. What is left
are plugins that deal damage on behalf of a player without them
having hit anything (traps, area abilities, summons).
enabledtrue
weight9
buffer3
disabled-worlds[ ]
alert-threshold18
punish-threshold45
decay8
max-angle85.0

Beyond how many degrees. 85 is almost sideways: an honest client always stays under twenty.

min-distance1.2

Below how much distance the angle means nothing.

checks.wallhit8 keys
DETECTS: a hit landed on somebody who had a block in the way from
  every point of view we try.
Keep reading

FALSE POSITIVES: corners, thin pillars and half-open doors, because

the ray is traced now while the hit was decided one ping ago. Rays
go to several points of the target's box (and, for players, to
where they were inside the ping window) with both the collision
and the outline shapes of the blocks, so a cow hit over a fence or
a mob seen through a gap is fine. It still takes several hits in a
row. Watch out for transparent blocks the game treats as full.
enabledtrue
weight6
buffer6
disabled-worlds[ ]
alert-threshold18
punish-threshold0

0 = never on its own.

decay10
min-distance1.0

Detects: two very different things

'snap'    the head snapping around at the instant of the hit,
            starting from a head that was still the tick before. A
            hand aims over several ticks, accelerating and braking;
            a program gets there in one.
  'pattern' the step of the rotations. A real mouse ALWAYS produces
            multiples of the same number, because it counts whole
            steps. What is measured is what FRACTION of the rotations
            sits on that grid: a mouse is near 1, a program well
            below. It is the strongest signal there is.
Keep reading

FALSE POSITIVES:

snap:    players with very high sensitivity who aim with a flick of
         the wrist (that is why it needs a still head before the
         snap). Rotations forced by the server (teleports, plugins)
         are ignored.
pattern: ANYONE PLAYING FROM BEDROCK THROUGH A BRIDGE, ON A
         CONTROLLER, OR WITH A MOD THAT SMOOTHS THE CAMERA. All of
         them sit off the grid and would ALL be flagged.

That is why 'pattern' is OFF by default. Only switch it on if you know that on your server people join from Java with a mouse and nothing else.

checks.aim14 keys
enabledtrue
weight6
buffer5
disabled-worlds[ ]
alert-threshold18
punish-threshold0

0 = never on its own.

decay10
snaptrue
snap-window-ms120
max-snap-degrees45.0
snap-still-before-degrees5.0

The head must have turned by at most this much in the tick BEFORE the snap. A human flick is already moving the tick before.

patternfalse

Read the paragraph above before touching this.

pattern-min-samples20
min-quantised-share0.75

Below what fraction of rotations on the grid it flags. A mouse sits at 1.0; 0.75 lets a quarter of the rotations be out of place before saying anything.

Interaction

checks.fastbreak9 keys
DETECTS: how long a dig took compared with the minimum that block
  requires, taking into account the tool, Efficiency, Haste, Mining
  Fatigue, water and whether the player was in mid-air (which divides
  the speed by five, and almost nobody remembers it). The dig is
  measured twice: in milliseconds AND in the client's own ticks,
  which the network cannot shorten; both must be too short.
Keep reading

FALSE POSITIVES: a plugin breaking blocks on behalf of the player

and blocks destroyed by an explosion (no dig was seen: nothing is
said). Jumping, water, Haste and Mining Fatigue changing during the
dig are taken the way that favours the player. If you have custom
tools that dig faster than the real ones, lower 'max-speed-ratio'.
enabledtrue
weight9
buffer3
disabled-worlds[ ]
alert-threshold18
punish-threshold45
decay8
max-speed-ratio0.65

Below what fraction of the minimum time it flags. 0.65 = "they took less than two thirds of the minimum possible". No connection gives away a third of the time; a cheat zeroes it.

min-expected-ms150

Below what expected time it does not look: blocks that break almost instantly have nothing to measure.

Detects: how many block breaks arrive inside the same server tick

The game allows ONE: to break the next block you have to start over.

With the packet layer on, the digs must also have been sent inside
one single CLIENT tick, so server lag never counts.
Keep reading

FALSE POSITIVES: other plugins (explosions, area abilities, tree

fellers, WorldEdit run on behalf of a player) send no dig packets
and are therefore not flagged when the packet layer is on. Without
it, raise 'max-per-tick' if you have plugins like that.
checks.nuker8 keys
enabledtrue
weight12
buffer1
disabled-worlds[ ]
alert-threshold24
punish-threshold48
decay8
max-per-tick3
checks.interactreach12 keys
DETECTS: the distance between the eye and the NEAREST point of the
  block they acted on. The range is the player's
  block_interaction_range attribute (4.5 in survival, 5 in creative)
  or the value below, whichever is longer.

FALSE POSITIVES: the movement of whoever is clicking during the

connection delay, already covered by the margin. And plugins that
make a player break or place blocks from far away.
enabledtrue
weight6
buffer4
disabled-worlds[ ]
alert-threshold18
punish-threshold36
decay8
survival-distance4.5
creative-distance6.0
tolerance0.60

The fixed margin, in blocks, on top of the range. The distance is measured to the nearest point of the block (it used to be the centre, with 0.9 of margin to cover that).

ping-allowance-per-100ms0.08
max-ping-allowance0.35
checks.visibility12 keys
DETECTS: a block broken or touched behind a wall, and a block acted
  on while facing the other way. The game allows a normal client
  neither of those.
Keep reading

FALSE POSITIVES: corners, doors, blocks placed by others in the same

instant and chunks the client sees differently. Rays are traced to
fifteen points of the block with both collision and outline
shapes, and blocks with no collision (grass, flowers, crops,
buttons, levers) are never judged on the wall test. It still takes
several cases in a row. Watch out for transparent blocks the game
treats as full, and for plugins that make a player act on blocks
they are not looking at.
enabledtrue
weight5
buffer6
disabled-worlds[ ]
alert-threshold15
punish-threshold0

0 = never on its own.

decay10
check-line-of-sighttrue
min-distance1.5
check-angletrue
max-angle90.0
angle-min-distance2.0
checks.place11 keys
DETECTS: a block placed against NOTHING, or placed without looking at
  it. Those are the two signatures of "scaffold" (the bridge built
  under your feet while walking over the void) and of "tower".
Keep reading

FALSE POSITIVES: plugins that place blocks on behalf of a player

(rollbacks, regenerations, build tools): in those cases there is no
clicked face and the check goes quiet on its own.
What is left are blocks that lean on things which are not full
blocks: if you see those flagged, set 'require-support' to false and
keep only the angle test.
enabledtrue
weight7
buffer4
disabled-worlds[ ]
alert-threshold21
punish-threshold0

0 = never on its own.

decay10
require-supporttrue
check-angletrue
max-angle100.0
angle-min-distance1.5

Packets

MIND THIS: these three checks have a LOW weight and a HIGH buffer, and that is not an oversight. An odd packet rhythm is not proof on its own.

Keep reading

They are there to CORROBORATE - to explain why a player who was being flagged elsewhere was being flagged elsewhere - not to decide on their own. Raising their weight is the fastest way to ban somebody because of their wifi.

They only work with the packet layer switched on (config.yml, engine.packets.enabled).

checks.rate10 keys
DETECTS: a sped-up client clock ("timer"). The client closes every
  tick with a packet: each one adds 50 ms to a balance, each real
  millisecond takes one away. An honest client stays around zero; a
  timer at 25 ticks a second gains 250 ms every second. A connection
  that stops and then delivers everything at once is forgiven by the
  balance itself: the silence becomes credit that the burst spends.
  It also counts floods of packets of any kind.
Keep reading

FALSE POSITIVES: very few. /tick rate is followed automatically.

Floods are not counted right after a silence of the connection.
enabledtrue
weight3
buffer10
disabled-worlds[ ]
alert-threshold9
punish-threshold0

0 = never on its own: it corroborates speed and fastbreak.

decay10
max-ahead-ms500.0

How far ahead of real time the client's clock may run, in milliseconds, before it counts. 500 = ten ticks: normal network jitter moves the balance by one or two.

max-lag-credit-ms2000.0

How much credit a silence of the connection can build up, in milliseconds. It is what forgives the burst that follows a lag spike. Larger silences are covered by the "connection" window in config.yml anyway; this cap only stops somebody from saving up minutes of ticks to spend later.

max-total-per-second500.0
checks.order9 keys
DETECTS: packet sequences the client does not produce. TCP reorders
  nothing, so two packets that arrived in one order were SENT in that
  order: it is not a hint, it is a fact.
  What is looked at: actions without the client's game loop running,
  interactions long after death, digs that end without having
  started.
Keep reading

FALSE POSITIVES: few. An attack sent a moment before dying arrives

after death with the ping in between: only interactions well after
death count. What is left are alternative clients that batch
actions differently (Bedrock through a bridge, excluded by default)
and plugins that send actions on behalf of a player.
enabledtrue
weight4
buffer8
disabled-worlds[ ]
alert-threshold12
punish-threshold0

0 = never on its own.

decay10
min-actions8

Below how many actions in the window it does not look: a player who has just joined has half-empty windows by definition.

dead-grace-ms1500

How long after death an interaction still counts as "sent while alive", in milliseconds; twice the player's ping is added.

Detects: values inside packets that the game never produces

NaN or infinite coordinates, pitch outside the -90/+90 range,

impossible position jumps, positions outside the world.
It is the check with the fewest false positives in the whole
plugin: nothing is estimated and nothing is tuned here.
Keep reading

FALSE POSITIVES: none known. If you see them on a player, before

thinking of a cheat consider a bridge between different versions or
a proxy that rewrites the packets: the result is the same, but the
person has nothing to do with it.
checks.field9 keys
enabledtrue
weight15
buffer1
disabled-worlds[ ]
alert-threshold15
punish-threshold30
decay5
max-jump-blocks100.0
world-border-blocks30000000.0

Languages

The texts the plugin shows to players, one file per language.

lang/

Texts and messages
2 files
plugins/ShadowBlocker/lang/
File notes

This file holds EVERY word a person reads: chat messages, screen titles, item names and their descriptions. Nothing else. Command names, configuration keys, units written to the database and console lines are English everywhere and are NOT in here - they are data, and data that changes name with the language is not data.

Colours are written with &: &a &c &l &r, and &#FF8800 for an exact colour. The only exception are gradients, which & cannot express:

<gradient:#7E57C2:#4FC3F7>text</gradient>

Placeholders are written <like-this>. The one you will meet most often is <label>: it is filled with the command the server ACTUALLY granted us. Never write "/sbl" by hand: if that command already belonged to another plugin, ours is called "/shadowblocker:shadowblocker", and a hardcoded message would send a staff member straight into somebody else's command.

A message can be a plain string or a map:

key:
  text: "&cText"
  channel: ACTION_BAR      # CHAT | ACTION_BAR | TITLE | SUBTITLE | NONE
  sound: block.note_block.bass
  cooldown: "2s"           # per-player anti-spam

channel: NONE silences a message without deleting the key. A key missing here is read from the copy of this language inside the plugin jar, then from English, one key at a time.

Every message the plugin shows to players, one file per language. The keys are not listed one by one: here are the files, how many texts they hold and the placeholders they use.

FileLanguageTexts
lang/en_US.yml reference English 419
lang/it_IT.yml Italiano 419

Most used placeholders

The plugin replaces them with the right value: leave them written as they are.

  • <prefix>
  • <label>
  • <player>
  • <id>
  • <reason>
  • <action>
  • <check>
  • <family>
  • <text>
  • <name>
  • <count>
  • <value>
  • <state>
  • <measured>

Message groups

  • prefix 1
  • common 10
  • command 4
  • unit 25
  • exempt 20
  • check 76
  • alert 23
  • punish 2
  • narrator 28
  • evidence 32
  • admin 115
  • gui 36
  • license 43
  • locked 4

All the documentation