feat(ui): preserve exact retail shortcut records
Carry signed index, object id, and raw spell word losslessly through PlayerDescription, session storage, drag mutation, and AddShortcut wire serialization while keeping gmToolbarUI object-only. Retire AP-103 and record the live ACE relog persistence gate. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
e65119f0c6
commit
b5b230c860
21 changed files with 271 additions and 181 deletions
|
|
@ -1,3 +1,5 @@
|
|||
using AcDream.Core.Items;
|
||||
|
||||
namespace AcDream.App.UI;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -21,4 +23,5 @@ public sealed record ItemDragPayload(
|
|||
uint ObjId, // dragged weenie guid (retail itemID, +0x5FC)
|
||||
ItemDragSource SourceKind, // what kind of slot it left
|
||||
int SourceSlot, // the source cell's SlotIndex (retail m_lastShortcutNumDragged)
|
||||
UiItemSlot SourceCell); // back-ref: reorder-restore / clear source state / ghost
|
||||
UiItemSlot SourceCell, // back-ref: reorder-restore / clear source state / ghost
|
||||
ShortcutEntry? Shortcut = null); // lossless raw entry for shortcut-alias mutation
|
||||
|
|
|
|||
|
|
@ -63,12 +63,12 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
private readonly UiButton? _inventoryButton;
|
||||
private readonly ClientObjectTable _repo;
|
||||
private readonly CombatState? _combatState;
|
||||
private readonly Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> _shortcuts;
|
||||
private readonly Func<IReadOnlyList<ShortcutEntry>> _shortcuts;
|
||||
private readonly Func<ItemType, uint, uint, uint, uint, uint> _iconIds; // (itemType, icon, underlay, overlay, effects) → GL tex
|
||||
private readonly Action<uint> _useItem; // guid → fire UseObject
|
||||
private readonly AcDream.Core.Items.ShortcutStore _store = new();
|
||||
private readonly ShortcutStore _store = new();
|
||||
private bool _storeLoaded;
|
||||
private readonly Action<uint, uint>? _sendAddShortcut; // (index, objectGuid)
|
||||
private readonly Action<ShortcutEntry>? _sendAddShortcut;
|
||||
private readonly Action<uint>? _sendRemoveShortcut; // (index)
|
||||
private readonly ItemInteractionController? _itemInteraction;
|
||||
private readonly Action<uint>? _selectItem;
|
||||
|
|
@ -89,7 +89,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
private ToolbarController(
|
||||
ImportedLayout layout,
|
||||
ClientObjectTable repo,
|
||||
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> shortcuts,
|
||||
Func<IReadOnlyList<ShortcutEntry>> shortcuts,
|
||||
Func<ItemType, uint, uint, uint, uint, uint> iconIds,
|
||||
Action<uint> useItem,
|
||||
CombatState? combatState,
|
||||
|
|
@ -97,7 +97,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
uint[]? warDigits,
|
||||
uint[]? emptyDigits,
|
||||
ItemInteractionController? itemInteraction = null,
|
||||
Action<uint, uint>? sendAddShortcut = null,
|
||||
Action<ShortcutEntry>? sendAddShortcut = null,
|
||||
Action<uint>? sendRemoveShortcut = null,
|
||||
Action? toggleCombat = null,
|
||||
Action<uint>? selectItem = null)
|
||||
|
|
@ -189,7 +189,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
}
|
||||
// Store not yet loaded — fall back to the shortcuts provider (pre-PD window).
|
||||
foreach (var sc in _shortcuts())
|
||||
if (sc.ObjectGuid == guid) return true;
|
||||
if (sc.ObjectId == guid) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
public static ToolbarController Bind(
|
||||
ImportedLayout layout,
|
||||
ClientObjectTable repo,
|
||||
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> shortcuts,
|
||||
Func<IReadOnlyList<ShortcutEntry>> shortcuts,
|
||||
Func<ItemType, uint, uint, uint, uint, uint> iconIds,
|
||||
Action<uint> useItem,
|
||||
CombatState? combatState = null,
|
||||
|
|
@ -234,7 +234,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
uint[]? warDigits = null,
|
||||
uint[]? emptyDigits = null,
|
||||
ItemInteractionController? itemInteraction = null,
|
||||
Action<uint, uint>? sendAddShortcut = null,
|
||||
Action<ShortcutEntry>? sendAddShortcut = null,
|
||||
Action<uint>? sendRemoveShortcut = null,
|
||||
Action? toggleCombat = null,
|
||||
Action<uint>? selectItem = null)
|
||||
|
|
@ -288,7 +288,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
// after Bind); thereafter the store is authoritative and drag ops mutate it directly.
|
||||
if (!_storeLoaded && _shortcuts().Count > 0)
|
||||
{
|
||||
_store.Load(System.Linq.Enumerable.Select(_shortcuts(), e => ((int)e.Index, e.ObjectGuid)));
|
||||
_store.Load(_shortcuts());
|
||||
_storeLoaded = true;
|
||||
}
|
||||
|
||||
|
|
@ -296,14 +296,15 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
|
||||
for (int slot = 0; slot < _slots.Length; slot++)
|
||||
{
|
||||
uint guid = _store.Get(slot);
|
||||
ShortcutEntry? entry = _store.GetEntry(slot);
|
||||
uint guid = entry?.ObjectId ?? 0u;
|
||||
if (guid == 0) continue;
|
||||
var list = _slots[slot];
|
||||
if (list is null) continue;
|
||||
var item = _repo.Get(guid);
|
||||
if (item is null) continue; // deferred: ObjectAdded re-calls Populate
|
||||
uint tex = _iconIds(item.Type, item.IconId, item.IconUnderlayId, item.IconOverlayId, item.Effects);
|
||||
list.Cell.SetItem(guid, tex);
|
||||
list.Cell.SetItem(guid, tex, entry);
|
||||
}
|
||||
|
||||
// Re-stamp slot number labels after any item change.
|
||||
|
|
@ -466,8 +467,9 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
if (empty < 0)
|
||||
return false;
|
||||
|
||||
_store.Set(empty, itemId);
|
||||
_sendAddShortcut?.Invoke((uint)empty, itemId);
|
||||
var entry = new ShortcutEntry(empty, itemId, 0u);
|
||||
_store.Set(entry);
|
||||
_sendAddShortcut?.Invoke(entry);
|
||||
Populate();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -476,7 +478,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
{
|
||||
if (_storeLoaded)
|
||||
return;
|
||||
_store.Load(System.Linq.Enumerable.Select(_shortcuts(), e => ((int)e.Index, e.ObjectGuid)));
|
||||
_store.Load(_shortcuts());
|
||||
_storeLoaded = true;
|
||||
}
|
||||
|
||||
|
|
@ -510,14 +512,18 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
// evict the target's occupant, place the dragged item there, and bump the evicted item into
|
||||
// the (now-vacated) source slot if it's free.
|
||||
int target = targetCell.SlotIndex;
|
||||
uint evicted = _store.Get(target); // RemoveShortCutInSlotNum(target)
|
||||
ShortcutEntry? evictedEntry = _store.GetEntry(target); // RemoveShortCutInSlotNum(target)
|
||||
uint evicted = evictedEntry?.ObjectId ?? 0u;
|
||||
if (evicted != 0) { _store.Remove(target); _sendRemoveShortcut?.Invoke((uint)target); }
|
||||
_store.Set(target, payload.ObjId); // AddShortcut(dragged, target)
|
||||
_sendAddShortcut?.Invoke((uint)target, payload.ObjId);
|
||||
ShortcutEntry dragged = (payload.Shortcut ?? new ShortcutEntry(payload.SourceSlot, payload.ObjId, 0u))
|
||||
.WithIndex(target);
|
||||
_store.Set(dragged); // AddShortcut(dragged, target)
|
||||
_sendAddShortcut?.Invoke(dragged);
|
||||
if (evicted != 0 && evicted != payload.ObjId && _store.IsEmpty(payload.SourceSlot))
|
||||
{ // displaced → vacated source slot
|
||||
_store.Set(payload.SourceSlot, evicted);
|
||||
_sendAddShortcut?.Invoke((uint)payload.SourceSlot, evicted);
|
||||
ShortcutEntry displaced = evictedEntry!.Value.WithIndex(payload.SourceSlot);
|
||||
_store.Set(displaced);
|
||||
_sendAddShortcut?.Invoke(displaced);
|
||||
}
|
||||
Populate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ public sealed record RadarRuntimeBindings(
|
|||
|
||||
public sealed record ToolbarRuntimeBindings(
|
||||
ClientObjectTable Objects,
|
||||
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> Shortcuts,
|
||||
Func<IReadOnlyList<ShortcutEntry>> Shortcuts,
|
||||
Func<ItemType, uint, uint, uint, uint, uint> ResolveIcon,
|
||||
Action<uint> UseItem,
|
||||
CombatState Combat,
|
||||
Action ToggleCombat,
|
||||
ItemInteractionController ItemInteraction,
|
||||
Action<uint, uint>? SendAddShortcut,
|
||||
Action<ShortcutEntry>? SendAddShortcut,
|
||||
Action<uint>? SendRemoveShortcut,
|
||||
SelectionState Selection,
|
||||
Action<Action<uint, float>> SubscribeHealthChanged,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Items;
|
||||
|
||||
namespace AcDream.App.UI;
|
||||
|
||||
|
|
@ -21,6 +22,12 @@ public sealed class UiItemSlot : UiElement
|
|||
/// <summary>Pre-composited icon GL texture for the bound item (0 = none).</summary>
|
||||
public uint IconTexture { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Lossless shortcut record when this cell belongs to the toolbar. Physical item
|
||||
/// lists leave it null. Snapshotted into the drag payload before remove-on-lift.
|
||||
/// </summary>
|
||||
public ShortcutEntry? Shortcut { get; private set; }
|
||||
|
||||
/// <summary>This cell's own index within its panel (0..17 toolbar; container slot
|
||||
/// for inventory). Distinct from <see cref="ShortcutNum"/> (the 1–9 label, -1 on the
|
||||
/// bottom row). Set by the controller; used as the drag payload's SourceSlot and to
|
||||
|
|
@ -79,17 +86,18 @@ public sealed class UiItemSlot : UiElement
|
|||
/// <summary>RenderSurface id -> (GL texture, w, h). Set by the factory/controller.</summary>
|
||||
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
|
||||
|
||||
public void SetItem(uint itemId, uint iconTexture)
|
||||
public void SetItem(uint itemId, uint iconTexture, ShortcutEntry? shortcut = null)
|
||||
{
|
||||
ItemId = itemId;
|
||||
IconTexture = iconTexture;
|
||||
Shortcut = shortcut;
|
||||
}
|
||||
|
||||
public void Clear() { ItemId = 0; IconTexture = 0; }
|
||||
public void Clear() { ItemId = 0; IconTexture = 0; Shortcut = null; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? GetDragPayload()
|
||||
=> ItemId != 0 ? new ItemDragPayload(ItemId, SourceKind, SlotIndex, this) : null;
|
||||
=> ItemId != 0 ? new ItemDragPayload(ItemId, SourceKind, SlotIndex, this, Shortcut) : null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (uint tex, int w, int h)? GetDragGhost()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue