How-To: Broadcast Ability Lifecycle Events

Goal: fire a gameplay tag at a fixed moment in an ability's life - activation, end, cost, cooldown, cast success, cast interrupt - so that a different ability can trigger off it, or a running ability can wait on it, without either side holding a reference to the other.

Prerequisites
Read How-To: Get the Building Blocks for the ASC and Concept: Gameplay Ability for the ability shapes. Your ability must derive from UCrimsonGameplayAbility (or one of its four shapes) - the hooks live on that base class, not on Epic's UGameplayAbility.

The six hooks

Every UCrimsonGameplayAbility carries an AbilityEventTags property of type FCrimsonAbilityEventTags. It is six independent FGameplayTagContainer fields, one per lifecycle moment. Leave a field empty and nothing is sent for that moment.

FieldFires whenAvailable on
OnActivatedTagsThe ability starts, from BroadcastActivationSideEffects.Every Crimson ability
OnEndedTagsThe ability ends, including when it is canceled.Every Crimson ability
OnApplyCostTagsAfter ApplyCost has run, including any AdditionalCosts.Every Crimson ability
OnApplyCooldownTagsAfter ApplyCooldown has applied the cooldown effect.Every Crimson ability
OnSucceedTagsA cast runs to completion, from CompleteCast.UCrimsonGameplayAbility_WithCastTime only
OnFailedTagsA cast is interrupted mid-flight.UCrimsonGameplayAbility_WithCastTime only
Succeed and Failed are cast-time only
UCrimsonGameplayAbility_Toggle, _Charges and _Channeling all derive straight from UCrimsonGameplayAbility, not from _WithCastTime. They show the OnSucceedTags and OnFailedTags fields in the details panel because the struct is shared, but nothing ever sends them. Use OnActivatedTags and OnEndedTags on those shapes.

One more edge: UCrimsonGameplayAbility_Charges overrides ApplyCooldown and returns early when a recharge timer is already running, so OnApplyCooldownTags fires on the first charge spent, not on every charge.

Step 1 - fill in the tags

Open your ability Blueprint, click Class Defaults, and find the Crimson > Ability Events category. Expand Ability Event Tags and add a tag to the container for the moment you want. Any gameplay tag works - it does not have to live under a particular parent - but a dedicated Event. branch keeps these separate from state tags.

Name them as events, not states
Event.Ability.Fireball.CastFinished reads as a moment in time. Ability.State.Casting reads as a condition that is true for a while. Mixing the two in one branch makes the tag picker hard to reason about later.
Verify
Nothing observable yet - a tag with no listener is sent and dropped. Step 2 gives it somewhere to land.

Step 2 - listen for the tag

Each tag goes out through UAbilitySystemBlueprintLibrary::SendGameplayEventToActor on the ability's owning actor, so it is an ordinary GAS gameplay event. That gives you two standard ways to receive it, and you do not have to pick just one.

You want toUseWhere you set it up
Start a whole other abilityAn ability triggerThe listening ability's Class Defaults -> Triggers
React inside an ability that is already runningThe Wait Gameplay Event ability taskThat ability's graph, or C++

To trigger an ability: open the listening ability Blueprint -> Class Defaults -> Triggers -> +. Set Trigger Tag to your tag and Trigger Source to Gameplay Event. The ability now activates whenever that tag is sent to the owner, and the event payload arrives on its Event ActivateAbilityFromEvent node instead of the usual Event ActivateAbility.

To react inside a running ability: in that ability's graph, Event ActivateAbility -> Wait Gameplay Event (set Event Tag to your tag) -> EventReceived. Tick Only Trigger Once off if the ability should keep listening for the whole activation.

ActivateAbilityFromEvent, not ActivateAbility
An ability activated by a trigger runs Event ActivateAbilityFromEvent. If you leave your logic on Event ActivateAbility the ability activates and immediately does nothing, which looks exactly like the trigger never fired.
Verify
Play in the editor and run the source ability. The listening ability activates at the right moment. Confirm with the console: showdebug abilitysystem lists active abilities on the target, and log LogAbilitySystem Verbose prints each activation attempt.

Step 3 - read the payload

Every one of these events carries the same FGameplayEventData, built by MakeGameplayEventDataFromEventPayload. Three fields are filled in:

FieldHoldsCast it to
InstigatorThe ability's owning actor.Your pawn or player state class
OptionalObjectOwner actor, owner pawn, owner player controller, the ability's family tags, and its FGameplayAbilitySpec.UCrimsonAbilityEventPayloadBase
OptionalObject2The ability instance that sent the event.UCrimsonGameplayAbility

From the payload pin: Break GameplayEventData -> Optional Object -> Cast To CrimsonAbilityEventPayloadBase. That gives you Get Owner Actor, Get Owner Pawn, Get Owner Player Controller, Get Ability Family Tags and Get Ability Spec. To find out which ability sent it, Optional Object 2 -> Cast To CrimsonGameplayAbility instead.

Family tags tell you what kind of ability sent it
Get Ability Family Tags lets one listener serve a whole class of abilities - listen on a broad tag, then branch on whether the family tags contain Ability.Family.Fire.
The payload object is reused
Each ability instance caches one UCrimsonAbilityEventPayloadBase and refills it per event rather than allocating a new one. Read what you need inside your handler - do not store the pointer and read it later, because the next event on that same ability will have overwritten it.

Where these run

All six broadcasts are authority only. The server sends them; the predicting client does not.

This matters because UCrimsonGameplayAbility defaults to LocalPredicted, so the ability's own code runs on both the owning client and the server. If the client sent these events too, an ability listening through a Trigger would activate on the client, predict itself, and RPC the server to activate it - while the server had already activated it off its own copy. That is a double activation, and with a short or InstancedPerExecution listener it lands as double damage.

MachineWhat happens
ServerSends the event. Any triggered ability activates here, once.
Owning clientDoes not send the event. It still runs the triggered ability, because GAS replicates the server's activation back to it.
Other clientsDo not send and do not receive it. Use a Gameplay Cue for anything they need to see.
Do not use these for local cosmetics
A triggered ability arrives on the owning client one round trip after the server sends it, so anything you hang off these events is latency-visible. For cosmetics that must land the instant the player presses the button, use the ability's own paired Blueprint events - On Cast Completed and friends run on both sides - or a Gameplay Cue. See Concept: Multiplayer and Prediction.

Ability events or augments?

Both systems let something react to an ability without the ability knowing about it, so it is easy to reach for the wrong one. They answer different questions.

Ability lifecycle eventsAugments
What it isA GAS gameplay event sent to the owner's ASC.A data asset equipped into one of an ability's augment slots.
Who owns the reactionThe listener. Any ability can subscribe without being told.The augment asset. It only runs while equipped.
Turned on and off at runtimeNo - it is part of the ability's defaults and always fires.Yes - that is the point. Equip and unequip per character.
Can change what the ability doesNo. It is a notification, sent after the fact.Yes. OnEffectApplying hands over the live spec through SpecHandleToModify.
Hook pointsSix fixed lifecycle moments.Six shipped Augment.Event.* tags plus any tag you declare.
Per-instance stateNone. Each event is standalone.Yes - stateful augments count hits, track stacks, and so on.
Runs onServer only.Server, and the owning client for OnActivate and OnEnd.
The one-line test
Does the reaction change what this ability does, and should a player be able to equip and unequip it? Use an augment. Does it start or advance something else, as a permanent part of how this ability works? Use a lifecycle event.

Worked examples

Use a lifecycle event: interrupting a caster opens a punish window. Fill Fireball's OnFailedTags with Event.Ability.Fireball.Interrupted. Every enemy ability that wants to punish an interrupted cast adds that trigger tag to its own Triggers array. The enemy designer never opens Fireball, the Fireball designer never learns which enemies punish, and adding a tenth punisher is a change to the tenth enemy only. An augment cannot do this cleanly: augments are equipped onto the caster's ability, so making the caster carry a slot whose only job is to help enemies punish them is backwards.

Use a lifecycle event: a resource system that no ability knows about. Put Event.Combat.CostPaid in OnApplyCostTags on every ability that spends mana. A single "mana burn" ability sits there with a Wait Gameplay Event task counting them. The abilities that spend mana have one tag between them and never reference the resource system.

Use an augment: Fireball burns on crit. The reaction has to modify Fireball's own damage effect before it lands, which is exactly what Augment.Event.OnEffectApplying and SpecHandleToModify exist for. A lifecycle event carries no effect spec and fires after the effect has already been applied, so it is the wrong tool - and this is build content the player equips, which is the augment system's whole job. See How-To: Create and Equip an Augment.

Use an augment: every third hit of a combo staggers. Counting hits across activations is per-instance state, which lifecycle events do not have. See How-To: Write a Stateful Augment.

They compose
Nothing stops an ability from doing both. A cast-time ability can fill OnSucceedTags so unrelated systems can react, and carry augment slots so players can reshape what the cast actually does. The augment machinery also re-broadcasts its own events on UCrimsonAbilitySystemComponent::OnAugmentEventBroadcast, which is how the combo system observes them - but subscribing to that is a C++ delegate, not a class-default checkbox like a trigger tag.

See also

  • Concept: Augment System - what augments are and when they run.
  • How-To: Add Your Own Augment Event - declaring a new Augment.Event.* tag.
  • How-To: Make a Cast-Time Ability - the only shape that sends OnSucceedTags and OnFailedTags.
  • How-To: Use Toggle, Charges and Channeling - the shapes that do not.
  • Concept: Multiplayer and Prediction - why these are authority only.
  • API Reference - the full FCrimsonAbilityEventTags and ECrimsonAbilityEventTagType surface.