Route accepted non-autonomous local UpdateMotion state through retail's wholesale interpreted-motion funnel, so ACE-selected melee and missile actions use the normal motion queue and action-stamp gate. Share nullable wire conversion with remotes and remove the local direct command replay. Co-Authored-By: Codex <codex@openai.com>
81 lines
3.2 KiB
Markdown
81 lines
3.2 KiB
Markdown
# Local combat UpdateMotion — retail receive pseudocode
|
|
|
|
Date: 2026-07-11
|
|
|
|
## Question
|
|
|
|
Who chooses and plays the local player's melee or missile attack motion?
|
|
|
|
## Named-retail oracle
|
|
|
|
- `ClientCombatSystem::ExecuteAttack` at `0x0056BB70` sends the targeted
|
|
melee/missile request and updates combat-repeat state. It does not choose a
|
|
concrete swing command and does not play an animation locally.
|
|
- `CPhysics::SetObjectMovement` at `0x00509690` rejects a local autonomous
|
|
echo, but accepts a newer non-autonomous movement event, stores
|
|
`last_move_was_autonomous = false`, calls `unpack_movement`, then returns 1
|
|
so `CommandInterpreter::LoseControlToServer` runs.
|
|
- `MovementManager::unpack_movement` at `0x00524440`, case 0, unpacks an
|
|
`InterpretedMotionState`, calls `move_to_interpreted_state`, then applies the
|
|
sticky-object and standing-long-jump trailers.
|
|
- `CMotionInterp::move_to_interpreted_state` at `0x005289C0` copies the state
|
|
wholesale, reapplies style/forward/side/turn, and dispatches fresh command
|
|
list entries under the 15-bit `server_action_stamp` gate. The player skips
|
|
only command entries whose autonomous bit is set.
|
|
|
|
## ACE cross-reference
|
|
|
|
ACE's `Player_Melee.DoSwingMotion` selects the actual maneuver from the combat
|
|
table, constructs a `Motion`, marks the target sticky, and broadcasts it.
|
|
Missile attacks similarly enqueue the server-selected aim action, followed by
|
|
reload and Ready. `MovementData.IsAutonomous` documents the wire split as
|
|
client-initiated `true`, server-initiated `false`.
|
|
|
|
## Retail pseudocode
|
|
|
|
```text
|
|
on local attack input:
|
|
send TargetedMeleeAttack or TargetedMissileAttack
|
|
do not invent or play a swing locally
|
|
|
|
on UpdateMotion for local player:
|
|
apply movement and server-control sequence gates
|
|
if event.isAutonomous:
|
|
drop the entire event
|
|
|
|
player.lastMoveWasAutonomous = false
|
|
interrupt current move
|
|
unstick
|
|
apply outer style if it changed
|
|
|
|
if movementType is MoveTo/TurnTo:
|
|
route to MoveToManager
|
|
return
|
|
|
|
if movementType == InterpretedMotionState:
|
|
ims = constructor defaults (NonCombat, Ready, speeds 1)
|
|
unpack every present field over ims
|
|
move_to_interpreted_state(ims)
|
|
copy every axis wholesale
|
|
apply style, forward, sidestep, turn
|
|
for each fresh command-list entry:
|
|
if local player and entry.autonomous: skip
|
|
dispatch entry through the motion table
|
|
apply sticky target trailer
|
|
write standingLongJump, including false
|
|
|
|
command interpreter loses control to server
|
|
next local input edge takes autonomous control back
|
|
```
|
|
|
|
## acdream correction
|
|
|
|
The local branch already had the autonomous-event rejection and the
|
|
interrupt/style/MoveTo routing, but deliberately skipped the mt-0 wholesale
|
|
copy. It then replayed `Commands[]` directly through
|
|
`AnimationCommandRouter`, bypassing retail's action-stamp and local-autonomous
|
|
gates. The correction routes local mt-0 through the existing
|
|
`MotionInterpreter.MoveToInterpretedState` and persistent motion-table sink,
|
|
exactly like remotes, and removes the direct replay. A shared App-layer factory
|
|
now owns nullable-wire-to-constructor-default conversion for both branches.
|
|
|