-
Notifications
You must be signed in to change notification settings - Fork 16
Cookbook UI And Infrastructure
The mod object surface: Reference: The Mod Object;
worked path: Tutorial 11.
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.
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.
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).
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 })
endCheckpoint: 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.
-- 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") endCheckpoint: 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.
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.
mod.hooks:wrap("ui.list_menu", function(next, opts, ctx)
opts = next(opts, ctx) or opts
if ctx.kind == "bag" then
opts.wrap, opts.pageJump, opts.keyRepeat = true, true, true
end
return opts
end)Checkpoint: the bag wraps top↔bottom, Left/Right jump a page, and
holding Up/Down scrolls. Other lists (kind unset) stay vanilla.
mod.hooks:wrap("ui.naming.grid", function(next, grid, ctx)
grid = next(grid, ctx)
local out = {}
for i, row in ipairs(grid) do out[i] = row end
out[4] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" }
return out
end)Checkpoint: NEW NAME can pick digits. Keep an "ED" cell and a
single-cell case-switch row so confirm / case-flip still work.
mod.hooks:wrap("zoom.range", function(next, lo, hi, S)
lo, hi = next(lo, hi, S)
return lo - S, hi -- another full fit-scale of survey-out
end)Checkpoint: hotkey 4 / the ZOOM row reach sub-1 scales so more of the
region fits on screen.
local border
mod.events:on("game.ready", function()
border = love.graphics.newImage(mod.assets:path("assets/sgb_border.png"))
end)
mod.hooks:wrap("render.letterbox", function(next, ctx)
next(ctx)
if not border or ctx.worldActive then return end
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(border, 0, 0, 0,
ctx.ww / border:getWidth(), ctx.wh / border:getHeight())
end)Checkpoint: menus / battles show your border in the letterbox; survey
zoom (worldActive) skips it so the world fill is undisturbed. For
post-process approaches see render_pipelines.
Engine 1.0.0 · mod API 2 · repository · Discord
This project ships no ROM data. All game content is decoded on the player's machine from their own verified Pokemon Red ROM; mods ship recipes and original assets, never extracted content.
Start here
Concepts
Tutorials
- The ladder
- 01 Sprite and Text Tweak
- 02 Balance Patch
- 03 New Species
- 04 New Item and Ball
- 05 New Move
- 06 NPC and Dialogue
- 07 New Map
- 08 Quest
- 09 Custom Music
- 10 New Mechanic
- 11 Custom UI Screen
- 12 Mini Total Conversion
Reference
Guides
- Art Pipeline
- Audio Authoring
- Translations
- Total Conversions
- Link Compatibility
- Publishing a Mod
- Docs Style Guide
Playing