using System; using System.Collections.Generic; namespace AcDream.Core.Items; /// /// Mutable client-side model of retail ShortCutManager::shortCuts_[18] /// (acclient.h:36492). Every present entry retains all three raw /// fields, including non-object records that the object-only /// gmToolbarUI does not render. /// public sealed class ShortcutStore { public const int SlotCount = 18; private readonly ShortcutEntry?[] _entries = new ShortcutEntry?[SlotCount]; /// /// Replace all slots from packed entries. Invalid signed indices are rejected exactly /// like ShortCutManager::AddShortCut @ 0x005D5790; valid entries are retained /// even when their object id is zero. /// public void Load(IEnumerable entries) { Array.Clear(_entries); foreach (var entry in entries) Set(entry); } /// Raw entry at , or null for absent/out-of-range. public ShortcutEntry? GetEntry(int slot) => (uint)slot < SlotCount ? _entries[slot] : null; /// Detached raw snapshot for planning a complete mutation before applying it. public ShortcutEntry?[] Snapshot() => (ShortcutEntry?[])_entries.Clone(); /// Visible object id at , or zero. public uint Get(int slot) => GetEntry(slot)?.ObjectId ?? 0u; /// /// Visual availability used by gmToolbarUI. A retained non-object entry remains /// invisible and therefore counts as an empty toolbar cell. /// public bool IsEmpty(int slot) => Get(slot) == 0u; public void Set(ShortcutEntry entry) { if ((uint)entry.Index < SlotCount) _entries[entry.Index] = entry; } /// Create/replace a retail object shortcut; toolbar-created records use spell word zero. public void SetItem(int slot, uint objectId) => Set(new ShortcutEntry(slot, objectId, 0u)); public void Remove(int slot) { if ((uint)slot < SlotCount) _entries[slot] = null; } }