Files
gen1recomp/tests/engine/bag_bicycle_no_options_bug1705.lua
T
bryanthaboi 1905261c5b Spider Car Unleashed
CLOSES #1483, CLOSES #1610, CLOSES #1615, CLOSES #1646, CLOSES #1649, CLOSES #1651, CLOSES #1653, CLOSES #1656, CLOSES #1683, CLOSES #1685, CLOSES #1686, CLOSES #1687, CLOSES #1688, CLOSES #1689, CLOSES #1690, CLOSES #1693, CLOSES #1694, CLOSES #1695, CLOSES #1696, CLOSES #1702, CLOSES #1704, CLOSES #1705, CLOSES #1706, CLOSES #1707, CLOSES #1708, CLOSES #1710, CLOSES #1711, CLOSES #1712, CLOSES #1713, CLOSES #1716, CLOSES #1717, CLOSES #1718, CLOSES #1719, CLOSES #1720, CLOSES #1721, CLOSES #1725, CLOSES #1732, CLOSES #1745, CLOSES #1748, CLOSES #1749, CLOSES #1751, CLOSES #1754
2026-08-24 07:52:05 -04:00

245 lines
8.6 KiB
Lua

-- The BICYCLE is used straight off the item list, with no USE/TOSS box
-- (#1705).
--
-- StartMenu_Item's .choseItem reads wCurItem and, before it ever loads
-- USE_TOSS_MENU_TEMPLATE into wTextBoxID, does `cp BICYCLE / jp z,
-- .useOrTossItem` (engine/menus/start_sub_menus.asm:340-342). So the bike
-- mounts, dismounts or refuses on one A press, and -- having no TOSS row to
-- reach -- can never be thrown away from the bag at all. Every other item,
-- key items included, still gets the option box.
--
-- The port pushed the USE/TOSS Menu for every field item, so the bike took
-- two presses and offered a TOSS the cart does not.
-- luajit tests/engine/bag_bicycle_no_options_bug1705.lua
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
love = love or require("tests.love_stub")
package.loaded["src.core.Sound"] = { play = function() end, playCry = function() end }
local music = {}
package.loaded["src.core.Music"] = {
playMap = function(_, mapId, biking) music[#music + 1] = { mapId, biking } end,
}
package.loaded["src.render.TextBox"] = {
new = function(_, text, done) return { textBox = true, text = text, done = done } end,
soundOpts = function() return nil end,
}
package.loaded["src.ui.BagMenu"] = nil
local BagMenu = require("src.ui.BagMenu")
local Menu = require("src.ui.Menu")
require("src.ui.Screens").invalidate()
local Fixtures = require("tests.modkit.fixtures")
local Bag = require("src.inventory.Bag")
local Data = Fixtures.fresh()
-- the fixture item table carries neither, and BagMenu reads only
-- name/keyItem/machine off a def; the branches under test key on the id
Data.items.BICYCLE = { id = "BICYCLE", index = 6, name = "BICYCLE", price = 0,
keyItem = true }
Data.items.TOWN_MAP = { id = "TOWN_MAP", index = 5, name = "TOWN MAP",
price = 0, keyItem = true }
Data.items.FIX_POTION = Data.items.FIX_POTION
or { id = "FIX_POTION", index = 20, name = "FIX POTION", price = 300 }
local BIKE_MAP = { id = "FIX_TOWN", def = { tileset = "OVERWORLD" } }
local function freshGame(extras)
local game = {
data = Data,
save = {
party = {},
player = { name = "RED", id = 1 },
inventory = {},
options = {},
flags = {},
money = 0,
},
}
game.stack = {
states = {},
push = function(self, s) table.insert(self.states, s) end,
pop = function(self) return table.remove(self.states) end,
top = function(self) return self.states[#self.states] end,
}
game.input = { pressed = nil }
function game.input:wasPressed(b) return self.pressed == b end
function game.input:isDown() return false end
-- IsBikeRidingAllowed reads the tileset off the loaded map, and the
-- mount/dismount lines read the player's name off the save
game.overworld = { map = BIKE_MAP, player = { surfing = false } }
Bag.add(game.save, "FIX_POTION", 3)
Bag.add(game.save, "BICYCLE", 1)
Bag.add(game.save, "TOWN_MAP", 1)
for k, v in pairs(extras or {}) do game.save[k] = v end
return game
end
local function rowFor(list, id)
for i, r in ipairs(list.items) do
if r.value == id then return i end
end
return nil
end
local function isMenu(s) return getmetatable(s) == Menu end
local function isBox(s) return type(s) == "table" and s.textBox == true end
local function inStack(game, pred)
for _, s in ipairs(game.stack.states) do
if pred(s) then return true end
end
return false
end
-- Open the bag, put the cursor on `id` and press A once. Returns the list
-- and every state the press left on the stack above it, so a USE/TOSS box
-- that appears and is then replaced is still caught.
local function chooseOnce(game, id)
local list = BagMenu.new(game, {})
game.stack:push(list)
local row = rowFor(list, id)
if not row then return nil, nil, "no " .. id .. " row in the bag" end
list.index = row
local seen = {}
local realPush = game.stack.push
game.stack.push = function(self, s)
seen[#seen + 1] = s
return realPush(self, s)
end
game.input.pressed = "a"
list:update(1 / 60)
game.input.pressed = nil
game.stack.push = realPush
return list, seen
end
local function sawMenu(seen)
for _, s in ipairs(seen or {}) do
if isMenu(s) then return true end
end
return false
end
local function boxText(game)
local top = game.stack:top()
return isBox(top) and top.text or nil
end
-- on foot, somewhere cycling is allowed: one A press mounts
do
music = {}
local game = freshGame()
local list, seen, why = chooseOnce(game, "BICYCLE")
if check(list ~= nil, "the bag opened on the BICYCLE row: " .. tostring(why)) then
check(not sawMenu(seen),
"no USE/TOSS Menu was pushed, not even for a frame (:340-342)")
eq(game.save.onBike, true, "the single press mounted the bike")
local said = boxText(game)
if check(said ~= nil, "and printed a line") then
check(said:find("got on", 1, true) ~= nil,
"which is the mount text: " .. tostring(said))
end
eq(game.save.inventory.BICYCLE, 1, "the bike is still in the bag")
eq(#music, 1, "the bike theme was cued once")
check(music[1] and music[1][2] == true, "as the riding track")
end
end
-- already riding: the same single press dismounts
do
music = {}
local game = freshGame({ onBike = true })
local list, seen = chooseOnce(game, "BICYCLE")
if check(list ~= nil, "the bag opened while riding") then
check(not sawMenu(seen), "still no option box on the way off the bike")
eq(game.save.onBike, false, "one press dismounted")
local said = boxText(game)
if check(said ~= nil, "and printed a line") then
check(said:find("got off", 1, true) ~= nil,
"which is the dismount text: " .. tostring(said))
end
check(music[1] and music[1][2] == false, "and the map theme came back")
end
end
-- Cycling Road: the refusal is reached on the same single press, and the
-- item list stays open behind it (`jp ItemMenuLoop`, #513)
do
local game = freshGame({ onBike = true, forcedBike = true })
local list, seen = chooseOnce(game, "BICYCLE")
if check(list ~= nil, "the bag opened on the Cycling Road") then
check(not sawMenu(seen), "the refusal is not behind an option box either")
eq(game.save.onBike, true, "and the player is still riding")
local said = boxText(game)
if check(said ~= nil, "the refusal printed") then
check(said:find("get off", 1, true) ~= nil,
"with _CannotGetOffHereText: " .. tostring(said))
end
check(inStack(game, function(s) return s == list end),
"the bag list is still on the stack under it (#513)")
end
end
-- somewhere cycling is not allowed: still one press, still no box
do
local game = freshGame()
game.overworld.map = { id = "FIX_INDOORS", def = { tileset = "FIX_HOUSE" } }
local list, seen = chooseOnce(game, "BICYCLE")
if check(list ~= nil, "the bag opened indoors") then
check(not sawMenu(seen), "the no-cycling refusal skips the box too")
eq(game.save.onBike, nil, "and nothing was mounted")
local said = boxText(game)
check(said ~= nil and said:find("cycling", 1, true) ~= nil,
"NoCyclingAllowedHere printed: " .. tostring(said))
end
end
-- the control cases: everything else still gets USE/TOSS, key items included
do
local game = freshGame()
local list, seen = chooseOnce(game, "FIX_POTION")
if check(list ~= nil, "the bag opened on a plain item") then
check(sawMenu(seen), "an ordinary item still opens the option box")
check(isMenu(game.stack:top()), "and it is what the A press left on top")
end
end
do
local game = freshGame()
local list, seen = chooseOnce(game, "TOWN_MAP")
if check(list ~= nil, "the bag opened on the TOWN MAP") then
check(sawMenu(seen),
"another key item still gets the box: pokered special-cases the "
.. "BICYCLE by id, not key items as a class")
end
end
-- the box the bike no longer opens is the only route to TOSS, so the bike
-- cannot be thrown away from the bag at all
do
local game = freshGame()
local list, seen = chooseOnce(game, "BICYCLE")
if check(list ~= nil, "the bag opened on the BICYCLE row") then
local rows = {}
for _, s in ipairs(seen or {}) do
for _, it in ipairs((isMenu(s) and s.items) or {}) do
rows[#rows + 1] = tostring(it.label)
end
end
eq(#rows, 0, "the press offered no menu rows at all, TOSS included")
eq(game.save.inventory.BICYCLE, 1, "and the bike survives the press")
end
end
package.loaded["src.core.Sound"] = nil
package.loaded["src.core.Music"] = nil
package.loaded["src.render.TextBox"] = nil
package.loaded["src.ui.BagMenu"] = nil
require("src.ui.Screens").invalidate()
T.finish()