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
47
src/AcDream.Core/Items/ToolbarAmmoPolicy.cs
Normal file
47
src/AcDream.Core/Items/ToolbarAmmoPolicy.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
namespace AcDream.Core.Items;
|
||||
|
||||
/// <summary>
|
||||
/// Pure retail toolbar ammunition selection.
|
||||
/// Retail references: <c>gmToolbarUI::UpdateAmmoID @ 0x004BF210</c> and
|
||||
/// <c>gmToolbarUI::UpdateAmmoNumber @ 0x004BE9E0</c>.
|
||||
/// </summary>
|
||||
public static class ToolbarAmmoPolicy
|
||||
{
|
||||
public readonly record struct Result(uint ObjectId, int DisplayCount)
|
||||
{
|
||||
public bool IsVisible => ObjectId != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve the first equipped missile weapon. If it is stackable, retail
|
||||
/// treats that stack as its own ammunition (thrown weapons). Otherwise it
|
||||
/// resolves the first separately equipped missile-ammo object.
|
||||
/// </summary>
|
||||
public static Result Resolve(IReadOnlyList<ClientObject> inventoryPlacements)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(inventoryPlacements);
|
||||
|
||||
ClientObject? missileWeapon = FindAtLocation(
|
||||
inventoryPlacements,
|
||||
EquipMask.MissileWeapon);
|
||||
ClientObject? ammo = missileWeapon is { StackSizeMax: > 1 }
|
||||
? missileWeapon
|
||||
: FindAtLocation(inventoryPlacements, EquipMask.MissileAmmo);
|
||||
|
||||
return ammo is null
|
||||
? default
|
||||
: new Result(ammo.ObjectId, ammo.StackSize == 0 ? 1 : ammo.StackSize);
|
||||
}
|
||||
|
||||
private static ClientObject? FindAtLocation(
|
||||
IReadOnlyList<ClientObject> inventoryPlacements,
|
||||
EquipMask location)
|
||||
{
|
||||
// ACCWeenieObject::GetObjectAtLocation @ 0x0058CE00 returns the first
|
||||
// inventory placement whose location mask intersects the requested mask.
|
||||
foreach (ClientObject item in inventoryPlacements)
|
||||
if ((item.CurrentlyEquippedLocation & location) != 0)
|
||||
return item;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue