How-To: Set Up a Cooldown
Goal: put an ability on cooldown for a duration that augments can modify, and understand why authoring a duration on the Gameplay Effect alone does nothing.
UCrimsonGameplayAbility::ApplyCooldown calls SpecHandle.Data->SetDuration(CooldownDuration, true), which overwrites and locks whatever Duration Magnitude you authored on the asset. The number comes from the ability's Ability.Property.Cooldown contract property.Worse, the effect is only applied at all when that number resolves above zero. An ability whose contract does not declare the property gets a duration of 0, the apply block is skipped entirely, and you get no cooldown, no warning and no log line. This is the single most common cooldown mistake in this plugin.
The two halves
| Piece | Supplies | Does NOT supply |
|---|---|---|
| The cooldown Gameplay Effect | The granted tag that blocks re-activation, and a Duration Policy of Has Duration. | The duration. It is overwritten at runtime. |
| The ability's contract | The duration in seconds, via a property tagged Ability.Property.Cooldown. | Any blocking. Without the effect's tag nothing stops activation. |
Both are required. Either one alone produces an ability that looks configured and does not go on cooldown.
Step 1 - create the cooldown effect
Create a GameplayEffect Blueprint, e.g. GE_Cooldown.
| Setting | Value | Why |
|---|---|---|
| Duration Policy | Has Duration | SetDuration sets the spec's duration, but the policy decides whether a duration applies at all. Instant or Infinite and the number is ignored. |
| Duration Magnitude | anything - leave the default | Overwritten by SetDuration(..., bLockDuration = true). Authoring it here is wasted effort and misleads the next reader. |
| Granted Tags | at least one tag, e.g. Cooldown.Ability.Fireball | This is the cooldown's identity. GAS CheckCooldown blocks activation by testing whether the ASC carries it. No granted tag means the effect applies and blocks nothing. |
| Modifiers | none | A cooldown effect is a tag carrier, not a stat change. |
Step 2 - declare the duration on the contract
ApplyCooldown and GetEffectiveCooldownDuration both read CrimsonGameplayTags::Ability_Property_Cooldown, which is the native tag Ability.Property.Cooldown. This is not configurable and not matched by parent tag.A property declared as
Ability.Property.CooldownTime, Ability.Cooldown or anything else is simply never read. The contract will look correct in the editor, the ability will validate, and no cooldown will ever apply.Open the ability's contract asset, add an entry to Properties, and set:
| Field | Value |
|---|---|
Tag | Ability.Property.Cooldown (exactly) |
Default Value | a float property definition, set to your cooldown in seconds |
Display Name | "Cooldown" - what augment tooltips show |
bAllowAdditive / bAllowMultiplicative | leave on so augments can shorten it |
bAllowOverride | off unless you want an augment to hard-set the value |
EvaluatePropertyCache, so an augment that shortens a cooldown is previewed automatically in tooltips as a before-and-after number. A duration baked into the effect asset could do neither.Step 3 - point the ability at the effect
On the ability Blueprint's Class Defaults, set Cooldowns -> Cooldown Gameplay Effect Class to your GE_Cooldown, and confirm its Ability Contract is the one you edited in step 2.
showdebug abilitysystem, which lists active effects and their remaining time on the target.One shared effect for the whole game
You do not need a cooldown effect per ability. Author one GE_Cooldown and point every ability at it - the duration already comes from each ability's own contract, and CooldownTags gives each ability its own cooldown identity.
| Piece | Supplies |
|---|---|
| The shared effect | Duration Policy Has Duration. Leave its Granted Tags empty. |
The ability's CooldownTags | This ability's cooldown identity, e.g. Cooldown.Ability.Fireball. |
| The ability's contract | The duration in seconds. |
Set CooldownTags on the ability Blueprint under Class Defaults -> Crimson | Ability | Cooldown. The tags are appended to the applied spec's DynamicGrantedTags and to GetCooldownTags(), which is what CheckCooldown tests - so what is granted and what is checked can never drift apart.
GetCooldownTags() appends rather than replaces. An ability that got its cooldown purely from a dedicated effect's granted tags, with CooldownTags left empty, behaves exactly as before. Adopt this per ability at your own pace.That is almost never what you want. Leave the shared effect's Granted Tags empty and let
CooldownTags carry identity. For a real global cooldown, see below - it is a different mechanism.GetCooldownTags(), so it cannot see the CooldownTags this plugin appends. Epic's own comment on the switch names this case as the reason it exists.The first time you open a project with CrimsonAbilitySystem enabled, it detects this and offers to write the setting for you along with the other required entries - see Quick Start. The manual equivalent, in
Config/DefaultEngine.ini:```ini
[SystemSettings]
AbilitySystem.WarnCooldownEffectWithoutTags=0
```
You are not losing the safety net.
UCrimsonGameplayAbility::IsDataValid replaces it with a check that understands the design: an error only when the effect grants nothing and CooldownTags is empty - the combination that really does leave an ability with no cooldown.Global cooldowns, silences and other lockouts
A cooldown answers "this specific ability is recharging". Anything that locks out a group of abilities - a global cooldown, a silence, a stun, being disarmed - is a different question and should not go through the cooldown system at all. Three separate mechanisms, and picking the wrong one shows up in the UI.
| You want | Mechanism | How |
|---|---|---|
| This ability recharges after use | Cooldown | CooldownTags + contract duration, as above. |
| Pressing any combat ability briefly locks the others (a GCD) | A separate effect + ActivationBlockedTags | Author GE_GlobalCooldown: Has Duration, a real duration on the asset (nothing overwrites it - it is not applied by ApplyCooldown), granting Cooldown.Global. Apply it from your ability base on activation. Every ability that respects the GCD lists Cooldown.Global in its Activation Blocked Tags. |
| Silenced, stunned, disarmed - a state blocks a whole class of abilities | Tag relationship mapping | The state effect grants Ability.State.Silenced; the ASC's tag relationship mapping blocks abilities carrying the matching category tag. The plugin already ships Ability.ActivateFail.Silenced so the failure widget prints the right reason. See How-To: Block and Cancel by Tag. |
GetCooldownTimeRemainingAndDuration, so a silence modeled as a cooldown draws a cooldown sweep on every affected slot instead of a silence treatment. And UCrimsonAbilitySystemComponent::ReduceCooldownsByTag and UCrimsonAugmentAction_ReduceCooldown operate on cooldown tags - a cooldown-reduction augment would happily cut the silence short.Blocking tags carry no duration and are invisible to both, which is exactly right.
CooldownTags recharge, Cooldown.Global in its Activation Blocked Tags for the GCD, and a silence category tag the relationship mapping blocks. They are independent axes, and FCrimsonAbilityStateSnapshot on the ability-state widget reports them separately - bOnCooldown for the first, FailureTags for the other two.When it does not work
OnCooldownStarted.Broadcast(CooldownDuration) fires outside the apply block, so it broadcasts even when nothing was applied. That splits the failure modes cleanly - bind it temporarily and print the value.
| Symptom | Cause | Fix |
|---|---|---|
Broadcast fires with 0.0 | The contract does not declare Ability.Property.Cooldown, or declares it under a different tag. | Step 2. Check the tag character for character. |
| Broadcast never fires | GetCooldownGameplayEffect() returned null - no effect class is set. | Step 3. |
| Broadcast fires with the right number, but the ability re-activates freely | The effect grants no tag and CooldownTags is empty, so CheckCooldown has nothing to test. | Set CooldownTags. UCrimsonGameplayAbility::IsDataValid flags this pairing at author time. |
| Cooldown applies but expires instantly | Duration Policy is Instant. | Step 1, Duration Policy. |
| One ability's cooldown blocks an unrelated ability | Both share a cooldown effect that grants a tag, so both grant and check that same tag. | Clear the shared effect's Granted Tags and give each ability its own CooldownTags - see above. A separate effect per ability is not the fix. |
| Validation error: "grants no tags" | Epic's check reads the effect asset and cannot see CooldownTags. | Set AbilitySystem.WarnCooldownEffectWithoutTags=0 - see the callout above. |
Cooldown recovery rate
The resolved duration is divided by GetCooldownRecoveryRate() before it is applied, so 2.0 halves every cooldown on that ability. It is a BlueprintNativeEvent returning 1.0 by default - override it to read a haste attribute. GetEffectiveCooldownDuration() returns the same post-rate number for UI.
UCrimsonAbilitySystemComponent::ReduceCooldownsByTag or the UCrimsonAugmentAction_ReduceCooldown augment action, both of which work on the granted tag - another reason the effect needs one.See also
- How-To: Declare an Ability Contract - where the duration property lives.
- How-To: Add Ability Costs - the same contract-property pattern for costs.
- How-To: Use Toggle, Charges and Channeling - the Charges shape reads the cooldown effect's first granted tag to drive recharging.
- Concept: Modifiers and Dynamic Tags - how an augment reaches the cooldown value.
- API Reference -> Ability lifecycle events -
OnApplyCooldownTags, broadcast after the effect lands.