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

282 lines
11 KiB
Lua

local U = require("tests.drivers.util")
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/em_pokenav"
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_pokenav 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(30)
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 T = Flags.forVersion("emerald")
local Pokenav = require("src.ui.game3.rse.pokenav.init")
local session = Runtime.getSession()
if not check(session ~= nil, "session after new game") then return finish() 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 var(name) return tonumber(Flags.getVar(Space.store, nil, T.VAR_IDS[name])) or 0 end
local function setVar(name, v) Flags.setVar(Space.store, nil, T.VAR_IDS[name], v) end
local function mapNow() local s = Runtime.getSession() return s and s.map end
local function shot(name) U.shot(game, DIR .. "/" .. name .. ".png") 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 Pokenav.isOpen()
end
local function goTo(mapId, x, y, facing)
for _ = 1, 600 do
if not (Warp.isBusy() or Message.isOpen()) 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(2)
return ok
end
local function mash(pred, limit, onChoice)
local n = 0
for _ = 1, limit or 3000 do
if pred() then return true end
if Pokenav.isOpen() then return pred() end
if Choice.isOpen() then
if onChoice then onChoice() else U.tap(game, "a") end
U.wait(4)
elseif Message.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 function nav() return Pokenav.active() end
local function screen() local s = nav() return s and s.screen end
local function idle()
local s = nav()
return s and s.phase == "menu" and not s:busy(s.screenTask) and not s:busy(s.loopTask)
end
local function waitIdle(limit)
for _ = 1, limit or 600 do
if idle() then return true end
U.wait(1)
end
return idle()
end
local function press(btn, settleFrames)
U.tap(game, btn)
U.wait(2)
waitIdle()
U.wait(settleFrames or 2)
end
local QLR = require("src.core.game3.quest_log_recorder")
local qlLocation = QLR.location
QLR.location = function(g, s)
local ok, v = pcall(qlLocation, g, s)
if not ok then print("[driver] NOTE quest_log_recorder.location on Emerald: " .. tostring(v) .. " (crossfile W3a-UD adapters.lua)") end
return ok and v or ""
end
-- pokeemerald/data/maps/RustboroCity_DevonCorp_3F/scripts.inc:28
local party = session.party or {}
check(#party >= 0, "party present")
setVar("VAR_DEVON_CORP_3F_STATE", 0)
setVar("VAR_RUSTBORO_CITY_STATE", 5)
check(goTo("EM_RUSTBORO_CITY_DEVON_CORP_3F", 2, 1, "down"), "Devon Corp 3F loads")
local gotNav = mash(function() return flag("FLAG_SYS_POKENAV_GET") and not busy() end, 6000)
check(gotNav and flag("FLAG_RECEIVED_POKENAV"), "Mr. Stone gives the PokeNav (FLAG_SYS_POKENAV_GET, FLAG_RECEIVED_POKENAV)")
check(var("VAR_RUSTBORO_CITY_STATE") == 6, "VAR_RUSTBORO_CITY_STATE=6 after the PokeNav (" .. var("VAR_RUSTBORO_CITY_STATE") .. ")")
shot("01_devon_pokenav_received")
-- pokeemerald/data/maps/RustboroCity/scripts.inc:31
check(goTo("EM_RUSTBORO_CITY", 12, 15, "down"), "Rustboro loads with the scientist event armed")
local menuUp = mash(function() return Choice.isOpen() or Pokenav.isOpen() end, 4000)
check(menuUp and flag("FLAG_HAS_MATCH_CALL") and flag("FLAG_ADDED_MATCH_CALL_TO_POKENAV"),
"scientist adds Match Call and opens the tutorial start menu")
shot("02_tutorial_start_menu")
if Choice.isOpen() then
for _ = 1, 3 do U.tap(game, "down") U.wait(4) end
U.tap(game, "a")
end
for _ = 1, 200 do if Pokenav.isOpen() then break end U.wait(1) end
check(Pokenav.isOpen() and nav().tutorial, "POKeNAV row opens OpenPokenavForTutorial")
U.wait(10)
shot("03_tutorial_field_fade")
waitIdle(600)
still("04_tutorial_main_menu")
local s = nav()
check(s and s.menuId == "main_menu" and screen().menuType == 1, "tutorial main menu is UNLOCK_MC (menu type 1)")
check(screen() and screen().handler == "tutorial", "tutorial input handler (POKENAV_MODE_FORCE_CALL_READY)")
press("a")
check(nav() and nav().menuId == "main_menu", "A on HOENN MAP is refused during the tutorial")
press("down") press("down")
still("05_tutorial_cursor_match_call")
check(screen().currMenuItem == 2, "cursor on MATCH CALL (item " .. tostring(screen().currMenuItem) .. ")")
press("a")
waitIdle(900)
check(nav() and nav().menuId == "match_call", "MATCH CALL opens the trainer list (" .. tostring(nav() and nav().menuId) .. ")")
U.wait(20)
still("06_match_call_list_mr_stone")
local mc = screen()
local first = mc and mc.entries[1]
check(first and first.isSpecialTrainer and first.headerId == 0, "first entry is Mr. Stone (header 0)")
press("a")
still("07_call_options")
check(mc.handler == "options" and #mc.options == 2, "Mr. Stone has CALL/CANCEL (no check page)")
U.tap(game, "a")
U.wait(30)
still("08_calling_mr_stone")
for _ = 1, 4000 do
if mc.printer and not mc.printer:isActive() and idle() then break end
if mc.printer and mc.printer.state ~= "char" then U.tap(game, "a") end
U.wait(1)
end
still("09_mr_stone_message")
check(nav().mode == 2, "calling Mr. Stone moves the tutorial to FORCE_CALL_EXIT")
press("a")
U.wait(10)
print("[driver] after call: handler=" .. tostring(mc.handler) .. " menu=" .. tostring(nav() and nav().menuId))
press("b")
waitIdle(900)
print("[driver] after b: menu=" .. tostring(nav() and nav().menuId) .. " handler=" .. tostring(screen() and screen().handler))
check(nav() and nav().menuId == "main_menu_cursor_on_match_call", "B returns to the main menu on MATCH CALL")
still("10_back_main_menu")
press("b")
for _ = 1, 300 do if not Pokenav.isOpen() then break end U.wait(1) end
check(not Pokenav.isOpen(), "B switches the PokeNav off after the tutorial")
local done = mash(function() return var("VAR_RUSTBORO_CITY_STATE") == 7 and not busy() end, 4000)
check(done, "tutorial script finishes (VAR_RUSTBORO_CITY_STATE=7)")
shot("11_after_tutorial")
-- pokeemerald/src/start_menu.c:685
local Rematch = require("src.core.game3.rse.rematch")
local MatchCall = require("src.core.game3.rse.match_call")
for _, i in ipairs({ 10, 13, 21 }) do
Rematch.setFlag(session, Rematch.registeredFlagId(session, i), true)
Rematch.setFlag(session, Flags.trainerFlagId(Rematch.trainerIds(i)[1]), true)
end
Rematch.set(session, 10, 1)
setFlag("FLAG_ENABLE_MOM_MATCH_CALL", true)
goTo("EM_ROUTE104", 12, 30, "down")
U.wait(30)
Pokenav.show({ session = session, game = game })
waitIdle(900)
still("12_main_menu")
check(nav().menuId == "main_menu" and screen().handler == "main", "normal PokeNav main menu")
check(screen().nearbyRematch == true, "blue light: Cindy (Route 104) wants a rematch nearby")
press("a")
waitIdle(900)
check(nav().menuId == "region_map", "HOENN MAP opens the region map")
U.wait(10)
still("13_hoenn_map_full")
local map = screen()
check(map.rm.mapSecId ~= nil, "region map cursor starts on the player's section (" .. tostring(map.rm.mapSecName) .. ")")
press("a")
U.wait(10)
still("14_hoenn_map_zoom")
check(map.zoomed == true, "A zooms the map in")
U.hold(game, "right", 24)
for _ = 1, 60 do if map.inputFn == "zoomed" then break end U.wait(1) end
waitIdle()
U.wait(4)
still("15_hoenn_map_zoom_right")
press("a")
still("16_hoenn_map_zoom_out")
check(map.zoomed == false, "A zooms back out")
press("b")
waitIdle(900)
check(nav().menuId == "main_menu_cursor_on_map", "B returns to the main menu")
check(session.regionMapZoom == false, "zoom state saved to regionMapZoom")
press("down") press("down")
press("a")
waitIdle(900)
mc = screen()
check(nav().menuId == "match_call", "MATCH CALL list opens")
U.wait(20)
still("17_match_call_list")
local names = {}
for i, e in ipairs(mc.entries) do
local d, n = MatchCall.entryNameAndDesc(e)
names[i] = tostring(n)
end
print("[driver] list: " .. table.concat(names, ","))
check(#mc.entries >= 5, "registered entries listed (" .. #mc.entries .. ")")
local cindyRow
for i, e in ipairs(mc.entries) do
if not e.isSpecialTrainer and e.headerId == 10 then cindyRow = i end
end
check(cindyRow ~= nil, "Cindy (rematch table 10) is in the list")
for _ = 1, (cindyRow or 1) - 1 do press("down") end
still("18_cursor_on_cindy")
press("a")
check(#mc.options == 3, "a trainer has CALL/CHECK/CANCEL")
press("down")
press("a")
waitIdle(600)
U.wait(10)
still("19_check_page_cindy")
check(mc.checkPage == true and mc.picId ~= nil and mc.picId >= 0, "CHECK shows the check page with the trainer pic")
press("b")
waitIdle(600)
still("20_back_to_list")
press("a")
press("a")
U.wait(40)
still("21_calling_cindy")
for _ = 1, 4000 do
if mc.printer and not mc.printer:isActive() and idle() then break end
if mc.printer and mc.printer.state ~= "char" then U.tap(game, "a") end
U.wait(1)
end
still("22_cindy_message")
press("a")
press("b")
waitIdle(900)
press("b")
for _ = 1, 300 do if not Pokenav.isOpen() then break end U.wait(1) end
check(not Pokenav.isOpen(), "switch off closes the PokeNav")
U.wait(30)
shot("23_field_after")
finish()
end