mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
454 lines
20 KiB
Lua
454 lines
20 KiB
Lua
#!/usr/bin/env luajit
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local H = require("tests.link3_harness")
|
|
local Cache = require("tests.game3_cache")
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
local function eq(a, b, msg)
|
|
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
|
|
end
|
|
|
|
if not Cache.root("meta.json") then
|
|
print("[skip] Gen 3 multi battle runs the real battle engine on ROM data: " .. tostring(Cache.reason))
|
|
os.exit(0)
|
|
end
|
|
H.mountCache()
|
|
|
|
local quietPrint = print
|
|
local function silent(fn)
|
|
print = function() end
|
|
local ok, a, b, c = pcall(fn)
|
|
print = quietPrint
|
|
if not ok then error(a, 0) end
|
|
return a, b, c
|
|
end
|
|
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
|
|
local function mon(species, level, extra)
|
|
local m = {
|
|
species = species, level = level, moves = extra and extra.moves or Pokemon.movesAtLevel(species, level),
|
|
personality = extra and extra.personality or 0,
|
|
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 = extra and extra.item or 0,
|
|
}
|
|
return H.legal(m)
|
|
end
|
|
|
|
local NAMES = { [0] = "RED", [1] = "LEAF", [2] = "BLUE", [3] = "GREEN" }
|
|
|
|
local function session(seat)
|
|
return { name = NAMES[seat], trainerId = 0x1000 + seat, gender = seat % 2, party = {}, bag = {} }
|
|
end
|
|
|
|
local function makePolicy(opts)
|
|
opts = opts or {}
|
|
return function(w, st)
|
|
local sel = w.Battle._dblSel
|
|
if not (sel and sel.active) then return end
|
|
local id = sel.active
|
|
if opts.run and opts.run(w, st, id) then
|
|
w.Ui._pendingCommand = { kind = "run", user = "player", battler = id }
|
|
return
|
|
end
|
|
if opts.switch then
|
|
local slot = opts.switch(w, st, id)
|
|
if slot then
|
|
w.Ui._pendingCommand = { kind = "switch", user = "player", battler = id, slot = slot }
|
|
return
|
|
end
|
|
end
|
|
local b = w.State.battler(st, id)
|
|
local mon0 = b and b.mon or {}
|
|
local slot = 1
|
|
for i = 1, 4 do
|
|
if mon0.moves and mon0.moves[i] and (tonumber(mon0.pp and mon0.pp[i]) or 0) > 0 then
|
|
slot = i
|
|
if (st.turn + id) % 2 == 0 then break end
|
|
end
|
|
end
|
|
local targets = {}
|
|
for _, t in ipairs({ 1, 3 }) do
|
|
if w.State.isPresent(st, t) then targets[#targets + 1] = t end
|
|
end
|
|
local target = targets[(st.turn % math.max(1, #targets)) + 1] or 1
|
|
w.Ui._pendingCommand = w.Commands.playerAction(st, 1, slot, id, target)
|
|
end
|
|
end
|
|
|
|
local function runMatch(opts)
|
|
local relay = H.relay({ seed = opts.seed, seats = 4, lag = opts.lag or {}, roomSeed = opts.seed })
|
|
local ws = {}
|
|
silent(function()
|
|
for seat = 0, 3 do ws[seat] = H.newWorld("seat" .. seat, session(seat)) end
|
|
for seat = 0, 3 do
|
|
H.attachSeat(ws[seat], relay, seat, { mode = "multi", myParty = H.pack(opts.parties[seat]),
|
|
profile = { rule = {} }, autoFight = opts.auto })
|
|
end
|
|
end)
|
|
local spectator
|
|
if opts.watch then
|
|
spectator = H.newWorld("spectator", { name = "WATCH", trainerId = 1, gender = 0, party = {}, bag = {} })
|
|
silent(function() H.attachSpectator(spectator, relay, { mode = "multi", profile = { rule = {} } }) end)
|
|
end
|
|
local policies = {}
|
|
for seat = 0, 3 do policies[seat] = (opts.policies and opts.policies[seat]) or makePolicy() end
|
|
local frames = 0
|
|
silent(function()
|
|
while frames < 40000 do
|
|
frames = frames + 1
|
|
for seat = 0, 3 do
|
|
for _ = 1, (opts.steps and opts.steps[seat]) or 1 do H.step(ws[seat], policies[seat]) end
|
|
end
|
|
if spectator then H.step(spectator, nil) end
|
|
if opts.onFrame then opts.onFrame(ws, relay, frames) end
|
|
relay:tick()
|
|
local done = true
|
|
for seat = 0, 3 do if not ws[seat].result then done = false end end
|
|
if spectator and not spectator.result then done = false end
|
|
if done then break end
|
|
end
|
|
end)
|
|
return ws, spectator, relay, frames
|
|
end
|
|
|
|
local function hashesOf(w)
|
|
return H.run(w, function()
|
|
local out = {}
|
|
for turn, value in pairs(w.LB._myHashes or {}) do out[turn] = value end
|
|
return out
|
|
end)
|
|
end
|
|
|
|
local function sameHashes(a, b)
|
|
local n = 0
|
|
for turn, value in pairs(a) do
|
|
if b[turn] ~= nil then
|
|
n = n + 1
|
|
if b[turn] ~= value then return false, n, turn end
|
|
end
|
|
end
|
|
return true, n
|
|
end
|
|
|
|
local MIRROR = { win = "lose", lose = "win", draw = "draw" }
|
|
|
|
local function verify(label, ws, spectator, relay)
|
|
for seat = 0, 3 do
|
|
check(ws[seat].result ~= nil, label .. ": seat " .. seat .. " finished (" .. tostring(ws[seat].result) .. ")")
|
|
eq(H.run(ws[seat], function() return ws[seat].LB.endReason end), nil, label .. ": seat " .. seat .. " saw no desync")
|
|
eq(#ws[seat].reports, 1, label .. ": seat " .. seat .. " reported once")
|
|
end
|
|
eq(ws[2].result, ws[0].result, label .. ": partners 0 and 2 share a result")
|
|
eq(ws[3].result, ws[1].result, label .. ": partners 1 and 3 share a result")
|
|
eq(MIRROR[ws[0].result], ws[1].result, label .. ": the sides mirror")
|
|
local h0 = hashesOf(ws[0])
|
|
for seat = 1, 3 do
|
|
local ok, n, turn = sameHashes(h0, hashesOf(ws[seat]))
|
|
check(ok and n >= 2, label .. ": seat " .. seat .. " digested every turn like seat 0 (" .. tostring(n) .. " turns"
|
|
.. (turn and (", split at " .. turn) or "") .. ")")
|
|
end
|
|
if spectator then
|
|
local okS, nS = sameHashes(h0, hashesOf(spectator))
|
|
check(okS and nS >= 1, label .. ": the spectator simulated the same turns (" .. tostring(nS) .. ")")
|
|
eq(spectator.result, "ended", label .. ": the spectator watched to the end")
|
|
eq(#spectator.reports, 0, label .. ": the spectator never reports")
|
|
end
|
|
local kinds, outcomes = {}, {}
|
|
for _, row in ipairs(relay.log) do
|
|
kinds[row.msg.type] = (kinds[row.msg.type] or 0) + 1
|
|
if row.msg.type == "game3_battle_action" then
|
|
check(row.msg.actions == nil and row.msg.kind ~= "list", label .. ": seat " .. tostring(row.seat)
|
|
.. " sent a single action for turn " .. tostring(row.msg.turn))
|
|
if row.msg.actions ~= nil then break end
|
|
end
|
|
if row.msg.type == "game3_battle_outcome" then outcomes[row.seat] = row.msg.outcome end
|
|
end
|
|
eq((kinds.game3_battle_setup or 0), 4, label .. ": four setups crossed the relay")
|
|
eq((kinds.game3_battle_outcome or 0), 4, label .. ": every seat sent one game3_battle_outcome")
|
|
check((kinds.game3_battle_hash or 0) >= 8, label .. ": hashes crossed the relay (" .. tostring(kinds.game3_battle_hash) .. ")")
|
|
end
|
|
|
|
local PARTIES = {
|
|
[0] = { mon(6, 50), mon(25, 50), mon(131, 50) },
|
|
[1] = { mon(9, 50), mon(3, 50), mon(143, 50) },
|
|
[2] = { mon(65, 50), mon(68, 50), mon(94, 50) },
|
|
[3] = { mon(59, 50), mon(130, 50), mon(26, 50) },
|
|
}
|
|
|
|
print("[test] 1. four seats over the relay, one battler each, side A vs side B, with a spectator")
|
|
do
|
|
local ws, spectator, relay = runMatch({ seed = 0x4D17, parties = PARTIES, watch = true })
|
|
verify("multi", ws, spectator, relay)
|
|
end
|
|
|
|
print("[test] 2. each machine sees its own mons on its side of the field")
|
|
do
|
|
local relay = H.relay({ seed = 0x51, seats = 4, roomSeed = 0x51 })
|
|
local ws = {}
|
|
silent(function()
|
|
for seat = 0, 3 do ws[seat] = H.newWorld("layout" .. seat, session(seat)) end
|
|
for seat = 0, 3 do
|
|
H.attachSeat(ws[seat], relay, seat, { mode = "multi", myParty = H.pack(PARTIES[seat]), profile = { rule = {} } })
|
|
end
|
|
for _ = 1, 30 do
|
|
for seat = 0, 3 do H.step(ws[seat], nil) end
|
|
relay:tick()
|
|
end
|
|
end)
|
|
local function species(list)
|
|
local out = {}
|
|
for i, m in ipairs(list or {}) do out[i] = tonumber(m.species) end
|
|
return table.concat(out, ",")
|
|
end
|
|
local function speciesOf(seats)
|
|
local out = {}
|
|
for _, seat in ipairs(seats) do
|
|
for _, m in ipairs(PARTIES[seat]) do out[#out + 1] = tonumber(m.species) end
|
|
end
|
|
return table.concat(out, ",")
|
|
end
|
|
for seat = 0, 3 do
|
|
local st = H.run(ws[seat], function() return ws[seat].Battle.getState() end)
|
|
check(st ~= nil and st.multi == true, "layout: seat " .. seat .. " runs a multi battle")
|
|
if st then
|
|
local mySide = seat % 2
|
|
local SIDE = { [0] = { 0, 2 }, [1] = { 3, 1 } }
|
|
eq(species(st.playerParty), speciesOf(SIDE[mySide]), "layout: seat " .. seat .. " player party = its side's two trainers, left flank first")
|
|
eq(species(st.foeParty), speciesOf(SIDE[1 - mySide]), "layout: seat " .. seat .. " foe party = the other side, left flank first")
|
|
local own = (seat == 0 or seat == 3) and 0 or 2
|
|
eq(st.linkOwn, own, "layout: seat " .. seat .. " controls battler " .. own)
|
|
eq(st.linkNames[1], NAMES[SIDE[1 - mySide][1]], "layout: seat " .. seat .. " sees the left-flank opponent at battler 1")
|
|
eq(st.linkNames[3], NAMES[SIDE[1 - mySide][2]], "layout: seat " .. seat .. " sees the right-flank opponent at battler 3")
|
|
local byId = {}
|
|
for i, id in ipairs(H.run(ws[seat], function() return ws[seat].State.battlerOrder(st) end)) do
|
|
byId[i] = st.linkSeatOf[id]
|
|
end
|
|
eq(table.concat(byId, ","), "0,1,2,3", "layout: seat " .. seat .. " walks battlers in cart battler id order")
|
|
local b = H.run(ws[seat], function() return ws[seat].State.battler(st, own) end)
|
|
eq(tonumber(b and b.mon and b.mon.species), tonumber(PARTIES[seat][1].species), "layout: seat " .. seat .. " leads with its own first mon")
|
|
eq(st.linkNames[own], NAMES[seat], "layout: seat " .. seat .. " own battler named for its trainer")
|
|
eq(st.linkNames[(own + 2) % 4], NAMES[(seat + 2) % 4], "layout: seat " .. seat .. " partner battler named for the partner")
|
|
eq(st.linkMaster, seat % 2 == 0, "layout: seat " .. seat .. " perspective follows its side")
|
|
local partnerSlot = (own == 0) and 4 or 1
|
|
local err = H.run(ws[seat], function() return ws[seat].Commands.switchError(st, partnerSlot, false, own) end)
|
|
check(type(err) == "string" and err:find(NAMES[(seat + 2) % 4], 1, true) ~= nil,
|
|
"layout: seat " .. seat .. " can't switch to the ally's mon (" .. tostring(err) .. ")")
|
|
local ownBench = (own == 0) and 2 or 5
|
|
local okErr = H.run(ws[seat], function() return ws[seat].Commands.switchError(st, ownBench, false, own) end)
|
|
eq(okErr, nil, "layout: seat " .. seat .. " may switch to its own bench mon")
|
|
local cands = H.run(ws[seat], function()
|
|
return require("src.core.game3.battle.engine").replacementCandidates(st, own)
|
|
end)
|
|
local allOwn = #cands > 0
|
|
for _, c in ipairs(cands) do if st.partyOwner.player[c] ~= own then allOwn = false end end
|
|
check(allOwn, "layout: seat " .. seat .. " replacement candidates are its own mons only")
|
|
local order = H.run(ws[seat], function() return ws[seat].Ui.battlePartyOrder(st) end)
|
|
local view = {}
|
|
for i, pi in ipairs(order) do view[i] = st.partyOwner.player[pi] end
|
|
eq(table.concat(view, ","), table.concat({ own, (own + 2) % 4, own, own, (own + 2) % 4, (own + 2) % 4 }, ","),
|
|
"layout: seat " .. seat .. " party menu = own lead, ally lead, own bench, ally bench")
|
|
local intro = H.run(ws[seat], function()
|
|
local log = ws[seat].Ui.log() or {}
|
|
return table.concat(log, " / ")
|
|
end)
|
|
local pics = H.run(ws[seat], function()
|
|
return require("src.core.game3.battle.intro_seq").multiTrainerPics(st, 0)
|
|
end)
|
|
local LBw = ws[seat].LB
|
|
local function front(s0) return (s0 % 2 == 1) and LBw.TRAINER_PIC_LEAF or LBw.TRAINER_PIC_RED end
|
|
local partner = (seat + 2) % 4
|
|
check(pics ~= nil, "pics: seat " .. seat .. " builds the multi trainer pics")
|
|
if pics then
|
|
eq(pics.gender, seat % 2, "pics: seat " .. seat .. " own back pic uses its own gender")
|
|
eq(pics.partnerGender, partner % 2, "pics: seat " .. seat .. " partner back pic uses seat " .. partner .. "'s link gender")
|
|
eq(pics.x, (own == 2) and 90 or 32, "pics: seat " .. seat .. " own back pic on its flank")
|
|
eq(pics.partnerX, (own == 2) and 32 or 90, "pics: seat " .. seat .. " partner back pic on the other flank")
|
|
eq(pics.enemyPic, front(SIDE[1 - mySide][1]), "pics: seat " .. seat .. " left opponent front pic by link gender")
|
|
eq(pics.enemyPic2, front(SIDE[1 - mySide][2]), "pics: seat " .. seat .. " right opponent front pic by link gender")
|
|
end
|
|
local opp1 = (seat % 2 == 0) and seat + 1 or seat - 1
|
|
local opp2 = (opp1 + 2) % 4
|
|
check(intro:find(NAMES[opp1] .. " and " .. NAMES[opp2], 1, true) ~= nil,
|
|
"layout: seat " .. seat .. " is challenged by both link opponents (" .. intro:sub(1, 80) .. ")")
|
|
end
|
|
end
|
|
end
|
|
|
|
print("[test] 3. a switch to an own bench mon crosses to all four machines")
|
|
do
|
|
local switched = {}
|
|
local policies = {}
|
|
for seat = 0, 3 do
|
|
policies[seat] = makePolicy({
|
|
switch = function(w, st, id)
|
|
if seat == 2 and st.turn == 1 and not switched[seat] then
|
|
switched[seat] = true
|
|
return 5
|
|
end
|
|
return nil
|
|
end,
|
|
})
|
|
end
|
|
local ws, _, relay = runMatch({ seed = 0x5117C, parties = PARTIES, policies = policies })
|
|
verify("switch", ws, nil, relay)
|
|
local sawSwitch = false
|
|
for _, row in ipairs(relay.log) do
|
|
if row.seat == 2 and row.msg.type == "game3_battle_action" and row.msg.kind == "switch" then sawSwitch = true end
|
|
end
|
|
check(sawSwitch, "switch: seat 2's switch went out as its own action")
|
|
end
|
|
|
|
print("[test] 4. a partner running ends the battle as a loss for that side")
|
|
do
|
|
local policies = {}
|
|
for seat = 0, 3 do
|
|
policies[seat] = makePolicy({
|
|
run = function(w, st, id) return seat == 3 and st.turn >= 1 end,
|
|
})
|
|
end
|
|
local ws = runMatch({ seed = 0x7777, parties = PARTIES, policies = policies })
|
|
eq(ws[0].result, "win", "run: seat 0 wins when the other side runs")
|
|
eq(ws[2].result, "win", "run: seat 2 wins too")
|
|
eq(ws[1].result, "lose", "run: seat 1 loses with its runaway partner")
|
|
eq(ws[3].result, "lose", "run: seat 3 loses")
|
|
end
|
|
|
|
print("[test] 5. lag and uneven frame rates across four seats stay in lockstep")
|
|
do
|
|
local ws, spectator, relay = runMatch({ seed = 0x2BAD, parties = {
|
|
[0] = { mon(149, 55), mon(26, 50) },
|
|
[1] = { mon(248, 55) },
|
|
[2] = { mon(94, 50), mon(65, 50), mon(3, 50) },
|
|
[3] = { mon(143, 50), mon(59, 50) },
|
|
}, watch = true, lag = { [0] = 2, [1] = 0, [2] = 3, [3] = 1, spectator = 2 }, steps = { [0] = 1, [1] = 3, [2] = 2, [3] = 1 } })
|
|
verify("lag", ws, spectator, relay)
|
|
end
|
|
|
|
print("[test] 6. a seat that sends a four-mon party is refused")
|
|
do
|
|
local extra = H.pack({ mon(3, 50) })[1]
|
|
local relay = H.relay({ seed = 0x99, seats = 4, roomSeed = 0x99, tamper = function(fromSeat, _, msg)
|
|
if fromSeat == 3 and msg.type == "game3_battle_setup" and type(msg.party) == "table" then
|
|
msg.party[#msg.party + 1] = extra
|
|
end
|
|
return msg
|
|
end })
|
|
local ws = {}
|
|
silent(function()
|
|
for seat = 0, 3 do ws[seat] = H.newWorld("refuse" .. seat, session(seat)) end
|
|
for seat = 0, 3 do
|
|
H.attachSeat(ws[seat], relay, seat, { mode = "multi", myParty = H.pack(PARTIES[seat]), profile = { rule = {} } })
|
|
end
|
|
for _ = 1, 60 do
|
|
for seat = 0, 3 do H.step(ws[seat], nil) end
|
|
relay:tick()
|
|
end
|
|
end)
|
|
for seat = 0, 2 do
|
|
eq(ws[seat].result, "error", "refuse: seat " .. seat .. " refuses seat 3's four-mon party")
|
|
eq(H.run(ws[seat], function() return ws[seat].LB.endReason end), "bad party", "refuse: seat " .. seat .. " names the reason")
|
|
end
|
|
local forfeits = 0
|
|
for _, row in ipairs(relay.log) do if row.msg.type == "forfeit" then forfeits = forfeits + 1 end end
|
|
check(forfeits >= 3, "refuse: every refusing seat sent forfeit (" .. forfeits .. ")")
|
|
end
|
|
|
|
print("[test] 7. a seat whose replacement never arrives is caught instead of stalling the room")
|
|
do
|
|
local relay = H.relay({ seed = 0x5EED, seats = 4, roomSeed = 0x5EED, tamper = function(fromSeat, _, msg)
|
|
if fromSeat == 2 and msg.type == "game3_battle_switch" then msg.type = "game3_link_card" end
|
|
return msg
|
|
end })
|
|
local parties = {
|
|
[0] = { mon(6, 50), mon(25, 50) },
|
|
[1] = { mon(9, 50), mon(3, 50) },
|
|
[2] = { mon(129, 5), mon(94, 50) },
|
|
[3] = { mon(59, 50), mon(26, 50) },
|
|
}
|
|
local ws = {}
|
|
silent(function()
|
|
for seat = 0, 3 do ws[seat] = H.newWorld("skip" .. seat, session(seat)) end
|
|
for seat = 0, 3 do
|
|
H.attachSeat(ws[seat], relay, seat, { mode = "multi", myParty = H.pack(parties[seat]), profile = { rule = {} } })
|
|
end
|
|
local policy = makePolicy()
|
|
for _ = 1, 20000 do
|
|
for seat = 0, 3 do H.step(ws[seat], policy) end
|
|
relay:tick()
|
|
if ws[0].result and ws[1].result and ws[2].result and ws[3].result then break end
|
|
end
|
|
end)
|
|
for seat = 0, 3 do
|
|
eq(ws[seat].result, "draw", "skip: seat " .. seat .. " ends drawn")
|
|
eq(H.run(ws[seat], function() return ws[seat].LB.endReason end), "desync", "skip: seat " .. seat .. " ends as a desync")
|
|
end
|
|
local log0 = H.run(ws[0], function() return table.concat(ws[0].LB.log, "\n") end)
|
|
check(log0:find("component=switch", 1, true) ~= nil, "skip: seat 0 named the missing switch (" .. log0:gsub("\n", " | ") .. ")")
|
|
end
|
|
|
|
print("[test] 8. layout helpers")
|
|
do
|
|
local w = H.newWorld("helpers", session(0))
|
|
H.run(w, function()
|
|
local LB = w.LB
|
|
eq(LB.localBattler(1, 1), 2, "helpers: seat 1 sees itself as battler 2 (player right)")
|
|
eq(LB.localBattler(1, 3), 0, "helpers: seat 1 sees its partner seat 3 as battler 0 (player left)")
|
|
eq(LB.localBattler(1, 0), 1, "helpers: seat 1 sees seat 0 as battler 1 (opponent left)")
|
|
eq(LB.localBattler(3, 3), 0, "helpers: seat 3 sees itself as battler 0 (player left)")
|
|
eq(LB.localBattler(2, 2), 2, "helpers: seat 2 sees itself as battler 2 (player right)")
|
|
eq(LB.localBattler(0, 3), 1, "helpers: seat 0 sees seat 3 as battler 1 (opponent left)")
|
|
eq(LB.localBattler(0, 1), 3, "helpers: seat 0 sees seat 1 as battler 3 (opponent right)")
|
|
eq(LB.localBattler(nil, 3), 1, "helpers: a spectator watches from seat 0's side")
|
|
eq(LB.oppositeSeat(2), 3, "helpers: seat 2 faces seat 3")
|
|
local layout, pp, fp = LB.multiLayout(3, { [0] = { "a" }, [1] = { "b", "c" }, [2] = { "d" }, [3] = { "e", "f", "g" } },
|
|
{ [0] = "RED", [1] = "LEAF", [2] = "BLUE", [3] = "GREEN" }, { [0] = 0, [1] = 1, [2] = 0, [3] = 1 })
|
|
eq(table.concat(pp, ""), "efgbc", "helpers: seat 3's side party is seat 3 then seat 1")
|
|
eq(table.concat(fp, ""), "ad", "helpers: seat 3's foe party is seat 0 then seat 2")
|
|
eq(table.concat(layout.owners.player, ","), "0,0,0,2,2", "helpers: owners follow the local battler ids")
|
|
eq(layout.own, 0, "helpers: seat 3 owns battler 0")
|
|
eq(layout.names[1], "RED", "helpers: seat 3's battler 1 is seat 0")
|
|
eq(layout.genders[3], 0, "helpers: seat 3's battler 3 is seat 2's gender")
|
|
eq(layout.seatOf[2], 1, "helpers: seat 3's battler 2 is seat 1")
|
|
eq(layout.localOf[1], 2, "helpers: seat 3 finds seat 1 at battler 2")
|
|
eq(table.concat(layout.order, ","), "1,2,3,0", "helpers: seat 3 walks cart battler ids 0-3 as local 1,2,3,0")
|
|
local IntroSeq = require("src.core.game3.battle.intro_seq")
|
|
local G = { [0] = 0, [1] = 1, [2] = 1, [3] = 0 }
|
|
local SIDE = { [0] = { 0, 2 }, [1] = { 3, 1 } }
|
|
local function front(s0) return (G[s0] == 1) and LB.TRAINER_PIC_LEAF or LB.TRAINER_PIC_RED end
|
|
for seat = 0, 3 do
|
|
local lay = LB.multiLayout(seat, { [0] = {}, [1] = {}, [2] = {}, [3] = {} }, NAMES, G)
|
|
local pics = IntroSeq.multiTrainerPics({ multi = true, linkOwn = lay.own, linkGenders = lay.genders }, 0)
|
|
local partner = (seat + 2) % 4
|
|
local foes = SIDE[1 - seat % 2]
|
|
eq(pics.gender, G[seat], "helpers: seat " .. seat .. " own back pic gender with mixed genders")
|
|
eq(pics.partnerGender, G[partner], "helpers: seat " .. seat .. " partner back pic is seat " .. partner .. "'s gender")
|
|
eq(pics.enemyPic, front(foes[1]), "helpers: seat " .. seat .. " left opponent pic is seat " .. foes[1] .. "'s")
|
|
eq(pics.enemyPic2, front(foes[2]), "helpers: seat " .. seat .. " right opponent pic is seat " .. foes[2] .. "'s")
|
|
end
|
|
local ok, why = LB.unpackMultiParty(H.pack({ mon(1, 5), mon(4, 5), mon(7, 5), mon(25, 5) }))
|
|
check(ok == nil and why == "bad party", "helpers: four mons are refused (" .. tostring(why) .. ")")
|
|
LB.mode = LB.MODE_OF.multi
|
|
LB._myPacked = H.pack({ mon(1, 5), mon(4, 5), mon(7, 5), mon(25, 5) })
|
|
eq(#LB.myPacked(), 3, "helpers: this seat sends only its first three")
|
|
LB.reset()
|
|
end)
|
|
end
|
|
|
|
if failed == 0 then
|
|
print("[pass] link3 multi")
|
|
os.exit(0)
|
|
end
|
|
print("[fail] link3 multi: " .. failed)
|
|
os.exit(1)
|