From db03b4bda8217cda450154000c6e59707e9e4c48 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 13 Jul 2026 10:22:03 +0200 Subject: [PATCH] feat(ui): port retail jump power bar Import gmFloatyPowerBarUI LayoutDesc 0x21000072, teach the meter factory its stateful single-image shape, and project the movement-owned retail jump charge through a focused retained controller. Preserve authored resize constraints and state-managed visibility, with named-decomp pseudocode plus controller, movement, and production-DAT conformance coverage. Co-Authored-By: Codex --- docs/ISSUES.md | 30 + docs/plans/2026-04-11-roadmap.md | 2 +- docs/plans/2026-05-12-milestones.md | 8 +- ...6-07-13-retail-jump-powerbar-pseudocode.md | 135 + .../Input/PlayerMovementController.cs | 14 + src/AcDream.App/Rendering/GameWindow.cs | 2 + src/AcDream.App/UI/Layout/DatWidgetFactory.cs | 45 +- .../UI/Layout/JumpPowerbarController.cs | 91 + src/AcDream.App/UI/RetailUiRuntime.cs | 68 +- src/AcDream.App/UI/UiMeter.cs | 30 +- src/AcDream.App/UI/WindowNames.cs | 1 + .../UI/Layout/DatWidgetFactoryTests.cs | 31 + .../UI/Layout/FixtureLoader.cs | 6 + .../UI/Layout/JumpPowerbarControllerTests.cs | 73 + .../JumpPowerbarLayoutConformanceTests.cs | 33 + .../UI/Layout/RetailLayoutFixtureGenerator.cs | 1 + .../UI/Layout/fixtures/powerbar_21000072.json | 2467 +++++++++++++++++ .../Input/PlayerMovementControllerTests.cs | 18 + 18 files changed, 3045 insertions(+), 10 deletions(-) create mode 100644 docs/research/2026-07-13-retail-jump-powerbar-pseudocode.md create mode 100644 src/AcDream.App/UI/Layout/JumpPowerbarController.cs create mode 100644 tests/AcDream.App.Tests/UI/Layout/JumpPowerbarControllerTests.cs create mode 100644 tests/AcDream.App.Tests/UI/Layout/JumpPowerbarLayoutConformanceTests.cs create mode 100644 tests/AcDream.App.Tests/UI/Layout/fixtures/powerbar_21000072.json diff --git a/docs/ISSUES.md b/docs/ISSUES.md index bb4a27e7..096a28f8 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -46,6 +46,36 @@ Copy this block when adding a new issue: --- +## #209 — Retail jump power bar missing + +**Status:** IN-PROGRESS — implementation complete 2026-07-13, pending live visual gate +**Severity:** MEDIUM +**Component:** retained UI / movement + +**Description:** Holding the jump key charged the movement system correctly, +but the separate retail floating power bar did not appear or show jump power. + +**Root cause:** The production retained runtime had not mounted +`gmFloatyPowerBarUI` LayoutDesc `0x21000072`. The generic meter importer also +mistook its stateful fill child plus hidden Recklessness child for a vitals-style +three-slice meter, leaving all slice ids empty. + +**Resolution:** The importer now recognizes the stateful single-image meter +shape: the authored direct image is the empty track and the selected numeric +child state supplies the fill. `JumpPowerbarController` selects JumpMode, reads +the movement controller's existing retail-timed charge snapshot, shows the +state-managed window on charge begin, fills left-to-right, and resets/hides it +on release. Geometry and resize limits come from the authored DAT layout. + +**Research:** `docs/research/2026-07-13-retail-jump-powerbar-pseudocode.md` + +**Acceptance:** In the connected Release client, hold Jump: the 610×25 retail +bar appears and fills smoothly from its left edge to its right edge in about +one second. Keep holding at full, then release: the bar resets and disappears as +the jump starts. Repeat after moving/resizing the bar and after relogging. + +--- + ## #208 — Combat bar appears after logging in peacefully **Status:** IN-PROGRESS — fix shipped 2026-07-12, pending live gate diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 6fa7e70d..5a75542c 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -506,7 +506,7 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar. - **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. - **✓ D.5.3 — Toolbar selected-object display (issue #141) SHIPPED.** Exact health/player/pet/Free-PK policy, selected-item mana (`0x100001A2`), stack entry (`0x100001A3`), stack slider (`0x100001A4`), formatted count/name, shared split quantity, click-to-use, peace/war, health/name, and paperdoll self/upper-item selection all passed live by 2026-07-11. Exact raw `ShortCutData` preservation is already shipped in Wave 4.2; the eight `favorite_spells_` lists belong to the separate spellbook/spell-bar phase. - **☐ D.5.5+ — Core panels.** Inventory (`gmInventoryUI`/`gmBackpackUI`), equipment/paperdoll (`gmPaperDollUI`/`gm3DItemsUI` + the `UiViewport` 3D doll), vendor, trade, spellbook. Research drop done (`docs/research/2026-06-16-*`). Depends on **D.5.4** (data model) + the item-slot/list/icon spine (D.5.1/D.5.2) + the **window manager** (Plan 2: open/close/z-order/persist + faithful grip/dragbar drag-resize) + the drag-drop spine wired (`UiRoot` has the chain; the per-cell accept/drop hooks are still stubs in `UiField`). Also deferred from D.5.1: drag/reorder + the `AddShortcut`/`RemoveShortcut` mutate wire. -- **D.6 — HUD.** **Radar/compass IMPLEMENTED 2026-07-10; live-world visual gate pending.** It is the retained retail `gmRadarUI` (`LayoutDesc 0x21000074`): a 120×140 circular radar (`0x06004CC1`) with N/E/S/W token sprites orbiting the face, a fixed bright-green player marker, and the coordinate footer (`0x06004CC0`). Projection is `radarPixelRadius / radarRange` with 75 m outdoor / 25 m indoor ranges — there is no 1.18 factor or scrolling compass strip. It lives in `AcDream.App/UI` beside the other imported retail panels. Remaining D.6: target name plate, damage floaters, and other world-space HUD elements through the raw `TextRenderer` path. See slice 06 §A.2, `docs/research/2026-07-10-retail-radar-pseudocode.md`, and the named `gmRadarUI` decomp. +- **D.6 — HUD.** **Radar/compass IMPLEMENTED 2026-07-10; jump power bar IMPLEMENTED 2026-07-13; live-world visual gates pending.** The radar is retained retail `gmRadarUI` (`LayoutDesc 0x21000074`): a 120×140 circular face (`0x06004CC1`) with orbiting N/E/S/W tokens, a fixed bright-green player marker, and coordinate footer (`0x06004CC0`). Projection is `radarPixelRadius / radarRange` with 75 m outdoor / 25 m indoor ranges — no 1.18 factor or scrolling strip. The separate `gmFloatyPowerBarUI` (`LayoutDesc 0x21000072`) now appears only while jump is pending and clips the authored JumpMode fill left-to-right from the movement controller's shared 0–1 charge. Both live in `AcDream.App/UI` behind focused controllers; `GameWindow` supplies state delegates only. Remaining D.6: target name plate, damage floaters, and other world-space HUD elements through the raw `TextRenderer` path. See slice 06 §A.2, `docs/research/2026-07-10-retail-radar-pseudocode.md`, `docs/research/2026-07-13-retail-jump-powerbar-pseudocode.md`, and the named `gmRadarUI`/`gmPowerbarUI` decomp. - **✓ SHIPPED — D.7 — Cursor manager (user-confirmed 2026-07-11).** All reachable `ClientUISystem::UpdateCursorState @ 0x00564630` branches resolve through production DAT EnumIDMap table 6, with event-ordered global-default versus captured/hovered `MediaDescCursor` layering from `UIElementManager::CheckCursor @ 0x0045ABF0`. OS fallback remains only AP-72 and emits a visible diagnostic. The live gate covered world/object, combat, targeted-use, text, move/resize, and drag contexts; combat-indicator mouse clicks and the quick key share the same toggle command. - ~~**D.8 — Sound.**~~ **Superseded — shipped as Phase E.2** (`SoundTable`/`Sound` dat decode, OpenAL 16-voice engine, per-entity 3D positional audio via `AudioHookSink`). Entry kept here for history; see the shipped table. diff --git a/docs/plans/2026-05-12-milestones.md b/docs/plans/2026-05-12-milestones.md index 9e8680ea..a125e463 100644 --- a/docs/plans/2026-05-12-milestones.md +++ b/docs/plans/2026-05-12-milestones.md @@ -566,11 +566,13 @@ Open the inventory panel — retail-skinned with the right font, icons, and - **C.3** — Palette range tuning (skin/hair/eye colors match retail). - **C.4** — Double-sided translucent polys. - **D.2b** — Custom retail-look UI backend. **In progress:** vitals + chat + toolbar + inventory (window manager, B-Grid, B-Controller, B-Wire, drag-drop, container-switching) + paperdoll (equip slots, 3-D doll UiViewport, Slots toggle) + **Character window** (Attributes tab, visually confirmed 2026-06-26) + **UI Studio** (standalone panel previewer + 26-window retail dump + interactive click-routing + headless screenshot) + **importer dat-fidelity** (Fix A/B/C: justification, FontColor, per-element FontDid resolver [studio-only; #157 tracks live-game path]) are shipped. Current campaign: shared retained-widget/window architecture, then selected-object mana/stack completion. **Retail correction:** the main toolbar is object-only; the eight favorite-spell lists drive separate spell bars. Execution plan: `docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md`. -- **D.3–D.7** — AcFont + dat sprites + core panels reskinned + HUD orbs + - cursor manager. **D.6 radar/compass shipped 2026-07-10; D.7 complete retail +- **D.3–D.7** — AcFont + dat sprites + core panels reskinned + HUD + + cursor manager. **D.6 radar/compass shipped 2026-07-10 and the separate retail + jump power bar was implemented 2026-07-13; D.7 complete retail cursor catalog/precedence shipped and user-confirmed 2026-07-11.** The radar uses the retained UI architecture using the retail DAT layout and recovered runtime behavior; - live-game visual confirmation remains the user gate. + the jump bar uses the same retained composition with movement-owned charge timing. + Live-game visual confirmation remains the user gate. - **L.1f** — NPC/monster + item-use animation coverage. **Freeze on landing:** diff --git a/docs/research/2026-07-13-retail-jump-powerbar-pseudocode.md b/docs/research/2026-07-13-retail-jump-powerbar-pseudocode.md new file mode 100644 index 00000000..ec17cdb6 --- /dev/null +++ b/docs/research/2026-07-13-retail-jump-powerbar-pseudocode.md @@ -0,0 +1,135 @@ +# Retail jump power bar — pseudocode and port map + +Date: 2026-07-13 + +Binary: Sept. 2013 EoR `acclient.exe` v11.4186 + +Scope: the floating `gmPowerbarUI` presentation used while the player charges a jump + +## Named retail sources + +- `gmPowerbarUI::PostInit @ 0x004DA4E0` +- `gmPowerbarUI::RecvNotice_SetPowerbarLevel @ 0x004DA550` +- `gmPowerbarUI::RecvNotice_FinishPowerbar @ 0x004DA580` +- `gmPowerbarUI::RecvNotice_BeginPowerbar @ 0x004DA730` +- `gmFloatyPowerBarUI::PostInit @ 0x004D0F00` +- `gmFloatyPowerBarUI::UpdateFromPlayerModule @ 0x004D1200` +- `gmFloatyPowerBarUI::ResizeTo @ 0x004D1460` +- `gmFloatyPowerBarUI::MoveTo @ 0x004D15D0` +- `gmFloatyPowerBarUI::SetVisible @ 0x004D17D0` +- `ClientCombatSystem::FinishJump @ 0x0056A9B0` +- `ClientCombatSystem::StartPowerBarBuild @ 0x0056ADB0` +- `ClientCombatSystem::GetPowerBarLevel @ 0x0056ADE0` +- `ClientCombatSystem::CommenceJump @ 0x0056AF90` +- `ClientCombatSystem::DoJump @ 0x0056B110` + +The symbols and addresses above are present in +`docs/research/named-retail/symbols.json`; the bodies are in +`docs/research/named-retail/acclient_2013_pseudo_c.txt`. + +## Authored DAT layout + +Portal DAT `LayoutDesc 0x21000072` is a `gmFloatyPowerBarUI` window: + +| Element | ID | Geometry | Media/state | +|---|---:|---:|---| +| Root | `0x10000612` | 610×25 px | custom type `0x10000053` | +| Meter | `0x10000034` | x=5, y=5, 600×15 px | empty track `0x06004D0B` | +| Fill field | `0x00000002` | 600×15 px | JumpMode `0x10000042` → `0x06001354`; MeleeMode `0x10000043` and MissileMode `0x10000044` → `0x06001200`; DDDMode `0x10000045` → `0x0600195B` | +| Recklessness field | `0x100005EE` | x=60, 480×15 px | `0x0600715E`; hidden by `PostInit` | + +The meter's fill is clipped horizontally. A level of 0.0 shows no fill; 0.5 +shows the left 300 px; 1.0 shows the full 600 px. It does not stretch from the +center or from the right. + +## Retail lifecycle pseudocode + +```text +CommenceJump(): + if jump_pending: + return + error = motion_interpreter.charge_jump() + if error != success: + report the matching jump error + return + cancel repeat attack if active + latest_powerbar_level = 0 + jump_pending = true + powerbar_mode = JUMP + notify BeginPowerbar(JUMP) + StartPowerBarBuild() + +StartPowerBarBuild(): + build_in_progress = true + build_start_time = current_time + notify SetPowerbarLevel(powerbar_mode, 0) + latest_powerbar_level = 0 + +GetPowerBarLevel(): + if not build_in_progress: + return 0 + duration = 0.8 seconds in dual-wield style, otherwise 1.0 second + return clamp((current_time - build_start_time) / duration, 0, 1) + +gmPowerbarUI.BeginPowerbar(JUMP): + set root state to JumpMode (0x10000042) + current_mode = JUMP + set meter 0x10000034 level to 0 + show window + +gmPowerbarUI.SetPowerbarLevel(mode, level): + if mode == current_mode: + set meter 0x10000034 level to level + +DoJump(on release): + extent = max(GetPowerBarLevel(), MIN_JUMP_EXTENT) + FinishJump() + motion_interpreter.jump(extent) + send JumpPack(extent, velocity, position/update sequences) + +FinishJump(): + if jump_pending: + stop build and clear its time + notify FinishPowerbar(JUMP) + powerbar_mode = UNDEFINED + clear standing_longjump + jump_pending = false + +gmPowerbarUI.FinishPowerbar(JUMP): + if JUMP == current_mode: + current_mode = UNDEFINED + set meter level to 0 + hide window +``` + +`gmPowerbarUI::PostInit` also hides the recklessness field. The floaty subclass +restores and persists its position, size, lock chrome, and visibility through +the normal retail window preferences. + +## Reference cross-checks + +- ACE `ACE.Server/Network/Structure/JumpPack.cs` documents `Extent` as jump + power 0–1. `ACE.Server/WorldObjects/Player.cs::HandleActionJump` clamps the + received extent to that same interval before applying jump stamina/height. +- holtburger `crates/holtburger-core/src/client/commands.rs` carries the chosen + extent unchanged into the `JumpData` action. Its vendored ACViewer movement + implementation likewise stores jump extent as the power input to the jump + calculation. + +These references corroborate the data range and its handoff. The named retail +decomp remains authoritative for charge timing, notices, visibility, and UI +state selection. + +## acdream port map + +- `PlayerMovementController` already owns the exact shared retail charge clock + and release transition. Expose it as a read-only `(IsCharging, Power)` + snapshot; do not add a UI timer. +- Teach `UiMeter`/`DatWidgetFactory` the DAT's stateful single-image meter shape: + meter direct media is the track, child state media is the selected fill. +- `JumpPowerbarController` selects JumpMode, binds fill to the movement snapshot, + and performs the begin/show and finish/reset/hide transitions. +- `RetailUiRuntime` imports and mounts `0x21000072` as a state-managed retained + window. `GameWindow` only supplies the movement snapshot delegate. + +This port introduces no intentional retail divergence. diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index 6649967b..314e493f 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -18,6 +18,13 @@ public readonly record struct MovementInput( float MouseDeltaX = 0f, bool Jump = false); +/// +/// Read-only presentation snapshot of retail's pending jump build. The movement +/// controller remains the sole owner of charge timing; retained UI only projects +/// this state into gmPowerbarUI. +/// +public readonly record struct JumpChargeSnapshot(bool IsCharging, float Power); + /// /// Result of a single frame's movement update. /// @@ -165,6 +172,13 @@ public sealed class PlayerMovementController // Jump charge state. private bool _jumpCharging; private float _jumpExtent; + + /// + /// Current retail jump-powerbar state. Power is always zero when no jump is + /// pending and otherwise lies in [0,1]. + /// + public JumpChargeSnapshot JumpCharge + => new(_jumpCharging, _jumpCharging ? _jumpExtent : 0f); // Matching v11.4186 x86 resolves GetPowerBarLevel's collapsed x87 // operands: ATTACK_POWERUP_TIME=1.0 s, DUAL_WIELD_POWERUP_TIME=0.8 s. // Jump uses the same shared powerbar function, so its normal fill rate is diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 2cb858f6..e03f0abc 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -2142,6 +2142,8 @@ public sealed class GameWindow : IDisposable _combatAttackController, () => _persistedGameplay, SetRetailCombatGameplay), + JumpPowerbar: new AcDream.App.UI.JumpPowerbarRuntimeBindings( + () => _playerController?.JumpCharge ?? default), Toolbar: new AcDream.App.UI.ToolbarRuntimeBindings( Objects, () => Shortcuts, diff --git a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs index 7021d661..e2f47f62 100644 --- a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs +++ b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs @@ -311,7 +311,9 @@ public static class DatWidgetFactory .OrderBy(c => c.ReadOrder) .ToList(); - if (containers.Count >= 2) + if (containers.Count >= 2 + && HasThreeSliceShape(containers[0]) + && HasThreeSliceShape(containers[1])) { // Vitals 3-slice shape: two Type-3 containers each holding 3 grandchild images // (left-cap / center-tile / right-cap). Back is the lower ReadOrder; front is higher. @@ -325,7 +327,7 @@ public static class DatWidgetFactory m.FrontTile = ft; m.FrontRight = fr; } - else if (containers.Count == 1) + else if (containers.Count == 1 && containers[0].StateMedia.ContainsKey("")) { // Single-image shape used by the toolbar selected-object meters // (health 0x100001A1, mana 0x100001A2). @@ -354,15 +356,50 @@ public static class DatWidgetFactory m.FrontTile = containers[0].StateMedia.TryGetValue("", out var fm) ? fm.File : 0u; m.FrontRight = 0; } + else if (containers.Any(HasStatefulFill)) + { + // Stateful single-image shape used by gmPowerbarUI (LayoutDesc + // 0x21000072). The meter's DirectState is the empty track. One + // Type-3 child supplies named Jump/Melee/Missile/DDD state media; + // another carries the optional recklessness range as DirectState. + // gmPowerbarUI::PostInit @ 0x004DA4E0 hides that range, so it is not + // promoted into a meter slice here. + m.BackLeft = 0; + m.BackTile = info.StateMedia.TryGetValue("", out var track) ? track.File : 0u; + m.BackRight = 0; + m.FrontLeft = 0; + m.FrontTile = 0; + m.FrontRight = 0; + + foreach (ElementInfo container in containers) + { + foreach (var (stateId, state) in container.States) + { + if (stateId == UiStateInfo.DirectStateId + || !container.StateMedia.TryGetValue(state.Name, out var media)) + continue; + m.ConfigureStateFill(stateId, media.File); + } + } + } else { - // Count == 0: no Type-3 containers at all — genuinely malformed meter dat. - Console.WriteLine($"[D.2b] meter 0x{info.Id:X8}: {containers.Count} Type-3 slice containers (expected 1 or 2) — bars may render as solid-color fallback."); + Console.WriteLine($"[D.2b] meter 0x{info.Id:X8}: {containers.Count} Type-3 containers but no recognized 3-slice, direct-fill, or stateful-fill shape — bar may render as solid-color fallback."); } return m; } + private static bool HasThreeSliceShape(ElementInfo container) + => container.Children.Count(c => + c.StateMedia.TryGetValue("", out var media) && media.File != 0) >= 3; + + private static bool HasStatefulFill(ElementInfo container) + => container.States.Any(pair => + pair.Key != UiStateInfo.DirectStateId + && container.StateMedia.TryGetValue(pair.Value.Name, out var media) + && media.File != 0); + /// /// Returns the (left, tile, right) sprite ids for a 3-slice container, /// extracting them from the container's image children that carry a DirectState diff --git a/src/AcDream.App/UI/Layout/JumpPowerbarController.cs b/src/AcDream.App/UI/Layout/JumpPowerbarController.cs new file mode 100644 index 00000000..ecdbd9b4 --- /dev/null +++ b/src/AcDream.App/UI/Layout/JumpPowerbarController.cs @@ -0,0 +1,91 @@ +using AcDream.App.Input; + +namespace AcDream.App.UI.Layout; + +/// +/// Projects into the imported +/// retail gmFloatyPowerBarUI. The movement controller owns the clock; this +/// controller owns only DAT state, fill, and window visibility. +/// +/// +/// Retail references: gmPowerbarUI::RecvNotice_BeginPowerbar +/// (0x004DA730), RecvNotice_SetPowerbarLevel (0x004DA550), +/// RecvNotice_FinishPowerbar (0x004DA580), and +/// ClientCombatSystem::CommenceJump (0x0056AF90). +/// +public sealed class JumpPowerbarController : IRetainedPanelController +{ + public const uint LayoutId = 0x21000072u; + public const uint MeterId = 0x10000034u; + public const uint JumpModeState = 0x10000042u; + + private readonly UiMeter _meter; + private readonly Func _snapshot; + private readonly Action _setWindowVisible; + private float _power; + private bool _isCharging; + private bool _hasSynced; + private bool _disposed; + + private JumpPowerbarController( + UiMeter meter, + Func snapshot, + Action setWindowVisible) + { + _meter = meter; + _snapshot = snapshot; + _setWindowVisible = setWindowVisible; + _meter.Fill = () => _power; + SyncVisibility(force: true); + } + + public static JumpPowerbarController? Bind( + ImportedLayout layout, + Func snapshot, + Action setWindowVisible) + { + ArgumentNullException.ThrowIfNull(layout); + ArgumentNullException.ThrowIfNull(snapshot); + ArgumentNullException.ThrowIfNull(setWindowVisible); + + if (layout.FindElement(MeterId) is not UiMeter meter + || !meter.TrySetRetailState(JumpModeState)) + return null; + + return new JumpPowerbarController(meter, snapshot, setWindowVisible); + } + + /// Applies the latest movement-owned charge snapshot. + public void Tick() => SyncVisibility(force: false); + + /// + /// Re-applies the state-driven visibility after the window has been mounted. + /// This covers a jump key already held during retained-UI construction. + /// + public void SyncVisibility() => SyncVisibility(force: true); + + private void SyncVisibility(bool force) + { + if (_disposed) return; + + JumpChargeSnapshot state = _snapshot(); + _power = state.IsCharging ? Math.Clamp(state.Power, 0f, 1f) : 0f; + + if (force || !_hasSynced || state.IsCharging != _isCharging) + { + _isCharging = state.IsCharging; + _hasSynced = true; + _setWindowVisible(_isCharging); + } + } + + public void OnShown() => SyncVisibility(force: false); + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _power = 0f; + _meter.Fill = () => null; + } +} diff --git a/src/AcDream.App/UI/RetailUiRuntime.cs b/src/AcDream.App/UI/RetailUiRuntime.cs index c2423ed4..43dfb6a9 100644 --- a/src/AcDream.App/UI/RetailUiRuntime.cs +++ b/src/AcDream.App/UI/RetailUiRuntime.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using AcDream.App.Plugins; using AcDream.App.Combat; +using AcDream.App.Input; using AcDream.App.Rendering; using AcDream.App.UI.Layout; using AcDream.App.UI.Testing; @@ -42,6 +43,8 @@ public sealed record CombatRuntimeBindings( Func Gameplay, Action SetGameplay); +public sealed record JumpPowerbarRuntimeBindings(Func Snapshot); + public sealed record ToolbarRuntimeBindings( ClientObjectTable Objects, Func> Shortcuts, @@ -106,6 +109,7 @@ public sealed record RetailUiRuntimeBindings( ChatRuntimeBindings Chat, RadarRuntimeBindings Radar, CombatRuntimeBindings Combat, + JumpPowerbarRuntimeBindings JumpPowerbar, ToolbarRuntimeBindings Toolbar, CharacterRuntimeBindings Character, InventoryRuntimeBindings Inventory, @@ -136,6 +140,7 @@ public sealed class RetailUiRuntime : IDisposable MountChat(); MountToolbar(); MountCombat(); + MountJumpPowerbar(); MountCharacter(); MountPlugins(); MountInventory(); @@ -150,7 +155,7 @@ public sealed class RetailUiRuntime : IDisposable persistence.Store, persistence.CharacterKey, persistence.ScreenSize, - stateManagedVisibilityWindows: [WindowNames.Combat]); + stateManagedVisibilityWindows: [WindowNames.Combat, WindowNames.JumpPowerbar]); } if (bindings.Probe.Enabled) @@ -173,6 +178,7 @@ public sealed class RetailUiRuntime : IDisposable public ToolbarController? ToolbarController { get; private set; } public ToolbarInputController? ToolbarInputController { get; private set; } public CombatUiController? CombatUiController { get; private set; } + public JumpPowerbarController? JumpPowerbarController { get; private set; } public SelectedObjectController? SelectedObjectController { get; private set; } public UiViewport? PaperdollViewportWidget { get; private set; } public UiNineSlicePanel? InventoryFrame { get; private set; } @@ -193,6 +199,7 @@ public sealed class RetailUiRuntime : IDisposable public void Tick(double deltaSeconds) { + JumpPowerbarController?.Tick(); SelectedObjectController?.Tick(deltaSeconds); Host.Tick(deltaSeconds); _automation?.Tick(deltaSeconds); @@ -520,6 +527,65 @@ public sealed class RetailUiRuntime : IDisposable Console.WriteLine("[M2] retail combat bar from gmCombatUI LayoutDesc 0x21000073."); } + private void MountJumpPowerbar() + { + ElementInfo? info; + ImportedLayout? layout; + lock (_bindings.Assets.DatLock) + { + info = LayoutImporter.ImportInfos( + _bindings.Assets.Dats, JumpPowerbarController.LayoutId); + layout = info is null ? null : LayoutImporter.Build( + info, + _bindings.Assets.ResolveSprite, + _bindings.Assets.DefaultFont, + _bindings.Assets.ResolveFont); + } + if (layout is null) + { + Console.WriteLine("[D.6] jump powerbar: LayoutDesc 0x21000072 not found."); + return; + } + + JumpPowerbarController? controller = Layout.JumpPowerbarController.Bind( + layout, + _bindings.JumpPowerbar.Snapshot, + visible => + { + if (visible) Host.ShowWindow(WindowNames.JumpPowerbar); + else Host.HideWindow(WindowNames.JumpPowerbar); + }); + if (controller is null) + { + Console.WriteLine("[D.6] jump powerbar: required JumpMode meter missing in LayoutDesc 0x21000072."); + return; + } + + JumpPowerbarController = controller; + UiElement root = layout.Root; + RetailWindowFrame.Mount( + Host.Root, + root, + _bindings.Assets.ResolveSprite, + new RetailWindowFrame.Options + { + WindowName = WindowNames.JumpPowerbar, + Chrome = RetailWindowChrome.Imported, + Left = Math.Max(0f, (Host.Root.Width - root.Width) * 0.5f), + Top = Math.Max(0f, Host.Root.Height - root.Height - 105f), + Visible = false, + DatConstraintSource = info, + Resizable = true, + ResizeX = true, + ResizeY = true, + ConstrainDragToParent = true, + ContentClickThrough = false, + Controller = controller, + }); + controller.SyncVisibility(); + Console.WriteLine("[D.6] retail jump bar from gmFloatyPowerBarUI LayoutDesc 0x21000072."); + } + private (uint[] Peace, uint[] War, uint[]? Empty) LoadToolbarDigits() { uint[]? peace = null, war = null, empty = null; diff --git a/src/AcDream.App/UI/UiMeter.cs b/src/AcDream.App/UI/UiMeter.cs index cf1ccb31..d4aac0cc 100644 --- a/src/AcDream.App/UI/UiMeter.cs +++ b/src/AcDream.App/UI/UiMeter.cs @@ -14,8 +14,10 @@ namespace AcDream.App.UI; /// retail's vitals are bars exactly like this, just sprited. /// /// -public sealed class UiMeter : UiElement +public sealed class UiMeter : UiElement, IUiDatStateful { + private readonly Dictionary _stateFillSprites = new(); + /// Dat element id, set by the layout importer so duplicated page copies can be scoped. public uint ElementId { get; set; } @@ -57,6 +59,32 @@ public sealed class UiMeter : UiElement /// Coloured-fill right-cap RenderSurface id. public uint FrontRight { get; set; } + /// The active numeric DAT state for a stateful fill meter. + public uint ActiveRetailStateId { get; private set; } + + /// + /// Registers a fill sprite carried by a child state of the imported meter. + /// Used by the powerbar shape in LayoutDesc 0x21000072, whose track is on + /// the meter and whose Jump/Melee/Missile/DDD fills are states of one child. + /// + internal void ConfigureStateFill(uint stateId, uint spriteId) + { + if (stateId != 0 && spriteId != 0) + _stateFillSprites[stateId] = spriteId; + } + + public bool TrySetRetailState(uint stateId) + { + if (!_stateFillSprites.TryGetValue(stateId, out uint spriteId)) + return false; + + ActiveRetailStateId = stateId; + FrontLeft = 0; + FrontTile = spriteId; + FrontRight = 0; + return true; + } + /// Vertical orientation (retail m_eDirection 2/4). Default false = /// horizontal (direction 1). The burden bar is the only vertical meter today. public bool Vertical { get; set; } diff --git a/src/AcDream.App/UI/WindowNames.cs b/src/AcDream.App/UI/WindowNames.cs index df65a9b6..28dd52bb 100644 --- a/src/AcDream.App/UI/WindowNames.cs +++ b/src/AcDream.App/UI/WindowNames.cs @@ -11,4 +11,5 @@ public static class WindowNames public const string Chat = "chat"; public const string Radar = "radar"; public const string Combat = "combat"; + public const string JumpPowerbar = "jump-powerbar"; } diff --git a/tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs b/tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs index 884ff7af..1919c87c 100644 --- a/tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs @@ -313,6 +313,37 @@ public class DatWidgetFactoryTests Assert.Equal(0u, m.FrontRight); } + [Fact] + public void BuildMeter_StatefulSingleImageShape_MapsNumericFillStatesAndHidesDirectRange() + { + const uint TrackFile = 0x06004D0Bu; + const uint JumpState = 0x10000042u; + const uint JumpFile = 0x06001354u; + const uint RangeFile = 0x0600715Eu; + + var meter = new ElementInfo { Type = 7, Id = 0x10000034u, Width = 600, Height = 15 }; + meter.StateMedia[""] = (TrackFile, 1); + + var fills = new ElementInfo { Type = 3, Id = 2u, ReadOrder = 2 }; + fills.States[JumpState] = new UiStateInfo { Id = JumpState, Name = "JumpMode" }; + fills.StateMedia["JumpMode"] = (JumpFile, 1); + meter.Children.Add(fills); + + var range = new ElementInfo { Type = 3, Id = 0x100005EEu, ReadOrder = 1 }; + range.StateMedia[""] = (RangeFile, 1); + meter.Children.Add(range); + + var result = Assert.IsType(DatWidgetFactory.Create(meter, NoTex, null)); + + Assert.Equal(TrackFile, result.BackTile); + Assert.Equal(0u, result.FrontTile); + Assert.True(result.TrySetRetailState(JumpState)); + Assert.Equal(JumpState, result.ActiveRetailStateId); + Assert.Equal(JumpFile, result.FrontTile); + Assert.NotEqual(RangeFile, result.BackTile); + Assert.NotEqual(RangeFile, result.FrontTile); + } + // ── Test 6: Meter slice extraction (the important one) ─────────────────── /// diff --git a/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs b/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs index 3feb8042..57fac03a 100644 --- a/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs +++ b/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs @@ -95,6 +95,12 @@ public static class FixtureLoader public static ElementInfo LoadCombatInfos() => LoadInfos("combat_21000073.json"); + public static ImportedLayout LoadPowerbar() + => LayoutImporter.Build(LoadPowerbarInfos(), _ => (0u, 0, 0), null); + + public static ElementInfo LoadPowerbarInfos() + => LoadInfos("powerbar_21000072.json"); + // ── Shared loader ──────────────────────────────────────────────────────── private static AcDream.App.UI.Layout.ElementInfo LoadInfos(string fileName) diff --git a/tests/AcDream.App.Tests/UI/Layout/JumpPowerbarControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/JumpPowerbarControllerTests.cs new file mode 100644 index 00000000..500c6eed --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/JumpPowerbarControllerTests.cs @@ -0,0 +1,73 @@ +using AcDream.App.Input; +using AcDream.App.UI; +using AcDream.App.UI.Layout; + +namespace AcDream.App.Tests.UI.Layout; + +public sealed class JumpPowerbarControllerTests +{ + private const uint JumpFill = 0x06001354u; + + [Fact] + public void MovementCharge_ShowsUpdatesAndHidesAuthoredJumpMeter() + { + JumpChargeSnapshot state = default; + ImportedLayout layout = BuildLayout(); + var visibility = new List(); + using var controller = JumpPowerbarController.Bind( + layout, () => state, visibility.Add)!; + var meter = Assert.IsType(layout.FindElement(JumpPowerbarController.MeterId)); + + Assert.Equal([false], visibility); + Assert.Equal(0f, meter.Fill()); + Assert.Equal(JumpPowerbarController.JumpModeState, meter.ActiveRetailStateId); + Assert.Equal(JumpFill, meter.FrontTile); + + state = new JumpChargeSnapshot(true, 0.35f); + controller.Tick(); + Assert.Equal([false, true], visibility); + Assert.Equal(0.35f, meter.Fill()); + + state = new JumpChargeSnapshot(true, 1.4f); + controller.Tick(); + Assert.Equal([false, true], visibility); + Assert.Equal(1f, meter.Fill()); + + state = default; + controller.Tick(); + Assert.Equal([false, true, false], visibility); + Assert.Equal(0f, meter.Fill()); + } + + [Fact] + public void Bind_RejectsLayoutWithoutJumpModeMeter() + { + var root = new UiPanel(); + var meter = new UiMeter { ElementId = JumpPowerbarController.MeterId }; + var layout = new ImportedLayout(root, new Dictionary + { + [JumpPowerbarController.MeterId] = meter, + }); + + Assert.Null(JumpPowerbarController.Bind(layout, () => default, _ => { })); + } + + private static ImportedLayout BuildLayout() + { + var root = new UiPanel { Width = 610, Height = 25 }; + var meter = new UiMeter + { + ElementId = JumpPowerbarController.MeterId, + Left = 5, + Top = 5, + Width = 600, + Height = 15, + }; + meter.ConfigureStateFill(JumpPowerbarController.JumpModeState, JumpFill); + root.AddChild(meter); + return new ImportedLayout(root, new Dictionary + { + [JumpPowerbarController.MeterId] = meter, + }); + } +} diff --git a/tests/AcDream.App.Tests/UI/Layout/JumpPowerbarLayoutConformanceTests.cs b/tests/AcDream.App.Tests/UI/Layout/JumpPowerbarLayoutConformanceTests.cs new file mode 100644 index 00000000..641fa9f9 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/JumpPowerbarLayoutConformanceTests.cs @@ -0,0 +1,33 @@ +using AcDream.App.UI; +using AcDream.App.UI.Layout; + +namespace AcDream.App.Tests.UI.Layout; + +public sealed class JumpPowerbarLayoutConformanceTests +{ + [Fact] + public void RetailFixture_UsesExactFloatyGeometryTrackAndJumpFill() + { + ElementInfo info = FixtureLoader.LoadPowerbarInfos(); + ImportedLayout layout = LayoutImporter.Build(info, _ => (0u, 0, 0), null); + var meter = Assert.IsType( + layout.FindElement(JumpPowerbarController.MeterId)); + + Assert.Equal((610f, 25f), (layout.Root.Width, layout.Root.Height)); + Assert.Equal((5f, 5f, 600f, 15f), + (meter.Left, meter.Top, meter.Width, meter.Height)); + Assert.Equal(0x06004D0Bu, meter.BackTile); + Assert.True(meter.TrySetRetailState(JumpPowerbarController.JumpModeState)); + Assert.Equal(0x06001354u, meter.FrontTile); + + Assert.Equal((0f, 0f, 300f, 15f), + UiMeter.ComputeFillRect(0.5f, meter.Width, meter.Height)); + + Assert.True(info.TryGetEffectiveInteger(0x3Fu, out int minWidth)); + Assert.True(info.TryGetEffectiveInteger(0x3Du, out int maxWidth)); + Assert.True(info.TryGetEffectiveInteger(0x3Eu, out int minHeight)); + Assert.True(info.TryGetEffectiveInteger(0x3Cu, out int maxHeight)); + Assert.Equal((50, 810, 25, 25), + (minWidth, maxWidth, minHeight, maxHeight)); + } +} diff --git a/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs b/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs index b3c7d5f3..1f463317 100644 --- a/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs +++ b/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs @@ -21,6 +21,7 @@ public sealed class RetailLayoutFixtureGenerator (0x21000024u, "paperdoll_21000024.json"), (0x2100002Eu, "character_2100002E.json"), (0x2100006Cu, "vitals_2100006C.json"), + (0x21000072u, "powerbar_21000072.json"), (0x21000073u, "combat_21000073.json"), (0x21000074u, "radar_21000074.json"), }; diff --git a/tests/AcDream.App.Tests/UI/Layout/fixtures/powerbar_21000072.json b/tests/AcDream.App.Tests/UI/Layout/fixtures/powerbar_21000072.json new file mode 100644 index 00000000..561ea4a1 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/fixtures/powerbar_21000072.json @@ -0,0 +1,2467 @@ +{ + "Id": 268437010, + "Type": 268435539, + "X": 0, + "Y": 0, + "Width": 610, + "Height": 25, + "OriginalParentWidth": 0, + "OriginalParentHeight": 0, + "HasOriginalParentSize": false, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 0, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": true, + "IncorporationFlags": 31, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "60": { + "Kind": 4, + "MasterPropertyId": 60, + "UnsignedValue": 0, + "IntegerValue": 25, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "61": { + "Kind": 4, + "MasterPropertyId": 61, + "UnsignedValue": 0, + "IntegerValue": 810, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "62": { + "Kind": 4, + "MasterPropertyId": 62, + "UnsignedValue": 0, + "IntegerValue": 25, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "63": { + "Kind": 4, + "MasterPropertyId": 63, + "UnsignedValue": 0, + "IntegerValue": 50, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "268435522": { + "Id": 268435522, + "Name": "JumpMode", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435523": { + "Id": 268435523, + "Name": "MeleeMode", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435524": { + "Id": 268435524, + "Name": "MissileMode", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435525": { + "Id": 268435525, + "Name": "DDDMode", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435508, + "Type": 7, + "X": 5, + "Y": 5, + "Width": 600, + "Height": 15, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 17, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100683019, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "111": { + "Kind": 0, + "MasterPropertyId": 111, + "UnsignedValue": 1, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "104": { + "Kind": 1, + "MasterPropertyId": 104, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "106": { + "Kind": 1, + "MasterPropertyId": 106, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "268435522": { + "Id": 268435522, + "Name": "JumpMode", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435523": { + "Id": 268435523, + "Name": "MeleeMode", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435524": { + "Id": 268435524, + "Name": "MissileMode", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435525": { + "Id": 268435525, + "Name": "DDDMode", + "PassToChildren": true, + "IncorporationFlags": 1, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100683019, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435509, + "Type": 12, + "X": 0, + "Y": 0, + "Width": 600, + "Height": 15, + "OriginalParentWidth": 600, + "OriginalParentHeight": 15, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "26": { + "Kind": 7, + "MasterPropertyId": 26, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 2, + "MasterPropertyId": 24, + "UnsignedValue": 1073741826, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 255, + "Green": 255, + "Red": 255, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 1, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "21": { + "Kind": 0, + "MasterPropertyId": 21, + "UnsignedValue": 1, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "13": { + "Id": 13, + "Name": "Ghosted", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "27": { + "Kind": 7, + "MasterPropertyId": 27, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [ + { + "Kind": 6, + "MasterPropertyId": 25, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 76, + "Green": 76, + "Red": 76, + "Alpha": 255 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + ], + "StructValue": {} + } + } + } + }, + "268435522": { + "Id": 268435522, + "Name": "JumpMode", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 86621556, + "TableId": 587202561, + "Override": 0, + "English": 1, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 3, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "268435523": { + "Id": 268435523, + "Name": "MeleeMode", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 261968564, + "TableId": 587202561, + "Override": 0, + "English": 1, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 3, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "268435524": { + "Id": 268435524, + "Name": "MissileMode", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 175542084, + "TableId": 587202561, + "Override": 0, + "English": 1, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 3, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + }, + "268435525": { + "Id": 268435525, + "Name": "DDDMode", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 5, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + }, + "20": { + "Kind": 0, + "MasterPropertyId": 20, + "UnsignedValue": 2, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741826, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 2, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 600, + "Height": 15, + "OriginalParentWidth": 600, + "OriginalParentHeight": 15, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435522": { + "Id": 268435522, + "Name": "JumpMode", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100668244, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435523": { + "Id": 268435523, + "Name": "MeleeMode", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100667904, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435524": { + "Id": 268435524, + "Name": "MissileMode", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100667904, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "268435525": { + "Id": 268435525, + "Name": "DDDMode", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100669787, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "JumpMode": { + "Item1": 100668244, + "Item2": 1 + }, + "MeleeMode": { + "Item1": 100667904, + "Item2": 1 + }, + "MissileMode": { + "Item1": 100667904, + "Item2": 1 + }, + "DDDMode": { + "Item1": 100669787, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268436974, + "Type": 3, + "X": 60, + "Y": 0, + "Width": 480, + "Height": 15, + "OriginalParentWidth": 600, + "OriginalParentHeight": 15, + "HasOriginalParentSize": true, + "Left": 4, + "Top": 1, + "Right": 4, + "Bottom": 1, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100692318, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100692318, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + }, + { + "Id": 268437123, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 2, + "Bottom": 2, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100693187, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100693187, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437124, + "Type": 3, + "X": 5, + "Y": 0, + "Width": 600, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100693183, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100693183, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437125, + "Type": 3, + "X": 605, + "Y": 0, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100693188, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100693188, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437126, + "Type": 3, + "X": 0, + "Y": 5, + "Width": 5, + "Height": 15, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 2, + "Bottom": 1, + "ReadOrder": 4, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100693184, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100693184, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437127, + "Type": 3, + "X": 0, + "Y": 20, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 2, + "Bottom": 1, + "ReadOrder": 5, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100693189, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100693189, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437128, + "Type": 3, + "X": 5, + "Y": 20, + "Width": 600, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 6, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100693185, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100693185, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437129, + "Type": 3, + "X": 605, + "Y": 20, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 7, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100693190, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100693190, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437130, + "Type": 3, + "X": 605, + "Y": 5, + "Width": 5, + "Height": 15, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 8, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100693186, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "59": { + "Kind": 1, + "MasterPropertyId": 59, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100693186, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437131, + "Type": 9, + "X": 0, + "Y": 0, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 2, + "Bottom": 2, + "ReadOrder": 9, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688169, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "43": { + "Kind": 1, + "MasterPropertyId": 43, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688169, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437132, + "Type": 2, + "X": 5, + "Y": 0, + "Width": 600, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 10, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688170, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688153, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688170, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688153, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437133, + "Type": 9, + "X": 605, + "Y": 0, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 11, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688169, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "44": { + "Kind": 1, + "MasterPropertyId": 44, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688169, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437134, + "Type": 9, + "X": 0, + "Y": 5, + "Width": 5, + "Height": 15, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 2, + "Bottom": 1, + "ReadOrder": 12, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688171, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "43": { + "Kind": 1, + "MasterPropertyId": 43, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688171, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437135, + "Type": 9, + "X": 0, + "Y": 20, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 2, + "Bottom": 1, + "ReadOrder": 13, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688169, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "43": { + "Kind": 1, + "MasterPropertyId": 43, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688169, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437136, + "Type": 2, + "X": 5, + "Y": 20, + "Width": 600, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 14, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688172, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688153, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688172, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688153, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437137, + "Type": 9, + "X": 605, + "Y": 20, + "Width": 5, + "Height": 5, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 2, + "Right": 1, + "Bottom": 1, + "ReadOrder": 15, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688169, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "44": { + "Kind": 1, + "MasterPropertyId": 44, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688169, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268437138, + "Type": 9, + "X": 605, + "Y": 5, + "Width": 5, + "Height": 15, + "OriginalParentWidth": 610, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 16, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100688173, + "DrawMode": 1 + }, + "Cursor": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + }, + "Properties": { + "Values": { + "44": { + "Kind": 1, + "MasterPropertyId": 44, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": true, + "StringInfoValue": { + "Token": 0, + "StringId": 0, + "TableId": 0, + "Override": 0, + "English": 0, + "Comment": 0 + }, + "ColorValue": { + "Blue": 0, + "Green": 0, + "Red": 0, + "Alpha": 0 + }, + "VectorValue": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "ArrayValue": [], + "StructValue": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100688173, + "Item2": 1 + } + }, + "StateCursors": { + "": { + "File": 100688168, + "HotspotX": 16, + "HotspotY": 16, + "IsValid": true + } + }, + "DefaultStateName": "", + "Children": [] + } + ] +} \ No newline at end of file diff --git a/tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs b/tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs index 1dd41b37..b61ec5ad 100644 --- a/tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs +++ b/tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs @@ -293,6 +293,24 @@ public class PlayerMovementControllerTests Assert.True(controller.VerticalVelocity > 0f); } + [Fact] + public void JumpChargeSnapshot_TracksHeldChargeAndResetsOnRelease() + { + var engine = MakeFlatEngine(); + var controller = new PlayerMovementController(engine); + controller.SetPosition(new Vector3(96f, 96f, 50f), 0x0001); + + Assert.Equal(default, controller.JumpCharge); + + controller.Update(0.25f, new MovementInput(Jump: true)); + Assert.True(controller.JumpCharge.IsCharging); + Assert.Equal(0.25f, controller.JumpCharge.Power, precision: 3); + + controller.Update(0.016f, new MovementInput(Jump: false)); + Assert.False(controller.JumpCharge.IsCharging); + Assert.Equal(0f, controller.JumpCharge.Power); + } + [Fact] public void Update_AirborneFrames_ZRiseThenFalls() {