Skip to content

Reference Hooks

bryanthaboi edited this page Jul 27, 2026 · 4 revisions

Hook reference

Every hook the engine calls, with the arguments a wrapper receives after next, what it must return, and the call site. Read from the Runtime.call sites in src/. Wrap with mod.hooks:wrap(name, function(next, ...) ... end, priority); see Concepts: Events and Hooks for chain order and error isolation.

A wrapper's contract: call next(...) at most once, return the same shape the vanilla function returns. next() with no arguments forwards the current arguments unchanged.

Battle

Hook Wrapper receives (after next) Returns Call site
battle.accuracy ctx = { battle, ruleset, move, user, target, rng } true on hit src/battle/BattleState.lua (accuracyRoll)
battle.damage ctx = { battle, ruleset, user, target, move, opts, rng } damage, { crit, typeMult } src/battle/BattleState.lua (computeDamage)
battle.crit ctx = { ruleset, attacker, moveId, rng, highCrit } true on crit src/battle/Damage.lua
battle.turn_order playerBattler, playerMove, enemyBattler, enemyMove, ctx = { rng, invertTie? } true when the player moves first src/battle/BattleState.lua; also src/link/LinkBattle.lua (must stay deterministic across peers)
battle.enemy_action battle the enemy action src/battle/BattleState.lua (enemyAction — the whole AI choke point)
battle.run ctx = { battle, pSpd, eSpd, attempts, rng } true on escape src/battle/BattleState.lua (runRoll)
catch.rate ball, mon, def, opts = { rng, rateOverride, battle } caught, shakes src/battle/BattleState.lua (catchAttempt)
trainer.party trainerClass, partyIndex, party the party list ({ { species, level }, ... }) src/battle/BattleState.lua (trainer battle setup)
exp.gain ctx = { defeatedDef, level, isTrainer, participants, traded, mon } the exp number src/battle/Experience.lua

Example — a global difficulty scaler:

mod.hooks:wrap("battle.damage", function(next, ctx)
  local dmg, info = next(ctx)
  if not ctx.user.isPlayer then
    dmg = math.floor(dmg * 1.25)
  end
  return dmg, info
end)

Progression and encounters

Hook Wrapper receives Returns Call site
evolution.check game, mon, evo, trigger true when the evolution fires src/pokemon/Evolution.lua (pendingFor)
encounter.roll encDef, ctx = { mapId, terrain, rng } an encounter ({ species, level }) or nil src/world/OverworldController.lua (rollEncounter)
encounter.species enc, ctx the (possibly rewritten) encounter src/world/OverworldController.lua (after a non-nil roll, before repel filtering)
encounter.fishing rod, mapId, candidates an encounter or nil src/world/OverworldController.lua (goFishing)

World

Hook Wrapper receives Returns Call site
movement.collision allowed, ctx = { map, mover, dir, fromX, fromY, toX, toY, reason } boolean; may rewrite ctx.reason src/world/Collision.lua (canMove)
warp.destination destMap, x, y, ctx = { warp, lastMap, data } destMap, x, y src/world/Warp.lua (destination)
map.palette name, map a palette name src/world/OverworldController.lua

Presentation and audio

Hook Wrapper receives Returns Call site
music.select song, ctx = { reason, mapId, mapSong, onBike, surfing, kind, battleKind, trainerId } a song id src/core/Music.lua (selectSong — every song choice passes through)
transition.style ctx = { trainer, stronger, dungeon, game } a transitions registry id src/render/BattleTransition.lua (an unknown style falls back to vanilla)
render.zones game, zones the zones list src/core/Game.lua (palette-zone pass before the blit)

Scripts, saves, UI, link

Hook Wrapper receives Returns Call site
script.command ctx, name, args the command's jump result (nil, a label, "end") src/script/ScriptRunner.lua (every script row)
save.new_game save the save table src/core/SaveData.lua (New Game skeleton)
ui.start_menu.items game, items the items list src/ui/StartMenu.lua
ui.party.submenu game, items, mon, ctx = { battle, overworld } the items list src/ui/PartyMenu.lua
ui.options.rows game, rows the rows list src/ui/OptionsMenu.lua
ui.pc.items game, items the items list src/world/OverworldController.lua (the PC menu)
intro.oak_speech.build steps, speech the steps list src/ui/OakSpeech.lua (buildSteps)
link.fingerprint data, mods the digest string src/link/Fingerprint.lua (compute)

The four ui.* list hooks and intro.oak_speech.build type-check the result: return anything but a table and the vanilla list is kept with a logged error. Insert with the mod.ui helpers rather than raw table.insert when position matters:

mod.hooks:wrap("ui.start_menu.items", function(next, game, items)
  mod.ui.insertBefore(items, "QUIT", {
    label = "QUESTS",
    onSelect = function() mod.ui.push(game, "QuestLog") end,
  })
  return next(game, items)
end)

Entries are { label, onSelect }; anchors are stable labels ("POKéDEX", "OPTION", "QUIT", ...), and a missing anchor appends.

Oak speech steps

intro.oak_speech.build reshapes the NEW GAME naming sequence without replacing the whole screen. Call next(steps, speech) first, then insert against stable step ids with mod.ui.insertStepBefore / insertStepAfter / removeStep:

Vanilla id Kind What it does
oak_welcome say Oak fades in + welcome text
demo_mon demo flipped demo species wipe + cry + text
world_spiel say pets / fights / profession
ask_player_name say "First, what is your name?" over the player pic
name_player name naming screen → save.player.name
ask_rival_name say rival intro over the rival pic
name_rival name naming screen → save.player.rival
legend say "your very own POKéMON legend..."
shrink shrink shrink-away into the overworld sprite

Step kinds a mod may add: say, demo, name, choice, yesno, pic, shrink, fn. Pics accept "oak" / "rival" / "player", a species id, or { type = "trainer"|"pokemon"|"player"|"image"|"sprite", ... }. A saveKey on choice / yesno / name is mirrored onto speech.answers and fired through intro.oak_speech.answered.

mod.hooks:wrap("intro.oak_speech.build", function(next, steps, speech)
  steps = next(steps, speech)
  mod.ui.insertStepAfter(steps, "oak_welcome", {
    id = "my_quiz",
    kind = "choice",
    pic = "oak",
    saveKey = "starter_bias",
    text = "Pick a type.",
    choices = { "FIRE", "WATER", "GRASS" },
  })
  return steps
end)

mod.events:on("intro.oak_speech.answered", function(ev)
  if ev.saveKey then mod.save:set(ev.saveKey, ev.value) end
end)

Worked example: mods/examples/example_silly_oak. To replace the whole speech instead, point field.boot.screens.newGame at your own screen (Tutorial 12).

Clone this wiki locally