mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
181 lines
6.5 KiB
Lua
181 lines
6.5 KiB
Lua
-- #2171: the intro's palette rotations and ShrinkPlayer's player icon.
|
|
-- ../pokecrystal/home/fade.asm:22-101
|
|
-- ../pokecrystal/engine/menus/intro_menu.asm:627-700, :802-950
|
|
--
|
|
-- POKEPORT_DRIVER=tests/drivers/gen2_intro_fades_2171.lua \
|
|
-- POKEPORT_IDENTITY=crystal-sep04 POKEPORT_VERSION=crystal \
|
|
-- POKEPORT_TOUCH=0 POKEPORT_SHOT_DIR=<dir> love .
|
|
local U = require("tests.drivers.util")
|
|
|
|
local GenderSelect = require("src.ui.gen2.GenderSelect")
|
|
local InitClock = require("src.ui.gen2.InitClock")
|
|
local MainMenu = require("src.ui.gen2.MainMenu")
|
|
local NamePick = require("src.ui.gen2.NamePick")
|
|
local NamingScreen = require("src.ui.gen2.NamingScreen")
|
|
local OakSpeech = require("src.ui.gen2.OakSpeech")
|
|
|
|
local GameVersion = require("src.core.GameVersion")
|
|
|
|
return function(game)
|
|
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/2171-fades"
|
|
local log = io.open(out .. "/driver.log", "w")
|
|
|
|
local function say(line)
|
|
print("[driver] " .. line)
|
|
if log then log:write(line .. "\n"); log:flush() end
|
|
end
|
|
|
|
local function bail(reason)
|
|
say("FAIL " .. reason)
|
|
if log then log:close() end
|
|
love.event.quit(1)
|
|
error(reason, 0)
|
|
end
|
|
|
|
local function top() return game.stack:top() end
|
|
local function isA(class)
|
|
local state = top()
|
|
return state ~= nil and getmetatable(state) == class
|
|
end
|
|
|
|
local function tap(button, frames)
|
|
game.input.pressQueue[#game.input.pressQueue + 1] = button
|
|
game.input.state[button] = true
|
|
U.wait(2)
|
|
game.input.state[button] = false
|
|
U.wait(frames or 4)
|
|
end
|
|
|
|
local function waitFor(label, predicate, frames)
|
|
for _ = 1, frames or 900 do
|
|
if predicate() then return end
|
|
U.wait(1)
|
|
end
|
|
bail(("stalled waiting for %s (top is %s)"):format(label, tostring(top())))
|
|
end
|
|
|
|
-- The fades block input, so the frame counter is deterministic: wait for the
|
|
-- rotation to reach `at`, then shoot. U.shot costs a couple of logic ticks
|
|
-- of its own, which is why nothing here counts frames across a shot.
|
|
local function shootFade(class, kind, at, name, label)
|
|
waitFor(("%s %s frame %d"):format(label, kind, at), function()
|
|
if not isA(class) then return false end
|
|
local f = top().fade
|
|
return f ~= nil and f.kind == kind and f.frame >= at
|
|
end, 900)
|
|
if not U.shot(game, out .. "/" .. name) then bail("no shot " .. name) end
|
|
say("OK " .. label .. " -> " .. name)
|
|
end
|
|
|
|
local version = tostring(GameVersion.get())
|
|
say("version=" .. version .. " engine=" .. tostring(GameVersion.engine()))
|
|
|
|
for _ = 1, 900 do
|
|
if isA(MainMenu) then break end
|
|
tap("start", 2)
|
|
end
|
|
if not isA(MainMenu) then
|
|
for _ = 1, 400 do
|
|
if isA(MainMenu) then break end
|
|
tap("a", 2)
|
|
end
|
|
end
|
|
waitFor("the intro menu", function() return isA(MainMenu) end)
|
|
local menu = top()
|
|
for i, item in ipairs(menu.list.items) do
|
|
if item.value == "new" then menu.list.index = i end
|
|
end
|
|
tap("a")
|
|
|
|
-- --------------------------------------------------- the NEW GAME seam
|
|
U.wait(4)
|
|
if isA(GenderSelect) then
|
|
-- Crystal: InitGender answers, and InitClock's RotateFourPalettesLeft runs
|
|
-- with the gender screen still on the LCD.
|
|
-- pokecrystal/engine/menus/init_gender.asm:29-34
|
|
waitFor("the gender menu to open",
|
|
function() return isA(GenderSelect) and top():menuOpen() end, 300)
|
|
tap("a")
|
|
shootFade(GenderSelect, "outBlack", 20,
|
|
"2171_04_gender_to_clock_mid.png", "gender -> clock, half faded out")
|
|
waitFor("the clock screen", function() return isA(InitClock) end, 300)
|
|
shootFade(InitClock, "inBlack", 22,
|
|
"2171_04_clock_fadein_mid.png", "the clock coming back from black")
|
|
else
|
|
-- Gold and Silver: NewGame_ClearTilemapEtc blanked the screen first, so
|
|
-- RotateFourPalettesLeft runs over a cleared tilemap and has nothing on it
|
|
-- to photograph; the rotation coming back is the judgeable half.
|
|
waitFor("the clock screen", function() return isA(InitClock) end, 300)
|
|
waitFor("RotateFourPalettesLeft over the cleared tilemap", function()
|
|
return isA(InitClock) and top().blank and top().fade
|
|
and top().fade.kind == "outBlack"
|
|
end, 300)
|
|
shootFade(InitClock, "inBlack", 22,
|
|
"2171_05_newgame_to_clock_mid.png", "NEW GAME -> clock, back from black")
|
|
end
|
|
|
|
waitFor("the clock screen to settle",
|
|
function() return isA(InitClock) and top().fade == nil end, 300)
|
|
|
|
-- --------------------------------------------------- the clock -> Oak seam
|
|
for _ = 1, 120 do
|
|
if not isA(InitClock) or top().fade then break end
|
|
tap("a", 3)
|
|
end
|
|
if not isA(InitClock) then bail("the clock left before its fade") end
|
|
shootFade(InitClock, "outBlack", 14,
|
|
"2171_03_clock_to_oak_mid.png", "clock -> Oak, half faded out")
|
|
|
|
waitFor("the Oak speech", function() return isA(OakSpeech) end, 400)
|
|
say("OK the speech is up")
|
|
|
|
-- --------------------------------------------------- the shrink
|
|
local shotShrink, shotWhite = false, false
|
|
for _ = 1, 900 do
|
|
if game.phase == "play" then break end
|
|
if isA(NamePick) and top().slide == nil then
|
|
top().cursor = 1
|
|
tap("a")
|
|
waitFor("the naming screen", function() return isA(NamingScreen) end, 240)
|
|
local naming = top()
|
|
naming.row = naming:bottomRow()
|
|
naming.col = 6
|
|
tap("a")
|
|
elseif isA(OakSpeech) then
|
|
local speech = top()
|
|
if not shotShrink and speech.shrink
|
|
and speech.shrink.frame >= OakSpeech.SHRINK_ICON + 12 then
|
|
if not U.shot(game, out .. "/2171_07_shrink_player_icon.png") then
|
|
bail("no shrink shot")
|
|
end
|
|
say("OK Intro_PlacePlayerSprite -> 2171_07_shrink_player_icon.png")
|
|
shotShrink = true
|
|
elseif shotShrink and not shotWhite and speech.fade
|
|
and speech.fade.kind == "outWhite" and speech.fade.frame >= 10 then
|
|
if not U.shot(game, out .. "/2171_08_post_shrink_fade.png") then
|
|
bail("no post-shrink shot")
|
|
end
|
|
say("OK RotateThreePalettesRight -> 2171_08_post_shrink_fade.png")
|
|
shotWhite = true
|
|
elseif speech.shrink == nil and speech.fade == nil then
|
|
tap("a", 2)
|
|
else
|
|
U.wait(1)
|
|
end
|
|
else
|
|
tap("a", 2)
|
|
end
|
|
end
|
|
|
|
if not shotShrink then bail("the shrink never put the player icon up") end
|
|
if not shotWhite then bail("the shrink never faded out to white") end
|
|
waitFor("the overworld", function()
|
|
return game.phase == "play" and game.world and game.world.map
|
|
end, 600)
|
|
say("OK reached " .. tostring(game.world.map.id))
|
|
|
|
say("PASS gen2 intro fades (" .. version .. ") in " .. out)
|
|
if log then log:close() end
|
|
love.event.quit(0)
|
|
end
|