Files
2026-09-24 05:17:32 -04:00

399 lines
15 KiB
Lua

local U = require("tests.drivers.util")
local DIR = os.getenv("POKEPORT_SHOT_DIR") or ".bazinga/BSA/09-23-26-03-gen3online/shots/minigames"
local CORNER = "FR_TWO_ISLAND_JOYFUL_GAME_CORNER"
-- pokefirered/include/constants/flags.h:790
local FLAG_GOT_MOON_STONE_FROM_JOYFUL_GAME_CORNER = 0x2FB
-- pokefirered/include/constants/flags.h:1375
local FLAG_SYS_POKEDEX_GET = 0x829
-- pokefirered/include/constants/vars.h:173
local VAR_MAP_SCENE_TWO_ISLAND_JOYFUL_GAME_CORNER = 0x4079
-- pokefirered/include/constants/vars.h:163
local VAR_CABLE_CLUB_STATE = 0x406F
local CHARMANDER, DODRIO = 4, 85
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()
local Link = package.loaded["src.core.game3.link"]
if Link then pcall(Link.reset) end
local Client = package.loaded["src.online.Client"]
if Client then pcall(Client.disconnect) end
if failures == 0 then
print("PASS g3mg_common_group")
love.event.quit(0)
else
print("FAIL g3mg_common_group failures=" .. failures)
love.event.quit(1)
end
end
local function now() return love.timer.getTime() end
local Stub = { MIN = 2, MAX = 5, END_FRAME = 420 }
Stub.__index = Stub
function Stub.loadArt() return {} end
function Stub.new(ctx)
local sim = setmetatable({ ctx = ctx, f = 0, scores = { 0, 0, 0, 0, 0 },
last = { 0, 0, 0, 0, 0 }, presses = 0, leader = ctx.leader }, Stub)
return sim
end
function Stub:leaderStep(inputs, present)
for s = 0, 4 do
local i = inputs[s + 1]
if present[s + 1] and type(i) == "number" and i > self.last[s + 1] then
self.scores[s + 1] = self.scores[s + 1] + (i - self.last[s + 1])
self.last[s + 1] = i
end
end
self.f = self.f + 1
end
function Stub:snapshot()
return { f = self.f, sc = { unpack(self.scores) }, la = { unpack(self.last) } }
end
function Stub:applySnapshot(s)
self.f = s.f
for i = 1, 5 do
self.scores[i] = s.sc[i] or 0
self.last[i] = s.la[i] or 0
end
end
function Stub:localInput(input)
if input and input:wasPressed("a") then self.presses = self.presses + 1 end
return self.presses
end
function Stub:predict(i) self.predicted = i end
function Stub:becomeLeader(last)
if last then self:applySnapshot(last) end
self.leader = true
end
function Stub:update() end
function Stub:draw()
local FrlgFont = require("src.ui.game3.frlg_font")
local y = 16
for _, p in ipairs(self.ctx.players) do
local mine = p.seat == self.ctx.seat
FrlgFont.draw(string.format("%s %d", p.name or "?", self.scores[p.seat + 1] or 0), 24, y,
{ colors = mine and FrlgFont.COLOR.GREEN or FrlgFont.COLOR.WHITE })
y = y + 16
end
FrlgFont.draw(string.format("%d", self.f), 200, 136, { colors = FrlgFont.COLOR.WHITE })
end
function Stub:finished() return self.f >= Stub.END_FRAME end
function Stub:results()
local results, powder = {}, {}
for _, p in ipairs(self.ctx.players) do
local sc = self.scores[p.seat + 1]
results[#results + 1] = { seat = p.seat, score = sc, stats = { presses = sc } }
powder[#powder + 1] = { seat = p.seat, amount = sc }
end
return { results = results, powder = powder }
end
function Stub.applyResults(session, result, mySeat)
local Records = require("src.ui.game3.minigame_records")
for _, r in ipairs(result.results) do
if r.seat == mySeat then Records.updatePokemonJump(session, r.score, r.score, 0) end
end
end
return function(game)
print("PASS driver_started")
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 Message = require("src.ui.game3.message")
local Choice = require("src.ui.game3.choice")
local SaveMenu = require("src.ui.game3.save_menu")
local PartyMenu = require("src.ui.game3.party_menu")
local Party = require("src.core.game3.party")
local Link = require("src.core.game3.link")
local Client = require("src.online.Client")
local Relay = require("tests.support.fake_relay")
local Wire = require("src.link.Wire")
local MG = require("src.core.game3.minigames.common")
local Lobby = require("src.ui.game3.minigames.common_lobby")
local Countdown = require("src.ui.game3.minigames.common_countdown")
package.loaded[MG.GAMES.jump] = Stub
local session = Runtime.getSession()
if not result(session ~= nil, "new game reached the game3 field") then return finish() end
session.trainerId = 0x1234
Party.giveMon(session, CHARMANDER, 12)
Party.giveMon(session, DODRIO, 30)
local relay = Relay.new({ clock = function() return now() end })
local me = relay:seat("a0000001", "RED")
Client.configure({ relayAddress = "fake:1", connect = function() return me.transport end })
local function ctx() return Space.vm and Space.vm.ctx end
local function getVar(id) return tonumber(Flags.getVar(Space.store, ctx(), id)) or 0 end
local function setVar(id, v) Flags.setVar(Space.store, ctx(), id, v) end
local peers = {}
local function peerSession(s)
local rs = { paired = true, closed = false, left = false, seq = 0, target = s.room }
function rs:send(msg)
self.seq = self.seq + 1
relay:handle(s, { type = "room_msg", seq = self.seq, msg = msg })
end
function rs:poll()
local out = {}
for _, m in ipairs(s.transport.inbox) do
if m.type == "room_msg" and type(m.msg) == "table" then
local inner = Wire.sanitize(m.msg)
if inner then
if m.relay then
inner.relay = true
if type(inner.seat) ~= "number" then inner.seat = -1 end
else
inner.seat = m.seat
end
out[#out + 1] = inner
end
end
end
s.transport.inbox = {}
return out
end
function rs:players()
local room = relay.rooms[s.room or ""]
return room and room.players or {}
end
function rs:close()
self.left = true
self.closed = true
relay:handle(s, { type = "room_leave" })
end
return rs
end
local function startPeer(s, pressEvery)
local room = relay.rooms[s.room or ""]
local seat = relay:seatOf(room, s.id)
local players = {}
for _, p in ipairs(room.players) do
players[#players + 1] = { id = p.id, name = p.name, seat = p.seat, trainerId = 0x2000 + p.seat, gender = 0 }
end
local input = { n = 0 }
function input:wasPressed(b) return b == "a" and self.n % pressEvery == 0 end
local m = MG.Match.new({ game = "jump", session = peerSession(s), seat = seat, seats = #players,
players = players, seed = room.seed, partySlot = 0 },
{ module = Stub, partyMon = { species = CHARMANDER }, leader = room.leader,
countdown = function() return Countdown.new(120, 80, { playSe = function() end }) end })
peers[#peers + 1] = { s = s, m = m, input = input, acc = 0, last = now() }
return m
end
local function stepPeers()
for _, p in ipairs(peers) do
if not p.gone and not p.m:finished() then
local t = now()
p.acc = p.acc + (t - p.last)
p.last = t
local n = 0
while p.acc >= MG.DT and n < 8 do
p.acc = p.acc - MG.DT
n = n + 1
p.input.n = p.input.n + 1
p.m:step(p.input)
end
end
end
end
local function wait(n)
for _ = 1, n do
relay:pump()
stepPeers()
U.wait(1)
end
end
local function waitFor(cond, seconds)
local t0 = now()
while not cond() do
relay:pump()
stepPeers()
U.wait(1)
if now() - t0 > (seconds or 5) then return false end
end
return true
end
local live = Link.liveProfile()
if not result(live ~= nil, "the live g3 profile computes (vanilla game)") then return finish() end
Link.connect()
result(waitFor(function() return Client.state() == "online" end, 5), "the Client is online on the relay")
wait(20)
print("[driver] lobby screens")
Lobby.showPlayers({}, { mode = "leader", group = { min = 2, max = 5 }, activity = 9 })
wait(12)
U.shot(game, DIR .. "/g3mg_common_lobby_leader_alone.png")
result(Lobby.modeText():find("1 player") ~= nil, "leader alone: 1 player needed")
local WirelessIcon = require("src.ui.game3.wireless_icon")
result(WirelessIcon.anim() == "3bars" and (WirelessIcon.frame() or 1) > 1,
"the lobby shows the animating wireless icon (frame " .. tostring(WirelessIcon.frame()) .. ")")
Lobby.showPlayers({ { name = "BLUE", trainerId = 0x2222 }, { name = "LEAF", trainerId = 0x3333, pending = true } },
{ mode = "leader", group = { min = 2, max = 5 }, activity = 9 })
wait(12)
U.shot(game, DIR .. "/g3mg_common_lobby_leader_3p.png")
result(Lobby.canStart(), "leader with members: START allowed (AwaitingLinkPressStart)")
Lobby.showPlayers({ { slot = 1, name = "BLUE", trainerId = 0x2222 }, { slot = 2, name = "LEAF", trainerId = 0x3333, started = true } },
{ mode = "group", capacity = { activity = 9, min = 2, max = 5 } })
wait(12)
U.shot(game, DIR .. "/g3mg_common_lobby_joiner.png")
Lobby.reset()
wait(2)
local blue = relay:seat("b0000002", "BLUE")
relay:handle(blue, { type = "lobby_hello", protocol = 3, name = "BLUE", profiles = { live } })
local leaf = relay:seat("c0000003", "LEAF")
relay:handle(leaf, { type = "lobby_hello", protocol = 3, name = "LEAF", profiles = { live } })
relay:handle(blue, { type = "group_open", activity = "minigame_jump", profile = live,
avatar = { name = "BLUE", trainerId = 0x2222, gender = 0, version = "leafgreen" } })
Flags.setFlag(Space.store, ctx(), FLAG_SYS_POKEDEX_GET, true)
Flags.setFlag(Space.store, ctx(), FLAG_GOT_MOON_STONE_FROM_JOYFUL_GAME_CORNER, true)
Map.load(nil, game, CORNER, { x = 6, y = 3, facing = "up" })
wait(60)
setVar(VAR_MAP_SCENE_TWO_ISLAND_JOYFUL_GAME_CORNER, 4)
Player.cellX, Player.cellY = 6, 3
Player.px, Player.py = 6 * 16, 3 * 16
Player.targetX, Player.targetY = 6, 3
Player.facing = "up"
session.x, session.y, session.facing = 6, 3, "up"
wait(10)
U.tap(game, "a")
local picked = false
local joined = false
local t0 = now()
while now() - t0 < 40 do
relay:pump()
stepPeers()
if PartyMenu.isOpen() and not picked then
wait(10)
U.shot(game, DIR .. "/g3mg_common_choose_mon.png")
picked = true
U.tap(game, "a")
elseif Lobby.isOpen() and Lobby.mode == "group" and #Lobby.players > 0 and not joined then
wait(30)
U.shot(game, DIR .. "/g3mg_common_group_list.png")
result(not Message.isOpen() and Lobby.message() == Lobby.chooseText() and Lobby.chooseText() ~= "",
"the group list owns the textbox: " .. Lobby.chooseText())
joined = true
U.tap(game, "a")
elseif Choice.active or SaveMenu.isOpen() or (Message.isOpen() and Message.isWaiting and Message.isWaiting()) then
U.tap(game, "a")
end
local g = relay.groups[blue.id]
if g and #g.pending > 0 then
for _, p in ipairs(g.pending) do
relay:handle(blue, { type = "group_accept", from = p.id, ok = true })
end
if #g.members == 2 then
relay:handle(leaf, { type = "group_join", leader = blue.id, profile = live,
avatar = { name = "LEAF", trainerId = 0x3333, gender = 1, version = "firered" } })
end
end
if g and #g.members == 3 then
relay:handle(blue, { type = "group_start" })
end
if MG.isActive() then break end
U.wait(2)
end
result(picked, "ChooseMonForWirelessMinigame opened the party menu")
result(MG.partySlot == 0, "Charmander (slot 0) entered")
result(joined, "the group list showed BLUE's group")
if not result(MG.isActive(), "MG.arm launched the minigame on the script's waitstate") then
U.shot(game, DIR .. "/g3mg_common_no_launch.png")
return finish()
end
local room = Client.room()
result(room and room.intent == "minigame" and room.origin == "group", "a group-origin minigame room")
startPeer(blue, 5)
startPeer(leaf, 9)
local mine = MG.match()
result(mine and mine.seat == 1, "RED sits at seat 1")
result(mine and mine.leader == 0, "BLUE leads")
result(waitFor(function() return mine.phase ~= "ready" end, 10), "every seat readied and BLUE started")
result(#(mine.startPlayers or {}) == 3, "three players in game3_mg_start")
result(waitFor(function() return mine.countdown and mine.countdown:digitShown() == 3 end, 5), "3")
wait(10)
U.shot(game, DIR .. "/g3mg_common_countdown_3.png")
result(waitFor(function() return mine.countdown and mine.countdown.starts ~= nil end, 8), "START")
wait(12)
U.shot(game, DIR .. "/g3mg_common_countdown_start.png")
result(waitFor(function() return mine.phase == "play" end, 6), "play")
for _ = 1, 40 do
U.tap(game, "a")
wait(5)
end
result(waitFor(function() return (mine.sim.scores[2] or 0) > 0 end, 5),
"RED's presses reach the leader and come back in states")
local roomId = Client.room() and Client.room().room
peers[1].gone = true
relay:handle(blue, { type = "room_leave" })
relay:migrateLeader(roomId)
result(waitFor(function() return mine:isLeader() end, 3), "BLUE dropped: leadership migrated to RED")
result(peers[2].m.leader == 1, "LEAF follows RED")
for _ = 1, 20 do
U.tap(game, "a")
wait(5)
end
U.shot(game, DIR .. "/g3mg_common_after_migration.png")
result(waitFor(function() return mine.phase == "results" or mine.phase == "done" or not MG.isActive() end, 15),
"RED finished the game as leader")
local res = mine.result
waitFor(function() return peers[2].m.result ~= nil or peers[2].m:finished() end, 5)
print("[driver] LEAF phase=" .. tostring(peers[2].m.phase) .. " why=" .. tostring(peers[2].m.why))
result(res ~= nil and peers[2].m.result ~= nil
and require("src.link.Json").encode(res.results) == require("src.link.Json").encode(peers[2].m.result.results),
"RED and LEAF agree on the results")
local myScore = 0
for _, r in ipairs(res and res.results or {}) do if r.seat == 1 then myScore = r.score end end
result(myScore > 0, "RED scored " .. myScore)
result((session.berryPowder or 0) == myScore, "Berry Powder = RED's share (" .. tostring(session.berryPowder) .. ")")
result(session.pokemonJumpRecords and session.pokemonJumpRecords.bestJumpScore == myScore, "record written")
result(waitFor(function() return not MG.isActive() end, 8), "the minigame exits")
result(waitFor(function() return Space.mapId == CORNER and getVar(VAR_CABLE_CLUB_STATE) == 0 end, 10),
"warped back and ExitMinigameRoom cleared VAR_CABLE_CLUB_STATE")
local Fade = require("src.ui.game3.fade")
waitFor(function() return not Fade.isActive() and (tonumber(Fade.t) or 0) == 0 and not Space.vm:isRunning() end, 10)
print(string.format("[driver] after exit fade.t=%s active=%s map=%s pos=%s,%s visible=%s stack=%s",
tostring(Fade.t), tostring(Fade.active), tostring(Space.mapId), tostring(Player.cellX), tostring(Player.cellY),
tostring(Player.visible), tostring(require("src.ui.game3.stack").depth())))
wait(30)
U.shot(game, DIR .. "/g3mg_common_returned.png")
result(Client.room() == nil, "RED left the relay room")
finish()
end