The pack
turnBattle
function turnBattle(): Pack<{ turnBattle: typeof api }>
Install the turn-battle API (game.turnBattle): the battle rules and the Battle state machine.
Helpers
BATTLE_RULES
const BATTLE_RULES: Readonly<BattleRules>
The default rules.
battleRules
const battleRules = (o: Partial<BattleRules> = {}): BattleRules=> …
The default rules with some replaced.
breakTurn
function breakTurn(b: BreakState): boolean
A broken fighter's turn comes round: it skips the turn and recovers a full shield. Returns true when a turn was skipped.
damage
const damage = (atk: number, def: number, factor: number, roll: number, rules: BattleRules = BATTLE_RULES): number=> …
Damage of one hit: max(minDamage, round(base(atk, def) × factor × spread)); roll in [0, 1) picks the spread.
dropFalls
const dropFalls = (m: { boss: boolean; dropItemId: string | null }, roll: number, rules: BattleRules = BATTLE_RULES): string | null=> …
A foe leaves its drop: always for a boss, else on a roll under dropChance.
ELEMENT_WHEEL
const ELEMENT_WHEEL: Readonly<Record<string, string | null>>
The built-in wheel: what each element beats. none is physical and beats nothing.
elementFactor
const elementFactor = (attack: string, target: string, rules: BattleRules = BATTLE_RULES): number=> …
The damage multiplier of an attack element against a target element: weak, resisted (the target beats it) or plain.
encounterFires
const encounterFires = (rate: number, roll: number): boolean=> …
A random encounter fires on a roll under the rate.
fleeSucceeds
const fleeSucceeds = (roll: number, rules: BattleRules = BATTLE_RULES): boolean=> …
A flee succeeds on a roll under fleeChance.
hitBreak
function hitBreak(b: BreakState, weak: boolean): boolean
A hit that exploits a weakness removes one shield point; at 0 the fighter is BROKEN. Returns whether this hit broke it.
isCrit
const isCrit = (lck: number, roll: number, rules: BattleRules = BATTLE_RULES): boolean=> …
A critical hit lands when the roll is under the rules' critChance(lck).
isWeak
const isWeak = (attack: string, target: string, rules: BattleRules = BATTLE_RULES): boolean=> …
True when attack beats target on the wheel.
newBreak
const newBreak = (max: number): BreakState=> …
A fresh gauge. A gauge of 0 can never be broken.
resolveHit
function resolveHit(s: HitSpec, rules: BattleRules = BATTLE_RULES): HitResult
Resolve one hit from the numbers alone.
scaleStats
function scaleStats<S extends { hp: number; atk: number }>(stats: S, difficulty: string, rules: BattleRules = BATTLE_RULES): S
Stats scaled by a difficulty: hp and attack times the rules' multiplier (1 for a difficulty it does not list).
SKILL_EFFECTS
const SKILL_EFFECTS: Readonly<Record<string, SkillEffect>>
The built-in skill effects: damage opponents, heal an ally, guard or haste the user (or the party).
turnOrder
const turnOrder = <E extends TurnEntry>(entries: ReadonlyArray<E>): E[]=> …
Turn order: by speed, highest first; ties go to heroes, then to the order given. Pure and stable.
Classes
Battle
class Battle
A turn-based battle.
readonly rules: BattleRules— The rules it resolves by.fighters: Fighter[]— Every fighter, heroes and foes, in the order given.round: number— The round number, 0 before the first.order: Fighter[]— This round's order.turn: number— Index intoorderof the current actor; -1 before the first turn.result: BattleResult | null— How it ended, or null while it runs.constructor(defs: readonly FighterDef[], o: BattleOptions)get(id: string): Fighter | undefined— A fighter by id.side(side: Side): Fighter[]— The fighters of a side, standing or not.standing(side: Side): Fighter[]— The standing fighters of a side.get actor(): Fighter | null— The fighter whose turn it is, or null.get done(): boolean— True once the current actor has taken its turn:next()moves on.get recovering(): boolean— True when the current actor is broken and will spend its turn recovering.emit(e: BattleEvent): void— Queue an event (for skill effects of a game's own).drain(): BattleEvent[]— Every event since the last call.next(): Fighter | null— Move to the next fighter's turn (a new round when this one is done) and return it; null when the battle is over.targets(user: Fighter, kind: 'foe' | 'ally' | 'downed'): Fighter[]— The choices for a one-target action: standing opponents, standing allies, or downed allies (a revive).reach(user: Fighter, skill: BattleSkill, target?: string): Fighter[]— Who a skill reaches whentarget(for one-target skills) is chosen: an unknown effect reaches nobody.picks(skill: BattleSkill): 'foe' | 'ally' | null— What a one-target skill asks the player to pick: an opponent, an ally, or nothing (it reaches its targets itself).auto(): BattleAction— What the battle would choose for the current actor.act(action: BattleAction): boolean— The current actor takes its turn. A broken actor recovers instead, whatever is asked. Returns false (and keeps the turn) when the action cannot be done: not enough MP, no such target, an item that did nothing, no fleeing, or the turn already taken.hit(from: Fighter, to: Fighter, element: string, power: number): HitResult— Hittofromfromwith an element and extra power, through the rules: crits, the wheel, the gauge, defending.heal(from: Fighter, to: Fighter, amount: number): number— Healtoby up toamount; a downed fighter is not healed. Returns what was restored.snapshot(): BattleSnapshot— The whole state, as plain data.restore(s: BattleSnapshot): void— Put a snapshot back (the same battle's options are assumed).
Types
BattleAction
type BattleAction = /** A plain attack on one opponent, with the attacker's element. */ | { kind: 'attack'; target: string } /** A skill; `target` for one-target skills (default the first standing choice). */ | { kind: 'skill'; skill: BattleSkill; target?: string } /** An item: `use` changes the target (pools, flags) and says whether it did anything; nothing done keeps the turn. */ | { kind: 'item'; target: string; name?: string; use(target: Fighter): boolean } /** Defend until the next turn, getting the rules' `defendMp` back. */ | { kind: 'defend' } /** Try to run: the rules' `fleeChance`. */ | { kind: 'flee' } /** Do nothing. */ | { kind: 'wait' }
What a fighter does with its turn.
BattleEvent
type BattleEvent = /** A round began, in this order. */ | { type: 'round'; round: number; order: string[] } /** A fighter's turn began. */ | { type: 'turn'; actor: string } /** A broken fighter spent its turn recovering its shield. */ | { type: 'recover'; actor: string } /** A fighter used a skill. */ | { type: 'skill'; actor: string; skill: string } /** A hit landed. */ | ({ type: 'hit'; from: string; to: string; element: string } & HitResult) /** A hit broke its target's shield. */ | { type: 'break'; to: string } /** A fighter fell. */ | { type: 'down'; id: string; by: string } /** A heal restored health. */ | { type: 'heal'; from: string; to: string; amount: number } /** An item was used. */ | { type: 'item'; actor: string; to: string; name: string; hp: number; mp: number } /** A fighter is guarded (defending) until its next turn. */ | { type: 'guard'; to: string } /** A fighter is hasted. */ | { type: 'haste'; to: string } /** A fighter defends. */ | { type: 'defend'; actor: string; mp: number } /** A flee was tried. */ | { type: 'flee'; actor: string; ok: boolean } /** The battle ended. */ | { type: 'end'; result: BattleResult }
What happened, in order.
BattleOptions
interface BattleOptions
What a battle is made with.
rules?: Partial<BattleRules>— The rules, overBATTLE_RULES.rng: () => number— Random numbers in [0, 1): a game's seeded stream (() => game.rng.next()).effects?: Readonly<Record<string, SkillEffect>>— Skill effects, overSKILL_EFFECTS.choose?: (battle: Battle, actor: Fighter) => BattleAction— Choose an action for a fighter the player does not command (foes, or heroes in an auto battle). Default: attack a random standing opponent.canFlee?: boolean— Can the heroes run. Default true.
BattleResult
type BattleResult = 'win' | 'lose' | 'flee'
How a battle ended.
BattleRules
interface BattleRules
How a battle is resolved. Every field has a default (BATTLE_RULES).
wheel: Readonly<Record<string, string | null>>— What each element beats (or null): an attack of an element on what it beats is a weakness. DefaultELEMENT_WHEEL.weakFactor: number— Damage on a weakness. Default 1.5.resistFactor: number— Damage on a target whose element beats the attack's. Default 0.5.critFactor: number— A critical's multiplier. Default 2.brokenFactor: number— What a broken fighter takes. Default 1.5.defendFactor: number— What a defending fighter takes. Default 0.5.defendMp: number— MP a turn spent defending gives back. Default 5.hasteFactor: number— Speed multiplier while hasted. Default 1.5.hasteTurns: number— Rounds a haste lasts after the turn it is cast (counted down on the hasted fighter's own turns); Infinity is the rest of the battle. Default Infinity.spread: number— The damage spread: a hit is the formula times 1 ± spread/2. Default 0.2 (0.9 to 1.1).minDamage: number— The least a hit does. Default 1.fleeChance: number— Chance a flee succeeds. Default 0.6.dropChance: number— Chance a non-boss foe leaves its drop. Default 0.3.healAtk: number— A heal's amount: the skill's power plus this share of the healer's attack. Default 0.5.difficulty: Readonly<Record<string, number>>— Stat multipliers per difficulty: hp and attack are scaled. Default gentle 0.8, fair 1, cruel 1.25.base(atk: number, def: number): number— The damage formula before the factors: defaultatk - def / 2.critChance(lck: number): number— The chance of a critical from luck. Defaultlck / 100.
BattleSkill
interface BattleSkill
A skill as the battle sees it.
id: string— Its id, for events.name?: string— Its name, for events. Default the id.effect: string— What it does: a key of the battle's effects (built in: damage, heal, guard, haste).power: number— For damage, added to the user's attack; for a heal, the amount (plus the rules' share of the user's attack).element?: string— Its element. Defaultnone.mp: number— MP it costs.target: 'one' | 'all' | 'ally' | 'party' | 'self'— Who it aims at: one foe, all foes, one ally, the party, or the user.
BattleSnapshot
interface BattleSnapshot
A battle's whole state, for snapshot and restore.
fighters: Fighter[]— The fighters.round: number— The round number, 0 before the first.order: string[]— This round's order, by id.turn: number— Index into the order of the current actor, -1 before the first turn.result: BattleResult | null— The result, or null while it runs.acted: boolean— The current actor has taken its turn.
BattleStats
interface BattleStats
A fighter's six stats.
hp: number— Most health.mp: number— Most magic points.atk: number— Attack: added to a skill's power and set against the target's defence.def: number— Defence: cuts the damage taken.spd: number— Speed: who acts first.lck: number— Luck: the chance of a critical hit.
BreakState
interface BreakState
The Break gauge of one fighter.
shield: number— Shield points left; 0 is broken.max: number— The full gauge.broken: boolean— True while broken: skips its next turn and takesbrokenFactor.
Fighter
interface Fighter
A fighter in battle: plain data.
id: string— Unique among the fighters.name: string— Display name.side: Side— The side it fights on.stats: BattleStats— Its stats.hp: number— Health now; 0 is down.mp: number— Magic now.element: string— Its element.brk: BreakState— Its Break gauge.defending: boolean— Defending until its next turn: takesdefendFactor.haste: number— Rounds of haste left, counted down at the start of each of its own turns (Infinity for the rest of the battle); 0 is not hasted.data: unknown— The game's own data.
FighterDef
interface FighterDef
A fighter going into battle.
id: string— Unique among the fighters.name?: string— Display name. Default the id.side: Side— The side it fights on.stats: BattleStats— Its stats, gear and difficulty already applied.hp?: number— Health going in. Default full.mp?: number— Magic going in. Default full.element?: string— The element it attacks with and defends as. Defaultnone.breaks?: number— Shield points of its Break gauge. Default 0 (never broken).data?: unknown— Anything the game wants to keep with it (a monster definition, a hero id). Kept in snapshots when it is plain data.
HitResult
interface HitResult
What one attack does.
amount: number— HP removed.crit: boolean— It was a critical.factor: number— The element factor applied.weak: boolean— The element exploited a weakness.
HitSpec
interface HitSpec
What an attack takes to resolve.
atk: number— Attacker's attack stat, gear included.lck: number— Attacker's luck stat, for the crit roll.power: number— Extra power from a skill; 0 for a plain attack.element: string— The attack's element.def: number— Target's defence stat, gear included.element2: string— Target's element.broken: boolean— Target is broken.defending: boolean— Target is defending.rolls: [number, number]— Two rolls in [0, 1): the spread and the crit.
Side
type Side = 'hero' | 'foe'
Which side a fighter is on.
SkillEffect
interface SkillEffect
A skill effect: who its one and all aim at, and what it does to the targets it reaches.
aim: 'foe' | 'ally' | 'self'— What aoneskill of this effect aims at (allis that whole side;selfmakes it the user's side): opponents, allies or the user.run(battle: Battle, user: Fighter, targets: Fighter[], skill: BattleSkill): void— Act on the targets through the battle's verbs (hit,heal,emit) or on the fighters' data.
TurnEntry
interface TurnEntry
A fighter as the turn order sees it.
id: string— Any id.spd: number— Speed.hero: boolean— Heroes act first on a tie.


