fix(combat): match retail live attack feedback
Keep AttackDone control statuses out of chat, preserve dead motion across zero-velocity position updates, render the authored power meter from right to left, and retain the rotatable viewer offset during target tracking. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
33cc9aa16a
commit
5276a83087
16 changed files with 231 additions and 67 deletions
|
|
@ -83,7 +83,7 @@ W1 plan: [`docs/superpowers/plans/2026-06-02-unified-cell-graph-stage1.md`](../s
|
|||
| I.4 | `ChatPanel` input field + slash commands — Enter-to-submit input field; `ChatInputParser` recognises `/say` `/t` `/tell` `/r` `/g` `/f` `/a` `/m` `/p` `/v` `/cv` `/lfg` `/trade` `/role` `/society` `/olthoi`; `ChatVM.LastIncomingTellSender` tracks for `/r` reply. `ImGui.WantCaptureKeyboard` already suppresses WASD on focus. | Live ✓ |
|
||||
| I.5 | Holtburger inbound chat parity + Windows-1252 codec — `EmoteText (0x01E0)`, `SoulEmote (0x01E2)`, `ServerMessage (0xF7E0)`, `PlayerKilled (0x019E)` parsers + `WeenieError` routing through `GameEventWiring`. Global string codec switch from `Encoding.ASCII` to `Encoding.GetEncoding(1252)` so accented names round-trip per retail + holtburger. | Tests ✓ |
|
||||
| I.6 | TurbineChat codec + `ChatChannelInfo` — full `0xF7DE` codec with three payload variants (`EventSendToRoom`, `RequestSendToRoomById`, `Response`), UTF-16LE strings with variable-length prefix, `SetTurbineChatChannels (0x0295)` parser, unified `ChatChannelInfo` (Legacy + Turbine variants), `TurbineChatState`. **ACE doesn't host a TurbineChat server — codec is ready when retail-emulating servers exist.** | Tests ✓ |
|
||||
| I.7 | `CombatChatTranslator` — retail-faithful combat-text formatters into `ChatLog` ("You hit drudge for 50 slashing damage (87%)"). Subscribes to `CombatState`'s `DamageTaken` / `DamageDealtAccepted` / `EvadedIncoming` / `MissedOutgoing` / `AttackDone` / `KillLanded`; templates ported verbatim from holtburger `panels/chat.rs:221-308`. | Tests ✓ |
|
||||
| I.7 | `CombatChatTranslator` — retail-faithful combat-text formatters into `ChatLog` ("You hit drudge for 50 slashing damage (87%)"). Subscribes to `CombatState`'s `DamageTaken` / `DamageDealtAccepted` / `EvadedIncoming` / `MissedOutgoing` / `KillLanded`; `AttackDone` is control-only and deliberately silent. | Tests ✓ |
|
||||
| K | Input architecture — `Action` enum, `KeyChord`, `KeyBindings`, multicast `InputDispatcher` with scope-stack + modal capture, retail-default keymap (152 bindings), `keybinds.json` persistence, F11 Settings panel with click-to-rebind + conflict detection, main menu bar + View menu | Live ✓ |
|
||||
| L.0 | Full retail-style Settings interface — F11 tabbed panel with 6 tabs (Keybinds + Display + Audio + Gameplay + Chat + Character). `settings.json` at `%LOCALAPPDATA%\acdream\`, per-toon `Character` keying (swapped on EnterWorld). Display GL knobs (Resolution / Fullscreen / VSync / FOV / ShowFps) + Audio (Master / SFX) live-wired; Gameplay / Chat / Character settings persist for server-sync wiring later. Tab API extension to `IPanelRenderer`; chat Copy mode (read-only multi-line); per-panel layout reset; FramebufferResize handler keeps GL viewport + camera aspect + panel positions in sync. | Live ✓ |
|
||||
| C.1 | PES particle system + sky-pass refinements — retail-faithful `ParticleEmitterInfo` unpack with all 13 motion integrators (`Particle::Init`/`Update` ports of `0x0051c290`/`0x0051c930`), `PhysicsScriptRunner` with `CallPES` self-loop semantics, `ParticleHookSink` with `EmitterDied` cleanup, instanced billboard `ParticleRenderer` with material-derived blend (DAT emitters never default additive — pulled from particle GfxObj surface), global back-to-front sort, BC clipmap alpha-keying, AttachLocal `is_parent_local=1` live-parent follow via `UpdateEmitterAnchor`. Sky pass: `Translucent+ClipMap` → alpha-blend cloud sheet (matches `D3DPolyRender::SetSurface` `0x0059c4d0`), raw-`Additive` fog-skip (matches `0x0059c882`), per-keyframe `SkyObjectReplace` Translucency/Luminosity/MaxBright divide-by-100, bit `0x01` pre/post-scene split (matches `GameSky::CreateDeletePhysicsObjects` `0x005073c0`), Setup-backed (`0x020xxxxx`) sky objects via `SetupMesh.Flatten`, persistent GL sampler objects (Wrap + ClampToEdge) replace per-frame wrap-mode mutation (ported from WorldBuilder's `OpenGLGraphicsDevice`), post-scene Z-offset gated on `(Properties & 4) != 0 && (Properties & 8) == 0` per `GameSky::UpdatePosition` `0x00506dd0`. Sky-PES playback disabled by default (named-retail proves `GameSky` drops `pes_id`); `ACDREAM_ENABLE_SKY_PES=1` opens the experimental path. 1325 → 1331 tests. | Live ✓ |
|
||||
|
|
@ -497,7 +497,7 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.
|
|||
- **Wave 4.4e implemented — exact missile ammo number (live gate pending).** Pure Core reproduces retail's thrown-weapon-vs-separate-ammo resolution over the ordered player equipment list and its zero-to-one count normalization. Relevant equipment/stack events update authored missile indicator `0x10000194` with the DAT font. Warning-free Release build and 4,754-pass / 5-skip suite are green; AP-101 is retired.
|
||||
- **M2 held-object parenting shipped and live-gated 2026-07-11.** The combat toggle now ports `GetDefaultCombatMode` over ordered equipped contents, so a bow requests Missile instead of the old hardcoded Melee. CreateObject preserves parent/placement/timestamp fields, `0xF749` ParentEvent is handled, and `EquippedChildRenderController` renders the weapon as a separate child composed from the animated hand part + holding frame + child placement frame. Live gate passed: bow selected missile stance, rendered in-hand, followed animation, unequipped cleanly, and melee remained correct. App Release builds with zero warnings; the full 4,765-pass / 5-skip suite is green. AP-111 is retired; research: `docs/research/2026-07-11-combat-default-and-parent-event-pseudocode.md`.
|
||||
- **M2 local attack receive funnel implemented 2026-07-11; live gate pending.** Retail `ExecuteAttack` only sends the request; ACE chooses the concrete melee/missile action and returns it in a non-autonomous mt-0 `UpdateMotion`. The local branch now runs that state through the same constructor-defaulted `MoveToInterpretedState` funnel and 15-bit action-stamp gate as remotes, then applies sticky/long-jump tails. The old local-only direct `Commands[]` replay is deleted. Shared conversion lives in `InboundInterpretedMotionFactory`; research: `docs/research/2026-07-11-local-combat-motion-pseudocode.md`.
|
||||
- **M2 basic retail combat bar implemented 2026-07-11; corrective live visual gate pending.** Production now mounts authored `gmCombatUI` LayoutDesc `0x21000073`, shows it only for Melee/Missile, binds authored high/medium/low buttons and desired-power slider, and routes mouse plus keyboard through one `CombatAttackController`. The corrective pass ports local.dat StringInfo labels, `gmCombatUI::PostInit` runtime option captions, interactive `UIOption_Checkbox` controls, structural horizontal-scrollbar track/thumb roles, and `ClientCombatSystem::UpdateTargetTracking` so Keep in View rotates the retail chase-camera boom around the player toward the selected attack target. Matching retail x86 recovered exact 1.0-second normal and 0.8-second dual-wield power-up times; the same constants retire the guessed jump charge. Keybinding schema v3 migrates attack actions from one-shot Press to transition-emitting Hold. Research: `docs/research/2026-07-11-retail-combat-bar-pseudocode.md`, `docs/research/2026-07-11-combat-target-camera-pseudocode.md`; AP-24/AP-95 retired, AP-110 narrowed, AP-112 records the remaining advanced/Recklessness and command-interpreter seams.
|
||||
- **M2 basic retail combat bar implemented 2026-07-11; corrective live visual gate pending.** Production mounts authored `gmCombatUI` LayoutDesc `0x21000073`, shows it only for Melee/Missile, and routes mouse plus keyboard through one `CombatAttackController`. Corrections now include the right-to-left red charge meter, silent control-only `AttackDone(ActionCancelled)`, persistent Dead cycles despite zero-velocity position updates, and target-frame Keep in View that preserves manual viewer-offset orbit. Matching retail x86 recovered exact 1.0-second normal and 0.8-second dual-wield power-up times. Research: `docs/research/2026-07-11-retail-combat-bar-pseudocode.md`, `docs/research/2026-07-11-combat-target-camera-pseudocode.md`, `docs/research/2026-07-11-combat-live-gate-corrections-pseudocode.md`; AP-24/AP-95 retired, AP-110 narrowed, AP-112 records the remaining advanced/Recklessness and command-interpreter seams.
|
||||
- **✓ SHIPPED — Character window** (`LayoutDesc 0x2100002E`, `CharacterStatController`, 2026-06-26, same branch). **Visually user-confirmed 2026-06-26 — Attributes tab reads as retail.** Three tabs, header (name/heritage/PK), large-gold level number (dat font, `largeDatFont` 18px), "Total Experience (XP):" + "XP for next level:" captions, 9-row attribute list (icons + right-aligned values + Health/Stamina/Mana vitals), click-to-select (top/bottom selection bars + footer State-B "{Attr}: {value}" / "Experience To Raise: Infinity!" + affordability-gated raise triangles), centered footer. User noted "still needs some polish for later" — deferred to Issue #158.
|
||||
- **✓ SHIPPED — D.5.4 — Client object/item data model (foundation).** Shipped 2026-06-18 (`b506f53`..`a33e897`, 11 commits). Renamed `ItemRepository`→`ClientObjectTable` / `ItemInstance`→`ClientObject`; broadened the table to hold EVERY server object (retail `weenie_object_table` shape). `CreateObject` is now the canonical merge-upsert (`ClientObjectTable.Ingest`, retail `SetWeenieDesc` semantics) via a new Core.Net `ObjectTableWiring` (off GameWindow); `DeleteObject` evicts; `PlayerDescription` is a membership manifest (`RecordMembership`); live container-membership index (`GetContents`, retail `object_inventory_table`). `_liveEntityInfoByGuid` retired (selection/describe resolve from the one table). Root fix: the old enrich-existing-only `EnrichItem` dropped `CreateObject`s for items with no `PlayerDescription` stub — live-Coldeve 4/6 hotbar slots blank; items are now created, not dropped. **Crux resolved:** retail is TWO tables (`object_table` + `weenie_object_table`), NOT one — acdream's `WorldEntity` (3D system) + `ClientObjectTable` (data/UI) split was already architecturally faithful; the fix was the ingestion path, not a table unification. 2671 tests green.
|
||||
- **Roadmap correction (2026-07-10):** the completion order is now the architecture-first campaign in `docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md`. Retail `gmToolbarUI` is object-only: preserve `ShortCutData.index_`, `objectID_`, and `spellID_`, but do not invent spell glyphs on this bar. `PlayerModule::favorite_spells_[8]` feeds separate spell bars.
|
||||
|
|
@ -580,7 +580,7 @@ the way retail + holtburger expect.
|
|||
- **✓ SHIPPED — I.4 — `ChatPanel` input field + slash commands.** Enter-to-submit input field on `ChatPanel`; `ChatInputParser` recognises `/say` `/t` `/tell` `/r` `/g` `/f` `/a` `/m` `/p` `/v` `/cv` `/lfg` `/trade` `/role` `/society` `/olthoi`; `ChatVM.LastIncomingTellSender` tracks for `/r` reply. `ImGui.WantCaptureKeyboard` already suppresses WASD on input focus. Commit `f14296c`.
|
||||
- **✓ SHIPPED — I.5 — Holtburger inbound chat parity + Windows-1252.** `EmoteText (0x01E0)`, `SoulEmote (0x01E2)`, `ServerMessage (0xF7E0)`, `PlayerKilled (0x019E)` parsers + `WeenieError` routing through `GameEventWiring`. Global string codec switch from `Encoding.ASCII` to `Encoding.GetEncoding(1252)` so accented names round-trip per retail + holtburger. Commit `ff5ed9e`.
|
||||
- **✓ SHIPPED — I.6 — TurbineChat codec + `ChatChannelInfo`.** Full `0xF7DE` codec with three payload variants (`EventSendToRoom`, `RequestSendToRoomById`, `Response`), UTF-16LE strings with variable-length prefix, `SetTurbineChatChannels (0x0295)` parser, unified `ChatChannelInfo` (Legacy + Turbine variants), `TurbineChatState`. **ACE doesn't host a TurbineChat server — codec is ready when retail-emulating servers exist.** Commit `ca968fc`.
|
||||
- **✓ SHIPPED — I.7 — `CombatChatTranslator`.** Retail-faithful combat-text formatters into `ChatLog` ("You hit drudge for 50 slashing damage (87%)"). Subscribes to `CombatState`'s `DamageTaken` / `DamageDealtAccepted` / `EvadedIncoming` / `MissedOutgoing` / `AttackDone` / `KillLanded`; templates ported verbatim from holtburger `panels/chat.rs:221-308`. Commit `3d26c8e`.
|
||||
- **✓ SHIPPED — I.7 — `CombatChatTranslator`.** Retail-faithful combat-text formatters into `ChatLog` ("You hit drudge for 50 slashing damage (87%)"). Subscribes to visible damage/evasion/miss/kill events; `AttackDone` was removed from chat after named retail + ACE proved its nonzero final status is control-only. Commit `3d26c8e`, corrected 2026-07-11.
|
||||
- **✓ SHIPPED — I.8 — Docs alignment.** Roadmap (this file) + `docs/ISSUES.md` issues #14-#20 closed + `memory/project_chat_pipeline.md` crib + `MEMORY.md` index entry + `CLAUDE.md` UI strategy paragraph all updated to reflect Phase I shipped state. Commit `(this commit)`.
|
||||
|
||||
**Acceptance (verified 2026-04-25):**
|
||||
|
|
|
|||
|
|
@ -462,7 +462,10 @@ include dungeons.
|
|||
`gmCombatUI` LayoutDesc `0x21000073` now pops for Melee/Missile and shares one
|
||||
press/hold/release attack-request owner across DAT buttons and keybindings.
|
||||
Retail x86 resolves full charge to 1.0 s (0.8 s dual wield), also retiring the
|
||||
guessed jump-charge timing. See `docs/research/2026-07-11-retail-combat-bar-pseudocode.md`.
|
||||
guessed jump-charge timing. The corrective gate ports the right-to-left red
|
||||
meter, silent AttackDone control status, persistent corpse motion, and
|
||||
target-frame Keep in View with retained manual orbit. See
|
||||
`docs/research/2026-07-11-combat-live-gate-corrections-pseudocode.md`.
|
||||
- **L.1b** — Command router + motion-state cleanup (prereq for L.1c).
|
||||
|
||||
**Freeze on landing:**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
# Combat live-gate corrections pseudocode
|
||||
|
||||
Sources: Sept 2013 EoR named retail client and ACE server source.
|
||||
|
||||
- `ClientCombatSystem::HandleAttackDoneEvent` (`0x0056C500`)
|
||||
- `gmCombatUI::RecvNotice_SetPowerbarLevel` (`0x004CC0E0`)
|
||||
- `CPhysicsObj::MoveOrTeleport` (`0x00516330`)
|
||||
- `CameraSet::Rotate` (`0x00458310`)
|
||||
- ACE `Player.OnAttackDone`, `Player_Melee.cs`
|
||||
- ACE `WeenieError.YouChargedTooFar`, `Player_Move.cs`
|
||||
|
||||
## Attack completion versus visible errors
|
||||
|
||||
```text
|
||||
HandleAttackDoneEvent(sequence, error):
|
||||
consume error as combat-controller state
|
||||
decide whether repeat may continue or the attack aborts
|
||||
do not write an error line to chat
|
||||
|
||||
ACE OnAttackDone(final sequence):
|
||||
send AttackDone(ActionCancelled) to reset the client power meter
|
||||
if the player must see a failure:
|
||||
send a separate WeenieError event
|
||||
```
|
||||
|
||||
Therefore `AttackDone(ActionCancelled)` is silent. A separate
|
||||
`YouChargedTooFar` (`0x003D`) remains visible and is formatted by the ordinary
|
||||
WeenieError path. ACE emits it when the charge exceeds 15 metres; target
|
||||
selection itself is not range-limited by retail.
|
||||
|
||||
## Power meter direction
|
||||
|
||||
```text
|
||||
RecvNotice_SetPowerbarLevel(level):
|
||||
find nested meter 0x10000050
|
||||
set meter scalar attribute 0x69 to level
|
||||
|
||||
authored meter direction = 1
|
||||
visible fill = rightmost (width * clamp(level)) pixels
|
||||
```
|
||||
|
||||
The red meter grows from the Power label at the right toward Speed at the
|
||||
left. The green desired-power thumb remains an independent scrollbar child.
|
||||
Clipping the rightmost portion must retain the full tiled image's UV offset.
|
||||
|
||||
## Persistent death motion
|
||||
|
||||
```text
|
||||
on UpdatePosition(position, velocity):
|
||||
update physics pose and velocity
|
||||
do not derive an interpreted motion command from velocity
|
||||
|
||||
on UpdateMotion(Dead):
|
||||
apply the persistent Dead motion cycle through the motion interpreter
|
||||
```
|
||||
|
||||
A corpse legitimately sends zero velocity. Turning that velocity into Ready
|
||||
overrides the server-authored Dead cycle and makes the creature stand again.
|
||||
Retail keeps position/velocity transport separate from interpreted motion.
|
||||
|
||||
## Keep in View orbit
|
||||
|
||||
See `2026-07-11-combat-target-camera-pseudocode.md`. The corrective detail is:
|
||||
|
||||
```text
|
||||
targetHeadingFrame = frame looking from player pivot toward target point
|
||||
viewerOffset.xy = the independently rotatable camera boom offset
|
||||
eye = pivot + targetHeadingFrame.transform(viewerOffset)
|
||||
forward = normalize(pivot - eye) // LOOK_AT_PIVOT
|
||||
```
|
||||
|
||||
Tracking changes the frame that transforms `viewerOffset`; it does not reset
|
||||
or discard the offset. Manual camera orbit therefore remains usable while
|
||||
Keep in View is active.
|
||||
|
|
@ -36,6 +36,10 @@ CameraSet.TrackTarget(targetId):
|
|||
|
||||
SetTargetForOffset(viewerOffset), while targeting:
|
||||
camera.targetStatus = LOOK_AT_OBJECT | LOOK_AT_PIVOT
|
||||
|
||||
CameraSet.Rotate(angle), including while targeting:
|
||||
viewerOffset.xy = rotate(viewerOffset.xy, angle)
|
||||
SetTargetForOffset(viewerOffset)
|
||||
```
|
||||
|
||||
## Per-frame pose
|
||||
|
|
@ -60,14 +64,17 @@ damp current viewer toward (desiredEye, desiredForward)
|
|||
The target therefore changes the direction of the boom around the player; it
|
||||
does not replace the player as the boom's pivot. `LOOK_AT_PIVOT` keeps the
|
||||
player centered while the target-facing boom keeps the combat target in view.
|
||||
The viewer offset remains independently rotatable: manual camera orbit rotates
|
||||
that offset inside the target-facing frame rather than being discarded or
|
||||
snapped back to zero.
|
||||
|
||||
## Port mapping
|
||||
|
||||
- `GameWindow.GetCombatCameraTargetPoint` applies the option/mode/valid-target
|
||||
gates and transforms the target-local 0.5 metre Z offset.
|
||||
- `RetailChaseCamera.ComputeTrackedHeading` ports the pivot-to-target direction.
|
||||
- Existing `RetailChaseCamera` boom construction and damping consume that
|
||||
heading unchanged.
|
||||
- `RetailChaseCamera.ComputeDesiredPose` ports the heading-frame transform and
|
||||
preserves the viewer-offset yaw while tracking.
|
||||
- The diagnostic legacy `ChaseCamera` remains intentionally non-retail under
|
||||
existing divergence row TS-19; the production retail camera owns this behavior.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue