Files
gen1recomp/tests/game3_field_boulder_test.lua

235 lines
8.7 KiB
Lua

#!/usr/bin/env luajit
-- pokefirered/src/field_player_avatar.c:1452 DoBoulderFinish
-- pokefirered/src/field_control_avatar.c:1066 HandleBoulderFallThroughHole
-- pokefirered/src/field_control_avatar.c:1076 HandleBoulderActivateVictoryRoadSwitch
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 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_field_boulder_test: " .. tostring(Cache.reason))
os.exit(0)
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 FieldMoves = require("src.core.game3.field_moves")
local Space = require("src.core.game3.scripting.space")
local Flags = require("src.core.game3.scripting.flags")
local Runtime = require("src.core.game3.runtime")
local game = { data = {} }
Dataset.hydrate(game)
local session = { map = nil, flags = {}, vars = {} }
game.session = session
Runtime.session = session
local VR = "FR_VICTORY_ROAD_1F"
local SEAFOAM = "FR_SEAFOAM_ISLANDS_B3F"
-- pokefirered/data/maps/Route23/scripts.inc:8
local ELSEWHERE = "FR_VICTORY_ROAD_2F"
-- pokefirered/include/constants/vars.h:152
local VAR_VR1F = 16484
local FLAG_HIDE_SEAFOAM_B4F_BOULDER_1 = 76
local FLAG_HIDE_SEAFOAM_B3F_BOULDER_3 = 72
local function enterMap(mapId)
local def = game.data.maps[mapId]
if not def then return nil end
Field._game = game
session.map = mapId
Field._session = session
Field.running = true
Field.locked = false
Field.clearMetatiles()
Space.activate(nil, mapId, game, nil)
Collision.bindMap(game, mapId, def)
Objects.loadMap(game, mapId, def)
Space.runOnLoad(mapId)
return def
end
local function pumpVm(limit)
local vm = Space.vm
if not vm then return 0 end
local n = 0
while vm:isRunning() and n < (limit or 512) do
vm:tick()
n = n + 1
end
return n
end
local function standAt(x, y, facing)
Player.moving = false
Player.progress = 0
Player.surfing = false
Player.biking = false
Player.cellX, Player.cellY = x, y
Player.targetX, Player.targetY = x, y
Player.px, Player.py = x * 16, y * 16
Player.facing = facing or "down"
end
-- pokefirered/src/event_object_movement.c:8959 UpdateWalkSlowerAnim
local function pumpPush(frames)
for _ = 1, frames or 33 do
Objects.update(game)
Player.update(game, nil)
end
end
print("[test] 1. Victory Road 1F: the floor switch coord event is var-gated shut")
local vrDef = enterMap(VR)
check(vrDef ~= nil, VR .. " is in the cache")
if not vrDef then finish() end
local switch = nil
for _, ev in ipairs(vrDef.coordEvents or {}) do
if ev.x == 20 and ev.y == 16 then switch = ev end
end
check(switch ~= nil and switch.scriptKey ~= nil, "a coord event sits on (20,16)")
if not switch then finish() end
check(tonumber(switch.var) == VAR_VR1F and tonumber(switch.value) == 99,
"it wants VAR_MAP_SCENE_VICTORY_ROAD_1F == 99 (" .. tostring(switch.value) .. ")")
check(Collision.behavior(20, 16) == 0x20, "(20,16) carries MB_STRENGTH_BUTTON")
check(Flags.getVar(Space.store, Space.vm.ctx, VAR_VR1F) ~= 99,
"the var never reaches 99, so the player path can never match")
local barrierTop = Field.metatileOverrideAt(VR, 12, 14)
local barrierBottom = Field.metatileOverrideAt(VR, 12, 15)
check(barrierTop ~= nil and barrierTop.impassable == true,
"ON_LOAD walled (12,14) with the rock barrier")
check(barrierBottom ~= nil and barrierBottom.impassable == true,
"ON_LOAD walled (12,15) with the rock barrier")
check(Collision.canEnter(game, 12, 15, { fromX = 12, fromY = 16, dir = "up" }) == false,
"the barrier blocks the way to the Elite Four")
print("[test] 2. a player standing on the switch does NOT fire it")
standAt(20, 16, "down")
check(Field.tryCoordEvents(game, 20, 16) == false,
"Field.tryCoordEvents refuses the var-gated trigger")
check(Flags.getVar(Space.store, Space.vm.ctx, VAR_VR1F) == 0,
"the var is untouched")
print("[test] 3. a boulder pushed onto the switch fires it anyway")
Flags.setFlag(Space.store, nil, FieldMoves.SYS_FLAGS.USE_STRENGTH, true)
local boulder = nil
for _, lid in ipairs({ 5, 6, 7 }) do
local eo = Objects.find(lid)
if eo and eo.graphicsId == FieldMoves.GFX_IDS.PUSHABLE_BOULDER then
boulder = boulder or eo
end
end
check(boulder ~= nil, "a pushable boulder is on the map")
if not boulder then finish() end
Objects.setObjectXY(boulder.localId, 20, 15)
standAt(20, 14, "down")
local result = Player.tryMove("down", game, false)
check(result == "push", "bumping the boulder southward pushes it (" .. tostring(result) .. ")")
check(Player.cellX == 20 and Player.cellY == 14 and not Player.moving,
"the player walks in place instead of stepping")
check(boulder.moving == true and boulder.stepFrames == 32,
"the boulder starts a 32-frame slide")
check(Flags.getVar(Space.store, Space.vm.ctx, VAR_VR1F) ~= 100,
"the switch has not fired before the slide ends")
pumpPush(31)
check(boulder.moving == true and Player.boulderPush ~= nil,
"the push is still running on frame 31")
pumpPush(2)
check(Player.boulderPush == nil, "the push task finished")
check(boulder.cellX == 20 and boulder.cellY == 16,
string.format("the boulder landed on the switch (%d,%d)", boulder.cellX, boulder.cellY))
pumpVm()
check(Flags.getVar(Space.store, Space.vm.ctx, VAR_VR1F) == 100,
"the floor-switch script ran and set the var to 100")
local openTop = Field.metatileOverrideAt(VR, 12, 14)
local openBottom = Field.metatileOverrideAt(VR, 12, 15)
check(openTop ~= nil and openTop.impassable == false,
"(12,14) is open floor now")
check(openBottom ~= nil and openBottom.impassable == false,
"(12,15) is open floor now")
check(Collision.canEnter(game, 12, 15, { fromX = 12, fromY = 16, dir = "up" }) == true,
"the way north is walkable")
print("[test] 4. the barrier stays down across a map reload")
enterMap(ELSEWHERE)
enterMap(VR)
check(Flags.getVar(Space.store, Space.vm.ctx, VAR_VR1F) == 100, "the var survived")
check(Field.metatileOverrideAt(VR, 12, 14) == nil,
"ON_LOAD did not re-wall (12,14)")
check(Field.metatileOverrideAt(VR, 12, 15) == nil,
"ON_LOAD did not re-wall (12,15)")
check(Collision.canEnter(game, 12, 15, { fromX = 12, fromY = 16, dir = "up" }) == true,
"the way north is still walkable")
print("[test] 5. Seafoam B3F: a boulder pushed into a hole falls through")
local sfDef = enterMap(SEAFOAM)
check(sfDef ~= nil, SEAFOAM .. " is in the cache")
if not sfDef then finish() end
check(Collision.behavior(6, 18) == 0x66, "(6,18) carries MB_FALL_WARP")
local sfBoulder = Objects.find(6)
check(sfBoulder ~= nil and sfBoulder.cellX == 6 and sfBoulder.cellY == 17,
"boulder 6 starts at (6,17)")
if not sfBoulder then finish() end
check(tonumber(sfBoulder.trainerType) == FLAG_HIDE_SEAFOAM_B4F_BOULDER_1,
"its trainerType carries the B4F reveal flag (" .. tostring(sfBoulder.trainerType) .. ")")
Flags.setFlag(Space.store, nil, FLAG_HIDE_SEAFOAM_B4F_BOULDER_1, true)
Flags.setFlag(Space.store, nil, FieldMoves.SYS_FLAGS.USE_STRENGTH, true)
standAt(6, 16, "down")
local r2 = Player.tryMove("down", game, false)
check(r2 == "push", "the boulder is pushed south into the hole (" .. tostring(r2) .. ")")
check(sfBoulder.visible ~= false and sfBoulder.moving == true,
"the boulder is still sliding toward the hole")
pumpPush(33)
check(Player.cellX == 6 and Player.cellY == 16, "the player never left (6,16)")
check(sfBoulder.visible == false and sfBoulder.hidden == true,
"the boulder is gone from the floor")
check(Objects.at(6, 18) == nil, "nothing stands on the hole any more")
check(Flags.getFlag(Space.store, nil, FLAG_HIDE_SEAFOAM_B4F_BOULDER_1) == false,
"the B4F reveal flag is cleared")
-- pokefirered/src/event_object_movement.c:1525 RemoveObjectEventByLocalIdAndMap
check(Flags.getFlag(Space.store, nil, FLAG_HIDE_SEAFOAM_B3F_BOULDER_3) == true,
"the boulder's own hide flag IS set, as RemoveObjectEventByLocalIdAndMap does")
print("[test] 6. the reveal survives a map reload, the boulder stays gone")
enterMap(ELSEWHERE)
enterMap(SEAFOAM)
check(Flags.getFlag(Space.store, nil, FLAG_HIDE_SEAFOAM_B4F_BOULDER_1) == false,
"the B4F boulder stays revealed")
local sfAgain = Objects.find(6)
check(sfAgain == nil or sfAgain.visible ~= true or sfAgain.hidden == true,
"the B3F boulder stays gone after the reload, as on the cart")
finish()