Thank you, I am happy if it helps. That’s great that you are taking that direction! It is also good for me to refresh my knowledge regularly around these subjects and stay sharp 
For ammo, basically it is an inventory problem. You can generalize the system around the needs of the game or specialize it for the weapon system. I generally approach it as inventory, but with a specialized architecture for ammo management. The trick is keeping it fast and lightweight to query.
Ammo does not have to be a GAS attribute, and the weapon does not need its own ASC just to store ammo.
A common separation would be:
- Current Magazine Ammo → Weapon instance
- Reserve Ammo → Inventory / Ammo Component
- Magazine Size, Ammo Type, Ammo Per Shot → Weapon definition data
For CanShoot(), I generally have a very fast check between Weapon and Inventory first, then continue with weapon-side checks.
The decrement is usually handled on the weapon runtime state, while Inventory, UI, and other systems listen to the changes. That way the Weapon System stays independent as much as possible. The authoritative decrement should still happen on the server.
I usually handle these through components, objects, or subsystems rather than forcing everything into GAS. You don’t have to use GAS for every part.
Since this subject is preparation rather than deployment, when you change a weapon many things can happen:
CanSwapWeapon() → Have weapon? Is swapping blocked?
SwappingWeapon() → Weapon data, animation, states, etc.
ProcessWeapon() → Grant/remove abilities, get inventory data, magazine size, current magazine, reserve ammo, ammo type, notify UI or inventory if necessary.
WeaponEquipped() → Weapon becomes active and ready for firing, reloading, aiming, etc.
Since the other systems are informed about the weapon change, they can simply listen and react to the current weapon status.
One important GAS point: a normal BulletCost Gameplay Effect can only directly modify an attribute on the ASC it is applied to. If magazine ammo is stored as a normal variable on the weapon, the GE cannot directly subtract from it.
In that case, the ability can check ammo through CanActivate / CanShoot, then tell the weapon to consume ammo when the shot is confirmed. GAS coordinates the action, while the weapon owns the magazine state.
Ammo systems can vary. For example:
-
Weapon
-
Ammo Type
- Standard
- Fragmenting
- Long Range
These can simply be tags or data on the weapon. The current weapon checks which ammo types it supports, and the inventory returns the compatible ammo.
Sometimes these are not actual ammo types but perks or modifiers. In that case, they can simply be effects or tags that change projectile or impact behaviour.
Think of it as two systems:
Weapon System
- Current Magazine
- Fire / Reload
- Projectile / Impact
Inventory System
- Reserve Ammo
- Ammo Types
- Pickups
The weapon owns what is loaded.
The inventory owns what the player carries.
Reload moves ammo from Inventory to Weapon.
Firing removes ammo from the Weapon.
Ammo does not need to be a GAS attribute, and the weapon does not need its own ASC just for ammo.
PS: I generally make weapon systems data-driven. GAS is mostly responsible for orchestration: ability activation, deployment, impacts, damage, effects, and states. It does not necessarily need to own the actual weapon data.
For example, my weapon definition contains data such as:
- Deployment type
- Weapon ability
- Projectile / impact classes
- Projectile / impact tags and effects
- Fire rate
- Damage
- Magazine and reload settings
- Spread, recoil, and kickback
- Animations, sounds, and effects
The Weapon System and Inventory own and process this data, while GAS coordinates when and how the action happens.
So basically, GAS is the orchestrator, not necessarily the data owner.
When the player presses input and the ability deploys, it is all about what the weapon data and progression data say. The weapon deploys using that data, and the same data defines what the base projectile should do and which effects should happen on impact, such as ApplyTargetDamageOverTime, besides common things like decals and impact effects.
Of course, this is also a personal preference. I like data-driven design because it is much easier to manage, everything is centralized, and the data can be overridden at any time through GAS. It also becomes much easier to extend when the systems grow larger.