Files
gen1recomp/tests/game3_union_rs_test.lua
T
2026-10-07 14:31:38 -04:00

156 lines
7.2 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 function eq(a, b, msg)
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
end
local session = { version = "ruby", map = "RU_OLDALE_TOWN_POKEMON_CENTER_2F", x = 2, y = 2, party = {} }
package.loaded["src.core.game3.runtime"] = { getSession = function() return session end, isActive = function() return true end }
local Plaza = require("src.core.game3.link.union_plaza_map")
local UnionRs = require("src.core.game3.rse.union_rs")
local Union = require("src.core.game3.link.union_room")
print("[test] RS map ids")
eq(Plaza.KIND, "rs", "Ruby builds the RS room")
eq(Plaza.SOURCE_ID, nil, "Ruby has no cart Union Room to redirect")
eq(Plaza.TILE_SOURCE_ID, "RU_RECORD_CORNER", "tiles come from Ruby's own Record Corner")
eq(Plaza.MAP_ID, "RU_UNION_ROOM", "the added room id")
check(Union.isUnionMap("RU_UNION_ROOM"), "the RS room is a Union Room map")
check(not Union.isUnionMap("RU_RECORD_CORNER"), "the Record Corner is not")
print("[test] RS role grid")
local roles, source, blocked = Plaza.tables("rs")
eq(#roles, 25, "25 rows")
local bad
for y, row in ipairs(roles) do
if #row ~= 25 then bad = y end
for i = 1, #row do if not source[row:sub(i, i)] then bad = y end end
end
check(bad == nil, "every row is 25 wide with known roles: " .. tostring(bad))
for slot = 1, Plaza.CAP do
local x, y = Plaza.cellFor(slot)
local r = Plaza.roleAt(x, y, "rs")
if r ~= "." then check(false, "slot " .. slot .. " is on floor") end
end
local ex, ey = Plaza.entry()
eq(Plaza.roleAt(ex, ey, "rs"), "p", "entry is an exit pad")
eq(#Plaza.EXITS, 3, "three exit pads")
local function key(x, y) return y * 64 + x end
local cells = {}
for _, c in ipairs(Plaza.CELLS) do cells[key(c.x, c.y)] = true end
local function walk(x, y)
local r = Plaza.roleAt(x, y, "rs")
return r ~= nil and not blocked[r] and not cells[key(x, y)]
end
local reach, queue, head = { [key(ex, ey)] = true }, { { ex, ey } }, 1
while queue[head] do
local p = queue[head]
head = head + 1
for _, d in ipairs({ { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }) do
local nx, ny = p[1] + d[1], p[2] + d[2]
if walk(nx, ny) and not reach[key(nx, ny)] then
reach[key(nx, ny)] = true
queue[#queue + 1] = { nx, ny }
end
end
end
local sides = true
for _, c in ipairs(Plaza.CELLS) do
for _, d in ipairs({ { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }) do
if not reach[key(c.x + d[1], c.y + d[2])] then sides = false end
end
end
check(sides, "every member cell is reachable from all four sides with the room full")
print("[test] 2F discovery and the added door")
local LayoutNative = require("src.core.game3.layout_native")
local function grid(w, h, fill)
local cells2 = {}
for i = 1, w * h do cells2[i] = { mid = fill, coll = 0, elev = 3 } end
return cells2
end
local twoF = grid(14, 9, 0x202)
twoF[0 * 14 + 5 + 1] = { mid = 0x25c, coll = 1, elev = 0 }
twoF[1 * 14 + 5 + 1] = { mid = 0x264, coll = 1, elev = 0 }
twoF[1 * 14 + 2 + 1] = { mid = 0x004, coll = 1, elev = 0 }
local maps = {
RU_OLDALE_TOWN_POKEMON_CENTER_2F = {
warps = { { x = 1, y = 5, destMap = "RU_OLDALE_TOWN_POKEMON_CENTER_1F", destWarp = 3 },
{ x = 5, y = 1, destMap = "RU_SINGLE_BATTLE_COLOSSEUM", destWarp = 1 } },
objects = { { x = 4, y = 2, graphicsId = 85 } },
midLayout = LayoutNative.fromDecoded({ width = 14, height = 9, cells = twoF }, "RU_OLDALE_TOWN_POKEMON_CENTER_2F", "p"),
},
RU_OLDALE_TOWN_POKEMON_CENTER_1F = {
warps = { { x = 1, y = 6, destMap = "RU_OLDALE_TOWN_POKEMON_CENTER_2F", destWarp = 1 } },
objects = { { x = 7, y = 2, graphicsId = 58 } },
midLayout = LayoutNative.fromDecoded({ width = 14, height = 9, cells = grid(14, 9, 0x202) }, "RU_OLDALE_TOWN_POKEMON_CENTER_1F", "p"),
},
RU_SINGLE_BATTLE_COLOSSEUM = { warps = {} },
RU_TRADE_CENTER = { warps = { { x = 5, y = 8, destMap = "RU_OLDALE_TOWN_POKEMON_CENTER_2F" } } },
}
maps.RU_OLDALE_TOWN_POKEMON_CENTER_1F.midLayout.cells[3 * 14 + 7 + 1].coll = 1
local found = UnionRs.discover(maps)
check(found.RU_OLDALE_TOWN_POKEMON_CENTER_2F ~= nil, "the 2F with a Colosseum bay is found")
eq(found.RU_OLDALE_TOWN_POKEMON_CENTER_1F, nil, "the 1F is not")
eq(found.RU_OLDALE_TOWN_POKEMON_CENTER_2F and found.RU_OLDALE_TOWN_POKEMON_CENTER_2F.oneF,
"RU_OLDALE_TOWN_POKEMON_CENTER_1F", "its origin is the 1F the stairs lead to")
local def = maps.RU_OLDALE_TOWN_POKEMON_CENTER_2F
local before = def.midLayout
UnionRs.patchLayout(def, "RU_OLDALE_TOWN_POKEMON_CENTER_2F")
check(def.midLayout ~= before, "the patch builds a fresh layout copy")
eq(before:midAt(2, 1), 0x004, "the cached layout is untouched")
eq(def.midLayout:midAt(2, 1), 0x264, "the added door copies the bay door")
eq(def.midLayout:midAt(2, 0), 0x25c, "with the wall above it")
eq(def.midLayout:midAt(4, 4), 0x202, "the rest of the floor is the same")
local again = def.midLayout
UnionRs.patchLayout(def, "RU_OLDALE_TOWN_POKEMON_CENTER_2F")
eq(def.midLayout, again, "patching twice is a no-op")
UnionRs._game = { data = { maps = maps } }
UnionRs.centers = found
eq(UnionRs.attendantGfx("RU_OLDALE_TOWN_POKEMON_CENTER_2F"), 85, "the attendant wears the bay attendant's graphics")
local o = UnionRs.originFor("RU_OLDALE_TOWN_POKEMON_CENTER_2F")
check(o and o.map == "RU_OLDALE_TOWN_POKEMON_CENTER_1F" and o.x == 7 and o.y == 4 and o.facing == "up",
"the origin is the cell in front of the 1F nurse desk")
eq(UnionRs.countMons({ party = { { species = 1 }, { species = 412, isEgg = true }, { species = 4 } } }), 2,
"eggs do not count toward entry")
print("[test] saves in the added room land in front of the nurse")
local Rules = require("src.core.game3.rse.union_rs_rules")
local Origin = require("src.online.union.Origin")
local s = { version = "ruby", map = "RU_UNION_ROOM", x = 12, y = 20, specialSaveWarpFlags = 0 }
Origin.record(s, { gen = 3, version = "ruby", map = o.map, x = o.x, y = o.y, facing = "up" })
local flags, warp = Rules.saveWarpFields(s)
eq(flags, 1, "a save in the room sets the continue warp")
check(warp and warp.map == o.map and warp.x == 7 and warp.y == 4, "pointing at the nurse front")
local m, x, y, f = Rules.saveLocation(s)
check(m == o.map and x == 7 and y == 4 and f == "up", "the saved location is the nurse front")
local s2 = { version = "ruby", map = "RU_OLDALE_TOWN", x = 5, y = 5, specialSaveWarpFlags = 0 }
local f2 = Rules.saveWarpFields(s2)
eq(f2, 0, "saves elsewhere keep the cart rules")
eq(Rules.saveLocation(s2), nil, "and their own location")
local loaded = { version = "ruby", map = "RU_UNION_ROOM", x = 12, y = 20, healMap = o.map }
Rules.useContinueGameWarp(loaded)
check(loaded.map == o.map and loaded.x == 7 and loaded.y == 4 and loaded.facing == "up",
"a save inside the room without a continue warp still loads at the nurse front")
local cont = { version = "ruby", map = "RU_UNION_ROOM", x = 12, y = 20, specialSaveWarpFlags = 1,
continueGameWarp = { map = o.map, x = 7, y = 4 } }
Rules.useContinueGameWarp(cont)
check(cont.map == o.map and cont.x == 7 and cont.y == 4 and cont.facing == "up", "the continue warp path faces the desk")
if failed > 0 then
print(("[FAIL] %d check(s) failed"):format(failed))
os.exit(1)
end
print("[PASS] union rs")