diff --git a/docs/ISSUES.md b/docs/ISSUES.md index dcb72441..93a426b6 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -61,6 +61,12 @@ back to inert generic DAT elements. `gmUIElement_EffectsIndicator`. The retained controller consequently had no live bindings for session packet age, Vitae, or the player load ratio and did not dispatch Character Information or the shared logout confirmation path. +The strip and its actions are now ported. Link Status (panel 8), both effect +pages (4/5), Vitae (15), and the authored Mini Game shell (9) are registered as +children of the same retained main-panel owner as Inventory/Character/Magic, +so switching pages preserves one canonical position. Link ping RTT and Vitae +recovery XP are live; transport packet-loss averaging and Mini Game gameplay +remain the explicitly narrowed AP-110 residual. **Files:** `src/AcDream.App/UI/Layout/IndicatorBarController.cs`; `src/AcDream.App/UI/Layout/DatWidgetFactory.cs`; @@ -74,6 +80,8 @@ quality follows the 5/20/40-second thresholds and bad-link flash cadence; helpful, harmful, Vitae, and burden react to live state; the burden icon opens Character Information; effects open their matching panels; crossed swords use the shared confirmation dialog and close gracefully only after acceptance. +Every non-ghosted detail icon opens its authored page in the shared main-panel +position; closing a restore-previous page returns to the deferred ordinary page. --- diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md index e0ff8525..31fb7525 100644 --- a/docs/architecture/retail-divergence-register.md +++ b/docs/architecture/retail-divergence-register.md @@ -207,7 +207,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps. | ~~AP-107~~ | **RETIRED 2026-07-11 (Wave 3.3 / #197)** — typed `OfferPrimaryClick` returns `NotActive`, `ConsumedSuccess`, or `ConsumedRejected`; every retained item surface plus radar/world offers active target mode before local selection/open/use fallback. Rejections are consumed and cannot drift selection. | `src/AcDream.App/UI/ItemInteractionController.cs`; inventory/paperdoll/toolbar/radar/world call sites | — | — | `UIElement_ItemList::HandleTargetedUseLeftClick @ 0x004E24D0` | | AP-108 | Paperdoll/AutoWield still omit full `AutoWieldIsLegal`/dual-wield rules, double-click examine/drag from the doll, body-part selection lighting, and retail's synchronous `" - cannot unwield the %s"` failure suffix (the current send seam reports rejection asynchronously). **Primary replacement retired from this row 2026-07-14:** inventory activation and paperdoll drops share the confirmed blocker transaction, preserve explicit slot intent, relocate an already-worn item to an explicitly selected compatible slot, and emit retail's successful move-to-backpack status. **Aetheria retired 2026-07-13.** | `src/AcDream.App/UI/Layout/PaperdollController.cs`; `src/AcDream.App/UI/AutoWieldController.cs` | Basic equip slots, Aetheria, and live doll work; primary weapon, incompatible shield, and mismatched ammo blockers sequence through server-confirmed dequip→wield in both peace and war | Remaining illegal/off-hand cases, asynchronous dequip rejection wording, doll examine/drag, and selection lighting still differ functionally | `CPlayerSystem::AutoWield @ 0x00560A60`; `ACCWeenieObject::BlocksUseOfShield @ 0x0055D3E0`; `gmPaperDollUI @ 0x004A3590..0x004A5F90` | | AP-109 | Character Titles page is inert and live displayed-title/luminance state is absent | `src/AcDream.App/UI/Layout/CharacterStatController.cs`; `CharacterSheetProvider.cs` | Attributes/skills core output is user-accepted | Titles cannot be selected/displayed and level-200 luminance fields are missing | `gmCharacterTitleUI @ 0x0049A610`; `gmStatManagementUI::UpdateExperience @ 0x004F0A70` | -| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, residual social/examine/floating chat, quests/map/vitae/options/smartbox, vendor/trade/salvage/tinkering, and D.6 nameplates/floaters | D.5/D.6 roadmap; retained layout registration set | Basic combat plus M3 spellbook/component/effects/favorite-spell surfaces now cover the active melee/missile/magic loops; later waves land each remaining surface against authoritative state | Large portions of retail gameplay still have no production UI outside the current combat/magic/inventory/character loops | Named `gm*UI::PostInit` methods and LayoutDesc catalog | +| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, residual social/examine/floating chat, quests/map/options/smartbox, vendor/trade/salvage/tinkering, mini-game gameplay, Link Status NAK/retransmission packet-loss averaging, and D.6 nameplates/floaters | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.Core.Net/LinkStatusSnapshot.cs`; D.5/D.6 roadmap | Basic combat plus M3 spellbook/component/effects/favorite-spell/Link/Vitae surfaces now cover the active melee/missile/magic loops; the Link page has exact ping RTT but reports the current transport's zero default until its missing reliability statistics land | Large portions of retail gameplay still have no production UI; real packet loss is displayed as 0.00% instead of retail's moving average | Named `gm*UI::PostInit` methods; `CLinkStatusAverages::GetAveragePacketLoss @ 0x00546610`; LayoutDesc catalog | | ~~AP-111~~ | **RETIRED 2026-07-11 (M2 held-object parenting)** — equipped hand items are no longer omitted from the render world. CreateObject now preserves Placement/Parent/position timestamp bootstrap; live `0xF749` ParentEvent is parsed with retail sequence freshness; a focused render controller resolves `Setup.HoldingLocations`, applies the child's placement frame, and recomposes the separate child entity after every parent animation tick. Pickup retains the weenie's visual metadata for a later wield. | `src/AcDream.Core.Net/Messages/{CreateObject,ParentEvent}.cs`; `src/AcDream.Core/Meshing/EquippedChildAttachment.cs`; `src/AcDream.App/Rendering/EquippedChildRenderController.cs` | — | — | `ClientCombatSystem::GetDefaultCombatMode @ 0x0056B310`; `SmartBox::HandleParentEvent @ 0x004535D0`; `CPhysicsObj::set_parent @ 0x00515A90`; `CPhysicsObj::UpdateChild @ 0x00512D50` | | AP-112 | The basic combat bar ports visibility, height selection, desired-power slider, exact 1.0/0.8-second charge, ready-stance gating, request/release, `MaybeStopCompletely`, server-response queueing, and auto-repeat, but still omits `StartAttackRequest`'s `FinishJump` call and exact trained-Recklessness visibility semantics (IA-20 keeps the dark range as the accepted baseline) | `src/AcDream.App/Combat/CombatAttackController.cs`; `src/AcDream.App/UI/Layout/CombatUiController.cs` | The shared player movement owner now performs retail's server-control-gated full stop and movement report before an attack build; the remaining seams require the jump owner and a distinct Recklessness treatment | Starting an attack while charging a jump may not finish that jump exactly when retail does; trained/untrained Recklessness presentation is identical | `ClientCombatSystem::StartAttackRequest @ 0x0056C040`; `CommandInterpreter::MaybeStopCompletely @ 0x006B3B90`; `gmCombatUI::ListenToElementMessage @ 0x004CC430` | | AP-113 | Invalid lifestone-command arguments display the local text `Usage: /lifestone`; retail definitely emits a local usage/error line but Binary Ninja misidentifies the referenced wide-string address, so its exact wording is not yet recovered | `src/AcDream.UI.Abstractions/Panels/Chat/ChatCommandRouter.cs`; `RetailClientCommandCatalog.cs` | The behavior boundary is exact (handled locally, no chat and no game action); only a low-impact diagnostic sentence differs | `/ls now` can show different wording/color from retail while still refusing the invalid request correctly | `ClientCommunicationSystem::DoLifestone @ 0x0056FC70` | diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 1fbb9b07..ca94d438 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -1,6 +1,6 @@ # acdream — strategic roadmap -**Status:** Living document. Updated 2026-07-17. **M3 active; automated implementation complete, visual gates pending.** M2's connected demo loop is complete. M3 now has the retail cast-intent/component/target lifecycle, exact live enchantment wire state, scoped Magic input, retained spell bar, spellbook, component book, positive/negative effects panels, the complete authored seven-control gameplay indicator strip, and shared `gmPanelUI` switching over the Step 9 projectile/PhysicsScript/particle/portal foundation. The remaining M3 work is the connected single-client magic-UI/casting gate and final two-client portal-out/materialization observer gate. +**Status:** Living document. Updated 2026-07-17. **M3 active; automated implementation complete, visual gates pending.** M2's connected demo loop is complete. M3 now has the retail cast-intent/component/target lifecycle, exact live enchantment wire state, scoped Magic input, retained spell bar, spellbook, component book, positive/negative effects panels, the complete authored seven-control gameplay indicator strip and its Link/Vitae detail pages, and shared `gmPanelUI` switching over the Step 9 projectile/PhysicsScript/particle/portal foundation. The remaining M3 work is the connected single-client magic-UI/casting gate and final two-client portal-out/materialization observer gate. **Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file. --- @@ -22,7 +22,9 @@ active enchantment, then recall through the DAT-driven portal presentation. equipped caster endowment, scoped retail keys, spellbook/component book, positive/negative active-enchantment panels, the seven-control link/effect/ Vitae/burden/mini-game/session indicator strip, and the retail - one-active-main-panel lifecycle. Connected-gate + one-active-main-panel lifecycle. Link Status, Helpful/Harmful, Vitae, and + the inactive Mini Game shell now occupy that same canonical main-panel + position; Link RTT and Vitae recovery XP are live. Connected-gate correction now also pins type-12 spellbook tabs, retail zero text margins, and `PassToChildren` Closed/Open chrome through real-DAT fixtures. 3. **Pending user visual gate:** connected projectile/self-buff casts plus the diff --git a/docs/plans/2026-05-12-milestones.md b/docs/plans/2026-05-12-milestones.md index e949cc0c..c89e2b3c 100644 --- a/docs/plans/2026-05-12-milestones.md +++ b/docs/plans/2026-05-12-milestones.md @@ -714,6 +714,11 @@ the burden/effect/logout actions use the shared panel/dialog owners. Main-panel visibility now flows through retail's one-active `gmPanelUI` owner. M3 remains active only for the connected single-client magic UI/casting visual gate and the final two-client portal observer gate. +The 2026-07-17 indicator follow-up also mounts the authored Link Status, Vitae, +and Mini Game detail roots as children of that same main-panel owner. Helpful, +Harmful, Link, and Vitae therefore replace Inventory/Character/Magic in one +canonical window position. Link uses retail's payload-free ping/RTT cadence; +Vitae uses PropertyInt 129/139 and the retail XP-pool threshold formula. The first connected gate exposed and corrected two retained-import defects: spellbook tabs are authored stateful text controls (not buttons), and retail text starts with zero margins. Real-DAT fixtures now pin spellbook binding, diff --git a/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md b/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md index b4dc6dc1..72ac82e2 100644 --- a/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md +++ b/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md @@ -25,6 +25,17 @@ Named retail functions: - `gmUIElement_BurdenIndicator::Update @ 0x004E6CE0` - `LinkStatusHolder::GetConnectionStatus @ 0x00411380` - `LinkStatusHolder::OnHeartbeat @ 0x004113D0` +- `gmPanelUI::SetupChildren @ 0x004BC9E0` +- `gmPanelUI::RecvNotice_SetPanelVisibility @ 0x004BC6F0` +- `gmLinkStatusUI::PostInit @ 0x004AADB0` +- `gmLinkStatusUI::Update @ 0x004AAEA0` +- `gmLinkStatusUI::ListenToElementMessage @ 0x004AB0B0` +- `gmLinkStatusUI::ListenToGlobalMessage @ 0x004AB110` +- `gmLinkStatusUI::RecvNotice_Ping @ 0x004AB150` +- `gmVitaeUI::PostInit @ 0x004A7240` +- `gmVitaeUI::Update @ 0x004A7400` +- `VitaeSystem::VitaeCPPoolThreshold @ 0x005C8FD0` +- `CM_Character::Event_RequestPing @ 0x006A19A0` - `gmEffectsUI::SpellEffectMatchesUIType @ 0x004B76C0` - `gmGamePlayUI::RecvNotice_EndCharacterSession @ 0x004EBEA0` @@ -40,6 +51,11 @@ The installed retail LayoutDesc establishes the fixed child order and art: | 105 | `0x100000F3` | `0x10000004` | mini-game | | 125 | `0x100000FA` | ordinary button | end character session | +`gmPanelUI::SetupChildren` reads enum property `0x10000029` from each +registered child. The installed main-panel LayoutDesc resolves the indicator +detail pages to Link Status `8`, Mini Game `9`, and Vitae `15` (Helpful and +Harmful remain `4` and `5`). They are not independent floating-window ids. + ## Pseudocode ### Link indicator @@ -82,6 +98,46 @@ acdream records the last successfully decoded server datagram in keeps socket/session facts out of the UI while preserving retail's thresholds and cadence. +### Link Status detail page + +```text +On shown: + register for time notices + pleaseRequestPing = true + Update() + +On hidden: + unregister time notices + pleaseRequestPing = false + +On time notice while visible: + if now >= nextUpdate: + nextUpdate = now + 5 seconds + Update() + +Update: + replace text with localized connection explanation + 5/20-second legend + append 10-second packet-loss percentage with two decimal places + append ping in milliseconds, or "????" before a response + if pleaseRequestPing or now - lastPingRequest >= 120 seconds: + lastPingRequest = now + Event_RequestPing() + pleaseRequestPing = false + +Event_RequestPing: + send F7B1, game-action sequence, opcode 0x01E9 + // no payload + +On empty 0x01EA PingResponse: + pingRoundTrip = monotonicNow - lastPingRequest +``` + +The network session owns the send timestamp and response sample. The retained +page owns only retail's display and refresh cadence. acdream's transport does +not yet expose the retail NAK/retransmission moving averages, so packet-loss +averaging remains the explicit AP-110 residual rather than being guessed from +ordinary packet sequence gaps. + ### Helpful and harmful effects ```text @@ -109,6 +165,25 @@ On PlayerDesc or VitaeChanged: SetState(Ghosted = 13) ``` +The detail page uses the same modifier: + +```text +lostPercent = 100 - int(vitae * 100) +if lostPercent <= 0: + show localized full-strength sentence +else: + vitaePool = player PropertyInt 129 + deathLevel = player PropertyInt 139, falling back to Level (25) + threshold = int(((deathLevel ^ 2.5 * 2.5) + 20) + * vitae ^ 5 + 0.5) + remaining = threshold - vitaePool + show localized lost-percent sentence and remaining-XP sentence +``` + +The x87 powers omitted from the named pseudo-C output for +`VitaeCPPoolThreshold` were cross-checked against the official ACE +`Player_Xp.cs` port. The property ids and rounding expression agree. + ### Burden / character information ```text @@ -136,6 +211,28 @@ The authored ghosted state is rendered now. The notices remain owned by the still-unported mini-game subsystem (existing divergence AP-110), so this task does not manufacture a game-active state from unrelated packets. +The authored `gmMiniGameUI` root `0x1000016A` is mounted and registered as +main panel `9`, including its close behavior. It remains unreachable while the +indicator is Ghosted; board state and game actions remain with the unported +mini-game subsystem. + +### Shared main-panel ownership + +```text +For every toolbar or indicator detail page: + register page with gmPanelUI panel id + when shown: + hide the current child + move the requested child to the shared main-panel placement + show only the requested child + when a page with bool property 0x10000049 closes: + restore the deferred ordinary child +``` + +Helpful/Harmful, Link Status, Vitae, and the Mini Game shell therefore share +the same retained host position as Inventory, Character, and Magic. They do +not remember independent positions. + ### End character session ```text @@ -159,8 +256,8 @@ CharacterLogOff and transport Disconnect packets. `EncumbranceSystem::Load @ 0x004FCC40`. - The existing enchantment parser preserves the separate Mult, Add, Vitae, and Cooldown registry buckets used by the retail indicator queries. -- ACE/reference source is not present in this worktree. No server behavior is - inferred for the strip: state is derived only from packets already parsed by - the client, and the named retail client remains the behavior oracle. -- Full Link Status, Vitae, and mini-game panels remain tracked by AP-110. This - port does not replace them with an ad-hoc panel. +- Official ACE was cross-checked for the empty PingResponse and the vitae-pool + threshold expression; the named retail client remains the behavior oracle. +- The Link Status and Vitae pages now use their authored DAT roots and localized + strings. AP-110 retains only packet-loss averaging and mini-game gameplay for + this slice; neither is replaced with an ad-hoc approximation. diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 3589acc9..8ac731d4 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -2320,6 +2320,7 @@ public sealed class GameWindow : IDisposable () => _liveSession?.LinkStatus ?? AcDream.Core.Net.LinkStatusSnapshot.Disconnected, ClientTimerNow, + () => _liveSession?.RequestLinkStatusPing(), () => _window?.Close()), Toolbar: new AcDream.App.UI.ToolbarRuntimeBindings( Objects, diff --git a/src/AcDream.App/UI/Layout/IndicatorBarController.cs b/src/AcDream.App/UI/Layout/IndicatorBarController.cs index ff5ab440..713e87f5 100644 --- a/src/AcDream.App/UI/Layout/IndicatorBarController.cs +++ b/src/AcDream.App/UI/Layout/IndicatorBarController.cs @@ -85,7 +85,10 @@ public sealed class IndicatorBarController : IRetainedPanelController _helpful.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.PositiveEffects); _harmful.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.NegativeEffects); + _link.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.LinkStatus); + _vitae.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.Vitae); _burden.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.Character); + _miniGame.OnClick = () => bindings.TogglePanel(RetailPanelCatalog.MiniGame); _endCharacterSession.OnClick = bindings.RequestEndCharacterSession; bindings.Spellbook.EnchantmentsChanged += UpdateEnchantments; diff --git a/src/AcDream.App/UI/Layout/IndicatorDetailText.cs b/src/AcDream.App/UI/Layout/IndicatorDetailText.cs new file mode 100644 index 00000000..fc613209 --- /dev/null +++ b/src/AcDream.App/UI/Layout/IndicatorDetailText.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace AcDream.App.UI.Layout; + +/// +/// Shared text shaping for retail indicator detail pages. Retail +/// UIElement_Text::SetText reflows newline-delimited strings into the +/// authored field width; acdream's retained text seam carries already-shaped +/// lines, so controllers perform the equivalent shaping before publication. +/// +internal static class IndicatorDetailText +{ + public static IReadOnlyList Shape(UiText target, string text) + { + ArgumentNullException.ThrowIfNull(target); + text ??= string.Empty; + + float maxWidth = Math.Max(1f, target.Width - (2f * target.Padding)); + float Measure(string value) + => target.DatFont?.MeasureWidth(value) + ?? target.Font?.MeasureWidth(value) + ?? value.Length * 8f; + + var lines = new List(); + string normalized = text.Replace("\\n", "\n", StringComparison.Ordinal); + foreach (string paragraph in normalized.Split('\n')) + { + if (paragraph.Length == 0) + { + lines.Add(new UiText.Line(string.Empty, target.DefaultColor)); + continue; + } + + foreach (string line in ChatWindowController.WrapText(paragraph, maxWidth, Measure)) + lines.Add(new UiText.Line(line, target.DefaultColor)); + } + return lines; + } +} diff --git a/src/AcDream.App/UI/Layout/LinkStatusUiController.cs b/src/AcDream.App/UI/Layout/LinkStatusUiController.cs new file mode 100644 index 00000000..4d461fc1 --- /dev/null +++ b/src/AcDream.App/UI/Layout/LinkStatusUiController.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using AcDream.Core.Net; + +namespace AcDream.App.UI.Layout; + +/// +/// Retained port of retail gmLinkStatusUI. LayoutDesc owns the page and +/// chrome; this controller owns its five-second text refresh and 120-second +/// ping cadence. +/// +public sealed class LinkStatusUiController : IRetainedPanelController +{ + public const uint LayoutId = 0x2100001Du; + public const uint RootId = 0x10000167u; + public const uint MainTextId = 0x10000169u; + public const uint CloseId = 0x100000FCu; + + private const double UpdateIntervalSeconds = 5d; + private const double PingIntervalSeconds = 120d; + + private readonly UiText _mainText; + private readonly UiButton? _close; + private readonly Func _snapshot; + private readonly Func _currentTime; + private readonly Action _requestPing; + private readonly LinkStatusStrings _strings; + private IReadOnlyList _lines = Array.Empty(); + private double _nextUpdateTime = -1d; + private double _lastPingRequestTime = -1d; + private bool _pleaseRequestPing; + private bool _visible; + + private LinkStatusUiController( + ImportedLayout layout, + Func snapshot, + Func currentTime, + Action requestPing, + LinkStatusStrings strings, + Action? close) + { + _mainText = (UiText)layout.FindElement(MainTextId)!; + _close = layout.FindElement(CloseId) as UiButton; + _snapshot = snapshot; + _currentTime = currentTime; + _requestPing = requestPing; + _strings = strings; + _mainText.LinesProvider = () => _lines; + if (_close is not null) _close.OnClick = close; + } + + public static LinkStatusUiController? Bind( + ImportedLayout layout, + Func snapshot, + Func currentTime, + Action requestPing, + LinkStatusStrings strings, + Action? close = null) + { + ArgumentNullException.ThrowIfNull(layout); + ArgumentNullException.ThrowIfNull(snapshot); + ArgumentNullException.ThrowIfNull(currentTime); + ArgumentNullException.ThrowIfNull(requestPing); + ArgumentNullException.ThrowIfNull(strings); + return layout.FindElement(MainTextId) is UiText + ? new LinkStatusUiController( + layout, snapshot, currentTime, requestPing, strings, close) + : null; + } + + /// + /// Port of gmLinkStatusUI::ListenToGlobalMessage @ 0x004AB110. + /// + public void Tick() + { + if (!_visible) return; + double now = _currentTime(); + if (!double.IsFinite(now) || now < _nextUpdateTime) return; + _nextUpdateTime = now + UpdateIntervalSeconds; + Update(now); + } + + public void OnShown() + { + _visible = true; + _pleaseRequestPing = true; + Update(_currentTime()); + } + + public void OnHidden() + { + _visible = false; + _pleaseRequestPing = false; + } + + private void Update(double now) + { + LinkStatusSnapshot value = _snapshot(); + string ping = value.RoundTripSeconds is double seconds + && double.IsFinite(seconds) + && seconds >= 0d + ? (seconds * 1000d).ToString("F0", CultureInfo.CurrentCulture) + : "????"; + string body = _strings.Description + + _strings.Legend + + _strings.DisconnectWarning + + _strings.PacketLossPrefix + + value.PacketLossPercentage.ToString("F2", CultureInfo.CurrentCulture) + + _strings.PingPrefix + + ping; + _lines = IndicatorDetailText.Shape(_mainText, body); + + if (!double.IsFinite(now)) return; + bool periodicPing = _lastPingRequestTime >= 0d + && now - _lastPingRequestTime >= PingIntervalSeconds; + if (!_pleaseRequestPing && !periodicPing) return; + _lastPingRequestTime = now; + _pleaseRequestPing = false; + _requestPing(); + } + + public void Dispose() + { + if (_close is not null) _close.OnClick = null; + } +} + +public sealed record LinkStatusStrings( + string Description, + string Legend, + string DisconnectWarning, + string PacketLossPrefix, + string PingPrefix) +{ + public static LinkStatusStrings English { get; } = new( + "The Link Indicator shows the current status of your connection to the game servers.", + "\n\nGREEN = your link is good.\nYELLOW = no packets for at least 5 sec.\nRED = no packets for at least 20 sec.", + "\n\nIf approximately forty seconds pass without receiving a packet, you will be disconnected from the server.", + "\n\n\nPacket loss for the last 10 sec: ", + "\n\nRoundtrip Ping time to Server: "); +} diff --git a/src/AcDream.App/UI/Layout/MiniGameUiController.cs b/src/AcDream.App/UI/Layout/MiniGameUiController.cs new file mode 100644 index 00000000..6fcda3f3 --- /dev/null +++ b/src/AcDream.App/UI/Layout/MiniGameUiController.cs @@ -0,0 +1,35 @@ +using System; + +namespace AcDream.App.UI.Layout; + +/// +/// Authored shell for retail gmMiniGameUI. The indicator remains +/// ghosted until a future game-state owner calls SetMiniGameActive; +/// mounting the page now keeps its window lifecycle on the shared retail host. +/// +public sealed class MiniGameUiController : IRetainedPanelController +{ + public const uint LayoutId = 0x2100001Eu; + public const uint RootId = 0x1000016Au; + public const uint CloseId = 0x1000016Bu; + + private readonly UiButton _close; + + private MiniGameUiController(UiButton close, Action? closeWindow) + { + _close = close; + _close.OnClick = closeWindow; + } + + public static MiniGameUiController? Bind( + ImportedLayout layout, + Action? close = null) + { + ArgumentNullException.ThrowIfNull(layout); + return layout.FindElement(CloseId) is UiButton button + ? new MiniGameUiController(button, close) + : null; + } + + public void Dispose() => _close.OnClick = null; +} diff --git a/src/AcDream.App/UI/Layout/VitaeUiController.cs b/src/AcDream.App/UI/Layout/VitaeUiController.cs new file mode 100644 index 00000000..e647848b --- /dev/null +++ b/src/AcDream.App/UI/Layout/VitaeUiController.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AcDream.Core.Items; +using AcDream.Core.Spells; + +namespace AcDream.App.UI.Layout; + +/// +/// Retained port of retail gmVitaeUI. It presents the active vitae +/// modifier and the authoritative experience pool needed to regain one percent. +/// +public sealed class VitaeUiController : IRetainedPanelController +{ + public const uint LayoutId = 0x21000020u; + public const uint RootId = 0x100001C1u; + public const uint MainTextId = 0x100001C3u; + public const uint CloseId = 0x100000FCu; + + internal const uint VitaeCpPoolProperty = 0x81u; + internal const uint DeathLevelProperty = 0x8Bu; + internal const uint LevelProperty = 0x19u; + + private readonly Spellbook _spellbook; + private readonly ClientObjectTable _objects; + private readonly Func _playerGuid; + private readonly VitaeStrings _strings; + private readonly UiText _mainText; + private readonly UiButton? _close; + private IReadOnlyList _lines = Array.Empty(); + private bool _disposed; + + private VitaeUiController( + ImportedLayout layout, + Spellbook spellbook, + ClientObjectTable objects, + Func playerGuid, + VitaeStrings strings, + Action? close) + { + _spellbook = spellbook; + _objects = objects; + _playerGuid = playerGuid; + _strings = strings; + _mainText = (UiText)layout.FindElement(MainTextId)!; + _close = layout.FindElement(CloseId) as UiButton; + _mainText.LinesProvider = () => _lines; + if (_close is not null) _close.OnClick = close; + + _spellbook.EnchantmentsChanged += Update; + _objects.ObjectAdded += OnObjectChanged; + _objects.ObjectUpdated += OnObjectChanged; + _objects.Cleared += Update; + Update(); + } + + public static VitaeUiController? Bind( + ImportedLayout layout, + Spellbook spellbook, + ClientObjectTable objects, + Func playerGuid, + VitaeStrings strings, + Action? close = null) + { + ArgumentNullException.ThrowIfNull(layout); + ArgumentNullException.ThrowIfNull(spellbook); + ArgumentNullException.ThrowIfNull(objects); + ArgumentNullException.ThrowIfNull(playerGuid); + ArgumentNullException.ThrowIfNull(strings); + return layout.FindElement(MainTextId) is UiText + ? new VitaeUiController( + layout, spellbook, objects, playerGuid, strings, close) + : null; + } + + public void OnShown() => Update(); + + private void OnObjectChanged(ClientObject item) + { + if (item.ObjectId == _playerGuid()) Update(); + } + + private void Update() + { + float vitae = CurrentVitae(); + int lostPercent = 100 - (int)(vitae * 100f); + string body; + if (lostPercent <= 0) + { + body = _strings.FullStrength; + } + else + { + ClientObject? player = _objects.Get(_playerGuid()); + int pool = player?.Properties.GetInt(VitaeCpPoolProperty) ?? 0; + int level = 0; + if (player is not null) + { + level = player.Properties.Ints.TryGetValue( + DeathLevelProperty, out int deathLevel) + ? deathLevel + : player.Properties.GetInt(LevelProperty); + } + int remaining = VitaeCpPoolThreshold(vitae, level) - pool; + body = _strings.LostPrefix + + lostPercent + + _strings.LostSuffix + + _strings.RecoveryPrefix + + remaining + + _strings.RecoverySuffix; + } + _lines = IndicatorDetailText.Shape(_mainText, body); + } + + private float CurrentVitae() + => _spellbook.ActiveEnchantmentSnapshot + .Where(record => record.Bucket == 4u) + .Select(record => record.StatModValue) + .OfType() + .Where(float.IsFinite) + .DefaultIfEmpty(1f) + .Last(); + + /// + /// Retail VitaeSystem::VitaeCPPoolThreshold @ 0x005C8FD0, with + /// the x87 expression cross-checked against ACE's Player_Xp port. + /// + internal static int VitaeCpPoolThreshold(float vitae, int level) + => (int)(((Math.Pow(level, 2.5d) * 2.5d) + 20d) + * Math.Pow(vitae, 5d) + + 0.5d); + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _spellbook.EnchantmentsChanged -= Update; + _objects.ObjectAdded -= OnObjectChanged; + _objects.ObjectUpdated -= OnObjectChanged; + _objects.Cleared -= Update; + if (_close is not null) _close.OnClick = null; + } +} + +public sealed record VitaeStrings( + string FullStrength, + string LostPrefix, + string LostSuffix, + string RecoveryPrefix, + string RecoverySuffix) +{ + public static VitaeStrings English { get; } = new( + "Your Vitae, or life force, is at full strength.", + "Due to your recent death, you have temporarily lost ", + " % of your Vitae, or life force.", + "\n\nYou will regain 1% of your Vitae once you earn ", + " more experience."); +} diff --git a/src/AcDream.App/UI/RetailPanelCatalog.cs b/src/AcDream.App/UI/RetailPanelCatalog.cs index 4df67e17..b2577b15 100644 --- a/src/AcDream.App/UI/RetailPanelCatalog.cs +++ b/src/AcDream.App/UI/RetailPanelCatalog.cs @@ -10,16 +10,22 @@ public static class RetailPanelCatalog public const uint PositiveEffects = 4u; public const uint NegativeEffects = 5u; public const uint Inventory = 7u; + public const uint LinkStatus = 8u; + public const uint MiniGame = 9u; public const uint Character = 11u; public const uint Magic = 13u; + public const uint Vitae = 15u; private static readonly (uint PanelId, string WindowName)[] Mounted = { (PositiveEffects, WindowNames.PositiveEffects), (NegativeEffects, WindowNames.NegativeEffects), (Inventory, WindowNames.Inventory), + (LinkStatus, WindowNames.LinkStatus), + (MiniGame, WindowNames.MiniGame), (Character, WindowNames.Character), (Magic, WindowNames.Spellbook), + (Vitae, WindowNames.Vitae), }; private static readonly (uint PanelId, string WindowName)[] Toolbar = diff --git a/src/AcDream.App/UI/RetailUiRuntime.cs b/src/AcDream.App/UI/RetailUiRuntime.cs index 2ab6d167..fcf8e198 100644 --- a/src/AcDream.App/UI/RetailUiRuntime.cs +++ b/src/AcDream.App/UI/RetailUiRuntime.cs @@ -81,6 +81,7 @@ public sealed record IndicatorRuntimeBindings( Func Strength, Func LinkStatus, Func CurrentTime, + Action RequestLinkStatusPing, Action EndCharacterSession); public sealed record ToolbarRuntimeBindings( @@ -196,6 +197,7 @@ public sealed class RetailUiRuntime : IDisposable MountCombat(); MountSpellbook(); MountEffects(); + MountIndicatorDetailPanels(); MountIndicators(); MountJumpPowerbar(); MountDialogFactory(); @@ -245,6 +247,9 @@ public sealed class RetailUiRuntime : IDisposable public SpellbookWindowController? SpellbookWindowController { get; private set; } public EffectsUiController? PositiveEffectsController { get; private set; } public EffectsUiController? NegativeEffectsController { get; private set; } + public LinkStatusUiController? LinkStatusUiController { get; private set; } + public VitaeUiController? VitaeUiController { get; private set; } + public MiniGameUiController? MiniGameUiController { get; private set; } public IndicatorBarController? IndicatorBarController { get; private set; } public JumpPowerbarController? JumpPowerbarController { get; private set; } public RetailFpsController? FpsController { get; private set; } @@ -274,6 +279,7 @@ public sealed class RetailUiRuntime : IDisposable SpellcastingUiController?.Tick(); PositiveEffectsController?.Tick(); NegativeEffectsController?.Tick(); + LinkStatusUiController?.Tick(); IndicatorBarController?.Tick(); JumpPowerbarController?.Tick(); SelectedObjectController?.Tick(deltaSeconds); @@ -880,7 +886,7 @@ public sealed class RetailUiRuntime : IDisposable if (positive) PositiveEffectsController = controller; else NegativeEffectsController = controller; UiElement root = layout.Root; - RetailWindowFrame.Mount( + RetailWindowHandle handle = RetailWindowFrame.Mount( Host.Root, root, _bindings.Assets.ResolveSprite, @@ -898,10 +904,184 @@ public sealed class RetailUiRuntime : IDisposable ContentClickThrough = false, Controller = controller, }); - _panelUi.Register( + _panelUi.RegisterMainPanel( positive ? RetailPanelCatalog.PositiveEffects : RetailPanelCatalog.NegativeEffects, positive ? WindowNames.PositiveEffects : WindowNames.NegativeEffects, - rootInfo); + handle, + rootInfo.TryGetEffectiveBool( + RetailPanelUiController.RestorePreviousPropertyId, + out bool restorePrevious) + && restorePrevious); + } + + private void MountIndicatorDetailPanels() + { + MountLinkStatusPanel(); + MountVitaePanel(); + MountMiniGamePanel(); + } + + private void MountLinkStatusPanel() + { + ElementInfo? rootInfo; + ImportedLayout? layout; + LinkStatusStrings strings; + lock (_bindings.Assets.DatLock) + { + rootInfo = LayoutImporter.ImportInfos( + _bindings.Assets.Dats, + LinkStatusUiController.LayoutId, + LinkStatusUiController.RootId); + var resolver = new DatStringResolver(_bindings.Assets.Dats); + layout = rootInfo is null + ? null + : LayoutImporter.Build( + rootInfo, + _bindings.Assets.ResolveSprite, + _bindings.Assets.DefaultFont, + _bindings.Assets.ResolveFont, + resolver.Resolve); + LinkStatusStrings fallback = LinkStatusStrings.English; + strings = new LinkStatusStrings( + resolver.Resolve(0x23000001u, 0x0D632F7Fu) ?? fallback.Description, + resolver.Resolve(0x23000001u, 0x038FABF3u) ?? fallback.Legend, + resolver.Resolve(0x23000001u, 0x05481084u) ?? fallback.DisconnectWarning, + resolver.Resolve(0x23000001u, 0x0CADBA93u) ?? fallback.PacketLossPrefix, + resolver.Resolve(0x23000001u, 0x0D6485F7u) ?? fallback.PingPrefix); + } + if (rootInfo is null || layout is null) return; + + LinkStatusUiController? controller = Layout.LinkStatusUiController.Bind( + layout, + _bindings.Indicators.LinkStatus, + _bindings.Indicators.CurrentTime, + _bindings.Indicators.RequestLinkStatusPing, + strings, + () => CloseWindow(WindowNames.LinkStatus)); + if (controller is null) return; + LinkStatusUiController = controller; + RegisterIndicatorDetailPanel( + RetailPanelCatalog.LinkStatus, + WindowNames.LinkStatus, + rootInfo, + layout.Root, + controller); + } + + private void MountVitaePanel() + { + ElementInfo? rootInfo; + ImportedLayout? layout; + VitaeStrings strings; + lock (_bindings.Assets.DatLock) + { + rootInfo = LayoutImporter.ImportInfos( + _bindings.Assets.Dats, + VitaeUiController.LayoutId, + VitaeUiController.RootId); + var resolver = new DatStringResolver(_bindings.Assets.Dats); + layout = rootInfo is null + ? null + : LayoutImporter.Build( + rootInfo, + _bindings.Assets.ResolveSprite, + _bindings.Assets.DefaultFont, + _bindings.Assets.ResolveFont, + resolver.Resolve); + VitaeStrings fallback = VitaeStrings.English; + strings = new VitaeStrings( + resolver.Resolve(0x23000001u, 0x0142DDECu) ?? fallback.FullStrength, + resolver.Resolve(0x23000001u, 0x045D7665u, 0) ?? fallback.LostPrefix, + resolver.Resolve(0x23000001u, 0x045D7665u, 1) ?? fallback.LostSuffix, + resolver.Resolve(0x23000001u, 0x0DCD8C35u, 0) ?? fallback.RecoveryPrefix, + resolver.Resolve(0x23000001u, 0x0DCD8C35u, 1) ?? fallback.RecoverySuffix); + } + if (rootInfo is null || layout is null) return; + + VitaeUiController? controller = Layout.VitaeUiController.Bind( + layout, + _bindings.Indicators.Spellbook, + _bindings.Indicators.Objects, + _bindings.Indicators.PlayerGuid, + strings, + () => CloseWindow(WindowNames.Vitae)); + if (controller is null) return; + VitaeUiController = controller; + RegisterIndicatorDetailPanel( + RetailPanelCatalog.Vitae, + WindowNames.Vitae, + rootInfo, + layout.Root, + controller); + } + + private void MountMiniGamePanel() + { + ElementInfo? rootInfo; + ImportedLayout? layout; + lock (_bindings.Assets.DatLock) + { + rootInfo = LayoutImporter.ImportInfos( + _bindings.Assets.Dats, + MiniGameUiController.LayoutId, + MiniGameUiController.RootId); + layout = rootInfo is null + ? null + : LayoutImporter.Build( + rootInfo, + _bindings.Assets.ResolveSprite, + _bindings.Assets.DefaultFont, + _bindings.Assets.ResolveFont, + new DatStringResolver(_bindings.Assets.Dats).Resolve); + } + if (rootInfo is null || layout is null) return; + + MiniGameUiController? controller = Layout.MiniGameUiController.Bind( + layout, + () => CloseWindow(WindowNames.MiniGame)); + if (controller is null) return; + MiniGameUiController = controller; + RegisterIndicatorDetailPanel( + RetailPanelCatalog.MiniGame, + WindowNames.MiniGame, + rootInfo, + layout.Root, + controller); + } + + private void RegisterIndicatorDetailPanel( + uint panelId, + string windowName, + ElementInfo rootInfo, + UiElement root, + IRetainedPanelController controller) + { + RetailWindowHandle handle = RetailWindowFrame.Mount( + Host.Root, + root, + _bindings.Assets.ResolveSprite, + new RetailWindowFrame.Options + { + WindowName = windowName, + Chrome = RetailWindowChrome.Imported, + Left = 18f, + Top = 18f, + Visible = false, + Resizable = false, + ResizeX = false, + ResizeY = false, + ConstrainDragToParent = true, + ContentClickThrough = false, + Controller = controller, + }); + _panelUi.RegisterMainPanel( + panelId, + windowName, + handle, + rootInfo.TryGetEffectiveBool( + RetailPanelUiController.RestorePreviousPropertyId, + out bool restorePrevious) + && restorePrevious); } private void MountIndicators() diff --git a/src/AcDream.App/UI/WindowNames.cs b/src/AcDream.App/UI/WindowNames.cs index 792cc4ad..21069b3e 100644 --- a/src/AcDream.App/UI/WindowNames.cs +++ b/src/AcDream.App/UI/WindowNames.cs @@ -16,4 +16,7 @@ public static class WindowNames public const string Indicators = "indicators"; public const string PositiveEffects = "effects-positive"; public const string NegativeEffects = "effects-negative"; + public const string LinkStatus = "link-status"; + public const string MiniGame = "mini-game"; + public const string Vitae = "vitae"; } diff --git a/src/AcDream.Core.Net/LinkStatusSnapshot.cs b/src/AcDream.Core.Net/LinkStatusSnapshot.cs index 9322c5c5..1d46cde3 100644 --- a/src/AcDream.Core.Net/LinkStatusSnapshot.cs +++ b/src/AcDream.Core.Net/LinkStatusSnapshot.cs @@ -7,7 +7,9 @@ namespace AcDream.Core.Net; /// public readonly record struct LinkStatusSnapshot( bool Connected, - double SecondsSinceLastPacket) + double SecondsSinceLastPacket, + double PacketLossPercentage = 0d, + double? RoundTripSeconds = null) { public static LinkStatusSnapshot Disconnected => new(false, 0d); } diff --git a/src/AcDream.Core.Net/Messages/GameEvents.cs b/src/AcDream.Core.Net/Messages/GameEvents.cs index 223a5d22..ac05cf2f 100644 --- a/src/AcDream.Core.Net/Messages/GameEvents.cs +++ b/src/AcDream.Core.Net/Messages/GameEvents.cs @@ -149,12 +149,9 @@ public static class GameEvents // ── Pings / misc ──────────────────────────────────────────────────────── - /// 0x01EA PingResponse: echoes the client ping id. - public static uint? ParsePingResponse(ReadOnlySpan payload) - { - if (payload.Length < 4) return null; - return BinaryPrimitives.ReadUInt32LittleEndian(payload); - } + /// 0x01EA PingResponse has no payload; receipt is the acknowledgement. + public static bool ParsePingResponse(ReadOnlySpan payload) + => payload.IsEmpty; // ── Spells / magic ────────────────────────────────────────────────────── diff --git a/src/AcDream.Core.Net/Messages/SocialActions.cs b/src/AcDream.Core.Net/Messages/SocialActions.cs index 01c0a854..09ce74cf 100644 --- a/src/AcDream.Core.Net/Messages/SocialActions.cs +++ b/src/AcDream.Core.Net/Messages/SocialActions.cs @@ -30,7 +30,7 @@ public static class SocialActions // Queries public const uint QueryHealthOpcode = 0x01BFu; // u32 targetGuid public const uint QueryItemManaOpcode = 0x0263u; // u32 itemGuid; zero cancels - public const uint PingRequestOpcode = 0x01E9u; // u32 clientId + public const uint PingRequestOpcode = 0x01E9u; // no payload // Fellowship public const uint FellowshipCreateOpcode = 0x00A2u; // string16L name, bool openness, bool shareXP @@ -71,14 +71,16 @@ public static class SocialActions return body; } - /// Keepalive ping — server echoes with PingResponse (0x01EA). - public static byte[] BuildPingRequest(uint seq, uint clientId) + /// + /// Request the empty PingResponse (0x01EA). Retail + /// CM_Character::Event_RequestPing @ 0x006A19A0 sends no payload. + /// + public static byte[] BuildPingRequest(uint seq) { - byte[] body = new byte[16]; + byte[] body = new byte[12]; BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope); BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq); BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), PingRequestOpcode); - BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), clientId); return body; } diff --git a/src/AcDream.Core.Net/WorldSession.cs b/src/AcDream.Core.Net/WorldSession.cs index e8fd9bae..b5e27ad8 100644 --- a/src/AcDream.Core.Net/WorldSession.cs +++ b/src/AcDream.Core.Net/WorldSession.cs @@ -529,20 +529,49 @@ public sealed class WorldSession : IDisposable CurrentState, Volatile.Read(ref _lastInboundPacketTicks), Stopwatch.GetTimestamp(), - Stopwatch.Frequency); + Stopwatch.Frequency, + PingRoundTripSeconds); + + internal double? PingRoundTripSeconds + { + get + { + double value = BitConverter.Int64BitsToDouble( + Volatile.Read(ref _lastPingRoundTripBits)); + return double.IsFinite(value) && value >= 0d ? value : null; + } + } internal static LinkStatusSnapshot BuildLinkStatus( State state, long lastInboundPacketTicks, long nowTicks, - long frequency) + long frequency, + double? roundTripSeconds = null, + double packetLossPercentage = 0d) { bool connected = state is not State.Disconnected and not State.Failed; if (!connected || frequency <= 0) return LinkStatusSnapshot.Disconnected; long elapsed = Math.Max(0, nowTicks - lastInboundPacketTicks); - return new LinkStatusSnapshot(true, elapsed / (double)frequency); + return new LinkStatusSnapshot( + true, + elapsed / (double)frequency, + packetLossPercentage, + roundTripSeconds); + } + + private void RecordPingResponse(long nowTicks) + { + long requestTicks = Interlocked.Exchange(ref _lastPingRequestTicks, 0L); + if (requestTicks <= 0L || nowTicks < requestTicks || Stopwatch.Frequency <= 0) + return; + + double elapsed = (nowTicks - requestTicks) / (double)Stopwatch.Frequency; + Volatile.Write( + ref _lastPingRoundTripBits, + BitConverter.DoubleToInt64Bits(elapsed)); } /// Movement sequence counters for outbound MoveToState/AutonomousPosition. @@ -574,6 +603,8 @@ public sealed class WorldSession : IDisposable private readonly NetClient _net; private long _lastInboundPacketTicks = Stopwatch.GetTimestamp(); + private long _lastPingRequestTicks; + private long _lastPingRoundTripBits = BitConverter.DoubleToInt64Bits(double.NaN); private readonly IPEndPoint _loginEndpoint; private readonly IPEndPoint _connectEndpoint; private readonly FragmentAssembler _assembler = new(); @@ -656,6 +687,11 @@ public sealed class WorldSession : IDisposable var parsed = SetTurbineChatChannels.TryParse(e.Payload.Span); if (parsed is not null) TurbineChannelsReceived?.Invoke(parsed.Value); }); + GameEvents.Register(GameEventType.PingResponse, e => + { + if (Messages.GameEvents.ParsePingResponse(e.Payload.Span)) + RecordPingResponse(Stopwatch.GetTimestamp()); + }); } /// @@ -1701,6 +1737,18 @@ public sealed class WorldSession : IDisposable SendGameAction(body); } + /// + /// Request the round-trip sample displayed by retail gmLinkStatusUI. + /// The response is an empty 0x01EA GameEvent, so the session retains the + /// monotonic send timestamp rather than putting an invented id on the wire. + /// + public void RequestLinkStatusPing() + { + Volatile.Write(ref _lastPingRequestTicks, Stopwatch.GetTimestamp()); + uint seq = NextGameActionSequence(); + SendGameAction(SocialActions.BuildPingRequest(seq)); + } + /// Send retail TargetedMeleeAttack (0x0008). public void SendMeleeAttack(uint targetGuid, AttackHeight attackHeight, float powerLevel) { diff --git a/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs b/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs index 3f5087bb..5a18205b 100644 --- a/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs +++ b/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs @@ -143,6 +143,24 @@ public static class FixtureLoader public static ElementInfo LoadIndicatorsInfos() => LoadInfos("indicators_21000071.json"); + public static ImportedLayout LoadLinkStatus() + => LayoutImporter.Build(LoadLinkStatusInfos(), _ => (0u, 0, 0), null); + + public static ElementInfo LoadLinkStatusInfos() + => LoadInfos("link_status_2100001D.json"); + + public static ImportedLayout LoadVitae() + => LayoutImporter.Build(LoadVitaeInfos(), _ => (0u, 0, 0), null); + + public static ElementInfo LoadVitaeInfos() + => LoadInfos("vitae_21000020.json"); + + public static ImportedLayout LoadMiniGame() + => LayoutImporter.Build(LoadMiniGameInfos(), _ => (0u, 0, 0), null); + + public static ElementInfo LoadMiniGameInfos() + => LoadInfos("mini_game_2100001E.json"); + // ── Shared loader ──────────────────────────────────────────────────────── private static AcDream.App.UI.Layout.ElementInfo LoadInfos(string fileName) diff --git a/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs index e83d8919..caf419a7 100644 --- a/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/IndicatorBarControllerTests.cs @@ -67,6 +67,8 @@ public sealed class IndicatorBarControllerTests 50u, 3u, 60f, 1u, StatModValue: 0.99f, Bucket: 4u)); Assert.True(vitae.Enabled); Assert.Equal(UiButtonStateMachine.Normal, vitae.ActiveRetailStateId); + vitae.OnEvent(new UiEvent(0, vitae, UiEventType.Click)); + Assert.Equal(RetailPanelCatalog.Vitae, h.ToggledPanels[^1]); h.Spellbook.OnEnchantmentRemoved(3u, 50u); Assert.False(vitae.Enabled); @@ -164,6 +166,8 @@ public sealed class IndicatorBarControllerTests Assert.False(miniGame.Enabled); controller.SetMiniGameActive(true); Assert.True(miniGame.Enabled); + miniGame.OnEvent(new UiEvent(0, miniGame, UiEventType.Click)); + Assert.Equal(RetailPanelCatalog.MiniGame, h.ToggledPanels[^1]); controller.SetMiniGameActive(false); Assert.False(miniGame.Enabled); @@ -171,6 +175,18 @@ public sealed class IndicatorBarControllerTests Assert.Equal(1, h.EndSessionRequests); } + [Fact] + public void LinkButton_DispatchesSharedLinkStatusPanel() + { + var h = CreateHarness(); + using IndicatorBarController controller = h.Controller; + UiButton link = h.Button(IndicatorBarController.LinkButtonId); + + link.OnEvent(new UiEvent(0, link, UiEventType.Click)); + + Assert.Equal([RetailPanelCatalog.LinkStatus], h.ToggledPanels); + } + private static Harness CreateHarness(int strength = 10) { var table = SpellTable.LoadFromReader(new StringReader( diff --git a/tests/AcDream.App.Tests/UI/Layout/IndicatorDetailPanelControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/IndicatorDetailPanelControllerTests.cs new file mode 100644 index 00000000..424bc5f7 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/IndicatorDetailPanelControllerTests.cs @@ -0,0 +1,166 @@ +using AcDream.App.UI; +using AcDream.App.UI.Layout; +using AcDream.Core.Items; +using AcDream.Core.Net; +using AcDream.Core.Spells; + +namespace AcDream.App.Tests.UI.Layout; + +public sealed class IndicatorDetailPanelControllerTests +{ + private const uint Player = 0x50000001u; + + [Fact] + public void AuthoredFixtures_AreRetailMainPanelPages() + { + AssertPage( + FixtureLoader.LoadLinkStatusInfos(), + LinkStatusUiController.RootId, + 0x1000001Du, + LinkStatusUiController.MainTextId, + LinkStatusUiController.CloseId); + AssertPage( + FixtureLoader.LoadVitaeInfos(), + VitaeUiController.RootId, + 0x10000020u, + VitaeUiController.MainTextId, + VitaeUiController.CloseId); + AssertPage( + FixtureLoader.LoadMiniGameInfos(), + MiniGameUiController.RootId, + 0x1000001Eu, + 0x1000016Eu, + MiniGameUiController.CloseId); + + Assert.Equal(8u, RetailPanelCatalog.LinkStatus); + Assert.Equal(9u, RetailPanelCatalog.MiniGame); + Assert.Equal(15u, RetailPanelCatalog.Vitae); + } + + [Fact] + public void LinkStatus_RequestsPingOnShow_RefreshesText_AndRepeatsAfter120Seconds() + { + ImportedLayout layout = FixtureLoader.LoadLinkStatus(); + double now = 0d; + int pings = 0; + int closes = 0; + LinkStatusSnapshot snapshot = new( + Connected: true, + SecondsSinceLastPacket: 0.25d, + PacketLossPercentage: 1.25d, + RoundTripSeconds: 0.123d); + using LinkStatusUiController controller = LinkStatusUiController.Bind( + layout, + () => snapshot, + () => now, + () => pings++, + LinkStatusStrings.English, + () => closes++)!; + + controller.OnShown(); + + Assert.Equal(1, pings); + string text = VisibleText(layout, LinkStatusUiController.MainTextId); + Assert.Contains("Link Indicator", text, StringComparison.Ordinal); + Assert.Contains("1.25", text, StringComparison.Ordinal); + Assert.Contains("123", text, StringComparison.Ordinal); + + controller.Tick(); + now = 119d; + controller.Tick(); + Assert.Equal(1, pings); + now = 124d; + controller.Tick(); + Assert.Equal(2, pings); + + UiButton close = Assert.IsType(layout.FindElement(LinkStatusUiController.CloseId)); + close.OnEvent(new UiEvent(0, close, UiEventType.Click)); + Assert.Equal(1, closes); + + controller.OnHidden(); + now = 300d; + controller.Tick(); + Assert.Equal(2, pings); + } + + [Fact] + public void Vitae_UsesDeathLevelPoolAndRetailThresholdFormula() + { + ImportedLayout layout = FixtureLoader.LoadVitae(); + var spellbook = new Spellbook(); + var objects = new ClientObjectTable(); + objects.AddOrUpdate(new ClientObject { ObjectId = Player }); + objects.UpdateIntProperty(Player, VitaeUiController.DeathLevelProperty, 10); + objects.UpdateIntProperty(Player, VitaeUiController.VitaeCpPoolProperty, 100); + using VitaeUiController controller = VitaeUiController.Bind( + layout, + spellbook, + objects, + () => Player, + VitaeStrings.English)!; + + Assert.Contains( + "full strength", + VisibleText(layout, VitaeUiController.MainTextId), + StringComparison.Ordinal); + + spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord( + SpellId: 666u, + LayerId: 1u, + Duration: -1f, + CasterGuid: Player, + StatModValue: 0.9f, + Bucket: 4u)); + + string text = VisibleText(layout, VitaeUiController.MainTextId); + Assert.Contains("lost 10 %", text, StringComparison.Ordinal); + Assert.Contains("earn 379 more experience", text, StringComparison.Ordinal); + Assert.Equal(479, VitaeUiController.VitaeCpPoolThreshold(0.9f, 10)); + } + + [Fact] + public void MiniGameFixture_BindsItsAuthoredCloseButton() + { + ImportedLayout layout = FixtureLoader.LoadMiniGame(); + int closes = 0; + using MiniGameUiController controller = MiniGameUiController.Bind( + layout, () => closes++)!; + + UiButton close = Assert.IsType(layout.FindElement(MiniGameUiController.CloseId)); + close.OnEvent(new UiEvent(0, close, UiEventType.Click)); + + Assert.Equal(1, closes); + } + + private static void AssertPage( + ElementInfo info, + uint rootId, + uint type, + uint contentId, + uint closeId) + { + Assert.Equal(rootId, info.Id); + Assert.Equal(type, info.Type); + Assert.Equal((300f, 362f), (info.Width, info.Height)); + Assert.NotNull(Find(info, contentId)); + Assert.NotNull(Find(info, closeId)); + Assert.True(info.TryGetEffectiveBool( + RetailPanelUiController.RestorePreviousPropertyId, + out bool restorePrevious)); + Assert.True(restorePrevious); + } + + private static string VisibleText(ImportedLayout layout, uint id) + { + UiText text = Assert.IsType(layout.FindElement(id)); + return string.Join(' ', text.LinesProvider().Select(line => line.Text)); + } + + private static ElementInfo? Find(ElementInfo root, uint id) + { + if (root.Id == id) return root; + foreach (ElementInfo child in root.Children) + if (Find(child, id) is { } found) return found; + return null; + } +} diff --git a/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs b/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs index 4ed18528..aa33afa5 100644 --- a/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs +++ b/tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs @@ -18,6 +18,8 @@ public sealed class RetailLayoutFixtureGenerator { (0x21000006u, "chat_21000006.json"), (0x21000016u, "toolbar_21000016.json"), + (0x2100001Du, "link_status_2100001D.json"), + (0x21000020u, "vitae_21000020.json"), (0x21000023u, "inventory_21000023.json"), (0x21000024u, "paperdoll_21000024.json"), (0x2100002Eu, "character_2100002E.json"), @@ -118,6 +120,17 @@ public sealed class RetailLayoutFixtureGenerator File.WriteAllText(Path.Combine(FixtureDirectory(), fileName), effectsJson); } + ElementInfo? miniGame = LayoutImporter.ImportInfos( + dats, MiniGameUiController.LayoutId, MiniGameUiController.RootId); + Assert.NotNull(miniGame); + File.WriteAllText( + Path.Combine(FixtureDirectory(), "mini_game_2100001E.json"), + JsonSerializer.Serialize(miniGame, new JsonSerializerOptions + { + IncludeFields = true, + WriteIndented = true, + })); + foreach ((uint rootId, string fileName) in new[] { (0x10000466u, diff --git a/tests/AcDream.App.Tests/UI/Layout/RetailPanelUiControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/RetailPanelUiControllerTests.cs index 62114556..23bd81c9 100644 --- a/tests/AcDream.App.Tests/UI/Layout/RetailPanelUiControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/RetailPanelUiControllerTests.cs @@ -113,7 +113,7 @@ public sealed class RetailPanelUiControllerTests } [Fact] - public void RestorePreviousEffect_DoesNotReplaceMainPanelPlacement() + public void IndicatorDetailPanel_SharesPlacementAndRestoresPreviousPanel() { var root = new UiRoot { Width = 1280f, Height = 720f }; RetailWindowHandle inventory = Mount(root, WindowNames.Inventory, 40f, 60f); @@ -122,13 +122,17 @@ public sealed class RetailPanelUiControllerTests using var controller = Create(root); controller.RegisterMainPanel(7u, WindowNames.Inventory, inventory); controller.RegisterMainPanel(11u, WindowNames.Character, character); - controller.Register(4u, WindowNames.PositiveEffects, restorePrevious: true); + controller.RegisterMainPanel( + 4u, + WindowNames.PositiveEffects, + effect, + restorePrevious: true); controller.SetPanelVisibility(7u, visible: true); inventory.MoveTo(280f, 120f); controller.SetPanelVisibility(4u, visible: true); - Assert.Equal((900f, 30f), (effect.Left, effect.Top)); + Assert.Equal((280f, 120f), (effect.Left, effect.Top)); Assert.False(inventory.IsVisible); Assert.True(effect.IsVisible); diff --git a/tests/AcDream.App.Tests/UI/Layout/fixtures/link_status_2100001D.json b/tests/AcDream.App.Tests/UI/Layout/fixtures/link_status_2100001D.json new file mode 100644 index 00000000..4fcf41c2 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/fixtures/link_status_2100001D.json @@ -0,0 +1,1059 @@ +{ + "Id": 268435815, + "Type": 268435485, + "X": 0, + "Y": 0, + "Width": 300, + "Height": 362, + "OriginalParentWidth": 0, + "OriginalParentHeight": 0, + "HasOriginalParentSize": false, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 0, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 31, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "88": { + "Kind": 0, + "MasterPropertyId": 88, + "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": {} + }, + "87": { + "Kind": 0, + "MasterPropertyId": 87, + "UnsignedValue": 268435465, + "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": {} + }, + "268435529": { + "Kind": 1, + "MasterPropertyId": 268435529, + "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": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435816, + "Type": 3, + "X": 0, + "Y": 25, + "Width": 300, + "Height": 337, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 31, + "Image": { + "File": 100682946, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682946, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435817, + "Type": 12, + "X": 8, + "Y": 0, + "Width": 284, + "Height": 337, + "OriginalParentWidth": 300, + "OriginalParentHeight": 337, + "HasOriginalParentSize": true, + "Left": 0, + "Top": 0, + "Right": 0, + "Bottom": 0, + "ReadOrder": 1, + "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": 1073741825, + "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": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741825, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + }, + { + "Id": 268435708, + "Type": 1, + "X": 276, + "Y": 0, + "Width": 24, + "Height": 25, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "18": { + "Kind": 0, + "MasterPropertyId": 18, + "UnsignedValue": 268435465, + "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": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100668308, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100668307, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100668307, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100668308, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + }, + { + "Id": 268435709, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 276, + "Height": 25, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 31, + "Image": { + "File": 100683002, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100683002, + "Item2": 3 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435710, + "Type": 12, + "X": 0, + "Y": 0, + "Width": 276, + "Height": 25, + "OriginalParentWidth": 276, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 0, + "Top": 0, + "Right": 0, + "Bottom": 0, + "ReadOrder": 1, + "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": 1073741825, + "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": {} + }, + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 187694290, + "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": {} + } + } + } + }, + "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": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741825, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/AcDream.App.Tests/UI/Layout/fixtures/mini_game_2100001E.json b/tests/AcDream.App.Tests/UI/Layout/fixtures/mini_game_2100001E.json new file mode 100644 index 00000000..5c896a61 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/fixtures/mini_game_2100001E.json @@ -0,0 +1,3402 @@ +{ + "Id": 268435818, + "Type": 268435486, + "X": 0, + "Y": 0, + "Width": 300, + "Height": 362, + "OriginalParentWidth": 0, + "OriginalParentHeight": 0, + "HasOriginalParentSize": false, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 0, + "ZLevel": 1000, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 62, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "88": { + "Kind": 0, + "MasterPropertyId": 88, + "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": {} + }, + "87": { + "Kind": 0, + "MasterPropertyId": 87, + "UnsignedValue": 268435466, + "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": {} + }, + "268435529": { + "Kind": 1, + "MasterPropertyId": 268435529, + "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": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435819, + "Type": 1, + "X": 276, + "Y": 0, + "Width": 24, + "Height": 25, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "18": { + "Kind": 0, + "MasterPropertyId": 18, + "UnsignedValue": 268435466, + "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": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100668308, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100668307, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100668307, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100668308, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + }, + { + "Id": 268435820, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 276, + "Height": 25, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "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": 100683002, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100683002, + "Item2": 3 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435821, + "Type": 12, + "X": 0, + "Y": 0, + "Width": 276, + "Height": 25, + "OriginalParentWidth": 276, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 0, + "Top": 0, + "Right": 0, + "Bottom": 0, + "ReadOrder": 1, + "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": 1073741825, + "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": {} + }, + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 133508146, + "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": {} + } + } + } + }, + "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": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741825, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + }, + { + "Id": 268435822, + "Type": 3, + "X": 0, + "Y": 25, + "Width": 300, + "Height": 337, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100668312, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100668312, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435829, + "Type": 1, + "X": 5, + "Y": 300, + "Width": 94, + "Height": 22, + "OriginalParentWidth": 300, + "OriginalParentHeight": 337, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 2, + "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": 1073741824, + "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": {} + }, + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 76273774, + "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": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100672325, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + } + } + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100672324, + "DrawMode": 1 + }, + "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": {} + }, + "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": {} + } + } + } + }, + "6": { + "Id": 6, + "Name": "Highlight", + "PassToChildren": false, + "IncorporationFlags": 0, + "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": {} + }, + "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": {} + } + } + } + }, + "8": { + "Id": 8, + "Name": "Highlight_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": { + "File": 100672326, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 1073741824, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": { + "Normal": { + "Item1": 100672324, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100672325, + "Item2": 1 + }, + "Ghosted": { + "Item1": 100672326, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + }, + { + "Id": 268435830, + "Type": 1, + "X": 103, + "Y": 300, + "Width": 94, + "Height": 22, + "OriginalParentWidth": 300, + "OriginalParentHeight": 337, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "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": 1073741824, + "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": {} + }, + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 129265635, + "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": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100672325, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + } + } + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100672324, + "DrawMode": 1 + }, + "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": {} + }, + "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": {} + } + } + } + }, + "6": { + "Id": 6, + "Name": "Highlight", + "PassToChildren": false, + "IncorporationFlags": 0, + "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": {} + }, + "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": {} + } + } + } + }, + "8": { + "Id": 8, + "Name": "Highlight_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": { + "File": 100672326, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 1073741824, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": { + "Normal": { + "Item1": 100672324, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100672325, + "Item2": 1 + }, + "Ghosted": { + "Item1": 100672326, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + }, + { + "Id": 268435831, + "Type": 1, + "X": 201, + "Y": 300, + "Width": 94, + "Height": 22, + "OriginalParentWidth": 300, + "OriginalParentHeight": 337, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 4, + "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": 1073741824, + "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": {} + }, + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 72446581, + "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": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100672325, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + } + } + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100672324, + "DrawMode": 1 + }, + "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": {} + }, + "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": {} + } + } + } + }, + "6": { + "Id": 6, + "Name": "Highlight", + "PassToChildren": false, + "IncorporationFlags": 0, + "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": {} + }, + "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": {} + } + } + } + }, + "8": { + "Id": 8, + "Name": "Highlight_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "35": { + "Kind": 4, + "MasterPropertyId": 35, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": {} + }, + "37": { + "Kind": 4, + "MasterPropertyId": 37, + "UnsignedValue": 0, + "IntegerValue": 1, + "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": { + "File": 100672326, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 1073741824, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": { + "Normal": { + "Item1": 100672324, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100672325, + "Item2": 1 + }, + "Ghosted": { + "Item1": 100672326, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + }, + { + "Id": 268435823, + "Type": 3, + "X": 10, + "Y": 10, + "Width": 280, + "Height": 280, + "OriginalParentWidth": 300, + "OriginalParentHeight": 337, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 2, + "Bottom": 2, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435824, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 280, + "Height": 12, + "OriginalParentWidth": 280, + "OriginalParentHeight": 280, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100671446, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100671446, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435825, + "Type": 3, + "X": 0, + "Y": 268, + "Width": 280, + "Height": 12, + "OriginalParentWidth": 280, + "OriginalParentHeight": 280, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 2, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100671447, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100671447, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435826, + "Type": 3, + "X": 0, + "Y": 12, + "Width": 12, + "Height": 256, + "OriginalParentWidth": 280, + "OriginalParentHeight": 280, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100671448, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100671448, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435827, + "Type": 3, + "X": 268, + "Y": 12, + "Width": 12, + "Height": 256, + "OriginalParentWidth": 280, + "OriginalParentHeight": 280, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 4, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100671449, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100671449, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + }, + { + "Id": 268435828, + "Type": 5, + "X": 12, + "Y": 12, + "Width": 256, + "Height": 256, + "OriginalParentWidth": 280, + "OriginalParentHeight": 280, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 5, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100671445, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": { + "100": { + "Kind": 7, + "MasterPropertyId": 100, + "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": 8, + "MasterPropertyId": 101, + "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": { + "99": { + "Kind": 2, + "MasterPropertyId": 99, + "UnsignedValue": 553648158, + "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": {} + }, + "98": { + "Kind": 0, + "MasterPropertyId": 98, + "UnsignedValue": 268435832, + "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": {} + }, + "268435489": { + "Kind": 7, + "MasterPropertyId": 268435489, + "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": 268435490, + "UnsignedValue": 100683005, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683006, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683007, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683008, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683009, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683010, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683011, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683012, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683013, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683014, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683015, + "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": {} + }, + { + "Kind": 2, + "MasterPropertyId": 268435490, + "UnsignedValue": 100683016, + "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": {} + }, + "92": { + "Kind": 1, + "MasterPropertyId": 92, + "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": {} + }, + "95": { + "Kind": 4, + "MasterPropertyId": 95, + "UnsignedValue": 0, + "IntegerValue": 8, + "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": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100671445, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/AcDream.App.Tests/UI/Layout/fixtures/vitae_21000020.json b/tests/AcDream.App.Tests/UI/Layout/fixtures/vitae_21000020.json new file mode 100644 index 00000000..6443b76d --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/fixtures/vitae_21000020.json @@ -0,0 +1,1059 @@ +{ + "Id": 268435905, + "Type": 268435488, + "X": 0, + "Y": 0, + "Width": 300, + "Height": 362, + "OriginalParentWidth": 0, + "OriginalParentHeight": 0, + "HasOriginalParentSize": false, + "Left": 2, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 0, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "88": { + "Kind": 0, + "MasterPropertyId": 88, + "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": {} + }, + "87": { + "Kind": 0, + "MasterPropertyId": 87, + "UnsignedValue": 268435468, + "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": {} + }, + "268435529": { + "Kind": 1, + "MasterPropertyId": 268435529, + "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": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435906, + "Type": 3, + "X": 0, + "Y": 25, + "Width": 300, + "Height": 337, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 1, + "ReadOrder": 3, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": { + "File": 100682946, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100682946, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435907, + "Type": 12, + "X": 8, + "Y": 0, + "Width": 284, + "Height": 337, + "OriginalParentWidth": 300, + "OriginalParentHeight": 337, + "HasOriginalParentSize": true, + "Left": 0, + "Top": 0, + "Right": 0, + "Bottom": 0, + "ReadOrder": 1, + "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": 1073741825, + "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": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741825, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + }, + { + "Id": 268435708, + "Type": 1, + "X": 276, + "Y": 0, + "Width": 24, + "Height": 25, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "HasOriginalParentSize": true, + "Left": 1, + "Top": 1, + "Right": 1, + "Bottom": 2, + "ReadOrder": 1, + "ZLevel": 0, + "States": { + "4294967295": { + "Id": 4294967295, + "Name": "", + "PassToChildren": false, + "IncorporationFlags": 30, + "Image": null, + "Cursor": null, + "Properties": { + "Values": { + "18": { + "Kind": 0, + "MasterPropertyId": 18, + "UnsignedValue": 268435468, + "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": {} + } + } + } + }, + "3": { + "Id": 3, + "Name": "Normal_pressed", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100668308, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + }, + "1": { + "Id": 1, + "Name": "Normal", + "PassToChildren": false, + "IncorporationFlags": 0, + "Image": { + "File": 100668307, + "DrawMode": 1 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 1, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "Normal": { + "Item1": 100668307, + "Item2": 1 + }, + "Normal_pressed": { + "Item1": 100668308, + "Item2": 1 + } + }, + "StateCursors": {}, + "DefaultStateName": "Normal", + "Children": [] + }, + { + "Id": 268435709, + "Type": 3, + "X": 0, + "Y": 0, + "Width": 276, + "Height": 25, + "OriginalParentWidth": 300, + "OriginalParentHeight": 362, + "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": 100683002, + "DrawMode": 3 + }, + "Cursor": null, + "Properties": { + "Values": {} + } + } + }, + "DefaultStateId": 0, + "FontDid": 0, + "HJustify": 1, + "VJustify": 1, + "FontColor": null, + "StateMedia": { + "": { + "Item1": 100683002, + "Item2": 3 + } + }, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [ + { + "Id": 268435710, + "Type": 12, + "X": 0, + "Y": 0, + "Width": 276, + "Height": 25, + "OriginalParentWidth": 276, + "OriginalParentHeight": 25, + "HasOriginalParentSize": true, + "Left": 0, + "Top": 0, + "Right": 0, + "Bottom": 0, + "ReadOrder": 1, + "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": 1073741825, + "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": {} + }, + "23": { + "Kind": 5, + "MasterPropertyId": 23, + "UnsignedValue": 0, + "IntegerValue": 0, + "FloatValue": 0, + "BoolValue": false, + "StringInfoValue": { + "Token": 0, + "StringId": 5801986, + "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": {} + } + } + } + }, + "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": {} + } + } + } + } + }, + "DefaultStateId": 0, + "FontDid": 1073741825, + "HJustify": 1, + "VJustify": 1, + "FontColor": { + "X": 1, + "Y": 1, + "Z": 1, + "W": 1 + }, + "StateMedia": {}, + "StateCursors": {}, + "DefaultStateName": "", + "Children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/AcDream.Core.Net.Tests/Messages/SocialActionsTests.cs b/tests/AcDream.Core.Net.Tests/Messages/SocialActionsTests.cs index ae8fe607..4cf08110 100644 --- a/tests/AcDream.Core.Net.Tests/Messages/SocialActionsTests.cs +++ b/tests/AcDream.Core.Net.Tests/Messages/SocialActionsTests.cs @@ -37,11 +37,14 @@ public sealed class SocialActionsTests [Fact] public void BuildPingRequest_HasOpcode0x01E9() { - byte[] body = SocialActions.BuildPingRequest(seq: 1, clientId: 42); + byte[] body = SocialActions.BuildPingRequest(seq: 1); + Assert.Equal(12, body.Length); + Assert.Equal(SocialActions.GameActionEnvelope, + BinaryPrimitives.ReadUInt32LittleEndian(body)); + Assert.Equal(1u, + BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(4))); Assert.Equal(SocialActions.PingRequestOpcode, BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(8))); - Assert.Equal(42u, - BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(12))); } [Fact] diff --git a/tests/AcDream.Core.Net.Tests/WorldSessionLinkStatusTests.cs b/tests/AcDream.Core.Net.Tests/WorldSessionLinkStatusTests.cs index 65abb859..999617e2 100644 --- a/tests/AcDream.Core.Net.Tests/WorldSessionLinkStatusTests.cs +++ b/tests/AcDream.Core.Net.Tests/WorldSessionLinkStatusTests.cs @@ -1,4 +1,6 @@ using AcDream.Core.Net; +using AcDream.Core.Net.Messages; +using System.Net; using Xunit; namespace AcDream.Core.Net.Tests; @@ -43,4 +45,40 @@ public sealed class WorldSessionLinkStatusTests Assert.True(snapshot.Connected); Assert.Equal(0d, snapshot.SecondsSinceLastPacket); } + + [Fact] + public void ConnectedSnapshot_CarriesPacketLossAndRoundTripTelemetry() + { + LinkStatusSnapshot snapshot = WorldSession.BuildLinkStatus( + WorldSession.State.InWorld, + lastInboundPacketTicks: 100, + nowTicks: 200, + frequency: 100, + roundTripSeconds: 0.125d, + packetLossPercentage: 2.5d); + + Assert.Equal(2.5d, snapshot.PacketLossPercentage); + Assert.Equal(0.125d, snapshot.RoundTripSeconds); + } + + [Fact] + public void RequestAndEmptyPingResponse_MeasureMonotonicRoundTrip() + { + using var session = new WorldSession( + new IPEndPoint(IPAddress.Loopback, 65000)); + byte[]? action = null; + session.GameActionCapture = bytes => action = bytes; + + session.RequestLinkStatusPing(); + session.GameEvents.Dispatch(new GameEventEnvelope( + PlayerGuid: 0u, + Sequence: 1u, + EventType: GameEventType.PingResponse, + Payload: ReadOnlyMemory.Empty)); + + Assert.NotNull(action); + Assert.Equal(12, action.Length); + Assert.NotNull(session.PingRoundTripSeconds); + Assert.True(session.PingRoundTripSeconds >= 0d); + } }