Skip to content

Repository files navigation

CombatEngine

A tick accurate combat engine for Paper. It takes over hit detection, reach validation, knockback and damage scaling, keeps a rolling history of every entity position so hits can be checked against where the attacker actually saw the target, and ships a small packet level anticheat on top.

Built for Minecraft 26.2, Java 25, Kotlin.

What it does

Custom hit detection. Every swing is re traced server side with a slab method ray against the target hitbox rather than trusting the client. No allocation per hit, the ray and the box are both reused.

Reach validation with rewind. Positions for the last N ticks live in one flat DoubleArray. When a hit comes in the target is rewound by the attacker's ping before the reach is measured, so a player on 200ms is not punished for their own latency.

Combat state machine. Every tracked entity sits in IDLE, ENGAGED or RECOVERING, with tag and invulnerability deadlines held in IntArrays. Other code can watch transitions without polling.

Knockback engine. Configurable horizontal, vertical, sprint bonus, enchant scaling, friction and air multiplier. Knockback is computed on the hit and applied on the next tick, because vanilla overwrites velocity set during the damage event.

Knockback sync. Predicts whether the victim's client has already landed by the time the velocity packet arrives, and uses grounded knockback values instead of airborne ones when it has. This is what stops the classic "high ping players fly further" desync.

Attack cooldown. Either LEGACY (1.8 style, always full damage) or SCALED (vanilla 0.2 + charge squared times 0.8), with a configurable recharge period.

Damage modifiers. An ordered pipeline. Built ins are charge scaling, criticals, reach falloff and a cap. Any of them can veto the hit and stop the chain.

Anticheat. Reach, click rate, swingless attacks and rotation checks, with violation levels that decay on a half life and permission gated alerts.

Low allocation

The whole engine is built on slot indexed flat arrays instead of per player objects. Every tracked entity gets an Int slot, and all state lives in parallel primitive arrays indexed by it. The per tick loop is a bare while over DoubleArray and IntArray with no map lookups, no iterators and no boxing. Vec, Aabb, RayHit and DamageContext are all reused rather than allocated per hit.

Position history is frame major, so writing a whole tick touches one contiguous run of memory.

Requirements

  • Paper 26.2 or newer
  • Java 25
  • packetevents 2.13.0 or newer, optional

Without packetevents the plugin still runs. Knockback sync falls back to the server reported ping instead of real round trip measurement, and the packet level checks are switched off. The console says which mode you are in on startup.

Build

./gradlew build

The jar lands in build/libs/CombatEngine-1.0.jar. packetevents is compileOnly so it is not bundled, install it separately.

Commands

Root command is /combat, aliased to /ce.

  • /combat status engine tick, tracked slots, accepted and rejected swings, average reach
  • /combat modifiers what is in the damage pipeline and in what order
  • /combat knockback the active knockback profile
  • /combat sync knockback sync state, corrections applied and skipped, tracked pings
  • /combat checks anticheat state and flag counts per check
  • /combat me your own slot, state, combat tag, attack charge, ping and violation level
  • /combat reload reload the config and restart the timers

Permissions

  • combat.use read engine status, everyone by default
  • combat.admin reload, op by default
  • combat.alerts receive anticheat alerts, op by default

How the reach check works

The engine records every tracked entity's position and hitbox size once per tick into a ring buffer sized by engine.history-ticks. When a hit arrives the attacker's ping is turned into a tick count (ping / 50, capped by reach.rewind-cap-ticks), the victim's box is pulled from that frame, and a ray is cast from the attacker's eye along their look vector.

If the ray misses the box entirely and reach.reject is on, the hit is cancelled. Otherwise the distance along the ray becomes the reach, which then feeds both the falloff modifier and the anticheat reach check.

The tape is checked before use. If the requested frame was overwritten or never recorded, the engine falls back to the victim's live hitbox instead of guessing.

How knockback sync works

When a player is hit while airborne, the server sends a velocity packet. If that player is a fraction of a block above the ground, their client may well have landed before the packet arrives, and the knockback then reads as far stronger than it should.

Knockback sync estimates the ground distance from four hitbox corners, works out how long the client needs to reach its apex and fall back (a per tick simulation using vanilla gravity 0.08, drag 0.98 and terminal velocity 3.92), and compares that against the measured ping. If the client is predicted to be grounded already, the vertical and horizontal knockback are replaced with the grounded values from the sync config block.

Ping comes from real ping and pong packets through packetevents rather than Player#getPing, which is smoothed and lags behind.

Hits older than sync.max-hit-age-ms are ignored, and a knockback vector that somehow points back at the attacker is left alone on the horizontal.

This is ported from the knockbacksync module of the Core plugin. Same constants, same behaviour, but it now lives in the engine and shares its config and ping tracking instead of standing on its own.

Anticheat

Four checks, all off a single packetevents listener except reach.

reach runs on the main thread and reuses the measurement the engine already made against the rewound hitbox. Flags anything past anticheat.reach.max.

hitrate counts attack packets in a rolling second and flags past max-cps.

swing flags an attack packet with no arm animation inside window-ms, which is a common killaura signature.

rotation flags a pitch outside the legal range, or a yaw jump larger than max-yaw-step in a single packet.

Every flag adds to a violation level that halves every vl-half-life-seconds, so a burst of noise fades instead of accumulating forever. Alerts go to the console and to anyone holding anticheat.alert-permission.

Packet listeners run off the main thread, so they only ever touch atomics and concurrent maps. None of them read the engine's slot arrays, which is why reach is measured on the main thread and not in the listener.

Configuration

plugins/CombatEngine/config.yml. Everything is clamped on load, so a bad value cannot take the server down.

cooldown.mode accepts SCALED or LEGACY. 1.8 and OLD also map to legacy.

engine:
  history-ticks: 20
  initial-slots: 64
  track-mobs: true

reach:
  limit: 3.0
  tolerance: 0.15
  hitbox-padding: 0.1
  rewind: true
  rewind-cap-ticks: 10
  reject: true

cooldown:
  mode: SCALED
  period-ticks: 12
  scale-damage: true

knockback:
  enabled: true
  horizontal: 0.4
  vertical: 0.4
  friction: 0.5

sync:
  enabled: true
  ping-offset: 0
  max-hit-age-ms: 50
  vertical-default: 0.36080000519752503
  horizontal-default: 0.36080000519752503

anticheat:
  enabled: true
  vl-half-life-seconds: 30
  reach:
    max: 3.1
  hitrate:
    max-cps: 16

Tests

./gradlew test

63 tests covering the ray and box maths, the position tape including wraparound and resize, slot allocation and reuse, the state machine transitions, the cooldown curve, knockback direction and caps, the damage pipeline ordering and veto, the fall simulation, and violation decay.

Notes

  • Knockback lands one tick after the hit. That is deliberate. Vanilla applies its own knockback after the damage event returns, so anything set during the event gets overwritten.
  • Mobs are tracked lazily the first time they are hit, and released when they die or stop being valid. Players are tracked on join.
  • There are no comments in the source. Naming carries it.
  • engine.history-ticks is the memory knob. Each frame costs five doubles per tracked entity, so 20 ticks and 200 entities is about 160KB.

License

MIT. See LICENSE.

Written by Am4er.

About

Tick accurate combat engine for Paper. Custom hit detection, reach rewind, knockback sync and a packetevents anticheat.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages