diff --git a/src/ui/BoxMenu.lua b/src/ui/BoxMenu.lua index a8bd6453..4f5b8748 100644 --- a/src/ui/BoxMenu.lua +++ b/src/ui/BoxMenu.lua @@ -99,6 +99,7 @@ local function withdraw(game) itemBox = true, onChoose = function(item, list) if item.cancel then list:close() return end + list.hollowIndex = list.index -- home/list_menu.asm:91 local mon = box[item.value] if not mon then return end monSubmenu(game, Strings("WITHDRAW"), mon, function() @@ -149,6 +150,7 @@ local function deposit(game) itemBox = true, onChoose = function(item, list) if item.cancel then list:close() return end + list.hollowIndex = list.index -- home/list_menu.asm:91 local mon = game.save.party[item.value] if not mon then return end local Follower = require("src.world.PikachuFollower") @@ -202,6 +204,7 @@ local function release(game) itemBox = true, onChoose = function(item, list) if item.cancel then list:close() return end + list.hollowIndex = list.index -- home/list_menu.asm:91 local mon = box[list.index] if not mon then return end local name = monName(game, mon) diff --git a/src/ui/PlayerPC.lua b/src/ui/PlayerPC.lua index 68c89d18..1bfa5569 100644 --- a/src/ui/PlayerPC.lua +++ b/src/ui/PlayerPC.lua @@ -143,6 +143,7 @@ local function withdraw(game) noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) if leftOnCancel(item, list) then return end + list.hollowIndex = list.index -- home/list_menu.asm:91 askQuantity(game, list, pc[item.value] or 1, item.value, function(qty) local Bag = require("src.inventory.Bag") if not Bag.add(game.save, item.value, qty, game.data) then @@ -190,6 +191,7 @@ local function deposit(game) noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) if leftOnCancel(item, list) then return end + list.hollowIndex = list.index -- home/list_menu.asm:91 askQuantity(game, list, inv[item.value] or 1, item.value, function(qty) if pcFull(game, pc, item.value) then list.footer = Strings("No room left to\nstore items.") @@ -223,6 +225,7 @@ local function toss(game) noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) if leftOnCancel(item, list) then return end + list.hollowIndex = list.index -- home/list_menu.asm:91 local def = game.data.items[item.value] if (def and def.keyItem) or item.value:find("^HM_") then list.footer = Strings("That's too impor-\ntant to toss!") @@ -259,7 +262,7 @@ function PlayerPC.new(game, opts) local logOff = function() if opts and opts.direct then Sound.play(game.data, "Turn_Off_PC") end end - return Menu.new(game, { + local rows = { -- 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 @@ -267,12 +270,40 @@ function PlayerPC.new(game, opts) { label = Strings("DEPOSIT ITEM"), keepOpen = true, onSelect = function() deposit(game) end }, { label = Strings("TOSS ITEM"), keepOpen = true, onSelect = function() toss(game) end }, { label = Strings("LOG OFF"), onSelect = logOff }, - -- silent PC session (BIT_NO_MENU_BUTTON_SOUND); players_pc.asm - -- PlayersPCMenu TextBoxBorder (0,0) b=8 c=14 → 16x10 - }, { tx = 0, ty = 0, tw = 16, th = 10, - title = romText(game.data, "_WhatDoYouWantText", - "What do you want\nto do?"), + } + -- silent PC session (BIT_NO_MENU_BUTTON_SOUND); players_pc.asm + -- PlayersPCMenu TextBoxBorder (0,0) b=8 c=14 → 16x10 + local menu = Menu.new(game, rows, { tx = 0, ty = 0, tw = 16, th = 10, noSound = true, onCancel = logOff }) + local prompt = TextBox.strip(romText(game.data, "_WhatDoYouWantText", + "What do you want\nto do?")) + for _, row in ipairs(rows) do + local onSelect = row.onSelect + if row.keepOpen and onSelect then + row.onSelect = function() + menu.hollowIndex = menu.index -- engine/menus/players_pc.asm:55 + onSelect() + end + end + end + local baseUpdate = menu.update + function menu:update(dt) + if self.game.stack:top() == self then self.hollowIndex = nil end + return baseUpdate(self, dt) + end + local baseDraw = menu.draw + function menu:draw() + baseDraw(self) + Font.drawBox(0, 12, 20, 6) -- engine/menus/players_pc.asm:50 + love.graphics.setColor(0, 0, 0, 1) + local y = 112 + for line in (prompt .. "\n"):gmatch("([^\n]*)\n") do + Font.draw(line, 8, y) + y = y + 16 + end + love.graphics.setColor(1, 1, 1, 1) + end + return menu end return PlayerPC diff --git a/src/world/OverworldController.lua b/src/world/OverworldController.lua index 9e262c46..c21e2a1b 100644 --- a/src/world/OverworldController.lua +++ b/src/world/OverworldController.lua @@ -3535,10 +3535,19 @@ function OverworldState:openPC(onDone) -- (engine/menus/pokemon_pc.asm gates on EVENT_MET_BILL; we reach that -- when Bill hands over the SS Ticket) local metBill = flags.EVENT_MET_BILL or flags.EVENT_GOT_SS_TICKET - -- keepOpen so B in the sub-PC returns here instead of exiting the - -- PC session (#695); the sub-PC screens (BoxMenu, PlayerPC) already - -- use keepOpen for their own rows, matching the original ROM's flow - -- where the main menu stays underneath. + local menu + -- engine/pokemon/bills_pc.asm:121, engine/menus/pc.asm:86 + local function openSub(id) + if Game.stack:top() == menu then Game.stack:pop() end + local sub = Screens.push(Game, id) + local baseExit = sub.exit + sub.exit = function(...) + if type(baseExit) == "function" then baseExit(...) end + menu.index = 1 -- engine/pokemon/bills_pc.asm:81 + Game.stack:push(menu) + end + return sub + end local boxPcLabel = metBill and Strings.source("BILL'S PC") or Strings.source("SOMEONE'S PC") table.insert(items, { @@ -3553,7 +3562,7 @@ function OverworldState:openPC(onDone) or romText(Game.data, "_AccessedSomeonesPCText", "Accessed someone's\nPC.\fAccessed POKéMON\nStorage System.") Game.stack:push(TextBox.new(Game, accessed, function() - Screens.push(Game, "BoxMenu") + openSub("BoxMenu") end)) done() end, @@ -3572,7 +3581,7 @@ function OverworldState:openPC(onDone) Game.stack:push(TextBox.new(Game, romText(Game.data, "_AccessedMyPCText", "Accessed my PC.\fAccessed Item\nStorage System."), - function() Screens.push(Game, "PlayerPC") end)) + function() openSub("PlayerPC") end)) done() end, }) @@ -3585,7 +3594,10 @@ function OverworldState:openPC(onDone) onSelect = function() -- pc.asm OaksPC plays SFX_ENTER_PC before the farcall (#960) require("src.core.Sound").play(Game.data, "Enter_PC") - self:openOaksPC(done) + self:openOaksPC(function() + menu.index = 1 -- engine/pokemon/bills_pc.asm:81 + done() + end) end, }) @@ -3601,7 +3613,7 @@ function OverworldState:openPC(onDone) Game.stack:push(TextBox.new(Game, romText(Game.data, "_AccessedHoFPCText", "Accessed POKéMON\nLEAGUE's site.\fAccessed the HALL\nOF FAME List."), - function() Screens.push(Game, "LeaguePC") end)) + function() openSub("LeaguePC") end)) done() end, }) @@ -3639,7 +3651,7 @@ function OverworldState:openPC(onDone) table.insert(items, { label = Strings("LOG OFF"), onSelect = logOff }) -- BIT_NO_MENU_BUTTON_SOUND for the whole PC session; DisplayPCMainMenu's -- TextBoxBorder c=14 interior -> tw 16 (engine/overworld/pokecenter_pc.asm) - local menu = Menu.new(Game, items, + menu = Menu.new(Game, items, { tx = 0, ty = 0, tw = 16, th = #items * 2 + 2, onCancel = logOff, noSound = true }) -- engine/menus/pc.asm:5 diff --git a/tests/drivers/pc_layout_2548.lua b/tests/drivers/pc_layout_2548.lua new file mode 100644 index 00000000..d54a7ce9 --- /dev/null +++ b/tests/drivers/pc_layout_2548.lua @@ -0,0 +1,145 @@ +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("POKEPORT_SHOT_DIR") or os.getenv("SHOT_DIR") or "/tmp/shots" + local Menu = require("src.ui.Menu") + local Font = require("src.render.Font") + local Theme = require("src.ui.Theme") + local Pokemon = require("src.pokemon.Pokemon") + local Boxes = require("src.pokemon.Boxes") + + local ok = true + local function check(label, cond) + if cond then + U.log("PASS", label) + else + ok = false + U.log("FAIL", label) + end + end + local function isMenu(s) return getmetatable(s) == Menu end + local function hasLabel(menu, needle) + for _, it in ipairs(menu.items or {}) do + if type(it.label) == "string" and it.label:find(needle, 1, true) then return true end + end + return false + end + local function mainMenu() + for _, s in ipairs(game.stack.states) do + if isMenu(s) and hasLabel(s, "LOG OFF") and hasLabel(s, "'s PC") then return s end + end + end + local function mash(cond) + for _ = 1, 200 do + if cond() then return true end + U.tap(game, "a") + U.wait(3) + end + return false + end + local function drawnY(state) + local ys = {} + local real = Font.draw + Font.draw = function(text, x, y) ys[text] = y return 8 end + local good, err = pcall(state.draw, state) + Font.draw = real + if not good then error(err, 0) end + return ys + end + local function cursorCode(state) + local seen + local real = Font.drawCode + Font.drawCode = function(code, x, y) + if code == Theme.cursor or code == Theme.cursorHollow then seen = code end + end + local good, err = pcall(state.draw, state) + Font.drawCode = real + if not good then error(err, 0) end + return seen + end + local function fail() + U.log("RESULT: FAIL") + love.event.quit(1) + end + + game.save.party = { Pokemon.new(game.data, "PIKACHU", 12), Pokemon.new(game.data, "EEVEE", 10) } + Boxes.ensure(game.save) + game.save.boxes[1] = { Pokemon.new(game.data, "SPEAROW", 8), Pokemon.new(game.data, "RATTATA", 5) } + game.save.pcItems = { POTION = 3, ANTIDOTE = 2 } + game.save.flags = game.save.flags or {} + game.save.flags.EVENT_MET_BILL = true + game.save.flags.EVENT_GOT_POKEDEX = true + + U.teleport(game, "VIRIDIAN_POKECENTER", 13, 4, "up") + U.tap(game, "a") + local main + mash(function() + local t = game.stack:top() + main = isMenu(t) and hasLabel(t, "LOG OFF") and t + return main + end) + check("main menu opens", main) + if not main then return fail() end + U.wait(8) + + U.tap(game, "a") + mash(function() local t = game.stack:top() return isMenu(t) and hasLabel(t, "SEE YA") end) + local bills = game.stack:top() + check("bills pc: main menu gone from stack", mainMenu() == nil) + U.still(game, DIR .. "/2548_01_bills_pc_no_main_menu.png") + + U.tap(game, "a") + U.wait(8) + local list = game.stack:top() + check("bills pc: withdraw list open", list.kind == "pc_box_withdraw") + U.tap(game, "a") + U.wait(8) + check("bills pc: withdraw list chosen row hollow", list.hollowIndex == list.index) + check("bills pc: withdraw list draws hollow arrow", cursorCode(list) == Theme.cursorHollow) + U.still(game, DIR .. "/2548_02_bills_withdraw_hollow_cursor.png") + U.tap(game, "b") + U.wait(6) + U.tap(game, "b") + U.wait(6) + + while game.stack:top() ~= main and game.stack:top() ~= game.overworld do + U.tap(game, "b") + U.wait(6) + if U.frame() > 1e7 then break end + end + check("main menu rebuilt on return", game.stack:top() == main) + check("main menu cursor back on row 1", main.index == 1) + U.still(game, DIR .. "/2548_03_main_menu_rebuilt.png") + + U.tap(game, "down") + U.wait(3) + U.tap(game, "a") + local ppc + mash(function() + local t = game.stack:top() + ppc = isMenu(t) and hasLabel(t, "WITHDRAW ITEM") and t + return ppc + end) + check("player pc: opens", ppc) + if not ppc then return fail() end + check("player pc: main menu gone from stack", mainMenu() == nil) + check("player pc: no title", ppc.title == nil) + local ys = drawnY(ppc) + check("player pc: prompt line 1 in bottom box", ys["What do you want"] == 112) + check("player pc: prompt line 2 in bottom box", ys["to do?"] == 128) + U.still(game, DIR .. "/2548_04_players_pc_prompt_bottom.png") + + U.tap(game, "a") + U.wait(8) + local items = game.stack:top() + check("player pc: item list open", items.kind == "pc_item_withdraw") + check("player pc: parent cursor hollow under list", ppc.hollowIndex == ppc.index) + check("player pc: parent draws hollow arrow", cursorCode(ppc) == Theme.cursorHollow) + U.still(game, DIR .. "/2548_05_players_pc_parent_hollow.png") + U.tap(game, "a") + U.wait(8) + check("player pc: item list row hollow under quantity box", items.hollowIndex == items.index) + U.still(game, DIR .. "/2548_06_players_pc_list_hollow.png") + + U.log(ok and "RESULT: ALL PASS" or "RESULT: FAIL") + love.event.quit(ok and 0 or 1) +end diff --git a/tests/engine/menu_row_offset_bug564.lua b/tests/engine/menu_row_offset_bug564.lua index a13ae1c1..abbab748 100644 --- a/tests/engine/menu_row_offset_bug564.lua +++ b/tests/engine/menu_row_offset_bug564.lua @@ -40,16 +40,24 @@ end -- Draw the menu with the font stubbed out and report, in tile rows, where -- the labels, the ▶ cursor and the "more below" arrow actually landed. -local function layout(menu) +local function layout(menu, prompt) local realDraw, realCode = Font.draw, Font.drawCode - local out = { rows = {}, cols = {}, titleRows = {} } + local out = { rows = {}, cols = {}, titleRows = {}, promptY = {} } local title = menu.title and menu.title:gsub("{DONE}%s*$", "") :gsub("{PROMPT}%s*$", "") local titleLines = {} for line in ((title or "") .. "\n"):gmatch("(.-)\n") do titleLines[line] = true end + local promptLines = {} + for line in ((prompt or "") .. "\n"):gmatch("(.-)\n") do + promptLines[line] = true + end Font.draw = function(text, x, y) + if promptLines[text] then + out.promptY[text] = y + return 8 + end if titleLines[text] then out.titleRows[#out.titleRows + 1] = y / 8 return 8 @@ -74,8 +82,8 @@ end -- Every boxed menu owes the same two things to its border, and the second -- is the one #564/#572 were about: a menu that starts at ty+1 leaves the -- bottom interior row empty and the text crowded under the top edge. -local function boxRules(menu, label) - local at = layout(menu) +local function boxRules(menu, label, prompt) + local at = layout(menu, prompt) local first, last = at.rows[1], at.rows[#at.rows] check(first and first >= menu.ty + 1 and last <= menu.ty + menu.th - 2, label .. ": every choice sits inside the border (rows " @@ -121,10 +129,16 @@ check(sat.arrow > sat.rows[#sat.rows], -- choices start at hlcoord 2,2. local pc = Screens.push(newGame(), "PlayerPC") eq(pc.th, 10, "player's PC box is 10 tiles tall") -local pcLayout = boxRules(pc, "player's PC") +local pcLayout = boxRules(pc, "player's PC", + "What do you want\nto do?") same(pcLayout.rows, { 2, 4, 6, 8 }, "player's PC rows match hlcoord 2,2") -same(pcLayout.titleRows, { 0, 1 }, "player's PC prompt stays above its choices") +same(pcLayout.titleRows, {}, "player's PC prompt is not a border title") +eq(pc.title, nil, "player's PC has no title") +eq(pcLayout.promptY["What do you want"], 112, + "players_pc.asm:50 prompt line 1 is in the bottom text box") +eq(pcLayout.promptY["to do?"], 128, + "players_pc.asm:50 prompt line 2 is in the bottom text box") -- The Pokédex side menu (pokedex.asm PokedexMenuItemsText): DATA / CRY / -- AREA / QUIT, opened by choosing a seen species off the dex list. diff --git a/tests/engine/pc_accessed_text_bug1529.lua b/tests/engine/pc_accessed_text_bug1529.lua index cf1b4942..fcf323e7 100644 --- a/tests/engine/pc_accessed_text_bug1529.lua +++ b/tests/engine/pc_accessed_text_bug1529.lua @@ -27,7 +27,12 @@ local function setUpvalue(fn, name, val) end local pushed, screens = {}, {} -local stackStub = { push = function(_, item) pushed[#pushed + 1] = item end } +local stackStub = { + push = function(_, item) pushed[#pushed + 1] = item end, + top = function() return pushed[#pushed] end, + pop = function() return table.remove(pushed) end, +} +local subs = {} local textBoxStub = { new = function(_, text, onDone, opts) return { kind = "text", text = text, onDone = onDone, opts = opts } @@ -43,7 +48,12 @@ local fakeGame = { data = Data, save = SaveData.newGame(), stack = stackStub } T.check(setUpvalue(OW.openPC, "Game", fakeGame), "Game upvalue on openPC") T.check(setUpvalue(OW.openPC, "TextBox", textBoxStub), "TextBox upvalue on openPC") T.check(setUpvalue(OW.openPC, "Screens", - { push = function(_, id) screens[#screens + 1] = id end }), "Screens upvalue on openPC") + { push = function(_, id) + screens[#screens + 1] = id + local sub = { id = id } + subs[#subs + 1] = sub + return sub + end }), "Screens upvalue on openPC") local fakeSelf = setmetatable({}, { __index = OW }) @@ -69,8 +79,16 @@ do T.check(tostring(box.text):find("Accessed someone's", 1, true) ~= nil, "before meeting BILL it is AccessedSomeonesPCText") T.eq(#screens, 0, "and the box screen has NOT opened yet") + table.remove(pushed) box.onDone() T.eq(screens[1], "BoxMenu", "BoxMenu follows the text, as the farcall does") + T.check(menu ~= pushed[#pushed], + "bills_pc.asm:121 the main menu is wiped once BoxMenu opens") + menu.index = 3 + subs[1].exit() + T.check(pushed[#pushed] == menu, + "pc.asm:86 ReloadMainMenu rebuilds the main menu on return") + T.eq(menu.index, 1, "bills_pc.asm:81 the cursor is back on row 1") end -- === BILL'S PC once EVENT_MET_BILL is set @@ -97,4 +115,29 @@ do T.eq(screens[1], "PlayerPC", "PlayerPC follows the text") end +-- engine/menus/pc.asm:86 +do + pushed, screens = {}, {} + fakeGame.save = SaveData.newGame() + fakeGame.save.flags.EVENT_GOT_POKEDEX = true + fakeSelf:openPC(function() end) + pushed[#pushed].onDone() + local menu = pushed[#pushed] + local oakDone + fakeSelf.openOaksPC = function(_, cb) oakDone = cb end + local oakRow + for _, row in ipairs(menu.items) do + if row.label == "PROF.OAK's PC" then oakRow = row end + end + T.check(oakRow, "the PC menu has a PROF.OAK's PC row") + menu.index = 3 + oakRow.onSelect() + T.check(pushed[#pushed] == menu, + "oaks_pc.asm:1 the main menu stays up while Oak's PC runs") + oakDone() + T.check(pushed[#pushed] == menu, "and it is still the top menu after") + T.eq(menu.index, 1, "pc.asm:65 ReloadMainMenu, bills_pc.asm:81 cursor back on row 1") + fakeSelf.openOaksPC = nil +end + T.finish("PC access text (#1529)") diff --git a/tests/engine/pc_league_row_bug1566.lua b/tests/engine/pc_league_row_bug1566.lua index 510ab4ba..12e9097e 100644 --- a/tests/engine/pc_league_row_bug1566.lua +++ b/tests/engine/pc_league_row_bug1566.lua @@ -25,21 +25,34 @@ local fakeGame = { flags = { EVENT_GOT_POKEDEX = true }, player = { name = "RED" }, }, - stack = { push = function(_, item) pushed[#pushed + 1] = item end }, + stack = { + push = function(_, item) pushed[#pushed + 1] = item end, + top = function() return pushed[#pushed] end, + pop = function() return table.remove(pushed) end, + }, } local sounds = {} package.loaded["src.core.Sound"] = { play = function(_, name) sounds[#sounds + 1] = name end, } -local menuItems +local menuItems, mainMenu package.loaded["src.ui.Menu"] = { - new = function(_, items) menuItems = items; return { menu = true } end, + new = function(_, items) + menuItems = items + mainMenu = { menu = true, index = 4 } + return mainMenu + end, } -local opened = {} +local opened, subs = {}, {} T.check(setUpvalue(OW.openPC, "Game", fakeGame), "Game upvalue on openPC") T.check(setUpvalue(OW.openPC, "Screens", { - push = function(_, id) opened[#opened + 1] = id end, + push = function(_, id) + opened[#opened + 1] = id + local sub = {} + subs[#subs + 1] = sub + return sub + end, }), "Screens upvalue on openPC") T.check(setUpvalue(OW.openPC, "TextBox", { new = function(_, text, onDone) return { text = text, onDone = onDone } end, @@ -84,8 +97,17 @@ T.eq(sounds[#sounds], "Enter_PC", "PKMNLeague plays SFX_ENTER_PC") local box = pushed[#pushed] T.eq(box.text, fakeGame.data.text._AccessedHoFPCText, "PKMNLeaguePC prints AccessedHoFPCText first") +T.check(pushed[#pushed - 1] == mainMenu, + "the main menu is up under the access text") +table.remove(pushed) box.onDone() T.same(opened, { "LeaguePC" }, "the Hall of Fame roster screen opens") +T.check(pushed[#pushed] ~= mainMenu, + "bills_pc.asm:121 the main menu is wiped once the roster opens") +subs[1].exit() +T.check(pushed[#pushed] == mainMenu, + "pc.asm:86 ReloadMainMenu rebuilds the main menu on return") +T.eq(mainMenu.index, 1, "bills_pc.asm:81 the cursor is back on row 1") -- without the Pokedex, .noOaksPC2 skips Oak's PC and the league row alike -- (bills_pc.asm:48-49, :68-72); only the box height ignores it (:5-7) diff --git a/tests/engine/pc_list_kinds.lua b/tests/engine/pc_list_kinds.lua index 0957a21c..fbadebc6 100644 --- a/tests/engine/pc_list_kinds.lua +++ b/tests/engine/pc_list_kinds.lua @@ -41,16 +41,44 @@ for i, kind in ipairs({ "pc_box_withdraw", "pc_box_deposit", pushed = nil box.items[i].onSelect() T.eq(pushed and pushed.kind, kind, kind .. " is stable") + T.eq(box.hollowIndex, box.index, "bills_pc.asm:176 " .. kind .. " parent row goes hollow") + box.hollowIndex = nil +end + +for _, kind in ipairs({ "pc_box_withdraw", "pc_box_deposit", "pc_box_release" }) do + local idx = ({ pc_box_withdraw = 1, pc_box_deposit = 2, pc_box_release = 3 })[kind] + pushed = nil + box.items[idx].onSelect() + local list = pushed + T.eq(list.kind, kind, kind .. " list opens") + local item = list.items[1] + list.hollowIndex = nil + list.index = 1 + list.onChoose(item, list) + T.eq(list.hollowIndex, 1, "list_menu.asm:91 " .. kind .. " chosen row goes hollow") end local items = PlayerPC.new(game) -T.eq(items.title, Data.text._WhatDoYouWantText, - "player PC root menu uses the extracted prompt") +T.eq(items.title, nil, "player PC prompt is a bottom box, not a title") +do + local realDraw = require("src.render.Font").draw + local Font = require("src.render.Font") + local ys = {} + Font.draw = function(text, _, y) ys[text] = y return 8 end + local ok, err = pcall(items.draw, items) + Font.draw = realDraw + if not ok then error(err, 0) end + T.eq(ys["What do you want"], 112, "player PC prompt line 1 at y=112") + T.eq(ys["to do?"], 128, "player PC prompt line 2 at y=128") +end for i, kind in ipairs({ "pc_item_withdraw", "pc_item_deposit", "pc_item_toss" }) do pushed = nil + items.hollowIndex = nil + items.index = i items.items[i].onSelect() T.eq(pushed and pushed.kind, kind, kind .. " is stable") + T.eq(items.hollowIndex, i, "players_pc.asm:55 parent row goes hollow") end game.save.pcItems = { POKE_FLUTE = 1, HM_CUT = 1, FIX_POTION = 3 } @@ -70,6 +98,7 @@ T.check(type(withdraw.onChoose) == "function", local originalPrompt = withdraw.footer withdraw.onChoose(rows.FIX_POTION, withdraw) local quantity = pushed +T.eq(withdraw.hollowIndex, withdraw.index, "list_menu.asm:91 chosen row goes hollow") T.eq(withdraw.footer, "How many?", "quantity selection replaces the PC prompt") quantity.onDone(nil) T.eq(withdraw.footer, originalPrompt,