mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-09 10:01:50 -04:00
417 lines
13 KiB
Lua
417 lines
13 KiB
Lua
-- Yes/No + multichoice (pret yesnobox / multichoice / multichoicegrid). Writes VAR_RESULT via callback.
|
|
|
|
local Window = require("src.ui.game3.window")
|
|
local Display = require("src.core.game3.display")
|
|
local RomText = require("src.core.game3.rom_text")
|
|
local SE = require("src.core.game3.se_ids")
|
|
|
|
local Choice = {}
|
|
|
|
Choice.active = false
|
|
Choice.kind = nil -- "yesno" | "multi"
|
|
Choice.options = nil
|
|
Choice.cursor = 1
|
|
Choice.done = nil
|
|
Choice.left = nil
|
|
Choice.top = nil
|
|
Choice.cols = 1
|
|
Choice.ignoreBPress = false
|
|
|
|
function Choice.isOpen()
|
|
return Choice.active and true or false
|
|
end
|
|
|
|
-- pokefirered/src/main.c:480
|
|
function Choice.reset()
|
|
Choice.active = false
|
|
Choice.kind = nil
|
|
Choice.options = nil
|
|
Choice.cursor = 1
|
|
Choice.done = nil
|
|
Choice.left = nil
|
|
Choice.top = nil
|
|
Choice.cols = 1
|
|
Choice.ignoreBPress = false
|
|
Choice.style = nil
|
|
return true
|
|
end
|
|
|
|
local function uiProfile()
|
|
local okR, Runtime = pcall(require, "src.core.game3.runtime")
|
|
local session = okR and type(Runtime) == "table" and Runtime.getSession and Runtime.getSession() or nil
|
|
local okP, Profile = pcall(require, "src.core.game3.profile")
|
|
local okF, row = pcall(function() return okP and Profile.forSession(session) end)
|
|
return okF and type(row) == "table" and type(row.ui) == "table" and row.ui or nil
|
|
end
|
|
|
|
function Choice.fieldLayout()
|
|
local ui = uiProfile()
|
|
return ui and ui.saveMenu == "rs" and "rs" or "frlg"
|
|
end
|
|
|
|
-- pokeemerald/src/menu.c:98, pokefirered/src/new_menu_helpers.c:48
|
|
function Choice.yesNoWidth()
|
|
local ui = uiProfile()
|
|
return ui and ui.saveMenu == "rse" and 5 or 6
|
|
end
|
|
|
|
function Choice.yesNo(cb, layout)
|
|
Choice.active = true
|
|
Choice.kind = "yesno"
|
|
layout = layout or {}
|
|
Choice.style = layout.style
|
|
if layout.style == "battle" then
|
|
-- pokefirered/src/battle_message.c:1288
|
|
local yes, no = RomText.plain("gText_BattleYesNoChoice"):match("^(.-)\n(.*)$")
|
|
Choice.options = { yes, no }
|
|
else
|
|
-- pokefirered/src/strings.c:414
|
|
Choice.options = { RomText.plain("gText_Yes"), RomText.plain("gText_No") }
|
|
end
|
|
Choice.cursor = 1
|
|
Choice.done = cb
|
|
if layout.style == "battle" then
|
|
Choice.left = tonumber(layout.left) or 24
|
|
Choice.top = tonumber(layout.top) or 9
|
|
elseif Choice.fieldLayout() == "rs" then
|
|
-- pokeruby/src/script_menu.c:765, pokeruby/src/start_menu.c:698
|
|
Choice.left = tonumber(layout.left) or 20
|
|
Choice.top = tonumber(layout.top) or 8
|
|
else
|
|
-- pokeemerald/src/script_menu.c:198, pokefirered/src/script_menu.c:864
|
|
Choice.left = 21
|
|
Choice.top = 9
|
|
end
|
|
Choice.maxRight = nil
|
|
Choice.cols = 1
|
|
Choice.ignoreBPress = layout.ignoreBPress or false
|
|
end
|
|
|
|
function Choice.multi(options, defaultIdx, cb, layout)
|
|
Choice.active = true
|
|
Choice.kind = "multi"
|
|
Choice.style = nil
|
|
Choice.options = options or {}
|
|
Choice.cursor = (tonumber(defaultIdx) or 0) + 1
|
|
if Choice.cursor < 1 then Choice.cursor = 1 end
|
|
if Choice.cursor > #Choice.options then Choice.cursor = 1 end
|
|
Choice.done = cb
|
|
layout = layout or {}
|
|
Choice.left = tonumber(layout.left) or (Display.COLS - 10)
|
|
Choice.top = tonumber(layout.top) or 5
|
|
Choice.maxRight = tonumber(layout.maxRight)
|
|
Choice.cols = tonumber(layout.cols) or 1
|
|
Choice.ignoreBPress = layout.ignoreBPress or false
|
|
end
|
|
|
|
function Choice.move(dy, dx)
|
|
if not Choice.active or not Choice.options then return end
|
|
local n = #Choice.options
|
|
if n < 1 then return end
|
|
local cols = Choice.cols or 1
|
|
if cols <= 1 then
|
|
local delta = dy or 0
|
|
if delta == 0 and dx then delta = dx end
|
|
if delta ~= 0 then
|
|
Choice.cursor = ((Choice.cursor - 1 + delta) % n) + 1
|
|
pcall(function() require("src.core.game3.audio").playSe(SE.SE_SELECT) end)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- 2D Grid navigation
|
|
local rows = math.ceil(n / cols)
|
|
local cur = Choice.cursor - 1
|
|
local curCol = cur % cols
|
|
local curRow = math.floor(cur / cols)
|
|
|
|
if dy and dy ~= 0 then
|
|
curRow = (curRow + dy) % rows
|
|
end
|
|
if dx and dx ~= 0 then
|
|
curCol = (curCol + dx) % cols
|
|
end
|
|
|
|
local target = curRow * cols + curCol
|
|
if target >= n then
|
|
target = n - 1
|
|
end
|
|
if target + 1 ~= Choice.cursor then
|
|
Choice.cursor = target + 1
|
|
pcall(function() require("src.core.game3.audio").playSe(SE.SE_SELECT) end)
|
|
end
|
|
end
|
|
|
|
function Choice.confirm()
|
|
if not Choice.active then return end
|
|
pcall(function() require("src.core.game3.audio").playSe(SE.SE_SELECT) end)
|
|
local cb = Choice.done
|
|
local kind = Choice.kind
|
|
local cursor = Choice.cursor
|
|
Choice.active = false
|
|
Choice.kind = nil
|
|
Choice.options = nil
|
|
Choice.cols = 1
|
|
Choice.ignoreBPress = false
|
|
Choice.done = nil
|
|
if not cb then return end
|
|
if kind == "yesno" then
|
|
cb(cursor == 1)
|
|
else
|
|
cb(cursor - 1)
|
|
end
|
|
end
|
|
|
|
function Choice.cancel()
|
|
if not Choice.active then return end
|
|
if Choice.ignoreBPress then
|
|
return
|
|
end
|
|
pcall(function() require("src.core.game3.audio").playSe(SE.SE_SELECT) end) -- pokefirered/src/menu_helpers.c:57
|
|
local cb = Choice.done
|
|
local kind = Choice.kind
|
|
Choice.active = false
|
|
Choice.kind = nil
|
|
Choice.options = nil
|
|
Choice.cols = 1
|
|
Choice.ignoreBPress = false
|
|
Choice.done = nil
|
|
if not cb then return end
|
|
if kind == "yesno" then
|
|
cb(false)
|
|
else
|
|
cb(127) -- FRLG B-cancel often 0x7F
|
|
end
|
|
end
|
|
|
|
function Choice.autoPick(indexOrYes)
|
|
if not Choice.active then return end
|
|
if Choice.kind == "yesno" then
|
|
Choice.cursor = indexOrYes and 1 or 2
|
|
else
|
|
Choice.cursor = (tonumber(indexOrYes) or 0) + 1
|
|
end
|
|
Choice.confirm()
|
|
end
|
|
|
|
-- pokefirered/src/menu.c:531
|
|
function Choice.drawYesNo(L, Tp, cursor, labels)
|
|
if Choice.fieldLayout() == "rs" then
|
|
-- pokeruby/src/menu.c:608
|
|
Window.stdFrame(Window.template(L + 1, Tp + 1, 5, 4))
|
|
for i, lab in ipairs(labels) do
|
|
-- pokeruby/src/menu.c:602
|
|
Window.printPx(lab, (L + 1) * 8, (Tp + 1 + 2 * (i - 1)) * 8)
|
|
end
|
|
-- pokeruby/src/menu.c:721, :750
|
|
require("src.ui.game3.rs.menu_cursor").draw((L + 1) * 8, (Tp + 1) * 8 + (cursor - 1) * 16, 40)
|
|
return
|
|
end
|
|
Window.stdFrame(Window.template(L, Tp, Choice.yesNoWidth(), 4))
|
|
for i, lab in ipairs(labels) do
|
|
local rowPx = Tp * 8 + 2 + (i - 1) * 14
|
|
if i == cursor then Window.cursorPx(L * 8, rowPx) end
|
|
Window.printPx(lab, L * 8 + 8, rowPx)
|
|
end
|
|
end
|
|
|
|
function Choice.battleYesNoGeometry(layout, L, Tp)
|
|
L, Tp = L or 24, Tp or 9
|
|
if layout == "rs" then
|
|
-- pokeruby/src/battle_script_commands.c:5273
|
|
return {
|
|
frame = { left = L + 1, top = Tp, width = 4, height = 4 },
|
|
cursor = "rs", cursorX = (L + 1) * 8, cursorWidth = 32,
|
|
-- pokeruby/src/battle_script_commands.c:9617
|
|
rowY = function(i) return Tp * 8 + (i - 1) * 16 end,
|
|
-- pokeruby/src/battle_script_commands.c:5274
|
|
textX = (L + 1) * 8, textDy = 0,
|
|
}
|
|
elseif layout == "emerald" then
|
|
-- pokeemerald/include/battle_script_commands.h:11
|
|
return {
|
|
frame = { left = L + 1, top = Tp, width = 4, height = 4 },
|
|
-- pokeemerald/src/battle_script_commands.c:10206
|
|
cursor = "arrow", cursorX = (L + 1) * 8,
|
|
rowY = function(i) return (Tp + (i - 1) * 2) * 8 end,
|
|
-- pokeemerald/src/battle_message.c:1600
|
|
textX = (L + 2) * 8, textDy = 1,
|
|
}
|
|
end
|
|
-- pokefirered/src/battle_script_commands.c:5149
|
|
return {
|
|
frame = { left = L, top = Tp, width = 5, height = 4 },
|
|
-- pokefirered/src/battle_script_commands.c:9775
|
|
cursor = "arrow", cursorX = L * 8,
|
|
rowY = function(i) return (Tp + (i - 1) * 2) * 8 end,
|
|
-- pokefirered/src/battle_message.c:2570
|
|
textX = (L + 1) * 8, textDy = 2,
|
|
}
|
|
end
|
|
|
|
-- pokeruby/src/script_menu.c:626
|
|
local function rsWidthTiles(labels)
|
|
local FrlgFont = require("src.ui.game3.frlg_font")
|
|
local w = 0
|
|
for _, lab in ipairs(labels) do
|
|
local px = FrlgFont.measure(tostring(lab or ""))
|
|
local tiles = math.floor((px + 7) / 8)
|
|
if tiles > w then w = tiles end
|
|
end
|
|
return w
|
|
end
|
|
|
|
-- pokeruby/src/menu.c:458
|
|
local function rsGridRows(n, cols)
|
|
if cols == 1 or cols == n or not (math.floor(n / 2) < cols or n % 2 ~= 0) then
|
|
return math.floor(n / cols)
|
|
end
|
|
return math.floor(n / cols) + 1
|
|
end
|
|
|
|
function Choice.rsMultiGeometry(labels, left, top, cols)
|
|
local n = #labels
|
|
local w = rsWidthTiles(labels)
|
|
local tx, ty = left, top
|
|
if cols <= 1 then
|
|
-- pokeruby/src/script_menu.c:647
|
|
if tx + w > 29 then tx = 29 - w end
|
|
local cells = {}
|
|
for i = 1, n do
|
|
cells[i] = { x = tx * 8, y = (ty + 2 * (i - 1)) * 8 }
|
|
end
|
|
return { frame = { tx, ty, w, 2 * n }, cells = cells, barWidth = w * 8 }
|
|
end
|
|
-- pokeruby/src/menu.c:454
|
|
local rows = rsGridRows(n, cols)
|
|
local total = cols * (w + 1) - 1
|
|
local cells = {}
|
|
for i = 1, n do
|
|
local c = (i - 1) % cols
|
|
local r = math.floor((i - 1) / cols)
|
|
cells[i] = { x = (tx + c * (w + 1)) * 8, y = (ty + 2 * r) * 8 }
|
|
end
|
|
return { frame = { tx, ty, total, 2 * rows }, cells = cells, barWidth = w * 8 }
|
|
end
|
|
|
|
-- pokeruby/src/script_menu.c:655, pokeruby/src/menu.c:524
|
|
function Choice.drawRsMulti()
|
|
local g = Choice.rsMultiGeometry(Choice.options, Choice.left or 20, Choice.top or 5, Choice.cols or 1)
|
|
Window.stdFrame(Window.template(g.frame[1], g.frame[2], g.frame[3], g.frame[4]))
|
|
for i, lab in ipairs(Choice.options) do
|
|
Window.printPx(lab, g.cells[i].x, g.cells[i].y)
|
|
end
|
|
local cur = g.cells[Choice.cursor] or g.cells[1]
|
|
if cur then
|
|
-- pokeruby/src/menu.c:748
|
|
require("src.ui.game3.rs.menu_cursor").draw(cur.x, cur.y, g.barWidth)
|
|
end
|
|
end
|
|
|
|
function Choice.draw()
|
|
if not Choice.active or not Choice.options then return end
|
|
if Choice.style == "battle" and Choice.kind == "yesno" then
|
|
local okB, BattleChrome = pcall(require, "src.ui.game3.battle_chrome")
|
|
local layout = okB and type(BattleChrome) == "table" and BattleChrome.layout and BattleChrome.layout() or "frlg"
|
|
local g = Choice.battleYesNoGeometry(layout, Choice.left, Choice.top)
|
|
local okR, Runtime = pcall(require, "src.core.game3.runtime")
|
|
local session = okR and type(Runtime) == "table" and Runtime.getSession and Runtime.getSession()
|
|
local opts = type(session) == "table" and session.options or nil
|
|
local frameType = tonumber(type(opts) == "table" and opts.frameType or nil) or 0
|
|
-- pokefirered/src/battle_bg.c:694
|
|
Window.userFrame(Window.template(g.frame.left, g.frame.top, g.frame.width, g.frame.height), frameType)
|
|
for i, lab in ipairs(Choice.options) do
|
|
local rowPx = g.rowY(i)
|
|
if i == Choice.cursor and g.cursor == "arrow" then Window.cursorPx(g.cursorX, rowPx) end
|
|
Window.printPx(lab, g.textX, rowPx + g.textDy)
|
|
end
|
|
if g.cursor == "rs" then
|
|
require("src.ui.game3.rs.menu_cursor").draw(g.cursorX, g.rowY(Choice.cursor), g.cursorWidth)
|
|
end
|
|
return
|
|
end
|
|
|
|
if Choice.kind == "yesno" then
|
|
Choice.drawYesNo(Choice.left, Choice.top, Choice.cursor, Choice.options)
|
|
return
|
|
end
|
|
|
|
if Choice.fieldLayout() == "rs" then
|
|
Choice.drawRsMulti()
|
|
return
|
|
end
|
|
|
|
local n = #Choice.options
|
|
local cols = Choice.cols or 1
|
|
if cols <= 1 then
|
|
local tw = 8
|
|
for _, lab in ipairs(Choice.options) do
|
|
local need = math.min(18, math.max(6, math.floor(#tostring(lab) * 0.7) + 2))
|
|
if need > tw then tw = need end
|
|
end
|
|
local th = math.max(2, math.ceil((n * Window.OPTION_HEIGHT) / 8))
|
|
local tx = Choice.left or (Display.COLS - tw - 2)
|
|
if Choice.kind == "multi" and Choice.maxRight and tx + tw > Choice.maxRight then
|
|
tx = Choice.maxRight - tw
|
|
end
|
|
local ty = Choice.top or 5
|
|
Window.stdFrame(Window.template(tx, ty, tw, th))
|
|
local leftPx = tx * 8
|
|
local topPx = ty * 8
|
|
for i, lab in ipairs(Choice.options) do
|
|
local yPx = Window.menuRowPx(topPx, i)
|
|
if i == Choice.cursor then Window.cursorPx(leftPx, yPx) end
|
|
Window.printPx(lab, leftPx + Window.CURSOR_WIDTH, yPx)
|
|
end
|
|
else
|
|
-- Multi-column grid
|
|
local rows = math.ceil(n / cols)
|
|
local colTileWidths = {}
|
|
for c = 1, cols do
|
|
local maxW = 4
|
|
for r = 1, rows do
|
|
local idx = (r - 1) * cols + c
|
|
if idx <= n then
|
|
local lab = tostring(Choice.options[idx] or "")
|
|
local need = math.floor(#lab * 0.7) + 2
|
|
if need > maxW then maxW = need end
|
|
end
|
|
end
|
|
colTileWidths[c] = maxW
|
|
end
|
|
local totalTileW = 0
|
|
for c = 1, cols do
|
|
totalTileW = totalTileW + colTileWidths[c]
|
|
end
|
|
local th = math.max(2, math.ceil((rows * Window.OPTION_HEIGHT) / 8))
|
|
local tx = Choice.left or 2
|
|
if Choice.maxRight and tx + totalTileW > Choice.maxRight then
|
|
tx = Choice.maxRight - totalTileW
|
|
end
|
|
if tx < 0 then tx = 0 end
|
|
local ty = Choice.top or 5
|
|
Window.stdFrame(Window.template(tx, ty, totalTileW, th))
|
|
|
|
local topPx = ty * 8
|
|
for i, lab in ipairs(Choice.options) do
|
|
local idx0 = i - 1
|
|
local c = (idx0 % cols) + 1
|
|
local r = math.floor(idx0 / cols) + 1
|
|
|
|
local colOffsetTiles = 0
|
|
for prevC = 1, c - 1 do
|
|
colOffsetTiles = colOffsetTiles + colTileWidths[prevC]
|
|
end
|
|
local colLeftPx = (tx + colOffsetTiles) * 8
|
|
local yPx = Window.menuRowPx(topPx, r)
|
|
|
|
if i == Choice.cursor then
|
|
Window.cursorPx(colLeftPx, yPx)
|
|
end
|
|
Window.printPx(lab, colLeftPx + Window.CURSOR_WIDTH, yPx)
|
|
end
|
|
end
|
|
end
|
|
|
|
return Choice
|