Skip to content

Cookbook UI And Infrastructure

bryanthaboi edited this page Jul 27, 2026 · 3 revisions

Cookbook — UI and infrastructure

The mod object surface: Reference: The Mod Object; worked path: Tutorial 11.

R31 — Add a Start-menu entry

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

Checkpoint: the row appears with the mod on, is gone with it off. Anchor on stable labels; a missing anchor appends. The same pattern serves ui.party.submenu, ui.options.rows, and ui.pc.items.

R32 — Register a custom screen

mod.content.screens:register("QuestLog", {
  new = function(game)
    local self = { isOpaque = true }
    function self:update(dt)
      if game.input:wasPressed("b") then game.stack:pop() end
    end
    function self:draw()
      mod.ui.Font.drawBox(0, 0, 20, 18)
      mod.ui.Font.draw("QUEST LOG", 16, 16)
    end
    return self
  end,
})

Checkpoint: mod.ui.push(game, "QuestLog") opens it, and the push_screen script command reaches it from any script row. A factory that throws is logged against the mod, never a dead end.

R33 — Persist mod state

mod.save:set("visits", (mod.save:get("visits", 0)) + 1)

Checkpoint: the value survives save/load under save.modData[<id>] — your namespace alone, quarantine-proof and removed cleanly when the mod is gone (Save Model).

R34 — Define a mod option

mod.options:define({
  { key = "hard_mode", type = "toggle", label = "HARD MODE", default = false },
})
if mod.options:get("hard_mode") then
  mod.content.moves:patch("TACKLE", { power = 50 })
end

Checkpoint: the toggle renders in the manager under your mod; changes persist in options.lua and fire mod.options_changed. Row types: toggle, choice, number, text.

R35 — Export an inter-mod API

-- in colorlib/main.lua
mod.exports.tint = function(hex) ... end

-- in a dependent mod (manifest: "dependencies": ["colorlib@^1.0"])
local color = mod.find("colorlib")
if color then color.exports.tint("#ff0044") end

Checkpoint: the dependent loads after colorlib and calls its export; with colorlib absent, find returns nil and the branch skips. Range-check color.version with require("src.mods.Semver") (a supported require) before relying on a shape.

R37 — Reshape Oak's intro speech

mod.hooks:wrap("intro.oak_speech.build", function(next, steps, speech)
  steps = next(steps, speech)
  mod.ui.insertStepAfter(steps, "demo_mon", {
    id = "show_mew",
    kind = "say",
    pic = { type = "pokemon", id = "MEW" },
    reveal = "wipe",
    cry = "MEW",
    text = "This is MEW.\nKeep it quiet.",
  })
  mod.ui.insertStepBefore(steps, "ask_rival_name", {
    id = "snack_pick",
    kind = "choice",
    pic = "oak",
    saveKey = "snack",
    text = "Pick a snack.",
    choices = { "BERRIES", "LEFTOVERS", "OLD ROD" },
  })
  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)

Checkpoint: NEW GAME shows the MEW beat and the snack menu; the pick survives under save.modData[<id>].snack. Anchor on vanilla step ids (oak_welcome, demo_mon, name_player, ... — see Reference: Hooks). Pics take "oak" / "rival" / "player", a species id, or { type = "image", path = mod.path .. "/art.png" }. Worked example: mods/examples/example_silly_oak.

Clone this wiki locally