fix(ui): resolve retail spell slot background

Keep shared shortcut digit overlays separate from the per-ItemList background. Resolve the magic favorite list through its cross-layout inherited cell prototype and use the pinned brown/gold ItemSlot_Empty surface instead of the blue toolbar slot.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-15 19:23:46 +02:00
parent e3605672bb
commit 09612f9981
9 changed files with 128 additions and 53 deletions

View file

@ -51,8 +51,8 @@ public class ItemListCellTemplateTests
{
uint sprite = ItemListCellTemplate.ResolveEmptySprite(dats, layout, elem);
_out.WriteLine($"{name}: list 0x{elem:X8} (layout 0x{layout:X8}) -> empty sprite 0x{sprite:X8}");
Assert.True(sprite != 0u, $"{name}: resolved 0 (no 0x1000000e attr / prototype / media)");
Assert.True(sprite != Generic, $"{name}: resolved the generic 0x060074CF — the bug, not the fix");
Assert.True(sprite != 0u, $"{name}: resolved 0 (no 0x1000000e attr / prototype / media)");
Assert.True(sprite != Generic, $"{name}: resolved the generic 0x060074CF — the bug, not the fix");
Assert.True((sprite & 0xFF000000u) == 0x06000000u, $"{name}: 0x{sprite:X8} is not a 0x06 RenderSurface id");
}
}
@ -72,4 +72,24 @@ public class ItemListCellTemplateTests
Assert.Equal(0x06000F6Eu, ItemListCellTemplate.ResolveEmptySprite(dats, BackpackLayout, SideBag));
Assert.Equal(0x06000F6Eu, ItemListCellTemplate.ResolveEmptySprite(dats, BackpackLayout, MainPack));
}
[Fact]
public void Spell_favorite_list_resolves_its_cross_layout_pinned_empty_sprite()
{
var datDir = DatDir();
if (datDir is null) return; // CI: no live dat — skip
using var dats = new DatCollection(datDir, DatAccessType.Read);
ElementInfo infos = Assert.IsType<ElementInfo>(
LayoutImporter.ImportInfos(dats, CombatUiController.LayoutId));
uint sprite = ItemListCellTemplate.ResolveEmptySprite(
dats, infos, SpellcastingUiController.FavoriteListId);
_out.WriteLine($"spell favorites -> empty sprite 0x{sprite:X8}");
// gmSpellcastingUI's ItemList is inherited from LayoutDesc 0x21000010.
// Its cell-template attr selects catalog prototype 0x10000341, whose
// m_elem_Icon ItemSlot_Empty state is the brown/gold 0x06001A97 surface.
Assert.Equal(0x06001A97u, sprite);
}
}

View file

@ -25,10 +25,12 @@ public sealed class SpellcastingUiControllerTests
uint[] regular = [101u, 102u, 103u, 104u, 105u, 106u, 107u, 108u, 109u];
uint[] ghosted = [201u, 202u, 203u, 204u, 205u, 206u, 207u, 208u, 209u];
uint[] empty = [301u, 302u, 303u, 304u, 305u, 306u, 307u, 308u, 309u];
var graphics = new UiShortcutSlotGraphics(regular, ghosted, empty, 0x060074CFu);
var digits = new UiShortcutDigitGraphics(regular, ghosted, empty);
using SpellcastingUiController controller = Bind(
layout, spellbook, objects, _ => { }, shortcutGraphics: graphics)!;
layout, spellbook, objects, _ => { },
shortcutDigits: digits,
emptySlotSprite: 0x06001A97u)!;
UiElement group = layout.FindElement(0x100000AAu)!;
UiItemList list = Descendants(group).OfType<UiItemList>().First();
@ -46,11 +48,15 @@ public sealed class SpellcastingUiControllerTests
{
UiCatalogSlot slot = Assert.IsType<UiCatalogSlot>(list.GetItem(i));
Assert.True(slot.IsEmptySlot);
Assert.Equal(0x06001A97u, slot.EmptySprite);
Assert.Equal(i, slot.ShortcutNum);
Assert.Same(empty, slot.ActiveDigitArray());
}
for (int i = 9; i < 13; i++)
{
Assert.Equal(0x06001A97u, list.GetItem(i)!.EmptySprite);
Assert.Equal(-1, list.GetItem(i)!.ShortcutNum);
}
var cast = Assert.IsType<UiButton>(
layout.FindElement(SpellcastingUiController.CastButtonId));
@ -222,7 +228,8 @@ public sealed class SpellcastingUiControllerTests
ClientObjectTable objects,
Action<uint> useItem,
Action<int, int, uint>? addFavorite = null,
UiShortcutSlotGraphics? shortcutGraphics = null)
UiShortcutDigitGraphics? shortcutDigits = null,
uint emptySlotSprite = 0u)
{
var casting = new SpellCastingController(
spellbook, () => null, () => 1u, () => { }, _ => { }, (_, _) => { }, _ => { });
@ -234,7 +241,8 @@ public sealed class SpellcastingUiControllerTests
new SelectionState(),
addFavorite ?? ((_, _, _) => { }),
(_, _) => { },
shortcutGraphics);
shortcutDigits,
emptySlotSprite);
}
private static void ApplyAnchors(UiElement parent)