mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
172 lines
6.3 KiB
Lua
172 lines
6.3 KiB
Lua
-- The end of the game has to hand the controller back.
|
|
--
|
|
-- GOLD_CACHE=".../gold" luajit tests/gen2_hof_continue_test.lua
|
|
--
|
|
-- Script_halloffame ends on ReturnFromCredits (engine/overworld/
|
|
-- scripting.asm): Script_endall plus MAPSTATUS_DONE, which returns out of
|
|
-- OverworldLoop entirely. FinishContinueFunction (engine/menus/
|
|
-- intro_menu.asm) then reads wSpawnAfterChampion:
|
|
--
|
|
-- SPAWN_RED SpawnAfterRed: wDefaultSpawnpoint = SPAWN_MT_SILVER,
|
|
-- clear the byte, MAPSETUP_WARP, loop back into the
|
|
-- overworld -- play resumes outside Silver Cave.
|
|
-- anything else `jp Reset` -- the champion's credits end on the title
|
|
-- screen, and the induction's SaveGameData already put
|
|
-- SPAWN_LANCE in the save.
|
|
--
|
|
-- and Continue's own `cp SPAWN_LANCE / jr z, .SpawnAfterE4` consumes the
|
|
-- saved byte BEFORE the saved position is honoured, spawning at New Bark
|
|
-- Town. Without either half the champion was left standing in HALL_OF_FAME,
|
|
-- whose only exit is the sealed Lance's room door: a soft lock at the moment
|
|
-- of victory.
|
|
--
|
|
-- These drive the shipped World:hallOfFame / World:credits /
|
|
-- World:consumePostGameSpawn against the real cache; only the screens are
|
|
-- registry fakes (they are pure presentation, and Screens.push resolves the
|
|
-- registry first for exactly this reason).
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 hof continue")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local World = require("src.world.gen2.World")
|
|
local HallOfFame = require("src.core.gen2.HallOfFame")
|
|
|
|
local cache = os.getenv("GOLD_CACHE")
|
|
if not cache then
|
|
local home = os.getenv("HOME") or ""
|
|
cache = home .. "/Library/Application Support/LOVE/gold-dev/gold"
|
|
end
|
|
local probe = io.open(cache .. "/data/generated/landmarks.lua", "r")
|
|
if not probe then
|
|
check(true, "gold cache absent (SKIP)")
|
|
S.finish()
|
|
return
|
|
end
|
|
probe:close()
|
|
|
|
local function loadLua(rel) return assert(loadfile(cache .. "/" .. rel))() end
|
|
local maps = loadLua("data/generated/maps.lua")
|
|
local landmarks = loadLua("data/generated/landmarks.lua")
|
|
|
|
-- One registry serves the whole file; Screens caches factories per id, so the
|
|
-- fakes are registered once and the recorder is swapped per test.
|
|
local pushes
|
|
local registry = {}
|
|
for _, id in ipairs({ "Gen2HallOfFame", "Gen2Credits" }) do
|
|
registry[id] = { new = function(_game, opts)
|
|
pushes[#pushes + 1] = { id = id, opts = opts }
|
|
return { screenId = id }
|
|
end }
|
|
end
|
|
|
|
local function makeStack()
|
|
local stack = { items = {} }
|
|
function stack:push(inst) self.items[#self.items + 1] = inst end
|
|
function stack:pop()
|
|
local top = self.items[#self.items]
|
|
self.items[#self.items] = nil
|
|
return top
|
|
end
|
|
return stack
|
|
end
|
|
|
|
local function makeWorld()
|
|
pushes = {}
|
|
local game = {
|
|
data = { audio = {}, screens = registry },
|
|
save = {
|
|
player = { name = "GOLD" },
|
|
party = { { species = "TYPHLOSION", level = 50, hp = 120,
|
|
otId = 33333 } },
|
|
},
|
|
stack = makeStack(),
|
|
titled = 0,
|
|
}
|
|
game.returnToTitle = function(g) g.titled = g.titled + 1 end
|
|
local w = World.new(game)
|
|
w.maps = maps
|
|
w.landmarks = landmarks
|
|
w.loaded = nil
|
|
w.setMap = function(self, id, x, y, facing)
|
|
self.loaded = { id = id, x = x, y = y, facing = facing }
|
|
return true
|
|
end
|
|
return w, game
|
|
end
|
|
|
|
-- ---- the champion's ending: induct, roll, reset ---------------------------
|
|
do
|
|
local w, game = makeWorld()
|
|
local scriptResumed = false
|
|
check(w:hallOfFame(function() scriptResumed = true end),
|
|
"the halloffame command takes the screen")
|
|
|
|
-- The ceremony's bookkeeping ran before the screen came up.
|
|
eq(game.save.spawnAfterChampion, HallOfFame.SPAWN_LANCE,
|
|
"induction writes wSpawnAfterChampion = SPAWN_LANCE")
|
|
eq(HallOfFame.count(game.save), 1, "and bumps the win count")
|
|
eq(pushes[1].id, "Gen2HallOfFame", "the roster ceremony is up first")
|
|
|
|
-- `pop af / jp Credits`.
|
|
pushes[1].opts.onDone()
|
|
eq(pushes[2] and pushes[2].id, "Gen2Credits", "the roll follows the ceremony")
|
|
eq(pushes[2].opts.allowSkip, false,
|
|
"a first-time champion cannot hurry the roll (the PRE-induction flags)")
|
|
|
|
-- The credits end: the script resumes (and immediately ends, as
|
|
-- ReturnFromCredits' Script_endall does), and then the reset.
|
|
eq(game.titled, 0, "no reset while the roll is still up")
|
|
pushes[2].opts.onDone()
|
|
check(scriptResumed, "the script got its resume before the teardown")
|
|
eq(game.titled, 1, "`jp Reset`: the credits end on the title screen")
|
|
check(w.loaded == nil, "and no warp happened in this session")
|
|
end
|
|
|
|
-- ---- CONTINUE consumes the saved spawn ------------------------------------
|
|
do
|
|
local w, game = makeWorld()
|
|
HallOfFame.induct(game.save, game.save.party)
|
|
eq(game.save.spawnAfterChampion, HallOfFame.SPAWN_LANCE, "the save is armed")
|
|
|
|
local spawn = w:consumePostGameSpawn()
|
|
check(spawn ~= nil, "Continue's .SpawnAfterE4 arm fires")
|
|
eq(spawn.map, "NEW_BARK_TOWN", "SPAWN_NEW_BARK is New Bark Town")
|
|
eq(spawn.x, 13, "at the spawn point's x")
|
|
eq(spawn.y, 6, "and y")
|
|
eq(game.save.spawnAfterChampion, nil,
|
|
"PostCreditsSpawn zeroes the byte on the way")
|
|
check(w:consumePostGameSpawn() == nil, "so the next load is ordinary")
|
|
end
|
|
|
|
-- ---- the Red ending: no reset, straight back to Silver Cave ---------------
|
|
do
|
|
local w, game = makeWorld()
|
|
local scriptResumed = false
|
|
check(w:credits(function() scriptResumed = true end),
|
|
"the credits command takes the screen")
|
|
eq(game.save.spawnAfterChampion, HallOfFame.SPAWN_RED,
|
|
"RedCredits writes wSpawnAfterChampion = SPAWN_RED")
|
|
eq(pushes[1].id, "Gen2Credits", "and goes straight to the roll")
|
|
|
|
pushes[1].opts.onDone()
|
|
check(scriptResumed, "the script got its resume")
|
|
eq(game.titled, 0, ".AfterRed does not reset")
|
|
check(w.loaded ~= nil, "it re-enters the overworld instead")
|
|
eq(w.loaded.id, "SILVER_CAVE_OUTSIDE", "outside Silver Cave")
|
|
eq(w.loaded.x, 23, "at SPAWN_MT_SILVER's x")
|
|
eq(w.loaded.y, 20, "and y")
|
|
eq(game.save.spawnAfterChampion, nil, "with the byte consumed in session")
|
|
end
|
|
|
|
-- ---- an ordinary continue is untouched ------------------------------------
|
|
do
|
|
local w, game = makeWorld()
|
|
game.save.position = { map = "GOLDENROD_CITY", x = 5, y = 5 }
|
|
check(w:consumePostGameSpawn() == nil,
|
|
"no pending spawn means the saved position stays in charge")
|
|
eq(game.save.position.map, "GOLDENROD_CITY", "and is not disturbed")
|
|
end
|
|
|
|
S.finish()
|