Reusable behaviors
A behavior is reusable Lua logic. A perk, enchantment, battle rule, or independent actor attaches it to a fighter and supplies its configuration. Registering a behavior by itself does not activate it; attach it using the perk or enchantment functions or a behavior rule, or pass it to an actor definition.
sf2.behaviors.register
Section titled “sf2.behaviors.register”Register a behavior with one or more supported combat callbacks.
Signature: sf2.behaviors.register { id, parameters?, state?, on_event = function, ... }
Requires: content.register. Operations performed by callbacks may require
additional capabilities such as combat.magic_charge.
When: Entrypoint, before registering perks, enchantments, or rules that use it.
Returns: A behavior handle.
| Field | Type | Default | Meaning |
|---|---|---|---|
id |
String | Required | Stable local behavior ID. |
parameters |
Schema table | Empty | Typed configuration supplied by each attachment. |
state |
State specification | Omitted | Opt in to instance state and the self callback form. |
Supported on_* names |
Lua functions | At least one required | See the callback reference. |
local opening_charge = sf2.behaviors.register { id = "opening_charge", parameters = { amount = sf2.behaviors.NUMBER }, on_fight_begin = function(parameters, fighter, event) fighter:add_magic_charge(parameters.amount) end,}This example needs combat.magic_charge when the callback runs. A perk or
enchantment using it supplies parameters = { amount = 0.35 }.
Parameter schema
Section titled “Parameter schema”The constants sf2.behaviors.NUMBER, INTEGER, BOOLEAN, and STRING have
the same bounds as saved state fields. Up to 64 parameters are
allowed. The short form amount = sf2.behaviors.NUMBER means a required number
with no default. The long form makes defaults explicit:
parameters = { amount = { type = sf2.behaviors.NUMBER, required = true, default = 0.35 }, label = { type = sf2.behaviors.STRING, required = false },}Missing required parameters, unknown parameter names, and wrong types are rejected. The attached perk/enchantment supplies values; the behavior supplies the schema and functions.
Instance state and callback arguments
Section titled “Instance state and callback arguments”Without state, callbacks receive (parameters, fighter, event).
Declaring state changes that to (self, fighter, event):
local ward = sf2.behaviors.register { id = "opening_ward", parameters = { multiplier = sf2.behaviors.NUMBER }, state = { lifetime = "round", fields = { ready = { type = sf2.behaviors.BOOLEAN, default = true }, }, }, on_damage_resolving = function(self, fighter, event) if self.state.ready and event.damage > 0 then fighter:scale_incoming_damage(self.params.multiplier) self.state.ready = false end end,}Attach this behavior with parameters = { multiplier = 0.5 } and declare
combat.modify_hit. It halves the first positive incoming hit of each round.
| State field | Default | Meaning |
|---|---|---|
fields |
Empty schema | Typed values; required fields must have defaults. |
lifetime |
"fight" |
"round", "fight", or "saved". |
version |
1 |
Positive schema version for saved instance state. |
migrations |
Empty | Old-version functions that upgrade primitive state values. |
self.params contains resolved configuration; self.state contains copied
state for this particular active effect. Round state resets for a new round;
fight state resets for a new fight. Saved state follows a player’s individual
equipped enchantment or learned perk and is persisted by the normal save path.
NPC, battle-rule and actor instances support round and fight state, not saved state.
Actor instances are separate for every spawn, including repeated copies of one
definition. They receive actor spawn/end, tick, damage/contact and animation
callbacks; they do not inherit main-fight/round, combo or style callbacks.
Rule state is isolated per rule and fighter, including when multiple rules share
one behavior. It never modifies an equipment save.
Saved-instance migrations use the same old-version → next-version function
shape as mod state. Do not retain self.state tables to edit later: a successful
callback validates and commits its current copy. Failed callbacks do not commit
state, but gameplay operations already performed are not undone.
Fighter/target/effect methods expire as soon as the callback returns. Keep your own primitive values, not a fighter object, for later callbacks.