Move shared combat reach checks out of swing handlers and update melee, range, and magic handlers to delegate to the new reach boundary. Add focused combat tests for large-target melee reach and planner behavior, and update the living combat movement rewrite notes with completed progress and next steps.
5.2 KiB
Combat Movement Rewrite Notes
This branch is intended to separate combat movement from generic interaction movement before changing combat behavior. The immediate goal is to make the current engine boundaries explicit, then replace the hidden coupling in small, testable steps.
Current Tick Order
The major update worker currently runs a tick in this order:
PacketProcessor.processQueue()GameWorld.Pulser.updateAll()GameWorld.tickListenersUpdateSequence.start()UpdateSequence.run()UpdateSequence.end()GameWorld.pulse()Managers.tick()
UpdateSequence.start() ticks NPCs first, then players. Each entity tick runs
scripts.preMovement(), dispatches TickEvent, pulses skills, applies
walkingQueue.update(), runs scripts.postMovement(...), processes timers, and
prepares update masks.
That means pulse code mutates movement state before the entity movement phase,
but the actual tile step is applied later by WalkingQueue.
Current Combat Movement Coupling
Properties constructs one CombatPulse for every entity. CombatPulse
constructs a private MovementPulse(entity, null) during initialization and
later uses it as a pathing helper by replacing its destination in
CombatPulse.setVictim(...).
During CombatPulse.interactable(), melee/range/magic swing handlers decide
whether the attacker can swing, should move and still interact, or cannot
interact. If movement is needed, CombatPulse calls the private movement
helper's updatePath(). That helper resets and repopulates the attacker's
WalkingQueue; WalkingQueue.update() applies the step later in the entity
tick.
NPC and player attack clicks do not go through normal approach scripts in the
common case. NPC attack interaction is registered as an instant listener, so it
starts CombatPulse directly.
Known Failure Modes
- A melee attacker following a running victim responds too late and can lose melee pressure.
- Mutual melee attackers can wait for the other actor to approach instead of both moving to meet.
MovementPulse.checkAllowMovement()contains an anti-loop rule that blocks a mover from approaching a target that is already combat-pathing back to it.MeleeSwingHandler.canSwing()only permits moving interaction against a moving victim when the victim is not targeting the attacker.- Combat pathing currently reuses generic interaction movement logic, including object/entity interaction shortcuts that are not necessarily valid combat movement rules.
Intended Boundary
Movement should remain an engine feature, not a ScriptProcessor feature.
Scripts may request actions and process content-specific results, but the
movement phase should own tile stepping, path queues, clipping, run/walk state,
and movement locks.
Combat should also remain an engine feature for targeting, swing timing, reach, path pressure, and movement intent. Scripts should be reserved for attack results and content effects such as damage application, sounds, graphics, special effects, drops, and dialogue/interface side effects.
The rewrite should replace the private combat MovementPulse helper with a
combat movement planner that produces explicit movement intents. Those intents
should be resolved in a deterministic engine phase before WalkingQueue applies
steps.
First Behavioral Targets
Pending tests live in Server/src/test/kotlin/content/CombatMovementTests.kt.
They are disabled until the combat movement engine exists so the main test suite
does not fail during the rewrite.
Initial targets:
- A PvP melee attacker mirrors a running victim and remains in melee reach.
- Mutual melee attackers approach instead of waiting indefinitely.
- Player versus moving NPC melee chase remains functional.
- Movement-locked attackers do not move, but may swing if already in range.
- Large target melee reach is measured against occupied border tiles.
Suggested Implementation Sequence
- Extract a
CombatReachutility from the swing handlers. - Add a
CombatMovementPlannerthat can choose target border tiles and predict one or two target movement steps fromWalkingQueue. - Add a combat movement intent phase that resolves step conflicts deterministically.
- Remove the private
MovementPulsefromCombatPulse. - Enable the pending tests one scenario at a time.
Progress Log
- 2026-04-28: Extracted
CombatReachincore.game.node.entity.combat. Melee occupied-tile reach, halberd reach, generic center-distance reach, NPC combat-distance overrides, and straight-line step validation now live behind that utility. Existing swing handlers delegate to it, whileMeleeSwingHandler.canMelee(...)remains as a compatibility wrapper for current callers. Added a focused regression inCombatTestsfor large-target melee occupied-tile reach. - 2026-04-28: Added a standalone
CombatMovementPlannerthat readsWalkingQueuewithout mutating it, predicts the target's next one or two movement locations using run state and run-disabled path points, and chooses the closest valid border tile around the predicted occupied area. The planner is not yet wired intoCombatPulse; the next step is the deterministic combat movement intent phase.