2
Cookbook World And Scripting
bryanthaboi edited this page 2026-07-27 08:15:06 -04:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Cookbook — world and scripting

Command vocabulary: Reference: Script Commands; worked paths: Tutorials 0608.

R20 — Add a script command

mod.commands:register("mod_reward", function(ctx, amount)
  ctx.save.money = math.min(999999, (ctx.save.money or 0) + (amount or 100))
end)

Checkpoint: { "mod_reward", 500 } works in any script row. The handler is fn(ctx, ...)ctx.save, ctx.overworld, ctx.game are the surface; return a label string to jump. The script.command hook sees every dispatched row if you need to intercept instead.

R21 — Add a text token

mod.content.tokens:register("CLOCK", function(game, arg)
  return tostring(math.floor((game.save.playSeconds or 0) / 3600))
end)

Checkpoint: {CLOCK} in any text renders the value. Grammar is {NAME} or {NAME:arg}, handler fn(game, arg) -> string | nil (src/script/Tokens.lua); nil drops the token, a thrown error is logged and drops it.

R22 — Add a talk script to an NPC

mod.content.map_scripts:register("PALLET_TOWN", {
  talk = {
    TEXT_PALLETTOWN_GIRL = {
      { "face_player" },
      { "show_text", "My line is\nmodded now!" },
    },
  },
})

Checkpoint: the Pallet Town girl says your line; every other TEXT constant on the map is untouched. Per TEXT constant the highest-precedence contribution wins; false suppresses lower ones.

R23 — Spawn a new NPC on a map

mod.events:on("map.entered", function(ev)
  if ev.mapId == "PALLET_TOWN" then
    mod.world:spawnNpc("PALLET_TOWN", {
      index = 95, x = 10, y = 9, sprite = "SPRITE_FISHER",
      movement = "WALK", range = "ANY_DIR", text = "TEXT_PALLETTOWN_GIRL",
    })
  end
end)

Checkpoint: the NPC appears and wanders on every visit. Runtime objects are not serialized — re-spawn on map.entered. For a permanent NPC, patch the map record's objects with __append instead (Tutorial 06).

R24 — Add an onStep trigger

mod.content.map_scripts:register("PALLET_TOWN", {
  onStep = function(game, ow, x, y)
    if x == 10 and y == 10 then
      mod.log:info("stepped on the spot")
      -- return true to consume the step
    end
  end,
})

Checkpoint: stepping on (10,10) logs; the map's own onStep still runs. Contributions chain first-truthy-return-consumes (src/script/MapScripts.lua).

R25 — Add a new map + connection

local W, H = 6, 5
local blocks = {}
for i = 1, W * H do blocks[i] = 10 end
mod.content.maps:register("MODROUTE", {
  id = "MODROUTE", label = "ModRoute", index = 1000,
  tileset = "OVERWORLD", width = W, height = H, blocks = blocks,
  borderBlock = 10,
  connections = { west = { map = "PALLET_TOWN", offset = 0 } },
})
mod.content.maps:patch("PALLET_TOWN", {
  connections = { east = { map = "MODROUTE", offset = 0 } },
})

Checkpoint: walking off Pallet Town's east edge enters MODROUTE and back. Both directions are needed; blocks must count width * height. Full treatment: Tutorial 07.

R40 — Running shoes

mod.hooks:wrap("movement.speed", function(next, frames, ctx)
  frames = next(frames, ctx)
  if not ctx.onBike and ctx.input and ctx.input:isDown("b") then
    return math.max(1, math.floor(frames / 2))
  end
  return frames
end)

Checkpoint: hold B on foot to move at bike cadence; the bicycle itself is unchanged. ctx.surfing is also available if you want surf dash.

R41 — Day / night palette

mod.hooks:wrap("world.tod", function(next, tod, ctx)
  -- 512 steps ≈ a short day cycle for demos
  local period = math.floor((ctx.steps or 0) / 512) % 2
  return period == 0 and "DAY" or "NIGHT"
end)

mod.hooks:wrap("map.palette", function(next, name, map, ctx)
  name = next(name, map, ctx)
  if ctx and ctx.tod == "NIGHT" and name == "ROUTE" then
    return "CAVE"  -- stand-in night tint
  end
  return name
end)

mod.events:on("world.tod_changed", function(ev)
  mod.log:info("time of day -> %s", ev.tod)
end)

Checkpoint: after enough steps the overworld palette flips and world.tod_changed fires. Heavier lighting / rain overlays belong on render.zones or a render_pipelines present pass (Rendering pipelines).

R42 — Slower / faster warp fades

Warp fades already live in the transitions registry (warp_fade, white_flash). Retiming them is a patch, not a new hook:

mod.content.transitions:patch("warp_fade", { frames = 30 })  -- slower

Checkpoint: door warps linger longer. Battle wipe styles use the same registry via transition.style.