Files
gen1recomp/tests/parity_E.lua
T

284 lines
13 KiB
Lua

-- Parity test, Workstream E.
-- Self-contained: run via `luajit tests/parity_E.lua`; also dofile'd by
-- tests/run_tests.lua's aggregator.
--
-- Covers: Cinnabar Lab fossil revival delay -- scripts/
-- CinnabarLabFossilRoom.asm (CinnabarLabFossilRoomScientist1Text),
-- engine/events/cinnabar_lab.asm (GiveFossilToCinnabarLab), and
-- scripts/CinnabarIsland.asm line 6 (ResetEvent
-- EVENT_LAB_STILL_REVIVING_FOSSIL on every map load). Deposit a fossil
-- through the fossil-select menu (every carried fossil in FossilsList
-- order, Yes/No confirm, ComeAgainText on either cancel) -> pending for
-- the rest of the visit -> ready once CINNABAR_ISLAND's onEnter has run
-- again -> grant the mon and reset the quest, ported in
-- data/scripts/story2.lua (the talk handler) and data/scripts/story5.lua
-- (M.CINNABAR_ISLAND.onEnter).
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 E")
local check, eq = S.check, S.eq
-- The real TextBox needs a loaded font plus frame-stepped input to type
-- and dismiss pages -- unrelated to what this workstream verifies (flag /
-- inventory / party state transitions). Stub it to fire its onDone
-- callback immediately, exactly like the real box does once the player
-- has mashed through it, and record the shown strings for the
-- text-content assertions below. opts.choice boxes (SeesFossilText's
-- YesNoChoice) resolve with the scripted `choiceAnswer`. Restored at
-- the end of this file so suites that dofile after this one (the
-- run_tests.lua aggregator) still get the real TextBox.
local realTextBox = package.loaded["src.render.TextBox"]
local shownTexts = {}
local choiceAnswer = true -- scripted YES/NO for opts.choice boxes
local realSoundOpts = require("src.render.TextBox").soundOpts
package.loaded["src.render.TextBox"] = {
new = function(game, text, onDone, opts)
table.insert(shownTexts, text)
return { text = text, box = true, onDone = onDone,
stay = opts and opts.stay,
choice = opts and opts.choice }
end,
soundOpts = realSoundOpts,
}
-- ../pokered/scripts/CinnabarLabFossilRoom.asm:74-83
local realCommands = package.loaded["src.script.Commands"]
package.loaded["src.script.Commands"] = nil
local ScriptCommands = require("src.script.Commands")
local function fakeRunner(game, ow)
local runner = {}
runner.resume = function() end
runner.yield = function() end
runner.isRunning = function() return false end
function runner:run(script, extra)
local ctx = { game = game, save = game.save, overworld = ow, runner = runner }
for k, v in pairs(extra or {}) do ctx[k] = v end
local labels = {}
for i, row in ipairs(script) do
if row[1] == "label" then labels[row[2]] = i end
end
local pc = 1
while pc <= #script do
local row = script[pc]
local fn = ScriptCommands[row[1]]
local target = fn and fn(ctx, row[2], row[3], row[4], row[5])
pc = target and (labels[target] or (#script + 1)) or (pc + 1)
end
if ctx.onDone then ctx.onDone() end
end
return runner
end
-- The fossil-select menu (GiveFossilToCinnabarLab's bordered list,
-- src/ui/Menu.lua) selects from frame-stepped input; stub it to pick
-- the scripted entry immediately (or back out with B when menuPick is
-- "cancel"), recording the labels for the order assertions.
local realMenu = package.loaded["src.ui.Menu"]
local menuPick = 1
local menuLabels = nil
local menuUnder, seesUnderMenu = nil, nil
package.loaded["src.ui.Menu"] = {
new = function(game, items, opts)
menuLabels = {}
for i, it in ipairs(items) do menuLabels[i] = it.label end
return { menu = true, items = items, opts = opts or {} }
end,
}
local SaveData = require("src.core.SaveData")
local story2 = require("data.scripts.story2")
local story5 = require("data.scripts.story5")
check(story2.CINNABAR_LAB_FOSSIL_ROOM ~= nil, "CINNABAR_LAB_FOSSIL_ROOM registered")
check(story2.CINNABAR_LAB_FOSSIL_ROOM
and story2.CINNABAR_LAB_FOSSIL_ROOM.talk.TEXT_CINNABARLABFOSSILROOM_SCIENTIST1 ~= nil,
"scientist1 talk handler registered")
check(story5.CINNABAR_ISLAND ~= nil and story5.CINNABAR_ISLAND.onEnter ~= nil,
"CINNABAR_ISLAND.onEnter registered")
local talkScientist1 = story2.CINNABAR_LAB_FOSSIL_ROOM.talk.TEXT_CINNABARLABFOSSILROOM_SCIENTIST1
local function newStack()
local stack = { states = {} }
function stack:pop() return table.remove(self.states) end
function stack:top() return self.states[#self.states] end
function stack:push(state)
table.insert(self.states, state)
if state.menu then
menuUnder = self.states[#self.states - 1]
local pick, opts = menuPick, state.opts
if pick == "cancel" then
if not opts.keepOnCancel then self:pop() end
if opts.onCancel then opts.onCancel() end
else
local item = state.items[pick]
if not item.keepOpen then self:pop() end
if item.onSelect then item.onSelect() end
end
elseif state.box then
if state.stay then
if state.stay.onShown then state.stay.onShown() end
elseif state.choice then
local below = self.states[#self.states - 1]
seesUnderMenu = (below and below.menu) or false
self:pop()
state.choice(choiceAnswer)
else
self:pop()
if state.onDone then state.onDone() end
end
end
end
return stack
end
local function newGame()
local save = SaveData.newGame()
local game = { data = Data, save = save, stack = newStack() }
return game
end
local function talk(game)
shownTexts = {}
game.stack.states = {}
local doneCalled = false
local ow = {}
ow.runner = fakeRunner(game, ow)
talkScientist1(game, ow, nil, function() doneCalled = true end)
return doneCalled
end
-- === 1) no fossil in inventory: no-fossils text, no flags set ===
do
local game = newGame()
check(talk(game), "no-fossil talk completes")
check(not game.save.flags.EVENT_GAVE_FOSSIL_TO_LAB, "no fossil: GAVE_FOSSIL_TO_LAB not set")
check(not game.save.flags.EVENT_LAB_STILL_REVIVING_FOSSIL, "no fossil: STILL_REVIVING not set")
eq(#game.save.party, 0, "no fossil: party unchanged")
eq(shownTexts[#shownTexts], "No! Is too bad!{DONE}", "no fossil shows NoFossilsText")
end
-- === 2)-5) full deposit -> pending -> re-entry -> grant cycle ===
do
local game = newGame()
game.save.inventory.OLD_AMBER = 1
-- 2) depositing OLD_AMBER through the menu + YES confirm: cleared
-- from the bag, quest flags set, AERODACTYL not granted yet (same
-- conversation as the deposit).
menuPick, choiceAnswer = 1, true
check(talk(game), "deposit talk completes")
eq(menuLabels and #menuLabels, 1, "fossil menu lists the one carried fossil")
eq(menuLabels and menuLabels[1], "OLD AMBER", "fossil menu shows the item name")
-- under the TextBoxBorder 0,0 menu (engine/events/cinnabar_lab.asm:22-24)
check(menuUnder ~= nil and menuUnder.box == true,
"the intro dialogue box is still on the stack under the fossil menu")
eq(menuUnder and menuUnder.text, shownTexts[1],
"and it is the intro text, not a box pushed after it")
-- SeesFossilText prints below it (engine/events/cinnabar_lab.asm:55-67)
eq(seesUnderMenu, true, "the menu box is still up when SeesFossilText prints")
eq(#game.stack.states, 0, "and both boxes are popped exactly once when it ends")
local sees
for _, s in ipairs(shownTexts) do
if s:find("Resurrection") then sees = s end
end
check(sees and sees:find("OLD AMBER", 1, true) and sees:find("AERODACTYL", 1, true),
"SeesFossilText names both the fossil (wNameBuffer) and the mon (wStringBuffer)")
eq(game.save.inventory.OLD_AMBER, nil, "OLD_AMBER cleared from inventory on deposit")
check(game.save.flags.EVENT_GAVE_FOSSIL_TO_LAB == true, "GAVE_FOSSIL_TO_LAB set on deposit")
check(game.save.flags.EVENT_LAB_STILL_REVIVING_FOSSIL == true, "STILL_REVIVING set on deposit")
eq(#game.save.party, 0, "AERODACTYL not granted in the deposit conversation")
eq(game.save.labFossilMon, "AERODACTYL", "pending species (AERODACTYL) remembered")
-- 3) re-talking within the same visit: still pending, no grant
check(talk(game), "same-visit re-talk completes")
check(game.save.flags.EVENT_GAVE_FOSSIL_TO_LAB == true,
"same-visit re-talk: GAVE_FOSSIL_TO_LAB still set")
check(game.save.flags.EVENT_LAB_STILL_REVIVING_FOSSIL == true,
"same-visit re-talk: STILL_REVIVING still set")
eq(#game.save.party, 0, "same-visit re-talk: still no mon granted")
eq(shownTexts[#shownTexts], "I take a little\ntime!\fYou go for walk a\nlittle while!{DONE}",
"same-visit re-talk shows GoForAWalkText")
-- 4) leaving and re-entering CINNABAR_ISLAND (its onEnter) clears
-- STILL_REVIVING but leaves GAVE_FOSSIL_TO_LAB set
story5.CINNABAR_ISLAND.onEnter(game, {})
check(game.save.flags.EVENT_GAVE_FOSSIL_TO_LAB == true,
"CINNABAR_ISLAND onEnter: GAVE_FOSSIL_TO_LAB survives the reload")
check(not game.save.flags.EVENT_LAB_STILL_REVIVING_FOSSIL,
"CINNABAR_ISLAND onEnter clears STILL_REVIVING")
-- 5) talking again grants AERODACTYL at level 30 and resets the whole
-- quest (all three EVENT_ flags, and the transient species field) so
-- a second fossil can be deposited later
check(talk(game), "ready talk completes")
eq(#game.save.party, 1, "AERODACTYL granted into the party after re-entry")
eq(game.save.party[1] and game.save.party[1].species, "AERODACTYL",
"granted species is AERODACTYL")
eq(game.save.party[1] and game.save.party[1].level, 30, "AERODACTYL granted at level 30")
check(not game.save.flags.EVENT_GAVE_FOSSIL_TO_LAB, "grant resets GAVE_FOSSIL_TO_LAB")
check(not game.save.flags.EVENT_LAB_STILL_REVIVING_FOSSIL, "grant resets STILL_REVIVING")
check(not game.save.flags.EVENT_LAB_HANDING_OVER_FOSSIL_MON, "grant resets HANDING_OVER_FOSSIL_MON")
eq(game.save.labFossilMon, nil, "pending species cleared after grant")
-- GivePokemon prints for itself (engine/events/give_pokemon.asm:52-71)
local sawGot, sawNick = false, false
for _, s in ipairs(shownTexts) do
if s:find("got", 1, true) and s:find("AERODACTYL", 1, true) then sawGot = true end
if s:find("nickname", 1, true) then sawNick = true end
end
check(sawGot, "the grant prints _GotMonText")
check(sawNick, "and asks for a nickname (AddPartyMon -> AskName)")
end
-- === the menu lists every carried fossil in FossilsList scan order
-- (DOME_FOSSIL, HELIX_FOSSIL, OLD_AMBER) and deposits the chosen one ===
do
local game = newGame()
game.save.inventory.OLD_AMBER = 1
game.save.inventory.HELIX_FOSSIL = 1
menuPick, choiceAnswer = 1, true
check(talk(game), "multi-fossil deposit talk completes")
eq(menuLabels and #menuLabels, 2, "menu lists both carried fossils")
eq(menuLabels and menuLabels[1], "HELIX FOSSIL", "HELIX FOSSIL listed first (FossilsList order)")
eq(menuLabels and menuLabels[2], "OLD AMBER", "OLD AMBER listed second")
eq(game.save.labFossilMon, "OMANYTE", "choosing HELIX FOSSIL deposits it")
eq(game.save.inventory.HELIX_FOSSIL, nil, "HELIX_FOSSIL cleared from inventory")
eq(game.save.inventory.OLD_AMBER, 1, "OLD_AMBER left untouched for a later visit")
end
-- === backing out of the menu with B: ComeAgainText, nothing taken
-- (GiveFossilToCinnabarLab .cancelledGivingFossil) ===
do
local game = newGame()
game.save.inventory.DOME_FOSSIL = 1
menuPick = "cancel"
check(talk(game), "menu-cancel talk completes")
eq(shownTexts[#shownTexts], "Aiyah! You come\nagain!{DONE}", "menu B-out shows ComeAgainText")
-- (engine/events/cinnabar_lab.asm:70-73)
eq(#game.stack.states, 0, "the B-out path pops the menu and the intro box too")
eq(game.save.inventory.DOME_FOSSIL, 1, "fossil kept after menu cancel")
check(not game.save.flags.EVENT_GAVE_FOSSIL_TO_LAB, "menu cancel sets no quest flags")
eq(game.save.labFossilMon, nil, "menu cancel leaves no pending species")
end
-- === answering NO on the SeesFossilText confirm: same cancel path ===
do
local game = newGame()
game.save.inventory.DOME_FOSSIL = 1
menuPick, choiceAnswer = 1, false
check(talk(game), "confirm-NO talk completes")
eq(shownTexts[#shownTexts], "Aiyah! You come\nagain!{DONE}", "NO on the confirm shows ComeAgainText")
eq(#game.stack.states, 0, "the NO path pops the menu and the intro box too")
eq(game.save.inventory.DOME_FOSSIL, 1, "fossil kept after NO")
check(not game.save.flags.EVENT_GAVE_FOSSIL_TO_LAB, "NO sets no quest flags")
eq(game.save.labFossilMon, nil, "NO leaves no pending species")
end
package.loaded["src.render.TextBox"] = realTextBox
package.loaded["src.ui.Menu"] = realMenu
package.loaded["src.script.Commands"] = realCommands
S.finish()