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
|
|
@ -1911,6 +1911,64 @@ public sealed class GameWindow : IDisposable
|
||||||
|
|
||||||
// Phase D.5.1 — toolbar window, data-driven from LayoutDesc 0x21000016
|
// Phase D.5.1 — toolbar window, data-driven from LayoutDesc 0x21000016
|
||||||
// (gmToolbarUI). Mirrors the vitals/chat import+bind+mount pattern above.
|
// (gmToolbarUI). Mirrors the vitals/chat import+bind+mount pattern above.
|
||||||
|
|
||||||
|
// Read the shortcut-slot digit sprite DID arrays from LayoutDesc 0x21000037
|
||||||
|
// (the UIItem cell template): element 0x1000034A under composite 0x10000346,
|
||||||
|
// StateDesc.Properties[0x10000042] = peace digits, [0x10000043] = war digits.
|
||||||
|
// Retail ref: UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465);
|
||||||
|
// gmToolbarUI::RecvNotice_SetCombatMode (196610-196621) re-stamps per stance.
|
||||||
|
uint[]? toolbarPeaceDigits = null;
|
||||||
|
uint[]? toolbarWarDigits = null;
|
||||||
|
lock (_datLock)
|
||||||
|
{
|
||||||
|
var uiItemLd = _dats!.Get<DatReaderWriter.DBObjs.LayoutDesc>(0x21000037u);
|
||||||
|
if (uiItemLd is not null
|
||||||
|
&& uiItemLd.Elements.TryGetValue(0x10000346u, out var composite)
|
||||||
|
&& composite.Children.TryGetValue(0x1000034Au, out var shortcutNumElem)
|
||||||
|
&& shortcutNumElem.StateDesc is { } sd
|
||||||
|
&& sd.Properties is { } props)
|
||||||
|
{
|
||||||
|
// Mirror LayoutImporter.ReadState: Properties[key] is ArrayBaseProperty
|
||||||
|
// containing DataIdBaseProperty entries. Each DataIdBaseProperty.Value is
|
||||||
|
// the RenderSurface DID for that digit.
|
||||||
|
// Peace: property 0x10000042; War: property 0x10000043.
|
||||||
|
if (props.TryGetValue(0x10000042u, out var rawPeace)
|
||||||
|
&& rawPeace is DatReaderWriter.Types.ArrayBaseProperty arrPeace)
|
||||||
|
{
|
||||||
|
toolbarPeaceDigits = new uint[arrPeace.Value.Count];
|
||||||
|
for (int i = 0; i < arrPeace.Value.Count; i++)
|
||||||
|
if (arrPeace.Value[i] is DatReaderWriter.Types.DataIdBaseProperty d)
|
||||||
|
toolbarPeaceDigits[i] = d.Value;
|
||||||
|
}
|
||||||
|
if (props.TryGetValue(0x10000043u, out var rawWar)
|
||||||
|
&& rawWar is DatReaderWriter.Types.ArrayBaseProperty arrWar)
|
||||||
|
{
|
||||||
|
toolbarWarDigits = new uint[arrWar.Value.Count];
|
||||||
|
for (int i = 0; i < arrWar.Value.Count; i++)
|
||||||
|
if (arrWar.Value[i] is DatReaderWriter.Types.DataIdBaseProperty d)
|
||||||
|
toolbarWarDigits[i] = d.Value;
|
||||||
|
}
|
||||||
|
Console.WriteLine(toolbarPeaceDigits is not null
|
||||||
|
? $"[D.5.1] digit arrays loaded: peace={toolbarPeaceDigits.Length}, war={toolbarWarDigits?.Length ?? 0} entries."
|
||||||
|
: "[D.5.1] digit arrays: property 0x10000042 not found in element 0x1000034A — falling back to cited constants.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("[D.5.1] digit arrays: element 0x1000034A/0x10000346 not found in LayoutDesc 0x21000037 — falling back to cited constants.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cited-constant fallback (UIElement_UIItem::SetShortcutNum, decomp 229465 + dat probe).
|
||||||
|
// Used when the dat navigation above fails (e.g. missing LayoutDesc in older dat).
|
||||||
|
if (toolbarPeaceDigits is null)
|
||||||
|
toolbarPeaceDigits = new uint[]
|
||||||
|
{ 0x0600109Eu, 0x0600109Fu, 0x060010A0u, 0x060010A1u, 0x060010A2u,
|
||||||
|
0x060010A3u, 0x060010A4u, 0x060010A5u, 0x060010A6u };
|
||||||
|
if (toolbarWarDigits is null)
|
||||||
|
toolbarWarDigits = new uint[]
|
||||||
|
{ 0x06001ACCu, 0x06001ACDu, 0x06001ACEu, 0x06001ACFu, 0x06001AD0u,
|
||||||
|
0x06001AD1u, 0x06001AD2u, 0x06001AD3u, 0x06001AD4u };
|
||||||
|
|
||||||
AcDream.App.UI.Layout.ImportedLayout? toolbarLayout;
|
AcDream.App.UI.Layout.ImportedLayout? toolbarLayout;
|
||||||
lock (_datLock)
|
lock (_datLock)
|
||||||
toolbarLayout = AcDream.App.UI.Layout.LayoutImporter.Import(
|
toolbarLayout = AcDream.App.UI.Layout.LayoutImporter.Import(
|
||||||
|
|
@ -1922,7 +1980,9 @@ public sealed class GameWindow : IDisposable
|
||||||
() => Shortcuts,
|
() => Shortcuts,
|
||||||
iconIds: (type, icon, under, over) => iconComposer.GetIcon(type, icon, under, over),
|
iconIds: (type, icon, under, over) => iconComposer.GetIcon(type, icon, under, over),
|
||||||
useItem: guid => UseItemByGuid(guid),
|
useItem: guid => UseItemByGuid(guid),
|
||||||
combatState: Combat);
|
combatState: Combat,
|
||||||
|
peaceDigits: toolbarPeaceDigits,
|
||||||
|
warDigits: toolbarWarDigits);
|
||||||
|
|
||||||
var toolbarRoot = toolbarLayout.Root;
|
var toolbarRoot = toolbarLayout.Root;
|
||||||
toolbarRoot.Left = 10; toolbarRoot.Top = 300;
|
toolbarRoot.Left = 10; toolbarRoot.Top = 300;
|
||||||
|
|
|
||||||
|
|
@ -54,18 +54,31 @@ public sealed class ToolbarController
|
||||||
private readonly Func<ItemType, uint, uint, uint, uint> _iconIds; // (itemType, iconId, underlayId, overlayId) → GL tex
|
private readonly Func<ItemType, uint, uint, uint, uint> _iconIds; // (itemType, iconId, underlayId, overlayId) → GL tex
|
||||||
private readonly Action<uint> _useItem; // guid → fire UseObject
|
private readonly Action<uint> _useItem; // guid → fire UseObject
|
||||||
|
|
||||||
|
// Digit sprite DID arrays for slot labels (top row, numbers 1-9).
|
||||||
|
// Peace set: property 0x10000042; war set: property 0x10000043.
|
||||||
|
// Read from LayoutDesc 0x21000037, element 0x1000034A under composite 0x10000346.
|
||||||
|
// Retail ref: UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465);
|
||||||
|
// gmToolbarUI::RecvNotice_SetCombatMode (196610-196621) re-stamps per stance.
|
||||||
|
private uint[]? _peaceDigits;
|
||||||
|
private uint[]? _warDigits;
|
||||||
|
private bool _peace = true; // true = NonCombat (peace), false = any war stance
|
||||||
|
|
||||||
private ToolbarController(
|
private ToolbarController(
|
||||||
ImportedLayout layout,
|
ImportedLayout layout,
|
||||||
ItemRepository repo,
|
ItemRepository repo,
|
||||||
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> shortcuts,
|
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> shortcuts,
|
||||||
Func<ItemType, uint, uint, uint, uint> iconIds,
|
Func<ItemType, uint, uint, uint, uint> iconIds,
|
||||||
Action<uint> useItem,
|
Action<uint> useItem,
|
||||||
CombatState? combatState)
|
CombatState? combatState,
|
||||||
|
uint[]? peaceDigits,
|
||||||
|
uint[]? warDigits)
|
||||||
{
|
{
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_shortcuts = shortcuts;
|
_shortcuts = shortcuts;
|
||||||
_iconIds = iconIds;
|
_iconIds = iconIds;
|
||||||
_useItem = useItem;
|
_useItem = useItem;
|
||||||
|
_peaceDigits = peaceDigits;
|
||||||
|
_warDigits = warDigits;
|
||||||
|
|
||||||
for (int i = 0; i < SlotIds.Length; i++)
|
for (int i = 0; i < SlotIds.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -113,15 +126,25 @@ public sealed class ToolbarController
|
||||||
/// combat-mode indicator elements accordingly.
|
/// combat-mode indicator elements accordingly.
|
||||||
/// Pass null to skip live wiring (e.g. in unit tests that don't exercise the indicator).
|
/// Pass null to skip live wiring (e.g. in unit tests that don't exercise the indicator).
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="peaceDigits">
|
||||||
|
/// Peace-mode digit DID array (property 0x10000042 from LayoutDesc 0x21000037 element
|
||||||
|
/// 0x1000034A under composite 0x10000346). Index i → slot label digit (i+1) RenderSurface id.
|
||||||
|
/// Null if the dat lookup failed (no digits drawn). Retail reference:
|
||||||
|
/// UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465).
|
||||||
|
/// </param>
|
||||||
|
/// <param name="warDigits">War-mode digit DID array (property 0x10000043, same element).</param>
|
||||||
public static ToolbarController Bind(
|
public static ToolbarController Bind(
|
||||||
ImportedLayout layout,
|
ImportedLayout layout,
|
||||||
ItemRepository repo,
|
ItemRepository repo,
|
||||||
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> shortcuts,
|
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> shortcuts,
|
||||||
Func<ItemType, uint, uint, uint, uint> iconIds,
|
Func<ItemType, uint, uint, uint, uint> iconIds,
|
||||||
Action<uint> useItem,
|
Action<uint> useItem,
|
||||||
CombatState? combatState = null)
|
CombatState? combatState = null,
|
||||||
|
uint[]? peaceDigits = null,
|
||||||
|
uint[]? warDigits = null)
|
||||||
{
|
{
|
||||||
var c = new ToolbarController(layout, repo, shortcuts, iconIds, useItem, combatState);
|
var c = new ToolbarController(layout, repo, shortcuts, iconIds, useItem, combatState,
|
||||||
|
peaceDigits, warDigits);
|
||||||
c.Populate();
|
c.Populate();
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +174,11 @@ public sealed class ToolbarController
|
||||||
uint tex = _iconIds(item.Type, item.IconId, item.IconUnderlayId, item.IconOverlayId);
|
uint tex = _iconIds(item.Type, item.IconId, item.IconUnderlayId, item.IconOverlayId);
|
||||||
list.Cell.SetItem(sc.ObjectGuid, tex);
|
list.Cell.SetItem(sc.ObjectGuid, tex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-stamp slot number labels after any item change.
|
||||||
|
// Numbers show on ALL top-row slots regardless of item occupancy —
|
||||||
|
// the user's retail screenshot confirms numbers on empty top-row slots.
|
||||||
|
RestampShortcutNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -178,6 +206,35 @@ public sealed class ToolbarController
|
||||||
if (_combatIndicators[i] is { } e)
|
if (_combatIndicators[i] is { } e)
|
||||||
e.Visible = show[i];
|
e.Visible = show[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-stamp digit set: peace glyphs in NonCombat, war glyphs in any combat stance.
|
||||||
|
// Retail ref: gmToolbarUI::RecvNotice_SetCombatMode (acclient_2013_pseudo_c.txt:196610-196621).
|
||||||
|
_peace = (mode == CombatMode.NonCombat);
|
||||||
|
RestampShortcutNumbers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Push digit-array references and shortcut-number state into every slot cell.
|
||||||
|
/// Top row (indices 0–8): SetShortcutNum(i, _peace) — numbers 1–9 on ALL slots
|
||||||
|
/// including empty ones (confirmed from user's retail screenshot; the numbers are
|
||||||
|
/// slot LABELS, not item indicators).
|
||||||
|
/// Bottom row (indices 9–17): ClearShortcutNum() — retail shows no numbers there.
|
||||||
|
/// Retail ref: UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465);
|
||||||
|
/// gmToolbarUI::RecvNotice_SetCombatMode (196610-196621).
|
||||||
|
/// </summary>
|
||||||
|
private void RestampShortcutNumbers()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _slots.Length; i++)
|
||||||
|
{
|
||||||
|
var cell = _slots[i]?.Cell;
|
||||||
|
if (cell is null) continue;
|
||||||
|
cell.PeaceDigits = _peaceDigits;
|
||||||
|
cell.WarDigits = _warDigits;
|
||||||
|
if (i < 9)
|
||||||
|
cell.SetShortcutNum(i, _peace); // top row: slot label digits 1–9 always shown
|
||||||
|
else
|
||||||
|
cell.ClearShortcutNum(); // bottom row: no slot labels
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,40 @@ public sealed class UiItemSlot : UiElement
|
||||||
|
|
||||||
public void Clear() { ItemId = 0; IconTexture = 0; }
|
public void Clear() { ItemId = 0; IconTexture = 0; }
|
||||||
|
|
||||||
|
// ── Shortcut number (slot label) ─────────────────────────────────────────
|
||||||
|
// Port of UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465).
|
||||||
|
// Retail draws the digit on the cell's ShortcutNum sub-element, picking the
|
||||||
|
// digit image from a DID-array property: 0x10000042 (peace) / 0x10000043 (war),
|
||||||
|
// indexed by slot position. Each digit is a 32×32 PFID_A8R8G8B8 RenderSurface
|
||||||
|
// with the digit baked into the top-left corner (rest alpha=0), drawn Alphablend.
|
||||||
|
|
||||||
|
/// <summary>Slot position in the shortcut bar (0-indexed). -1 = no number (retail
|
||||||
|
/// SetVisible(0) when edi < 0). Top row: 0..8 → digits 1..9. Bottom row: -1.</summary>
|
||||||
|
public int ShortcutNum { get; private set; } = -1;
|
||||||
|
|
||||||
|
/// <summary>True = draw peace digit set; false = war digit set.</summary>
|
||||||
|
public bool ShortcutPeace { get; private set; } = true;
|
||||||
|
|
||||||
|
/// <summary>Peace digit DID array. Index i → digit (i+1) sprite RenderSurface id.
|
||||||
|
/// Injected by the controller after reading LayoutDesc 0x21000037.</summary>
|
||||||
|
public uint[]? PeaceDigits { get; set; }
|
||||||
|
|
||||||
|
/// <summary>War digit DID array. Same layout as PeaceDigits.</summary>
|
||||||
|
public uint[]? WarDigits { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Set the slot's shortcut position and combat stance so the correct digit
|
||||||
|
/// is drawn. Call with index 0..8 for the top row; pass peace=true for NonCombat.</summary>
|
||||||
|
public void SetShortcutNum(int index, bool peace)
|
||||||
|
{
|
||||||
|
ShortcutNum = index;
|
||||||
|
ShortcutPeace = peace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Clear the shortcut number label (hides the digit).</summary>
|
||||||
|
public void ClearShortcutNum() { ShortcutNum = -1; }
|
||||||
|
|
||||||
|
// ── Events / draw ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>Invoked by <see cref="OnEvent"/> when a left-button-down lands on
|
/// <summary>Invoked by <see cref="OnEvent"/> when a left-button-down lands on
|
||||||
/// a bound slot. Wired by <c>ToolbarController</c> to the use-item callback.</summary>
|
/// a bound slot. Wired by <c>ToolbarController</c> to the use-item callback.</summary>
|
||||||
public Action? Clicked { get; set; }
|
public Action? Clicked { get; set; }
|
||||||
|
|
@ -50,16 +84,37 @@ public sealed class UiItemSlot : UiElement
|
||||||
|
|
||||||
protected override void OnDraw(UiRenderContext ctx)
|
protected override void OnDraw(UiRenderContext ctx)
|
||||||
{
|
{
|
||||||
|
// Draw the icon (filled slot) or the empty-slot border. Both paths fall
|
||||||
|
// through to the digit draw below, so the slot label shows on all top-row
|
||||||
|
// slots regardless of whether they hold an item (retail screenshot confirms
|
||||||
|
// numbers on empty slots).
|
||||||
if (ItemId != 0 && IconTexture != 0)
|
if (ItemId != 0 && IconTexture != 0)
|
||||||
{
|
{
|
||||||
ctx.DrawSprite(IconTexture, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
|
ctx.DrawSprite(IconTexture, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (SpriteResolve is not null && EmptySprite != 0)
|
else if (SpriteResolve is not null && EmptySprite != 0)
|
||||||
{
|
{
|
||||||
var (tex, _, _) = SpriteResolve(EmptySprite);
|
var (tex, _, _) = SpriteResolve(EmptySprite);
|
||||||
if (tex != 0)
|
if (tex != 0)
|
||||||
ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
|
ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Digit overlay: UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465).
|
||||||
|
// Each digit image is corner-baked (glyph in top-left, rest alpha=0) so we
|
||||||
|
// draw it full-cell size and the transparent region is invisible. DrawMode=Alphablend.
|
||||||
|
if (ShortcutNum >= 0 && SpriteResolve is not null)
|
||||||
|
{
|
||||||
|
var arr = ShortcutPeace ? PeaceDigits : WarDigits;
|
||||||
|
if (arr is not null && ShortcutNum < arr.Length)
|
||||||
|
{
|
||||||
|
uint did = arr[ShortcutNum];
|
||||||
|
if (did != 0)
|
||||||
|
{
|
||||||
|
var (tex, _, _) = SpriteResolve(did);
|
||||||
|
if (tex != 0)
|
||||||
|
ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,4 +166,110 @@ public class ToolbarControllerTests
|
||||||
Assert.False(indicators[0x10000194u].Visible, "missile should be hidden in magic mode");
|
Assert.False(indicators[0x10000194u].Visible, "missile should be hidden in magic mode");
|
||||||
Assert.True (indicators[0x10000195u].Visible, "magic indicator should be visible");
|
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.ItemId);
|
||||||
Assert.Equal(0u, s.IconTexture);
|
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