Skip to main content

Combat Events

The RPG Companion App fires a set of global events throughout the combat encounter lifecycle. You can use these event names in a mechanic's event_names parameter to trigger effects at specific moments in combat — for example, restoring a resource at the start of each turn, applying a status effect when the round changes, or showing a message when the encounter ends.

How to access event data

When a mechanic is triggered by an event, the event's payload is injected into your stat context under a special stat called $event. Each key in the payload becomes accessible via the usual dot notation: $event.<key>.

Only stat managers (resources / characters) and primitives (strings, integers, booleans) in the payload are traversable in formulas. Other internal object types are not usable.

Use null-safe access (?. and ? suffix) when reading from $event, since the payload content may vary:

mechanic my_mechanic(event_names = "turnAdvanced") =
when {
$event?.combatant?.id? == id -> setStat(stat = "actions_remaining", new_value = 3, aggregation_type = "set"),
};

Lifecycle events

These events mark the start and end of an encounter and are useful as mechanic triggers on their own. The encounter value they carry is an internal object and is not accessible in formulas.

Event nameFires when…
encounterStartedA new encounter begins.
encounterEndedThe encounter finishes (manually or via resolution).
encounterMinimizedThe encounter is hidden (minimized to the FAB).
encounterResumedA minimized encounter is brought back.

encounterEnded payload

KeyTypeDescription
resolution_messagestring(optional) The message returned by encounter_resolution_message if the encounter ended automatically through that formula. Not present when ended manually.

Turn & round events

turnAdvanced

Fires every time the active turn moves to a different combatant, including when navigating back with "Previous Turn".

KeyTypeDescription
combatantstat managerThe stat context of the combatant whose turn it now is. May be null if no resource is loaded for that combatant.
All active character mechanics receive this event

Because combat events are global, every character whose mechanics list turnAdvanced in event_names will fire — not just the character whose turn it is. Guard with an identity check if you only want to react on your own turn:

mechanic reset_actions_on_turn_advanced(event_names = "turnAdvanced") =
when {
$event?.combatant?.id? == id -> sequence(
effects = [
setStat(stat = "actions_remaining", new_value = 3, aggregation_type = "set"),
setStat(stat = "reaction_available", new_value = 1, aggregation_type = "set"),
],
),
};

roundAdvanced

Fires when a full round completes and the initiative order resets.

KeyTypeDescription
roundintegerThe new round number (starts at 2 on the first roundAdvanced).
mechanic reset_focus_on_round_advanced(event_names = "roundAdvanced") =
setStat(stat = "focus_points", new_value = focus_points_max, aggregation_type = "set");

Combatant roster events

These events fire when the roster changes mid-encounter.

Event nameFires when…
combatantAddedA combatant is added to an ongoing encounter.
combatantRemovedA combatant is removed from the encounter.
combatantUpdatedA combatant's initiative or status is changed.

combatantRemoved payload

KeyTypeDescription
combatantIdstringThe record ID of the removed combatant's resource.
note

combatantAdded and combatantUpdated carry a combatant payload key, but it is an internal object and is not accessible in formulas at this time.


The $combatants variable

While an encounter is active, every combatant's stat context has a special temporary stat called $combatants. This is a list of stat managers — one for each combatant currently in the encounter, in initiative order. It is available anywhere a formula is evaluated during combat: in calc stats, mechanic conditions, encounterResolutionMessage, and isEncounterWithinDifficultyLevel.

Each element in $combatants behaves like any other stat manager: you can read its stats with the usual dot notation and use it in lambdas.

Filtering combatants by type

The app injects a $combatant_type temp stat on each combatant, containing its combatant type ID (e.g. "player", "monster", "npc"). Use it to separate allies from enemies:

calc resource<monster>[] enemies(name = "Enemies") =
filter($combatants, filter = ($combatant) => $combatant.$combatant_type? == "monster");

calc resource<player>[] players(name = "Players") =
filter($combatants, filter = ($combatant) => $combatant.$combatant_type? == "player");

Checking for encounter resolution

A common use case for $combatants is driving the encounterResolutionMessage formula:

calc string encounter_resolution_message(name = "Encounter resolution message") =
when {
isEmpty(filter(enemies, filter = ($combatant) => $combatant.current_hp > 0)) ->
"All enemies have been defeated!",
else -> null,
};

Computing values across all combatants

calc integer total_enemy_hp(name = "Total Enemy HP") =
add(map(enemies, mapper = ($enemy) => $enemy.current_hp ?? 0));
tip

$combatants is also available inside isEncounterWithinDifficultyLevel for encounter building — see CombatSystem for details on those formulas.