Files
gen1recomp/tests/game3_daycare_hatch_test.lua
2026-09-21 11:14:17 -04:00

186 lines
7.3 KiB
Lua

#!/usr/bin/env luajit
-- pokefirered/src/daycare.c:1749
package.path = "./?.lua;./?/init.lua;" .. package.path
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
local function eq(a, b, msg)
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
end
local function finish()
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
local songs, ses, fanfares, cries = {}, {}, {}, {}
package.loaded["src.core.game3.audio"] = setmetatable({
_mapSong = 101,
playSong = function(id) songs[#songs + 1] = id end,
playSe = function(id) ses[#ses + 1] = id end,
playFanfare = function(id) fanfares[#fanfares + 1] = id end,
playCry = function(id) cries[#cries + 1] = id end,
isCryFinished = function() return true end,
isFanfareFinished = function() return true end,
}, { __index = function() return function() end end })
local hudMessages = {}
package.loaded["src.ui.game3.hud"] = {
openMessage = function(_game, text, opts)
hudMessages[#hudMessages + 1] = text
if opts and opts.done then opts.done() end
end,
}
local store = { flags = {}, vars = {} }
local session = {
store = store, map = "FR_FOUR_ISLAND_POKEMON_DAY_CARE", party = {},
name = "RED", trainerId = 4242, vars = {}, flags = {},
dex = { seen = {}, owned = {} },
}
package.loaded["src.core.game3.runtime"] = {
getSession = function() return session end,
isActive = function() return true end,
}
local Daycare = require("src.core.game3.daycare")
local StepEvents = require("src.core.game3.step_events")
local EggHatch = require("src.ui.game3.egg_hatch")
local Stack = require("src.ui.game3.stack")
local Message = require("src.ui.game3.message")
local Choice = require("src.ui.game3.choice")
print("[test] 1. the hatch scene is its own scene, not the evolution scene")
-- pokefirered/src/daycare.c:1749 EggHatch is a CB2 of its own
check(type(EggHatch.start) == "function", "src/ui/game3/egg_hatch.lua exposes start")
local stepSource = assert(io.open("src/core/game3/step_events.lua")):read("*a")
check(stepSource:find("src.ui.game3.egg_hatch", 1, true) ~= nil,
"the hatch leg of step_events opens the egg-hatch scene")
check(stepSource:find("evolution_scene", 1, true) == nil,
"and no longer routes a hatching EGG into the evolution scene")
local Cache = require("tests.game3_cache")
local cacheRoot = Cache.mount("meta.json")
if not cacheRoot then
print("[skip] game3_daycare_hatch_test needs species data: " .. tostring(Cache.reason))
finish()
end
print("[info] FireRed cache at " .. cacheRoot)
local Pokemon = require("src.core.game3.pokemon")
Pokemon.install(nil)
local Party = require("src.core.game3.party")
local Breeding = require("src.core.game3.breeding")
local MAGIKARP = 129
print("[test] 2. an egg in the party hatches out of the daycare step counter")
local scratch = { party = {}, name = "RED", trainerId = 4242, map = session.map }
local ok, _, egg = Party.giveMon(scratch, MAGIKARP, 5, "EGG")
check(ok, "built a MAGIKARP")
Breeding.applyEggData(egg, true)
egg.nickname = "EGG"
-- pokefirered/src/daycare.c:1106 METLOC_SPECIAL_EGG
eq(egg.metLocation, 253, "CreateEgg's hot springs met location")
local cycles = tonumber(Pokemon.speciesMeta(MAGIKARP).eggCycles)
eq(egg.friendship, cycles, "the hatch counter is the species' eggCycles")
session.party = { egg }
session.dex = { seen = {}, owned = {} }
StepEvents.flush()
local dc = Daycare.stateOf(session)
dc.stepCounter = 0
local steps, hatchEvent = 0, nil
while steps < 256 * (cycles + 3) and not hatchEvent do
StepEvents.onStepTaken(session, nil)
steps = steps + 1
for _, event in ipairs(StepEvents._queue) do
if event.type == "egg_hatch" then hatchEvent = event end
end
end
check(hatchEvent ~= nil, "the egg queued its hatch event")
-- pokefirered/src/daycare.c:1157
eq(steps, 255 + cycles * 256, "on the cart's 255 + eggCycles * 256th step")
check(not session.dex.seen[MAGIKARP], "and the egg was still unknown to the POKeDEX")
-- pokefirered/src/field_control_avatar.c:672 GAME_STAT_HATCHED_EGGS
eq(session.gameStats and session.gameStats[13], 1, "the hatched-eggs game stat went up")
print("[test] 3. the scene shows Huh? then the hatch line, not an evolution")
local doneResult = nil
hatchEvent.run(function(result) doneResult = result end)
-- pokefirered/data/scripts/day_care.inc:112 DayCare_Text_Huh
eq(hudMessages[1], "Huh?", "the field script prints DayCare_Text_Huh first")
check(EggHatch.isOpen(), "the egg-hatch scene is on the stack")
check(Stack.has("egg_hatch"), "as its own stack layer")
check(not Stack.has("evolution_scene"), "and no evolution scene was pushed")
eq(egg.isEgg, false, "AddHatchedMonToParty ran at scene setup")
check(session.dex.seen[MAGIKARP], "which is where the POKeDEX flags are set")
local guard = 0
local framesSeen, maxShards = {}, 0
while EggHatch._state ~= "hatched_msg" and guard < 600 do
EggHatch.update(1 / 60)
local f = EggHatch._eggFrame or 0
if framesSeen[#framesSeen] ~= f then framesSeen[#framesSeen + 1] = f end
maxShards = math.max(maxShards, #(EggHatch._shards or {}))
guard = guard + 1
end
eq(EggHatch._state, "hatched_msg", "the shake sequence reached the hatch message")
-- pokefirered/src/daycare.c:2009 StartSpriteAnim 1, then 2, then 2 again
eq(table.concat(framesSeen, ","), "0,1,2", "the egg walked sEggHatchTiles frames 0, 1, 2")
-- pokefirered/src/daycare.c:2128 CreateRandomEggShardSprite
check(maxShards >= 16, "the shards flew, most of them at the flash, got " .. tostring(maxShards))
-- pokefirered/src/daycare.c:2091 PlaySE(SE_EGG_HATCH)
check(ses[#ses] == 106, "SE_EGG_HATCH closed the crack sequence, got " .. tostring(ses[#ses]))
check(Message.isOpen(), "the message box is open")
local page = Message.currentPage() or ""
check(tostring(page):find("hatched from the EGG", 1, true) ~= nil,
"gText_HatchedFromEgg is on screen, got " .. tostring(page))
-- pokefirered/src/daycare.c:1929 PlayFanfare(MUS_EVOLVED)
eq(fanfares[1], 259, "MUS_EVOLVED played")
eq(cries[1], MAGIKARP, "the hatchling cried")
-- pokefirered/src/daycare.c:2008 PlaySE(SE_BALL)
check(#ses >= 4, "the egg cracked on all four PlaySE(SE_BALL) beats, got " .. tostring(#ses))
print("[test] 4. the nickname prompt, and No returns to the field")
guard = 0
while EggHatch._state ~= "nickname_ask" and guard < 600 do
EggHatch.update(1 / 60)
guard = guard + 1
end
eq(EggHatch._state, "nickname_ask", "the scene asked about a nickname")
page = Message.currentPage() or ""
check(tostring(page):find("nickname the newly", 1, true) ~= nil,
"gText_NickHatchPrompt is on screen, got " .. tostring(page))
check(Choice.active, "the yes/no menu is up")
eq(Choice.left, 21, "at sYesNoWinTemplate's tilemapLeft")
eq(Choice.top, 9, "and tilemapTop")
Choice.cancel()
guard = 0
while EggHatch.isOpen() and guard < 600 do
EggHatch.update(1 / 60)
guard = guard + 1
end
check(not EggHatch.isOpen(), "the scene closed")
-- pokefirered/src/daycare.c:2125 DestroySprite past y2 > 20
check(#(EggHatch._shards or {}) == 0, "every shard fell past its 20px floor")
check(not Stack.has("egg_hatch"), "and left the stack")
eq(doneResult, "hatched", "the step event was released")
eq(songs[#songs], 101, "the map song came back")
eq(egg.nickname, "", "declining the prompt leaves the species name")
finish()