feat(D.5.3a): selected-object meter — Health bar + name on the action bar
Port of gmToolbarUI::HandleSelectionChanged (acclient_2013_pseudo_c.txt:198635). When the player selects a world object the action bar's bottom strip shows the object name + (for player/pet/attackable targets) a live Health meter; deselect clears it. Mana (#140) + stack slider deferred. - SelectedObjectController (new): clear-then-populate on selection change; sets name (UiText child, VitalsController pattern), overlay state (ObjectSelected / StackedItemSelected via UiDatElement.ActiveState), shows the health meter and sends QueryHealth for health targets. Subscribes via a delegate seam (no GameWindow coupling). - GameWindow: _selectedGuid field -> SelectedGuid property + SelectionChanged event (fires on actual change only); 3 write sites converted, reads untouched. All selection-write paths (LMB pick, Tab/Q, despawn-clear via Tick()) run on the render thread, so the event-driven UI mutation is single-threaded. - WorldSession.SendQueryHealth (0x01BF) — wraps SocialActions.BuildQueryHealth. - DatWidgetFactory.BuildMeter: handle the single-image toolbar meter shape (back-track on the element's own DirectState, fill on one Type-3 child). The sprites go in the TILE slot (DrawMode=Normal tiles to full bar geometry per UIElement_Meter::DrawChildren) — a left-cap assignment would gap/clamp a sub-140px sprite. Vitals 3-slice path unchanged. - ToolbarController.HiddenIds: A1 (health) now owned by SelectedObjectController; A2 (mana) + A4 (stack) stay hidden (deferred) so their dat back-tracks don't render as stray empty bars. Adversarial Opus review found + fixed: the mana-meter orphan (A2 left unhidden) and the meter tile-vs-cap render bug (C1). Divergence rows AP-46 (health gate approximation: IsLiveCreatureTarget vs IsPlayer||pet||attackable) + AP-47 (meter shown on select vs on UpdateHealth reply). Spec §5 corrected. Build + full test suite green (2,684 passed / 4 skipped). Health meter render fidelity (full-width fill + fraction mapping) pending the user's visual gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e8562fc4e2
commit
6636e50c2a
11 changed files with 851 additions and 30 deletions
|
|
@ -90,11 +90,11 @@ public static class DatWidgetFactory
|
|||
// ── Meter ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Builds a <see cref="UiMeter"/> and populates its six 3-slice sprite ids by
|
||||
/// reading the meter's grandchild image elements (format doc §11).
|
||||
/// Builds a <see cref="UiMeter"/> and populates its sprite ids from the meter's
|
||||
/// child/grandchild elements (format doc §11). Two shapes are handled:
|
||||
///
|
||||
/// <para>
|
||||
/// Structure the importer produces for each meter (UIElement_Meter):
|
||||
/// <b>3-slice shape</b> (vitals meters — 2 Type-3 containers, each with 3 image grandchildren):
|
||||
/// <code>
|
||||
/// meter (Type 7)
|
||||
/// ├── back-layer container (Type 3, lower ReadOrder — drawn first / behind)
|
||||
|
|
@ -106,13 +106,27 @@ public static class DatWidgetFactory
|
|||
/// │ ├── center image (→ front-tile sprite)
|
||||
/// │ ├── right-cap image (→ front-right sprite)
|
||||
/// │ └── expand overlay (named "ShowDetail"/"HideDetail" only — NO DirectState — IGNORED)
|
||||
/// └── text label (Type 0) (IGNORED — Fill/Label providers bound by VitalsController in Task 6)
|
||||
/// └── text label (Type 0) (IGNORED — Fill/Label providers bound by VitalsController)
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Single-image shape</b> (toolbar selected-object meters 0x100001A1/0x100001A2 — 1 Type-3
|
||||
/// child, no grandchildren): the back-track sprite is on the meter element's own DirectState;
|
||||
/// the fill sprite is on the single Type-3 child's own DirectState. Both are placed in the
|
||||
/// TILE slot (Back/FrontTile) with left/right caps 0, so <see cref="UiMeter.DrawHBar"/> tiles
|
||||
/// them across the full bar geometry (DrawMode=Normal) and clips the fill to the fraction.
|
||||
/// (retail: gmToolbarUI::HandleSelectionChanged :198635, UIElement_Meter::Initialize :123328)
|
||||
/// <code>
|
||||
/// meter (Type 7) [DirectState "" → back-track sprite, e.g. 0x0600193E]
|
||||
/// └── fill container (Type 3) [DirectState "" → fill sprite, e.g. 0x0600193F]
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <see cref="UiMeter.Fill"/> and <see cref="UiMeter.Label"/> are NOT set here.
|
||||
/// They are bound to the live stat providers in Task 6 (VitalsController).
|
||||
/// They are bound to the live stat providers by the controller (VitalsController /
|
||||
/// SelectedObjectController).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private static UiMeter BuildMeter(ElementInfo info,
|
||||
|
|
@ -132,23 +146,53 @@ public static class DatWidgetFactory
|
|||
.OrderBy(c => c.ReadOrder)
|
||||
.ToList();
|
||||
|
||||
if (containers.Count != 2)
|
||||
Console.WriteLine($"[D.2b] meter 0x{info.Id:X8}: {containers.Count} Type-3 slice containers (expected 2) — bars may render as solid-color fallback.");
|
||||
|
||||
if (containers.Count >= 1)
|
||||
{
|
||||
var (l, t, r) = SliceIds(containers[0]);
|
||||
m.BackLeft = l;
|
||||
m.BackTile = t;
|
||||
m.BackRight = r;
|
||||
}
|
||||
|
||||
if (containers.Count >= 2)
|
||||
{
|
||||
var (l, t, r) = SliceIds(containers[1]);
|
||||
m.FrontLeft = l;
|
||||
m.FrontTile = t;
|
||||
m.FrontRight = r;
|
||||
// Vitals 3-slice shape: two Type-3 containers each holding 3 grandchild images
|
||||
// (left-cap / center-tile / right-cap). Back is the lower ReadOrder; front is higher.
|
||||
var (bl, bt, br) = SliceIds(containers[0]);
|
||||
m.BackLeft = bl;
|
||||
m.BackTile = bt;
|
||||
m.BackRight = br;
|
||||
|
||||
var (fl, ft, fr) = SliceIds(containers[1]);
|
||||
m.FrontLeft = fl;
|
||||
m.FrontTile = ft;
|
||||
m.FrontRight = fr;
|
||||
}
|
||||
else if (containers.Count == 1)
|
||||
{
|
||||
// Single-image shape used by the toolbar selected-object meters
|
||||
// (health 0x100001A1, mana 0x100001A2).
|
||||
// - The back-track sprite lives on the meter ELEMENT's own DirectState ("" key of
|
||||
// info.StateMedia) — not on any grandchild image. e.g. health back = 0x0600193E.
|
||||
// - The fill sprite lives on the single Type-3 child's own DirectState ("" key of
|
||||
// containers[0].StateMedia). e.g. health fill = 0x0600193F.
|
||||
// The fill child has NO image grandchildren, so SliceIds would return all-zero —
|
||||
// read the container's StateMedia directly instead.
|
||||
//
|
||||
// These go in the TILE slot (not the left-cap slot): the sprites are DrawMode=Normal,
|
||||
// which retail renders as "tile at native width to fill the full element geometry"
|
||||
// (format doc §6; the generic UiDatElement.OnDraw Normal path; UIElement_Meter::
|
||||
// DrawChildren :123574 clips the child's FULL 140px geometry box to the fill fraction).
|
||||
// With the sprite on BackLeft instead, UiMeter.DrawHBar would clamp the cap to the
|
||||
// sprite's NATIVE width (capL = min(nativeW, 140)) — leaving a right-side gap and
|
||||
// mapping the fill fraction to native width when nativeW < 140. The tile slot makes
|
||||
// midW = full bar width, so the back tiles across all 140px and the front clips to
|
||||
// 140*fraction correctly for any native sprite width (left/right caps unused = 0).
|
||||
// (retail: gmToolbarUI::HandleSelectionChanged :198635 / UIElement_Meter::DrawChildren :123574)
|
||||
m.BackLeft = 0;
|
||||
m.BackTile = info.StateMedia.TryGetValue("", out var bm) ? bm.File : 0u;
|
||||
m.BackRight = 0;
|
||||
|
||||
m.FrontLeft = 0;
|
||||
m.FrontTile = containers[0].StateMedia.TryGetValue("", out var fm) ? fm.File : 0u;
|
||||
m.FrontRight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Count == 0: no Type-3 containers at all — genuinely malformed meter dat.
|
||||
Console.WriteLine($"[D.2b] meter 0x{info.Id:X8}: {containers.Count} Type-3 slice containers (expected 1 or 2) — bars may render as solid-color fallback.");
|
||||
}
|
||||
|
||||
return m;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue