Files
gen1recomp/tests/drivers/em_match_call_rematch.lua
T
bryanthaboi 690f0cd9a1 TOUCHDOWN
2026-09-27 16:07:31 -04:00

242 lines
11 KiB
Lua

local U = require("tests.drivers.util")
local L = require("tests.drivers.em_battle_loop")
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/em_match_call_rematch"
local failures = 0
local function check(ok, label)
print((ok and "PASS " or "FAIL ") .. label)
if not ok then failures = failures + 1 end
return ok
end
local function finish()
print((failures == 0 and "PASS" or "FAIL") .. " em_match_call_rematch failures=" .. failures)
love.event.quit(failures == 0 and 0 or 1)
end
local function try(label, fn)
local ok, err = xpcall(fn, debug.traceback)
if not ok then print("[driver] " .. label .. " error: " .. tostring(err)) end
return ok
end
return function(game)
for _ = 1, 900 do
if game.phase == "boot" and game.boot then break end
U.wait(1)
end
if not check(game.boot ~= nil, "boot reached") then return finish() end
try("new_game", function() game:_handleBootAction({ action = "new_game", name = "BRENDAN", gender = 0 }) end)
U.wait(60)
local Runtime = require("src.core.game3.runtime")
local Map = require("src.core.game3.map")
local Player = require("src.core.game3.player")
local Warp = require("src.core.game3.warp")
local Message = require("src.ui.game3.message")
local Choice = require("src.ui.game3.choice")
local Field = require("src.core.game3.field")
local Space = require("src.core.game3.scripting.space")
local Flags = require("src.core.game3.scripting.flags")
local Party = require("src.core.game3.party")
local Battle = require("src.core.game3.battle")
local Rng = require("src.core.game3.rng")
local Rtc = require("src.core.game3.rtc")
local Encounters = require("src.core.game3.encounters")
local Trainers = require("src.core.game3.scripting.trainers")
local StepEvents = require("src.core.game3.step_events")
local Rematch = require("src.core.game3.rse.rematch")
local MatchCall = require("src.core.game3.rse.match_call")
local CallWindow = require("src.ui.game3.rse.pokenav.call_window")
local C = require("src.core.game3.constants").of("emerald")
local T = Flags.forVersion("emerald")
local session = Runtime.getSession()
if not check(session ~= nil, "session after new game") then return finish() end
Encounters.onStep = function() return nil end
local function flag(name) return Flags.getFlag(Space.store, nil, T.IDS[name]) == true end
local function setFlag(name, on) Flags.setFlag(Space.store, nil, T.IDS[name], on ~= false) end
local function mapNow() local s = Runtime.getSession() return s and s.map end
local function still(name) U.still(game, DIR .. "/" .. name .. ".png") end
local function busy()
return (Space.vm and Space.vm:isRunning()) or Warp.isBusy() or Message.isOpen() or Field.locked
or Player.moving or Choice.isOpen() or Battle.isActive() or CallWindow.isOpen() or StepEvents.busy()
end
local function goTo(mapId, x, y, facing)
for _ = 1, 600 do
if not busy() then break end
if Message.isOpen() then U.tap(game, "b") end
U.wait(1)
end
local ok = try("Map.load " .. mapId, function() Map.load(nil, game, mapId, { x = x, y = y, facing = facing }) end)
local s = Runtime.getSession()
if s then s.x, s.y, s.facing = x, y, facing end
Player.cellX, Player.cellY = x, y
Player.px, Player.py = x * 16, y * 16
Player.targetX, Player.targetY = x, y
Player.facing = facing
U.wait(20)
return ok
end
local function mash(pred, limit)
local n = 0
for _ = 1, limit or 3000 do
if pred() then return true end
if Battle.isActive() then return pred() end
if Message.isOpen() or Choice.isOpen() or CallWindow.isOpen() then
n = n + 1
if n % 4 == 0 then U.tap(game, "a") else U.wait(1) end
else
U.wait(1)
end
end
return pred()
end
local battles = {}
local function fight(label)
for _ = 1, 600 do if Battle.isActive() then break end if Message.isOpen() then U.tap(game, "a") end U.wait(1) end
if not check(Battle.isActive(), label .. " starts") then return false end
local st0 = Battle._st
local foeMon = st0 and st0.foeParty and st0.foeParty[1]
battles[#battles + 1] = { trainerId = st0 and st0.trainerId, species = foeMon and foeMon.species }
local ok = L.run(game, {})
local st = Battle._st
check(ok and st and st.result == "win", label .. " won (" .. tostring(st and st.result) .. ")")
for _ = 1, 1200 do
if not Battle.isActive() and not busy() then break end
if Message.isOpen() or Choice.isOpen() then U.tap(game, "a") end
U.wait(2)
end
return true
end
session.party = {}
Party.giveMon(session, C.species.byName.SPECIES_SWAMPERT, 70, "SWAMPERT")
setFlag("FLAG_SYS_POKENAV_GET")
setFlag("FLAG_RECEIVED_POKENAV")
setFlag("FLAG_HAS_MATCH_CALL")
setFlag("FLAG_ADDED_MATCH_CALL_TO_POKENAV")
local CINDY = C.trainers.byName.TRAINER_CINDY_1
local CINDY_3 = C.trainers.byName.TRAINER_CINDY_3
local idx = Rematch.firstBattleTableId(CINDY)
check(idx == 10 and Rematch.trainerIds(idx)[2] == CINDY_3, "gRematchTable[REMATCH_CINDY] = { CINDY_1, CINDY_3, ... }")
-- pokeemerald/data/maps/Route104/scripts.inc:924
check(goTo("EM_ROUTE104", 11, 43, "down"), "Route 104 loads next to Cindy")
U.tap(game, "a")
if not fight("first battle with Cindy") then return finish() end
mash(function() return not busy() end, 3000)
check(Flags.getFlag(Space.store, nil, Flags.trainerFlagId(CINDY)), "TRAINER_CINDY_1 flag set after the win")
check(Rematch.flag(session, Rematch.registeredFlagId(session, idx)), "register_matchcall registered Cindy (SetMatchCallRegisteredFlag)")
still("01_cindy_registered")
for i = 1, 5 do setFlag(string.format("FLAG_BADGE%02d_GET", i)) end
local before = MatchCall.stepHookCalls or 0
check(goTo("EM_PETALBURG_CITY", 15, 12, "left"), "Petalburg City loads (outdoor, not Cindy's route)")
local Collision = require("src.core.game3.collision")
local function open(x, y)
local function can(fx, fy, tx, ty, dir)
return Collision.canEnter(game, tx, ty, { fromX = fx, fromY = fy, dir = dir, elevation = Player.currentElevation })
end
return can(x, y, x - 1, y, "left") and can(x - 1, y, x, y, "right")
end
local spot
for y = 8, 24 do
for x = 6, 26 do
if not spot and open(x, y) and open(x + 1, y) then spot = { x + 1, y } end
end
end
if spot then goTo("EM_PETALBURG_CITY", spot[1], spot[2], "left") end
print("[driver] walking spot " .. tostring(spot and (spot[1] .. "," .. spot[2])))
Rng.SeedRng(0x2468)
MatchCall.initCounters(session)
local called, steps = false, 0
local dirs = { "left", "right" }
for i = 1, 200 do
-- pokeemerald/src/match_call.c:1041
if i % 10 == 9 then Rtc.advance(10) end
local dir = dirs[(i % 2) + 1]
U.hold(game, dir, 16)
for _ = 1, 40 do if not Player.moving then break end U.wait(1) end
steps = steps + 1
if i == 1 then
check((MatchCall.stepHookCalls or 0) > before, "step_events drives the match call step counter")
end
if CallWindow.isOpen() or StepEvents.busy() then called = true break end
if i % 25 == 0 then
local st = MatchCall._state or {}
local def = MatchCall.currentDef() or {}
print(string.format("[driver] step %d cell=%s,%s map=%s mc.steps=%s mc.minutes=%s now=%s allows=%s reg=%d type=%s sec=%s",
i, tostring(Player.cellX), tostring(Player.cellY), tostring(mapNow()), tostring(st.stepCounter), tostring(st.minutes),
tostring(MatchCall.totalMinutes(session)), tostring(MatchCall.mapAllowsMatchCall(session)),
MatchCall.numRegisteredTrainers(session), tostring(def.mapType), tostring(def.regionMapSectionId)))
end
end
for _ = 1, 60 do if CallWindow.isOpen() then break end U.wait(1) end
check(called and CallWindow.isOpen(), "a match call rings within " .. steps .. " steps (seed 0x2468)")
local last = MatchCall.lastCall
check(last and last.trainerId == CINDY, "Cindy is the caller (" .. tostring(last and last.trainerId) .. ")")
U.wait(12)
still("02_call_window_slide_in")
local cw = CallWindow.active()
if not cw then return finish() end
for _ = 1, 600 do
if cw.state >= 6 then break end
if cw.printer and cw.printer.state ~= "char" then U.tap(game, "a") end
U.wait(1)
end
U.wait(40)
still("03_call_message")
local sel = cw.selected
check(sel and sel.key and sel.key:find("DifferentRouteBattleRequest") ~= nil,
"five badges: Cindy asks for a rematch (" .. tostring(sel and sel.key) .. ")")
check(sel and sel.newRematchRequest == true, "the call is a new rematch request")
for _ = 1, 1200 do
if not CallWindow.isOpen() then break end
if cw.printer and cw.printer.state ~= "char" then U.tap(game, "a") elseif cw.state == 6 then U.tap(game, "a") end
U.wait(1)
end
check(not CallWindow.isOpen(), "the call hangs up")
check(Rematch.get(session, idx) == 1, "trainerRematches[REMATCH_CINDY] = 1 after UpdateRematchIfDefeated")
check(goTo("EM_ROUTE104", 11, 43, "down"), "back on Route 104")
battles = {}
U.tap(game, "a")
if not fight("rematch with Cindy") then return finish() end
local b = battles[#battles]
check(b and b.trainerId == CINDY_3, "rematch battle uses the next party TRAINER_CINDY_3 (" .. tostring(b and b.trainerId) .. ")")
local want = Trainers.get(CINDY_3)
check(b and want and b.species == (want.party[1] and want.party[1].species), "foe lead is CINDY_3's first mon")
mash(function() return not busy() end, 3000)
check(Flags.getFlag(Space.store, nil, Flags.trainerFlagId(CINDY_3)), "TRAINER_CINDY_3 flag set after the rematch")
check(Rematch.get(session, idx) == 0, "trainerRematches[REMATCH_CINDY] cleared after the rematch")
still("04_after_rematch")
-- pokeemerald/src/field_specials.c:353
setFlag("FLAG_ENABLE_FIRST_WALLY_POKENAV_CALL")
Flags.setVar(Space.store, nil, T.VAR_IDS.VAR_WALLY_CALL_STEP_COUNTER, 247)
check(goTo("EM_PETALBURG_CITY", spot and spot[1] or 15, spot and spot[2] or 12, "left"), "back in Petalburg for Wally's call")
local wallyWindow = false
for i = 1, 6 do
U.hold(game, dirs[(i % 2) + 1], 16)
for _ = 1, 60 do
if CallWindow.isOpen() then wallyWindow = true break end
U.wait(1)
end
if wallyWindow then break end
end
check(wallyWindow, "VAR_WALLY_CALL_STEP_COUNTER reaching 250 starts MauvilleCity_EventScript_RegisterWallyCall")
local wcw = CallWindow.active()
check(wcw and wcw.text ~= nil, "pokenavcall opens the PokeNav call window with the script text")
for _ = 1, 400 do
if wcw and wcw.state >= 6 and not (wcw.printer and wcw.printer:isActive()) then break end
if wcw and wcw.printer and wcw.printer.state ~= "char" then U.tap(game, "a") end
U.wait(1)
end
still("05_wally_pokenavcall")
local registered = mash(function() return flag("FLAG_ENABLE_WALLY_MATCH_CALL") and not busy() end, 4000)
check(registered and not flag("FLAG_ENABLE_FIRST_WALLY_POKENAV_CALL"), "Wally's call registers him (FLAG_ENABLE_WALLY_MATCH_CALL)")
still("06_after_wally_call")
finish()
end