Hooks
The fifteen moments a rule can attach to, and what each one carries.
A hook is a rule that fires when the engine does something, anywhere in the world — as opposed to an item rule, which fires when a player uses one specific object.
A hook has a trigger, an optional condition, and a body.
The triggers
| Trigger | Fires when | Carries |
|---|---|---|
onSpawnCreature |
a creature enters the world, before anyone acts on it | |
onDamage |
hit points actually move — any source | .amount |
onHeal |
the mirror of onDamage |
.amount |
onDeath |
once per entity, after loot and XP | .killed |
onCombatStart |
a fight begins (subject is the location) | |
onEffectApplied |
a status effect lands on someone | |
onAttack |
every swing, including a miss | |
onLocationEnter |
the party arrives (subject is the new location) | |
onItemUse |
an item rule runs, whether or not it did anything | |
onRoll |
a skill check resolves — observe-only | .passed .total .dc .margin .crit .botch |
onTurnStart |
once per actor per turn | |
onTurnEnd |
once per actor per turn | .inCombat |
onTimePasses |
in-game time advances — turn, travel or sleep | .minutes .resting |
onTravel |
the party covers ground | .distance |
onBeforeAction |
a player attempts an action, before it happens | .attack .weapon .ranged |
Picking the right one
onAttack vs onDamage. onAttack fires on every swing including misses; onDamage
fires only when hit points actually move. A rule about trying uses the first; a rule about
hurting uses the second.
onTurnEnd vs onTimePasses. This is the one that matters. onTurnEnd fires once per
actor per turn, in every game. onTimePasses fires when the clock moves, and carries
minutes that may be the narrator's estimate — see Minutes are not time.
For anything that ticks per character per turn — a resource regenerating, a cooldown —
use onTurnEnd.
onRoll is observe-only
It may write fields and nothing else. A hook that could rewrite a roll would make the roll card unable to explain its own number.
This is what lets a number go up by being used: gate on the roll you care about, and add to a declared field. There is no built-in proficiency system — if you want one, this plus a field with a roll term is how you build it.
// condition: $.event.passed && $.event.margin > 5
$.adjustField({ field: "lockpicking", by: 1 })
$.event.margin is total minus DC — negative on a failure. crit and botch read the
natural face, which is a different question from passed: a natural 20 passes a DC it
could not otherwise reach, so a rule firing on a fumble cannot find one from passed alone.
onBeforeAction is the one that says no
It is the only trigger whose job is refusal. A refuse inside it stops the
action and explains why.
// condition: $.event.attack && $.field('stamina') < 5
$.refuse("their arms are too heavy to lift the blade again")
It carries which action was attempted, so a rule gates on $.event.attack rather than
parsing a string.
Worked example: a fever that worsens outdoors
Three hooks, one system.
onTimePasses — the fever climbs with time:
if ($.field('fever') > 0) {
$.adjustField({ field: "fever", by: $.timed
? ($.event.minutes / 60) * $.dial('feverPerHour')
: $.dial('feverPerTurn') })
}
onTurnEnd — a high fever hurts, once per turn:
if ($.field('fever') >= 10) {
$.damage({ amount: 1 })
}
onBeforeAction — too sick to fight:
if ($.event.attack && $.field('fever') >= 15) {
$.refuse("the fever has taken their balance; the blade will not answer")
}
Next
Conditions — gating when a hook is allowed to run.