feat: port retail magic lifecycle and retained spell UI
Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
7b7ffcd278
commit
07be994d97
84 changed files with 17822 additions and 1051 deletions
|
|
@ -295,11 +295,9 @@ public static class LayoutImporter
|
|||
|
||||
// ── Inheritance resolution ────────────────────────────────────────────────
|
||||
|
||||
/// <summary>True when a pure-container leaf should inherit its base's subtree (the
|
||||
/// gmInventoryUI sub-window mount): the derived element has no own children, no own
|
||||
/// state media, and the base resolved to ≥1 child. Inert for media-bearing inheritors
|
||||
/// (close button / title), elements with their own children, and childless style
|
||||
/// prototypes (vitals/chat/toolbar text — the base prototype has no children).</summary>
|
||||
/// <summary>True when a pure-container inheritor needs the mounted-base Z-layer
|
||||
/// correction. Child inheritance itself is unconditional and follows retail
|
||||
/// <c>ElementDesc::Incorporate</c> (0x0069B5A0).</summary>
|
||||
internal static bool ShouldMountBaseChildren(int derivedChildCount, int derivedMediaCount, int baseChildCount)
|
||||
=> derivedChildCount == 0 && derivedMediaCount == 0 && baseChildCount > 0;
|
||||
|
||||
|
|
@ -316,7 +314,7 @@ public static class LayoutImporter
|
|||
// Read this element's own fields + media (no inheritance, no children yet).
|
||||
var self = ToInfo(d);
|
||||
var result = self;
|
||||
List<ElementInfo>? baseChildren = null;
|
||||
ElementInfo? baseInfo = null;
|
||||
|
||||
// Apply BaseElement / BaseLayoutId inheritance if present.
|
||||
if (d.BaseElement != 0 && d.BaseLayoutId != 0
|
||||
|
|
@ -327,32 +325,23 @@ public static class LayoutImporter
|
|||
if (baseDesc is not null)
|
||||
{
|
||||
// Recurse the base chain (already guarded by the HashSet add above).
|
||||
var baseInfo = Resolve(dats, baseDesc, baseChain);
|
||||
baseInfo = Resolve(dats, baseDesc, baseChain);
|
||||
// Derived fields override the base; children are attached below.
|
||||
result = ElementReader.Merge(baseInfo, self);
|
||||
baseChildren = baseInfo.Children; // capture for the sub-window mount
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve + attach children. Each child gets a FRESH base-chain set:
|
||||
// the cycle guard is per-element, not shared across siblings.
|
||||
foreach (var kv in d.Children)
|
||||
{
|
||||
var child = Resolve(dats, kv.Value, new HashSet<(uint, uint)>());
|
||||
SetOriginalParentSize(child, result.Width, result.Height);
|
||||
result.Children.Add(child);
|
||||
}
|
||||
// Retail LayoutDesc::InqFullDesc (0x0069A520) recursively resolves the base,
|
||||
// then ElementDesc::Incorporate (0x0069B5A0) merges the complete child table:
|
||||
// base-only children remain, same-ID children incorporate recursively, and
|
||||
// derived-only children append after the retained base entries.
|
||||
IncorporateChildren(dats, result, baseInfo?.Children, d);
|
||||
|
||||
// Sub-window mount: a pure-container leaf (no own children, no own media) that inherits
|
||||
// from a base WITH content attaches the base's subtree. Targets the gmInventoryUI panels
|
||||
// (0x100001CD/CE/CF — Type-0, media-less, childless, BaseLayoutId → a gm*UI window);
|
||||
// inert for media-bearing inheritors (close button/title) and childless style prototypes
|
||||
// (vitals/chat/toolbar). See ShouldMountBaseChildren + the B-Grid spec.
|
||||
if (baseChildren is not null
|
||||
&& ShouldMountBaseChildren(d.Children.Count, self.StateMedia.Count, baseChildren.Count))
|
||||
// A pure-container sub-window mount needs one additional layer correction.
|
||||
// Child-table incorporation has already happened above for every inheritor.
|
||||
if (baseInfo is not null
|
||||
&& ShouldMountBaseChildren(d.Children.Count, self.StateMedia.Count, baseInfo.Children.Count))
|
||||
{
|
||||
result.Children.AddRange(baseChildren);
|
||||
|
||||
// The mounted slot's layer WITHIN THE FRAME is its OWN ZLevel, not the mounted
|
||||
// sub-window root's. The gm*UI sub-window roots carry ZLevel 1000 (their standalone
|
||||
// top-window layer); ElementReader.Merge's zero-wins-base rule made the slot (own
|
||||
|
|
@ -372,6 +361,57 @@ public static class LayoutImporter
|
|||
return result;
|
||||
}
|
||||
|
||||
private static void IncorporateChildren(
|
||||
DatCollection dats,
|
||||
ElementInfo result,
|
||||
IReadOnlyList<ElementInfo>? baseChildren,
|
||||
ElementDesc derived)
|
||||
{
|
||||
baseChildren ??= Array.Empty<ElementInfo>();
|
||||
var baseById = baseChildren.ToDictionary(child => child.Id);
|
||||
int retainedBaseCount = baseChildren.Count(child =>
|
||||
!derived.Children.ContainsKey(child.Id));
|
||||
|
||||
foreach (ElementInfo baseChild in baseChildren)
|
||||
{
|
||||
bool hasOverlay = derived.Children.TryGetValue(
|
||||
baseChild.Id, out ElementDesc? overlay);
|
||||
ElementInfo child = hasOverlay
|
||||
? IncorporateResolvedChild(dats, baseChild, overlay!)
|
||||
: baseChild;
|
||||
// Base-only descendants retain the base layout's design parent size;
|
||||
// UiLayoutPolicy then performs retail's base-to-derived parent resize.
|
||||
if (hasOverlay)
|
||||
SetOriginalParentSize(child, result.Width, result.Height);
|
||||
result.Children.Add(child);
|
||||
}
|
||||
|
||||
// Every new child receives a fresh base-chain set. Retail offsets its read order
|
||||
// by the count of inherited children not replaced by a same-ID overlay.
|
||||
foreach (var pair in derived.Children)
|
||||
{
|
||||
if (baseById.ContainsKey(pair.Key)) continue;
|
||||
ElementInfo child = Resolve(dats, pair.Value, new HashSet<(uint, uint)>());
|
||||
child.ReadOrder += checked((uint)retainedBaseCount);
|
||||
SetOriginalParentSize(child, result.Width, result.Height);
|
||||
result.Children.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
private static ElementInfo IncorporateResolvedChild(
|
||||
DatCollection dats,
|
||||
ElementInfo baseChild,
|
||||
ElementDesc derivedChild)
|
||||
{
|
||||
// ElementDesc::Incorporate consumes the partial child directly. It does not
|
||||
// independently re-resolve that child's BaseElement when the inherited table
|
||||
// already contains the same identity.
|
||||
ElementInfo self = ToInfo(derivedChild);
|
||||
ElementInfo result = ElementReader.Merge(baseChild, self);
|
||||
IncorporateChildren(dats, result, baseChild.Children, derivedChild);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an <see cref="ElementDesc"/>'s own scalar fields + state media into a
|
||||
/// fresh <see cref="ElementInfo"/>. No inheritance is applied; children are not
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue