Return to the start menu when a pause submenu is cancelled (#270) (#300)

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:
johnjohto
2026-07-27 10:05:15 -04:00
committed by GitHub
parent ff90062741
commit 531a719794
7 changed files with 112 additions and 14 deletions
+2
View File
@@ -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
+6 -3
View File
@@ -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
View File
@@ -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
+3 -1
View File
@@ -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
View File
@@ -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
+8 -2
View File
@@ -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