mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-08 09:42:55 -04:00
305 lines
14 KiB
Lua
305 lines
14 KiB
Lua
#!/usr/bin/env luajit
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
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
|
|
|
|
local FakeRelay = require("tests.g3link_fake_relay")
|
|
|
|
local store = { flags = {}, vars = {} }
|
|
local session = {
|
|
version = "ruby", store = store, map = "RS_OLDALE_TOWN_POKEMON_CENTER_2F", x = 5, y = 3,
|
|
name = "BRENDAN", gender = 0, trainerId = 0x2222,
|
|
party = { { species = 280, level = 12 }, { species = 283, level = 9 } },
|
|
bag = { pockets = { items = {} } },
|
|
}
|
|
local input = { pressed = {} }
|
|
function input:wasPressed(k) return self.pressed[k] == true end
|
|
function input:isDown(k) return self.pressed[k] == true end
|
|
local game = { data = { maps = {} }, session = session, input = input, save = { player = { name = "BRENDAN" } } }
|
|
|
|
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 = 3, facing = "up" }
|
|
package.loaded["src.core.game3.map"] = { load = 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,
|
|
ir = function(key) return { { t = "text", s = key } } end,
|
|
translate = function(ir) return ir end,
|
|
key = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
|
|
at = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (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,
|
|
}
|
|
|
|
local ctx = { specialVars = {}, stringVars = {} }
|
|
local adapters = { log = function() end, playSe = function() end }
|
|
package.loaded["src.core.game3.scripting.space"] = {
|
|
store = store, mapId = session.map, vm = { ctx = ctx, adapters = adapters },
|
|
ensureBundle = function() return nil end,
|
|
}
|
|
|
|
local LIVE = { engine = 3, engineVersion = "1.0.0", fingerprint = "f00dcafe", kind = "vanilla" }
|
|
package.loaded["src.online.ArenaData"] = {
|
|
liveProfile3 = function(_, rulesetId)
|
|
local p = { version = session.version }
|
|
for k, v in pairs(LIVE) do p[k] = v end
|
|
p.rulesetId = rulesetId
|
|
return p
|
|
end,
|
|
}
|
|
local Client = FakeRelay.client({ state = "online", id = "0000beef" })
|
|
package.loaded["src.online.Client"] = Client
|
|
|
|
local Link = require("src.core.game3.link")
|
|
local LB = require("src.core.game3.link.battle")
|
|
local Union = require("src.core.game3.link.union_room")
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
local Lobby = require("src.ui.game3.rs.cable_lobby")
|
|
local Game3Link = require("src.link.Game3Link")
|
|
local NativesLinkRs = require("src.core.game3.scripting.natives_link_rs")
|
|
|
|
local function getVar(id) return tonumber(Flags.getVar(store, ctx, id)) or 0 end
|
|
local function setVar(id, v) Flags.setVar(store, ctx, id, v) end
|
|
local function fresh()
|
|
ctx.specialVars = {}
|
|
ctx.nativePoll, ctx.mode, ctx.status = nil, nil, nil
|
|
Client.clear()
|
|
Client._group, Client._groups, Client._directEntries = nil, {}, {}
|
|
Client._room, Client._seat = nil, nil
|
|
Lobby.reset()
|
|
Link.reset()
|
|
Link._live = nil
|
|
end
|
|
local function rowIds()
|
|
local out = {}
|
|
for i, row in ipairs(Lobby.rows()) do out[i] = row.id end
|
|
return out
|
|
end
|
|
local function pick(id)
|
|
for _, row in ipairs(Lobby.rows()) do
|
|
if row.id == id then return Lobby.opts.select(row) end
|
|
end
|
|
error("no row " .. tostring(id))
|
|
end
|
|
local function poll(n)
|
|
local r
|
|
for _ = 1, n or 1 do
|
|
r = ctx.nativePoll()
|
|
if r then break end
|
|
end
|
|
return r
|
|
end
|
|
local function me(id) return { id = id or "0000beef", name = "BRENDAN", avatar = { name = "BRENDAN" } } end
|
|
local function peerOn(room, seat, version, linkType)
|
|
local hello = Game3Link.hello(game, linkType, { version = version, name = version:upper(), trainerId = 0x1000 + seat })
|
|
return Game3Link.attach(FakeRelay.transport(room, seat), {
|
|
game = game, linkType = linkType, seat = seat, seats = room.seats, hello = hello,
|
|
})
|
|
end
|
|
local function pump(peers)
|
|
for _ = 1, 4 do
|
|
for _, p in ipairs(peers) do p:update(0) end
|
|
Link.update(0)
|
|
end
|
|
end
|
|
|
|
print("[test] 1. a Ruby cable leader auto-accepts and starts a two-seat group")
|
|
fresh()
|
|
setVar(0x8004, 1)
|
|
local yielded = NativesLinkRs.BY_NAME.sub_808347C(ctx, adapters)
|
|
check(yielded, "sub_808347C parks the script")
|
|
check(Lobby.isOpen(), "the cable lobby is up")
|
|
eq(table.concat(rowIds(), ","), "leader,join", "CREATE GROUP / JOIN GROUP")
|
|
pick("leader")
|
|
local og = Client.last("openGroup")
|
|
eq(og and og[1], "battle_single", "the leader opens a battle_single group")
|
|
eq(og and og[2] and og[2].rulesetId, "g3_single", "with the battle ruleset profile")
|
|
Client._group = { leader = "0000beef", min = 2, max = 2, members = { me() },
|
|
pending = { { id = "00e00001", name = "MAY", avatar = { name = "MAY" } } } }
|
|
poll()
|
|
local acc = Client.last("acceptGroup")
|
|
eq(acc and acc[1], "00e00001", "the first group_request is accepted without a prompt")
|
|
eq(acc and acc[2], true, "with ok = true")
|
|
Client._group = { leader = "0000beef", min = 2, max = 2, members = { me(), { id = "00e00001", name = "MAY" } }, pending = {} }
|
|
poll()
|
|
eq(Client.count("startGroup"), 1, "a full two-seat group starts by itself")
|
|
|
|
print("[test] 2. an Emerald wireless peer seated into the group links up with no linkup message")
|
|
local room = FakeRelay.room({ seats = 2 })
|
|
Client.bindRoom(room, 0)
|
|
local peer = peerOn(room, 1, "emerald", Game3Link.LINKTYPE.BATTLE)
|
|
check(poll() == false, "the match opens the relay link")
|
|
check(Link.link ~= nil, "Link.openRelay ran")
|
|
pump({ peer })
|
|
check(Link.link and Link.link:isReady(), "the hellos agree")
|
|
local done = poll(LB.LINKUP_TICKS + 5)
|
|
check(done == true, "the linkup finishes")
|
|
eq(getVar(Link.VAR_RESULT), Link.LINKUP.SUCCESS, "Ruby vs an Emerald wireless player is LINKUP_SUCCESS")
|
|
eq(LB.state, "seat", "and the colosseum seat wait begins")
|
|
eq(Link.link and Link.link.linkType, 0x2233, "the link runs as LINKTYPE_SINGLE_BATTLE")
|
|
check(not Lobby.isOpen(), "the lobby closed")
|
|
|
|
print("[test] 3. a Ruby joiner sees Direct Corner hosts next to cable groups")
|
|
fresh()
|
|
setVar(0x8004, 2)
|
|
NativesLinkRs.BY_NAME.sub_808347C(ctx, adapters)
|
|
pick("join")
|
|
eq(Client.last("groupList") and Client.last("groupList")[1], "battle_double", "it watches battle_double groups")
|
|
eq(Client.last("directList") and Client.last("directList")[1], "battle_double", "and the Direct Corner list")
|
|
Client._groups = { { leader = "00c00001", name = "RED", avatar = { name = "RED" }, joined = 1, max = 2 } }
|
|
Client._directEntries.battle_double = {
|
|
{ kind = "room", room = "r00000000000000aa", host = "00d00001", name = "LEAF", avatar = { name = "LEAF" } },
|
|
{ kind = "room", room = "r00000000000000bb", host = "00d00002", name = "PIN", locked = true },
|
|
{ kind = "player", id = "00d00003", name = "AUTO" },
|
|
}
|
|
local rows = Lobby.rows()
|
|
eq(#rows, 3, "one group and two hosted rooms (queued players are not joinable)")
|
|
eq(rows[2].kind, "room", "the Direct Corner host is listed")
|
|
eq(rows[3].disabled, true, "a PIN room is not joinable from the cable")
|
|
pick("r00000000000000aa")
|
|
local jr = Client.last("joinRoom")
|
|
eq(jr and jr[1], "r00000000000000aa", "room_join to the Direct Corner host")
|
|
eq(jr and jr[2], "player", "as a player")
|
|
Lobby.opts.cancel()
|
|
eq(Client.count("leaveRoom"), 1, "B while joining leaves the room")
|
|
check(Lobby.isOpen(), "and goes back to the list")
|
|
|
|
print("[test] 4. FireRed's cable counter falls back to the relay instead of timing out")
|
|
fresh()
|
|
session.version = "firered"
|
|
local NativesLink = require("src.core.game3.scripting.natives_link")
|
|
setVar(0x8004, Link.USING.TRADE_CENTER)
|
|
yielded = NativesLink.BY_NAME.TryTradeLinkup(ctx, adapters)
|
|
check(yielded, "TryTradeLinkup parks the script")
|
|
check(Lobby.isOpen(), "with the cable lobby, not a dead wait")
|
|
for _ = 1, LB.LINKUP_TICKS + 5 do ctx.nativePoll() end
|
|
eq(getVar(Link.VAR_RESULT), Link.LINKUP.ONGOING, "and it does not time out to CONNECTION_ERROR")
|
|
pick("leader")
|
|
eq(Client.last("openGroup") and Client.last("openGroup")[1], "trade", "the trade group")
|
|
Lobby.opts.cancel()
|
|
eq(getVar(Link.VAR_RESULT), Link.LINKUP.FAILED, "B is LINKUP_FAILED")
|
|
eq(Client.count("leaveGroup"), 1, "and leaves the group")
|
|
|
|
print("[test] 5. Emerald's cable counters pick the matching relay activity")
|
|
session.version = "emerald"
|
|
local cases = {
|
|
{ "TryBattleLinkup", Link.USING.SINGLE_BATTLE, "battle_single" },
|
|
{ "TryBattleLinkup", Link.USING.MULTI_BATTLE, "battle_multi" },
|
|
{ "TryBattleLinkup", Link.USING.BATTLE_TOWER, "battle_tower" },
|
|
{ "TryRecordMixLinkup", 0, "record_corner" },
|
|
}
|
|
local NativesLinkRse = require("src.core.game3.scripting.natives_link_rse")
|
|
for _, c in ipairs(cases) do
|
|
fresh()
|
|
setVar(0x8004, c[2])
|
|
NativesLinkRse.BY_NAME[c[1]](ctx, adapters)
|
|
pick("leader")
|
|
eq(Client.last("openGroup") and Client.last("openGroup")[1], c[3], c[1] .. " " .. c[2] .. " opens " .. c[3])
|
|
end
|
|
fresh()
|
|
NativesLinkRse.BY_NAME.TryBerryBlenderLinkup(ctx, adapters)
|
|
pick("leader")
|
|
eq(Client.last("openGroup") and Client.last("openGroup")[1], "berry_blender", "TryBerryBlenderLinkup opens berry_blender")
|
|
fresh()
|
|
setVar(0x8011, 2)
|
|
local NativesContest = require("src.core.game3.scripting.natives_contest")
|
|
NativesContest.BY_NAME.TryContestEModeLinkup(ctx, adapters)
|
|
pick("leader")
|
|
eq(Client.last("openGroup") and Client.last("openGroup")[1], "contest_cute", "a CUTE link contest opens contest_cute")
|
|
eq(LB.towerSpec().linkType, 0x2266, "a level 50 tower challenge links as LINKTYPE_BATTLE_TOWER_50")
|
|
|
|
print("[test] 6. a 2..4 group still asks the leader")
|
|
fresh()
|
|
setVar(0x8004, 0)
|
|
NativesLinkRse.BY_NAME.TryRecordMixLinkup(ctx, adapters)
|
|
pick("leader")
|
|
Client._group = { leader = "0000beef", min = 2, max = 4, members = { me() },
|
|
pending = { { id = "00e00002", name = "WALLY", avatar = { name = "WALLY" } } } }
|
|
poll()
|
|
eq(table.concat(rowIds(), ","), "yes,no", "ADD WALLY? YES / NO")
|
|
eq(Client.count("acceptGroup"), 0, "nothing accepted yet")
|
|
pick("yes")
|
|
eq(Client.last("acceptGroup") and Client.last("acceptGroup")[2], true, "YES accepts")
|
|
|
|
print("[test] 7. the activity names follow each game's sLinkGroupActivityNameTexts")
|
|
session.version = "emerald"
|
|
eq(Union.ACTIVITY_NAMES[15], "sLinkGroupActivityNameTexts[15]", "Emerald RECORD CORNER")
|
|
eq(Union.ACTIVITY_NAMES[23], "sLinkGroupActivityNameTexts[23]", "Emerald COOL CONTEST")
|
|
eq(Union.ACTIVITY_NAMES[28], "sLinkGroupActivityNameTexts[28]", "Emerald BATTLE TOWER LV. 50")
|
|
eq(Union.ACTIVITY_NAMES[13], nil, "Emerald SPIN TRADE is an empty string")
|
|
eq(Union.ACTIVITY_NAMES[3], "sLinkGroupActivityNameTexts[3]", "MULTI BATTLE")
|
|
session.version = "firered"
|
|
eq(Union.ACTIVITY_NAMES[13], "sLinkGroupActivityNameTexts[13]", "FireRed SPIN TRADE")
|
|
eq(Union.ACTIVITY_NAMES[15], nil, "FireRed has no RECORD CORNER name")
|
|
eq(Union.ACTIVITY_NAMES[10], "sLinkGroupActivityNameTexts[10]", "BERRY CRUSH")
|
|
|
|
print("[test] 8. the Direct Corner CHOOSE list shows open cable groups")
|
|
Client._directEntries.trade = {
|
|
{ kind = "group", leader = "00f00001", name = "BRENDAN", avatar = { name = "BRENDAN", version = "ruby" }, joined = 1, max = 2 },
|
|
{ kind = "group", leader = "00f00002", name = "FULL", avatar = { name = "FULL" }, joined = 2, max = 2 },
|
|
}
|
|
local drows = Union.directRows("trade")
|
|
eq(#drows, 1, "the open group is listed, the full one is not")
|
|
eq(drows[1] and drows[1].kind, "group", "as a group row")
|
|
eq(drows[1] and drows[1].leader, "00f00001", "with its leader id")
|
|
eq(drows[1] and drows[1].version, "ruby", "and the Ruby version for the trade check")
|
|
|
|
print("[test] 9. record mixing packs byte runs so a full Emerald packet fits the relay cap")
|
|
local RecordMix = require("src.core.game3.link.record_mix")
|
|
local Json = require("src.link.Json")
|
|
local Wire = require("src.link.Wire")
|
|
local raw = {}
|
|
for i = 1, 160 do raw[i] = (i * 37) % 256 end
|
|
local packet = { secretBases = { { trainerName = "MAY", _recordMixNativeBytes = raw } }, tvShows = { [0] = { kind = 1 } } }
|
|
local msg = Wire.sanitize(Json.decode(Json.encode({ type = RecordMix.MSG.PACKET, spot = 1, packet = RecordMix.toWire(packet) })))
|
|
local back = RecordMix.fromWire(msg.packet)
|
|
local same = true
|
|
for i = 1, 160 do if back.secretBases[1]._recordMixNativeBytes[i] ~= raw[i] then same = false end end
|
|
check(same, "160 native bytes survive the wire")
|
|
eq(back.tvShows[0].kind, 1, "zero-indexed rows still round trip")
|
|
check(#Json.encode(RecordMix.toWire(packet)) < 700, "and the byte run travels as one hex string")
|
|
|
|
print("[test] 10. a contest with a Ruby player is flagged and skips the wireless standby")
|
|
local CL = require("src.core.game3.link.contest_link")
|
|
local fakeLink = {
|
|
isOpen = function() return true end,
|
|
players = function() return { { seat = 0, version = "emerald" }, { seat = 1, version = "ruby" } } end,
|
|
send = function(self, m) self.sent = (self.sent or 0) + 1 end,
|
|
take = function() return nil end,
|
|
getSeat = function() return 0 end,
|
|
}
|
|
local flags = CL.flagsFor(fakeLink, true)
|
|
eq(flags, CL.FLAG.IS_LINK + CL.FLAG.IS_WIRELESS + CL.FLAG.HAS_RS_PLAYER, "IS_LINK | IS_WIRELESS | HAS_RS_PLAYER")
|
|
local s = CL.newSession(fakeLink, { flags = flags })
|
|
eq(s:standby(), true, "LinkContest_TryLinkStandby skips the standby with an RS player")
|
|
eq(fakeLink.sent, nil, "without sending a standby block")
|
|
CL.active = s
|
|
NativesContest.linkFlags = flags
|
|
eq(select(2, NativesContest.BY_NAME.IsContestWithRSPlayer()), 1, "IsContestWithRSPlayer answers TRUE")
|
|
eq(select(1, NativesContest.linkContestWaitForConnection(ctx, adapters)), false,
|
|
"LinkContestWaitForConnection does not park the script for an RS contest")
|
|
eq(CL.flagsFor({ players = function() return { { seat = 0, version = "emerald" } } end }, false), CL.FLAG.IS_LINK,
|
|
"an all-Emerald cable contest is just IS_LINK")
|
|
CL.reset()
|
|
NativesContest.linkFlags = 0
|
|
|
|
Link.reset()
|
|
Lobby.reset()
|
|
print(string.format("%s game3_link_rs_entry_test", failed == 0 and "PASS" or ("FAIL " .. failed)))
|
|
os.exit(failed == 0 and 0 or 1)
|