Files
gen1recomp/tests/game3_import_battle_terrain_test.lua

162 lines
6.1 KiB
Lua

#!/usr/bin/env luajit
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 BattleChromeExtract = require("src.import.gba.battle_chrome_extract")
local Versions = require("src.import.gba.versions")
-- include/constants/battle.h:287
local PRET_KEYS = {
[0] = "grass", "long_grass", "sand", "underwater", "water", "pond", "mountain",
"cave", "building", "plain", "link", "gym", "leader", "indoor_2", "indoor_1",
"lorelei", "bruno", "agatha", "lance", "champion",
}
print("[test] 1. every sBattleTerrainTable id has a sheet key")
local keys = BattleChromeExtract.TERRAIN_KEYS or {}
local count = 0
for _ in pairs(keys) do count = count + 1 end
check(count == 20, "20 terrain ids are named (" .. count .. ")")
for id = 0, 19 do
check(keys[id] == PRET_KEYS[id],
string.format("terrain %d is %s (%s)", id, tostring(PRET_KEYS[id]), tostring(keys[id])))
end
print("[test] 2. the terrain table is read out of the ROM")
check(type(BattleChromeExtract.terrainTable) == "function"
and tonumber(BattleChromeExtract.TERRAIN_ENTRY_SIZE) ~= nil
and tonumber(BattleChromeExtract.TERRAIN_TABLE) ~= nil,
"battle_chrome_extract reads sBattleTerrainTable")
if type(BattleChromeExtract.terrainTable) ~= "function"
or not BattleChromeExtract.TERRAIN_ENTRY_SIZE then
print("[test] FAILED " .. failed)
os.exit(1)
end
local grass = Versions.BATTLE_UI.terrain_grass
local base = BattleChromeExtract.TERRAIN_TABLE
local bytes = {}
local function poke32(off, value)
for i = 0, 3 do
bytes[off + i] = math.floor(value / 256 ^ i) % 256
end
end
local function entry(id, tiles, tilemap, pal)
local off = base + id * BattleChromeExtract.TERRAIN_ENTRY_SIZE
poke32(off, tiles + 0x08000000)
poke32(off + 4, tilemap + 0x08000000)
poke32(off + 8, 0x08000000)
poke32(off + 12, 0x08000000)
poke32(off + 16, pal + 0x08000000)
end
entry(0, grass.tiles, grass.tilemap, grass.pal)
for id = 1, 19 do
entry(id, 0x100000 + id * 0x1000, 0x200000 + id * 0x1000, 0x300000 + id * 0x100)
end
local function get(off) return bytes[off] or 0 end
local table_ = BattleChromeExtract.terrainTable(get)
check(type(table_) == "table" and #table_ == 20, "20 entries parsed (" ..
tostring(type(table_) == "table" and #table_ or table_) .. ")")
if type(table_) == "table" and #table_ == 20 then
check(table_[1].key == "grass" and table_[1].cfg.tiles == grass.tiles,
"entry 0 is the pinned grass set")
check(table_[8].key == "cave" and table_[8].cfg.tiles == 0x100000 + 7 * 0x1000,
"entry 7 is cave (" .. tostring(table_[8].key) .. ")")
check(table_[5].key == "water" and table_[5].cfg.pal == 0x300000 + 4 * 0x100,
"entry 4 is water (" .. tostring(table_[5].key) .. ")")
check(table_[20].key == "champion", "entry 19 is champion (" .. tostring(table_[20].key) .. ")")
end
print("[test] 3. a table that does not match the pinned grass set is refused")
entry(0, grass.tiles + 4, grass.tilemap, grass.pal)
check(BattleChromeExtract.terrainTable(get) == nil, "a shifted entry 0 falls back")
entry(0, grass.tiles, grass.tilemap, grass.pal)
poke32(base + 4 * BattleChromeExtract.TERRAIN_ENTRY_SIZE, 0)
check(BattleChromeExtract.terrainTable(get) == nil, "a null tilemap pointer falls back")
local okRequire, requireErr = pcall(BattleChromeExtract.requireTerrainTable, get)
check(okRequire == false and tostring(requireErr):find("sBattleTerrainTable") ~= nil,
"the bake refuses a table it cannot decode instead of writing a partial terrain set")
entry(4, 0x100000 + 4 * 0x1000, 0x200000 + 4 * 0x1000, 0x300000 + 4 * 0x100)
local okAll, allTerrains = pcall(BattleChromeExtract.requireTerrainTable, get)
check(okAll and type(allTerrains) == "table" and #allTerrains == 20,
"a decodable table bakes all 20 sets")
print("[test] 4. baked terrain sheets in an imported cache")
local Cache = require("tests.game3_cache")
local root = Cache.root("pokemon/battle/manifest.lua")
if not root then
print("[skip] baked terrains: " .. tostring(Cache.reason))
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
print("[info] FireRed cache at " .. root)
local function readFile(rel)
local f = io.open(root .. "/pokemon/battle/" .. rel, "rb")
if not f then return nil end
local data = f:read("*a")
f:close()
return data
end
local manifestSrc = readFile("manifest.lua")
local manifest = manifestSrc and loadstring(manifestSrc)
manifest = manifest and manifest()
if type(manifest) ~= "table" or (tonumber(manifest.format) or 0) < 6 then
print("[skip] baked terrains: cache manifest is format " ..
tostring(manifest and manifest.format) .. ", the terrain bake needs 6")
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
local baked = manifest.terrains or {}
for id = 0, 19 do
local key = PRET_KEYS[id]
local info = baked[key]
check(type(info) == "table" and info.w == 256 and info.h == 256,
"manifest lists terrain " .. key)
local sheet = readFile("terrain_" .. key .. ".rgba")
check(sheet ~= nil and #sheet == 256 * 256 * 4,
string.format("terrain_%s.rgba is %s bytes", key, tostring(sheet and #sheet)))
for _, layer in ipairs({ "bg", "enemy", "player" }) do
local split = readFile("terrain_" .. layer .. "_" .. key .. ".rgba")
check(split ~= nil and #split == 256 * 160 * 4,
string.format("terrain_%s_%s.rgba is %s bytes", layer, key, tostring(split and #split)))
end
end
local cave = readFile("terrain_cave.rgba")
local water = readFile("terrain_water.rgba")
local building = readFile("terrain_building.rgba")
check(cave ~= building, "the cave sheet is not the building sheet")
check(water ~= building and water ~= cave, "the water sheet is its own art")
local opaque = 0
for i = 4, #cave, 4 do
if cave:byte(i) == 255 then opaque = opaque + 1 end
end
check(opaque > 256 * 100, "the cave sheet is opaque battle art (" .. opaque .. " pixels)")
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")