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

221 lines
9.1 KiB
Lua

#!/usr/bin/env luajit
package.path = "./?.lua;./?/init.lua;" .. package.path
require("tests.game3_cache").stubSpeciesNames()
require("tests.fixture_data.game3_items").install()
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, 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 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 TRADE_CENTER = "FR_TRADE_CENTER"
local function mon(species, level, personality, extra)
local m = {
species = species, level = level, hp = 30, maxHp = 30,
moves = { 33 }, pp = { 35 }, maxPp = { 35 },
personality = personality or (species * 7), nickname = "",
friendship = 120, otName = "RED", otId = 0x1234,
}
for k, v in pairs(extra or {}) do m[k] = v end
return m
end
local store = { flags = {}, vars = {} }
local session = {
store = store, map = TRADE_CENTER, x = 5, y = 8,
name = "RED", gender = 0, trainerId = 0x1234,
party = { mon(1, 10), mon(4, 12) },
bag = { pockets = { items = {} } },
}
local game = { data = { maps = {} }, session = session }
package.loaded["src.core.game3.runtime"] = {
getSession = function() return session end,
isActive = function() return true end,
_game = game,
_mod = nil,
}
package.loaded["src.core.game3.player"] = { cellX = 5, cellY = 8, facing = "up" }
package.loaded["src.core.game3.map"] = { load = function() end, current = TRADE_CENTER }
local ctx = { specialVars = {}, stringVars = {} }
local adapters = { log = function() end, playSe = function() end }
package.loaded["src.core.game3.scripting.space"] = {
store = store, mapId = TRADE_CENTER, vm = { ctx = ctx, adapters = adapters },
}
local Natives = require("src.core.game3.scripting.natives")
local NativesLink = require("src.core.game3.scripting.natives_link")
local Std = require("src.core.game3.scripting.stdscripts")
local Link = require("src.core.game3.link")
local LT = require("src.core.game3.link.trade")
local Union = require("src.core.game3.link.union_room")
local Status = require("src.core.game3.link.status")
local Trade = require("src.core.game3.scripting.natives_trade")
local Flags = require("src.core.game3.scripting.flags")
print("[test] 1. the trade specials answer to their pret index")
local EXPECTED = {
TryTradeLinkup = 0x1D,
EnterTradeSeat = 0x21,
StartWiredCableClubTrade = 0x22,
ShowWirelessCommunicationScreen = 0x16E,
}
for name, id in pairs(EXPECTED) do
eq(NativesLink.SPECIAL[name], id, "NativesLink.SPECIAL." .. name)
check(Natives.ALLOW["special:" .. id] ~= nil, name .. " is bound in Natives.ALLOW")
eq(Std.SPECIAL_NAME_BY_ID[id], name,
string.format("special 0x%X answers to its pret name", id))
end
-- pokefirered/include/link.h:73
eq(LT.LINKCMD.READY_TO_TRADE, 0xAABB, "LINKCMD_READY_TO_TRADE")
eq(LT.LINKCMD.INIT_BLOCK, 0xBBBB, "LINKCMD_INIT_BLOCK")
eq(LT.LINKCMD.START_TRADE, 0xCCDD, "LINKCMD_START_TRADE")
eq(LT.LINKCMD.CONFIRM_FINISH_TRADE, 0xDCBA, "LINKCMD_CONFIRM_FINISH_TRADE")
eq(LT.LINKCMD.SET_MONS_TO_TRADE, 0xDDDD, "LINKCMD_SET_MONS_TO_TRADE")
-- pokefirered/src/cable_club.c:527
eq(LT.LINKUP.linkType, 0x1133, "TryTradeLinkup asks for LINKTYPE_TRADE_SETUP")
eq(LT.LINKUP.min, 2, "and for exactly two players")
eq(LT.MSG.CONFIRM, "game3_trade_confirm", "the barrier confirm is game3_trade_confirm")
eq(LT.MSG.COMMIT, "trade_commit", "the relay answers trade_commit")
eq(LT.MSG.ABORT, "trade_abort", "or trade_abort")
print("[test] 2. the wireless communication status screen counts")
Link.reset()
LT.reset()
Union.reset()
local G = Status.GROUPTYPE
local counts = Status.counts({
{ activity = Union.ACTIVITY.TRADE },
{ activity = Union.ACTIVITY.BATTLE_SINGLE },
{ activity = Union.ACTIVITY.BATTLE_MULTI },
{ activity = Union.ACTIVITY.NONE + Union.IN_UNION_ROOM },
})
eq(counts[G.TRADE], 2, "a trading group is two people")
eq(counts[G.BATTLE], 6, "a single and a multi battle are six")
eq(counts[G.UNION], 1, "one player idling in the UNION ROOM")
eq(counts[G.TOTAL], 9, "and the total is trade plus battle plus union")
-- pokefirered/src/wireless_communication_status_screen.c:505
local wonder = Status.counts({ { activity = Union.ACTIVITY.WONDER_CARD } })
eq(wonder[G.TOTAL], 0,
"the retail total leaves WONDER CARD players out, as the cart does")
local chat = Status.counts({
{ activity = Union.ACTIVITY.CHAT + Union.IN_UNION_ROOM, members = 3 },
})
eq(chat[G.UNION], 3, "a chat group counts its own members")
local rows = Status.rows()
eq(#rows, 4, "the screen has four rows")
eq(rows[1].label, "sHeaderTexts[1]", "the first is the trading count (sHeaderTexts[GROUPTYPE_TRADE + 1])")
eq(rows[4].total, true, "and the last is the total")
print("[test] 3. the adapter answer and the screen the monitor opens")
Flags.setVar(store, ctx, Link.VAR_RESULT, 9)
local yieldW, value = Natives.special(ctx, NativesLink.SPECIAL.IsWirelessAdapterConnected, adapters)
eq(yieldW, false, "IsWirelessAdapterConnected does not yield")
eq(value, 0, "offline it answers FALSE, and the monitor says so")
local LinkMenu = require("src.ui.game3.link_menu")
eq(LinkMenu.isOpen(), false, "the status screen starts closed")
local closed = false
LinkMenu.show({ onClose = function() closed = true end })
eq(LinkMenu.isOpen(), true, "ShowWirelessCommunicationScreen puts it up")
eq(#LinkMenu.rows, 4, "with the four group counts on it")
LinkMenu.update(1 / 60)
eq(LinkMenu.palIdx, 0, "the wave palette does not move on the first frame")
for _ = 1, 6 do LinkMenu.update(1 / 60) end
eq(LinkMenu.palIdx, 1, "and steps once every six frames, as CyclePalette does")
eq(LinkMenu.countText(3), " 3", "counts are right aligned to two places")
LinkMenu.close()
eq(closed, true, "closing it hands the script back")
print("[test] 4. grid, HP-bar and level tile tables")
local Menu = require("src.ui.game3.link_trade_menu")
local full = {}
for i = 0, 12 do full[i] = true end
-- pokefirered/src/trade.c:349
eq(Menu.newCursorPosition(0, 4, full), 1, "0 RIGHT -> 1")
eq(Menu.newCursorPosition(1, 4, full), 6, "1 RIGHT -> 6")
eq(Menu.newCursorPosition(4, 2, full), 0, "4 DOWN wraps to 0")
eq(Menu.newCursorPosition(11, 2, full), 12, "11 DOWN -> CANCEL")
local twoEach = { [0] = true, [1] = true, [6] = true, [7] = true, [12] = true }
eq(Menu.newCursorPosition(12, 1, twoEach), 7, "CANCEL UP with two partner mons -> 7")
eq(Menu.newCursorPosition(5, 4, twoEach), 6, "an absent source still reads its row")
-- pokefirered/src/battle_interface.c:2165
eq(Menu.hpBarLevel(30, 30), 4, "full HP")
eq(Menu.hpBarLevel(20, 30), 3, "over half")
eq(Menu.hpBarLevel(10, 30), 2, "over a fifth")
eq(Menu.hpBarLevel(1, 100), 1, "one HP is still red")
eq(Menu.hpBarLevel(0, 30), 0, "fainted")
-- pokefirered/src/trade.c:2397
local lv5 = Menu.levelGenderTiles(mon(1, 5))
eq(lv5.tens, nil, "a one-digit level has no tens tile")
eq(lv5.ones, 0x75, "Lv5 ones tile")
local lv100 = Menu.levelGenderTiles(mon(1, 100))
eq(lv100.tens, 0x6A, "Lv100 uses the 10 tile")
eq(lv100.ones, 0x70, "and a 0")
check(lv100.symbol == 0x83 or lv100.symbol == 0x84 or lv100.symbol == 0x85, "gender tile is one of 0x83-0x85")
local egg = Menu.levelGenderTiles(mon(1, 5, nil, { isEgg = true }))
check(egg.egg and egg.ones == nil, "an egg prints no level")
eq(egg.symbol, 0x80, "and the egg symbol tile")
check(egg.symbolFlip, "drawn flipped")
-- pokefirered/src/trade.c:2339
eq(#Menu.movesLines(mon(1, 5)), 4, "four move rows")
eq(Menu.movesLines(mon(1, 5, nil, { isEgg = true }))[1], "gText_4Qmark", "an egg shows ????")
eq(LT.resumeMenu(), false, "resumeMenu only answers a canceled trade")
print("[test] 5. the barrier states keep the trade screen up until the commit")
Menu.reset()
LT.reset()
Menu.show()
Menu.cb = "idle"
for _, st in ipairs({ "exchange", "commit_wait" }) do
LT.state = st
Menu.update(0)
check(Menu.isOpen(), "the screen stays up in " .. st)
end
LT.state = "committed"
Menu.update(0)
check(not Menu.isOpen(), "trade_commit hands off to the trade scene")
Menu.reset()
LT.reset()
print("[test] 6. a received EGG is not entered in the POKeDEX")
do
local s = { party = { mon(1, 10), mon(4, 12) }, dex = { seen = {}, owned = {}, caught = {} } }
-- pokefirered/src/trade_scene.c:1036
Trade.tradeMons(s, 0, mon(152, 5, nil, { isEgg = true }))
eq(s.party[1].species, 152, "the egg is in the party")
check(not s.dex.seen[152], "its species is not seen")
check(not s.dex.owned[152] and not s.dex.caught[152], "and not caught")
Trade.tradeMons(s, 1, mon(155, 5))
check(s.dex.seen[155] == true, "a received mon still is")
end
Link.reset()
LT.reset()
Union.reset()
if failed == 0 then
print("[pass] link trade tables")
os.exit(0)
end
print("[FAIL] link trade tables: " .. failed .. " failed")
os.exit(1)