mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 05:32:07 -04:00
282 lines
9.6 KiB
Lua
282 lines
9.6 KiB
Lua
#!/usr/bin/env luajit
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local Cache = require("tests.game3_cache")
|
|
if not Cache.root("meta.json") then
|
|
print("[skip] online_trade_interop3: " .. tostring(Cache.reason))
|
|
os.exit(0)
|
|
end
|
|
if not _G.love then _G.love = require("tests.love_stub") end
|
|
love.graphics = nil
|
|
|
|
local failed, total = 0, 0
|
|
local function check(cond, msg)
|
|
total = total + 1
|
|
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
|
|
|
|
local TRADE_CENTER = "FR_TRADE_CENTER"
|
|
local MAPS = {
|
|
[TRADE_CENTER] = {
|
|
warps = { { x = 5, y = 8, mapGroup = 0x7F, mapNum = 0x7F, destWarp = 128 } },
|
|
},
|
|
}
|
|
|
|
local function isWorldModule(k)
|
|
return k:match("^src%.") ~= nil
|
|
end
|
|
|
|
local current
|
|
|
|
local function stash()
|
|
if not current then return end
|
|
for k, v in pairs(package.loaded) do
|
|
if isWorldModule(k) then current.mods[k] = v end
|
|
end
|
|
end
|
|
|
|
local function clearWorld()
|
|
for k in pairs(package.loaded) do
|
|
if isWorldModule(k) then package.loaded[k] = nil end
|
|
end
|
|
end
|
|
|
|
local function within(w, fn, ...)
|
|
if current ~= w then
|
|
stash()
|
|
clearWorld()
|
|
for k, v in pairs(w.mods) do package.loaded[k] = v end
|
|
current = w
|
|
end
|
|
return fn(...)
|
|
end
|
|
|
|
local function newWorld()
|
|
stash()
|
|
clearWorld()
|
|
local w = { mods = {} }
|
|
current = w
|
|
Cache.mount("meta.json")
|
|
return w
|
|
end
|
|
|
|
local function gameWorld(name, trainerId, species)
|
|
local w = newWorld()
|
|
w.saves = 0
|
|
local session = {
|
|
store = { flags = {}, vars = {} }, map = TRADE_CENTER, x = 5, y = 8,
|
|
name = name, gender = 0, trainerId = trainerId, party = {},
|
|
bag = { pockets = { items = {} } }, dex = { seen = {}, owned = {}, caught = {} },
|
|
gameStats = {},
|
|
}
|
|
local game = { data = { maps = MAPS, generation = 3 }, session = session }
|
|
function game:saveGame() w.saves = w.saves + 1 end
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return session end,
|
|
isActive = function() return true end,
|
|
_game = game,
|
|
}
|
|
package.loaded["src.core.game3.player"] = { cellX = 5, cellY = 8, facing = "up" }
|
|
package.loaded["src.core.game3.quest_log_recorder"] = { event = function() end }
|
|
package.loaded["src.core.game3.rom_text"] = {
|
|
plain = function(key) return key end, box = function(key) return key end,
|
|
ascii = function(key) return key end, has = function() return true end,
|
|
key = function(n, i) return n .. "[" .. i .. "]" end,
|
|
at = function(n, i) return n .. "[" .. i .. "]" end,
|
|
count = function() return 0 end, list = function() return {} end,
|
|
lazy = function(map) return setmetatable({}, { __index = function(_, k) return map[k] end }) end,
|
|
}
|
|
package.loaded["src.core.game3.map"] = { load = function() end, current = TRADE_CENTER }
|
|
package.loaded["src.core.game3.objects"] = {
|
|
addObject = function() return true end,
|
|
removeObject = function() return true end,
|
|
refreshGraphics = function() return 0 end,
|
|
}
|
|
package.loaded["src.core.game3.scripting.space"] = {
|
|
store = session.store, mapId = TRADE_CENTER,
|
|
vm = { ctx = { specialVars = {}, stringVars = {} }, adapters = { log = function() end } },
|
|
}
|
|
require("src.core.game3.pokemon").install(nil)
|
|
local Party = require("src.core.game3.party")
|
|
for _, sp in ipairs(species) do Party.giveMon(session, sp[1], sp[2]) end
|
|
w.session, w.game = session, game
|
|
w.Link = require("src.core.game3.link")
|
|
w.LT = require("src.core.game3.link.trade")
|
|
w.Game3Link = require("src.link.Game3Link")
|
|
stash()
|
|
return w
|
|
end
|
|
|
|
local function launcherWorld(name, trainerId, species)
|
|
local w = newWorld()
|
|
w.files = {}
|
|
local SaveData = require("src.core.SaveData")
|
|
SaveData.portableFs = function()
|
|
return {
|
|
getInfo = function(n) return w.files[n] and { type = "file" } or nil end,
|
|
read = function(n) return w.files[n] end,
|
|
write = function(n, body) w.files[n] = body return true end,
|
|
remove = function(n) w.files[n] = nil return true end,
|
|
createDirectory = function() return true end,
|
|
}
|
|
end
|
|
require("src.core.GameVersion").set("leafgreen")
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
Pokemon.install(nil)
|
|
local Schema = require("src.core.game3.save_schema_firered")
|
|
local Party = require("src.core.game3.party")
|
|
local s = Schema.newGame({ version = "leafgreen", name = name, rngSeed = trainerId })
|
|
s.trainerId = trainerId
|
|
s.party = {}
|
|
for _, sp in ipairs(species) do Party.giveMon(s, sp[1], sp[2]) end
|
|
local save = SaveData.decode(SaveData.encode(Schema.toSaveTable(s)))
|
|
local Trade = require("src.online.Trade")
|
|
local path = Trade.slotPath("leafgreen", "slot1")
|
|
w.files[path] = SaveData.encode(save)
|
|
w.path, w.disk = path, w.files[path]
|
|
w.handle = { version = "leafgreen", generation = 3, slotId = "slot1", save = save,
|
|
path = path, party = save.party,
|
|
data = { generation = 3, Pokemon = Pokemon, pokemon = Pokemon } }
|
|
w.Trade, w.SaveData = Trade, SaveData
|
|
stash()
|
|
return w
|
|
end
|
|
|
|
local function newRelay()
|
|
local R = { seats = {}, confirms = {}, n = 1, commits = 0, aborts = 0 }
|
|
local function deliver(from, msg)
|
|
for s, t in pairs(R.seats) do
|
|
if s ~= from and not t.closed then
|
|
local m = {}
|
|
for k, v in pairs(msg) do m[k] = v end
|
|
m.seat = from
|
|
t.inbox[#t.inbox + 1] = m
|
|
end
|
|
end
|
|
end
|
|
function R.receive(seat, msg)
|
|
if msg.type == "game3_trade_confirm" then
|
|
R.confirms[seat] = msg.digest
|
|
local d0, d1 = R.confirms[0], R.confirms[1]
|
|
if d0 and d1 then
|
|
R.confirms = {}
|
|
if d0 == d1 then
|
|
R.commits = R.commits + 1
|
|
deliver(-1, { type = "trade_commit", n = R.n, digests = { d0, d1 } })
|
|
R.n = R.n + 1
|
|
else
|
|
R.aborts = R.aborts + 1
|
|
deliver(-1, { type = "trade_abort", n = R.n, why = "digest" })
|
|
end
|
|
end
|
|
return
|
|
end
|
|
deliver(seat, msg)
|
|
end
|
|
function R.transport(seat)
|
|
local t = { relay = true, paired = true, closed = false, inbox = {} }
|
|
function t:update() end
|
|
function t:send(msg)
|
|
if self.closed then return false end
|
|
local copy = {}
|
|
for k, v in pairs(msg) do copy[k] = v end
|
|
copy.seat = nil
|
|
R.receive(seat, copy)
|
|
return true
|
|
end
|
|
function t:poll()
|
|
local out = self.inbox
|
|
self.inbox = {}
|
|
return out
|
|
end
|
|
function t:take() return nil end
|
|
function t:close() self.closed = true end
|
|
function t:seat() return seat end
|
|
function t:seats() return 2 end
|
|
function t:peerOnline(s) local o = R.seats[s] return o ~= nil and not o.closed end
|
|
R.seats[seat] = t
|
|
return t
|
|
end
|
|
return R
|
|
end
|
|
|
|
local function scenario(label, gameSeat)
|
|
print("[test] " .. label)
|
|
local launcherSeat = 1 - gameSeat
|
|
local relay = newRelay()
|
|
local G = gameWorld("RED", 0x1234, { { 64, 30 }, { 25, 14 } })
|
|
local L = launcherWorld("LEAF", 0x2222, { { 95, 25 }, { 1, 8 } })
|
|
local tG, tL = relay.transport(gameSeat), relay.transport(launcherSeat)
|
|
within(G, function()
|
|
G.Link.reset()
|
|
G.LT.reset()
|
|
G.Link.attach(G.Game3Link.attach(tG, { seat = gameSeat, seats = 2, game = G.game,
|
|
linkType = G.LT.LINKTYPE.TRADE }))
|
|
end)
|
|
local remote = within(L, function()
|
|
local r = assert(L.Trade.remote(L.handle, tL, { transport = tL }))
|
|
r:start()
|
|
return r
|
|
end)
|
|
local function step(n)
|
|
for _ = 1, n or 1 do
|
|
within(G, function() G.Link.update(0) end)
|
|
within(L, function() remote:update() end)
|
|
end
|
|
end
|
|
step(3)
|
|
check(within(G, function() return G.Link.link and G.Link.link:isReady() end),
|
|
"the in-game link handshakes with the launcher as full")
|
|
within(G, function()
|
|
G.Link.link:send({ type = "game3_battle_seat", seat = gameSeat })
|
|
G.LT.startMenu({ screen = false })
|
|
end)
|
|
step(3)
|
|
eq(remote:stage(), "picking", "the launcher reaches the trade menu")
|
|
eq(within(G, function() return #G.LT.peerParty end), 2, "the game sees the launcher's party")
|
|
eq(within(G, function() return G.LT.peerParty[1].species end), 95, "packed as packMon3")
|
|
eq(#remote.session.theirParty, 2, "the launcher sees the game's party")
|
|
within(G, function() G.LT.offer(1) end)
|
|
within(L, function() remote:pick(1) end)
|
|
step(3)
|
|
eq(remote:stage(), "confirming", "the launcher is asked to confirm")
|
|
eq(within(G, function() return G.LT.state end), "confirm", "and so is the game")
|
|
within(G, function() G.LT.confirm(true) end)
|
|
within(L, function() remote:confirm(true) end)
|
|
local done = false
|
|
for _ = 1, 6000 do
|
|
step(1)
|
|
if within(G, function() return G.LT.completed end) == 1 and remote:stage() == "committed" then
|
|
done = true
|
|
break
|
|
end
|
|
end
|
|
check(done, "the in-game scene finished and the launcher committed ("
|
|
.. tostring(within(G, function() return G.LT.state .. "/" .. tostring(G.LT.lastResult)
|
|
.. "/" .. tostring(G.LT.lastRefusal) end)) .. ", " .. tostring(remote:stage()) .. ")")
|
|
eq(relay.commits, 1, "one trade_commit")
|
|
eq(G.saves, 1, "the game saved once")
|
|
eq(within(G, function() return G.session.party[1].species end), 95,
|
|
"the game holds the launcher's Onix")
|
|
eq(within(G, function() return G.session.party[1].otName end), "LEAF",
|
|
"with the launcher player as its OT")
|
|
local disk = within(L, function() return L.SaveData.decode(L.files[L.path]) end)
|
|
eq(disk and disk.party[1].species, 65, "the launcher's file holds the Kadabra, now Alakazam")
|
|
check(within(L, function() return L.files[L.path] end) ~= L.disk, "the launcher wrote its file")
|
|
end
|
|
|
|
scenario("1. in-game seat 0 trades with a launcher at seat 1", 0)
|
|
scenario("2. a launcher at seat 0 leads an in-game seat 1", 1)
|
|
|
|
print(("%d/%d checks passed"):format(total - failed, total))
|
|
os.exit(failed == 0 and 0 or 1)
|