-- 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()