mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
253 lines
8.8 KiB
Lua
253 lines
8.8 KiB
Lua
-- Crystal's InitGender screen, end to end, and the six things that follow from
|
|
-- the answer. POKEPORT_GENDER picks the arm: "girl", "boy" or "none" (Gold,
|
|
-- which must never see the screen at all).
|
|
--
|
|
-- POKEPORT_IDENTITY=<sandbox> POKEPORT_GAME=crystal POKEPORT_BOOT_CINEMA=1 \
|
|
-- POKEPORT_GENDER=girl POKEPORT_SHOT_DIR=<dir> \
|
|
-- POKEPORT_DRIVER=tests/drivers/crystal_gender_shots.lua 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 TrainerCard = require("src.ui.gen2.TrainerCard")
|
|
|
|
local FieldMoves = require("src.world.gen2.FieldMoves")
|
|
local GameVersion = require("src.core.GameVersion")
|
|
local Screens = require("src.ui.Screens")
|
|
|
|
return function(game)
|
|
local want = os.getenv("POKEPORT_GENDER") or "girl"
|
|
local out = os.getenv("POKEPORT_SHOT_DIR") or ("/tmp/crystal-gender-" .. want)
|
|
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
|
|
|
|
local function eq(got, wanted, label)
|
|
if got ~= wanted then
|
|
bail(("%s: got %s, want %s"):format(label, tostring(got),
|
|
tostring(wanted)))
|
|
end
|
|
say("OK " .. label .. " = " .. tostring(got))
|
|
end
|
|
|
|
say("version=" .. tostring(GameVersion.get())
|
|
.. " engine=" .. tostring(GameVersion.engine())
|
|
.. " gender=" .. want)
|
|
|
|
-- Straight to the intro menu; the boot chain itself is crystal_boot_smoke's.
|
|
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")
|
|
|
|
if want == "none" then
|
|
-- Gold and Silver: PlayerProfileSetup has no InitGender to farcall.
|
|
waitFor("the clock screen", function() return isA(InitClock) end, 400)
|
|
if isA(GenderSelect) then bail("Gold put up the gender screen") end
|
|
say("OK no gender screen; NEW GAME went straight to the clock")
|
|
for _ = 1, 200 do
|
|
if isA(OakSpeech) then break end
|
|
tap("a", 2)
|
|
end
|
|
waitFor("the Oak speech", function() return isA(OakSpeech) end, 400)
|
|
local speech = top()
|
|
for _, step in ipairs(speech.steps or {}) do
|
|
if step.kind == "gender" then bail("Gold's speech grew a gender beat") end
|
|
end
|
|
eq(speech.steps and speech.steps[1] and speech.steps[1].id, "init_clock",
|
|
"the first beat")
|
|
-- ../pokecrystal/engine/menus/intro_menu.asm:629-635
|
|
waitFor("the clock hand-off rotations",
|
|
function() return top().fade == nil end, 200)
|
|
U.wait(40)
|
|
U.shot(game, out .. "/01-oak.png")
|
|
eq(game.save.player.gender, "male", "save.player.gender")
|
|
say("PASS gold has no gender prompt")
|
|
if log then log:close() end
|
|
love.event.quit(0)
|
|
return
|
|
end
|
|
|
|
waitFor("the gender screen", function() return isA(GenderSelect) end, 600)
|
|
local gender = top()
|
|
-- pokecrystal/engine/menus/init_gender.asm:29-34
|
|
waitFor("the prompt to finish and the menu to open",
|
|
function() return isA(GenderSelect) and top():menuOpen() end, 600)
|
|
say("OK gender screen, cursor = " .. tostring(gender.cursor))
|
|
U.shot(game, out .. "/01-genderselect.png")
|
|
if want == "girl" then
|
|
tap("down")
|
|
U.wait(10)
|
|
U.shot(game, out .. "/02-genderselect-girl.png")
|
|
else
|
|
U.shot(game, out .. "/02-genderselect-boy.png")
|
|
end
|
|
tap("a")
|
|
|
|
waitFor("the clock screen", function() return isA(InitClock) end, 400)
|
|
eq(game.save.player.gender, want == "girl" and "female" or "male",
|
|
"wPlayerGender after the answer")
|
|
eq(game.save.player.name, want == "girl" and "KRIS" or "CHRIS",
|
|
"NamePlayer's InitName default")
|
|
-- ../pokecrystal/engine/rtc/timeset.asm:22-32, :42-43: PrintText only runs
|
|
-- once RotateFourPalettesRight is done, so wait the rotation out.
|
|
waitFor("the clock's fade in",
|
|
function() return isA(InitClock) and top().fade == nil end, 200)
|
|
U.wait(20)
|
|
U.shot(game, out .. "/02b-initclock.png")
|
|
|
|
local shotDemo = false
|
|
for _ = 1, 200 do
|
|
if isA(OakSpeech) then break end
|
|
tap("a", 2)
|
|
end
|
|
waitFor("the Oak speech", function() return isA(OakSpeech) end, 400)
|
|
local speech = top()
|
|
eq(speech.steps[1].id, "gender_select", "the beat the speech opened on")
|
|
|
|
local shotPic = false
|
|
for _ = 1, 500 do
|
|
if isA(NamePick) then break end
|
|
-- ../pokecrystal/engine/menus/intro_menu.asm:875
|
|
if not shotDemo and isA(OakSpeech) and top().pic == top().marillPic then
|
|
U.wait(6)
|
|
U.shot(game, out .. "/02c-demowipe.png")
|
|
shotDemo = true
|
|
end
|
|
if not shotPic and isA(OakSpeech) then
|
|
local at = top().steps and top().steps[top().step]
|
|
if at and at.id == "ask_player_name" then
|
|
U.wait(45)
|
|
local wanted = (want == "girl") and top().playerPicFemale
|
|
or top().playerPic
|
|
if top().pic ~= wanted then
|
|
bail("DrawIntroPlayerPic put up the wrong pic")
|
|
end
|
|
U.shot(game, out .. "/03-intropic.png")
|
|
shotPic = true
|
|
end
|
|
end
|
|
tap("a", 2)
|
|
end
|
|
waitFor("the name picker", function() return isA(NamePick) end, 200)
|
|
waitFor("the name menu to slide in",
|
|
function() return top().slide == nil end, 240)
|
|
local picker = top()
|
|
eq(picker.items[2], want == "girl" and "KRIS" or "CHRIS", "preset row 1")
|
|
eq(picker.items[3], want == "girl" and "AMANDA" or "MAT", "preset row 2")
|
|
U.shot(game, out .. "/04-namepick.png")
|
|
|
|
picker.cursor = 1
|
|
tap("a")
|
|
waitFor("the naming screen", function() return isA(NamingScreen) end)
|
|
local naming = top()
|
|
eq(naming.gender, want == "girl" and "female" or "male",
|
|
"the keyboard's header icon gender")
|
|
U.wait(20)
|
|
U.shot(game, out .. "/05-naming.png")
|
|
naming.row = naming:bottomRow()
|
|
naming.col = 6
|
|
tap("a")
|
|
|
|
for _ = 1, 700 do
|
|
if game.phase == "play" and game.world and game.world.map then break end
|
|
tap("a", 2)
|
|
end
|
|
if not (game.phase == "play" and game.world and game.world.map) then
|
|
bail("never reached the overworld (top is " .. tostring(top()) .. ")")
|
|
end
|
|
eq(game.world.map.id, "PLAYERS_HOUSE_2F", "the bedroom")
|
|
eq(game.save.player.name, want == "girl" and "KRIS" or "CHRIS",
|
|
"the name that reached the save")
|
|
local sprite = game.world.player and game.world.player.spriteDef
|
|
eq(sprite and sprite.id, FieldMoves.playerSprite(game.save.player.gender),
|
|
"the overworld sheet")
|
|
U.wait(20)
|
|
U.shot(game, out .. "/06-bedroom.png")
|
|
|
|
-- Walk a step so the sheet is seen mid-stride rather than only standing.
|
|
U.hold(game, "down", 20)
|
|
U.wait(20)
|
|
U.shot(game, out .. "/07-bedroom-walk.png")
|
|
|
|
-- The card, opened directly: the START-menu route is Gen2StartMenu's and is
|
|
-- already covered by crystal_boot_smoke.
|
|
game.save.player.badges = { ZEPHYR = true, HIVE = true }
|
|
local card = Screens.push(game, "Gen2TrainerCard",
|
|
{ save = game.save, onClose = function() end })
|
|
if getmetatable(card) ~= TrainerCard then bail("the card did not open") end
|
|
eq(card.female, want == "girl", "GetCardPic picked KrisCardPic")
|
|
U.wait(30)
|
|
U.shot(game, out .. "/08-trainercard.png")
|
|
tap("right")
|
|
U.wait(20)
|
|
U.shot(game, out .. "/09-trainercard-badges.png")
|
|
game.stack:pop()
|
|
|
|
local gear = Screens.push(game, "Gen2Pokegear", { save = game.save })
|
|
U.wait(30)
|
|
local pals = gear.pals and gear:pals()
|
|
local femalePals = gear.gfx and gear.gfx.palettesFemale
|
|
if want == "girl" and femalePals and pals ~= femalePals then
|
|
bail("the gear kept MalePokegearPals for Kris")
|
|
end
|
|
if want == "boy" and femalePals and pals == femalePals then
|
|
bail("the gear took FemalePokegearPals for Chris")
|
|
end
|
|
say("OK pokegear palettes = " .. (pals == femalePals and "female" or "male"))
|
|
U.shot(game, out .. "/10-pokegear.png")
|
|
game.stack:pop()
|
|
|
|
say("PASS crystal gender run (" .. want .. ") in " .. out)
|
|
if log then log:close() end
|
|
love.event.quit(0)
|
|
end
|