fix(D.5.1): toolbar movable + chrome-grab + peace-only indicator + no prototype square
D1 — Toolbar not movable: toolbarRoot.Anchors = AnchorEdges.None (was Left|Top) so ApplyAnchor early-returns and doesn't re-pin the window every frame. Matches the vitalsRoot idiom exactly. D2 — Cannot grab toolbar by chrome: toolbarRoot.ClickThrough = false so HitTest succeeds over the UiDatElement chrome and the drag starts. UiDatElement ctor defaults ClickThrough=true; vitalsRoot already overrides it. C1 — All four combat-mode indicators visible at once (war/flame stacked on peace): ports gmToolbarUI::RecvNotice_SetCombatMode (acclient_2013_pseudo_c.txt:196632-196669). CombatIndicatorIds[] maps index 0-3 to NonCombat/Melee/Missile/Magic; SetCombatMode shows exactly one and hides the other three. Default to NonCombat at bind (player always spawns in peace). Wires CombatState.CombatModeChanged for live updates. Tests: CombatIndicator_defaultNonCombat_onlyPeaceVisible, CombatIndicator_setCombatModeMelee_onlyMeleeVisible, CombatIndicator_liveSignal_updatesWhenCombatStateChanges. V1 — Blue empty-slot square at top-left (prototype 0x100001B2 materialized): ImportInfos now skips top-level elements that are (a) referenced as a BaseElement by another element in the same layout AND (b) have no own state media. The CollectBaseRefsInDesc walk covers nested children; HasNoOwnMedia re-uses ToInfo's media extraction. The Resolve path reads BaseElement from the raw dat via dats.Get<LayoutDesc> — it never depends on the prototype being in the built widget tree — so the skip is safe. Conformance tests (vitals, chat) are unaffected (they exercise Build, not ImportInfos). Test: BuildFromInfos_PrototypeSkipped_DerivedPresent_PrototypeAbsent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b3e5e8b0f7
commit
bfc452d610
5 changed files with 262 additions and 11 deletions
|
|
@ -139,9 +139,34 @@ public static class LayoutImporter
|
|||
var ld = dats.Get<LayoutDesc>(layoutId);
|
||||
if (ld is null) return null;
|
||||
|
||||
// Collect the set of element ids that are referenced as a BaseElement by ANY
|
||||
// element in THIS layout (where BaseLayoutId == layoutId). Such elements are
|
||||
// purely inheritance templates ("prototypes") — retail never instantiates them
|
||||
// as live widgets. Example: the toolbar slot prototype 0x100001B2 in LayoutDesc
|
||||
// 0x21000016, which all 18 slot elements inherit from and which has no own media.
|
||||
//
|
||||
// NOTE: the Resolve path reads BaseElement from the raw dat directly (via
|
||||
// dats.Get<LayoutDesc>), so the prototype never needs to appear in the built
|
||||
// widget tree for inheritance to work. Skipping it here is safe.
|
||||
var referencedAsBase = new HashSet<uint>();
|
||||
foreach (var kv in ld.Elements)
|
||||
CollectBaseRefsInDesc(kv.Value, layoutId, referencedAsBase);
|
||||
|
||||
var tops = new List<ElementInfo>();
|
||||
foreach (var kv in ld.Elements)
|
||||
tops.Add(Resolve(dats, kv.Value, new HashSet<(uint, uint)>()));
|
||||
{
|
||||
// Skip pure prototype elements: top-level elements that are referenced as a
|
||||
// base template by another element in this same layout AND have no own state
|
||||
// media (so they draw nothing and contribute nothing but their inherited shape).
|
||||
var d = kv.Value;
|
||||
if (referencedAsBase.Contains(d.ElementId) && HasNoOwnMedia(d))
|
||||
{
|
||||
Console.WriteLine($"[D.2b] LayoutImporter: skipping prototype element 0x{d.ElementId:X8} in layout 0x{layoutId:X8} (no own media, referenced as BaseElement).");
|
||||
continue;
|
||||
}
|
||||
|
||||
tops.Add(Resolve(dats, d, new HashSet<(uint, uint)>()));
|
||||
}
|
||||
|
||||
return tops.Count == 1
|
||||
? tops[0]
|
||||
|
|
@ -270,6 +295,37 @@ public static class LayoutImporter
|
|||
}
|
||||
}
|
||||
|
||||
// ── Prototype detection helpers ───────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Recursively walks <paramref name="d"/> and all its children, adding to
|
||||
/// <paramref name="result"/> the <c>BaseElement</c> of every descriptor that
|
||||
/// references this layout (<c>BaseLayoutId == layoutId</c>). Used by
|
||||
/// <see cref="ImportInfos"/> to identify pure prototype/template elements that
|
||||
/// should not be instantiated as live widgets.
|
||||
/// </summary>
|
||||
private static void CollectBaseRefsInDesc(ElementDesc d, uint layoutId, HashSet<uint> result)
|
||||
{
|
||||
if (d.BaseElement != 0 && d.BaseLayoutId == layoutId)
|
||||
result.Add(d.BaseElement);
|
||||
foreach (var kv in d.Children)
|
||||
CollectBaseRefsInDesc(kv.Value, layoutId, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when <paramref name="d"/> carries no own state media — i.e. its
|
||||
/// <c>StateDesc</c> (DirectState) and <c>States</c> (named states) yield no
|
||||
/// <see cref="MediaDescImage"/> entries with a non-zero file id.
|
||||
/// Such elements are pure inheritance templates with no rendering content.
|
||||
/// </summary>
|
||||
private static bool HasNoOwnMedia(ElementDesc d)
|
||||
{
|
||||
// Re-use ToInfo's media extraction: if the resulting StateMedia is empty the
|
||||
// element has no renderable image in any state.
|
||||
var info = ToInfo(d);
|
||||
return info.StateMedia.Count == 0;
|
||||
}
|
||||
|
||||
// ── Element tree search ───────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue