Files
bryanthaboi 72a17b27dd CLOSES #2444
2026-09-24 08:39:43 -04:00

297 lines
12 KiB
Lua

local U = require("tests.drivers.util")
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/game3_doubles"
local TRAINER_TWINS_ELI_ANNE = 484
local MAGIKARP, IVYSAUR, WARTORTLE = 129, 2, 8
local TURN_CAP = 30
local failures = 0
local function result(ok, label)
print((ok and "PASS " or "FAIL ") .. label)
if not ok then failures = failures + 1 end
return ok
end
local function finish()
if failures == 0 then
print("PASS game3_doubles")
love.event.quit(0)
else
print("FAIL game3_doubles failures=" .. failures)
love.event.quit(1)
end
end
local function log_has(Ui, needle)
for _, t in ipairs(Ui.log and Ui.log() or {}) do
if type(t) == "string" and t:find(needle, 1, true) then return true end
end
return false
end
local function find_trainer_object(TrainerSight, Objects, tid)
local found = {}
for _, lid in ipairs(Objects.listActive()) do
local eo = Objects.find(lid)
if eo and TrainerSight.getTrainerId(eo) == tid then found[#found + 1] = eo end
end
table.sort(found, function(a, b) return (a.cellX or 0) < (b.cellX or 0) end)
return found[1], found[2]
end
return function(game)
for _ = 1, 600 do
if game.phase == "boot" and game.boot then break end
U.wait(1)
end
game:_handleBootAction({ action = "new_game", name = "RED" })
U.wait(180)
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 Party = require("src.core.game3.party")
local Objects = require("src.core.game3.objects")
local TrainerSight = require("src.core.game3.trainer_sight")
local Trainers = require("src.core.game3.scripting.trainers")
local session = Runtime.getSession()
if not result(session ~= nil, "new game reached the field") then return finish() end
local info = Trainers.info(TRAINER_TWINS_ELI_ANNE)
result(info and info.doubleBattle == true, "trainer 484 ELI & ANNE is flagged doubleBattle")
session.party = {}
Party.giveMon(session, MAGIKARP, 5)
result(Party.monsStateToDoubles(session.party) == Party.PLAYER_HAS_ONE_MON, "one mon: PLAYER_HAS_ONE_MON")
local okC, MapCatalog = pcall(require, "src.import.gba.map_catalog")
local mapId = (okC and MapCatalog.pretToEngine and MapCatalog.pretToEngine("Route8")) or "FR_ROUTE_8"
Map.load(nil, game, mapId, { x = 40, y = 6, facing = "up" })
game.session.x, game.session.y, game.session.facing = 40, 6, "up"
U.wait(90)
session = Runtime.getSession()
local eli, anne = find_trainer_object(TrainerSight, Objects, TRAINER_TWINS_ELI_ANNE)
if not result(eli ~= nil, "found a TRAINER_TWINS_ELI_ANNE object on " .. tostring(mapId)) then return finish() end
result(anne ~= nil, "found the second twin object sharing the trainer id")
result(TrainerSight.battleType(eli) == 4, "twin script is trainerbattle type 4 (got " .. tostring(TrainerSight.battleType(eli)) .. ")")
result(TrainerSight.blockedByDoubles(eli) == true, "trainer sight blocks a one-mon player (trainer_see.c:114)")
local tx, ty = eli.cellX, eli.cellY + 1
Map.load(nil, game, mapId, { x = tx, y = ty, facing = "up" })
game.session.x, game.session.y, game.session.facing = tx, ty, "up"
U.wait(60)
local Battle = require("src.core.game3.battle")
local Message = package.loaded["src.ui.game3.message"] or require("src.ui.game3.message")
U.tap(game, "a")
local sawMsg = false
for _ = 1, 240 do
if Message.isOpen and Message.isOpen() then sawMsg = true break end
U.wait(1)
end
result(sawMsg, "talking with one mon shows the not-enough-mons line")
U.wait(30)
U.shot(game, DIR .. "/00_not_enough.png")
for _ = 1, 200 do
if not (Message.isOpen and Message.isOpen()) and not (Space.vm and Space.vm:isRunning()) then break end
U.tap(game, "a")
U.wait(4)
end
local fid = Flags.trainerFlagId(TRAINER_TWINS_ELI_ANNE)
result(not Battle.isActive(), "no battle with one mon")
result(not Flags.getFlag(Space.store, Space.vm and Space.vm.ctx, fid), "trainer flag still clear after the refusal")
Party.giveMon(session, IVYSAUR, 30)
Party.giveMon(session, WARTORTLE, 30)
result(Party.monsStateToDoubles(session.party) == Party.PLAYER_HAS_TWO_USABLE_MONS, "three mons: PLAYER_HAS_TWO_USABLE_MONS")
result(TrainerSight.blockedByDoubles(eli) == false, "trainer sight allows the pair once two mons are usable")
local moneyBefore = tonumber(session.money) or 0
local Ui = require("src.core.game3.battle.ui")
local Anim = require("src.core.game3.battle.anim")
local PartyMenu = require("src.ui.game3.party_menu")
for _ = 1, 600 do
if Battle.isActive() then break end
U.tap(game, "a")
U.wait(3)
end
if not result(Battle.isActive(), "trainerbattle script started the battle") then return finish() end
local st = Battle._st
result(st and st.double == true, "battle state is double")
result(st and st.battlersCount == 4, "battlersCount is 4")
result(st and st.battlers[2] ~= nil and not st.absent[2], "player right flank present")
result(st and st.battlers[3] ~= nil and not st.absent[3], "opponent right flank present")
local shots = {}
local function shot_once(key, name)
if shots[key] then return end
shots[key] = true
U.wait(2)
result(U.shot(game, DIR .. "/" .. name .. ".png"), "screenshot " .. name)
end
for _ = 1, 400 do
if Battle._phase ~= "intro" then break end
if log_has(Ui, " and ") and Ui.dialogPending and Ui.dialogPending() then
U.wait(20)
shot_once("intro", "01_intro_sent_out")
end
if Ui.dialogPending and Ui.dialogPending() then U.tap(game, "a") else U.wait(1) end
end
result(log_has(Ui, "sent\nout ") and log_has(Ui, " and "), "intro used the two-mon send-out line")
result(log_has(Ui, "Go! ") and log_has(Ui, " and\n"), "intro used Go! X and Y!")
local Commands = require("src.core.game3.battle.commands")
local Moves = require("src.core.game3.battle.moves")
local StatGrowth = require("src.ui.game3.stat_growth")
local function pick_move(state, id)
local b = state and state.battlers and state.battlers[id]
local mon = b and b.mon
if not mon then return nil end
local best, bestPow
for slot = 1, 4 do
local mv = mon.moves and mon.moves[slot]
if mv and mv ~= 0 and mv ~= "" and Commands.moveUsable(state, slot, id) then
local def = Moves.get(mv)
local pow = def and tonumber(def.power) or 0
if not best or pow > bestPow then best, bestPow = slot, pow end
end
end
return best
end
local sawFaint, sawReplacement, sawSelErr = false, false, false
local guard = 0
while Battle.isActive() and guard < 20000 do
guard = guard + 1
st = Battle._st
if st and (st.turn or 0) > TURN_CAP then break end
local phase = Battle._phase
if st then
for id = 0, 3 do
local b = st.battlers[id]
if b and b.mon and (tonumber(b.mon.hp) or 0) <= 0 and not sawFaint then
sawFaint = true
U.wait(10)
shot_once("faint", "05_faint")
end
end
end
if PartyMenu.isOpen and PartyMenu.isOpen() then
sawReplacement = sawReplacement or phase == "switching"
shot_once("replace", "06_replacement_party")
local pick
for i = 1, #(PartyMenu._party or {}) do
local ok = PartyMenu._validate == nil or PartyMenu._validate(i) == nil
local mon = PartyMenu._party[i]
if ok and mon and (tonumber(mon.hp) or 0) > 0 then pick = i break end
end
result(pick ~= nil, "a valid replacement is listed")
if not pick then return finish() end
PartyMenu.cursor = pick
U.wait(30)
U.tap(game, "a")
U.wait(20)
result(PartyMenu.mode == "action" and PartyMenu.ACTIONS[1] == "SEND OUT",
"replacement action menu offers SEND OUT")
shot_once("replace_action", "06b_send_out_menu")
PartyMenu.actionCursor = 1
U.tap(game, "a")
U.wait(20)
result(not PartyMenu.isOpen(), "SEND OUT closed the party menu")
elseif phase == "command" and Ui._mode == "menu" and not Anim.busy() then
local who = Ui.activeBattler and Ui.activeBattler() or 0
shot_once("cmd" .. tostring(who), who == 0 and "02_command_left" or "02_command_right")
U.tap(game, "a")
U.wait(8)
elseif phase == "command" and Ui._mode == "moves" then
local who = Ui.activeBattler and Ui.activeBattler() or 0
local slot
if not shots.target then
for s = 1, 4 do
if not slot and Commands.moveUsable(st, s, who) and Ui.targetSelection(st, who, s) then slot = s end
end
end
slot = slot or pick_move(st, who)
if slot then
for _ = 1, 6 do
local cur = (Ui._moveIndex or 1) - 1
local want = slot - 1
if cur == want then break end
local key = (cur % 2 ~= want % 2) and "right" or "down"
U.tap(game, key)
U.wait(4)
if (Ui._moveIndex or 1) - 1 == cur then
U.tap(game, key == "right" and "down" or "right")
U.wait(4)
end
end
end
U.tap(game, "a")
U.wait(8)
elseif phase == "command" and Ui._mode == "selmsg" then
sawSelErr = true
U.tap(game, "a")
U.wait(8)
elseif phase == "command" and Ui._mode == "target" then
if not shots.target then
shots.target = true
local cur = Ui.targetCursor()
for _ = 1, 40 do
if Ui.targetHidden(cur) then break end
U.wait(1)
end
result(Ui._mode == "target" and Ui.targetHidden(cur), "target cue blink is on for battler " .. tostring(cur))
result(U.still(game, DIR .. "/03_target_select.png"), "screenshot 03_target_select")
end
U.tap(game, "a")
U.wait(8)
elseif phase == "animating" or phase == "residuals" then
if not shots.mid then
U.wait(12)
shot_once("mid", "04_mid_turn")
end
if Ui.dialogPending and Ui.dialogPending() then U.tap(game, "a") else U.wait(1) end
elseif StatGrowth.isOpen() then
shot_once("statgrowth", "05b_level_up_stats")
U.tap(game, "a")
U.wait(4)
else
if Ui.dialogPending and Ui.dialogPending() then U.tap(game, "a") else U.wait(1) end
if Ui.choiceActive and Ui.choiceActive() then U.tap(game, "b") U.wait(4) end
end
end
if Battle.isActive() then
local lg = Ui.log and Ui.log() or {}
print(string.format("[driver] stuck guard=%d phase=%s mode=%s active=%s last=%q", guard,
tostring(Battle._phase), tostring(Ui._mode), tostring(Ui.activeBattler and Ui.activeBattler()),
tostring(lg[#lg])))
end
local res = Battle.getResult() or (st and st.result)
result(not Battle.isActive(), "battle ended within " .. TURN_CAP .. " turns (turn=" .. tostring(st and st.turn) .. ")")
result(res == "win", "player won the double battle (result=" .. tostring(res) .. ")")
result(sawFaint, "a battler fainted during the fight")
if sawReplacement then
result(true, "player picked a replacement for a fainted slot")
else
print("NOTE no player replacement was needed this run")
end
if sawSelErr then print("NOTE a move selection was refused this run") end
for _ = 1, 600 do
if not (Space.vm and Space.vm:isRunning()) and not (Message.isOpen and Message.isOpen()) then break end
U.tap(game, "a")
U.wait(3)
end
result(Flags.getFlag(Space.store, Space.vm and Space.vm.ctx, fid), "trainer flag set after the win")
local gained = (tonumber(session.money) or 0) - moneyBefore
local Prize = require("src.core.game3.battle.prize")
local expected = Prize.calc(TRAINER_TWINS_ELI_ANNE, { double = true })
result(res ~= "win" or gained == expected, "prize money doubled (gained=" .. tostring(gained) .. " expected=" .. tostring(expected) .. ")")
U.shot(game, DIR .. "/07_after.png")
finish()
end