feat(ui): Campaign OP slice OP4 — the Character tab
Binds LayoutDesc 0x21000028 (gmCharacterSettingsUI) through OP2's template-list mechanism and OP3's OptionPage model: 6 authored group headers + 50 toggle rows (49 from the 2013 build + D3's "Listen to PK death messages", AP-193) in research doc §2's authored order, each row resolved by PlayerOption id through CharacterOptionTable, seeded from live RuntimeCharacterOptionsState, defaulted from CharacterOptionTable. ClientDefault (byte-verified against UIOption_Checkbox::SetPlayerOption @0x00486e80's own GetDefaultOptionValue call — AP-194 updated to confirm the directive was followed), labels/tooltips resolved by name from string table 0x23000003 (never hard-coded English), and registered with OptionsPanelController.CharacterPage. Apply/Reset/Defaults (0x100001FC/FD/FE) are now wired per-page via a scoped subtree search (UiElement.FindDescendant, promoted from UiTabPanel) since Character/ Chat/Config each author their own physical instance under the SAME element ids. Consumers: Group A (29 ids) wire+store only via the existing SetSingleCharacterOptionRuntimeCmd/TrySetOption seam. Group B: Display Timestamps prefixes new transcript lines (RuntimeCommunicationState. DisplayTimestampsSource); Disable Distance Fog forces FogMode.Off (WeatherSystem.DisableDistanceFogSource, retiring half of TS-73); Run as Default Movement inverts the walk-mode modifier's default (RuntimeLocalPlayerMovementState.RunAsDefaultMovementSource). Group C re-points AutoTarget/AutoRepeatAttack/ViewCombatTarget (CharacterOptionCombatSettingsSource), VividTargetingIndicator/ CoordinatesOnRadar/LockUI/AcceptLootPermits from the client-local GameplaySettings record to the canonical server bit — closing two previously-unfiled divergences where AutoRepeatAttack and AcceptCorpseLootingPermissions never reached the wire despite being retail auto-save ids. TS-73 narrowed to its two still-open cases; TS-75..TS-80 file the genuine gaps (no day/night force, no weather- particle/profanity-filter/salvage/housing/pickup-preference subsystem, fellowship-create's unaudited client-sourced field) rather than inventing stand-ins. Conformance: CharacterOptionsPageControllerTests pins all 50 rows against CharacterOptionTable in both directions (an invented or dropped row fails the build), the authored group/order row-by-row, and the build/seed/Apply/Reset/Defaults/wire-publish behavior end-to-end against the committed fixture. 52 new tests; full solution suite 13,008 passed / 4 skipped / 0 failed (was 12,956/4/0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7e9f372ce8
commit
22b86b9ff4
25 changed files with 1674 additions and 41 deletions
|
|
@ -195,7 +195,11 @@ public sealed record OptionsRuntimeBindings(
|
|||
Action<string> DisplaySystemMessage,
|
||||
Action<string> DisplayMouseTurningMacroLine,
|
||||
Func<CameraTurningSettings> LoadCameraTurning,
|
||||
Action<CameraTurningSettings> SaveCameraTurning);
|
||||
Action<CameraTurningSettings> SaveCameraTurning,
|
||||
// Campaign OP slice OP4 (2026-08-11): reads a live character-option
|
||||
// bit by its linear id (RuntimeCharacterOptionsState.GetOptionBit) —
|
||||
// the Character-tab panel's row-seed source.
|
||||
Func<uint, bool> CurrentCharacterOption);
|
||||
|
||||
public sealed record InventoryRuntimeBindings(
|
||||
ClientObjectTable Objects,
|
||||
|
|
@ -1992,6 +1996,42 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
}
|
||||
|
||||
OptionsPanelController = controller;
|
||||
|
||||
// Campaign OP slice OP4 (2026-08-11): the Character tab's 6
|
||||
// headers + 50 rows. Runs BEFORE ActivateTabs (below) so the
|
||||
// Character page's model is fully populated before the default
|
||||
// tab's OnShown fires. Needs a SECOND dat-lock scope — resolving
|
||||
// each row's template (property 0x64 on the ListBox) is a live
|
||||
// DAT read per row, unlike the already-built tree the block above
|
||||
// only wires callbacks into.
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
var strings = new DatStringResolver(_bindings.Assets.Dats);
|
||||
bool bound = Layout.CharacterOptionsPageController.Bind(
|
||||
layout,
|
||||
controller.CharacterPage,
|
||||
templateResolver: (templateLayoutId, templateElementId) =>
|
||||
{
|
||||
ElementInfo? info = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats, templateLayoutId, templateElementId);
|
||||
return info is null
|
||||
? null
|
||||
: LayoutImporter.Build(
|
||||
info,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
strings.Resolve).Root;
|
||||
},
|
||||
resolveString: (tableId, stringId) => strings.Resolve(tableId, stringId),
|
||||
new Layout.CharacterOptionsPageController.Bindings(
|
||||
CurrentValue: id => _bindings.Options.CurrentCharacterOption((uint)id),
|
||||
SetOption: (id, value) => _bindings.Options.CommandBus().Publish(
|
||||
new SetSingleCharacterOptionRuntimeCmd((uint)id, value))));
|
||||
if (!bound)
|
||||
Console.WriteLine("[UI] options panel: Character tab rows did not bind.");
|
||||
}
|
||||
|
||||
controller.ActivateTabs();
|
||||
|
||||
RetailWindowHandle handle = RetailWindowFrame.Mount(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue