Files
gen1recomp/tests/engine/evolution_timing_bug2033.lua

275 lines
8.7 KiB
Lua

-- engine/movie/evolution.asm:17-19, :20-40 (#2033)
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
love = love or require("tests.love_stub")
local PIC_LOAD_FRAMES = 84
local CANCEL_GRACE_FRAMES = 80
local CRY_FRAMES = 85
local frame = 0
local played, cries, musicLog = {}, {}, {}
local function fakeSource(len)
local start = frame
return {
isPlaying = function() return frame - start < len end,
stop = function() end,
getDuration = function() return len / 60 end,
getPitch = function() return 1 end,
}
end
package.loaded["src.core.Sound"] = {
play = function(_, id) played[#played + 1] = { id = id, frame = frame } end,
playPress = function() end,
sfxBusy = function() return false end,
waitFrames = function(_, fallback) return fallback or 180 end,
playCry = function(_, species)
cries[#cries + 1] = { species = species, frame = frame }
return fakeSource(CRY_FRAMES)
end,
}
local Music = require("src.core.Music")
for _, name in ipairs({ "stop", "play", "restoreMap" }) do
local real = Music[name]
Music[name] = function(...)
musicLog[#musicLog + 1] = { name = name, frame = frame }
return real(...)
end
end
local Fixtures = require("tests.modkit.fixtures")
local EvolutionState = require("src.ui.EvolutionState")
local Input = require("src.core.Input")
local Pokemon = require("src.pokemon.Pokemon")
local StateStack = require("src.core.StateStack")
local TextBox = require("src.render.TextBox")
local Data = Fixtures.fresh()
require("src.render.Font").load(Data)
local EVO_LEVEL = 16
local B_KEY = "x"
local fakeSprite = {
getWidth = function() return 40 end,
getHeight = function() return 40 end,
getDimensions = function() return 40, 40 end,
}
local drawn = 0
local realDraw = love.graphics.draw
love.graphics.draw = function(img, ...)
if img == fakeSprite then drawn = drawn + 1 return end
return realDraw(img, ...)
end
local function newGame()
local game = { data = Data }
local mon = Pokemon.new(Data, "FIXMON_A", EVO_LEVEL)
game.save = {
party = { mon },
player = { name = "RED", id = 1 },
options = { textSpeed = 5 },
flags = {},
pokedex = { seen = {}, owned = {} },
}
game.stack = setmetatable({}, { __index = StateStack })
game.stack:init()
game.input = Input
Input:init()
return game, mon
end
local function step(game)
frame = frame + 1
game.input:step()
game.stack:update(1 / 60)
end
local function openMovie(game, mon)
local state = EvolutionState.new(game, mon, "FIXMON_B", nil, "LEVEL")
state.oldSprite, state.newSprite = fakeSprite, fakeSprite
game.stack:push(state)
return state
end
local function textOf(box)
local out = {}
for _, page in ipairs(box.pages) do
for _, line in ipairs(page) do out[#out + 1] = line end
end
return table.concat(out, " ")
end
local function lastMusic(name)
for i = #musicLog, 1, -1 do
if musicLog[i].name == name then return musicLog[i].frame end
end
end
do
local game, mon = newGame()
played, cries, musicLog = {}, {}, {}
local state = openMovie(game, mon)
eq(#played, 1, "one SFX at the clear")
eq(played[1] and played[1].id, "Tink",
"and it is SFX_TINK (evolution.asm:17)")
eq(#cries, 0, "the old cry does not start with the state (evolution.asm:41)")
eq(state.loading, PIC_LOAD_FRAMES,
"the pic load window is the frozen-screen stretch (evolution.asm:20-40)")
drawn = 0
state:draw()
eq(drawn, 0, "nothing is painted over the blank field while it loads")
for _ = 1, PIC_LOAD_FRAMES - 1 do step(game) end
eq(state.loading, 1, "still loading one frame short of the window")
eq(#cries, 0, "and still silent")
eq(state.t, 0, "the cancel-grace clock has not started either")
drawn = 0
state:draw()
eq(drawn, 0, "the pic is still off screen on the last loading frame")
step(game)
eq(state.loading, nil, "hAutoBGTransferEnabled comes back (evolution.asm:39)")
eq(#cries, 1, "the old cry starts on that frame (evolution.asm:41)")
eq(cries[1] and cries[1].species, "FIXMON_A", "and it is the old species")
eq(state.cryWait, true, "WaitForSoundToFinish holds the movie (evolution.asm:43)")
drawn = 0
state:draw()
eq(drawn, 1, "the pic appears on the same frame as the cry")
-- the evolution music only starts once the cry is out (evolution.asm:45)
local cryFrame = frame
check(lastMusic("play") == nil, "no evolution music under the cry")
for _ = 1, 400 do
if not state.cryWait then break end
step(game)
end
check(lastMusic("play") ~= nil and lastMusic("play") - cryFrame >= CRY_FRAMES - 2,
"the music waits out the full cry")
played, cries, musicLog = {}, {}, {}
for _ = 1, 500 do
if state.done then break end
step(game)
end
check(state.done, "the flash ran to the end")
eq(mon.species, "FIXMON_B", "the mon evolved")
local doneFrame = frame
eq(#cries, 1, "the new cry starts at .done (evolution.asm:74)")
eq(cries[1] and cries[1].species, "FIXMON_B", "and it is the evolved species")
eq(lastMusic("stop"), doneFrame,
"SFX_STOP_ALL_MUSIC lands first (evolution.asm:70-72)")
check(game.stack:top() == state,
"EvolvedText does not print on the cry frame (evos_moves.asm:136)")
for _ = 1, 400 do
if game.stack:top() ~= state then break end
step(game)
end
local box = game.stack:top()
if check(getmetatable(box) == TextBox, "the result text follows the cry") then
check(textOf(box):find("evolved"), "and it is _EvolvedText + _IntoText")
end
check(frame - doneFrame >= CRY_FRAMES - 2,
"PlayCry blocked the text for the whole cry (home/pokemon.asm:145-149)")
eq(#played, 0, "and no jingle sounded under it")
end
do
local game, mon = newGame()
played, cries, musicLog = {}, {}, {}
local state = openMovie(game, mon)
eq(played[1] and played[1].id, "Tink", "the cancel path opens the same way")
for _ = 1, 600 do
if state.t > 80 then break end
step(game)
end
check(state.t > 80 and not state.done, "the movie is past the poll window")
played, cries, musicLog = {}, {}, {}
Input:keypressed(B_KEY)
step(game)
eq(state.canceled, true, "a fresh B press cancels")
local cancelFrame = frame
-- evolution.asm:89-94 falls into .done with wEvoOldSpecies
eq(#cries, 1, "a cancelled evolution still cries at .done")
eq(cries[1] and cries[1].species, "FIXMON_A", "with the old species")
eq(lastMusic("stop"), cancelFrame, "and stops the evolution music there")
check(game.stack:top() == state, "StoppedEvolvingText waits on the cry")
for _ = 1, 400 do
if game.stack:top() ~= state then break end
step(game)
end
local box = game.stack:top()
if check(getmetatable(box) == TextBox, "the stopped-evolving text follows") then
check(textOf(box):find("stopped evolving"), "and it is _StoppedEvolvingText")
end
check(frame - cancelFrame >= CRY_FRAMES - 2, "after the whole cry")
eq(mon.species, "FIXMON_A", "the mon kept its species")
end
-- ../pokered/engine/movie/evolution.asm:23-26, :49-50, :70-76
do
local realPaletteFX = package.loaded["src.render.PaletteFX"]
package.loaded["src.render.PaletteFX"] = {
pal = function(_, name) return { name = name } end,
monPal = function(_, species) return { species = species } end,
whole = function(c) return { whole = c } end,
wholeNamed = function(_, n) return { { whole = { name = n } } } end,
markTrueColor = function() end,
}
local game, mon = newGame()
played, cries, musicLog = {}, {}, {}
local state = openMovie(game, mon)
local function palOf()
local packets = state:sgbPalettes(game)
local w = packets and packets[1] and packets[1].whole
return w and (w.species or w.name) or "?"
end
eq(palOf(), "FIXMON_A", "the old mon's palette is loaded before the pic")
for _ = 1, PIC_LOAD_FRAMES do step(game) end
eq(state.cryWait, true, "the cry is holding the movie")
eq(palOf(), "FIXMON_A", "the pic arrives in the old mon's own colours")
for _ = 1, 400 do
if not state.cryWait then break end
step(game)
end
eq(state.t, 0, "the 80-frame grace has not started yet")
eq(palOf(), "FIXMON_A", "and the music starts with the mon still coloured")
for _ = 1, CANCEL_GRACE_FRAMES - 1 do step(game) end
eq(state.t, CANCEL_GRACE_FRAMES - 1, "one frame short of the grace")
eq(palOf(), "FIXMON_A", "still the mon palette on the last grace frame")
step(game)
eq(state.t, CANCEL_GRACE_FRAMES, "the grace is over")
eq(palOf(), "BLACK", "PAL_BLACK lands with .animLoop, not before it")
for _ = 1, 400 do
if state.done then break end
step(game)
end
check(state.done, "the flash finished")
eq(palOf(), "FIXMON_B", "and the settled form wears its own palette")
package.loaded["src.render.PaletteFX"] = realPaletteFX
end
love.graphics.draw = realDraw
T.finish()