Cooldown Gameplay Effect uses an attribute to set the duration, but duration doesn't change when the attribute does.

Hello,

I have a gameplay ability that uses a generic “ShootCooldown” gameplay effect.

The shoot cooldown simply gets the attribute “FireRate” and sets the duration to that magnitude.

However, the duration seems to only be affected by whatever triggers the cooldown first. Here, I fire the shotgun first, which has a Fire Rate of 1.25, and the duration is properly set to 1.25.

But on swapping to the Revolver, and thus changing the weapon attributes. The duration is still 1.25, even though the revolver has a Fire Rate of .75.

The issue occurs in the reverse as well. (Shooting with the Revolver and swapping to the Shotgun, making the Shotgun shoot faster.)
I’ve tried using a custom mod magnitude calculation class, both in blueprints, and C++, but the issue remains in both of those methods as well. Does anyone have any ideas what may be causing this? Thank you in advance for the help.

Hi there,

Everything seems to be fine, instanced ability, at first glance , gets the normal cooldown which simply reads the attribute snapshot correctly. Then somehow it doesn’t read the snapshot correctly even though we can clearly see the attribute is changing.

1 - First, I cannot see the stacking part in the effect. Let’s be sure that it is not stacking.

2 - A common cause would be the ASC still holding the old weapon FireRate when the cooldown spec is created. That would produce exactly the behaviour you describe: the cooldown uses the previous weapon’s rate. However, your debug screenshots appear to show the player’s FireRate changing correctly, so this becomes less likely unless the cooldown is applied before the new value is fully updated.

So you change weapon, spec gets created with the old CD → start shooting → you see the attribute changed, but actually the spec was already created earlier in the lifecycle.
This is quite common in fast-paced weapon systems and can get tricky when doing multiple CD tracking, Doom-style switching, and switch shots. So it is important to check this.

Simply print out what is created while changing weapons, and if there is a difference between them, then that is your culprit over there. If FireRate is correct there but the spec duration is still old, then I would inspect whether the same Gameplay Effect Spec or active cooldown effect is being reused or refreshed.

If nothing works, during actuation of ability (deployment of projectile) manually create spec everytime and apply outgoing spec for cooldown, that should fundamentally solve the issue.

Also, you can experiment with one thing:
Shoot → Wait for CD → Switch Weapon → Wait for CD, and so on.
If this is not buggy, the problem can be somewhere in how or when you apply the spec.

Edit: In this system, Source and Target are basically the same ASC. Try changing the capture source to Source as a test, since Source and Target captures can happen at different points in the spec lifecycle.

I can confirm that no stacking is taking place.

As for the values of Fire Rate printed on screen, both the base and current values are swapped correctly. And changing target to source didn’t affect the output:

I don’t actually know how to interact with the spec though. Especially since its in the dedicated cooldown slot, I don’t know how it truly works under the hood. The cooldown duration when I printed that out was the same as in the debug menu, .75 if the revolver was used first, and 1.25 if shotgun was used first. The cooldown itself is not buggy, its consistent. Just some fundamental misalignment with the timing as you said.

It seems like applying the cooldown in the event graph itself with a new spec everytime might be the way to go. I’m not familiar with making specs and applying them, how do you do that?

Edit: Changed some wording

Himm interesting, are you commiting ability on start?

Probably this is something easy that we are missing (generally like that)

However let’s give a shot

Simply ASC MakeOutGoingSpec for cooldown effect, I generally use tag ->AssignTagSetbyCallerMagnitude (Magnitude Read the Attribute at that moment)->ApplyGMPLEffectToSelf.

Remove the default cooldown from the ability slot and in the effect itself → change it to SetByCaller (Give the DataTag).
I generally do agnostic tags like Data.Magnitude.Cooldown etc. where applicable. That’s pretty much it. The upper and lower chains are just examples; use one tag and one application path.

So this will rather than Effect Reading attribute and writing, ability will read ->create spec and apply directly.

and yes , there is a timing problem / lifecycle problem here nothing else. As mentioned probably something simple, some misused tag or something else but let’s first try this so you can diagnose. Even you can do a separate generic cooldown for this manual application so maybe that way we can diagnose further the problem.

Yes, I do have a commit ability node in the graph, it fires in the very beginning. I removed the commit node, and added the blueprint recipe.

With this approach it works!

Weirdly though, when the cooldown class is removed from the defaults, even though there is no commit, it spams as if theres no cooldown. So having it in the cooldown effect default is necessary.

The ability has a bullet cost so I err on the side of keeping the commit. Plus, it doesn’t duplicate the cooldown effect when its applied by both blueprint and by cooldown effect class according to debug mode.

As you said, making a new spec does fundamentally solve the issue.

This is the current setup:

Hey, good that it’s working.

So you can keep commit, there is no reason not to keep it. When you have the CD, ability normally resolves to it and check it, when you don’t have default define it doesn’t automatically check its tag.

To do it manually that part, if you prefer to remove default cooldown, you can simply add to ability a ActivationBlockedTags-> GameplayAbilities.EffectStates.Cooldown and then your ability will not be activated and that’s the proper way of handling it manually.

.
.
.

Since the cooldown is solved couple of notes for you to consider since you are dealing with GAS and Combat Weapon systems with respect to what you are doing and these are architectural level advice that can help you design robust weapon systems considering this is a prototype.

1 - I see three abilities for one weapon, which is completely fine at this stage. Just something to consider as the system grows: Reload and Aim are common weapon actions, even though their behaviour can still vary between weapons.

One possible approach is to have a base Reload ability and a base Aim ability, then create child abilities only where a weapon needs different behaviour. That can help keep common logic and animations in one place without forcing every weapon into the same setup.

2 - The same idea can also be applied to weapon deployment. You could have a base WeaponDeployment ability that handles the common flow, such as checking whether the weapon can shoot, checking ammo, consuming ammo, applying cooldown, and triggering the shot.

Then child abilities such as Shotgun, Revolver, or Gatling can override only what is different: firing type, rate, spread, projectile count, recoil, charging, burst behaviour, bolt action, automatic fire, and so on.

Not everything has to live directly inside the ability either. Ammo, projectile spawning, traces, recoil, and damage can still stay in separate components or shared functions, while the ability mainly coordinates them.

3 - The reason I mention this is that cooldowns, rates of fire, costs, and firing behaviours can become harder to track once more weapons are added and it would cost manual crafting of each and every aspect of the weapon, rather than relying on its defination data.
Also, for a repeatedly used weapon ability, Instanced Per Actor may be more suitable than Instanced Per Execution. The ability instance is reused instead of creating a new one for every activation, and it can retain the state needed during firing.

For high-rate automatic weapons, you may also keep one ability active while it handles the firing loop, instead of activating a new ability separately for every bullet. The exact setup depends on prediction, cancellation, and how much state the ability needs to hold.

Way ahead of you on that first bit, the “shoot” ability is a parent class that all of the weapons create children of, the anim montages, amount of bullets to fire at once, and the type of gameplay cue for the flash and sound are all instance editable. As the weapons get more varied, I will definitely be overriding the use functions for things like autofire.

As for the 2nd, thats a really good point. Right now, the code just plays an equip animation on switch, but actually checking the different attributes and states could be really useful for scalability.

I don’t think its proper technique, but I hacked together a function in C++ that allows me to query a data table and set all of the base attributes and their clamps from data table rows.

Thank you so much for your help. This whole conversation has been extremely enlightening. Being able to talk to someone about it is so much more helpful than reading about it.

Actually, I do want to ask:

As I move out of the ability testing phase into the more data heavy resource part. How do you recommend storing the different ammo amounts? Right now, the data table has an entry for setting the base and current ammo amount, which obviously won’t cut it in the real game.

How would you go about setting up an ammo management system?

Does it have to be an attribute to function properly within the GAS system?

Should it be stored in the weapon blueprint? If it does need to be an attribute, does the weapon bp need to have an Ability System Component as well?

How would a “BulletCost” gameplay effect interact with a weapon attribute if the gameplay effect is with the owning character?

Edit: changed some wording

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 :slight_smile:

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.