mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
Selecting a start-menu row pops the start menu before the submenu is pushed (Menu's generic A handler), so B in a submenu had no parent to return to and dropped straight to the overworld. pokered redisplays the start menu instead (RedisplayStartMenu from the party/item/ trainer-card/option handlers), so give each vanilla submenu an onCancel that re-opens the start menu; the saved cursor row (wBattleAndStartSavedMenuItem) restores on re-entry. - PokedexMenu/BagMenu forward opts.onCancel into their ListMenu - TrainerCard dismisses back via onCancel on A or B (WaitForTextScrollButtonPress then RedisplayStartMenu) - OptionsMenu fires onCancel from both B/START and the CANCEL row - PlayerPC rows are keepOpen so B in WITHDRAW/DEPOSIT/TOSS returns to the PC root menu (players_pc.asm), matching the BoxMenu pattern The SAVE flow is untouched: StartMenu_SaveReset falls through to HoldTextDisplayOpen and never redisplays the menu. Battle-opened party/bag screens get no onCancel, so mid-battle cancel behavior is unchanged. Co-authored-by: johnjohto <johnjohto@users.noreply.github.com>
This commit is contained in:
@@ -372,6 +372,8 @@ function BagMenu.new(game, opts)
|
||||
list = ListMenu.new(game, "ITEMS", buildItems(game), {
|
||||
kind = "bag",
|
||||
footer = ("¥%d"):format(game.save.money),
|
||||
-- B returns to the start menu when the bag was opened from it
|
||||
onCancel = opts.onCancel,
|
||||
-- SELECT reorders items like the original bag (swap_items.asm)
|
||||
onSelectKey = function(item, l)
|
||||
if not item then return end
|
||||
|
||||
@@ -328,7 +328,8 @@ local function buildRows(game)
|
||||
return rows
|
||||
end
|
||||
|
||||
function OptionsMenu.new(game)
|
||||
function OptionsMenu.new(game, opts)
|
||||
opts = opts or {}
|
||||
local rows = buildRows(game)
|
||||
local hooked = Runtime.call("ui.options.rows", sameRows, game, rows)
|
||||
if type(hooked) == "table" then
|
||||
@@ -337,8 +338,8 @@ function OptionsMenu.new(game)
|
||||
Logger.error("ui.options.rows returned %s; keeping the vanilla rows",
|
||||
type(hooked))
|
||||
end
|
||||
return setmetatable({ game = game, rows = rows, index = 1, scroll = 0 },
|
||||
OptionsMenu)
|
||||
return setmetatable({ game = game, rows = rows, index = 1, scroll = 0,
|
||||
onCancel = opts.onCancel }, OptionsMenu)
|
||||
end
|
||||
|
||||
function OptionsMenu:update(dt)
|
||||
@@ -361,9 +362,11 @@ function OptionsMenu:update(dt)
|
||||
changed = row.step(self.game, dir) and true or false
|
||||
elseif input:wasPressed("a") then -- CANCEL
|
||||
self.game.stack:pop()
|
||||
if self.onCancel then self.onCancel() end
|
||||
end
|
||||
elseif input:wasPressed("b") or input:wasPressed("start") then
|
||||
self.game.stack:pop()
|
||||
if self.onCancel then self.onCancel() end
|
||||
end
|
||||
if changed and self.game.writeOptions then
|
||||
self.game:writeOptions()
|
||||
|
||||
+6
-3
@@ -160,9 +160,12 @@ end
|
||||
function PlayerPC.new(game)
|
||||
game.save.pcItems = game.save.pcItems or {}
|
||||
return Menu.new(game, {
|
||||
{ label = "WITHDRAW ITEM", onSelect = function() withdraw(game) end },
|
||||
{ label = "DEPOSIT ITEM", onSelect = function() deposit(game) end },
|
||||
{ label = "TOSS ITEM", onSelect = function() toss(game) end },
|
||||
-- keepOpen so B in the item lists returns here instead of dropping the
|
||||
-- whole PC session (players_pc.asm re-shows the PC menu); same pattern
|
||||
-- as BoxMenu's rows
|
||||
{ label = "WITHDRAW ITEM", keepOpen = true, onSelect = function() withdraw(game) end },
|
||||
{ label = "DEPOSIT ITEM", keepOpen = true, onSelect = function() deposit(game) end },
|
||||
{ label = "TOSS ITEM", keepOpen = true, onSelect = function() toss(game) end },
|
||||
{ label = "LOG OFF" },
|
||||
-- silent PC session (BIT_NO_MENU_BUTTON_SOUND); players_pc.asm
|
||||
-- PlayersPCMenu TextBoxBorder (0,0) b=8 c=14 → 16x10
|
||||
|
||||
@@ -9,7 +9,8 @@ function PokedexMenu:sgbPalettes(game)
|
||||
return require("src.render.PaletteFX").wholeNamed(game.data, "BROWNMON")
|
||||
end
|
||||
|
||||
function PokedexMenu.new(game)
|
||||
function PokedexMenu.new(game, opts)
|
||||
opts = opts or {}
|
||||
local dex = game.save.pokedex or { seen = {}, owned = {} }
|
||||
local byDex = {}
|
||||
for species, def in pairs(game.data.pokemon) do
|
||||
@@ -47,6 +48,7 @@ function PokedexMenu.new(game)
|
||||
local list = ListMenu.new(game, "POKéDEX", items, {
|
||||
footer = ("SEEN %d OWNED %d"):format(seen, owned),
|
||||
pageJump = true, -- Left/Right page jumps like the original
|
||||
onCancel = opts.onCancel, -- B returns to the start menu when opened from it
|
||||
onChoose = function(item)
|
||||
if not item.value then return end
|
||||
-- the DATA / CRY / AREA / QUIT choice (engine/menus/pokedex.asm
|
||||
|
||||
+10
-5
@@ -19,10 +19,15 @@ function StartMenu.new(game)
|
||||
local flags = game.save.flags or {}
|
||||
local items = {}
|
||||
|
||||
-- vanilla start submenus return here on B (RedisplayStartMenu): the
|
||||
-- generic Menu pops the start menu when a row is selected, so each
|
||||
-- submenu gets an onCancel that re-opens it
|
||||
local function reopen() Screens.push(game, "StartMenu") end
|
||||
|
||||
-- POKéDEX: only after Oak hands it over
|
||||
if flags.EVENT_GOT_POKEDEX then
|
||||
table.insert(items, { label = "POKéDEX", onSelect = function()
|
||||
Screens.push(game, "PokedexMenu")
|
||||
Screens.push(game, "PokedexMenu", { onCancel = reopen })
|
||||
end })
|
||||
end
|
||||
|
||||
@@ -30,17 +35,17 @@ function StartMenu.new(game)
|
||||
-- an empty party; selecting it then just no-ops)
|
||||
table.insert(items, { label = "POKéMON", onSelect = function()
|
||||
if #game.save.party == 0 then return end
|
||||
Screens.push(game, "PartyMenu")
|
||||
Screens.push(game, "PartyMenu", { onCancel = reopen })
|
||||
end })
|
||||
|
||||
table.insert(items, { label = "ITEM", onSelect = function()
|
||||
Screens.push(game, "BagMenu")
|
||||
Screens.push(game, "BagMenu", { onCancel = reopen })
|
||||
end })
|
||||
|
||||
-- the player's name opens the trainer card (StartMenu_TrainerInfo)
|
||||
table.insert(items, { label = game.save.player.name or "RED",
|
||||
onSelect = function()
|
||||
Screens.push(game, "TrainerCard")
|
||||
Screens.push(game, "TrainerCard", { onCancel = reopen })
|
||||
end })
|
||||
|
||||
-- SAVE shows the player/badges/dex/time panel then asks to confirm
|
||||
@@ -74,7 +79,7 @@ function StartMenu.new(game)
|
||||
end })
|
||||
|
||||
table.insert(items, { label = "OPTION", onSelect = function()
|
||||
Screens.push(game, "OptionsMenu")
|
||||
Screens.push(game, "OptionsMenu", { onCancel = reopen })
|
||||
end })
|
||||
|
||||
-- LINK needs a party
|
||||
|
||||
@@ -30,8 +30,10 @@ local function quads16(img, count, stride, x0, y0)
|
||||
return q
|
||||
end
|
||||
|
||||
function TrainerCard.new(game)
|
||||
local self = setmetatable({ game = game }, TrainerCard)
|
||||
function TrainerCard.new(game, opts)
|
||||
opts = opts or {}
|
||||
local self = setmetatable({ game = game, onCancel = opts.onCancel },
|
||||
TrainerCard)
|
||||
local img = tryImage("assets/generated/trainer_card/badges.png")
|
||||
if img then
|
||||
-- badges.2bpp is 8 stacked [face, badge] pairs (DrawBadges FaceBadgeTiles)
|
||||
@@ -66,8 +68,12 @@ end
|
||||
|
||||
function TrainerCard:update(dt)
|
||||
local input = self.game.input
|
||||
-- either button dismisses the card back to the start menu
|
||||
-- (StartMenu_TrainerInfo: WaitForTextScrollButtonPress then
|
||||
-- RedisplayStartMenu)
|
||||
if input:wasPressed("a") or input:wasPressed("b") then
|
||||
self.game.stack:pop()
|
||||
if self.onCancel then self.onCancel() end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -185,6 +185,83 @@ check(#menu.items == #VANILLA_START and menu.items[3].label == "ITEM",
|
||||
check(logged("ui.start_menu.items returned"), "the degrade is logged")
|
||||
hooks:removeOwner("bad")
|
||||
|
||||
-- ------- issue #270: B in a start submenu returns to the start menu
|
||||
-- (RedisplayStartMenu), not to the overworld. The generic Menu pops the
|
||||
-- start menu when a row is selected, so every vanilla submenu must carry
|
||||
-- an onCancel that re-opens it.
|
||||
local function gameForSubmenus()
|
||||
local game = startGame()
|
||||
game.save.inventory = {}
|
||||
game.save.money = 0
|
||||
game.save.pcItems = { POTION = 1 }
|
||||
game.data.items = { POTION = { name = "POTION" } }
|
||||
game.data.pokemon = {}
|
||||
game.data.constants = {}
|
||||
game.data.rulesets = {}
|
||||
game.modStatus = { available = {} }
|
||||
return game
|
||||
end
|
||||
|
||||
local MenuClass = require("src.ui.Menu")
|
||||
for _, rowLabel in ipairs({ "POKéDEX", "POKéMON", "ITEM", "RED", "OPTION" }) do
|
||||
local game = gameForSubmenus()
|
||||
local start = Screens.push(game, "StartMenu")
|
||||
for i, item in ipairs(start.items) do
|
||||
if item.label == rowLabel then start.index = i end
|
||||
end
|
||||
press(start, "a")
|
||||
local sub = game.stack:top()
|
||||
check(sub ~= start, rowLabel .. " opens a submenu over the overworld")
|
||||
press(sub, "b")
|
||||
local top = game.stack:top()
|
||||
check(top ~= nil and top ~= sub and getmetatable(top) == MenuClass
|
||||
and top.items[1].label == "POKéDEX",
|
||||
"B from " .. rowLabel .. " re-opens the start menu")
|
||||
check(#game.stack.states == 1,
|
||||
"B from " .. rowLabel .. " leaves exactly the start menu on the stack")
|
||||
check(top.index == start.index,
|
||||
"the re-opened start menu restores the " .. rowLabel .. " cursor row")
|
||||
end
|
||||
|
||||
-- the trainer card also dismisses on A, back to the start menu
|
||||
local cardGame = gameForSubmenus()
|
||||
local cardStart = Screens.push(cardGame, "StartMenu")
|
||||
for i, item in ipairs(cardStart.items) do
|
||||
if item.label == "RED" then cardStart.index = i end
|
||||
end
|
||||
press(cardStart, "a")
|
||||
press(cardGame.stack:top(), "a")
|
||||
check(getmetatable(cardGame.stack:top()) == MenuClass
|
||||
and cardGame.stack:top().items[1].label == "POKéDEX",
|
||||
"A on the trainer card also returns to the start menu")
|
||||
|
||||
-- the options CANCEL row takes the same path as B
|
||||
local optCancelGame = gameForSubmenus()
|
||||
local optStart = Screens.push(optCancelGame, "StartMenu")
|
||||
for i, item in ipairs(optStart.items) do
|
||||
if item.label == "OPTION" then optStart.index = i end
|
||||
end
|
||||
press(optStart, "a")
|
||||
local optMenu = optCancelGame.stack:top()
|
||||
optMenu.index = #optMenu.rows + 1 -- CANCEL
|
||||
press(optMenu, "a")
|
||||
check(getmetatable(optCancelGame.stack:top()) == MenuClass
|
||||
and optCancelGame.stack:top().items[1].label == "POKéDEX",
|
||||
"the options CANCEL row returns to the start menu")
|
||||
|
||||
-- the PC session (players_pc.asm): B in an item list returns to the PC's
|
||||
-- root menu, which stays on the stack via keepOpen rows
|
||||
local pcGame = gameForSubmenus()
|
||||
local pcRoot = Screens.push(pcGame, "PlayerPC")
|
||||
press(pcRoot, "a") -- WITHDRAW ITEM
|
||||
check(#pcGame.stack.states == 2,
|
||||
"WITHDRAW ITEM keeps the PC root menu underneath")
|
||||
press(pcGame.stack:top(), "b")
|
||||
check(pcGame.stack:top() == pcRoot,
|
||||
"B in the withdraw list returns to the PC root menu")
|
||||
press(pcRoot, "b")
|
||||
check(#pcGame.stack.states == 0, "B on the PC root logs off to the overworld")
|
||||
|
||||
-- ------- ui.options.rows and the descriptor refactor
|
||||
local OptionsMenu = require("src.ui.OptionsMenu")
|
||||
local function optGame()
|
||||
|
||||
Reference in New Issue
Block a user