diff --git a/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs index b90a124da..8e44075ed 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs @@ -46,6 +46,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerC { get => _ammo; } + public override bool HasMagazine => false; public float Progress { diff --git a/Assets/Scripts/ServerShared/Behaviors/Weapon.cs b/Assets/Scripts/ServerShared/Behaviors/Weapon.cs index 62e4f4b14..f4cb9bc07 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Weapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Weapon.cs @@ -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); diff --git a/Assets/Scripts/UI/HUD/SchematicDisplay.cs b/Assets/Scripts/UI/HUD/SchematicDisplay.cs index 526a83c80..ac49c1297 100644 --- a/Assets/Scripts/UI/HUD/SchematicDisplay.cs +++ b/Assets/Scripts/UI/HUD/SchematicDisplay.cs @@ -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) diff --git a/tests/Aetheria.Shared.Tests/AmmunitionStockingTests.cs b/tests/Aetheria.Shared.Tests/AmmunitionStockingTests.cs index a5e7e53c9..2a52e1535 100644 --- a/tests/Aetheria.Shared.Tests/AmmunitionStockingTests.cs +++ b/tests/Aetheria.Shared.Tests/AmmunitionStockingTests.cs @@ -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().Single(item => item.Name == "Fed Gun").Behaviors.OfType().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().Single(item => item.Name == name); + var loadout = new Loadout { Name = "label", Hull = _cache.RefOf(_cache.GetAll().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(); + 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")); + } }