feat(D.5.1): faithful toolbar slot numbers 1-9 (SetShortcutNum digit sprites, peace/war)
Port of UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465) and
gmToolbarUI::RecvNotice_SetCombatMode (196610-196621). The 9 top-row toolbar slots
show digit labels 1-9 at all times (even when empty — confirmed from the user''s
retail screenshot). The digit sprites are real 32x32 PFID_A8R8G8B8 RenderSurfaces
with glyphs baked into the top-left corner (rest alpha=0), drawn Alphablend over
the slot icon/empty sprite.
Digit DID arrays (peace: property 0x10000042, war: 0x10000043) are read at startup
from LayoutDesc 0x21000037 element 0x1000034A under composite 0x10000346 using the
same ArrayBaseProperty{DataIdBaseProperty} pattern as LayoutImporter.ReadState.
A cited-constant fallback (same confirmed dat ids) is used if the dat navigation
fails. The war glyph set (darker/golden glyphs) switches on any combat stance;
peace glyphs (lighter) restore on NonCombat — re-stamped by RestampShortcutNumbers()
called from both Populate() and SetCombatMode().
Changes:
- UiItemSlot: ShortcutNum/ShortcutPeace/PeaceDigits/WarDigits state; SetShortcutNum/
ClearShortcutNum; OnDraw restructured (no early return) so digit draws after icon.
- ToolbarController: _peaceDigits/_warDigits/_peace fields; Bind() gains peaceDigits/
warDigits optional params; RestampShortcutNumbers() helper; Populate() and
SetCombatMode() both call RestampShortcutNumbers().
- GameWindow: reads digit arrays under _datLock from LayoutDesc 0x21000037, passes to
Bind(); cited constants as fallback.
- Tests: 5 new UiItemSlotTests (SetShortcutNum/ClearShortcutNum state); 4 new
ToolbarControllerTests (top-row/bottom-row labels, peace/war switch, array injection).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f21dbfad80
commit
b2a812d1fa
5 changed files with 328 additions and 6 deletions
|
|
@ -166,4 +166,110 @@ public class ToolbarControllerTests
|
|||
Assert.False(indicators[0x10000194u].Visible, "missile should be hidden in magic mode");
|
||||
Assert.True (indicators[0x10000195u].Visible, "magic indicator should be visible");
|
||||
}
|
||||
|
||||
// ── D1: Shortcut number (slot label) tests ───────────────────────────────
|
||||
// Port of UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465);
|
||||
// gmToolbarUI::RecvNotice_SetCombatMode (196610-196621).
|
||||
|
||||
// Fake digit arrays: 9 peace entries (0x10..0x18), 9 war entries (0x20..0x28).
|
||||
private static readonly uint[] FakePeace = { 0x10u,0x11u,0x12u,0x13u,0x14u,0x15u,0x16u,0x17u,0x18u };
|
||||
private static readonly uint[] FakeWar = { 0x20u,0x21u,0x22u,0x23u,0x24u,0x25u,0x26u,0x27u,0x28u };
|
||||
|
||||
/// <summary>
|
||||
/// After Bind with peace/war digit arrays, top-row cells (indices 0–8) have
|
||||
/// ShortcutNum == i (the slot position) and ShortcutPeace == true (default NonCombat).
|
||||
/// Bottom-row cells (indices 9–17) have ShortcutNum == -1 (no label).
|
||||
/// Retail: numbers are slot LABELS — shown on ALL top-row slots including empty ones.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ShortcutNumbers_afterBind_topRowHasNumbers_bottomRowEmpty()
|
||||
{
|
||||
var (layout, slots, _) = FakeToolbar();
|
||||
var repo = new ItemRepository();
|
||||
|
||||
ToolbarController.Bind(layout, repo,
|
||||
() => Array.Empty<PlayerDescriptionParser.ShortcutEntry>(),
|
||||
iconIds: (_,_,_,_) => 0u, useItem: _ => { },
|
||||
peaceDigits: FakePeace, warDigits: FakeWar);
|
||||
|
||||
// Top row: ShortcutNum == slot index, peace == true.
|
||||
for (int i = 0; i < Row1.Length; i++)
|
||||
{
|
||||
var cell = slots[Row1[i]].Cell;
|
||||
Assert.Equal(i, cell.ShortcutNum);
|
||||
Assert.True(cell.ShortcutPeace, $"top-row slot {i} should be peace at NonCombat");
|
||||
}
|
||||
// Bottom row: no shortcut number.
|
||||
foreach (var id in Row2)
|
||||
Assert.Equal(-1, slots[id].Cell.ShortcutNum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// After SetCombatMode(Melee), top-row cells switch to ShortcutPeace == false (war).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ShortcutNumbers_setCombatModeWar_topRowUsesWarDigits()
|
||||
{
|
||||
var (layout, slots, _) = FakeToolbar();
|
||||
var repo = new ItemRepository();
|
||||
var ctrl = ToolbarController.Bind(layout, repo,
|
||||
() => Array.Empty<PlayerDescriptionParser.ShortcutEntry>(),
|
||||
iconIds: (_,_,_,_) => 0u, useItem: _ => { },
|
||||
peaceDigits: FakePeace, warDigits: FakeWar);
|
||||
|
||||
ctrl.SetCombatMode(CombatMode.Melee);
|
||||
|
||||
// Top row: still ShortcutNum == i, but now peace == false.
|
||||
for (int i = 0; i < Row1.Length; i++)
|
||||
{
|
||||
var cell = slots[Row1[i]].Cell;
|
||||
Assert.Equal(i, cell.ShortcutNum);
|
||||
Assert.False(cell.ShortcutPeace, $"top-row slot {i} should be war after Melee");
|
||||
}
|
||||
// Bottom row still has no number.
|
||||
foreach (var id in Row2)
|
||||
Assert.Equal(-1, slots[id].Cell.ShortcutNum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// After SetCombatMode back to NonCombat, top-row switches back to peace (ShortcutPeace == true).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ShortcutNumbers_backToNonCombat_restoresPeaceDigits()
|
||||
{
|
||||
var (layout, slots, _) = FakeToolbar();
|
||||
var repo = new ItemRepository();
|
||||
var ctrl = ToolbarController.Bind(layout, repo,
|
||||
() => Array.Empty<PlayerDescriptionParser.ShortcutEntry>(),
|
||||
iconIds: (_,_,_,_) => 0u, useItem: _ => { },
|
||||
peaceDigits: FakePeace, warDigits: FakeWar);
|
||||
|
||||
ctrl.SetCombatMode(CombatMode.Melee);
|
||||
ctrl.SetCombatMode(CombatMode.NonCombat);
|
||||
|
||||
for (int i = 0; i < Row1.Length; i++)
|
||||
Assert.True(slots[Row1[i]].Cell.ShortcutPeace,
|
||||
$"top-row slot {i} should be peace after returning to NonCombat");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Digit arrays are correctly injected into each cell (PeaceDigits + WarDigits references).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ShortcutNumbers_digitArraysInjected()
|
||||
{
|
||||
var (layout, slots, _) = FakeToolbar();
|
||||
var repo = new ItemRepository();
|
||||
|
||||
ToolbarController.Bind(layout, repo,
|
||||
() => Array.Empty<PlayerDescriptionParser.ShortcutEntry>(),
|
||||
iconIds: (_,_,_,_) => 0u, useItem: _ => { },
|
||||
peaceDigits: FakePeace, warDigits: FakeWar);
|
||||
|
||||
foreach (var id in Row1)
|
||||
{
|
||||
Assert.Same(FakePeace, slots[id].Cell.PeaceDigits);
|
||||
Assert.Same(FakeWar, slots[id].Cell.WarDigits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,4 +38,48 @@ public class UiItemSlotTests
|
|||
Assert.Equal(0u, s.ItemId);
|
||||
Assert.Equal(0u, s.IconTexture);
|
||||
}
|
||||
|
||||
// ── Shortcut number tests ────────────────────────────────────────────────
|
||||
// Port of UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465).
|
||||
|
||||
[Fact]
|
||||
public void ShortcutNum_defaultIsMinusOne()
|
||||
{
|
||||
var s = new UiItemSlot();
|
||||
Assert.Equal(-1, s.ShortcutNum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShortcutPeace_defaultIsTrue()
|
||||
{
|
||||
var s = new UiItemSlot();
|
||||
Assert.True(s.ShortcutPeace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetShortcutNum_setsIndexAndPeace()
|
||||
{
|
||||
var s = new UiItemSlot();
|
||||
s.SetShortcutNum(3, peace: false);
|
||||
Assert.Equal(3, s.ShortcutNum);
|
||||
Assert.False(s.ShortcutPeace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetShortcutNum_peaceTrue()
|
||||
{
|
||||
var s = new UiItemSlot();
|
||||
s.SetShortcutNum(0, peace: true);
|
||||
Assert.Equal(0, s.ShortcutNum);
|
||||
Assert.True(s.ShortcutPeace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearShortcutNum_setsMinusOne()
|
||||
{
|
||||
var s = new UiItemSlot();
|
||||
s.SetShortcutNum(5, peace: true);
|
||||
s.ClearShortcutNum();
|
||||
Assert.Equal(-1, s.ShortcutNum);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue