Skip to content

Implement attack handler#342

Open
ch-iv wants to merge 13 commits into
ferrumc-rs:masterfrom
ch-iv:ch-iv/implement-attack-handler
Open

Implement attack handler#342
ch-iv wants to merge 13 commits into
ferrumc-rs:masterfrom
ch-iv:ch-iv/implement-attack-handler

Conversation

@ch-iv

@ch-iv ch-iv commented Mar 23, 2026

Copy link
Copy Markdown

This PR enables attack events to be handled properly.

This pull request updates the decoding logic for the InteractEntity packet in the networking layer. It also adds a friction handler to make sure an entity slows down over time.

Note: I came up with velocity constants on my own. If you have a reference to an implementation in a more mature project, please let me know.

Screen.Recording.2026-03-23.at.12.58.40.PM.mov

@ch-iv

ch-iv commented Mar 23, 2026

Copy link
Copy Markdown
Author

I think the formatter is failing due to an inconsistency on main.

@ch-iv ch-iv force-pushed the ch-iv/implement-attack-handler branch from 6794c20 to c9ec7e9 Compare March 24, 2026 02:49
NanCunChild and others added 9 commits June 6, 2026 08:40
The cave pass was ~47% of all chunk generation time (~660us of ~1.4ms). Two
changes roughly halve it, with no correctness regression (the full world_gen
suite — trees-not-undercut-by-caves, snow-never-floats, not-all-air, etc. —
passes unchanged):

- Sample the 3D cave noise with three RidgedMulti octaves instead of five and a
  coarser vertical grid step (8 instead of 6). The noise is taken on a coarse
  grid and trilinearly interpolated, which already smooths away the finest
  octaves, so the large-scale tunnel/chamber structure is effectively the same
  for ~40% fewer (the priciest, 3D) noise evaluations. `generate_caves` and
  `cave_opening_at_surface` share the same `cave_noise`, so they stay
  consistent and the tree/cave-mouth gate margin is preserved.

- Collapse the per-cell trilerp in the fill loop. Trilinear interpolation is
  separable, so the X interpolation at the two bracketing Z edges is computed
  once per column for each grid-Y level and reused, turning the inner Y loop
  (160 cells/column) from eight grid fetches plus a full trilerp into two
  Y-lerps and one Z-lerp — the same operations in the same X->Y->Z order, so
  the carved result is identical. The now-unused `trilerp` helper is removed.

Measured on one machine: cave pass ~662us -> ~283us, full generate_chunk
~1.41ms -> ~937us.
A dev-only load-testing tool that spawns a swarm of offline-mode azalea bots
to connect to a running FerrumC server and (optionally) wander, reproducing the
chunk-loading, movement, and broadcast load of many simultaneous players for
measuring server tick performance under real concurrency.

- Standalone crate (its own `[workspace]`, listed under `exclude` in the root
  Cargo.toml) so azalea's large dependency tree never compiles as part of the
  main build, CI, or release.
- Pinned to `azalea = "=0.14.0"` (Minecraft 1.21.8 / protocol 772) to match
  FerrumC's protocol. azalea 0.14 transitively depends on a set of pre-release
  RustCrypto crates whose newer published versions have incompatible APIs; the
  committed Cargo.lock pins the whole stack back to the working versions
  (documented in the README, with restore commands).
- Configurable bot count, join stagger, idle vs wandering, reconnect-on-drop,
  and a periodic connected/peak/joins/disconnects metrics line.

Transport and login are verified end to end against a local server (bots reach
the server and begin the login sequence). Note: a strict client cannot yet
fully join because FerrumC's registry NBT encodes some `dimension_type` fields
with the wrong tag width (e.g. `height` as Long instead of Int; `ambient_light`
/`coordinate_scale` as int instead of float/double). The vanilla Java client
coerces these leniently; azalea deserializes into typed structs and rejects
them. Making that registry data wire-conformant is a separate server-side fix.
Rivers are driven by a dedicated low-frequency noise whose 0.5 contour traces
each river's centreline, independent of the climate axes so they wander across
biomes rather than following them (mirroring vanilla's use of rivers as biome
separators).

- climate: a `river` noise field and a per-column `river` factor on
  ClimateSample (0 away from rivers, rising to 1 at the centreline).
- carving: in `column`, the river factor lowers the surface into a channel that
  the existing global sea-level flood then fills with water. The carve is gated
  to lowland columns above sea level and fades out toward mountain height, so
  rivers stay valleys winding through plains/forests instead of gashing peaks.
- classify: a column is a river biome only where its channel was actually
  carved below sea level, so the river biome coincides with the visible water
  and dry banks keep their surrounding land biome. Cold columns record
  `frozen_river` (id 24), others `river` (id 41); both share one bed decorator.
- a new `RiverBiome` decorator lays a gravel bed over dirt on the channel floor.

Over a sampled region rivers cover ~3% of columns, every one water-filled, and
the server generates spawn chunks with rivers without error. Frozen rivers are
not yet ice-capped — a follow-up.
… no beaches

Addresses several issues with the first river pass:

- Consistent width: the river centreline distance is now normalised by the
  noise gradient, so rivers are a fixed handful of blocks wide everywhere
  instead of ballooning into lakes wherever the noise plateaued near its
  contour. The river noise gains an octave (and a gradient floor) so those
  plateaus do not occur in the first place.
- Continuity: the channel is carved to a fixed depth along its centreline
  (deeper than it is wide) rather than scaling the depth by land height, so the
  bed stays below sea level — and the river unbroken — as the lowland surface
  rises and falls. Earlier the height scaling lifted the centreline above sea
  level on raised ground, breaking the river into disconnected pools.
- Stays off mountains with a visible source: rivers carve at full depth across
  the lowlands and taper to nothing in a band just below a height ceiling, so a
  river peters out at the foot of high ground instead of gouging a canyon into
  it; no river is carved at or above the ceiling.
- Inland only: a continentalness gate keeps rivers off the immediate coast, so
  they no longer spawn as a redundant strip of water beside the ocean.
- River banks are no longer beaches: beach classification now requires
  ocean-coast continentalness, so inland river edges keep their surrounding land
  biome (grassy plains) with the gravel riverbed showing through at the shallow
  margins, rather than turning into a wide sand beach.

Rivers cover ~2.4% of columns, are essentially fully connected, and widen
naturally toward the sea where they reach it. Triangular alluvial deltas at
river mouths and ice-capped frozen rivers remain follow-ups.
perf: speed up cave generation by ~57% (half of chunk-gen cost)
Brings in ch-iv's attack-handling work from ferrumc-rs#342:

- Correct `InteractEntity` decoding. The packet's optional fields
  (target x/y/z, hand) depend on the interaction type, so a derived decoder
  mis-read every non-attack interaction and left unconsumed bytes in the
  stream. Decoding is now done by hand, reading the trailing fields only for
  the variants that carry them.
- An attack handler that applies invulnerability frames, decrements health,
  applies knockback, and broadcasts the hurt animation.
- A per-tick combat system that ticks `CombatProperties`.
- A general ground/air friction system for the physics pipeline.

The PR also removed `#![feature(const_cmp)]` from `ferrumc-data`; that file
has since diverged on master (all of its feature attributes were already
dropped), so the conflict is resolved by keeping master's version and
discarding the now-obsolete change.

Review fixes are applied in the following commit.
Follow-up to the PR ferrumc-rs#342 merge:

- Reject attacks from outside reach. The attack handler fetched the
  attacker's position but never used it, so a modified client could send
  `InteractEntity` for any entity id at any distance. Attacks beyond the
  vanilla six-block server-side interaction range are now dropped, and a
  target without a position is treated as out of reach.
- Stop scanning once the target is found. Entity ids are unique, so the
  handler breaks out of the entity loop after applying an attack instead of
  walking every remaining entity.
- Gate the friction system on `HasGravity`, matching how gravity and water
  drag are gated. It previously damped the velocity of every entity with a
  velocity and ground flag, including ones deliberately spawned without
  physics.
- Drop the obsolete `#[expect(unused_parens)]` and single-element tuple in
  the mob system registration; register the combat system directly.
- Restore the doc comment on `InteractEntity::is_attack`.
@NanCunChild NanCunChild requested a review from Sweattypalms June 6, 2026 07:21
@NanCunChild NanCunChild self-assigned this Jun 6, 2026
Brings the reviewed attack-handling work onto master. It had been prepared
on `feature/attack-handler` (upstream ferrumc-rs#342 plus review
fixes) but was not part of the earlier integration, so attacks were not
handled at all: the old derived `InteractEntity` decoder mis-read non-attack
interactions, and the attack, combat-tick and friction systems were never
registered.

This merge is clean against master's later work (the LMDB reopen fix and the
river/cave generation changes); it touches only the attack-handler files.
Spawned mobs carried combat properties but no health, so the attack handler
could decrement nothing and entities could not die. This adds health to every
entity bundle (from the vanilla `max_health`, falling back to twenty) and a
death sequence:

- `detect_deaths` finds entities whose health has reached zero, broadcasts the
  vanilla death status (Entity Event 3) so clients play the death animation,
  marks them non-attackable, and tags them `Dying`.
- `tick_dying` counts the death-animation timer down and, once it elapses,
  despawns the entity and broadcasts `RemoveEntities` so clients drop it.

Removal is deferred by `Dying::DEATH_ANIMATION_TICKS` so the animation has time
to play rather than the entity vanishing on the killing blow. Only entities
with an `EntityIdentity` are affected, so players are never despawned.

`ferrumc-entities` gains a dependency on `ferrumc-components` for the health
component (acyclic: components does not depend on entities).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants