Files
gen1recomp/tests/online_trade_relay3_test.lua
2026-09-24 05:17:32 -04:00

171 lines
6.5 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
if not _G.love then _G.love = require("tests.love_stub") end
local Cache = require("tests.game3_cache")
if not Cache.mount("meta.json") then
print("[skip] online_trade_relay3: " .. tostring(Cache.reason))
os.exit(0)
end
local okR, FakeRelay = pcall(require, "tests.support.fake_relay")
if not okR then
print("[skip] online_trade_relay3: no tests/support/fake_relay.lua: " .. tostring(FakeRelay))
os.exit(0)
end
local GameVersion = require("src.core.GameVersion")
GameVersion.set("firered")
local SaveData = require("src.core.SaveData")
local Trade = require("src.online.Trade")
local Fingerprint = require("src.link.Fingerprint")
local Pokemon = require("src.core.game3.pokemon")
local Party = require("src.core.game3.party")
local Schema = require("src.core.game3.save_schema_firered")
local RelayTransport = require("src.core.game3.link.relay_transport")
local Version = require("src.core.Version")
Pokemon.install(nil)
local files = {}
SaveData.portableFs = function()
return {
getInfo = function(name) return files[name] and { type = "file" } or nil end,
read = function(name) return files[name] end,
write = function(name, body) files[name] = body return true end,
remove = function(name) files[name] = nil return true end,
createDirectory = function() return true end,
}
end
local cache = require("src.core.game3.dataset").cache()
local fingerprint = Fingerprint.compute({ generation = 3,
gen3Inputs = Fingerprint.gen3Inputs(function(rel) return cache:read(rel) end) }, {}, 3)
local PROFILE = { engine = 3, version = "firered", engineVersion = Version.engine,
apiVersion = Version.modApi, fingerprint = fingerprint, rulesetId = "g3_link",
kind = "vanilla", rule = {} }
local function newClient(relay, seat)
package.loaded["src.online.Client"] = nil
local Client = require("src.online.Client")
Client.reset()
Client.configure({ relayAddress = "fake:1", connect = function() return seat.transport end })
return Client
end
local function makeSave(version, name, trainerId, mons)
local session = Schema.newGame({ version = version, name = name, rngSeed = trainerId })
session.trainerId = trainerId
session.dex.nationalUnlocked = true
session.party = {}
for _, row in ipairs(mons) do
assert(Party.giveMon(session, row.species, row.level))
local mon = session.party[#session.party]
for k, v in pairs(row.extra or {}) do mon[k] = v end
end
return SaveData.decode(SaveData.encode(Schema.toSaveTable(session)))
end
local DATA = { generation = 3, Pokemon = Pokemon, pokemon = Pokemon }
local function handle(version, slotId, save)
local path = Trade.slotPath(version, slotId)
files[path] = SaveData.encode(save)
return { version = version, generation = 3, slotId = slotId, save = save,
path = path, party = save.party, data = DATA }
end
local function setup(slot)
local relay = FakeRelay.new()
local seatA = relay:seat("a0000001", "RED")
local seatB = relay:seat("b0000002", "LEAF")
local B = newClient(relay, seatB)
local A = newClient(relay, seatA)
local function step(n)
for _ = 1, n or 1 do
relay:pump()
A.update(0)
B.update(0)
end
end
A.connect({ name = "RED", profiles = { PROFILE } })
B.connect({ name = "LEAF", profiles = { PROFILE } })
step(3)
A.queueDirect({ activity = "trade", ruleset = "g3_link", auto = true, profile = PROFILE,
avatar = { name = "RED", trainerId = 1, gender = 0, version = "firered" }, preview = { 64 } })
step(2)
B.queueDirect({ activity = "trade", ruleset = "g3_link", auto = true, profile = PROFILE,
avatar = { name = "LEAF", trainerId = 2, gender = 1, version = "leafgreen" }, preview = { 95 } })
step(3)
local hA = handle("firered", slot, makeSave("firered", "RED", 11111, {
{ species = 64, level = 30 }, { species = 25, level = 12 } }))
local hB = handle("leafgreen", slot, makeSave("leafgreen", "LEAF", 22222, {
{ species = 95, level = 25, extra = { item = 199, heldItem = 199 } }, { species = 1, level = 8 } }))
local rsA, rsB = A.roomSession(), B.roomSession()
local remoteA = Trade.remote(hA, rsA, { transport = RelayTransport.new(rsA, { client = A }) })
local remoteB = Trade.remote(hB, rsB, { transport = RelayTransport.new(rsB, { client = B }) })
return { relay = relay, A = A, B = B, step = step, rA = remoteA, rB = remoteB,
hA = hA, hB = hB, diskA = files[hA.path], diskB = files[hB.path] }
end
local function run(s, n, until_)
for _ = 1, n do
s.step(1)
s.rA:update()
s.rB:update()
if until_ and until_() then return true end
end
return until_ == nil or until_()
end
do
local s = setup("slot1")
T.eq(s.A.seat(), 0, "the AUTO pair seats FireRed first")
T.check(s.rA ~= nil and s.rB ~= nil, "both launchers open a Gen 3 remote trade")
s.rA:start()
s.rB:start()
T.check(run(s, 12, function()
return s.rA:stage() == "picking" and s.rB:stage() == "picking"
end), "both reach the trade menu over the real Client rooms")
s.rA:pick(1)
s.rB:pick(1)
T.check(run(s, 8, function()
return s.rA:stage() == "confirming" and s.rB:stage() == "confirming"
end), "the leader sets the mons to trade")
s.rA:confirm(true)
s.rB:confirm(true)
T.check(run(s, 12, function()
return s.rA:stage() == "committed" and s.rB:stage() == "committed"
end), "the relay barrier commits both sides: " .. tostring(s.rA:stage()) .. "/"
.. tostring(s.rB:stage()))
T.eq(SaveData.decode(files[s.hA.path]).party[1].species, 208, "FireRed holds Steelix")
T.eq(SaveData.decode(files[s.hB.path]).party[1].species, 65, "LeafGreen holds Alakazam")
s.rA:close()
s.rB:close()
s.step(3)
T.eq(s.A.room(), nil, "closing leaves the room")
end
do
local s = setup("slot2")
s.rA:start()
s.rB:start()
run(s, 12, function() return s.rA:stage() == "picking" and s.rB:stage() == "picking" end)
s.rA:pick(1)
s.rB:pick(1)
run(s, 8, function() return s.rA:stage() == "confirming" and s.rB:stage() == "confirming" end)
s.rB:confirm(true)
s.rA:confirm(true)
run(s, 12, function() return s.rB:stage() == "commit_wait" end)
T.eq(s.rB:stage(), "commit_wait", "seat 1 has confirmed its digest")
T.eq(s.rA:stage(), "exchange", "seat 0 has not sent its digest yet")
s.A.leaveRoom()
run(s, 10, function() return s.rB:stage() == "cancelled" end)
T.eq(s.rB:stage(), "cancelled", "seat 0 leaving before its digest aborts the trade")
T.eq(s.rA:stage(), "cancelled", "and seat 0's own trade is off")
T.eq(files[s.hA.path], s.diskA, "nothing written on seat 0")
T.eq(files[s.hB.path], s.diskB, "nothing written on seat 1")
end
T.finish()