Files
gen1recomp/tests/game3_ball_sheets_test.lua

159 lines
6.0 KiB
Lua

#!/usr/bin/env luajit
package.path = "./?.lua;./?/init.lua;" .. package.path
local GameCache = require("tests.game3_cache")
local root = GameCache.mountOrSkip("game3_ball_sheets_test")
require("tests.fixture_data.game3_items").install()
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 slurp(path)
local f = io.open(path, "rb")
if not f then return nil end
local d = f:read("*a")
f:close()
return d
end
local NAMES = { [0] = "poke", "great", "safari", "ultra", "master", "net",
"dive", "nest", "repeat", "timer", "luxury", "premier" }
local W, H = 16 * 12, 48
local rgba = slurp(root .. "/pokemon/battle/ball_open/balls.rgba")
print("[test] baked ball sheet strip")
check(rgba ~= nil and #rgba == W * H * 4, "balls.rgba is 192x48 RGBA")
local function alpha(ball, x, y)
return rgba:byte(((y * W) + ball * 16 + x) * 4 + 4)
end
if rgba and #rgba == W * H * 4 then
for ball = 0, 11 do
local n = 0
for y = 32, 47 do
for x = 0, 15 do
if alpha(ball, x, y) ~= 0 then n = n + 1 end
end
end
check(n > 0, "ball " .. NAMES[ball] .. " open frame 2 is not blank")
end
local pret = "../pokefirered/graphics/interface/"
local openGfx = slurp(pret .. "ball_open.4bpp")
if not openGfx then
print("[skip] pret pokefirered graphics not found; pixel parity not checked")
else
print("[test] pixel parity vs pret graphics/interface/ball/*.4bpp + ball_open.4bpp")
local NO_SPLICE = { [6] = true, [10] = true, [11] = true }
for ball = 0, 11 do
local gfx = slurp(pret .. "ball/" .. NAMES[ball] .. ".4bpp")
local pal = slurp(pret .. "ball/" .. NAMES[ball] .. ".gbapal")
local bad = 0
for ti = 0, 11 do
local src, off = gfx, ti * 32
if ti >= 8 and not NO_SPLICE[ball] then src, off = openGfx, (ti - 8) * 32 end
local frame, sub = math.floor(ti / 4), ti % 4
for py = 0, 7 do
for px = 0, 7 do
local byte = src:byte(off + py * 4 + math.floor(px / 2) + 1)
local idx = (px % 2 == 0) and (byte % 16) or math.floor(byte / 16)
local x = (sub % 2) * 8 + px
local y = frame * 16 + math.floor(sub / 2) * 8 + py
local o = ((y * W) + ball * 16 + x) * 4
local r, g, b, a = rgba:byte(o + 1, o + 4)
if idx == 0 then
if a ~= 0 then bad = bad + 1 end
else
local c = pal:byte(idx * 2 + 1) + pal:byte(idx * 2 + 2) * 256
local er, eg, eb = c % 32, math.floor(c / 32) % 32, math.floor(c / 1024) % 32
local function to8(v) return math.floor(v * 255 / 31 + 0.5) end
if a ~= 255 or r ~= to8(er) or g ~= to8(eg) or b ~= to8(eb) then bad = bad + 1 end
end
end
end
end
check(bad == 0, NAMES[ball] .. " ball sheet matches pret (" .. bad .. " px differ)")
end
end
end
local BallOpen = require("src.core.game3.battle.ball_open")
local Anim = require("src.core.game3.battle.anim")
local CatchSeq = require("src.core.game3.battle.catch_seq")
local Catching = require("src.core.game3.battle.catching")
local Audio = require("src.core.game3.audio")
local Ui = require("src.core.game3.battle.ui")
local SummaryMenu = require("src.ui.game3.summary_menu")
print("[test] manifest names the ball strip")
local d = BallOpen.data()
check(d and d.ballSheet == "balls.rgba" and d.ballSheetW == W and d.ballSheetH == H, "manifest ballSheet/ballSheetW/ballSheetH")
print("[test] battle renderer picks the per-ball column")
local realLove = rawget(_G, "love")
love = {
image = { newImageData = function(w, h) return { w = w, h = h } end },
graphics = {
newImage = function(id) return { getDimensions = function() return id.w, id.h end, setFilter = function() end } end,
newQuad = function(x, y, w, h) return { x = x, y = y, w = w, h = h } end,
},
}
local okQ = type(Ui.ballQuad) == "function"
check(okQ, "Ui.ballQuad exists")
if okQ then
local img, q = Ui.ballQuad(11, 2)
check(img ~= nil and q and q.x == 176 and q.y == 32, "premier open frame quad at (176,32)")
local _, q3 = Ui.ballQuad(3, 0)
check(q3 and q3.x == 48 and q3.y == 0, "ultra closed frame quad at (48,0)")
local _, qn = Ui.ballQuad(nil, 1)
check(qn and qn.x == 0 and qn.y == 16, "missing ball id draws the poke ball")
end
love = realLove
print("[test] thrown ball is tagged with the used item's ball")
Audio.playSe = function() return true end
Audio.stopAll = function() end
Audio.waitSe = function() end
Audio.playSong = function() return true end
Catching.storeCaught = function() return { firstTimeCaught = false } end
Ui.battlerSpriteCenter = function(_, _, base) return base.x, base.y end
for _, case in ipairs({ { 12, 11 }, { 2, 3 }, { 3, 1 }, { 1, 4 }, { 4, 0 }, { 11, 10 }, { 7, 6 } }) do
Anim.reset({ headless = false })
Anim.present("enemy").visible = true
local st = { enemy = { species = 16, mon = { species = 16, name = "PIDGEY" } } }
CatchSeq.begin(st, case[1], false, 1, {
pushMsg = function() end, headless = false, session = { name = "RED" },
})
CatchSeq.update()
CatchSeq._waitingMsg = false
CatchSeq.update()
local seen
for _ = 1, 120 do
Anim.update(1 / 60)
local s = Anim.stage().ball
if s.visible then seen = s.ballId break end
end
check(seen == case[2], ("item %d throws ball sheet %d (got %s)"):format(case[1], case[2], tostring(seen)))
end
print("[test] summary ball icon id")
check(type(SummaryMenu.ballIdOf) == "function", "SummaryMenu.ballIdOf exists")
if type(SummaryMenu.ballIdOf) == "function" then
check(SummaryMenu.ballIdOf({ species = 25, pokeball = 12 }) == 11, "premier-caught mon shows premier icon")
check(SummaryMenu.ballIdOf({ species = 25, pokeball = 1 }) == 4, "master-caught mon shows master icon")
check(SummaryMenu.ballIdOf({ species = 25, pokeball = 12, isEgg = true }) == 0, "egg shows poke ball icon")
end
if failed > 0 then
print(("[FAIL] %d check(s) failed"):format(failed))
os.exit(1)
end
print("[ok] game3_ball_sheets_test")