mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-29 15:08:50 -04:00
282 lines
11 KiB
Lua
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
|