Fighter methods
For objective-based victories and defeats, see
fighter:end_round. It requires a declared
controller rule and queues a round result through the normal offline result flow.
These methods are supplied as the fighter argument to supported
combat callbacks. Use a colon (:), which passes the
fighter as the method’s first argument. They are not global sf2 functions.
The examples below run inside a callback. A fighter reference must not be
saved and used after that callback returns. Missing capability declarations,
expired references, invalid values, or an unavailable fighter raise Lua errors.
Most mutation methods return nil on success; exceptions are documented below.
Observation methods return detached data.
For native collision-rig rectangle queries and owned arena warning art, see Arena regions and markers.
fighter:snapshot
Section titled “fighter:snapshot”Read fresh combat observations, including both fighters and the engine’s elapsed
fight clock. Use this when making a health or distance
decision; the older fighter.health field is captured at callback entry.
Signature: fighter:snapshot()
Returns: A CombatSnapshot table, or nil if the fighter cannot be observed.
When: Inside any supported combat behavior callback, including battle rules, perks, enchantments, warrior behaviors and attached actor behaviors. Each call samples the current state. The callable reference expires when that callback returns; the returned data may be retained as an observation, but will not update itself.
Requires: No additional capability. Observing the opponent does not require
combat.target; changing the opponent still requires the normal capabilities.
| Field | Meaning |
|---|---|
self |
The fighter receiving this callback, even when it is the enemy. |
opponent |
This fighter’s current native target snapshot, or nil when unavailable. It can be a main fighter or an independent actor. |
frame |
Nonnegative elapsed active-fight frame count, shared by both sides. |
seconds |
frame / 60, using the recovered engine’s simulation rate. |
round_active |
Whether the engine is processing the round at capture time. |
difficulty |
The fight’s difficulty, described below, or nil when the fight has none. |
Both fighter snapshots contain health, max_health, health_bars, and
position = { x, y, z }. Health uses the game’s life units, the same units as
fighter.health, damage and fighter:change_health: a normal fighter’s full
health is 1. A raid boss stores its whole shield in these units, so its
max_health equals its warrior’s health_bars (for example 20), and a hit of
0.25 removes 0.25 of it. health_bars is that authored shield total (at least
one), not the number of bars remaining or the number drawn on the HUD. Divide
health by max health for a fraction, guarding against a zero maximum.
difficulty is the band the map’s difficulty bar shows for this encounter,
worked out the same way from the player’s and the current opponent’s equipment
and rating. It is a table with two fields:
| Difficulty field | Meaning |
|---|---|
level |
Integer band: 0 very easy, 1 easy, 2 normal, 3 hard, 4 very hard. |
ratio |
The opponent-to-player rating ratio the band is chosen from; above 0. Bands start above 0.82, 1.3, 2.6 and 5.2 in the shipped settings. |
It is measured once for each opponent of a fight, so it does not change as health
or charges change, and it is the same for both fighters’ callbacks. It is nil in
fights that have no difficulty rating. Use it to scale your own rules, for example a
computer opponent’s chance to use an ability:
local combat = fighter:snapshot()local level = combat and combat.difficulty and combat.difficulty.level or 2local chance = level * 0.1 -- 0% on very easy, 40% on very hardEach fighter snapshot may also have an actor table when the observed root is
an initialized independent fighter. Main fighters omit it. This applies to
actor snapshots and Lua AI observations.
| Actor field | Meaning |
|---|---|
id |
Live fight-local actor instance ID, such as a1; changes when a replacement is spawned. |
definition |
Qualified actor definition ID, such as my.mod:actors/helper. |
owner |
Mod that owns this actor instance. |
team |
Absolute "player" or "opponent" team, irrespective of who receives this observation. |
These are copied observations, not actor handles or permissions. Editing them cannot retarget, transfer ownership or change a team. Retained snapshots remain historical data; reacquire a live scoped actor reference for commands.
Each fighter snapshot also has an optional animation table.
It is nil when the native controller is absent, stopped, or cannot provide a
valid bounded observation. The rest of the fighter snapshot remains available.
| Animation field | Meaning |
|---|---|
name |
Native name of the currently playing animation, including authored namespaced moves. |
type |
Native authored classification: "none", "move", or "attack". |
facing |
Native orientation sign: 1 along positive model X, -1 along negative model X. |
frame |
The animation’s current frame number, as a perk’s <CurrentAnimation Min Max> condition compares it. Use it to act on a particular frame of a move. While the fighter is in a physics reaction (for example falling), it is the physics frame instead. |
intervals |
Array of currently active { name, type } interval observations, in native order. May be empty; at most 256 entries. |
Interval type is one of "none", "unstable", "uninterrupt",
"self_uninterrupt", "attack", "block", "invulnerable", or
"invisible". name preserves the authored interval name, or is empty when
the native interval has no name. Custom named intervals may have type "none".
These are observations from the controller’s latest interval update, not the
animation’s complete list of future windows. An active attack interval does not
guarantee contact; an interval label does not override other native combat rules.
For example, inside a callback you can detect an opponent’s active attack window:
local combat = fighter:snapshot()local animation = combat and combat.opponent and combat.opponent.animationlocal attacking = falseif animation then for _, interval in ipairs(animation.intervals) do if interval.type == "attack" then attacking = true; break end endend-- Use attacking in your own Lua rule logic.Animation and interval tables are detached values: editing them cannot start/stop an animation, turn a fighter, or add/remove an interval. They can be retained as historical observations, but never used as an AI candidate or an engine operation handle.
Positions use arena model coordinates, not screen pixels or metres. The clock
counts active fight frames across rounds, excluding round transitions and frames
when fight processing is stopped; it is neither wall time nor the remaining round
timer. Capture a baseline in on_round_begin to measure a round-local duration.
This method does not schedule callbacks or grant control of the clock.
on_damage_resolving = function(self, fighter, event) local combat = fighter:snapshot() if not combat or combat.self.max_health <= 0 then return end local fraction = combat.self.health / combat.self.max_health if fraction < 0.25 then fighter:scale_incoming_damage(0.5) -- requires combat.modify_hit endendEach result is a detached Lua table. Editing it affects only your copy and cannot move a fighter, change health, alter later snapshots, or change the engine clock. Call again after a health operation to obtain a fresh observation. Query failure does not fabricate zero health or a default position.
fighter:scale_outgoing_damage
Section titled “fighter:scale_outgoing_damage”Scale the current attacker’s pending hit.
Signature: fighter:scale_outgoing_damage(multiplier)
Returns: nil on success; invalid values, missing capability, unavailable hit
or expired references raise a Lua error.
When: Inside on_damage_dealing, and inside attacker-side on_post_hit where
event.target == "opponent". It is absent from on_hit_post_crit and incoming
on_post_hit callbacks.
Requires: combat.modify_outgoing_hit.
on_damage_dealing = function(parameters, fighter, event) fighter:scale_outgoing_damage(1.25)endThe multiplier must be finite and between 0 and 16 inclusive. The resulting damage must remain finite, nonnegative, and representable as a single-precision number. Invalid scaling leaves the pending value unchanged. Calls multiply the current value; zero remains zero. Invulnerability, shields, incoming modifiers, and the normal health path run afterward. This does not bypass defense or change the shared weapon/stat economy. Earlier successful operations remain applied if a later handler fails. Retained methods cannot modify a later hit.
fighter:add_outgoing_damage
Section titled “fighter:add_outgoing_damage”Add normalized health damage to the current attacker’s pending hit.
Signature: fighter:add_outgoing_damage(amount)
Requires: combat.modify_outgoing_hit.
When: Inside on_damage_dealing, and inside attacker-side on_post_hit where
event.target == "opponent". It is absent from on_hit_post_crit and incoming
post-hit callbacks.
Returns: nil on success; invalid values, missing capability, unavailable hit
or expired references raise a Lua error.
amount must be finite and from 0 through 1 normalized health units. It is
added to the current pending damage value; the resulting value must remain finite,
nonnegative and representable as a single-precision number. Defender rules and
normal health application still run afterward.
fighter:add_outgoing_damage(0.04)fighter:change_health
Section titled “fighter:change_health”Add or subtract health through the game’s normal health-change path.
Signature: fighter:change_health(amount)
Requires: combat.change_life.
When: A supported callback with a live fighter capability.
Returns: nil.
amount is a finite number representable by a 32-bit float. Positive values
heal; negative values damage. Values use the game’s life units rather than a
separate percentage-conversion API. A fighter whose health has already reached
zero cannot be revived with this operation.
fighter:change_health(0.05)Use small values and test them against the relevant opponent and health-bar
configuration: -1 removes a normal fighter’s whole health but only 1 of the 20
units of a health_bars = 20 raid shield. Healing after on_damage_received
does not reverse a lethal hit.
fighter:add_magic_charge
Section titled “fighter:add_magic_charge”Add to the fighter’s magic charge and let the game normalize it.
Signature: fighter:add_magic_charge(amount)
Requires: combat.magic_charge.
When: A supported callback with a fighter capability.
Returns: nil.
amount must be a finite 32-bit-float value. The example adds 0.35 charge through
the normal magic-charge path. The player needs a usable magic item to observe it.
fighter:add_magic_charge(0.35)fighter.opponent:change_health
Section titled “fighter.opponent:change_health”Apply the health-change operation to the opposing fighter.
Signature: fighter.opponent:change_health(amount)
Requires: Both combat.target and combat.change_life.
When: A supported callback where fighter.opponent is available.
Returns: nil; the same numeric and no-revival rules as change_health apply.
if fighter.opponent then fighter.opponent:change_health(-0.05)endfighter.opponent:add_magic_charge
Section titled “fighter.opponent:add_magic_charge”Adjust the opposing fighter’s magic charge.
Signature: fighter.opponent:add_magic_charge(amount)
Requires: Both combat.target and combat.magic_charge.
When: A supported callback where the opponent is available.
Returns: nil; accepts a finite 32-bit-float number.
if fighter.opponent then fighter.opponent:add_magic_charge(0.1)endThe opponent target also exposes move_by and
play_move. There are
no opponent shield or opponent pending-hit methods on this target object.
fighter:scale_incoming_damage
Section titled “fighter:scale_incoming_damage”Multiply this fighter’s pending incoming damage before it is applied.
Signature: fighter:scale_incoming_damage(multiplier)
Requires: combat.modify_hit.
When: Only inside on_damage_resolving.
Returns: nil.
multiplier is finite and between 0 and 1 inclusive. 0 prevents this hit’s
damage, 0.5 halves it, and 1 leaves it unchanged. Values above 1 are rejected.
Multiple active behaviors multiply in dispatch order.
fighter:scale_incoming_damage(0.5)Calling this after damage has resolved is too late; the method is not supplied in those callbacks.
fighter:set_hit_blocked
Section titled “fighter:set_hit_blocked”Decide whether the hit being classified counts as blocked. Use it to make your attacks unblockable for a while, or to let a guard stop a hit that would land.
Signature: fighter:set_hit_blocked(blocked)
Requires: combat.modify_outgoing_hit when event.target == "opponent"
(this fighter is the attacker), or combat.modify_hit when event.target == "self"
(this fighter is being hit).
When: Only inside on_hit_pre_crit.
Returns: nil. A non-boolean argument, a missing capability or a call after
the callback returned raises a Lua error.
| Argument | Type | Meaning |
|---|---|---|
blocked |
Boolean | false lets the hit through as unblocked; true counts it as blocked. |
-- Inside on_hit_pre_crit: the opponent's guard does not stop this fighter's hits.if event.target == "opponent" and event.blocked then fighter:set_hit_blocked(false)endThis works like a perk’s <SetHit Block="0"> in a <HitPreCrit> trigger. The game
then rolls the critical and calculates damage from your decision: an unblocked hit
can be critical and deals full, unblocked damage. Several handlers can change the
decision; the last call wins, and later handlers see it in event.blocked.
fighter:add_damage_shield
Section titled “fighter:add_damage_shield”Apply temporary damage reduction to future hits.
Signature: fighter:add_damage_shield(key, fraction, frames)
Requires: combat.effects.
When: A supported callback with fighter effect operations.
Returns: nil.
| Argument | Type and bounds | Meaning |
|---|---|---|
key |
1–64 letters, digits, or underscores | Name used to refresh/remove this behavior’s shield. |
fraction |
Finite number, 0–1 | Fraction of damage reduced; 0.25 means 25% reduction. |
frames |
Integer, 1–3600 | Duration in simulation frames; 60 frames is one combat second. |
fighter:add_damage_shield("opening_ward", 0.25, 180)This requests 25% reduction for three combat seconds. Reusing a key refreshes that behavior’s shield; different keys can compound. At most 128 shields are active per fighter. The timer pauses with combat and shields clear at round/fight cleanup. They do not add native buff icons or extra boss health bars.
fighter:remove_damage_shield
Section titled “fighter:remove_damage_shield”Remove a shield created under this behavior’s key.
Signature: fighter:remove_damage_shield(key)
Requires: combat.effects.
When: A supported callback with fighter effect operations.
Returns: nil.
key uses the same naming rules as add_damage_shield. It is scoped by the
behavior identity; this does not remove arbitrary native effects.
fighter:remove_damage_shield("opening_ward")fighter:set_flag
Section titled “fighter:set_flag”Create a native combat flag owned by this behavior instance.
Signature: fighter:set_flag(key, frames?)
Requires: combat.effects.
When: A supported callback during an active round, with a currently registered fighter. The method expires when the callback returns.
Returns: The qualified native flag name as a string. Setting an existing key does nothing and returns the same name. Invalid input or unavailable support raises a Lua error.
key is 1–64 ASCII letters, digits or underscores. The native name is
<qualified behavior ID>:<key> and must fit within 128 characters in total.
For behavior example.spell:behaviors/cast, key pending becomes
example.spell:behaviors/cast:pending.
on_animation_start = function(parameters, fighter, event) if event.target == "self" and event.animation_name == sf2.mod.id .. ":moves/cast" then fighter:set_flag("pending") endend,frames is optional: an integer from 1 to 216,000 native simulation frames
(60 per second). With it, the flag expires on its own after that time, exactly
like a perk’s timed <ModFlag>: native expiry removes it and emits its
ModExpires event, and a recharge indicator
can show its remaining time. Without it, the flag has no timer: clear it
explicitly or let native round/fight cleanup remove it. Setting a key that is
already active does nothing, even with a new duration; clear it first to
restart it:
fighter:clear_flag("cooldown")fighter:set_flag("cooldown", 600) -- ten secondsFlags are not saved in the profile. They participate in native
modifier lists, mod_exists conditions, expiry notifications and fighter form
transfers. There are at most 64 flag-owning instances per fighter registration
and 64 active flags per instance; exceeding either limit raises an error.
Ownership includes both behavior identity and equipped/rule instance. One instance
cannot clear another instance’s flags, even if both use the same key. Native move
conditions see the qualified name, so they match any active instance using that
name. This method cannot create or overwrite a core flag such as Stun.
fighter:has_flag
Section titled “fighter:has_flag”Check this behavior instance’s flag without changing it.
Signature: fighter:has_flag(key)
Requires: combat.effects.
When: A supported callback during an active round with a registered fighter.
Returns: true if this instance owns the flag, otherwise false. Invalid
keys, expired callback references or unavailable support raise a Lua error.
if fighter:has_flag("pending") then sf2.log.info("This cast still owns its pending flag")endThe key and length rules are the same as set_flag. This does not query arbitrary
native modifiers or another instance’s state.
fighter:clear_flag
Section titled “fighter:clear_flag”Remove this behavior instance’s flag through native modifier expiry handling.
Signature: fighter:clear_flag(key)
Requires: combat.effects.
When: A supported callback during an active round with a registered fighter.
Returns: nil. Clearing an absent key succeeds without emitting another
expiry event. Invalid keys, expired methods or unavailable support raise an error.
fighter:clear_flag("pending")Clearing an existing flag removes it from mod_exists observations and emits the
native ModExpires event for its qualified name. A move can listen using
events = { { type = "mod_expires", name = "example.spell:behaviors/cast:pending" } }.
Normal move conditions, locks and priority still decide whether that move runs.
Clearing one instance emits its expiry even if another instance retains a flag
with the same qualified name. For purely Lua bookkeeping with no native move
interaction, prefer declared behavior state.
fighter:set_control_blocked
Section titled “fighter:set_control_blocked”Restrict one player action for this behavior instance.
Signature: fighter:set_control_blocked(control, blocked)
Returns: nil. Invalid arguments, expired references, unavailable support,
or exceeding the source limit raise a Lua error.
When: A behavior callback for the player during an active round, including
on_fight_begin and on_round_begin setup before combat starts. Unavailable for
opponents, after a round ends, and in local versus.
Requires: combat.effects.
control is one of the case-sensitive strings "punch", "kick", "ranged",
"magic", or "raid_charge". blocked must be a boolean.
local restriction = sf2.behaviors.register { id = "restricted_magic", on_round_begin = function(parameters, fighter) fighter:set_control_blocked("magic", true) end,}sf2.rules.behavior { id = "restricted_magic", behavior = restriction, target = sf2.rules.PLAYER,}-- Attach the returned rule to a fight's rules list to apply it.true hides and blocks the action through the shared touch, keyboard and gamepad
gate. Blocking a held action releases it; resuming requires neutral input followed
by a fresh press. false releases only this behavior instance’s restriction. It
cannot override another instance, a native no_button rule, or ordinary equipment
and charge availability, and does not force a hidden ability button to appear.
Restrictions persist across callbacks and pause/resume. They clear when the next
round is prepared or the fight ends; reapply them in on_round_begin when needed.
Repeated calls are idempotent. Ownership includes the behavior and its attached
perk/rule instance. At most 256 instances can hold restrictions per controller;
each can block all five actions. Releasing an instance’s last block frees its slot.
fighter:set_button_cooldown
Section titled “fighter:set_button_cooldown”Start a recharge animation on one of this fighter’s combat buttons.
Signature: fighter:set_button_cooldown(control, frames)
Requires: combat.effects.
When: A supported callback during an active round, with a currently registered fighter. The method expires when the callback returns.
Returns: nil. Unknown controls, out-of-range frames, an inactive round or an
expired fighter raise a Lua error.
| Argument | Type and bounds | Meaning |
|---|---|---|
control |
String | "punch", "kick", "ranged" or "raid_charge". |
frames |
Integer, 1–3600 | Native combat frames (60 per second) for the button’s progress ring to refill. |
-- After the ability is used, show a ten-second recharge on the RaidCharge button.on_animation_start = function(self, fighter, event) if event.target == "self" and event.animation_name == "SummonAssistant" then fighter:set_button_cooldown("raid_charge", 600) endend,This is the same presentation as the native SetCooldown perk action: the button
empties and refills over frames. It only changes what the button shows. It does
not stop the move from being used; gate the move itself with a timed
flag and a not_mod move condition. Calling it again restarts
the ring. Only the player’s buttons are drawn, so an opponent’s call has no visible
effect. The magic button’s ring follows the magic charge and is not supported here.
fighter:set_control_visible
Section titled “fighter:set_control_visible”Show the RaidCharge button in a fight that would otherwise hide it.
Signature: fighter:set_control_visible(control, visible)
Requires: combat.effects.
When: A supported callback during an active round, with a currently registered fighter. The method expires when the callback returns.
Returns: nil. An unsupported control, a non-boolean visible, an inactive
round or an expired fighter raises a Lua error.
| Argument | Type | Meaning |
|---|---|---|
control |
String | "raid_charge". Other buttons follow their equipment and are not supported. |
visible |
Boolean | true shows the button, false hides it again. |
-- An equipped ability uses the RaidCharge input, so show its button.on_tick = function(self, fighter, event) if not self.state.shown then self.state.shown = true fighter:set_control_visible("raid_charge", true) endend,The game normally shows the RaidCharge button only in raid fights where the player carries raid charges. Use this when your perk or enchantment adds a move on the RaidCharge input in other fights. The raid charge count is not shown on a button made visible this way. A control restriction still hides a blocked button. Only the player’s button is drawn: for an opponent or in Local Versus the call succeeds and changes nothing. The next fight starts with the game’s own visibility again.
fighter:show_status_icon
Section titled “fighter:show_status_icon”Show or refresh a transient status icon owned by this behavior instance.
Signature: fighter:show_status_icon(key, sprite, frames, stacks?)
Requires: combat.effects.
When: A supported behavior callback with a live fighter capability.
Returns: nil; invalid arguments, unavailable native support or an expired
fighter method raise a Lua error.
| Argument | Type and bounds | Meaning |
|---|---|---|
key |
Valid behavior field name | Local key used to refresh or clear this behavior instance’s icon. |
sprite |
Sprite handle | Captured sprite handle from the current mod context. |
frames |
Integer, 1–3600 | Native-frame lifetime. 0 is rejected. |
stacks |
Integer, 0–10000 | Optional stack count; defaults to 0. |
local icon = sf2.assets.sprite("core:ui/skills/IconCrackedApple_Blue")fighter:show_status_icon("relentless", icon, 300, 5)The native key includes the behavior instance identity, so matching Lua keys from different behavior instances are isolated. The method expires when the callback returns. This documents the scripting contract only; it does not claim rendered HUD acceptance for a particular sprite.
fighter:clear_status_icon
Section titled “fighter:clear_status_icon”Clear a status icon addressed by this behavior instance.
Signature: fighter:clear_status_icon(key)
Requires: combat.effects.
When: A supported behavior callback with a live fighter capability.
Returns: nil; invalid keys, unavailable native support or expired references
raise a Lua error.
fighter:clear_status_icon("relentless")The key uses the same validation and behavior-instance isolation as
show_status_icon; it does not clear arbitrary status entries owned elsewhere.
fighter:set_model_color
Section titled “fighter:set_model_color”Tint this fighter’s whole model, including weapons, for a number of frames.
An alpha below FF makes the fighter see-through.
Signature: fighter:set_model_color(color, frames)
Requires: combat.effects.
When: A supported behavior callback with a live fighter capability, during an active round.
Returns: nil; an invalid color or frame count, or an expired fighter method,
raises a Lua error.
| Argument | Type and bounds | Meaning |
|---|---|---|
color |
"#RRGGBB" or "#RRGGBBAA" |
Multiplies the model’s colors. #FFFFFF leaves the colors unchanged; the alpha defaults to FF (opaque). |
frames |
Integer, 1–216000 | How long the tint lasts, in simulation frames (60 per second). |
-- Half-transparent for 650 frames, as DE's Smoke Bomb does.fighter:set_model_color("#FFFFFF80", 650)This is the game’s own <ChangeModelColor> perk effect. Each behavior instance owns
one tint: calling again replaces it and restarts its timer. When the frames run
out, when you call clear_model_color, or when the round
resets, the fighter returns to its normal colors. A fighter shows one tint at a time:
the most recent tint from any perk or mod is shown, and removing any tint returns
the fighter to its normal colors. This changes only how the fighter looks; it does
not hide the fighter from the opponent or its AI.
fighter:clear_model_color
Section titled “fighter:clear_model_color”Remove the tint this behavior instance set with
set_model_color before its frames run out.
Signature: fighter:clear_model_color()
Requires: combat.effects.
When: A supported behavior callback with a live fighter capability, during an active round.
Returns: nil. Clearing when this behavior has no tint does nothing.
fighter:clear_model_color()fighter:set_cloak
Section titled “fighter:set_cloak”Turn this fighter into refracting glass for a number of frames: the stage behind shows through the body, blurred and bent toward the edges, with a slow shimmer and a bright rim. Use it for invisibility and stealth abilities.
Signature: fighter:set_cloak(frames)
Requires: combat.effects.
When: A supported behavior callback with a live fighter capability, during an active round.
Returns: nil; an invalid frame count or an expired fighter method raises a
Lua error.
| Argument | Type and bounds | Meaning |
|---|---|---|
frames |
Integer, 1–216000 | How long the cloak lasts, in simulation frames (60 per second). |
-- Glass for 650 frames, as DE128's Smoke Bomb does.fighter:set_cloak(650)The cloak flashes as it appears, flashes briefly whenever this fighter lands a hit,
and ends with a short white flash before the fighter is drawn normally again. The
whole body, including weapons, is drawn as one shape, so overlapping parts do not
show through each other the way a transparent set_model_color
does.
Each behavior instance owns one cloak: calling again restarts its timer. It ends
when the frames run out, when you call clear_cloak, or when
the round resets. Cloaks from several behaviors add up; the fighter stays cloaked
while any is active.
This changes only how the fighter looks. Hit detection, the opponent’s AI and every
combat rule see the fighter exactly as before; pair it with
set_hit_blocked or other effects for gameplay. If the
device cannot run the cloak shaders, the fighter is drawn normally.
fighter:clear_cloak
Section titled “fighter:clear_cloak”End the cloak this behavior instance started with set_cloak
before its frames run out.
Signature: fighter:clear_cloak()
Requires: combat.effects.
When: A supported behavior callback with a live fighter capability, during an active round.
Returns: nil. Clearing when this behavior has no cloak does nothing.
fighter:clear_cloak()fighter:move_by
Section titled “fighter:move_by”Queue an additive displacement of the current main fighter.
Signature: fighter:move_by(x, y, z?)
Returns: true, nil for an accepted request; false, reason when the native
host cannot accept it. Invalid arguments, missing capabilities, expired methods
or forbidden callback timing raise a Lua error. Acceptance is not a completion
receipt and does not change a snapshot taken in the same callback.
When: An active simulation callback with a living main fighter in an offline
fight. Unavailable in on_fight_begin, on_round_begin, on_round_end and
on_fight_end, while paused, after round ending, in training/title sparring,
local versus, legacy PvP and online raids. Offline mod raid encounters are eligible.
Requires: combat.motion.
-- Inside on_tick, when your ability becomes ready:local accepted, reason = fighter:move_by(80, 0)if not accepted then sf2.log.warn(reason) endx and y are required finite numbers; z is optional and defaults to 0
(including an explicit nil). Each axis must be within -1000..1000 native rig
units. Positive X is independent of facing, positive Y is downward (negative Y moves upward); these are
relative offsets, not pixels, destinations or velocities. Only two or three
arguments are accepted. Both colon and dot-call syntax are supported.
Accepted requests add together for each body and apply once after model, collision and animation processing, before round arbitration and interpolation capture. The running sum must stay within the same per-axis bound; at most 32 nonzero requests can be pending per body per step across all behaviors/mods. A rejection preserves earlier accepted requests. Zero offsets consume no slot.
The rig’s current/previous points, derived geometry, keyframes and animation buffers translate together. The native constraint/friction solver can correct rig positions and velocity, including configured wall/floor constraints. This does not create a hit or sweep collisions along the path. The API does not validate a safe destination; native collision processing continues on subsequent steps. A stale round, body, script session or dead fighter discards pending motion; pause retains it until simulation resumes. There is no completion callback. See Create a movement ability for the complete example and composition rules.
fighter.opponent:move_by
Section titled “fighter.opponent:move_by”Queue an additive displacement of the opposing main fighter.
Signature: fighter.opponent:move_by(x, y, z?)
Returns: true, nil for an accepted request; false, reason for native
rejection. Invalid arguments, missing capabilities, expired methods and forbidden
callback timing raise a Lua error. Acceptance does not guarantee application.
When: The same active simulation callbacks and offline fight eligibility as
fighter:move_by, with a living opposing main fighter. The opponent table may be
absent; check it before calling. Begin/end callbacks cannot move either fighter.
Requires: combat.motion and combat.target.
-- Inside an active combat callback:if fighter.opponent then local accepted, reason = fighter.opponent:move_by(-60, 0, 0) if not accepted then sf2.log.warn(reason) endendThe self method’s coordinate, argument, displacement, request-count, deferred application and lifetime limits also apply. The opponent shares its pending queue with requests it receives through other behaviors. Moving both participants requires two independent calls; they are not a transaction. A call cannot target arbitrary child actors or retained fighters from earlier callbacks.
fighter:play_move
Section titled “fighter:play_move”Request explicit playback of an authored move on this main fighter.
Signature: fighter:play_move(move)
Returns: A PlayMoveRequest table with status ("queued", "applied" or
"failed") and optional error. Native rejection immediately returns failed;
accepted work starts queued and the host updates the receipt at its simulation
boundary. applied means playback started, not that the move completed or hit.
Invalid arguments, missing capabilities, expired references and forbidden
callback timing raise Lua errors.
When: Active simulation callbacks with a living main fighter in an offline
round. Unavailable in on_fight_begin, on_round_begin, on_round_end,
on_fight_end, while paused, in training/title sparring, local versus, legacy PvP
or online raids. Offline mod raids are eligible. Requests during the native
playback boundary reject to prevent recursive start/end chains.
Requires: combat.animation. move must be a handle returned by this mod’s
sf2.moves.register or sf2.moves.replace; raw names, forged handles and another
script context’s handles are rejected.
-- strike is a move handle registered by this script during loading.-- Inside an active callback, request once when the ability activates:local request = fighter:play_move(strike)-- You may retain this receipt and poll it in later ticks, unlike fighter handles.if request.status == "failed" then sf2.log.warn(request.error) endThe move must exist in the current fighter’s native animation cache, which is built for its rig/equipment/locks. Availability is checked at queuing and again at application. This API deliberately uses explicit named playback, rather than input/AI selection: it does not evaluate move selection events/conditions, key bindings, priorities, cancel windows or player control restrictions. It can interrupt a running animation. Native model readiness/physics checks still apply. Define ability costs, readiness, cancellation and cooldown policy in Lua; add attack windows, damage, effects and transitions to the declarative move. Test the binary’s rig compatibility. A cached move alone is not proof that it is appropriate to play during every state.
Only one request per body can be pending in a simulation step, shared across mods. The first accepted request wins; another returns a failed receipt without overwriting it, even for the same move. Each participant has a separate slot. An accepted request remains queued if its handler later raises an error; combat operations are not a transaction. The host applies motion first, then playback, after normal model/collision/ animation processing and before round arbitration/interpolation capture. Snapshots inside the requesting callback still show the old animation.
Pause retains already queued work until simulation resumes. A dead/replaced/
detached body, unavailable move, stale round/session or round/fight teardown
fails its receipt and never replays the request. Native start failure also sets
failed, without suppressing the other participant’s request. Receipt updates
do not invoke a Lua completion callback. The receipt is transient; do not persist
it in saved state. Editing its fields cannot control the host.
See Active Strike for a complete HUD ability using a registered punch and an applied receipt before starting cooldown.
fighter.opponent:play_move
Section titled “fighter.opponent:play_move”Request explicit playback of a registered move on the opposing main fighter.
Signature: fighter.opponent:play_move(move)
Returns: A PlayMoveRequest with the same queued/applied/failed/error contract
as the self method. applied confirms startup only, not completion or damage.
When: The same active simulation callbacks and offline-round eligibility as the self method, with a living opponent. Check that the opponent table exists; begin/end callbacks cannot start a move on either participant.
Requires: combat.animation and combat.target; a registered move handle
owned by this script context.
-- Inside an active callback; reaction was registered during loading.if fighter.opponent then local request = fighter.opponent:play_move(reaction) if request.status == "failed" then sf2.log.warn(request.error) endendAll self-method availability, argument, explicit-selection, queue, receipt and lifetime rules apply. The opponent’s slot is shared with requests it receives through its own behaviors. Two calls are independent, not an atomic pair; this operation cannot address arbitrary child actors.