mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
361 lines
16 KiB
Lua
361 lines
16 KiB
Lua
local U = require("tests.drivers.util")
|
|
local H = require("tests.link3_harness")
|
|
|
|
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/g3battle_multi"
|
|
|
|
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 g3battle_multi_arena")
|
|
love.event.quit(0)
|
|
else
|
|
print("FAIL g3battle_multi_arena failures=" .. failures)
|
|
love.event.quit(1)
|
|
end
|
|
end
|
|
|
|
local function mon(species, level, moves)
|
|
return H.legal({
|
|
species = species, level = level, moves = moves, personality = 0x51,
|
|
ivs = { hp = 20, atk = 20, def = 20, spe = 20, spa = 20, spd = 20 },
|
|
evs = { hp = 0, atk = 0, def = 0, spe = 0, spa = 0, spd = 0 }, item = 0,
|
|
nickname = "", friendship = 70,
|
|
})
|
|
end
|
|
|
|
local NAMES = { [0] = "RED", [1] = "LEAF", [2] = "BLUE", [3] = "GREEN" }
|
|
local TRAINER_IDS = { [0] = 0x1234, [1] = 0x2468, [2] = 0x1357, [3] = 0x8642 }
|
|
|
|
local function profile()
|
|
return { engine = 3, version = "firered", kind = "vanilla", rulesetId = "g3_multi", rule = { partySize = 3 } }
|
|
end
|
|
|
|
local function players()
|
|
local out = {}
|
|
for seat = 0, 3 do out[#out + 1] = { id = string.format("%08x", seat + 1), name = NAMES[seat], seat = seat } end
|
|
return out
|
|
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
|
|
local Battle = require("src.core.game3.battle")
|
|
local BattleUi = require("src.core.game3.battle.ui")
|
|
local State = require("src.core.game3.battle.state")
|
|
local Engine = require("src.core.game3.battle.engine")
|
|
local LB = require("src.core.game3.link.battle")
|
|
|
|
local parties = {
|
|
[0] = { mon(6, 50, { 53, 17 }), mon(25, 50, { 85, 98 }), mon(131, 50, { 57, 58 }) },
|
|
[1] = { mon(9, 50, { 57, 44 }), mon(3, 50, { 76, 22 }), mon(143, 50, { 34, 29 }) },
|
|
[2] = { mon(65, 50, { 94, 93 }), mon(68, 50, { 2, 67 }), mon(94, 50, { 85, 95 }) },
|
|
[3] = { mon(59, 50, { 53, 44 }), mon(130, 50, { 57, 44 }), mon(26, 50, { 85, 98 }) },
|
|
}
|
|
local packed = {}
|
|
for seat = 0, 3 do packed[seat] = H.pack(parties[seat]) end
|
|
local SEED = 0x3C3C5A5A
|
|
|
|
local returned
|
|
game.returnToLauncher = function(o) returned = o or {} end
|
|
|
|
local function canon(st, id)
|
|
return st.linkSeatOf[id]
|
|
end
|
|
|
|
local function scriptedPeers(relay, mySeat)
|
|
local peers, sent, switched, outcomes = {}, {}, {}, {}
|
|
for seat = 0, 3 do
|
|
if seat ~= mySeat then peers[seat] = relay:session(seat) end
|
|
end
|
|
local seatSent = {}
|
|
return function()
|
|
for seat, s in pairs(peers) do
|
|
for _, msg in ipairs(s:poll()) do
|
|
if msg.seat == mySeat then
|
|
if msg.type == "game3_hello" then
|
|
local hello = LB.copy(msg)
|
|
hello.seat = nil
|
|
hello.name = NAMES[seat]
|
|
hello.game3.trainerId, hello.game3.gender, hello.game3.seat = TRAINER_IDS[seat], seat % 2, seat
|
|
s:send(hello)
|
|
elseif msg.type == "game3_battle_linkup" then
|
|
s:send({ type = "game3_battle_linkup", linkType = msg.linkType, players = 4 })
|
|
elseif msg.type == "game3_battle_seat" and not seatSent[seat] then
|
|
seatSent[seat] = true
|
|
s:send({ type = "game3_battle_seat" })
|
|
elseif msg.type == "game3_battle_setup" then
|
|
s:send({ type = "game3_battle_setup", mode = "multi", unionRoom = false, name = NAMES[seat],
|
|
trainerId = TRAINER_IDS[seat], gender = seat % 2, party = packed[seat] })
|
|
elseif msg.type == "game3_battle_hash" then
|
|
s:send({ type = "game3_battle_hash", turn = msg.turn, value = msg.value, parts = msg.parts })
|
|
elseif msg.type == "game3_battle_outcome" and not outcomes[seat] then
|
|
outcomes[seat] = true
|
|
local out = msg.outcome
|
|
if seat % 2 ~= mySeat % 2 then out = ({ [1] = 2, [2] = 1, [3] = 3, [128] = 1 })[msg.outcome] or 3 end
|
|
s:send({ type = "game3_battle_outcome", outcome = out, turn = msg.turn })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local st = Battle.getState()
|
|
if Battle.isActive() and st and st.multi then
|
|
if Battle._phase == "linkwait" and Battle._linkMulti then
|
|
for id = 0, 3 do
|
|
local seat = st.linkSeatOf[id]
|
|
local key = tostring(seat) .. ":" .. tostring(st.turn)
|
|
if peers[seat] and not sent[key] and State.isPresent(st, id) then
|
|
sent[key] = true
|
|
local b = State.battler(st, id)
|
|
local slot = 1
|
|
for i = 1, 4 do
|
|
if b.mon.moves[i] and (tonumber(b.mon.pp and b.mon.pp[i]) or 0) > 0 then slot = i break end
|
|
end
|
|
local target = State.isPresent(st, State.OPPOSITE(id)) and State.OPPOSITE(id)
|
|
or State.PARTNER(State.OPPOSITE(id))
|
|
peers[seat]:send({ type = "game3_battle_action", turn = st.turn, kind = "move", slot = slot,
|
|
target = canon(st, target) })
|
|
end
|
|
end
|
|
end
|
|
local pending = Battle._linkSwitch
|
|
if Battle._phase == "linkswitch" and pending and pending.battler ~= nil then
|
|
local seat = st.linkSeatOf[pending.battler]
|
|
local key = tostring(seat) .. ":" .. tostring(st.turn) .. ":" .. tostring(pending.battler)
|
|
if peers[seat] and not switched[key] then
|
|
switched[key] = true
|
|
local cands = Engine.replacementCandidates(st, pending.battler)
|
|
if cands[1] then peers[seat]:send({ type = "game3_battle_switch", slot = cands[1] }) end
|
|
end
|
|
end
|
|
end
|
|
relay:tick()
|
|
end
|
|
end
|
|
|
|
local function runArena(mySeat, shots)
|
|
returned = nil
|
|
local relay = H.relay({ seed = SEED, seats = 4, roomSeed = 10 + mySeat })
|
|
local session = relay:session(mySeat)
|
|
local peerStep = scriptedPeers(relay, mySeat)
|
|
local outcome
|
|
local digests
|
|
game:enterArena({
|
|
profile = profile(), role = ({ [0] = "host", [1] = "guest", [2] = "seat2", [3] = "seat3" })[mySeat],
|
|
seat = mySeat, seats = 4, seed = SEED, match = "m" .. mySeat, room = relay.room, players = players(),
|
|
myParty = packed[mySeat], session = session, client = relay:client(), mode = "multi",
|
|
onDone = function(r)
|
|
outcome = r
|
|
digests = {}
|
|
for turn, value in pairs(LB._myHashes or {}) do digests[turn] = value end
|
|
end,
|
|
})
|
|
if game.session then
|
|
game.session.name, game.session.gender = NAMES[mySeat], mySeat % 2
|
|
end
|
|
local taken, info = {}, {}
|
|
local lastAction = -1
|
|
local partyOpened = false
|
|
for _ = 1, 40000 do
|
|
if returned then break end
|
|
peerStep()
|
|
local st = Battle.getState()
|
|
if Battle.isActive() and st and st.multi then
|
|
info.own = st.linkOwn
|
|
local PartyMenu = package.loaded["src.ui.game3.party_menu"]
|
|
local partyOpen = PartyMenu and PartyMenu.isOpen and PartyMenu.isOpen()
|
|
local menuUp = Battle._phase == "command" and BattleUi._mode == "menu" and not partyOpen
|
|
local Message = package.loaded["src.ui.game3.message"]
|
|
local page = (Message and Message.isOpen and Message.isOpen() and Message.isWaiting and Message.isWaiting()
|
|
and tostring(Message.currentPage() or "")) or ""
|
|
page = page:gsub("\n", " ")
|
|
if shots.result and not taken.result and (page:find("defeated", 1, true) or page:find("lost to", 1, true)
|
|
or page:find("lost against", 1, true) or page:find("draw", 1, true)) then
|
|
taken.result = true
|
|
info.resultText = page
|
|
U.still(game, DIR .. "/" .. shots.result)
|
|
elseif shots.intro and not taken.intro and page:find("want to battle", 1, true) then
|
|
taken.intro = true
|
|
info.introText = page
|
|
local tp = require("src.core.game3.battle.anim").stage().trainer.player
|
|
info.backs = { own = tp.gender, partner = tp.gender2 }
|
|
U.wait(20)
|
|
U.still(game, DIR .. "/" .. shots.intro)
|
|
elseif shots.sendout and not taken.sendout and page:find("Go! ", 1, true) then
|
|
taken.sendout = true
|
|
info.sendoutText = page
|
|
U.wait(10)
|
|
U.still(game, DIR .. "/" .. shots.sendout)
|
|
elseif shots.start and not taken.start and menuUp then
|
|
taken.start = true
|
|
U.wait(8)
|
|
U.still(game, DIR .. "/" .. shots.start)
|
|
elseif shots.party and not taken.party and menuUp and not partyOpened then
|
|
partyOpened = true
|
|
U.tap(game, "down")
|
|
U.wait(4)
|
|
U.tap(game, "a")
|
|
U.wait(4)
|
|
elseif shots.party and not taken.party and partyOpen then
|
|
U.wait(40)
|
|
info.order = BattleUi.battlePartyOrder(st)
|
|
info.allies = BattleUi.allySlots(st, info.order)
|
|
U.still(game, DIR .. "/" .. shots.party)
|
|
taken.party = true
|
|
U.tap(game, "b")
|
|
U.wait(20)
|
|
if PartyMenu.isOpen() then U.tap(game, "b") U.wait(10) end
|
|
elseif not partyOpen and Battle._phase == "command"
|
|
and (BattleUi._mode == "menu" or BattleUi._mode == "moves" or BattleUi._mode == "target")
|
|
and U.frame() - lastAction > 6 and (taken.party or not shots.party) then
|
|
lastAction = U.frame()
|
|
if BattleUi._mode == "menu" and (tonumber(BattleUi._menuIndex) or 1) ~= 1 then
|
|
U.tap(game, "up")
|
|
U.wait(2)
|
|
U.tap(game, "left")
|
|
else
|
|
U.tap(game, "a")
|
|
end
|
|
elseif partyOpen and taken.party and PartyMenu.mode == "battle_faint" then
|
|
local order = BattleUi.battlePartyOrder(st)
|
|
local pick
|
|
for view, pi in ipairs(order) do
|
|
local m = st.playerParty[pi]
|
|
if not pick and st.partyOwner.player[pi] == st.linkOwn and m and (tonumber(m.hp) or 0) > 0 then pick = view end
|
|
end
|
|
if pick then PartyMenu.cursor = pick end
|
|
U.tap(game, "a")
|
|
U.wait(8)
|
|
U.tap(game, "a")
|
|
U.wait(8)
|
|
elseif partyOpen and taken.party then
|
|
U.tap(game, "b")
|
|
U.wait(4)
|
|
else
|
|
U.wait(1)
|
|
end
|
|
else
|
|
U.wait(1)
|
|
end
|
|
end
|
|
return outcome, digests, taken, info, relay
|
|
end
|
|
|
|
local r0, d0, shots0, info0, relay0 = runArena(0, {
|
|
intro = "g3battle_multi_seat0_intro_two_trainers.png",
|
|
sendout = "g3battle_multi_seat0_partner_sendout.png",
|
|
start = "g3battle_multi_seat0_battle_start.png",
|
|
party = "g3battle_multi_seat0_party_menu.png",
|
|
result = "g3battle_multi_seat0_result.png",
|
|
})
|
|
result(shots0.result, "seat 0 multi result was captured (" .. tostring(info0.resultText) .. ")")
|
|
result(shots0.intro and (info0.introText or ""):find("LEAF and GREEN", 1, true) ~= nil,
|
|
"seat 0 intro names both link opponents (" .. tostring(info0.introText) .. ")")
|
|
result(shots0.sendout and (info0.sendoutText or ""):find("BLUE sent out", 1, true) ~= nil,
|
|
"seat 0 intro has the partner send out first (" .. tostring(info0.sendoutText) .. ")")
|
|
result(r0 == "win" or r0 == "lose" or r0 == "draw", "seat 0 multi battle finished (" .. tostring(r0) .. ")")
|
|
result(returned and returned.tab == "online", "seat 0 returned to the launcher's online tab")
|
|
result(shots0.start and shots0.party, "seat 0 multi battle start and party menu were captured")
|
|
result(info0.own == 0, "seat 0 controls battler 0")
|
|
local own0 = {}
|
|
for view, allied in pairs(info0.allies or {}) do if allied then own0[#own0 + 1] = view end end
|
|
table.sort(own0)
|
|
result(table.concat(own0, ",") == "2,5,6", "seat 0 party menu tints the ally's boxes 2, 5 and 6 (" .. table.concat(own0, ",") .. ")")
|
|
local n0 = 0
|
|
for _ in pairs(d0 or {}) do n0 = n0 + 1 end
|
|
result(n0 >= 1, "seat 0 digested " .. n0 .. " turns")
|
|
local actions = {}
|
|
for _, row in ipairs(relay0.log) do
|
|
if row.msg.type == "game3_battle_action" and row.seat == 0 then
|
|
actions[#actions + 1] = row.msg
|
|
end
|
|
end
|
|
local single = #actions > 0
|
|
for _, a in ipairs(actions) do if a.actions ~= nil or a.kind == "list" then single = false end end
|
|
result(single, "seat 0 sent one action per turn for its own battler (" .. #actions .. ")")
|
|
|
|
local r2, d2, shots2, info2 = runArena(2, {
|
|
intro = "g3battle_multi_seat2_intro_two_trainers.png",
|
|
start = "g3battle_multi_seat2_battle_start.png",
|
|
party = "g3battle_multi_seat2_party_menu.png",
|
|
})
|
|
result(r2 == "win" or r2 == "lose" or r2 == "draw", "seat 2 multi battle finished (" .. tostring(r2) .. ")")
|
|
result(shots2.start and shots2.party, "seat 2 multi battle start and party menu were captured")
|
|
result(info2.own == 2, "seat 2 controls battler 2")
|
|
|
|
local r1, _, shots1, info1 = runArena(1, {
|
|
intro = "g3battle_multi_seat1_intro_two_trainers.png",
|
|
sendout = "g3battle_multi_seat1_partner_sendout.png",
|
|
start = "g3battle_multi_seat1_battle_start.png",
|
|
party = "g3battle_multi_seat1_party_menu.png",
|
|
})
|
|
result(shots1.intro and (info1.introText or ""):find("RED and BLUE", 1, true) ~= nil,
|
|
"seat 1 intro names seat 0 then seat 2 (" .. tostring(info1.introText) .. ")")
|
|
result(shots1.sendout and (info1.sendoutText or ""):find("GREEN sent out", 1, true) ~= nil,
|
|
"seat 1 intro has its partner seat 3 send out first (" .. tostring(info1.sendoutText) .. ")")
|
|
result(r1 == "win" or r1 == "lose" or r1 == "draw", "seat 1 multi battle finished (" .. tostring(r1) .. ")")
|
|
result(shots1.start and shots1.party, "seat 1 multi battle start and party menu were captured")
|
|
result(info1.own == 2, "seat 1 controls battler 2 (player right)")
|
|
local b1 = info1.backs or {}
|
|
result(b1.own == 1 and b1.partner == 1,
|
|
"seat 1 draws LEAF's and partner GREEN's female back pics (" .. tostring(b1.own) .. "," .. tostring(b1.partner) .. ")")
|
|
local b0 = info0.backs or {}
|
|
result(b0.own == 0 and b0.partner == 0,
|
|
"seat 0 draws RED's and partner BLUE's male back pics (" .. tostring(b0.own) .. "," .. tostring(b0.partner) .. ")")
|
|
local own1 = {}
|
|
for view, allied in pairs(info1.allies or {}) do if allied then own1[#own1 + 1] = view end end
|
|
table.sort(own1)
|
|
result(table.concat(own1, ",") == "2,5,6", "seat 1 party menu tints the ally's boxes 2, 5 and 6 (" .. table.concat(own1, ",") .. ")")
|
|
local relayS = H.relay({ seed = SEED, seats = 4, roomSeed = 30 })
|
|
local feeds = {}
|
|
for seat = 0, 3 do feeds[seat] = relayS:session(seat) end
|
|
for _, row in ipairs(relay0.log) do
|
|
if row.seat ~= nil and feeds[row.seat] and row.msg.type ~= "game3_bye" then feeds[row.seat]:send(row.msg) end
|
|
end
|
|
local watcher = relayS:session(nil)
|
|
returned = nil
|
|
local rS
|
|
game:enterArena({
|
|
profile = profile(), role = "spectator", seats = 4, seed = SEED, match = "ms", room = relayS.room,
|
|
players = players(), session = watcher, client = relayS:client(), mode = "multi",
|
|
onDone = function(r) rS = r end,
|
|
})
|
|
local takenS = {}
|
|
for _ = 1, 40000 do
|
|
if returned then break end
|
|
relayS:tick()
|
|
local st = Battle.getState()
|
|
local Message = package.loaded["src.ui.game3.message"]
|
|
local page = (Battle.isActive() and st and Message and Message.isOpen and Message.isOpen() and Message.isWaiting
|
|
and Message.isWaiting() and tostring(Message.currentPage() or "")) or ""
|
|
page = page:gsub("\n", " ")
|
|
if not takenS.intro and page:find("want to battle", 1, true) then
|
|
takenS.intro = true
|
|
U.still(game, DIR .. "/g3battle_multi_spectator_intro.png")
|
|
elseif not takenS.mid and st and (st.turn or 0) >= 2 and page:find(" used ", 1, true) then
|
|
takenS.mid = true
|
|
U.still(game, DIR .. "/g3battle_multi_spectator_mid_turn.png")
|
|
elseif not takenS.result and (page:find("defeated", 1, true) or page:find("lost to", 1, true)
|
|
or page:find("lost against", 1, true) or page:find("draw", 1, true)) then
|
|
takenS.result = true
|
|
U.still(game, DIR .. "/g3battle_multi_spectator_result.png")
|
|
end
|
|
U.wait(1)
|
|
end
|
|
result(rS == "ended", "the spectator watched the whole multi battle (" .. tostring(rS) .. ")")
|
|
result(takenS.intro and takenS.mid and takenS.result, "spectator intro, mid-turn and result were captured")
|
|
local spoke = false
|
|
for _, row in ipairs(relayS.log) do if row.seat == nil then spoke = true end end
|
|
result(not spoke, "the spectator never sent a message")
|
|
result(game.phase == "arena" and game.arena == nil, "the arena state was torn down")
|
|
finish()
|
|
end
|