# Action System

> Give an enemy a moveset: create an Action Set, point an action at a montage, and let scoring pick the right move.

Markdown version of https://rigbak.com/docs/soulslike-combat/action-system, a page of the Soulslike Enemy Combat documentation. Link that page when citing it.

> An enemy's moveset lives in one asset. Each move scores itself against the live fight, and the highest score wins.

## Set it up (60 seconds)

1. **Create an Action Set**

   Right-click in the Content Browser, **SEC**, **Actions**, **Action Set**. It opens on its own card canvas.

   Create the asset: right-click in the Content Browser, choose **SEC > Actions > Action Set**, and name it **DA_ActionSet_Grunt**.

   *Right-click in the Content Browser, SEC, Actions, Action Set.*
2. **Add an action**

   Right-click the empty canvas and choose **Add Action**. A card appears carrying an **Action ID** and a **Gameplay Ability** execution method already on it. Rename the Action ID to something you will recognize in a log, such as `LightAttack`.
3. **Point it at a montage**

   Set **Ability Class** to **GA_SEC_MontageAbility**, which ships with the plugin. A **Montage Payload** appears underneath it. Set its **Montage** to your attack animation.

   That is the whole attack. The ability plays the montage, and the action ends when the montage does.

   In **DA_ActionSet_Grunt**, under Execution, set:

   | Property | Value |
   | --- | --- |
   | Ability Class | GA_SEC_MontageAbility |
   | Montage | AM_Attack_Light |

   *Pick the shipped ability, then pick the montage it plays.*
4. **Tell it what range it works at**

   On the card's **Scoring > Scorers** list, add a **Distance Scorer**. Its **Range** defaults to a melee band that peaks between 100 and 250 cm.

   Skip this and the action has no opinion about distance, so the enemy swings from across the room.
5. **Point the AI Config at it, and play**

   Open the enemy's **Enemy AI Config** and set **Default Action Set** to the asset. Leave **Manage Action Sets Automatically** ticked.

   In **EnemyConfig_Grunt**, under Action Sets, set:

   | Property | Value |
   | --- | --- |
   | Manage Action Sets Automatically | true |
   | Default Action Set | DA_ActionSet_Grunt |

   *Default Action Set covers every role, so one entry is enough to start.*

   Press Play and run `SEC.Debug.Scoring 1` to see what each action scored and which one won.

> **Note**
>
> **Want the enemy to walk in before it swings?** Change the action's **Execution Method** to **Behavior Tree Sequence**, set **Ability to Grant** to `GA_SEC_MontageAbility`, add **BT_SEC_AbilityAction** to the **Behavior Tree Sequence** list, and set the **Montage** on its payload. That shipped tree walks to 150 cm, runs the ability, backs off to 300 cm, then waits a second. No wiring beyond those three fields.

> **Warning**
>
> An enemy needs a registered combat target before it runs any action at all. If the moveset looks right and nothing happens, that is what is missing. See [Getting Started](https://rigbak.com/docs/soulslike-combat/getting-started).

For the attack to deal damage, the montage also needs a melee trace window. See [Melee Trace](https://rigbak.com/docs/soulslike-combat/melee-trace).

---

## Ways to run an action

**Execution Method** is how the action runs. Pick one per action; its fields appear underneath it.

| Method | Pick it for |
| --- | --- |
| **Gameplay Ability** | Almost every attack. Set **Ability Class** and the action is set up. |
| **Behavior Tree Sequence** | A move with stages: approach, attack, back off. Set the tree list, and **Ability to Grant** if a tree starts an ability. |
| **Gameplay Ability By Tag** | An ability something else already granted. Takes an **Activation Tag** instead of a class. |
| **Gameplay Ability (Fire And Forget)** | A buff or a shout the enemy should not stand still for. Starts the ability and finishes the action in the same frame. |

**Category: Execution**

| Property | Value |
| --- | --- |
| Execution Method | Gameplay Ability |

**Category: Execution > Ability**

| Property | Value |
| --- | --- |
| Ability Class | GA_SEC_Stomp |
| Ability Timeout | 0 (advanced) |

Grants the ability, starts it, and finishes the action when the ability ends.

The combo above is live. Pick another method to see what that one exposes.

> **Note**
>
> Nothing has to report back. SEC watches the ability system for the ability it started, so any ability that reaches **End Ability** on completion, cancel and interrupt works with no extra wiring. An ability that never ends is cut off after 30 seconds; set **Ability Timeout** to cap it sooner.

<details>
<summary>What GA_SEC_MontageAbility gives you (Advanced)</summary>

It is a Blueprint ability that plays whatever montage the action's payload names, and you can point at it directly without subclassing. Its payload carries three fields:

| Field | Does |
| --- | --- |
| **Montage** | The animation to play. |
| **Start Section** | Which section to start at. Leave it empty to play from the top. |
| **Play Rate** | Speed multiplier, 1 for the authored speed. |

Because it derives from `UGameplayAbilityBase` it also brings rotation lock, the ledge guard, motion warping toward the target, and the action context.

Subclass it when you want per-attack graph work, such as spawning an effect on a montage notify. Subclasses can also fill **Montages to Pick From**, an array the ability draws from at random when the action carries no payload.

</details>

<details>
<summary>What the shipped behavior tree does (Advanced)</summary>

`BT_SEC_AbilityAction` is a four-step sequence:

1. **Move Until Distance** to 150 cm, giving up after 5 seconds or past a 400 cm chase limit.
2. **Activate Action Ability**, which starts the running action's **Ability to Grant** and waits for it to end.
3. **Make Distance** back out to 300 cm.
4. **Wait** one second.

It reads the target from the `SEC_TargetActor` blackboard key, which the execution method writes before the tree starts, along with `SEC_SelfActor`, `SEC_Distance`, `SEC_ActionId` and the payload under **Payload Key Name** (`SEC_Payload` by default). A key the blackboard does not declare gets no value; `BB_SEC_Basic` declares all of them except `SEC_SelfActor`.

Copy the asset into your own content to change the distances. Build your own tree on `BB_SEC_Basic`, or on a blackboard declaring the same keys.

</details>

<details>
<summary>Writing your own execution method (Advanced)</summary>

For a latent task, a spawned projectile, a timeline, or a third-party system, subclass `USECExecutionMethod` in Blueprint or C++. Your method carries its own fields and appears in the **Execution Method** picker beside the built-ins.

The action holds your method as a definition and never mutates it. On execution the component duplicates it, so the running instance can hold per-execution state as ordinary properties.

Override the phases you need: **Begin Execute** to start the work (return false to fail the start, which charges no cooldown), **Tick Execute** for per-frame work, and **Abort Execute** to tear down on interrupt or cancel. Call **Finish Execution** when the work ends.

Four pure reads shape how the component treats it: **Has Valid Data** (an unconfigured method is skipped), **Get Ability to Grant**, **Get Execution Timeout**, and **Get Display Name**.

</details>

---

## Decide when the enemy picks a move

Every action scores itself each time the enemy chooses, and the highest score above zero wins.

**Final Score** (ActionEvaluationComponent > Scoring): `Final Score = Selection Weight ÷ Risk Penalty × Tag Multipliers × Novelty × Chain Bonus × Scorers × Runtime Modifiers × Jitter`

- Novelty: Penalizes recent use
- Scorers: Distance · Angle · Health · Speed · custom
- Runtime Modifiers: GlobalMultiplier × SetActionOverride
- Jitter: ±5%, deterministic

*Highest score above zero wins. Leave a scorer off and that dimension drops out of the product. Jitter is deterministic from the decision context seed.*

Two numbers sit on every action. **Selection Weight** biases it up or down, so weight 2 doubles its score against a weight 1 action. **Risk Penalty** divides the final score to hold a risky move back, where 2 halves it and 0.5 doubles it.

Everything else is opt-in. Add a **Scorer** for each dimension the action should care about, and leave the rest off.

*Interactive diagram on the page: Each scorer multiplies in; each gate can veto. Mix built-ins with Blueprint subclasses of your own.*

| Scorer | Scores on |
| --- | --- |
| **Distance Scorer** | Distance to the target in cm. Add this to any melee attack. |
| **Angle Scorer** | Angle to the target, 0 facing it and 180 away. |
| **Health Scorer** | The enemy's own health, as a 0 to 1 fraction. Shape it low for desperate moves. |
| **Speed Scorer** | The enemy's own speed in cm/s, for a running attack. |
| **Vital Scorer** | Any named vital, the general form behind Health Scorer. |
| **Attribute Scorer** | Any GAS attribute, optionally divided by a second one such as Mana over MaxMana. |

A **Gate** is the same idea with a yes or no answer. A gate that says no drops the action for that decision.

| Gate | Blocks unless |
| --- | --- |
| **Combat Token Gate** | The target has a free attack slot. This is what stops five enemies swinging at once. See [Combat Tokens](https://rigbak.com/docs/soulslike-combat/combat-tokens). |
| **Stamina Gate** | The enemy's stamina is at least **Min Stamina**. |
| **Vital Gate** | A named vital holds at least **Min Value**. This is how an action declares a resource cost. |
| **Attribute Gate** | A GAS attribute sits between a min and a max. |

Every gate has an **Invert** tick, which turns "pass when X" into "block when X".

### Shaping a range

Each built-in scorer shapes its dimension through one **Range** curve. Below **Min Value** the score is 0. It ramps up to 1 across **Optimal Min**, holds at 1 through the sweet spot, then ramps back to 0 at **Max Value**.

**Category: Range**

| Property | Value |
| --- | --- |
| Min Value | 0 |
| Optimal Min | 100 |
| Optimal Max | 250 |
| Max Value | 500 |
| Exponent | 2 |
| Clamp to Zero | true |

So an attack can be legal from 0 to 500 cm while scoring best between 100 and 250. A new **Distance Scorer** starts on that melee band (0, 100, 250, 500), and a new **Angle Scorer** on a frontal one (0, 0, 30, 90). The other scorers start on a range that scores 1 everywhere. For any other shape, type the four numbers in, such as 300, 500, 1000 and 1800 for a ranged attack.

> **Warning**
>
> A brand new action has no scorers at all, so it fires at any distance and any angle. That is the design: add a scorer to opt the action into a dimension. An attack that swings from across the room is missing its Distance Scorer.

<details>
<summary>Writing your own scorer or gate (Advanced)</summary>

Subclass `USECScorer` or `USECGate` in Blueprint or C++ and override its one function. The context hands you the controller, the target, the owning ability system component, a random seed, and a snapshot carrying distance, angle, speed, health, stamina and the pawn's vitals component.

Keep subclasses stateless. Every enemy using the asset shares one instance, so a mutable field aliases across the whole pack. For randomness, use the **Seeded Random** helper rather than a member.

Override **Get Display Name** to label the row in the editor and in the decision log.

The same scorers and gates work on reactions. Four more gates ship for reactions and trigger rules only: **Incoming Angle**, **Time To Impact**, **Stimulus Magnitude**, and **Source Distance**.

</details>

<details>
<summary>Rules that apply to every action (Advanced)</summary>

For logic that covers a whole enemy rather than one move, `ActionEvaluationComponent` exposes two overridable events. **Can Execute Action** vetoes an action after the built-in gates pass. **Modify Action Score** adjusts a score after the pipeline computes it. Override them in a Blueprint subclass of the component.

**World Tags** carry global game state (boss phase, weather, arena state) into scoring. Push them through the **World Tag** library and they reach every action's **Requires Tags**, **Block Tags** and tag multipliers. Three ship as examples: `SEC.World.Combat.Active`, `SEC.World.Boss.Active`, and `SEC.World.Boss.Casting`.

A **Lifecycle Hook** runs your logic around an action on every exit path: **Pre Execute** (which can veto), **Tick Execute**, and **Post Execute** (which receives the end reason). Attach one per action on **Hook**, or to the whole enemy on the AI Config's **Global Hook**. When both are set they compose, global first.

</details>

---

## Pace the moveset

**Cooldown Duration** is the wait before the same action can run again. It defaults to 0, so a lone action with nothing else set repeats back to back.

**Category: Cooldown**

| Property | Value |
| --- | --- |
| Cooldown Duration | 5 |
| Initial Cooldown | 0 |
| Randomization (%) | 0.2 |
| Interrupt Refund (%) | 0 |
| Spawn Cooldown Chance | 0 |
| Max Consecutive Uses | -1 |

**Randomization** adds jitter so a pack does not attack in lockstep, and **Spawn Cooldown Chance** rolls a starting cooldown for the same reason. **Max Consecutive Uses** stops one move being spammed, where `-1` is unlimited and `2` forces a switch after two. **Initial Cooldown** blocks a move at spawn, so an enemy cannot open with its heaviest hit.

An action pays its cooldown only once it starts. A failed activation charges nothing and can retry on the next tick.

Swapping the Action Set ends every running cooldown, and so does a reset: a role change that brings a different set, a StateTree set task that swaps the set, **Set Action Set and Reset State** and **Reset Runtime State** all do it. A role change that keeps the same set keeps its cooldowns running. Each ended cooldown fires **On Action Cooldown Expired**, on the server and on every client. An interrupt that hands back the whole cooldown (**Interrupt Refund (%)** at 1) fires it too, since the action is ready again.

### Recovery: a gap between any two moves

Cooldown gates one move. **Recovery** gates every offensive action after any one of them ends. The enemy still strafes, repositions and reacts; it does not start a new attack.

Set it on the **Enemy AI Config** under **Recovery**. Every field defaults to 0, so recovery stays off until you set one.

| Field | Sets |
| --- | --- |
| **Action Recovery Time** | Seconds held off after an action completes. |
| **Interrupt Recovery Time** | Seconds held off after an action is interrupted or canceled. Keep it shorter, so a parry does not double-stun. |
| **Action Recovery Time Randomization** | Jitter on the completion window. |

To override it for one move, tick **Override Recovery Time** on that action and set **Recovery Time**. It replaces the global value rather than adding to it.

`SEC.Debug.LogActionDecisions 1` prints `Recovering (X.Xs remaining)` while the gate holds.

---

## Build a combo

An action's **Chain Links** name the follow-ups it prefers. After it completes, each named follow-up scores higher for **Chain Followup Window Seconds** (0.6 by default, on the Action Evaluation component). An interrupt or a cancel clears the window, so a parried enemy cannot combo through its own recovery.

| Setting | Lives on | Does |
| --- | --- | --- |
| **Bonus Multiplier** | the link | Multiplies the follow-up's score while the window is open. 1.5 by default. |
| **Pacing** | the link | **Score Only** gives the bonus and nothing else, so the follow-up waits out the recovery breather. **Immediate** starts it as soon as the first action ends. |
| **Selection** | the follow-up | Set it to **Chain Only** to make the link a requirement rather than a preference: automatic selection reaches that action only inside the chain window. Good for a finisher that should never open a fight. |

A follow-up answers to its cooldown, its **Max Consecutive Uses**, a cooldown its ability applies to itself, its gates and its scorers whichever Pacing carries it, and a direct **Execute Action** call reaches a Chain Only action at any time.

> **Tip: Combos that read as decisions**
>
> Raise Smash's score after Hit. If the player rolls out of range, the Distance Scorer drops Smash and the enemy picks something else. It does not swing at air.

Drawing these as wires, with each link's setting on the wire that carries it, is what the [Action Set Editor](https://rigbak.com/docs/soulslike-combat/action-set-editor#building-a-combo) is for.

---

## Which moveset an enemy uses

Fill in **Default Action Set** on the AI Config and every role uses it. Beyond that, four things can answer instead, and the first match wins:

| Source | Use it for |
| --- | --- |
| **Runtime Override** | Boss phase transitions. Set it in Blueprint, clear it to fall back. |
| **An equipped weapon** | A weapon's own moveset. Give a skeleton a bow and it fights like an archer. |
| **Role Action Sets** | A different moveset per [combat role](https://rigbak.com/docs/soulslike-combat/combat-roles), so a Flanker opens differently from an Attacker. |
| **Default Action Set** | Everything else. |

A weapon carries its moveset in **Weapon Action Set** on the weapon Blueprint. While the enemy holds it, that set wins; drop the weapon and the enemy falls back to its config. See [Weapons](https://rigbak.com/docs/soulslike-combat/weapons).

> **Warning**
>
> Author **Role Action Sets** without a **Default Action Set** and an enemy that has not been assigned a role gets nothing. Fill in the default as well.

<details>
<summary>Granting single actions at runtime (Advanced)</summary>

**Grant Action** and **Revoke Action** on the pawn's **SEC Action Set Component** add and remove one action without swapping the whole set. Granted actions survive role changes and set swaps, and they score alongside the base set with the same cooldowns.

**Grant Actions from Set**, **Revoke Actions from Set** and **Clear Granted Actions** do the same in bulk.

</details>

<details>
<summary>Handing an action data at the moment it runs (Advanced)</summary>

When one action needs to serve several cases (a different target, item, or strength), pass context instead of authoring a new action per case. **Execute Action with Context** takes a target, an optional object, a magnitude and a tag container.

An ability deriving from `UGameplayAbilityBase` reads it through **Get Action Context**, or reacts to the **On Action Context Received** event. When no target is passed, the context falls back to the enemy's focus actor.

Override **Can Activate Ability** on the ability, read the context, and return false to refuse. A refusal costs nothing: no cooldown is charged and no running action is interrupted.

</details>

---

## Motion warping: steering an attack toward the target

Every action's ability can steer an attack toward its target with motion warping, so a swing lands even when the montage was authored at a slightly different range.

1. Add a **Motion Warping** component to the character.
2. Place a **Motion Warp** notify window over the attack's windup in the montage.
3. Set the ability's **Motion Warping Target Name** to match the notify's Warp Target Name.

`AM_SEC_TwoHanded_TripleAttack` in the demo content has a working setup to copy.

<details>
<summary>Motion warping and rotation lock settings (Advanced)</summary>

| Property | Default | Effect |
| --- | --- | --- |
| **Motion Warping Target Name** | `Target` | Must match the Warp Target Name on the montage notify. Set it to None to skip warp setup for this ability. |
| **Motion Warping Offset** | 100 cm | How far short of the target the warp point sits, which is where the AI comes to rest. Keeps the AI facing the target when the montage uses the SEC Skew Warp modifier; on the stock Skew Warp modifier the AI passes this point and turns around. 50 to 150 cm depending on the attack's range. |
| **Max Warp Distance** | 0 | Skip warping when the enemy is closer than this. 0 leaves the gate off. |
| **Lock AI Rotation** | on | Stops the controller yawing the pawn toward its focus during the ability, so the attack commits to a direction. Warping runs separately and still rotates the pawn inside the notify window. |
| **Prevent Ledge Fall During Ability** | on | Keeps the character on the ledge while the ability runs, so a root-motion attack slides along the edge instead of dropping over it. The pawn's own setting returns when the ability ends, including on cancel. |

The ledge guard covers ground movement only. An ability that calls **Launch Character**, or one using a root motion source with vertical velocity, leaves the ground on purpose and still drops. To stop a pawn dropping at any time, untick **Can Walk Off Ledges** on its Character Movement component instead, which also stops it taking drop-down nav links.

</details>

<details>
<summary>The SEC Skew Warp modifier (Advanced)</summary>

Pick **SEC Skew Warp** as the **Root Motion Modifier** on the montage's Motion Warping notify for an attack that should land facing its target. It keeps its goal on the standoff, **Motion Warping Offset** short of the target, while the enemy closes on it, so the window ends there without a jump. Once the enemy stands on the standoff, the warp holds it there facing the target, and turns it to follow a target that moves.

It steers relative to an actor, so the warp target has to name a component on that actor, which the ability's own warp setup does. A warp target given as a bare transform plays as the stock **Skew Warp** from the start. If the target actor is destroyed partway through the window, the rest of it plays as the stock **Skew Warp** does, toward the point the warp target recorded.

**Min Lead** shows as Deprecated and defaults to 1 cm. A warp left on that default ends on its standoff; one set higher keeps its goal that far ahead of the enemy and lands up to that much past the standoff.

</details>

---

## Debug it

Open the Action Set and press Play. The [Action Set Editor](https://rigbak.com/docs/soulslike-combat/action-set-editor#watching-the-ai-decide) lights each card up with its live score, its cooldown, and the gate that refused it.

For a record you can scroll back through:

```
SEC.Debug.Scoring 1              // Score list above each enemy, highest first
SEC.Debug.Actions 1              // Cooldown list above each enemy
SEC.Debug.LogActionDecisions 1   // Why each action scored what it did
SEC.Debug.LogActionExecution 1   // Starts, ends and cooldowns
```

`SEC.Debug.ScoringRows` sets how many rows the score list keeps, 5 by default and 0 for all of them. Each switch has a matching checkbox on the **Action Evaluation** component, for isolating one enemy.

Saving the asset also runs validation, which reports into the **Asset Check** message log listing and names any action that has nothing to run.

<details>
<summary>It scores and wins, but nothing happens (Advanced)</summary>

- **The ability has no activation graph.** An ability implementing neither **Activate Ability** nor **Activate Ability From Event** starts and never ends. It is the classic first attempt.
- **The Montage Payload has no montage.** Asset validation says so on save.
- **The Action ID is empty.** An action added through the Details panel array instead of the canvas has no ID, wins the poll, and is then discarded with no log line. Opening the asset on the canvas repairs it.
- **Enabled is unticked.** That also stops the ability being granted, so a later manual call has nothing to start. For a scripted boss move set **Selection** to **Manual Only** instead.
- **Manage Action Sets Automatically is unticked** on the AI Config, which applies nothing at all.

</details>

<details>
<summary>What silently drops an action before scoring (Advanced)</summary>

| Cause | Drops the action when |
| --- | --- |
| **Enabled** | It is toggled off in the set. |
| **Selection** is **Manual Only** | It is reserved for a direct Execute Action call. |
| **Selection** is **Chain Only** | The chain window is shut, so no link can reach it. |
| **`SEC.Action.BlockActions`** | That tag sits on the enemy's ability system component. |
| **No usable execution method** | The method is missing something it needs to run. |
| **Cooldown or repeat limit** | It is still cooling down, or **Max Consecutive Uses** is spent. |
| **Recovery** | The enemy is in a recovery window, and no Immediate chain link is carrying this action through it. |
| **Requires Tags / Block Tags** | The enemy, the target or the world does not hold the right tags. |
| **Require Line Of Sight** | The box is ticked and the view to the target is blocked. |
| **A gate** | Any gate on the action answered no. |

While an action runs it applies its **Add Tags** to the enemy. `SEC.State.IsMovementBlocked` ships in that list and holds the enemy still mid-swing. Remove it to let the enemy move during that action.

</details>

<details>
<summary>Editing an Action Set during Play In Editor (Advanced)</summary>

Tuning values on an existing action while the game runs is safe, and the running enemy picks the change up. Adding, deleting or renaming an action goes through, but a running enemy holds indexes into the list and the entries move underneath it. The editor warns once per play session, in a toast and in the message log. Restart Play In Editor after those.

</details>

---

Previous page: [Movement System](https://rigbak.com/docs/soulslike-combat/movement-system). Next page: [Action Set Editor](https://rigbak.com/docs/soulslike-combat/action-set-editor)
