feat(items): port retail shared cooldown overlays

Preserve public shared-cooldown metadata, resolve the authoritative cooldown enchantment with retail expiry semantics, and project the exact ten DAT-authored radial steps through the shared retained item-slot architecture.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 10:41:16 +02:00
parent 19e8863f4e
commit 6b1ae4fb76
27 changed files with 875 additions and 20 deletions

View file

@ -0,0 +1,62 @@
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Items;
using AcDream.Core.Spells;
namespace AcDream.App.Tests.UI.Layout;
public sealed class ItemCooldownUiControllerTests
{
[Fact]
public void Shared_group_updates_existing_and_future_UIItems_from_one_clock()
{
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject
{
ObjectId = 0x5001u,
CooldownId = 42u,
CooldownDuration = 30d,
});
var spellbook = new Spellbook();
spellbook.OnEnchantmentAdded(new ActiveEnchantmentRecord(
SpellId: 0x802Au,
LayerId: 1u,
Duration: 30d,
CasterGuid: 0u,
Bucket: Spellbook.CooldownBucket,
StartTime: 100d));
double now = 112.5d;
var root = new UiPanel();
var list = new UiItemList();
list.Cell.SetItem(0x5001u, 99u);
root.AddChild(list);
uint[] sprites = Enumerable.Range(1, 10)
.Select(index => 0x06000000u + (uint)index)
.ToArray();
ItemCooldownUiController controller = ItemCooldownUiController.Bind(
root,
spellbook,
objects,
() => now,
new ItemCooldownAssets(sprites));
Assert.Equal(sprites[5], list.Cell.ActiveCooldownSprite());
var future = new UiItemSlot();
future.SetItem(0x5001u, 100u);
list.AddItem(future);
Assert.Equal(sprites[5], future.ActiveCooldownSprite());
now = 129.999d;
controller.Tick();
Assert.Equal(sprites[0], list.Cell.ActiveCooldownSprite());
now = 130d;
controller.Tick();
Assert.Equal(0u, list.Cell.ActiveCooldownSprite());
Assert.Equal(0, spellbook.ActiveCount);
}
}

View file

@ -92,4 +92,27 @@ public class ItemListCellTemplateTests
// m_elem_Icon ItemSlot_Empty state is the brown/gold 0x06001A97 surface.
Assert.Equal(0x06001A97u, sprite);
}
[Fact]
public void Shared_UIItem_resolves_all_ten_cooldown_overlays()
{
var datDir = DatDir();
if (datDir is null) return;
using var dats = new DatCollection(datDir, DatAccessType.Read);
ItemCooldownAssets? assets = ItemCooldownAssets.TryLoad(dats);
Assert.NotNull(assets);
Assert.Equal(ItemCooldownAssets.OverlayCount, assets.Value.Sprites.Count);
Assert.Equal(
new uint[]
{
0x060067CFu, 0x060067D0u, 0x060067D1u, 0x060067D2u, 0x060067D3u,
0x060067D4u, 0x060067D5u, 0x060067D6u, 0x060067D7u, 0x060067D8u,
},
assets.Value.Sprites);
_out.WriteLine(
string.Join(", ", assets.Value.Sprites.Select(id => $"0x{id:X8}")));
}
}