mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
275 lines
8.7 KiB
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()
|