mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
148 lines
5.6 KiB
Lua
148 lines
5.6 KiB
Lua
-- The end of the game, sampled: the Hall of Fame induction, the credits roll,
|
|
-- and the roster the PC shows afterwards.
|
|
--
|
|
-- POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_halloffame_shots.lua love .
|
|
-- POKEPORT_SHOT_DIR=/tmp/gold-hof (default)
|
|
--
|
|
-- Everything here is a cinematic, which is exactly what no assertion can
|
|
-- check: "does the backpic really sweep off to the left before the frontpic
|
|
-- comes back", "does the banner change mon on each CREDITS_SCENE", "does THE
|
|
-- END stay up after the last blank" are questions for eyes. So this stands
|
|
-- the real screens up on the real stack, lets them run at their own 60 Hz, and
|
|
-- lays each one out as a contact sheet.
|
|
--
|
|
-- Shots are named by the frame the screen has been running for and the phase
|
|
-- (or credits scene) it is in, so a file is directly comparable against
|
|
-- engine/events/halloffame.asm and engine/movie/credits.asm.
|
|
local U = require("tests.drivers.util")
|
|
|
|
local Core = require("src.core.gen2.HallOfFame")
|
|
local Credits = require("src.ui.gen2.Credits")
|
|
local HallOfFame = require("src.ui.gen2.HallOfFame")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
-- The induction is 292 frames a mon, so a six-mon party runs about 1900
|
|
-- frames; the credits are about 4400. Half a second apiece keeps both
|
|
-- readable without an unusable number of files.
|
|
local INDUCT_INTERVAL = 20
|
|
local CREDITS_INTERVAL = 60
|
|
local LIMIT = 8000
|
|
|
|
return function(game)
|
|
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-hof"
|
|
|
|
U.wait(45)
|
|
assert(game.world and game.world.map, "gold world did not boot")
|
|
|
|
local save = game.save
|
|
local data = game.data
|
|
|
|
-- A champion's party. Mon.new is the ONE builder for a Gen 2 party member:
|
|
-- anything routed through Gen 1's Pokemon.new comes back with no moves,
|
|
-- because a Gen 2 moveset is levelMoves and Gen 1 reads level1Moves.
|
|
local roster = {
|
|
{ "TYPHLOSION", 50, "BLAZE" },
|
|
{ "LANTURN", 46, "SPARK" },
|
|
{ "AMPHAROS", 45, nil },
|
|
{ "UMBREON", 44, "DUSK" },
|
|
{ "SCIZOR", 43, nil },
|
|
{ "GYARADOS", 47, "RAGE" },
|
|
}
|
|
save.party = {}
|
|
for _, row in ipairs(roster) do
|
|
local mon = Mon.new(data, row[1], row[2], { nickname = row[3] })
|
|
if mon then
|
|
mon.otId = save.player and save.player.id or 12345
|
|
save.party[#save.party + 1] = mon
|
|
end
|
|
end
|
|
assert(#save.party > 0, "no party could be built from this cache")
|
|
save.player.name = save.player.name or "GOLD"
|
|
save.playTime = { hours = 42, minutes = 7, seconds = 0, frames = 0 }
|
|
|
|
-- ---- the induction ------------------------------------------------------
|
|
|
|
-- What the `halloffame` opcode does to the save before the screen opens.
|
|
-- `wasEntered` is the ALLOW_SKIPPING_CREDITS_F bit Credits wants: false the
|
|
-- first time, which is why a first-time champion cannot hurry the roll.
|
|
local entry, wasEntered = Core.induct(save, save.party)
|
|
U.log(("inducted: %d mon(s), win count %d, spawn %s")
|
|
:format(#entry.mons, entry.winCount, tostring(save.spawnAfterChampion)))
|
|
|
|
local inducted = false
|
|
local induction = HallOfFame.new(game, {
|
|
save = save, entry = entry,
|
|
onDone = function() inducted = true end,
|
|
})
|
|
game.stack:clear()
|
|
game.stack:push(induction)
|
|
|
|
local shots, phase = 0, nil
|
|
while not inducted and induction.frames < LIMIT do
|
|
U.wait(INDUCT_INTERVAL)
|
|
if induction.phase ~= phase then
|
|
phase = induction.phase
|
|
U.log(("hof phase %s at frame %d (mon %d, scx=%02x scy=%02x)")
|
|
:format(tostring(phase), induction.frames, induction.index,
|
|
induction.scx, induction.scy))
|
|
end
|
|
U.shot(game, ("%s/hof-%04d-%s.png")
|
|
:format(out, induction.frames, tostring(phase)))
|
|
shots = shots + 1
|
|
end
|
|
assert(inducted, "the induction never reached HOF_AnimatePlayerPic's end")
|
|
U.log(("%d induction shots over %d frames"):format(shots, induction.frames))
|
|
game.stack:pop()
|
|
|
|
-- ---- the credits --------------------------------------------------------
|
|
|
|
local rolled = false
|
|
local credits = Credits.new(game, {
|
|
allowSkip = wasEntered,
|
|
onDone = function() rolled = true end,
|
|
})
|
|
game.stack:clear()
|
|
game.stack:push(credits)
|
|
|
|
shots = 0
|
|
local scene = -1
|
|
while not credits.exiting and credits.frames < LIMIT do
|
|
U.wait(CREDITS_INTERVAL)
|
|
if credits.scene ~= scene then
|
|
scene = credits.scene
|
|
U.log(("credits scene %d at frame %d (pass %d, pos %d)")
|
|
:format(scene, credits.frames, credits.passes, credits.pos))
|
|
end
|
|
U.shot(game, ("%s/credits-%04d-scene%d.png")
|
|
:format(out, credits.frames, scene))
|
|
shots = shots + 1
|
|
end
|
|
assert(credits.exiting, "the credits script never reached CREDITS_END")
|
|
-- CREDITS_END only sets the exit flag; the screen waits on A, so the last
|
|
-- shot is THE END sitting there exactly as the player sees it.
|
|
U.wait(30)
|
|
U.shot(game, ("%s/credits-%04d-theend.png"):format(out, credits.frames))
|
|
U.log(("%d credits shots over %d frames, %d passes")
|
|
:format(shots + 1, credits.frames, credits.passes))
|
|
U.tap(game, "a")
|
|
U.wait(10)
|
|
assert(rolled, "A did not leave the credits once the exit flag was up")
|
|
game.stack:clear()
|
|
|
|
-- ---- the roster, as the PC shows it -------------------------------------
|
|
|
|
-- _HallOfFamePC over the row that was just written: A walks the team, and
|
|
-- the header is "-Time Famer" rather than "New Hall of Famer!".
|
|
local viewer = HallOfFame.new(game, {
|
|
mode = "view", save = save, onDone = function() end,
|
|
})
|
|
game.stack:push(viewer)
|
|
for index = 1, #entry.mons do
|
|
U.wait(10)
|
|
U.shot(game, ("%s/pc-%02d-%s.png")
|
|
:format(out, index, tostring((viewer:currentMon() or {}).species)))
|
|
U.tap(game, "a")
|
|
end
|
|
U.log("roster viewer shot for " .. #entry.mons .. " mon(s) in " .. out)
|
|
end
|