Files
gen1recomp/tests/game3_stitchseam_mod_collision_test.lua

190 lines
6.8 KiB
Lua

#!/usr/bin/env luajit
-- pokefirered/src/event_object_movement.c:4830 GetCollisionAtCoords
-- pokefirered/src/event_object_movement.c:4889 IsMetatileDirectionallyImpassable
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 function finish()
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
local GameVersion = require("src.core.GameVersion")
GameVersion.set("firered")
local Cache = require("tests.game3_cache")
local cacheRoot = Cache.mount("scripts/events.lua", { native = true })
if not cacheRoot then
print("[skip] game3_stitchseam_mod_collision_test: " .. tostring(Cache.reason))
finish()
end
print("[info] FireRed cache at " .. cacheRoot)
local Dataset = require("src.core.game3.dataset")
local Collision = require("src.core.game3.collision")
local Objects = require("src.core.game3.objects")
local Player = require("src.core.game3.player")
local Field = require("src.core.game3.field")
local Runtime = require("src.core.game3.runtime")
local Map = require("src.core.game3.map")
local Gen3Compat = require("src.mods.Gen3Compat")
local WorldAPI = require("src.world.game3.WorldAPI")
local game = { data = {} }
Dataset.hydrate(game)
local session = { map = nil, flags = {}, vars = {} }
game.session = session
Runtime.session = session
Gen3Compat.bind(function() return game end)
-- pokefirered/include/constants/metatile_behaviors.h:41
local MB_IMPASSABLE_NORTH = 0x32
local BAND = "FR_VICTORY_ROAD_1F"
local NAOMI = 2
local BX, BY = 14, 5
local bandDef = game.data.maps[BAND]
check(bandDef ~= nil, BAND .. " is in the cache")
if not bandDef then finish() end
Field._game = game
session.map = BAND
Field._session = session
Field.running = true
Field.locked = false
Map.load(nil, game, BAND)
Player.reset(11, 9, "down")
eq(Map.current, BAND, "the real map loader put the engine on " .. BAND)
eq(Collision.behavior(BX, BY), MB_IMPASSABLE_NORTH,
string.format("(%d,%d) carries MB_IMPASSABLE_NORTH", BX, BY))
check(Collision.isWalkable(BX, BY - 1), "(14,4) north of it is open cave floor")
check(Collision.isWalkable(BX, BY + 1), "(14,6) south of it is open cave floor")
print("[test] 1. mod.world npc:canStep obeys the direction-blocked metatile")
local api = WorldAPI.new(game, "stitchseam-test")
-- pokefirered/src/scrcmd.c:1074 ScrCmd_setobjectxy
Objects.setObjectXY(NAOMI, BX, BY)
local handle, why = api:npc(BAND, NAOMI)
check(handle ~= nil, "mod.world:npc handed back a live object handle (" ..
tostring(why) .. ")")
if not handle then finish() end
eq(select(1, handle:position()), BX, "the handle stands on the banded cell (x)")
eq(select(2, handle:position()), BY, "the handle stands on the banded cell (y)")
eq(handle:canStep("up"), false,
"canStep refuses the northbound step off MB_IMPASSABLE_NORTH")
eq(handle:canStep("down"), true, "and allows the southbound step off it")
eq(Collision.behavior(BX - 1, BY), MB_IMPASSABLE_NORTH,
"(13,5) carries the same band, so the seal runs along the row")
eq(handle:canStep("left"), true,
"and the band is sealed north only, so she may still walk west along it")
print("[test] 2. and the enter-blocked side of the same rule")
Objects.setObjectXY(NAOMI, BX, BY - 1)
local above = api:npc(BAND, NAOMI)
check(above ~= nil, "the handle now stands north of the band")
if not above then finish() end
eq(above:canStep("down"), false,
"canStep refuses entering MB_IMPASSABLE_NORTH from the north")
print("[test] 3. a land object no longer borrows the player's surfing flag")
local SHORE = "FR_PALLET_TOWN"
local BEACH_X, BEACH_Y = 8, 16
local shoreDef = game.data.maps[SHORE]
check(shoreDef ~= nil, SHORE .. " is in the cache")
if not shoreDef then finish() end
session.map = SHORE
Map.load(nil, game, SHORE)
Player.reset(5, 14, "down")
eq(Map.current, SHORE, "the engine is on " .. SHORE)
check(Collision.isWater(BEACH_X, BEACH_Y + 1) == true, "(8,17) is the ocean")
check(Collision.isWater(BEACH_X, BEACH_Y) == false, "(8,16) is the beach above it")
Objects.setObjectXY(1, BEACH_X, BEACH_Y)
local walker = api:npc(SHORE, 1)
check(walker ~= nil, "a Pallet Town object handle is live")
if not walker then finish() end
-- pokefirered/src/event_object_movement.c:8346 IsElevationMismatchAt
Player.surfing = true
eq(walker:canStep("down"), false,
"canStep keeps the beach walker out of the sea while the player surfs")
Player.surfing = false
eq(walker:canStep("down"), false, "and keeps her out of it on land too")
eq(walker:canStep("up"), true, "while she may still walk north along the beach")
print("[test] 4. an object that stands on water keeps the swimmer's verdict")
Objects.setObjectXY(1, BEACH_X, BEACH_Y + 1)
local swimmer = api:npc(SHORE, 1)
check(swimmer ~= nil, "the same object now treads water at (8,17)")
if not swimmer then finish() end
eq(swimmer:canStep("down"), true, "it may swim further out to (8,18)")
eq(swimmer:canStep("up"), false, "and may not climb out onto the beach")
Player.surfing = true
eq(swimmer:canStep("up"), false, "not even while the player is surfing")
Player.surfing = false
print("[test] 5. the src.world.Collision facade passes the same context")
session.map = BAND
Map.load(nil, game, BAND)
Player.reset(11, 9, "down")
Objects.setObjectXY(NAOMI, 11, 12)
local Facade = Gen3Compat.resolve("src.world.Collision")
check(type(Facade) == "table" and type(Facade.canMove) == "function",
"mods reach Collision.canMove through the Gen 3 facade")
if not (Facade and Facade.canMove) then finish() end
local mover = { cellX = BX, cellY = BY, surfing = false }
local okUp, whyUp = Facade.canMove(nil, {}, mover, "up")
eq(okUp, false, "canMove refuses the northbound step off MB_IMPASSABLE_NORTH")
eq(whyUp, "tile", "and reports it as a tile refusal")
eq(Facade.canMove(nil, {}, mover, "down"), true,
"and allows the southbound step off it")
local moverAbove = { cellX = BX, cellY = BY - 1, surfing = false }
eq(Facade.canMove(nil, {}, moverAbove, "down"), false,
"canMove refuses entering MB_IMPASSABLE_NORTH from the north")
print("[test] 6. the facade still answers with the mover's own surfing flag")
session.map = SHORE
Map.load(nil, game, SHORE)
Player.reset(5, 14, "down")
Objects.setObjectXY(1, 5, 10)
local rider = { cellX = BEACH_X, cellY = BEACH_Y + 1, surfing = true }
eq(Facade.canMove(nil, {}, rider, "up"), true,
"a surfing mover may come ashore onto the beach")
local lander = { cellX = BEACH_X, cellY = BEACH_Y, surfing = false }
local okSea, whySea = Facade.canMove(nil, {}, lander, "down")
eq(okSea, false, "a walking mover may not step into the sea")
eq(whySea, "tile", "and the sea refusal reaches the mod as a tile refusal")
finish()