Files
gen1recomp/tests/drivers/game3_ui_party_dig.lua
2026-09-23 16:34:15 -04:00

345 lines
13 KiB
Lua

local U = require("tests.drivers.util")
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/game3_ui_party_dig"
local PALLET = "FR_PALLET_TOWN"
local MT_MOON = "FR_MT_MOON_1F"
-- pokefirered/include/constants/flags.h:1090
local FLAG_BADGE03_GET = 0x822
local failures = 0
local seenCtx = nil
local function say(line)
print(line)
os.execute('mkdir -p "' .. DIR .. '" 2>/dev/null')
local fh = io.open(DIR .. "/ui_party_dig.log", "a")
if fh then
fh:write(line, "\n")
fh:close()
end
end
local function result(ok, label)
say((ok and "PASS " or "FAIL ") .. label)
if not ok then failures = failures + 1 end
return ok
end
local function finish()
if failures == 0 then
say("PASS ui_party_dig")
love.event.quit(0)
else
say("FAIL ui_party_dig failures=" .. failures)
love.event.quit(1)
end
end
local function run(game)
for _ = 1, 900 do
if game.phase == "boot" and game.boot then break end
U.wait(1)
end
game:_handleBootAction({ action = "new_game", name = "RED" })
U.wait(240)
local Runtime = require("src.core.game3.runtime")
local Map = require("src.core.game3.map")
local Space = require("src.core.game3.scripting.space")
local Flags = require("src.core.game3.scripting.flags")
local Player = require("src.core.game3.player")
local Party = require("src.core.game3.party")
local PartyMenu = require("src.ui.game3.party_menu")
local StartMenu = require("src.ui.game3.start_menu")
local RegionMap = require("src.ui.game3.region_map")
local Message = require("src.ui.game3.message")
local FieldMoves = require("src.core.game3.field_moves")
local realFromMenu = FieldMoves.fromMenu
FieldMoves.fromMenu = function(moveId, moveCtx)
seenCtx = moveCtx
return realFromMenu(moveId, moveCtx)
end
local session = Runtime.getSession()
if not result(session ~= nil, "new game reached the game3 field") then return finish() end
local function ctx() return Space.vm and Space.vm.ctx end
Flags.setFlag(Space.store, ctx(), FLAG_BADGE03_GET, true)
session.party = {}
require("src.core.game3.scripting.flags").setFlag(require("src.core.game3.scripting.space").store, nil, 0x828, true) -- data/maps/PalletTown_ProfessorOaksLab/scripts.inc:1120
Party.giveMon(session, 6, 40)
local mon = session.party[1]
mon.moves = { "FLY", "DIG", "TELEPORT", "EMBER" }
mon.pp = { 15, 10, 20, 25 }
result(#session.party == 1, "party has one mon that knows FLY, DIG and TELEPORT")
local function goTo(mapId, x, y, facing)
Map.load(nil, game, mapId, { x = x, y = y, facing = facing or "down" })
if game.session then
game.session.x, game.session.y, game.session.facing = x, y, facing or "down"
end
Player.cellX, Player.cellY = x, y
Player.px, Player.py = x * 16, y * 16
Player.targetX, Player.targetY = x, y
Player.facing = facing or "down"
U.wait(90)
end
local function dismiss(frames)
for _ = 1, frames or 20 do
if Message.isWaiting and Message.isWaiting() then U.tap(game, "a") end
U.wait(4)
end
end
local function openParty()
for _ = 1, 40 do
if PartyMenu.isOpen and PartyMenu.isOpen() then return true end
if StartMenu.isOpen and StartMenu.isOpen() then break end
U.tap(game, "start")
U.wait(8)
end
for _ = 1, 20 do
local e = StartMenu.ENTRIES and StartMenu.ENTRIES[StartMenu.cursor]
if e and e.id == "pokemon" then break end
U.tap(game, "down")
U.wait(6)
end
U.tap(game, "a")
for _ = 1, 60 do
if PartyMenu.isOpen and PartyMenu.isOpen() then return true end
U.wait(4)
end
return false
end
local function chooseAction(label)
U.tap(game, "a")
U.wait(12)
local target = nil
for i, name in ipairs(PartyMenu.ACTIONS or {}) do
if name == label then target = i end
end
if not target then return false end
for _ = 1, 12 do
if PartyMenu.actionCursor == target then break end
U.tap(game, "down")
U.wait(6)
end
return PartyMenu.actionCursor == target
end
local function closeParty()
-- pokefirered/src/region_map.c:4019
for _ = 1, 30 do
if not (RegionMap.isOpen and RegionMap.isOpen()) then break end
U.tap(game, "b")
U.wait(6)
end
for _ = 1, 30 do
if not (PartyMenu.isOpen and PartyMenu.isOpen()) then break end
if PartyMenu._messageText then U.tap(game, "a") else U.tap(game, "b") end
U.wait(6)
end
for _ = 1, 20 do
if not (StartMenu.isOpen and StartMenu.isOpen()) then break end
U.tap(game, "b")
U.wait(6)
end
U.wait(20)
end
-- pokefirered/src/party_menu.c:4099
goTo(PALLET, 10, 6, "down")
if not result(openParty(), "party menu opens in Pallet Town") then return finish() end
result(chooseAction("FLY"), "FLY is on the action list")
U.still(game, DIR .. "/party_dig_01_pallet_fly_row.png")
U.tap(game, "a")
U.wait(30)
result(PartyMenu._messageText == nil, "FLY outdoors is accepted (no refusal text)")
-- pokefirered/src/party_menu.c:3953
result(RegionMap.isOpen() and RegionMap.isFlyMode(), "FLY opened the fly map")
for _ = 1, 300 do
if RegionMap.inputReady() then break end
U.wait(1)
end
U.tap(game, "b")
for _ = 1, 200 do
if not RegionMap.isOpen() then break end
U.wait(1)
end
result(not RegionMap.isOpen(), "B closed the fly map")
-- pokefirered/src/region_map.c:4019
result(PartyMenu.isOpen and PartyMenu.isOpen(), "a cancelled fly map came back to the party menu")
closeParty()
-- pokefirered/src/item_use.c:614
if not result(openParty(), "party menu reopens in Pallet Town") then return finish() end
result(chooseAction("DIG"), "DIG is on the action list")
U.tap(game, "a")
U.wait(30)
local palletRefusal = PartyMenu._messageText
result(palletRefusal ~= nil, "DIG in Pallet Town is refused: " .. tostring(palletRefusal))
U.still(game, DIR .. "/party_dig_02_pallet_dig_refused.png")
closeParty()
-- pokefirered/src/fldeff_dig.c:12
goTo(MT_MOON, 14, 22, "down")
session.escapeWarp = { map = "FR_ROUTE_4", warpId = 255, x = 19, y = 6 }
local CaveTransition = require("src.ui.game3.cave_transition")
local realCaveStart = CaveTransition.start
local caveKinds = {}
CaveTransition.start = function(kind, ...)
caveKinds[#caveKinds + 1] = kind
return realCaveStart(kind, ...)
end
if not result(openParty(), "party menu opens in Mt Moon 1F") then return finish() end
result(chooseAction("DIG"), "DIG is on the action list in Mt Moon")
U.still(game, DIR .. "/party_dig_03_mtmoon_dig_row.png")
U.tap(game, "a")
U.wait(30)
local digCtx = seenCtx
result(digCtx ~= nil and tonumber(digCtx.mapType) == 4,
"the Mt Moon context carries MAP_TYPE_UNDERGROUND (mapType=" .. tostring(digCtx and digCtx.mapType) .. ")")
result(digCtx ~= nil and digCtx.canEscapeRope == true,
"the Mt Moon context carries allowEscaping out of header.json")
result(PartyMenu._messageText == nil,
"DIG in Mt Moon is accepted (refusal: " .. tostring(PartyMenu._messageText) .. ")")
-- pokefirered/src/party_menu.c:3946
local digPrompt = tostring(PartyMenu._yesNoPrompt)
result(PartyMenu.mode == "yesno" and digPrompt:find("escape from here", 1, true) ~= nil
and digPrompt:find("ROUTE 4", 1, true) ~= nil,
"DIG asks gText_EscapeFromHereAndReturnTo with the escape warp's place (" .. digPrompt .. ")")
U.still(game, DIR .. "/party_dig_04_mtmoon_dig_yesno.png")
U.tap(game, "down")
U.wait(6)
U.tap(game, "a")
U.wait(12)
-- pokefirered/src/party_menu.c:4014 Task_ReturnToChooseMonAfterText
result(PartyMenu.isOpen() and PartyMenu.mode == "list" and session.map == MT_MOON,
"NO goes back to choosing a mon and stays in Mt Moon (mode=" .. tostring(PartyMenu.mode) .. ")")
result(chooseAction("DIG"), "DIG is still on the action list")
U.tap(game, "a")
U.wait(30)
result(PartyMenu.mode == "yesno", "DIG asks again")
U.tap(game, "a")
U.wait(6)
result(not (PartyMenu.isOpen and PartyMenu.isOpen()), "YES closes the party menu and DIG runs")
local ShowMon = require("src.core.game3.field_move_show_mon")
local Player = require("src.core.game3.player")
for _ = 1, 120 do
if ShowMon.isActive() then break end
U.wait(1)
end
for _ = 1, 200 do
local fx = ShowMon._fx
if fx and fx.sprite and fx.sprite.state == "wait" then break end
U.wait(1)
end
result(ShowMon._fx and ShowMon._fx.sprite and ShowMon._fx.sprite.state == "wait", "DIG shows the mon cut-in")
U.still(game, DIR .. "/party_dig_04a_mtmoon_dig_show_mon.png")
local rose = false
for _ = 1, 400 do
if (Player.spriteYOffset or 0) <= -40 then rose = true break end
U.wait(1)
end
result(rose, "DIG spins the player and lifts them off (offY=" .. tostring(Player.spriteYOffset) .. ")")
U.still(game, DIR .. "/party_dig_04_mtmoon_dig_used.png")
U.wait(150)
dismiss(30)
U.wait(90)
local FieldMod = require("src.core.game3.field")
result(FieldMod.locked ~= true, "the dig warp finishes and unlocks the field")
result(session.map == "FR_ROUTE_4", "DIG landed on the escape warp (map=" .. tostring(session.map) .. ")")
CaveTransition.start = realCaveStart
-- pokefirered/src/fldeff_flash.c:236 TryDoMapTransition
result(caveKinds[1] == "exit", "DIG out of Mt Moon played FlashTransition_Exit ("
.. table.concat(caveKinds, ",") .. ")")
U.still(game, DIR .. "/party_dig_05_dig_landed.png")
closeParty()
-- pokefirered/src/party_menu.c:4099
goTo(PALLET, 10, 6, "down")
if not result(openParty(), "party menu opens back in Pallet Town") then return finish() end
result(chooseAction("TELEPORT"), "TELEPORT is on the action list")
U.tap(game, "a")
U.wait(20)
result(PartyMenu._messageText == nil, "TELEPORT outdoors is accepted")
-- pokefirered/src/party_menu.c:3939
local tpPrompt = tostring(PartyMenu._yesNoPrompt)
result(PartyMenu.mode == "yesno" and tpPrompt:find("healing spot", 1, true) ~= nil
and tpPrompt:find("PALLET TOWN", 1, true) ~= nil,
"TELEPORT asks gText_ReturnToHealingSpot with the heal map's place (" .. tpPrompt .. ")")
U.still(game, DIR .. "/party_dig_06_teleport_yesno.png")
U.tap(game, "a")
U.wait(6)
result(not (PartyMenu.isOpen and PartyMenu.isOpen()), "YES closes the party menu and TELEPORT runs")
result(not (StartMenu.isOpen and StartMenu.isOpen()), "the start menu closes with it")
local FieldEffects = require("src.core.game3.field_effects")
local function fxAnim(kind)
for _, a in ipairs(FieldEffects._anims or {}) do
if a.kind == kind then return a end
end
end
local function waitFor(pred, frames)
for _ = 1, frames do
if pred() then return true end
U.wait(1)
end
return pred() and true or false
end
waitFor(function() return fxAnim("teleport_out") ~= nil end, 400)
local tpOut = fxAnim("teleport_out")
if not result(tpOut ~= nil, "TELEPORT starts the teleport field effect task") then return finish() end
-- pokefirered/src/field_effect.c:2371 TeleportFieldEffectTask2
waitFor(function() return tpOut.d2 >= 2 end, 40)
local f0 = Player.facing
waitFor(function() return Player.facing ~= f0 end, 20)
local f1, gap = Player.facing, 0
for _ = 1, 20 do
if Player.facing ~= f1 then break end
U.wait(1)
gap = gap + 1
end
result(gap == 8 and Player.spriteYOffset == 0,
"TELEPORT first turns in place every 8 frames (gap=" .. gap .. ")")
U.still(game, DIR .. "/party_dig_07_teleport_spin.png")
-- pokefirered/src/field_effect.c:2397 TeleportFieldEffectTask3
waitFor(function() return tpOut.state == 3 and tpOut.d4 >= 64 end, 200)
result(tpOut.state == 3 and tpOut.d3 == 8 and Player.spriteYOffset == -tpOut.d4
and Player.oamPriority == 1,
"after 8+ turns back to the start facing it spins faster and rises (y=" .. tostring(Player.spriteYOffset) .. ")")
U.still(game, DIR .. "/party_dig_07b_teleport_rise.png")
waitFor(function() return fxAnim("teleport_out") == nil end, 120)
result(tpOut.d4 >= 0xa8 and not Player.isVisible(),
"it rises 0xa8 px before the fade (d4=" .. tostring(tpOut.d4) .. ")")
-- pokefirered/src/field_effect.c:2483 TeleportInFieldEffectTask2
waitFor(function() return fxAnim("teleport_in") ~= nil end, 600)
local tpIn = fxAnim("teleport_in")
if not result(tpIn ~= nil, "the heal map runs the teleport-in effect") then return finish() end
waitFor(function() return tpIn.y2 >= -40 end, 60)
result(Player.isVisible() and Player.spriteYOffset == tpIn.y2 and tpIn.y2 < 0,
"the player spins down from above the screen (y=" .. tostring(tpIn.y2) .. ")")
-- pokefirered/src/field_effect.c:2451
result(require("src.core.game3.field").locked == true,
"the field stays locked while the teleport-in spin runs")
U.still(game, DIR .. "/party_dig_07c_teleport_arrive.png")
waitFor(function() return fxAnim("teleport_in") == nil end, 200)
-- pokefirered/src/field_effect.c:2530
result(Player.facing == "down" and Player.spriteYOffset == 0 and Player.oamPriority == nil,
"the teleport-in spin stops facing south on the ground")
dismiss(40)
U.wait(90)
local Field = require("src.core.game3.field")
result(Field.locked ~= true, "the teleport warp finishes and unlocks the field")
result(session.map ~= PALLET, "TELEPORT landed on the heal map (" .. tostring(session.map) .. ")")
U.still(game, DIR .. "/party_dig_08_teleport_landed.png")
finish()
end
return run