fix(chargen): Campaign CC gate round 1 Batch B — authored selection states, label state, zoom/swatch feedback
GF-1/GF-8: UiButton now recognizes retail's custom Unselected/Selected radio-pair (0x10000016/0x10000017), bypassing the standard Normal/ Highlight machine that never admitted those state names — .Selected now lights the heritage/template/gender/Face-Clothes rows it was always a no-op for. AP-222/GF-11b: per-state label color/outline (dat 0x1B/0x21) now applies off the REQUESTED retail state id, not the art-gated committed ActiveState — resolves the Appearance spins' current-part highlight (text recolors even though no Highlight art exists on either client) and the Town caption's Normal-to-white swap. GF-11c: UiButton.LabelBox lets a lifted caption with its own authored rect draw there instead of the face-relative offset that's only correct when the label is authored directly on the button (heritage/template family, unchanged). GF-9: wires the real nine companion overlay elements (SetColor's SetVisible mechanism) that swatch clicks were always meant to drive, retiring AP-215 item 1 (the swatch.Selected substitution was a permanent no-op — swatches author no Highlight media at all). GF-10: zoom buttons now set the retail-mirrored mutual-exclusive Highlight/Normal pair on click; InitializePage carries no initial SetState for either button, so both stay at "Normal" until first click. Register: AP-222 retired (mechanism identified and ported), AP-215 narrowed (item 1 retired, item 2 unrelated and unchanged), row count recount corrected 164 (was already one high before this batch). App suite 5282/3 (was 5266/3), Runtime 1735/0 unchanged. Fixture + live- DAT tests only — no graphical client launch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1d9de5e095
commit
7d09821fdc
11 changed files with 1043 additions and 42 deletions
|
|
@ -126,6 +126,31 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
|
|||
0x10000314u, 0x10000315u, 0x10000316u, 0x10000317u,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// GF-9 (Campaign CC gate round 1 Batch B): the nine Type-3 companion
|
||||
/// "selected" overlay elements, one per <see cref="SwatchIds"/> entry at
|
||||
/// the SAME index — retail <c>gmCGAppearancePage::InitializePage</c>'s
|
||||
/// own id-pair table (<c>@0x004800ff-00480164</c>, the switch that fills
|
||||
/// <c>m_tColorWheel[i][0x10]</c>/<c>[0x14]</c>-ish offsets with the
|
||||
/// swatch/overlay id pair per index) resolves the SAME nine ids this
|
||||
/// array carries, in the SAME index order. <c>SetColor</c> (case
|
||||
/// <c>0x0047DD50</c>, <c>iCurColor</c> assignment) is retail's actual
|
||||
/// click-feedback mechanism — <c>SetVisible</c> on the overlay at
|
||||
/// <c>iCurColor</c>'s index, NOT a state swap on the swatch itself (the
|
||||
/// swatch buttons author only an unnamed DirectState sprite; measured
|
||||
/// against the installed dat, they have NO Normal/Highlight media at
|
||||
/// all, so <see cref="UiButton.Selected"/> was always a complete no-op
|
||||
/// here — see <see cref="RefreshColorAndShadeControls"/>). Live-DAT-
|
||||
/// probe-confirmed siblings of the swatches under the same color-wheel
|
||||
/// container (<c>0x100003B9</c>), each roughly centered on its paired
|
||||
/// swatch's own rect.
|
||||
/// </summary>
|
||||
internal static readonly uint[] SwatchOverlayIds =
|
||||
[
|
||||
0x10000318u, 0x10000319u, 0x1000031Au, 0x1000031Bu, 0x1000031Cu,
|
||||
0x1000031Du, 0x1000031Eu, 0x1000031Fu, 0x10000320u,
|
||||
];
|
||||
|
||||
/// <summary>Live-DAT-measured arrow geometry, uniform across all nine
|
||||
/// spins (every one is 200px wide): decrement child at local
|
||||
/// x=[80,127), increment child at x=[127,174). Anything outside both
|
||||
|
|
@ -144,6 +169,7 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
|
|||
private readonly UiElement? _clothesChoices;
|
||||
private readonly Dictionary<Part, UiButton> _spins = [];
|
||||
private readonly UiButton?[] _swatches = new UiButton?[SwatchIds.Length];
|
||||
private readonly UiElement?[] _swatchOverlays = new UiElement?[SwatchOverlayIds.Length];
|
||||
private readonly UiScrollbar? _shadeScroll;
|
||||
private readonly UiButton? _rotateClockwise;
|
||||
private readonly UiButton? _rotateCounterClockwise;
|
||||
|
|
@ -209,6 +235,9 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
|
|||
_swatches[i] = swatch;
|
||||
}
|
||||
|
||||
for (int i = 0; i < SwatchOverlayIds.Length; i++)
|
||||
_swatchOverlays[i] = Find<UiElement>(pageRoot, SwatchOverlayIds[i]);
|
||||
|
||||
_shadeScroll = Find<UiScrollbar>(pageRoot, ShadeScrollId);
|
||||
if (_shadeScroll is not null)
|
||||
_shadeScroll.ScalarChanged = SetShadeFromScalar;
|
||||
|
|
@ -223,10 +252,33 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
|
|||
_rotateCounterClockwise.OnClick = () => PreviewControl?.RotateCounterClockwise();
|
||||
_zoomIn = Find<UiButton>(pageRoot, ZoomInId);
|
||||
if (_zoomIn is not null)
|
||||
_zoomIn.OnClick = () => PreviewControl?.ZoomIn();
|
||||
_zoomIn.OnClick = () =>
|
||||
{
|
||||
PreviewControl?.ZoomIn();
|
||||
// GF-10: gmCGAppearancePage::ZoomIn @0x0047CF00
|
||||
// (@0x0047d005/0x0047d00f) ends ZoomInButton->SetState(6)
|
||||
// (Highlight), ZoomOutButton->SetState(1) (Normal) — a
|
||||
// mutual-exclusive pair. Re-derived from InitializePage
|
||||
// @0x0047fdd0-0048032e (m_bZoomedIn = 0 at construction,
|
||||
// @0x004802c3): NO explicit initial SetState call exists
|
||||
// for either button, so both start at their DAT-authored
|
||||
// "Normal" default (live-DAT-probe-confirmed) until the
|
||||
// first real zoom click — this port does not force an
|
||||
// initial Highlight.
|
||||
_zoomIn.TrySetRetailState(UiButtonStateMachine.Highlight);
|
||||
_zoomOut?.TrySetRetailState(UiButtonStateMachine.Normal);
|
||||
};
|
||||
_zoomOut = Find<UiButton>(pageRoot, ZoomOutId);
|
||||
if (_zoomOut is not null)
|
||||
_zoomOut.OnClick = () => PreviewControl?.ZoomOut();
|
||||
_zoomOut.OnClick = () =>
|
||||
{
|
||||
PreviewControl?.ZoomOut();
|
||||
// GF-10: gmCGAppearancePage::ZoomOut @0x0047D050
|
||||
// (@0x0047d140/0x0047d14a) mirrors ZoomIn — ZoomOutButton
|
||||
// -> Highlight(6), ZoomInButton -> Normal(1).
|
||||
_zoomOut.TrySetRetailState(UiButtonStateMachine.Highlight);
|
||||
_zoomIn?.TrySetRetailState(UiButtonStateMachine.Normal);
|
||||
};
|
||||
|
||||
ApplyChoiceVisibility();
|
||||
}
|
||||
|
|
@ -608,12 +660,21 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
|
|||
: UiButtonStateMachine.Normal);
|
||||
}
|
||||
|
||||
// GF-9 (Campaign CC gate round 1 Batch B): retail's ACTUAL swatch
|
||||
// click feedback is the companion overlay's visibility (SetColor
|
||||
// @0x0047DD50 -> m_tColorWheel[...][0x10][iCurColor*7]->SetVisible),
|
||||
// not a state swap on the swatch button — measured against the
|
||||
// installed dat, the nine swatches author only a DirectState sprite
|
||||
// with no Normal/Highlight media at all, so a prior
|
||||
// swatch.Selected assignment here was a permanent no-op (see
|
||||
// SwatchOverlayIds' own doc comment). Exactly one overlay is
|
||||
// visible: the one at the current part's own selected color index.
|
||||
ChargenAppearanceSlot? colorSlot = ColorSlotFor(_currentPart);
|
||||
uint currentColor = colorSlot is null ? Unset : ColorCurrent(_currentPart, snapshot.Appearance);
|
||||
for (int i = 0; i < _swatches.Length; i++)
|
||||
for (int i = 0; i < _swatchOverlays.Length; i++)
|
||||
{
|
||||
if (_swatches[i] is { } swatch)
|
||||
swatch.Selected = colorSlot is not null && currentColor == (uint)i;
|
||||
if (_swatchOverlays[i] is { } overlay)
|
||||
overlay.Visible = colorSlot is not null && currentColor == (uint)i;
|
||||
}
|
||||
|
||||
ChargenShadeSlot? shadeSlot = ShadeSlotFor(_currentPart);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue