mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-28 23:00:21 -04:00
260 lines
9.7 KiB
Lua
260 lines
9.7 KiB
Lua
local U = require("tests.drivers.util")
|
|
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/em_anim_sweep"
|
|
|
|
local SHOTS = {
|
|
MORNING_SUN = 40, BLIZZARD = 60, TRANSFORM = 30, ROLE_PLAY = 40, SNATCH = 40, ODOR_SLEUTH = 30,
|
|
GLARE = 30, METEOR_MASH = 30, BLOCK = 30, TORMENT = 40, ENCORE = 30, DOOM_DESIRE = 60,
|
|
SURF = 40, PSYCHIC = 40, THUNDERBOLT = 20, FLAMETHROWER = 30, HYPER_BEAM = 40, EARTHQUAKE = 20,
|
|
}
|
|
|
|
return function(game)
|
|
local fails = 0
|
|
local function result(ok, label)
|
|
print((ok and "PASS " or "FAIL ") .. label)
|
|
if not ok then fails = fails + 1 end
|
|
end
|
|
|
|
local issues, issueSeen, current = {}, {}, "setup"
|
|
local function issue(kind, name)
|
|
local key = kind .. ":" .. tostring(name)
|
|
if issueSeen[key] then return end
|
|
issueSeen[key] = true
|
|
issues[#issues + 1] = key .. " (" .. current .. ")"
|
|
end
|
|
local realPrint = print
|
|
print = function(...)
|
|
local parts = {}
|
|
for i = 1, select("#", ...) do parts[#parts + 1] = tostring((select(i, ...))) end
|
|
local s = table.concat(parts, " ")
|
|
if s:find("[battle.anim]", 1, true) and not s:find("cap", 1, true) then issue("log", s) end
|
|
realPrint(...)
|
|
end
|
|
|
|
for _ = 1, 900 do
|
|
if game.phase == "boot" and game.boot then break end
|
|
U.wait(1)
|
|
end
|
|
game:_handleBootAction({ action = "new_game", name = "NICK", gender = 0 })
|
|
U.wait(240)
|
|
|
|
local Runtime = require("src.core.game3.runtime")
|
|
local Party = require("src.core.game3.party")
|
|
local BattleBridge = require("src.core.game3.battle_bridge")
|
|
local Battle = require("src.core.game3.battle")
|
|
local Anim = require("src.core.game3.battle.anim")
|
|
local AnimCtx = require("src.core.game3.battle.anim_ctx")
|
|
local AnimTasks = require("src.core.game3.battle.anim_tasks")
|
|
local AnimCallbacks = require("src.core.game3.battle.anim_callbacks")
|
|
local AnimSprites = require("src.core.game3.battle.anim_sprites")
|
|
local Ui = require("src.core.game3.battle.ui")
|
|
local Map = require("src.core.game3.map")
|
|
local GAME = require("src.core.GameVersion").get()
|
|
local C = require("src.core.game3.constants").of(GAME)
|
|
local session = Runtime.getSession()
|
|
result(session ~= nil, "field session exists")
|
|
if not session then love.event.quit(1) return end
|
|
|
|
local function sp(name) return C.species.byName["SPECIES_" .. name] end
|
|
|
|
local realSpawn = AnimTasks.spawn
|
|
AnimTasks.spawn = function(name, priority, args, vm)
|
|
local n = tostring(name or "stub")
|
|
local key = n:gsub("^g", "")
|
|
if not (AnimTasks.REGISTRY[n] or AnimTasks.REGISTRY[key] or AnimTasks.REGISTRY["AnimTask_" .. key]) then
|
|
issue("task", n)
|
|
end
|
|
return realSpawn(name, priority, args, vm)
|
|
end
|
|
local realGet = AnimCallbacks.get
|
|
AnimCallbacks.get = function(name)
|
|
if name and name ~= "" then
|
|
local clean = tostring(name):gsub("^Anim", "")
|
|
if not (rawget(AnimCallbacks, name) or AnimCallbacks[name] or AnimCallbacks[clean] or AnimCallbacks["Anim" .. clean]) then
|
|
issue("callback", name)
|
|
end
|
|
end
|
|
return realGet(name)
|
|
end
|
|
|
|
if GAME == "emerald" then
|
|
local ok, err = pcall(function() Map.load(nil, game, "EM_ROUTE101", { x = 10, y = 12, facing = "down" }) end)
|
|
result(ok, "load EM_ROUTE101 " .. tostring(err or ""))
|
|
end
|
|
U.wait(30)
|
|
session.party = {}
|
|
Party.giveMon(session, sp("SALAMENCE"), 50, "SALAMENCE")
|
|
session.party[1].hp = session.party[1].maxHp or session.party[1].hp
|
|
|
|
local started, serr = BattleBridge.startWild(Runtime._mod, game, { species = sp("ZIGZAGOON"), level = 50 }, { fade = false })
|
|
result(started == true, "wild battle started " .. tostring(serr or ""))
|
|
if not started then love.event.quit(1) return end
|
|
local ready = false
|
|
for _ = 1, 6000 do
|
|
if Battle.isActive() and Ui._mode == "menu" and not Anim.busy() then ready = true break end
|
|
if Ui.dialogPending and Ui.dialogPending() then U.tap(game, "a") else U.wait(1) end
|
|
end
|
|
result(ready, "battle reached the action menu")
|
|
if not ready then love.event.quit(1) return end
|
|
U.wait(20)
|
|
|
|
local pack = Anim._pack or (Anim.scriptForMove(1) and Anim._pack)
|
|
result(pack and pack.moves ~= nil, "Emerald anim pack loaded")
|
|
local moveCount = 0
|
|
for _ in pairs(pack.moves) do moveCount = moveCount + 1 end
|
|
result(moveCount == 355, "pack has 355 move scripts (" .. moveCount .. ")")
|
|
if GAME == "emerald" then
|
|
result(#pack.generalNames == 22 and pack.generalNames[4] == "POKEBLOCK_THROW", "23 general anims, 4 = POKEBLOCK_THROW")
|
|
end
|
|
|
|
local st = Battle._st
|
|
local pSpecies = st and st.player and st.player.mon and st.player.mon.species or sp("SALAMENCE")
|
|
local eSpecies = st and st.enemy and st.enemy.mon and st.enemy.mon.species or sp("ZIGZAGOON")
|
|
|
|
local function restore()
|
|
for _, s in ipairs({ "player", "enemy" }) do
|
|
local p = Anim.present(s)
|
|
if p then
|
|
p.visible = true
|
|
p.ox, p.oy, p.sx, p.sy, p.rotation = 0, 0, 1, 1, 0
|
|
p.blendCoeff = 0
|
|
p.invisible = nil
|
|
p.transformSpecies = nil
|
|
end
|
|
if p and p.substitute then Anim.setSubstitute(s, false) end
|
|
end
|
|
end
|
|
|
|
local function audit_sprites()
|
|
AnimSprites.init()
|
|
for i = 1, AnimSprites.MAX do
|
|
local s = AnimSprites._pool[i]
|
|
if s.active and s.tag and s.tag ~= "" and not s.image and not s.invisible and s.visible ~= false
|
|
and not (s._op and s._op.noGfx) then
|
|
issue("sprite-no-image", s.tag)
|
|
end
|
|
end
|
|
end
|
|
|
|
local RESTORED = { visible = true, ox = true, oy = true, sx = true, sy = true, rotation = true, blendCoeff = true }
|
|
local function snap()
|
|
local out = {}
|
|
for _, side in ipairs({ "player", "enemy" }) do
|
|
local p = Anim.present(side) or {}
|
|
for k, v in pairs(p) do
|
|
local t = type(v)
|
|
if (t == "number" or t == "boolean" or t == "string") and not RESTORED[k] then out[side .. "." .. k] = v end
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
local baseline
|
|
local leaks = {}
|
|
local function check_leak(label)
|
|
local now = snap()
|
|
for k, v in pairs(now) do
|
|
if baseline[k] ~= v and not leaks[k] then
|
|
leaks[k] = true
|
|
print("[driver] leak " .. k .. " " .. tostring(baseline[k]) .. " -> " .. tostring(v) .. " after " .. label)
|
|
end
|
|
end
|
|
for k, v in pairs(baseline) do
|
|
if now[k] == nil and not leaks[k] then
|
|
leaks[k] = true
|
|
print("[driver] leak " .. k .. " " .. tostring(v) .. " -> nil after " .. label)
|
|
end
|
|
end
|
|
end
|
|
|
|
local timeouts, totalFrames, ran = 0, 0, 0
|
|
local function run(label, launch, shotAt)
|
|
current = label
|
|
local okL, e = pcall(launch)
|
|
if not okL then issue("launch", tostring(e)) return end
|
|
local f = 0
|
|
while f < 1500 do
|
|
U.wait(1)
|
|
f = f + 1
|
|
if f % 8 == 0 then audit_sprites() end
|
|
local want = shotAt and (type(shotAt) == "table" and shotAt[f] or shotAt == f)
|
|
if want then
|
|
local stem = label:lower():gsub("[^%w]+", "_")
|
|
U.still(game, DIR .. "/" .. stem .. (type(shotAt) == "table" and string.format("_%03d", f) or "") .. ".png")
|
|
end
|
|
if not Anim.busy() and (not shotAt or f > (type(shotAt) == "table" and shotAt.last or shotAt)) then break end
|
|
end
|
|
if Anim.busy() then
|
|
timeouts = timeouts + 1
|
|
issue("timeout", label)
|
|
end
|
|
totalFrames = totalFrames + f
|
|
ran = ran + 1
|
|
restore()
|
|
check_leak(label)
|
|
U.wait(4)
|
|
end
|
|
|
|
local function opts(side, turn, moveId)
|
|
local target = side == "player" and "enemy" or "player"
|
|
return {
|
|
attackerSide = side,
|
|
targetSide = target,
|
|
isReversed = side == "enemy",
|
|
attackerSpecies = side == "player" and pSpecies or eSpecies,
|
|
targetSpecies = side == "player" and eSpecies or pSpecies,
|
|
moveTurn = turn or 0,
|
|
turn = turn or 0,
|
|
ctx = AnimCtx.build(side, target, { moveId = moveId }),
|
|
}
|
|
end
|
|
|
|
baseline = snap()
|
|
local only = os.getenv("EM_ANIM_MOVES")
|
|
local shotList = nil
|
|
if os.getenv("EM_ANIM_SHOTS") then
|
|
shotList = { last = 0 }
|
|
for n in os.getenv("EM_ANIM_SHOTS"):gmatch("%d+") do
|
|
shotList[tonumber(n)] = true
|
|
if tonumber(n) > shotList.last then shotList.last = tonumber(n) end
|
|
end
|
|
end
|
|
local sides = os.getenv("EM_ANIM_SIDES") or "player,enemy"
|
|
for id = 1, 354 do
|
|
local name = (C:name("moves", id, "MOVE_") or ("MOVE_" .. id)):gsub("^MOVE_", "")
|
|
if not only or only:find(name, 1, true) then
|
|
for side in sides:gmatch("[^,]+") do
|
|
local shot = shotList or (side == "player" and SHOTS[name] or nil)
|
|
run(name .. "_" .. side, function() Anim.launchMove(id, opts(side, 0, id)) end, shot)
|
|
for _, op in ipairs(pack.moves[id]) do
|
|
if op.op == "choosetwoturnanim" or op.op == "jumpifmoveturn" then
|
|
run(name .. "_turn1_" .. side, function() Anim.launchMove(id, opts(side, 1, id)) end, nil)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if not only then
|
|
for i = 0, #pack.generalNames do
|
|
local n = pack.generalNames[i]
|
|
do
|
|
run("general_" .. n, function() Anim.launchGeneral(n, opts("player")) end, n == "STATS_CHANGE" and 20 or nil)
|
|
end
|
|
end
|
|
for i = 0, #pack.statusNames do
|
|
local n = pack.statusNames[i]
|
|
run("status_" .. n, function() Anim.launchStatus(i, opts("enemy")) end, n == "STATUS_FRZ" and 20 or nil)
|
|
end
|
|
for _, n in ipairs({ "LVL_UP", "SUBSTITUTE_TO_MON", "MON_TO_SUBSTITUTE" }) do
|
|
run("special_" .. n, function() Anim.launchSpecial(n, opts("player")) end, nil)
|
|
end
|
|
end
|
|
|
|
print = realPrint
|
|
print(string.format("[driver] ran %d anims, %d frames, %d timeouts", ran, totalFrames, timeouts))
|
|
for _, s in ipairs(issues) do print("[driver] issue " .. s) end
|
|
result(#issues == 0, "no unresolved task/callback, sprite without sheet, anim log or launch error (" .. #issues .. ")")
|
|
result(timeouts == 0, "every anim finishes within 1500 frames")
|
|
print(string.format("[driver] em_anim_sweep: %d failure(s)", fails))
|
|
love.event.quit(fails == 0 and 0 or 1)
|
|
end
|