diff --git a/Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs b/Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs
new file mode 100644
index 0000000000..f0f3b72d8d
--- /dev/null
+++ b/Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs
@@ -0,0 +1,70 @@
+using System.Collections.Generic;
+using Content.Client.Weapons.Ranged.Components;
+using Content.Shared.Prototypes;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.IntegrationTests.Tests;
+
+///
+/// Tests all entity prototypes with the MagazineVisualsComponent.
+///
+[TestFixture]
+public sealed class MagazineVisualsSpriteTest
+{
+ [Test]
+ public async Task MagazineVisualsSpritesExist()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+ var client = pair.Client;
+ var protoMan = client.ResolveDependency();
+ var componentFactory = client.ResolveDependency();
+
+ await client.WaitAssertion(() =>
+ {
+ foreach (var proto in protoMan.EnumeratePrototypes())
+ {
+ if (proto.Abstract || pair.IsTestPrototype(proto))
+ continue;
+
+ if (!proto.TryGetComponent(out var visuals, componentFactory))
+ continue;
+
+ Assert.That(proto.TryGetComponent(out var sprite, componentFactory),
+ @$"{proto.ID} has MagazineVisualsComponent but no SpriteComponent.");
+ Assert.That(proto.HasComponent(componentFactory),
+ @$"{proto.ID} has MagazineVisualsComponent but no AppearanceComponent.");
+
+ var toTest = new List<(int, string)>();
+ if (sprite.LayerMapTryGet(GunVisualLayers.Mag, out var magLayerId))
+ toTest.Add((magLayerId, ""));
+ if (sprite.LayerMapTryGet(GunVisualLayers.MagUnshaded, out var magUnshadedLayerId))
+ toTest.Add((magUnshadedLayerId, "-unshaded"));
+
+ Assert.That(toTest, Is.Not.Empty,
+ @$"{proto.ID} has MagazineVisualsComponent but no Mag or MagUnshaded layer map.");
+
+ var start = visuals.ZeroVisible ? 0 : 1;
+ foreach (var (id, midfix) in toTest)
+ {
+ Assert.That(sprite.TryGetLayer(id, out var layer));
+ var rsi = layer.ActualRsi;
+ for (var i = start; i < visuals.MagSteps; i++)
+ {
+ var state = $"{visuals.MagState}{midfix}-{i}";
+ Assert.That(rsi.TryGetState(state, out _),
+ @$"{proto.ID} has MagazineVisualsComponent with MagSteps = {visuals.MagSteps}, but {rsi.Path} doesn't have state {state}!");
+ }
+
+ // MagSteps includes the 0th step, so sometimes people are off by one.
+ var extraState = $"{visuals.MagState}{midfix}-{visuals.MagSteps}";
+ Assert.That(rsi.TryGetState(extraState, out _), Is.False,
+ @$"{proto.ID} has MagazineVisualsComponent with MagSteps = {visuals.MagSteps}, but more states exist!");
+ }
+ }
+ });
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/caseless_rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/caseless_rifle.yml
index 5aa1f285c8..b12deec582 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/caseless_rifle.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/caseless_rifle.yml
@@ -91,6 +91,10 @@
capacity: 99
- type: Sprite
sprite: Objects/Weapons/Guns/Ammunition/Magazine/CaselessRifle/10x24.rsi
+ - type: MagazineVisuals
+ magState: mag
+ steps: 8
+ zeroVisible: false
- type: entity
id: MagazinePistolCaselessRifle
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml
index 5784564378..c2bf5efeff 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml
@@ -80,6 +80,8 @@
layers:
- state: base
map: ["enum.GunVisualLayers.Base"]
+ - state: mag-1
+ map: ["enum.GunVisualLayers.Mag"]
- type: entity
id: MagazineLightRiflePractice
@@ -146,6 +148,6 @@
sprite: Objects/Weapons/Guns/Ammunition/Magazine/LightRifle/pk_box.rsi
- type: MagazineVisuals
magState: mag
- steps: 7
+ steps: 8
zeroVisible: false
- type: Appearance
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml
index b7378b9991..653ac8c068 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml
@@ -59,6 +59,8 @@
layers:
- state: base
map: ["enum.GunVisualLayers.Base"]
+ - state: mag-1
+ map: ["enum.GunVisualLayers.Mag"]
- type: entity
id: MagazineMagnum
@@ -129,6 +131,8 @@
layers:
- state: base
map: ["enum.GunVisualLayers.Base"]
+ - state: mag-1
+ map: ["enum.GunVisualLayers.Mag"]
- type: entity
id: MagazineMagnumSubMachineGun
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml
index b554a15a98..680ec5d795 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml
@@ -27,7 +27,7 @@
map: ["enum.GunVisualLayers.Mag"]
- type: MagazineVisuals
magState: mag
- steps: 5
+ steps: 6
zeroVisible: false
- type: Appearance
@@ -162,6 +162,8 @@
layers:
- state: base
map: ["enum.GunVisualLayers.Base"]
+ - state: mag-1
+ map: ["enum.GunVisualLayers.Mag"]
- type: entity
@@ -212,6 +214,8 @@
layers:
- state: base
map: ["enum.GunVisualLayers.Base"]
+ - state: mag-1
+ map: ["enum.GunVisualLayers.Mag"]
- type: entity
id: MagazinePistolHighCapacity
@@ -273,6 +277,8 @@
layers:
- state: base
map: ["enum.GunVisualLayers.Base"]
+ - state: mag-1
+ map: ["enum.GunVisualLayers.Mag"]
- type: entity
id: MagazinePistolSubMachineGunPractice
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/rifle.yml
index c882a3b2f1..8e15cb18ba 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/rifle.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/rifle.yml
@@ -59,6 +59,8 @@
layers:
- state: base
map: ["enum.GunVisualLayers.Base"]
+ - state: mag-1
+ map: ["enum.GunVisualLayers.Mag"]
- type: entity
id: MagazineRifleIncendiary
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SpeedLoaders/magnum.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SpeedLoaders/magnum.yml
index ae6bf0cebf..fd02fdf21b 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SpeedLoaders/magnum.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SpeedLoaders/magnum.yml
@@ -51,6 +51,11 @@
layers:
- state: base
map: [ "enum.GunVisualLayers.Base" ]
+ # TODO: This is actually a issue with all the speed loaders:
+ # You can mix different ammo types, but it will always
+ # use the one it was printed for.
+ - state: base-6
+ map: [ "enum.GunVisualLayers.Mag" ]
- type: entity
id: SpeedLoaderMagnumIncendiary
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
index c51aef5fe8..6426e59bd5 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
@@ -23,11 +23,6 @@
- type: Battery
maxCharge: 1000
startingCharge: 1000
- - type: MagazineVisuals
- magState: mag
- steps: 5
- zeroVisible: false
- - type: Appearance
- type: StaticPrice
price: 500
@@ -160,6 +155,11 @@
- state: mag-unshaded-4
map: ["enum.GunVisualLayers.MagUnshaded"]
shader: unshaded
+ - type: MagazineVisuals
+ magState: mag
+ steps: 5
+ zeroVisible: false
+ - type: Appearance
- type: Clothing
sprite: Objects/Weapons/Guns/Battery/makeshift.rsi
- type: HitscanBatteryAmmoProvider
@@ -212,6 +212,11 @@
- state: mag-unshaded-4
map: ["enum.GunVisualLayers.MagUnshaded"]
shader: unshaded
+ - type: MagazineVisuals
+ magState: mag
+ steps: 5
+ zeroVisible: false
+ - type: Appearance
- type: Clothing
sprite: Objects/Weapons/Guns/Battery/laser_gun.rsi
- type: Gun
@@ -250,6 +255,11 @@
- state: mag-unshaded-4
map: ["enum.GunVisualLayers.MagUnshaded"]
shader: unshaded
+ - type: MagazineVisuals
+ magState: mag
+ steps: 5
+ zeroVisible: false
+ - type: Appearance
- type: Clothing
sprite: Objects/Weapons/Guns/Battery/pulse_pistol.rsi
- type: Gun
@@ -279,6 +289,11 @@
- state: mag-unshaded-4
map: ["enum.GunVisualLayers.MagUnshaded"]
shader: unshaded
+ - type: MagazineVisuals
+ magState: mag
+ steps: 5
+ zeroVisible: false
+ - type: Appearance
- type: Clothing
sprite: Objects/Weapons/Guns/Battery/pulse_carbine.rsi
- type: Gun
@@ -310,6 +325,11 @@
- state: mag-unshaded-4
map: ["enum.GunVisualLayers.MagUnshaded"]
shader: unshaded
+ - type: MagazineVisuals
+ magState: mag
+ steps: 5
+ zeroVisible: false
+ - type: Appearance
- type: Clothing
sprite: Objects/Weapons/Guns/Battery/pulse_rifle.rsi
- type: Gun
@@ -337,6 +357,11 @@
- state: mag-unshaded-4
map: ["enum.GunVisualLayers.MagUnshaded"]
shader: unshaded
+ - type: MagazineVisuals
+ magState: mag
+ steps: 5
+ zeroVisible: false
+ - type: Appearance
- type: Clothing
sprite: Objects/Weapons/Guns/Battery/laser_cannon.rsi
- type: Gun
@@ -507,6 +532,7 @@
magState: mag
steps: 5
zeroVisible: true
+ - type: Appearance
- type: StaticPrice
price: 260
@@ -711,11 +737,6 @@
- type: BatterySelfRecharger
autoRecharge: true
autoRechargeRate: 40
- - type: MagazineVisuals
- magState: mag
- steps: 5
- zeroVisible: true
- - type: Appearance
- type: StaticPrice
price: 750
@@ -733,6 +754,11 @@
- state: mag-unshaded-4
map: ["enum.GunVisualLayers.MagUnshaded"]
shader: unshaded
+ - type: MagazineVisuals
+ magState: mag
+ steps: 5
+ zeroVisible: false
+ - type: Appearance
- type: Clothing
sprite: Objects/Weapons/Guns/Battery/energy_shotgun.rsi
- type: Gun
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/HMGs/hmgs.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/HMGs/hmgs.yml
index ebcba7d5e4..e1de308b12 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/HMGs/hmgs.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/HMGs/hmgs.yml
@@ -31,7 +31,6 @@
sprite: Objects/Weapons/Guns/HMGs/minigun.rsi
layers:
- state: icon
- map: ["enum.GunVisualLayers.Base"]
- type: Item
sprite: Objects/Weapons/Guns/HMGs/minigun.rsi
- type: Gun
@@ -41,11 +40,6 @@
- type: BallisticAmmoProvider
proto: CartridgeMinigun
capacity: 1000
- - type: MagazineVisuals
- magState: mag
- steps: 4
- zeroVisible: true
- - type: Appearance
- type: ContainerContainer
containers:
ballistic-ammo: !type:Container
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
index 4c48ec1f20..64f1fdac29 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
@@ -117,7 +117,7 @@
path: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
- type: MagazineVisuals
magState: mag
- steps: 1
+ steps: 2
zeroVisible: true
- type: Appearance