API Reference¶
Documentation Files¶
| File | Contents |
|---|---|
| Automation System | How the automation engine works: trigger lifecycle, filters, callbacks, activation modes, sockets, cancel/modify, flow injection, registration, caches |
| Combat API | Combat & execution flows, weapon/item details |
| Spatial API | Distance & grid math, coordinate helpers, faction/disposition, cell data, debug overlays |
| Effects API | Status effect management, global/constant bonuses, immunities, flow state injection |
| Interactive API | Token picker, zones, knockback, choice/vote cards, deployables, thrown weapons, hard cover |
| Items API | Item tags, resource management, activated items, auto-consume config |
| Flags API | Item/token/actor flags, once-per-round/turn gates, flow flags |
| HUD API | Extra actions, action locks, and combat overlays in the Token Action HUD |
| Movement API | Movement tracking, history, movement cap |
| Token Display API | Extra token stat bars |
| API How-To | Registration, user helpers, how-tos, Grid-Aware Auras wrapper |
| Macros | The shipped L.A - macro compendium |
Accessing the API¶
Or via hook:
Fundamentals¶
Shared Types¶
Source: scripts/typing/types.d.ts.
CancelFunction → Promise<void>
(reasonText?, title?, allowConfirm?, userIdControl?, preConfirm?, postChoice?, opts?) => Promise<void>
.wait(): Promise<void>
Used by cancelAttack, cancelTechAttack, cancelCheck, cancelAction, cancelChange, cancelStructure, cancelStress, cancelStructureOutcome, cancelStressOutcome. Aborts synchronously, so call it before any await in your activationCode.
| Param | Type | Default | Description |
|---|---|---|---|
| reasonText | string |
per-trigger | Card description |
| title | string |
per-trigger | Card header |
| allowConfirm | boolean |
true |
false cancels with no card |
| userIdControl | string \| string[] \| null |
null |
Who sees the card. null = active GM |
| preConfirm | (() => Promise<boolean>) \| null |
null |
Asked first. Truthy lets the confirmation card proceed. The ignore path runs only when every registered preConfirm returns falsy |
| postChoice | ((chose: boolean) => Promise<void>) \| null |
null |
chose is true if it stayed cancelled |
| opts | { item?, originToken?, relatedToken? } |
{} |
Documents shown on the card |
The ignore path re-runs the original action. Reactors already in _cancelledBy are skipped on the redo, so a gate is not asked twice. .wait() resolves after the card and any redo.
cancelStructureOutcome and cancelStressOutcome are the exception. They render no Ignore choice, and their ignore path does nothing.
CancelMoveFunction
ChangeMoveFunction → Promise<void>
cancelTriggeredMove(reasonText?, allowConfirm?, userIdControl?, preConfirm?, postChoice?, opts?)
changeTriggeredMove(position: {x: number, y: number}, extraData?, reasonText?, allowConfirm?, userIdControl?, preConfirm?, postChoice?, opts?)
CancelFunction params minus title. A rerouted move is a new move, so reactors may evaluate it again.
ModifyValueFunction
RerollFunction
ChangeRollFunction → Promise<void>
modifyHpChange(newValue: number, reasonText?, allowConfirm?, userIdControl?, preConfirm?, postChoice?, opts?)
modifyHeatChange(newValue: number, ...same)
modifyRoll(newTotal: number) => void
reroll(reasonText?, subtype?, title?, allowConfirm?, userIdControl?, opts?)
changeRoll(newTotal: number, reasonText?, title?, allowConfirm?, userIdControl?, preConfirm?, postChoice?, opts?)
modifyHpChange and modifyHeatChange both expose .wait(). modifyRoll is structure/stress only and shows no card. subtype defaults to 'retry'.
Trailing params behave as on CancelFunction for modifyHpChange, modifyHeatChange and changeRoll. reroll has no positional preConfirm / postChoice slots, pass them inside opts.
ActivationCallback - shape of evaluate and activationCode
(triggerType: TriggerType, triggerData: TriggerData, reactorToken: Token,
item: Item | null, activationName: string, api: LancerAutomationsAPI) => any
item is null for general activations.
actionData
flowState - the recurring payload objects
actionData field |
Type |
|---|---|
| type | string ("action" / "attack" / "tech") |
| title | string |
| action | { name: string, activation: string } on "action", { name: string } on "attack" / "tech" |
| detail | string |
| tags | Array<{ lid: string, val?: string }> |
| attack_type | string - "attack" only |
| deployable | { actor: Actor, lid: string } \| null - "action" only |
| flowState | FlowState |
flowState is the Lancer flow state: state.data (e.g. data.damage, data.bonus_damage), state.la_extraData, state.injectFlowExtraData(obj), state.getFlowExtraData(), state.actor, state.item.
Trigger Types & Data¶
Every trigger passes a data object. All objects receive distanceToTrigger and canTriggerReaction (reactor to triggering token), plus _cancelledBy on cancellable triggers. Attack-carrying triggers also get isRangedAttack(): item-less basic attacks report Melee, so past 1 hex from every target they count as ranged.
Tag is { lid: string, val?: string }. ActionData and FlowState are defined above.
Attack Triggers¶
onInitAttack - attack initiated, before Attack HUD
onAttack - attack roll made
onHit - attack hit
onMiss - attack missed
onPreDamage - once per damage roll, before the damage HUD builds
Mutate triggerData.flowState.data.damage or .bonus_damage to alter base damage types/values before the player rolls.
{
triggeringToken: Token,
weapon: Item,
targets: Array<Token>,
hitTokens: Array<Token>,
attackType: string,
actionName: string,
tags: Array<Tag>,
actionData: ActionData,
cancelDamage: CancelFunction,
flowState: FlowState
}
cancelDamage aborts the whole damage roll, so no damage card is printed. It is flow-wide, not per target: to spare one target of several, set that entry's hit to false in flowState.data.hit_results instead.
onDamage - damage applied
Tech Triggers¶
onInitTechAttack - before Tech HUD
onTechAttack - tech roll made
onTechHit - tech attack hit
onTechMiss - tech attack missed
Movement Triggers¶
onPreMove - before movement is finalized
{
triggeringToken: Token,
distanceToMove: number,
elevationToMove: number,
startPos: { x, y },
endPos: { x, y },
isDrag: boolean,
moveInfo: {
isInvoluntary: boolean,
isTeleport: boolean,
isUndo: boolean,
isModified: boolean,
pathHexes: Array<{ x, y, cx, cy, isHistory, hexes }>
},
cancel: () => void,
cancelTriggeredMove: CancelMoveFunction,
changeTriggeredMove: ChangeMoveFunction
}
onMove - movement completed
{
triggeringToken: Token,
distanceMoved: number,
elevationMoved: number,
startPos: { x, y },
endPos: { x, y },
isDrag: boolean,
moveInfo: {
isInvoluntary: boolean,
isTeleport: boolean,
pathHexes: Array<Object>,
isFreeMovement: boolean,
movementCost: number,
isModified: boolean,
extraData: Record<string, any>
},
moveLeg: {
start: string | null,
end: string | null,
boosted: boolean,
granted: boolean,
spentBefore: number,
spentAfter: number
} | null
}
moveLeg names the movement band the leg started and ended in (standard, boost, over-boost), with the movement spent either side. It is null out of combat and on free movement.
onInvoluntaryMove - before each involuntary per-token move, cancellable
{
triggeringToken: Token,
token: Token,
distance: number,
actionName: string,
item: Item,
destination: { x: number, y: number },
cancel: (reason?: string) => void
}
cancel(reason?)synchronously skips this specific token's move. Other tokens in the batch still proceed.- Does not fire when
knockBackToken()is called with{ asVoluntary: true }- in that mode the move goes throughonPreMove/onMovelike a regular drag. actionNameanditemare passed from the caller (e.g."Grapple"), used byonlyOnSourceMatch.
Deployment & Placement Triggers¶
onDeploy - deployable or weapon token placed on the map
{
triggeringToken: Token,
item: Item,
deployedTokens: Array<TokenDocument | Token>,
deployType: "deployable" | "throw",
distanceToTrigger: number,
canTriggerReaction?: boolean
}
deployedTokens holds TokenDocuments from the throw and deploy flows, but the manual drop path passes canvas Tokens. Read through .document ?? entry if you need one shape.
Turn Events¶
onRoundStart - once at the start of every round, including round 1
onEnterCombat / onExitCombat - token added to / removed from the combat tracker
Status Effect Triggers¶
onPreStatusApplied - before a status is applied (non-async evaluate only)
onPreStatusRemoved - before a status is removed (non-async evaluate only)
onStatusApplied / onStatusRemoved
Structure & Stress Triggers¶
onPreStructure - before the structure roll, can cancel the flow
onStructure - after the structure roll
onPreStress - before the overheat roll, can cancel the flow
onStress - after the overheat roll
onRoll - between a roll resolving and its chat card printing
Fires for attackRoll, techAttackRoll, damageRoll, skillRoll, structureRoll, stressRoll.
{
triggeringToken: Token,
rollType: string,
roll: Roll,
total: number,
success: boolean,
targets: Array<Object> | undefined,
item: Item,
isReroll: boolean,
rerollCount: number,
reroll: RerollFunction,
changeRoll: ChangeRollFunction,
flowState: FlowState
}
reroll()re-runs the Lancer flow step that produced the roll.changeRoll(newTotal)sets the total (and recomputes hit/crit for attack flows). Both cascade: after either call,onRollre-fires so later reactions see the new state.- No engine-level reroll cap. Reactions that reroll should gate themselves via
api.setFlowFlag(triggerData, '_myReactionRerolled')+api.getFlowFlaginevaluate. successrule: attack/tech = any hit, skill = total >= 10, damage/structure/stress = undefined.targetsisundefined(not an empty array) onskillRoll,structureRollandstressRoll.changeRollon structure/stress only updatesroll._total(title/desc stay stale, preferreroll()).
onDestroyed - token delete when structure.value <= 0 || stress.value <= 0
triggeringToken may be a fallback { document, id, name, actor } object if the canvas token is already gone.
onTokenCreated - any token placed on the canvas (100ms delay, same timing as onInit)
onTokenRemoved - any token deletion (unconditional, unlike onDestroyed)
Fires before the token leaves canvas.tokens, and not at all if it is already gone.
onTokenVisibility - token hidden flag toggled (GM eye icon)
HP & Heat Triggers¶
onPreHpChange - before HP changes, can cancel or modify the value
onHpGain - after HP increases
onPreHeatChange - before heat changes, can cancel or modify the value
onHeatGain - after heat increases
onHeatLoss - after heat decreases
Stat & Activation Triggers¶
onInitCheck - before the check roll
{
triggeringToken: Token,
statName: string,
checkAgainstToken: Token,
targetVal: number,
item: Item | null,
actionName: string | null,
cancelCheck: CancelFunction,
flowState: FlowState
}
item / actionName are null unless the caller stamped sourceItemUuid / sourceAction.
onCheck - check result
onInitActivation - before item/action activates, before resource use (non-async evaluate only)
onActivation - item/action fired
extraData carries anything injected via startRelatedFlowToReactor / flow-state injection. Profile switches fire it with the profile name. Mod activations carry extraData.hostWeapon.
Ending an activation fires onEndActivation instead, never this trigger.
reactionJustConsumed is true when this activation is what spent the actor's Reaction, which is how a self-reactor with checkReaction on still passes its own gate here.
onInitEndActivation - before an activation is ended (non-async evaluate only)
Fires when the end action from setItemAsActivated runs, in place of onInitActivation. cancelAction only suppresses the end card, the item is already marked inactive by then.
onEndActivation - an activation was ended
Fires when the end action from setItemAsActivated runs, in place of onActivation. This is where teardown belongs.
onPostActivation - after the onActivation sweep
Fires once every onActivation reaction has run, with the same payload plus results: whatever each awaited reaction returned from activationCode, keyed by reaction name. A reaction that returns nothing is absent. Two reactors firing the same name give an array.
Not in results: popup activations, which resolve later, and reactions with awaitActivationCompletion: false. This trigger only reads what happened, it cannot cancel or change the activation.
onPostEndActivation - after the onEndActivation sweep
Same as onPostActivation for the end action, with endActivation: true.
onUpdate - token document update, after it settles (movement animation done, final ruler segment only)
{
triggeringToken: Token,
document: TokenDocument,
change: Record<string, any>,
options: Record<string, any>,
distanceToTrigger: number | null
}
Fires for any token update, so gate on change. For movement that means x, y or elevation present. The hook waits for movementAnimationPromise and skips non-final ruler segments, so distanceToTrigger is the settled end-of-move distance. Engagement runs on this trigger.
Custom Triggers¶
dispatchCustomTrigger → Promise<void> - fire your own trigger name
| Param | Type | Description |
|---|---|---|
| name | string |
Not a built-in name, not onInit* |
| data | object |
Becomes triggerData. Add triggeringToken for the self/other, disposition and distance filters |
Listen via the editor's Custom field or triggers: ["myTrigger"]. Always fires, in or out of combat. Not cancellable, no consumption.
Callback Signatures¶
Shared params: triggerType: TriggerType, triggerData: TriggerData, reactorToken: Token, item: Item | null (null for general activations), activationName: string, api: LancerAutomationsAPI.
| Callback | Signature | Returns |
|---|---|---|
evaluate |
(triggerType, triggerData, reactorToken, item, activationName, api) |
boolean - must be synchronous on cancellable triggers |
activationCode |
(triggerType, triggerData, reactorToken, item, activationName, api) |
Promise<void> |
onInit |
(token: Token, item: Item, api: LancerAutomationsAPI) |
Promise<void> - runs when a token carrying the item is created |
onMessage |
(triggerType, data: any, reactorToken, item, activationName, api) |
Promise<void> - runs on the client targeted by sendMessageToReactor |
debugActivation → Object
triggerData.debugActivation(label?)
api.debugActivation(triggerType, triggerData, reactorToken, item, activationName, label?)
Console-logs everything the current callback received, including the helper functions the trigger provides, and returns the same summary object. Available in evaluate and activationCode.
| Param | Type | Default | Description |
|---|---|---|---|
| label | string |
activation name | Heading on the console group |
Debug mode and breakpoints: Automation Engine - Debugging an automation.
Concepts¶
Consumption¶
Charge-consumption config attached to an effect. Pass it as extraOptions.consumption on applyEffectsToTokens, or options.consumption on addGlobalBonus.
The charge count is not part of the config, it sits beside it: extraOptions.stack on applyEffectsToTokens, bonusData.uses on addGlobalBonus.
consumeEffectCharge(effect) decrements the effect's statuscounter on each matching trigger and deletes the effect at 0. grouped / groupId make several effects share one counter (deleted together).
| Field | Type | Default | Description |
|---|---|---|---|
| trigger | TriggerType \| TriggerType[] |
required | Trigger(s) that consume a charge |
| originId | string |
bearer token id | Only consume if this token is involved |
| role | "source" \| "target" |
either | How the origin must be involved: caused the trigger, or was one of its targets |
| grouped | boolean |
false |
Share one counter across all effects in this call (auto-fills groupId) |
| groupId | string |
auto | Shared counter id across calls |
| evaluate | (triggerType: TriggerType, data: TriggerData, token: Token, effect: ActiveEffect) => boolean |
null |
Extra gate |
| itemLid | string |
- | Only consume for this item source. Comma-separated for several LIDs |
| itemId | string |
- | Only consume for this exact item document id |
| actionName | string |
- | Only consume for this action name, e.g. "Boost" |
| minDistance | number |
- | Only consume if distanceMoved is at least this. Movement triggers only |
| checkType | string |
- | Only consume for this roll title, e.g. "AGI Check" or "AGI Save (>= 12)". Matched whole against the card title, not a stat name |
| checkAbove | number |
- | Only consume if the roll total is at or above this |
| checkBelow | number |
- | Only consume if the roll total is at or below this |
| statusId | string |
- | Status triggers only. Only consume for this status id. Comma-separated for several |
Resistance that lasts 3 hits (Dispersal Shield). One counter shared by all three resistance effects, so they vanish together on the third hit:
await api.applyEffectsToTokens({
tokens: [target],
effectNames: ["resistance_kinetic", "resistance_energy", "resistance_explosive"],
note: "Dispersal Shield"
}, {
stack: 3,
consumption: { trigger: "onDamage", originId: target.id, grouped: true }
});
+1 accuracy on the next attack only, spent when it hits:
await api.addGlobalBonus(target.actor, { name: "Squad Leader", val: 1, type: "accuracy", rollTypes: ["attack"] },
{ duration: "1 Round", origin: reactorToken, consumption: { trigger: "onHit" } });
Only spend on a failed AGI check, using the gate:
consumption: {
trigger: "onCheck",
checkType: "AGI Check",
evaluate: (triggerType, data, token, effect) => data.success === false
}
One die, several possible uses (Leadership die). Bonuses added in separate calls share one groupId: whichever consumes first removes them all.
const groupId = foundry.utils.randomID();
await api.addGlobalBonus(actor, { name: "Leadership (Accuracy)", type: "accuracy", val: 1, rollTypes: ["attack"] },
{ consumption: { trigger: "onAttack", groupId } });
await api.addGlobalBonus(actor, { name: "Leadership (Damage)", type: "damage", damage: [{ val: "1d6", type: "Kinetic" }] },
{ consumption: { trigger: "onDamage", groupId } });
addGlobalBonus copies only the trigger filters. It drops originId, role, grouped and statusId, silently. Without originId the engine falls back to the bearer token at trigger time, so each bearer consumes on its own. When the bearer can also be a target of the trigger, gate with evaluate, e.g. (t, data, bearer) => data.triggeringToken?.id === bearer.id.
Reaction economy¶
Two separate keys, both opt-in, at different scopes:
- checkReaction (reaction config, default off) - the availability gate, opt-in per activation. When set, the reaction is skipped if the reactor has no reaction left this round.
- consumeReaction (world setting, default off) - what spends a reaction, opt-in per world: when a Reaction-type action fires, it decrements system.action_tracker.reaction by 1.
A reactor that just spent its own reaction still passes its own checkReaction gate on that same trigger, so an activation can react to the action that consumed it.
Effect flags¶
Every effect this module creates stores its metadata under flags['lancer-automations']. Any extra keys you pass in extraOptions (beyond reserved meta keys like stack / consumption / changes) are copied there as-is: extraFlags on removeEffectsByName* deletes an effect only if ALL supplied keys equal the stored values.
Two keys the module manages itself:
- linkedBonusId - ties an effect to a bonus so removing one removes the other.
- statDirect - stat-reversal metadata { key, value, preBonusValue } used to restore a current-resource stat by its delta when the effect ends.
extraData / la_extraData¶
Ad-hoc state that round-trips through a flow. Pass it in (startRelatedFlowToReactor(userId, extraData), or flowState.injectFlowExtraData(extraData) mid-flow). It is merged onto state.la_extraData and resurfaces as triggerData.extraData on the downstream onActivation. Read it back inside a flow with flowState.getFlowExtraData().
Immunity subtypes¶
Immunity bonuses (type: "immunity") carry exactly one subtype. The engine only recognises these values. All resolve through getImmunityBonuses(actor, subtype):
| Subtype | Checked by | Extra fields |
|---|---|---|
effect |
checkEffectImmunities |
effects: [names] |
damage |
applyDamageImmunities |
damageTypes: [types], "all" for every type |
resistance |
checkDamageResistances (halves) |
damageTypes: [types], "all" for every type |
crit |
hasCritImmunity |
- |
hit |
hasHitImmunity |
- |
miss |
hasMissImmunity |
- |
elevation |
isClimbingImmune (movement) |
- |
terrain |
isTerrainImmune (terrain / zones) |
- |
obstacle |
isPhasing (move through other characters) |
- |
provoke |
engagement + reaction gate | - |
Duration labels¶
Accepted duration.label values:
- start / end / round - tick down at turn start, turn end, or round change. Only these expire by time, and only in combat.
- indefinite - never expires by time. unlimited is a retired alias, still accepted and normalized to indefinite.
- permanent - never expires by time and survives a Full Repair.
- constant - bonus only: passive and invisible, no token icon or counter (same as addConstantBonus).
Stat codes¶
HASE-plus-grit keys used by stat rolls and checks: HULL, AGI, SYS, ENG, GRIT.
Activation Object Structure¶
One entry in an activation group's reactions array. Interface: ReactionConfig in types.d.ts.
| Field | Type | Default | Description |
|---|---|---|---|
| triggers | (TriggerType \| string)[] |
required | Trigger names this entry listens to. Any non built-in name is a custom trigger |
| name | string |
"" |
Display name in the manager, and the key that matches an entry to its saved user settings |
| enabled | boolean |
true |
Master toggle |
| awaitActivationCompletion | boolean |
true |
Required to intercept onPreMove, onInitActivation, onInitEndActivation, onInitAttack, onInitTechAttack, onInitCheck. The engine tests !== false, so a code-registered entry awaits unless you opt out. The manager's checkbox writes an explicit value and starts unchecked |
| triggerDescription | string |
"" |
Header text on the activation card |
| effectDescription | string |
"" |
Body text on the activation card |
| comments | string |
"" |
Author notes. Never shown in play |
| actionType | "Automation" \| "Reaction" \| "Free Action" \| "Quick Action" \| "Full Action" \| "Protocol" \| "Other" |
"Automation" |
Lancer action type. "Reaction" is what spends a reaction |
| frequency | string |
"" |
Display-only text |
| triggerSelf | boolean |
false |
React to own actions |
| triggerOther | boolean |
true |
React to others' actions (includes targets) |
| triggerTarget | boolean |
false |
React when the reactor is one of the event's targets, even with triggerOther off. Target-capable triggers only |
| checkReaction | boolean |
false |
Skip if the reactor has no Reaction left this round. Plain truthy check, so omitting it leaves the gate off |
| requireCanProvoke | boolean |
false |
Skip unless the trigger source can provoke the reactor (engagement, provoke immunity). Used by Overwatch |
| checkUsage | boolean |
false |
Item entries only. Skip when the item is unloaded, uncharged, out of uses, or past its tg_turn / tg_round limit |
| isReaction | boolean |
false |
Marks the entry as a reaction in the manager UI |
| outOfCombat | boolean |
false |
Also fire outside combat. Bypassed for onEnterCombat, onExitCombat, onTurnStart, onTurnEnd, onRoundStart and custom triggers, which always fire |
| onlyOnSourceMatch | boolean |
false |
Match by name (general) or by possession (item) |
| dispositionFilter | Array<"hostile" \| "friendly" \| "neutral" \| "secret"> |
[] |
Restrict by disposition toward the trigger |
| reactionPath | string |
"" |
Action path, e.g. extraActions.Print. Also gates availability: ranks[N] needs the talent at rank N+1, profiles[N] needs that weapon profile selected |
| evaluate | ActivationCallback \| string |
- | Gate. Must be synchronous on cancellable triggers |
| activationType | "code" \| "macro" \| "flow" \| "none" |
"flow" |
What runs |
| activationMode | "instead" \| "after" |
item: "instead", general: "after" |
after also fires the reaction's own flow/card. Macro/code only |
| sceneReactor | "off" \| "add" \| "only" |
"off" |
General only. Evaluate once as the active scene on the GM client. add keeps the per-token passes, only replaces them |
| sceneId | string |
"" |
General only. Limit the activation to this scene id, empty for every scene |
| activationCode | ActivationCallback \| string |
- | The body, for activationType: "code" |
| activationMacro | string |
"" |
Macro name, for activationType: "macro" |
| autoActivate | boolean |
false |
Skip the popup and run immediately |
| onInit | ((token, item, api) => Promise<void>) \| string |
- | Runs on token creation. Scene reactors: also on scene load, token is the scene stand-in |
| onMessage | ((triggerType, data, reactorToken, item, activationName, api) => Promise<void>) \| string |
- | Runs on the client targeted by sendMessageToReactor |
The group wrapper (ReactionGroup) is { category?: string, itemType?: string, enabled?: boolean, reactions: ReactionConfig[] }.
A whole group, as registered by an item LID:
api.registerDefaultItemReactions({
"npcf_suppress_archer": {
category: "NPC",
itemType: "npc_feature",
reactions: [{
triggers: ["onActivation"],
onlyOnSourceMatch: true,
triggerSelf: true,
autoActivate: true,
outOfCombat: true,
actionType: "Quick Action",
activationType: "code",
activationMode: "instead",
activationCode: async function (triggerType, triggerData, reactorToken, item, activationName, api) {
const targets = await api.chooseToken(reactorToken, { range: 10, count: 1 });
if (targets?.length)
await api.applyMark(reactorToken, targets, { effect: "impaired" });
}
}]
}
});
A setup-only entry (no trigger, runs once per token) uses triggers: [] with activationType: "none" and an onInit.