mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
229 lines
7.7 KiB
Lua
229 lines
7.7 KiB
Lua
-- Parity test, Bill's House PC + Route25ToggleBillsScript (#120).
|
|
--
|
|
-- asm sources:
|
|
-- engine/events/hidden_events/bills_house_pc.asm (BillsHousePC /
|
|
-- BillsHousePokemonList: after EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING
|
|
-- the PC opens EEVEE/FLAREON/JOLTEON/VAPOREON; before that the
|
|
-- teleporter monitor text or cell-separator cutscene)
|
|
-- scripts/Route25.asm (Route25ToggleBillsScript: first Route 25 load
|
|
-- after EVENT_MET_BILL_2 + EVENT_GOT_SS_TICKET sets
|
|
-- EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING, hides BILL1 / nugget-bridge
|
|
-- guy, shows BILL2; mid-quest leave resets the separator flag)
|
|
--
|
|
-- Self-contained: run via `luajit tests/parity_bills_pc.lua`; also
|
|
-- dofile'd by tests/run_tests.lua's aggregator.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
if not _G.love then _G.love = require("tests.love_stub") end
|
|
local Data = require("src.core.Data")
|
|
if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end
|
|
local S = require("tests.harness").suite("parity bills pc")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local OW = require("src.world.OverworldController")
|
|
local SaveData = require("src.core.SaveData")
|
|
local story = require("data.scripts.story")
|
|
|
|
local function getUpvalue(fn, name)
|
|
local i = 1
|
|
while true do
|
|
local n, v = debug.getupvalue(fn, i)
|
|
if not n then return nil end
|
|
if n == name then return v end
|
|
i = i + 1
|
|
end
|
|
end
|
|
local function setUpvalue(fn, name, val)
|
|
local i = 1
|
|
while true do
|
|
local n = debug.getupvalue(fn, i)
|
|
if not n then return false end
|
|
if n == name then debug.setupvalue(fn, i, val); return true end
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
local pushed = {}
|
|
local stackStub = {
|
|
push = function(_, item)
|
|
pushed[#pushed + 1] = item
|
|
end,
|
|
}
|
|
local textBoxStub = {
|
|
new = function(_, text, onDone)
|
|
local box = { kind = "text", text = text, onDone = onDone }
|
|
if onDone then onDone() end
|
|
return box
|
|
end,
|
|
}
|
|
local menuStub = {
|
|
new = function(_, items, opts)
|
|
return { kind = "menu", items = items, opts = opts or {} }
|
|
end,
|
|
}
|
|
local screensStub = {
|
|
push = function(_, id, species)
|
|
pushed[#pushed + 1] = { kind = "screen", id = id, species = species }
|
|
end,
|
|
}
|
|
|
|
local fakeGame = {
|
|
data = Data,
|
|
save = SaveData.newGame(),
|
|
stack = stackStub,
|
|
}
|
|
check(setUpvalue(OW.billsHousePC, "TextBox", textBoxStub), "TextBox upvalue")
|
|
check(setUpvalue(OW.billsHousePC, "Game", fakeGame), "Game upvalue for PC")
|
|
-- Screens is only referenced from billsHousePokemonList
|
|
check(setUpvalue(OW.billsHousePokemonList, "Screens", screensStub),
|
|
"Screens upvalue on pokemon list")
|
|
check(setUpvalue(OW.billsHousePokemonList, "Game", fakeGame),
|
|
"Game upvalue on pokemon list")
|
|
check(setUpvalue(OW.billsHousePokemonList, "TextBox", textBoxStub),
|
|
"TextBox upvalue on pokemon list")
|
|
|
|
-- Menu is required inside billsHousePokemonList; stub via package.loaded
|
|
local realMenu = package.loaded["src.ui.Menu"]
|
|
package.loaded["src.ui.Menu"] = menuStub
|
|
|
|
local fakeSelf = setmetatable({
|
|
queueScript = function() end,
|
|
billsHouseBillExits = function() end,
|
|
}, { __index = OW })
|
|
|
|
local function resetFlags()
|
|
fakeGame.save = SaveData.newGame()
|
|
setUpvalue(OW.billsHousePC, "Game", fakeGame)
|
|
pushed = {}
|
|
end
|
|
|
|
local function lastPush()
|
|
return pushed[#pushed]
|
|
end
|
|
|
|
local function runPC()
|
|
pushed = {}
|
|
fakeSelf:billsHousePC()
|
|
end
|
|
|
|
-- === 1) default: teleporter monitor text ===
|
|
resetFlags()
|
|
runPC()
|
|
eq(lastPush() and lastPush().kind, "text", "default PC is a text box")
|
|
check(tostring(lastPush().text):find("TELEPORTER", 1, true)
|
|
or tostring(lastPush().text):find("displayed on the", 1, true),
|
|
"default PC shows monitor text")
|
|
|
|
-- === 2) Bill in machine, separator not used yet: initiated text ===
|
|
resetFlags()
|
|
fakeGame.save.flags.EVENT_BILL_SAID_USE_CELL_SEPARATOR = true
|
|
local musicStopped = false
|
|
local realMusic = package.loaded["src.core.Music"]
|
|
package.loaded["src.core.Music"] = {
|
|
stop = function() musicStopped = true end,
|
|
playMap = function() end,
|
|
}
|
|
local realSound = package.loaded["src.core.Sound"]
|
|
package.loaded["src.core.Sound"] = {
|
|
play = function() end,
|
|
playCry = function() end,
|
|
}
|
|
runPC()
|
|
check(musicStopped, "cell separator stops map music")
|
|
check(tostring(lastPush().text):find("Cell", 1, true)
|
|
or tostring(lastPush().text):find("TELEPORTER", 1, true),
|
|
"separator path prints initiated text")
|
|
check(fakeGame.save.flags.EVENT_USED_CELL_SEPARATOR_ON_BILL,
|
|
"separator path sets EVENT_USED_CELL_SEPARATOR_ON_BILL")
|
|
|
|
-- === 3) after separator, before leaving: monitor text again ===
|
|
resetFlags()
|
|
fakeGame.save.flags.EVENT_BILL_SAID_USE_CELL_SEPARATOR = true
|
|
fakeGame.save.flags.EVENT_USED_CELL_SEPARATOR_ON_BILL = true
|
|
fakeGame.save.flags.EVENT_MET_BILL = true
|
|
runPC()
|
|
check(tostring(lastPush().text):find("TELEPORTER", 1, true)
|
|
or tostring(lastPush().text):find("displayed on the", 1, true),
|
|
"post-separator pre-leave PC shows monitor text")
|
|
|
|
-- === 4) after leaving: Eevee collection menu ===
|
|
resetFlags()
|
|
fakeGame.save.flags.EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING = true
|
|
runPC()
|
|
local menu
|
|
for _, p in ipairs(pushed) do
|
|
if p.kind == "menu" then menu = p break end
|
|
end
|
|
check(menu ~= nil, "post-leave PC opens a menu")
|
|
local labels = {}
|
|
for _, item in ipairs(menu.items) do labels[#labels + 1] = item.label end
|
|
eq(table.concat(labels, ","), "EEVEE,FLAREON,JOLTEON,VAPOREON,CANCEL",
|
|
"Eevee collection lists the four mons + CANCEL")
|
|
|
|
-- selecting EEVEE marks seen and opens DexEntryMenu without closing list
|
|
fakeGame.save.pokedex = { seen = {}, owned = {} }
|
|
menu.items[1].onSelect()
|
|
check(fakeGame.save.pokedex.seen.EEVEE, "viewing marks EEVEE seen")
|
|
local dexPush
|
|
for i = #pushed, 1, -1 do
|
|
if pushed[i].kind == "screen" then dexPush = pushed[i] break end
|
|
end
|
|
eq(dexPush and dexPush.id, "DexEntryMenu", "selection opens DexEntryMenu")
|
|
eq(dexPush and dexPush.species, "EEVEE", "DexEntryMenu gets EEVEE")
|
|
check(menu.items[1].keepOpen, "dex pick keeps the list open")
|
|
|
|
-- === 5) Route25ToggleBillsScript ===
|
|
local toggles = {}
|
|
local Commands = require("src.script.Commands")
|
|
local realHide, realShow = Commands.hide_object, Commands.show_object
|
|
Commands.hide_object = function(_, mapId, name)
|
|
toggles[mapId .. ":" .. name] = false
|
|
end
|
|
Commands.show_object = function(_, mapId, name)
|
|
toggles[mapId .. ":" .. name] = true
|
|
end
|
|
|
|
local function runRoute25(flags)
|
|
toggles = {}
|
|
local save = SaveData.newGame()
|
|
for k, v in pairs(flags) do save.flags[k] = v end
|
|
story.ROUTE_25.onEnter({ save = save }, {})
|
|
return save.flags, toggles
|
|
end
|
|
|
|
local f, t = runRoute25({})
|
|
check(not f.EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING,
|
|
"no leave flag before meeting Bill")
|
|
check(t["BILLS_HOUSE:BILLSHOUSE_BILL_POKEMON"] == true,
|
|
"mid-quest leave restores monster Bill")
|
|
check(f.EVENT_BILL_SAID_USE_CELL_SEPARATOR == nil,
|
|
"mid-quest leave clears separator arming flag")
|
|
|
|
f, t = runRoute25({ EVENT_MET_BILL_2 = true })
|
|
check(not f.EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING,
|
|
"MET_BILL_2 without ticket does not arm leave flag")
|
|
|
|
f, t = runRoute25({
|
|
EVENT_MET_BILL_2 = true,
|
|
EVENT_GOT_SS_TICKET = true,
|
|
})
|
|
check(f.EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING,
|
|
"ticket + MET_BILL_2 arms EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING")
|
|
eq(t["BILLS_HOUSE:BILLSHOUSE_BILL1"], false, "hides SS-Ticket Bill")
|
|
eq(t["BILLS_HOUSE:BILLSHOUSE_BILL2"], true, "shows rare-POKéMON Bill")
|
|
eq(t["ROUTE_24:ROUTE24_COOLTRAINER_M1"], false, "hides nugget-bridge guy")
|
|
|
|
f, t = runRoute25({
|
|
EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING = true,
|
|
EVENT_MET_BILL_2 = true,
|
|
EVENT_GOT_SS_TICKET = true,
|
|
})
|
|
eq(next(t), nil, "already-left Route 25 enter is a no-op")
|
|
|
|
Commands.hide_object = realHide
|
|
Commands.show_object = realShow
|
|
package.loaded["src.ui.Menu"] = realMenu
|
|
if realMusic ~= nil then package.loaded["src.core.Music"] = realMusic end
|
|
if realSound ~= nil then package.loaded["src.core.Sound"] = realSound end
|
|
|
|
S.finish()
|