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:
parent
19e8863f4e
commit
6b1ae4fb76
27 changed files with 875 additions and 20 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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}")));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,24 @@ namespace AcDream.App.Tests.UI;
|
|||
|
||||
public class UiItemListTests
|
||||
{
|
||||
[Fact]
|
||||
public void CooldownPresentation_propagates_to_existing_and_future_cells()
|
||||
{
|
||||
var list = new UiItemList();
|
||||
uint[] sprites = Enumerable.Range(1, 10).Select(i => (uint)i).ToArray();
|
||||
Func<uint, int> provider = _ => 7;
|
||||
|
||||
list.CooldownSprites = sprites;
|
||||
list.CooldownStepProvider = provider;
|
||||
var future = new UiItemSlot();
|
||||
list.AddItem(future);
|
||||
|
||||
Assert.Same(sprites, list.Cell.CooldownSprites);
|
||||
Assert.Same(provider, list.Cell.CooldownStepProvider);
|
||||
Assert.Same(sprites, future.CooldownSprites);
|
||||
Assert.Same(provider, future.CooldownStepProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLeafWidget() => Assert.True(new UiItemList().ConsumesDatChildren);
|
||||
|
||||
|
|
|
|||
|
|
@ -194,4 +194,36 @@ public class UiItemSlotTests
|
|||
|
||||
[Fact]
|
||||
public void CapacityFrontSprite_default() => Assert.Equal(0x06004D23u, new UiItemSlot().CapacityFrontSprite);
|
||||
|
||||
[Fact]
|
||||
public void ActiveCooldownSprite_selects_the_exact_one_based_step()
|
||||
{
|
||||
uint[] sprites =
|
||||
[
|
||||
0x06000001u, 0x06000002u, 0x06000003u, 0x06000004u, 0x06000005u,
|
||||
0x06000006u, 0x06000007u, 0x06000008u, 0x06000009u, 0x0600000Au,
|
||||
];
|
||||
var slot = new UiItemSlot
|
||||
{
|
||||
CooldownSprites = sprites,
|
||||
CooldownStepProvider = id => id == 0x5001u ? 6 : 0,
|
||||
};
|
||||
slot.SetItem(0x5001u, 99u);
|
||||
|
||||
Assert.Equal(0x06000006u, slot.ActiveCooldownSprite());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveCooldownSprite_isHidden_for_empty_or_inactive_slot()
|
||||
{
|
||||
var slot = new UiItemSlot
|
||||
{
|
||||
CooldownSprites = Enumerable.Range(1, 10).Select(i => (uint)i).ToArray(),
|
||||
CooldownStepProvider = _ => 0,
|
||||
};
|
||||
|
||||
Assert.Equal(0u, slot.ActiveCooldownSprite());
|
||||
slot.SetItem(0x5001u, 99u);
|
||||
Assert.Equal(0u, slot.ActiveCooldownSprite());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue