How-To: NPC Combos

Goal: have an AI enemy perform a combo string - start it, chain it step by step, end it, and tell a finisher from a whiff - driven from the NPC's StateTree, with enough variation that it does not read as a machine.

Requires every Crimson plugin
CrimsonCore is free to download, but compiles only with all Crimson plugins installed. NPC combos are CrimsonCore's wiring of the CrimsonAbilitySystem combo system onto an ACrimsonNPCBase, so both plugins must be enabled.
Prerequisites
An ACrimsonNPCBase subclass with a UCrimsonNPCData asset that has an ability set and a StateTree - see Character Pipeline for how NPC ability sets are granted, and Spawning Pooled NPCs for the spawn path. A compiled combo graph asset and the ability that roots it: CrimsonAbilitySystem -> How-To: Author a Combo covers the graph and its transition windows, and CrimsonAbilitySystem -> How-To: Drive Combos from AI covers the authority drive API this page uses.

What CrimsonCore adds

The combo state machine itself belongs to CrimsonAbilitySystem. CrimsonCore puts it on every AI character and gives StateTree a task that drives it.

PieceWhat it is
ComboControllerA UCrimsonComboController created by ACrimsonNPCBase as a default subobject. It is created on every NPC, never conditionally - a subobject whose existence depends on a project setting changes the CDO's component list between runs and corrupts NPC Blueprint archetypes when the setting flips. It is fully inert while the combo system is disabled or while nothing is granted. Read it with GetComboController() (Get Combo Controller in Blueprint).
FCrimsonPerformComboTaskA StateTree task, shown in the node picker as Crimson Perform Combo under Crimson. Starts one combo on enter, drives it while the state is active, and succeeds once the string is over.
ECrimsonCoreComboDrivePolicyThe four orders the task can try the active step's continuations in: First Available (graph order), Highest Priority First, Random, Explicit Path.
Pooling resetACrimsonNPCBase::DeactivateForPooling() resets a running combo with reason Manual before the ability system teardown, so a pooled NPC can never come back mid-step with a live phase tag still on its ability system component.
Remove a hand-added combo controller
If one of your NPC Blueprints already has a UCrimsonComboController added by hand in the Components panel, delete it. ACrimsonNPCBase now creates one as a default subobject, and the Blueprint would carry two.

1. Turn the combo system on

Open Project Settings -> Crimson -> Crimson Ability System and tick Enable Combo System, then restart the editor. The setting is read at module startup, so the restart is what makes the combo asset type, the graph editor and the Combo category on gameplay abilities exist. With it off, every drive call returns ECrimsonComboDriveResult::ComboSystemDisabled and the controller on your NPC does nothing at all.

Verify
After the restart, a UCrimsonGameplayAbility Blueprint shows a Combo category in its Class Defaults, and the Content Browser right-click menu offers a Crimson Combo Graph Asset.

2. Grant the combo and its step abilities to the NPC

A driven combo has two hard requirements, and both are checked before anything plays: the graph must be granted and unlocked on this NPC's own ComboController, and every step ability in the graph must be granted on the NPC's ability system component. There is no press and no rooting ability involved - the drive path starts an asset the controller already holds.

The data path, which needs no code at all. Open the ability that roots the combo (any UCrimsonGameplayAbility Blueprint). In Class Defaults, under Combo -> Default Combos, add an entry: set Combo to your combo graph asset and leave Start Unlocked ticked. The controller seeds the grant the moment that ability is granted to the NPC. Then open the UCrimsonAbilitySet listed in your UCrimsonNPCData's Ability Sets and add, under Granted Abilities, both the root ability and every ability used as a Step Ability Class in the graph.

The runtime path, for a combo learned mid-fight: Event -> Has Authority (Branch, True) -> Get Combo Controller -> Grant Dynamic Combo (Root Ability Class, Combo) -> Preload Combo Abilities (Combo). Authored the entry with Start Unlocked off instead? Call Unlock Combo Grant (Root Ability Class, Combo) on the same controller. Both nodes live under Crimson|Combo|Grants and are server-only.

Verify
On the server, GetGrantedCombosFor(RootAbilityClass) (Get Granted Combos For) on the NPC's combo controller returns your graph asset, and GetUngrantedStepAbilities(Combo) returns an empty array. Call Preload Combo Abilities first and let a frame or two pass for a precise answer there - a step ability class that is not resident yet counts as ungranted.

3. Add the task to the NPC's StateTree

The NPC's tree is the asset on UCrimsonNPCData::StateTree; ACrimsonNPCController assigns it and restarts logic on possession. Open that tree, select the state that means "attacking", and add the task Crimson Perform Combo. Its AIController context property binds itself to the schema's AI controller, so the only pin you must fill is Combo. This step is the same asset work in a Blueprint project and a C++ one.

PropertyTypeDefaultWhat it does
AIController (Context)AAIControllerNoneAuto-bound to the schema's AI controller. The combo controller is found on that controller's pawn.
Combo (Parameter)UCrimsonComboGraphAssetNoneThe graph the NPC performs. Required - the task fails on enter without it.
Policy (Parameter)ECrimsonCoreComboDrivePolicyHighest Priority FirstThe order continuations are tried in when the active step has more than one. See step 4.
Explicit Path (Parameter)Array of FGuidEmptyExplicit Path policy only: the node GUIDs to walk in order after the first step. Ignored by every other policy.
Drop Chance (Parameter)float, clamped 0.0 to 1.00.0Chance per step that the NPC deliberately lets the string drop. Rolled once when a step begins, so it reads as "chance this step is the last one". 0.0 means it never drops on purpose.
Min Step Delay Seconds (Parameter)float, clamped to 0.0 or more0.0Seconds to wait after a step begins before attempting a continuation. 0.0 means as soon as the transition window allows.
Completed Cleanly (Output)boolfalseWritten once, as the task succeeds: true when the combo ended on an End node, false when it dropped, timed out, or was reset.

The task is polled rather than delegate-bound - StateTree instance data is a USTRUCT and cannot bind dynamic delegates - so every tick it re-reads the controller's own state. It drives the graph and nothing else: targeting, facing and range belong to the states and tasks around it. Crimson Focus Target on the parent state is the usual partner, bound to the Crimson NPC Context evaluator's HostileTarget.

PhaseResultWhen
EnterStateFailedThe AI controller has no pawn, the pawn has no UCrimsonComboController, the pawn is not authoritative, no combo asset is set, or StartComboByAsset refused. Every one of those logs a warning naming the pawn and, for a refusal, the ECrimsonComboDriveResult value.
EnterStateRunningThe combo started. First the task preloads the step ability classes, warns about anything still ungranted, and snapshots the controller's completion counter.
TickFailedThe combo controller went away: the pawn was unpossessed, destroyed, or returned to the pool mid-string.
TickSucceededThe combo is no longer active. Completed Cleanly is set from whether the completion counter changed, which only an End node does.
TickRunningThe string is still going. A refused continuation is never fatal - the window may simply not have opened yet, and the candidate list is rebuilt and retried next tick.
ExitState-Cancels any pending drive latch, then resets the combo - but only on authority, only while a combo is still active, and only when the running combo is still this task's Combo. A string that already ended and was replaced by something else is not this task's to cancel.
Verify
Enter the state in play: the NPC plays the graph's first step, and the output log has no Crimson Perform Combo warning. A warning naming a refusal code tells you exactly which prerequisite is missing - ComboNotGranted for step 2, ComboSystemDisabled for step 1, NotAuthority if something is running the tree off the server.

4. Choose a policy

When the active step has more than one outgoing transition, Policy decides the order the task asks for them in. It only picks an order: the combo controller stays the authority on whether a transition may actually fire, and it answers one candidate at a time until one is accepted.

PolicyOrder it asks inUse it for
First Available (graph order)The order the controller reports the active step's transitions in.A string that should always take the same branch, without depending on priorities.
Highest Priority FirstSorted by the edge's Priority, descending - a larger Priority means preferred, matching the compile-time transition sort. This is the default.Most enemies: author the intended string as the high-priority edges and let the fallbacks cover the rest.
RandomThe whole candidate list is shuffled, so the second and third picks are unbiased too. It is rebuilt and reshuffled on each tick the task drives.Variety between repeats of the same fight.
Explicit PathOnly the node at the current Explicit Path index, and only while that node really is a transition target of the active step.Scripted set-pieces, boss phases, and tutorials where the exact string matters.
How candidates are built
Candidates come from the controller's available-input list, filtered to entries whose availability is Continuation with a valid target node - starting handshakes and branches re-root the machine on a different asset, which is a different task's job. Several edges can name the same node, one per input that reaches it; the node is kept once, carrying the highest Priority among them. The "would fire if pressed now" flag is deliberately not consulted: it answers whether a human press would fire the edge, which folds in the direction, chord and kind gates an authority drive request ignores. The task asks, and lets the refusal codes do the filtering.
Where Explicit Path GUIDs come from
Each step node in a combo graph carries a NodeGUID, and that is what Explicit Path holds - the nodes to walk after the first step. The walk advances only when the step that actually landed is the one the task asked for, so a transition fired by anything else does not desynchronize it. Once the list runs out, or the next GUID is not reachable from the active step, the task stops driving and lets the string end on its own. GetCurrentNodeID() on the combo controller reports the running step's GUID if you would rather read them while playing than copy them from the asset.
Verify
Set Policy to Random and watch the same enemy over several fights: the branch it takes after the first step changes between runs. Switch to Explicit Path with one GUID and it performs exactly two steps, then stops driving and lets the second step finish.

5. Make it read as a person, not a machine

With both humanizing values left at 0.0 the task chains the instant the transition window allows and never drops a string on purpose. That is technically perfect and immediately readable as a machine. Two values fix it, and neither needs any code.

PropertyValueWhat the player sees
Drop Chance0.0The NPC always performs the full string, every time.
Drop Chance0.25Roughly one step in four is the last one: the enemy sometimes stops after the first or second hit, which opens a punish window.
Drop Chance0.6Long strings become rare. Useful for a weak enemy that should mostly poke once and reset.
Min Step Delay Seconds0.0The next step fires on the first frame its window is open.
Min Step Delay Seconds0.15A short, consistent hesitation before each chain. Enough to read the first hit before the second arrives.
Min Step Delay Seconds0.4A visibly deliberate fighter. Set it longer than the step's transition window and the chain will not fire at all, so keep it inside the window you authored.

Drop Chance is rolled once when a step begins, not every tick, which is what makes it read as "chance this step is the last one" rather than a per-frame coin flip. Min Step Delay Seconds measures from the moment the step began, not from the moment its window opened.

Verify
With Drop Chance at 0.25 and Min Step Delay Seconds at 0.15, the same enemy produces different string lengths across a dozen engagements, and each chained hit is visibly separated from the one before it.

6. Branch on Completed Cleanly

A dropped or timed-out string is a normal outcome, not a task failure, so the task succeeds either way and reports how it ended on Completed Cleanly. Branch the tree on the output, not on Succeeded versus Failed - Failed means the combo never ran at all.

In the StateTree asset, bind the task's Completed Cleanly output to a bool parameter on the state, then add two transitions out of the state that trigger on state completion, each with a bool condition comparing that parameter: true goes to your follow-up or recovery state, false goes back to the approach state. This is the same asset work in both Blueprint and C++ projects.

Verify
Give the state a Drop Chance of 1.0 temporarily: the task always succeeds with Completed Cleanly false after the first step, and the tree takes the false branch every time. Set it back to 0.0 and the true branch runs once the string reaches its End node.

7. Driving a combo without a StateTree (optional)

Nothing about the combo system is StateTree-specific. A Behavior Tree task, a custom controller, or a scripted sequence calls the same authority drive API on the NPC's combo controller. No Behavior Tree nodes ship - the drive API is what you would build one out of.

From any server-side graph on the NPC: Has Authority (Branch, True) -> Get Combo Controller -> Start Combo By Asset (Combo, with the Out Result pin telling you why a refusal happened). To chain, Request Transition To Node (Target Node ID, Timing) with Timing = When Window Opens, which latches the request on the active step and retries it until the window opens. Cancel Pending Drive Request drops the latch, and Get Completion Counter before and after tells a clean finish from a reset - compare for inequality only, the counter wraps.

What a drive request obeys
A drive request ignores the edge's input action, its kind (tap, hold, release), its fire mode, and its direction and chord gates - those describe how a human press maps onto a fire moment, and there is no press here. It does respect the edge's transition window, including any per-edge overrides, and its required-tags query - CrimsonAbilitySystem -> How-To: Drive Behavior from Step Phases covers the phase tags that query can match. It is also refused outright on a player-controlled pawn: a server-initiated step on a human's pawn is never predicted by the owning client and would roll that client back. CrimsonAbilitySystem -> How-To: Drive Combos from AI documents the full drive API and every refusal code.
Verify
Call your own start path on a spawned NPC and the first step plays. Call the same path on a possessed player pawn and it refuses with OwnerIsPlayerControlled, which is the intended answer.

Where it runs

PieceRuns onNotes
The StateTree and the taskServer (authority) onlyStateTree AI runs on the server, and the drive API checks HasAuthority() explicitly rather than relying on the Blueprint-only authority gate. The task fails cleanly on enter anywhere else instead of half-running.
The grant callsServer onlyGrantDynamicCombo, UnlockComboGrant and LockComboGrant are all authority-only.
The combo controller's replicated stateNowhere useful, on an NPCGrants and the current node replicate to the owning connection, and an AI pawn has no owning connection. Do not build client logic that reads an NPC's combo state off the controller.
What clients actually seeAll clientsThe step abilities and their montages replicate like any other ability, so every client sees the full string play out on the NPC.
The pooling resetServerDeactivateForPooling() resets a running combo before the ability system teardown. A node authored to ignore the Manual reset reason survives it, because ResetCombo honors a node's reset-ignore list.
Verify the whole loop
Run Play As Client with a dedicated server and two clients, then pull the enemy into its attack state.

- Both clients see the NPC play the whole string, step by step, including the chained steps - the montages replicate even though the combo state does not.
- The string length varies between engagements once Drop Chance is above 0.0.
- The server log has no Crimson Perform Combo warning.
- Kill the NPC mid-string while it is pooled (bCanBePooled ticked on its UCrimsonNPCData) and spawn it again: it comes back idle, with no leftover combo phase tag, because DeactivateForPooling() reset it.

API reference

Type / memberModulePurpose
FCrimsonPerformComboTaskCrimsonCoreStateTree task, shown as Crimson Perform Combo under Crimson. Instance data FCrimsonPerformComboTaskInstanceData carries AIController, Combo, Policy, ExplicitPath, DropChance, MinStepDelaySeconds and the output bCompletedCleanly. Server only.
ECrimsonCoreComboDrivePolicyCrimsonCoreFirstAvailable (First Available (graph order)), HighestPriority (Highest Priority First, the default), Random, ExplicitPath.
ACrimsonNPCBase::GetComboController()CrimsonCoreBlueprint pure, category Crimson|NPC. Returns the UCrimsonComboController default subobject ComboController that every NPC carries.
ACrimsonNPCBase::DeactivateForPooling()CrimsonCoreResets a running combo with reason Manual before the ability system teardown, then runs the rest of the pooling teardown.
UCrimsonComboControllerCrimsonAbilitySystemThe combo state machine. Its authority drive API - StartComboByAsset, RequestTransitionToNode, CancelPendingDriveRequest, GetCompletionCounter, GetUngrantedStepAbilities, PreloadComboAbilities - is what the task calls, and what a Behavior Tree would call. Documented in CrimsonAbilitySystem -> How-To: Drive Combos from AI.

See also

  • Character Pipeline - how an NPC's ability sets are granted, and why order matters
  • Spawning Pooled NPCs - the spawn path and the pooling contract the reset belongs to
  • CrimsonAbilitySystem -> How-To: Author a Combo - authoring a combo graph and its transition windows
  • CrimsonAbilitySystem -> How-To: Drive Behavior from Step Phases - the step phase tags and the edge tag queries they gate
  • CrimsonAbilitySystem -> How-To: Drive Combos from AI - the authority drive API and every refusal code