feat(ui): importer Fix 5 — build meter text children; drop character XP injection hacks
LayoutImporter.BuildWidget gains a UiMeter-specific branch after the ConsumesDatChildren gate: Type-3 slice containers (already consumed by DatWidgetFactory.BuildMeter to extract sprite ids) are skipped, but all other children (Type-12 UIElement_Text overlays) are built normally, registered in byId, and attached as UiElement children of the meter. This fixes the XP meter (0x10000236): its two Type-12 children 0x10000237 "XP for next level:" label 0x10000238 XP-to-next-level value are now real UiText widgets in the tree, findable via FindElement. CharacterStatController.Bind now uses FindElement + LinesProvider binding instead of injecting runtime AddChild nodes. The two previously-injected UiText overlays are removed; the controller binds Padding=0, ClickThrough, RightAligned on the dat-origin widgets instead. New constants XpNextLabelId + XpNextValueId replace the old comment block. TAB GROUPS (SKIPPED — documented): UiText.ConsumesDatChildren flipping is NOT safe globally (risks chat/vitals) and the page-visibility pass in CharacterStatController depends on tab group Children.Count==0. Tab sprite injection onto layout.Root stays and is explicitly commented as deliberate. VITALS SAFE: health/stamina/mana meters have only Type-3 children → new loop finds nothing → zero UiElement children added → byte-identical render. VitalsController.BindMeter AddChild(number) pattern unchanged. Tests: 3 new LayoutImporterTests (slice-only, Fix-5 text children, vitals regression guard) + 3 CharacterStatControllerTests (label bound, value bound, missing children no-throw). 715 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1b9dd6c7a8
commit
ad4ed51d6b
4 changed files with 241 additions and 58 deletions
|
|
@ -825,6 +825,78 @@ public class CharacterStatControllerTests
|
|||
public void Bind_MissingElements_DoesNotThrow()
|
||||
=> CharacterStatController.Bind(Fake(), SampleData.SampleCharacter);
|
||||
|
||||
// ── Fix 5: XP meter text children bound via FindElement ──────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Fix 5: the XP label (0x10000237) is a dat-origin UiText child of the XP meter
|
||||
/// (now built by the importer). After Bind(), FindElement must return a UiText with
|
||||
/// a LinesProvider that emits "XP for next level:".
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Bind_XpMeter_XpNextLabel_IsBoundViaFindElement()
|
||||
{
|
||||
var meter = new UiMeter();
|
||||
var xpLabel = new UiText();
|
||||
// Attach the label as a child of the meter so it matches the real importer layout.
|
||||
meter.AddChild(xpLabel);
|
||||
var layout = Fake(
|
||||
(CharacterStatController.XpMeterId, meter),
|
||||
(CharacterStatController.XpNextLabelId, xpLabel));
|
||||
|
||||
CharacterStatController.Bind(layout, SampleData.SampleCharacter);
|
||||
|
||||
Assert.NotNull(xpLabel.LinesProvider);
|
||||
var lines = xpLabel.LinesProvider();
|
||||
Assert.Single(lines);
|
||||
Assert.Equal("XP for next level:", lines[0].Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix 5: the XP value (0x10000238) is bound to the XpToNextLevel string.
|
||||
/// ClickThrough=true and RightAligned=true must be set by the controller.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Bind_XpMeter_XpNextValue_IsBoundViaFindElement()
|
||||
{
|
||||
var meter = new UiMeter();
|
||||
var xpValue = new UiText();
|
||||
meter.AddChild(xpValue);
|
||||
var layout = Fake(
|
||||
(CharacterStatController.XpMeterId, meter),
|
||||
(CharacterStatController.XpNextValueId, xpValue));
|
||||
|
||||
CharacterStatController.Bind(layout, SampleData.SampleCharacter);
|
||||
|
||||
Assert.NotNull(xpValue.LinesProvider);
|
||||
Assert.True(xpValue.ClickThrough, "XP value overlay must be ClickThrough");
|
||||
Assert.True(xpValue.RightAligned, "XP value overlay must be RightAligned");
|
||||
|
||||
var lines = xpValue.LinesProvider();
|
||||
Assert.Single(lines);
|
||||
// XpToNextLevel from SampleData = 42_000_000L formatted as "42,000,000"
|
||||
Assert.Equal((42_000_000L).ToString("N0"), lines[0].Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix 5: if XpNextLabelId / XpNextValueId are absent from the layout (the old
|
||||
/// ConsumesDatChildren path, or a test layout that doesn't include them), Bind()
|
||||
/// must not throw — the meter Fill is still bound.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Bind_XpMeter_MissingTextChildren_DoesNotThrow()
|
||||
{
|
||||
var meter = new UiMeter();
|
||||
var layout = Fake((CharacterStatController.XpMeterId, meter));
|
||||
|
||||
// No XpNextLabelId or XpNextValueId in the layout.
|
||||
var ex = Record.Exception(() =>
|
||||
CharacterStatController.Bind(layout, SampleData.SampleCharacter));
|
||||
|
||||
Assert.Null(ex);
|
||||
// Fill must still be bound.
|
||||
Assert.NotNull(meter.Fill());
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Manufacture a minimal UiButton with a fake ElementInfo (no dat sprites).</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue