fix(ui): keep spell components in authored foreground
Install examination formula icons as the template root UIRegion image, matching retail ClearImage/SetImage behavior while retaining the authored missing-component overlay. Add the real DAT template fixture and conformance coverage. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
d4ce64f56b
commit
2c3da8e153
14 changed files with 231 additions and 34 deletions
|
|
@ -115,8 +115,8 @@ and the final two-client portal-out/materialization observer flow are
|
|||
user-gated. Deterministic world-lifecycle automation protects fresh login,
|
||||
outdoor/world-edge/dungeon travel, same-location revisit, exact graceful
|
||||
disconnect, and uncapped fresh-process reconnect with JSON/PNG artifacts.
|
||||
Release builds; the appraisal formula-media/geometry correction passes
|
||||
3,555 App tests / 3 skips and 7,986 complete-solution tests / 5 skips.
|
||||
Release builds; the appraisal formula-media/foreground correction passes
|
||||
3,557 App tests / 3 skips and 7,988 complete-solution tests / 5 skips.
|
||||
|
||||
**Current work order:** the six-slice pre-M4 world-interaction completion
|
||||
program in `docs/plans/2026-07-23-world-interaction-completion.md`: favorite
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ and the final two-client portal-out/materialization observer flow are
|
|||
user-gated. Deterministic world-lifecycle automation protects fresh login,
|
||||
outdoor/world-edge/dungeon travel, same-location revisit, exact graceful
|
||||
disconnect, and uncapped fresh-process reconnect with JSON/PNG artifacts.
|
||||
Release builds; the appraisal formula-media/geometry correction passes
|
||||
3,555 App tests / 3 skips and 7,986 complete-solution tests / 5 skips.
|
||||
Release builds; the appraisal formula-media/foreground correction passes
|
||||
3,557 App tests / 3 skips and 7,988 complete-solution tests / 5 skips.
|
||||
|
||||
**Current work order:** the six-slice pre-M4 world-interaction completion
|
||||
program in `docs/plans/2026-07-23-world-interaction-completion.md`: favorite
|
||||
|
|
|
|||
|
|
@ -168,11 +168,14 @@ from a pending object appraisal to a spell releases exactly that appraisal
|
|||
transaction before showing the SpellPanel.
|
||||
The formula-media correction now passes the component's DAT icon DID to
|
||||
`GetSpellComponentIcon` instead of its WCID, so the authored cells render
|
||||
their actual scarab/taper images. ACE characters with component enforcement
|
||||
disabled show the modern scarab/prismatic formula rather than an inactive
|
||||
legacy recipe (IA-21). A per-window authored-geometry revision resets the
|
||||
obsolete saved 545-pixel examination height once to LayoutDesc's 310 x 400
|
||||
extent, preserving its position and all future retail-style user resizing.
|
||||
their actual scarab/taper images. Each image is installed as the authored
|
||||
template root UIRegion's own image, matching retail's `ClearImage`/`SetImage`
|
||||
path instead of sinking a synthetic child behind the window content. ACE
|
||||
characters with component enforcement disabled show the modern
|
||||
scarab/prismatic formula rather than an inactive legacy recipe (IA-21). A
|
||||
per-window authored-geometry revision resets the obsolete saved 545-pixel
|
||||
examination height once to LayoutDesc's 310 x 400 extent, preserving its
|
||||
position and all future retail-style user resizing.
|
||||
|
||||
**Files:** `src/AcDream.Core.Net/Messages/AppraiseInfoParser.cs`;
|
||||
`src/AcDream.App/UI/RetailUiRuntime.cs`;
|
||||
|
|
|
|||
|
|
@ -47,8 +47,10 @@ name/school/mana/duration/range/description and authored appropriate-formula
|
|||
component cells, including live missing-component state. The follow-up resolves
|
||||
those cells through the component icon DID, shows the modern
|
||||
scarab/prismatic formula for ACE characters whose component enforcement is
|
||||
disabled (IA-21), and one-time migrates stale saved examination dimensions to
|
||||
the authored 310 x 400 extent while preserving retail-style later resizing.
|
||||
disabled (IA-21), installs each icon through the shared retained
|
||||
`UIRegion::SetImage` seam so it keeps the authored foreground stack, and
|
||||
one-time migrates stale saved examination dimensions to the authored
|
||||
310 x 400 extent while preserving retail-style later resizing.
|
||||
Its connected visual gate is pending. Slice 3's first connected gate exposed
|
||||
three deeper defects, now corrected: the response enum mislabeled ACE's
|
||||
creature bit `0x0100` and dropped monster packets while retaining the busy
|
||||
|
|
|
|||
|
|
@ -114,9 +114,10 @@ SpellPanel locally, and no spell ID enters global object selection or the
|
|||
status-bar magnifier command. Exact spell fields and the current appropriate
|
||||
formula's authored component cells are automated. The correction resolves
|
||||
component icons by DAT DID, projects scarabs/prismatic tapers when ACE
|
||||
disables component enforcement (IA-21), and resets obsolete saved dimensions
|
||||
disables component enforcement (IA-21), installs each icon as its authored
|
||||
template root's own foreground image, and resets obsolete saved dimensions
|
||||
once to the authored 310 x 400 extent. The Release build with #228's 17
|
||||
tracked test warnings, 3,555 App tests / 3 skips, and 7,986 complete-solution
|
||||
tracked test warnings, 3,557 App tests / 3 skips, and 7,988 complete-solution
|
||||
tests / 5 skips pass; the connected visual gate is pending.
|
||||
Slice 3's first connected gate found and corrected the real remaining defects:
|
||||
ACE creature flag `0x0100` was mislabeled as a weapon profile and left the
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ authored 310 x 400 layout correction are implemented; their connected visual
|
|||
gate is pending. Favorite-spell press-time selection and right-click local
|
||||
SpellPanel examination are implemented. The follow-up now maps
|
||||
component-disabled ACE characters to the modern scarab/prismatic formula,
|
||||
resolves formula icons by their DAT icon DIDs, and migrates stale examination
|
||||
resolves formula icons by their DAT icon DIDs, installs those icons as each
|
||||
authored template root's own foreground image, and migrates stale examination
|
||||
dimensions once to the authored 310 x 400 extent. Their connected visual gate
|
||||
is pending.
|
||||
**Milestone:** M4 prerequisite/preamble.
|
||||
|
|
|
|||
|
|
@ -105,14 +105,21 @@ for each SCID in GetAppropriateSpellFormula(spell):
|
|||
component = SpellComponentTable[SCID]
|
||||
texture = GetSpellComponentIcon(component.iconDID)
|
||||
cell = AddItemFromTemplateList(0x1000032E)
|
||||
cell.icon = texture
|
||||
cell.ClearImage()
|
||||
cell.SetBlitMode(Blit_3Alpha)
|
||||
cell.SetImage(texture)
|
||||
cell.missingOverlay = !ComponentTracker.ComponentIsOwned(component.WCID)
|
||||
|
||||
initial floaty extent = LayoutDesc root extent = 310 x 400 pixels
|
||||
restore user position/size from player UI state
|
||||
```
|
||||
|
||||
The component icon lookup consumes the component's icon DID, not its WCID.
|
||||
`SetImage` replaces the template root UIRegion's own image; retail does not add
|
||||
a separate, negatively layered icon child. Template child `0x10000330` remains
|
||||
the missing-component overlay. `UIRegion::DrawHere @ 0x0069FA30` draws the
|
||||
root's image before its children for this template, preserving the authored
|
||||
icon/overlay foreground stack. The component icon lookup consumes the
|
||||
component's icon DID, not its WCID.
|
||||
acdream's retained-layout persistence carries an authored-geometry revision:
|
||||
when a shipped LayoutDesc extent correction invalidates an older saved size,
|
||||
only width/height reset once; position, visibility, and later user resizing
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ namespace AcDream.App.UI.Layout;
|
|||
|
||||
/// <summary>
|
||||
/// Instantiates the authored formula-icon template used by retail
|
||||
/// <c>SpellExamineUI::ExamineSpell @ 0x004B6210</c>. The controller replaces
|
||||
/// the template root's placeholder image with the resolved component icon while
|
||||
/// <c>SpellExamineUI::ExamineSpell @ 0x004B6210</c>. The controller installs
|
||||
/// the component icon as the template root's own <c>UIRegion</c> image while
|
||||
/// retaining child <c>0x10000330</c> as the missing-component overlay.
|
||||
/// </summary>
|
||||
public sealed class SpellExamineComponentTemplateFactory
|
||||
|
|
@ -62,20 +62,13 @@ public sealed class SpellExamineComponentTemplateFactory
|
|||
did => _fonts.TryGetValue(did, out UiDatFont? font)
|
||||
? font
|
||||
: _defaultFont);
|
||||
UiElement root = content.Root;
|
||||
if (root is UiDatElement datRoot)
|
||||
datRoot.MediaVisible = false;
|
||||
|
||||
var icon = new UiTextureElement
|
||||
if (content.Root is not UiDatElement root)
|
||||
{
|
||||
Width = root.Width,
|
||||
Height = root.Height,
|
||||
Anchors = AnchorEdges.Left | AnchorEdges.Top
|
||||
| AnchorEdges.Right | AnchorEdges.Bottom,
|
||||
Texture = iconTexture,
|
||||
ZOrder = int.MinValue / 2,
|
||||
};
|
||||
root.AddChild(icon);
|
||||
throw new InvalidOperationException(
|
||||
$"Retail spell-component template 0x{TemplateId:X8} "
|
||||
+ "must resolve to a UIRegion-compatible element.");
|
||||
}
|
||||
root.RuntimeImageTexture = iconTexture;
|
||||
|
||||
if (content.FindElement(MissingOverlayId) is { } missing)
|
||||
missing.Visible = !owned;
|
||||
|
|
|
|||
|
|
@ -165,8 +165,37 @@ public sealed class UiDatElement : UiElement, IUiDatStateful
|
|||
/// </summary>
|
||||
public bool MediaVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Runtime image installed on this region in place of its authored state media.
|
||||
/// A non-null value mirrors retail <c>UIRegion::ClearImage</c> followed by
|
||||
/// <c>UIRegion::SetImage</c>; zero deliberately leaves the region image-less.
|
||||
/// The image remains this element's own media, so authored descendants retain
|
||||
/// their normal foreground relationship.
|
||||
/// </summary>
|
||||
public uint? RuntimeImageTexture { get; set; }
|
||||
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
{
|
||||
if (MediaVisible && RuntimeImageTexture is uint runtimeTexture)
|
||||
{
|
||||
if (runtimeTexture != 0u)
|
||||
{
|
||||
ctx.DrawSprite(
|
||||
runtimeTexture,
|
||||
0f,
|
||||
0f,
|
||||
Width,
|
||||
Height,
|
||||
0f,
|
||||
0f,
|
||||
1f,
|
||||
1f,
|
||||
Vector4.One);
|
||||
}
|
||||
DrawLabel(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
var (file, _) = ActiveMedia();
|
||||
if (MediaVisible && file != 0)
|
||||
{
|
||||
|
|
@ -180,6 +209,11 @@ public sealed class UiDatElement : UiElement, IUiDatStateful
|
|||
}
|
||||
}
|
||||
|
||||
DrawLabel(ctx);
|
||||
}
|
||||
|
||||
private void DrawLabel(UiRenderContext ctx)
|
||||
{
|
||||
// Centered text label over the sprite (retail draws button captions as text;
|
||||
// their dat sprites are blank frames).
|
||||
if (Label is { Length: > 0 } label && LabelFont is { } lf)
|
||||
|
|
|
|||
|
|
@ -809,9 +809,8 @@ public sealed class AppraisalUiControllerTests
|
|||
Assert.Equal(
|
||||
new uint[] { 0x06000010u + 2_000u, 0x06000011u + 2_000u },
|
||||
formula.Children
|
||||
.SelectMany(cell => cell.Children)
|
||||
.OfType<UiTextureElement>()
|
||||
.Select(texture => texture.Texture));
|
||||
.OfType<UiDatElement>()
|
||||
.Select(cell => cell.RuntimeImageTexture!.Value));
|
||||
}
|
||||
|
||||
private static AppraisalUiController? Bind(
|
||||
|
|
|
|||
|
|
@ -116,6 +116,9 @@ public static class FixtureLoader
|
|||
public static ElementInfo LoadExaminationRowTemplateInfos()
|
||||
=> LoadInfos("examine_row_2100006B_10000166.json");
|
||||
|
||||
public static ElementInfo LoadExaminationComponentTemplateInfos()
|
||||
=> LoadInfos("examine_component_2100006B_1000032E.json");
|
||||
|
||||
public static ImportedLayout LoadPowerbar()
|
||||
=> LayoutImporter.Build(LoadPowerbarInfos(), _ => (0u, 0, 0), null);
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,8 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
(0x2100006Bu, 0x100005F2u, "examine_2100006B_100005F2.json"),
|
||||
(AppraisalUiController.LayoutId, CreatureAppraisalRowTemplateFactory.TemplateId,
|
||||
"examine_row_2100006B_10000166.json"),
|
||||
(AppraisalUiController.LayoutId, SpellExamineComponentTemplateFactory.TemplateId,
|
||||
"examine_component_2100006B_1000032E.json"),
|
||||
})
|
||||
{
|
||||
ElementInfo? panelPart = LayoutImporter.ImportInfos(dats, layoutId, rootId);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class SpellExamineComponentTemplateFactoryTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(false, true)]
|
||||
public void Create_InstallsIconOnRetailRootAndKeepsMissingOverlayAboveIt(
|
||||
bool owned,
|
||||
bool missingVisible)
|
||||
{
|
||||
ElementInfo template =
|
||||
FixtureLoader.LoadExaminationComponentTemplateInfos();
|
||||
var factory = new SpellExamineComponentTemplateFactory(
|
||||
template,
|
||||
did => (did + 1_000u, 32, 32),
|
||||
defaultFont: null);
|
||||
|
||||
UiDatElement root = Assert.IsType<UiDatElement>(
|
||||
factory.Create(0xABCDu, owned));
|
||||
UiDatElement missing = Assert.IsType<UiDatElement>(
|
||||
Assert.Single(root.Children));
|
||||
|
||||
Assert.Equal(SpellExamineComponentTemplateFactory.TemplateId, root.ElementId);
|
||||
Assert.Equal(0xABCDu, root.RuntimeImageTexture);
|
||||
Assert.Empty(root.Children.OfType<UiTextureElement>());
|
||||
Assert.Equal(
|
||||
SpellExamineComponentTemplateFactory.MissingOverlayId,
|
||||
missing.ElementId);
|
||||
Assert.Equal(missingVisible, missing.Visible);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"Id": 268436270,
|
||||
"Type": 3,
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Width": 32,
|
||||
"Height": 32,
|
||||
"OriginalParentWidth": 0,
|
||||
"OriginalParentHeight": 0,
|
||||
"HasOriginalParentSize": false,
|
||||
"Left": 0,
|
||||
"Top": 0,
|
||||
"Right": 0,
|
||||
"Bottom": 0,
|
||||
"ReadOrder": 1,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": null,
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": [
|
||||
{
|
||||
"Id": 268436272,
|
||||
"Type": 3,
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Width": 32,
|
||||
"Height": 32,
|
||||
"OriginalParentWidth": 32,
|
||||
"OriginalParentHeight": 32,
|
||||
"HasOriginalParentSize": true,
|
||||
"Left": 0,
|
||||
"Top": 0,
|
||||
"Right": 0,
|
||||
"Bottom": 0,
|
||||
"ReadOrder": 1,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": {
|
||||
"File": 100682946,
|
||||
"DrawMode": 3
|
||||
},
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {
|
||||
"59": {
|
||||
"Kind": 1,
|
||||
"MasterPropertyId": 59,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 0,
|
||||
"HJustify": 1,
|
||||
"VJustify": 1,
|
||||
"FontColor": null,
|
||||
"StateMedia": {
|
||||
"": {
|
||||
"Item1": 100682946,
|
||||
"Item2": 3
|
||||
}
|
||||
},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue