feat(ui): port retail toolbar ammo count
Resolve stacked thrown weapons versus separately equipped missile ammo exactly like gmToolbarUI, normalize a zero stack to one, and refresh the authored missile indicator from equipment and stack events. Keep the decision in pure Core with retained and real-DAT conformance coverage. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
400f01eef1
commit
564d39dfea
9 changed files with 330 additions and 7 deletions
|
|
@ -336,6 +336,17 @@ public class ToolbarControllerTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetailFixture_ammoNumberUsesAuthoredMissileIndicatorElement()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadToolbar();
|
||||
|
||||
var ammoIndicator = Assert.IsType<UiButton>(layout.FindElement(0x10000194u));
|
||||
|
||||
Assert.Equal(55f, ammoIndicator.Width);
|
||||
Assert.Equal(58f, ammoIndicator.Height);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseAndExamineButtons_dispatchSelectedObjectPolicy()
|
||||
{
|
||||
|
|
@ -387,6 +398,76 @@ public class ToolbarControllerTests
|
|||
|
||||
// ── C1: combat-mode indicator tests ─────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void AmmoNumber_usesStackableMissileWeapon_andTracksStackChanges()
|
||||
{
|
||||
const uint player = 0x50000001u;
|
||||
const uint thrownWeapon = 0x50000002u;
|
||||
const uint separateAmmo = 0x50000003u;
|
||||
var (layout, _, _) = FakeToolbar();
|
||||
var repo = new ClientObjectTable();
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = player, Type = ItemType.Creature });
|
||||
repo.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = thrownWeapon,
|
||||
StackSize = 7,
|
||||
StackSizeMax = 25,
|
||||
});
|
||||
repo.MoveItem(thrownWeapon, player, newEquipLocation: EquipMask.MissileWeapon);
|
||||
repo.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = separateAmmo,
|
||||
StackSize = 80,
|
||||
StackSizeMax = 100,
|
||||
});
|
||||
repo.MoveItem(separateAmmo, player, newEquipLocation: EquipMask.MissileAmmo);
|
||||
|
||||
ToolbarController.Bind(
|
||||
layout,
|
||||
repo,
|
||||
() => Array.Empty<ShortcutEntry>(),
|
||||
iconIds: (_, _, _, _, _) => 0u,
|
||||
useItem: _ => { },
|
||||
playerGuid: () => player);
|
||||
var ammoButton = (UiButton)layout.FindElement(0x10000194u)!;
|
||||
|
||||
Assert.Equal("7", ammoButton.Label);
|
||||
|
||||
Assert.True(repo.UpdateStackSize(thrownWeapon, 6, value: 0));
|
||||
Assert.Equal("6", ammoButton.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AmmoNumber_forLauncher_usesAmmoSlot_andClearsOnUnwield()
|
||||
{
|
||||
const uint player = 0x50000001u;
|
||||
const uint pack = 0x50000010u;
|
||||
const uint bow = 0x50000002u;
|
||||
const uint arrows = 0x50000003u;
|
||||
var (layout, _, _) = FakeToolbar();
|
||||
var repo = new ClientObjectTable();
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = player, Type = ItemType.Creature });
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = pack, Type = ItemType.Container });
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = bow, StackSize = 1, StackSizeMax = 1 });
|
||||
repo.MoveItem(bow, player, newEquipLocation: EquipMask.MissileWeapon);
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = arrows, StackSize = 42, StackSizeMax = 100 });
|
||||
repo.MoveItem(arrows, player, newEquipLocation: EquipMask.MissileAmmo);
|
||||
|
||||
ToolbarController.Bind(
|
||||
layout,
|
||||
repo,
|
||||
() => Array.Empty<ShortcutEntry>(),
|
||||
iconIds: (_, _, _, _, _) => 0u,
|
||||
useItem: _ => { },
|
||||
playerGuid: () => player);
|
||||
var ammoButton = (UiButton)layout.FindElement(0x10000194u)!;
|
||||
|
||||
Assert.Equal("42", ammoButton.Label);
|
||||
|
||||
Assert.True(repo.MoveItem(arrows, pack));
|
||||
Assert.Null(ammoButton.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CombatIndicators_allDispatchSharedRetailToggleCommand()
|
||||
{
|
||||
|
|
|
|||
65
tests/AcDream.Core.Tests/Items/ToolbarAmmoPolicyTests.cs
Normal file
65
tests/AcDream.Core.Tests/Items/ToolbarAmmoPolicyTests.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using AcDream.Core.Items;
|
||||
|
||||
namespace AcDream.Core.Tests.Items;
|
||||
|
||||
public sealed class ToolbarAmmoPolicyTests
|
||||
{
|
||||
[Fact]
|
||||
public void StackableMissileWeapon_isItsOwnAmmo()
|
||||
{
|
||||
var thrown = Item(1u, EquipMask.MissileWeapon, stack: 7, maxStack: 25);
|
||||
var separateAmmo = Item(2u, EquipMask.MissileAmmo, stack: 80, maxStack: 100);
|
||||
|
||||
ToolbarAmmoPolicy.Result result = ToolbarAmmoPolicy.Resolve(
|
||||
new[] { thrown, separateAmmo });
|
||||
|
||||
Assert.Equal(new ToolbarAmmoPolicy.Result(1u, 7), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonStackableLauncher_usesSeparateAmmoSlot()
|
||||
{
|
||||
var bow = Item(1u, EquipMask.MissileWeapon, stack: 1, maxStack: 1);
|
||||
var arrows = Item(2u, EquipMask.MissileAmmo, stack: 42, maxStack: 100);
|
||||
|
||||
ToolbarAmmoPolicy.Result result = ToolbarAmmoPolicy.Resolve(
|
||||
new[] { bow, arrows });
|
||||
|
||||
Assert.Equal(new ToolbarAmmoPolicy.Result(2u, 42), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingAmmo_isHidden_andWireZeroStackDisplaysOne()
|
||||
{
|
||||
Assert.Equal(default, ToolbarAmmoPolicy.Resolve(Array.Empty<ClientObject>()));
|
||||
|
||||
var arrows = Item(3u, EquipMask.MissileAmmo, stack: 0, maxStack: 100);
|
||||
Assert.Equal(
|
||||
new ToolbarAmmoPolicy.Result(3u, 1),
|
||||
ToolbarAmmoPolicy.Resolve(new[] { arrows }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstIntersectingInventoryPlacementWins()
|
||||
{
|
||||
var first = Item(1u, EquipMask.MissileAmmo | EquipMask.Held, stack: 10, maxStack: 100);
|
||||
var second = Item(2u, EquipMask.MissileAmmo, stack: 20, maxStack: 100);
|
||||
|
||||
Assert.Equal(
|
||||
new ToolbarAmmoPolicy.Result(1u, 10),
|
||||
ToolbarAmmoPolicy.Resolve(new[] { first, second }));
|
||||
}
|
||||
|
||||
private static ClientObject Item(
|
||||
uint id,
|
||||
EquipMask location,
|
||||
int stack,
|
||||
int maxStack)
|
||||
=> new()
|
||||
{
|
||||
ObjectId = id,
|
||||
CurrentlyEquippedLocation = location,
|
||||
StackSize = stack,
|
||||
StackSizeMax = maxStack,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue