HUD: a weapon says what its ammunition label shows; a beam reads its reserve alone

ConstantWeapon names an AmmoType but has no magazine (a crate per AmmoInterval straight from the cargo), so the label printed its unfilled
magazine ("1/N"). Weapon.AmmoReadout owns the label's text, HasMagazine says whether there is a magazine to show, and SchematicDisplay reads it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Nibu 2026-11-11 04:36:56 +00:00
commit 908712b2ee
4 changed files with 36 additions and 1 deletions

View file

@ -46,6 +46,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerC
{
get => _ammo;
}
public override bool HasMagazine => false;
public float Progress
{

View file

@ -104,6 +104,12 @@ public abstract class Weapon : ActivatedBehavior
public abstract int Ammo { get; }
// Rounds in the ship's cargo that reload this weapon's magazine: crates of AmmoType, 0 when the weapon draws none.
public int Reserve => _data.AmmoType.IsSet() ? Entity.CountItemsInCargo(_data.AmmoType.Key) : 0;
// Whether Ammo counts a magazine. A weapon that names an AmmoType and fires from one (InstantWeapon) does; a beam (ConstantWeapon) draws one
// crate per AmmoInterval straight from the cargo and has none.
public virtual bool HasMagazine => true;
// What the HUD's ammunition label shows: "magazine/reserve" for a magazine weapon, the reserve alone for one without a magazine, empty when
// the weapon draws no ammunition. The one rule the HUD reads, so the label cannot show a magazine a weapon does not have.
public string AmmoReadout => !_data.AmmoType.IsSet() ? "" : HasMagazine ? $"{Ammo}/{Reserve}" : Reserve.ToString();
public WeaponData WeaponData => _data;
public float RangeFactor(float at) => WeaponData.RangeFactor(MinRange, EffectiveRange, Range, _data.DamageAtMaxRange, at);

View file

@ -234,7 +234,7 @@ public class SchematicDisplay : MonoBehaviour
x.ListElement.CooldownFill.anchorMax = new Vector2(x.Cooldown.Progress, 1);
x.ListElement.DurabilityLabel.text = $"{(int)(x.Row.Item.EquippableItem.Durability / itemData.Durability * 100)}%";
if (x.Weapon.WeaponData.AmmoType.IsSet())
x.ListElement.AmmoLabel.text = $"{x.Weapon.Ammo}/{x.Weapon.Reserve}";
x.ListElement.AmmoLabel.text = x.Weapon.AmmoReadout;
}
if (x.ListElement.RangeLabel)

View file

@ -166,4 +166,32 @@ public sealed partial class RunStartTests
}
Assert.True(fed > 0, "fixture: some new-run player ship carries an ammunition-fed weapon");
}
// The HUD's ammunition label (Weapon.AmmoReadout): a magazine gun reads "magazine/reserve", a beam that names an AmmoType has no magazine (it
// draws a crate per interval) and reads its reserve alone, a weapon that draws nothing reads empty. Kills: a label that prints the beam's
// never-filled magazine as "0/N", or one that drops the magazine from a gun's.
[Fact]
public void TheAmmoLabelShowsAMagazineOnlyForAWeaponThatHasOne()
{
var (hull, _) = FedFit(bay: true);
var rounds = _cache.GetAll<WeaponItemData>().Single(item => item.Name == "Fed Gun").Behaviors.OfType<WeaponData>().Single().AmmoType;
_cache.Upsert(new WeaponItemData
{
Name = "Fed Beam", Hardpoint = HardpointType.Tool, Shape = new Shape(), Durability = 1,
Behaviors = { new ConstantWeaponData { AmmoType = rounds } }
});
EquippableItemData Design(string name) => _cache.GetAll<EquippableItemData>().Single(item => item.Name == name);
var loadout = new Loadout { Name = "label", Hull = _cache.RefOf(_cache.GetAll<HullData>().Single(h => h.Name == hull)), WeaponGroups = new int[0][] };
foreach (var (name, x) in new[] { ("Fed Gun", 3), ("Fed Beam", 2), ("Plain Gun", 1), ("Fed Bay", 4) })
loadout.Slots.Add(new LoadoutSlot { Position = int2(x, 3), Rotation = ItemRotation.None, Design = _cache.RefOf(Design(name)) });
var failures = new List<string>();
var ship = Loadouts.Materialize(_items, null, loadout, _ => null, failures);
Assert.True(failures.Count == 0, string.Join("; ", failures));
string Label(string name) => ship.Weapons.Single(weapon => _items.GetData(weapon.Item.EquippableItem).Name == name).AmmoReadout;
var reserve = LoadoutGenerator.AmmunitionCrates;
Assert.Equal(reserve.ToString(), Label("Fed Beam"));
Assert.Equal($"{ship.Weapons.Single(w => _items.GetData(w.Item.EquippableItem).Name == "Fed Gun").Ammo}/{reserve}", Label("Fed Gun"));
Assert.Equal("", Label("Plain Gun"));
}
}