Action Modules
Per-node Blueprint behaviour units with NetMode gating — the universal extension hook
Every combo-graph node — attack nodes, the root node, Reset Combo nodes, any future node type — has a Modules array. Designers attach Blueprint subclasses of UActionModule to a node, picking which FGameplayTag each module reacts to. While that node is the active combo cursor, the component dispatches matching events to the modules — locally on whichever side(s) the module's NetMode allows.
This is the universal hook for per-node game logic: damage, VFX, sound, camera shakes, HUD updates, AI perception, state-tag flips — anything that should react to a moveset event on a specific node.
Built-in events
The component dispatches these to modules attached to the node that currently owns the combo cursor:
| Event tag | Fired when |
|---|---|
Moveset.Event.Ready |
After Apply Moveset finishes initialisation. |
Moveset.Event.AttackStarted |
A new attack montage begins (after cursor sync). |
Moveset.Event.AttackEnded |
An attack finishes naturally (montage timeout). |
Moveset.Event.Hit |
A registered hitbox detected a hit (or ReportHit was called manually). |
Moveset.Event.ComboAdvanced |
Chain transitioned to a new attack. |
Moveset.Event.ComboDropped |
Chain ended (Reset Combo node, cancel, or timeout). |
Moveset.Event.ComboPoint |
The Moveset: Combo Point AnimNotify fired. |
Moveset.Event.Failed |
Initialisation failed (no MovesetComponent, etc.). |
Authoring a module
Two configuration sites come together: the module class carries the reaction logic + NetMode + matching policy; the node entry says "for this event tag, run these module instances in order".
1. Make the module class
Right-click in Content Browser → Blueprint Class → search "Action Module" → create your subclass (e.g. BP_DamageModule). Open it.
Override the Handle event:
Handle (Owner, EventTag, Payload)
├─ Switch on EventTag
│ case Moveset.Event.Hit → ApplyDamage(Payload.HitResult.HitActor, 25.0)
│ case Moveset.Event.AttackStarted → SpawnTrailVFX
│ case Moveset.Event.ComboAdvanced → AddSuperMeter(0.05)
In Class Defaults, set:
NetModeAll(default) — fires on autoproxy + authority + simproxy.Authority— server-only. Use for damage and any state mutation.LocallyControlled— only the owning player's side. Use for HUD / prediction.SimulatedProxy— only on remote viewers. Use for cosmetic-only effects you don't want the owner / authority to also fire.
bExactMatch— false by default; the dispatcher usesMatchesTagso a module subscribed toMoveset.Event.Hitalso fires for child tags likeMoveset.Event.Hit.Critical. Flip to true for strict equality.
2. Attach to a combo graph node
Open your UComboGraphAsset. Click any node (attack, root, reset). In the Details panel:
Modules: TArray<FActionModuleEntry>
[0]
EventTag: Moveset.Event.Hit
Modules: [ BP_DamageModule, BP_HitVFXModule ]
[1]
EventTag: Moveset.Event.AttackStarted
Modules: [ BP_StartupSoundModule ]
Each entry pairs an event tag with the modules that should run when the dispatched event matches it. Modules run in array order on whichever side(s) their NetMode allows.
NetMode strategy
The right NetMode depends on what the module does:
| Module purpose | NetMode | Why |
|---|---|---|
| Damage application | Authority |
Server is the only side that owns hit truth. |
| Hit VFX (sparks, blood) | All |
Every viewer needs to see it. |
| Hit VFX gated to the attacker's side | Authority + SimulatedProxy |
Skip the autonomous proxy, who already plays its own predicted VFX from OnHit. |
| Camera shake | LocallyControlled |
Only the player feels it. |
| HUD ammo decrement | LocallyControlled |
Owning client UI. |
| AI perception event | Authority |
Authority drives world state. |
| Audio cue for nearby enemies | All |
Spatial audio plays on every viewer. |
| State tag flip ("HitStun") | Authority |
Replicated state must originate on auth. |
If you find yourself wanting BOTH "fires on owning side immediately for prediction" AND "fires on authority for server-side state", split into two modules: one with LocallyControlled, one with Authority.
Sending custom events from your game code
You can emit your own event tags into the same routing system. Define your tag in Project Settings (e.g. Game.Combat.PerfectParry), then from C++ or BP:
FMovesetEventPayload Payload;
Payload.EventTag = FGameplayTag::RequestGameplayTag("Game.Combat.PerfectParry");
MovesetComponent->SendMovesetEvent(Payload.EventTag, Payload);
…and any module on the active combo node that subscribes to that tag fires. This is the recommended hook point from AnimNotifys, ability tasks, or other game systems that want to talk to per-node logic without coupling to specific attack classes.
The payload is fully extensible — fill in AttackTag, PreviousAttackTag, HitResult, FailureReason as appropriate for your event.
Lifetime caveat
Modules are subobjects of the graph asset (Instanced UPROPERTY). One module instance is shared across every actor running the same graph. Don't store per-actor state on the module itself — look up state through the UMovesetComponent* Owner argument passed to Handle.
Concretely: don't do this in BP_DamageModule:
Variable: int32 HitsLandedSoFar
Handle: HitsLandedSoFar = HitsLandedSoFar + 1
Every player on the server using this module would share the counter. Instead:
Handle:
MyHits = Owner->GetActor()->FindComponentByClass<UMyCombatComponent>()->HitsLanded
ApplyDamage(...)
Owner->GetActor()->FindComponentByClass<UMyCombatComponent>()->HitsLanded = MyHits + 1
State lives on a per-actor component or in your character class. The module is pure logic.
Composition patterns
Damage + VFX + Sound on every Hit
One node entry, three modules:
Modules:
[0] EventTag: Moveset.Event.Hit
Modules: [ BP_DamageModule (Authority), BP_HitVFX (All), BP_HitSound (All) ]
The runtime calls them in array order. BP_DamageModule runs only on the server; the others run on every connected machine.
Status effect chain
A heavy attack that applies stagger if it lands during a parry window:
Attack Node: HeavyFinisher
Modules:
[0] EventTag: Moveset.Event.Hit
Modules: [ BP_DamageModule (Authority), BP_TryApplyStagger (Authority) ]
[1] EventTag: Moveset.Event.AttackStarted
Modules: [ BP_AnticipationCameraShake (LocallyControlled) ]
BP_TryApplyStagger checks the hit target's tags and applies a stagger gameplay effect if the target had State.Parrying.
UI-only feedback on Combo Point
Attack Node: PerfectComboMoment
Modules:
[0] EventTag: Moveset.Event.ComboPoint
Modules: [ BP_PerfectFlash (LocallyControlled), BP_PerfectSound (All) ]
The Combo Point notify fires from the montage at a designer-chosen frame (typically the end of an active hit window for "perfect-timing" feedback).