Files
gen1recomp/tests/game3_stitchsave_summary_test.lua
bryanthaboi dee935a4ea CLOSES #2430
2026-09-23 11:58:32 -04:00

223 lines
9.7 KiB
Lua

#!/usr/bin/env luajit
package.path = "./?.lua;./?/init.lua;" .. package.path
package.loaded["src.core.game3.audio"] = setmetatable({}, {
__index = function() return function() end end,
})
local failed, passed = 0, 0
local function check(cond, msg)
if cond then
passed = passed + 1
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 SummaryData = require("src.core.game3.summary_data")
local AILMENT_NONE, AILMENT_PSN, AILMENT_PKRS, AILMENT_FNT = 0, 1, 6, 7
local function memoLine(mon, playerState)
local lines = SummaryData.formatTrainerMemo(mon, playerState)
return lines and lines[2] or ""
end
print("[test] 1. CheckPartyPokerus reads the low nibble only")
do
-- pokefirered/src/pokemon.c:5630
eq(SummaryData.statusAilment({ pokerus = 0 }), AILMENT_NONE, "never infected is no ailment")
eq(SummaryData.statusAilment({ pokerus = 0x41 }), AILMENT_PKRS, "strain 4 with 1 day left is PKRS")
eq(SummaryData.statusAilment({ pokerus = 0x04 }), AILMENT_PKRS, "4 days left is PKRS")
eq(SummaryData.statusAilment({ pokerus = 0x0F }), AILMENT_PKRS, "15 days left is PKRS")
eq(SummaryData.statusAilment({ pokerus = 0x10 }), AILMENT_NONE, "strain 1, cured, is not PKRS")
eq(SummaryData.statusAilment({ pokerus = 0x40 }), AILMENT_NONE, "strain 4, cured, is not PKRS")
eq(SummaryData.statusAilment({ pokerus = 0xF0 }), AILMENT_NONE, "strain 15, cured, is not PKRS")
end
print("[test] 2. The cured badge is the summary dot, not the PKRS icon")
do
local Pokemon = require("src.core.game3.pokemon")
for _, byte in ipairs({ 0x10, 0x40, 0xF0 }) do
local mon = { pokerus = byte }
check(Pokemon.hasPokerus(mon) == false,
string.format("CheckPartyPokerus is false for 0x%02X", byte))
check(Pokemon.hasHadPokerus(mon) == true,
string.format("CheckPartyHasHadPokerus is true for 0x%02X", byte))
eq(SummaryData.statusAilment(mon), AILMENT_NONE,
string.format("and the status icon stays clear for 0x%02X", byte))
end
local sick = { pokerus = 0x41 }
check(Pokemon.hasPokerus(sick) == true, "an infected mon is still infected")
eq(SummaryData.statusAilment(sick), AILMENT_PKRS, "and still draws the PKRS icon")
end
print("[test] 3. A real ailment still outranks pokerus")
do
-- pokefirered/src/party_menu.c:1758 GetMonAilment
eq(SummaryData.statusAilment({ pokerus = 0x41, hp = 0 }), AILMENT_FNT, "fainted wins")
eq(SummaryData.statusAilment({ pokerus = 0x41, hp = 10, status = 0x08 }), AILMENT_PSN,
"poison wins")
eq(SummaryData.statusAilment({ pokerus = 0x10, hp = 10, status = 0x08 }), AILMENT_PSN,
"and a cured mon shows only the poison")
end
if not require("tests.game3_cache").mount() then
print("[skip] tests 4-9 read ROM map section names: " .. tostring(require("tests.game3_cache").reason))
print(string.format("[test] %d passed, %d failed", passed, failed))
os.exit(failed > 0 and 1 or 0)
end
print("[test] 4. The met location comes from the map section, not a PALLET TOWN default")
do
-- pokefirered/src/pokemon_summary_screen.c:2632 GetMapNameGeneric_(mapNameStr, metLocation)
local cases = {
{ 88, "PALLET TOWN" },
{ 89, "VIRIDIAN CITY" },
{ 94, "CELADON CITY" },
{ 101, "ROUTE 1" },
{ 127, "MT. MOON" },
{ 136, "SAFARI ZONE" },
{ 143, "ONE ISLAND" },
{ 196, "CELADON DEPT." },
}
for _, case in ipairs(cases) do
local line = memoLine({ metLocation = case[1], metLevel = 7, personality = 3 })
check(line:find(case[2], 1, true) ~= nil,
string.format("mapsec %d reads %s (%s)", case[1], case[2], line:gsub("\n", " ")))
end
end
print("[test] 5. Outside Kanto and the Sevii Islands the memo says a trade")
do
-- pokefirered/src/pokemon_summary_screen.c:5213 MapSecIsInKantoOrSevii,
-- pokefirered/src/pokemon_summary_screen.c:2737 gText_PokeSum_ATrade
local outside = { 0, 1, 87, 197, 253, 254 }
for _, sec in ipairs(outside) do
local line = memoLine({ metLocation = sec, metLevel = 7, personality = 3 })
check(line:find("a trade", 1, true) ~= nil,
string.format("mapsec %d is outside the region (%s)", sec, line:gsub("\n", " ")))
check(line:find("PALLET TOWN", 1, true) == nil,
string.format("and mapsec %d does not claim PALLET TOWN", sec))
end
local none = memoLine({ metLevel = 7, personality = 3 })
check(none:find("a trade", 1, true) ~= nil, "a mon with no met location says a trade")
check(none:find("PALLET TOWN", 1, true) == nil, "and does not claim PALLET TOWN")
end
print("[test] 6. A stamped name still overrides the lookup")
do
local line = memoLine({ metLocation = 101, metLocationName = "TREASURE BEACH",
metLevel = 7, personality = 3 })
check(line:find("TREASURE BEACH", 1, true) ~= nil, "the stamped name wins")
check(line:find("ROUTE 1", 1, true) == nil, "the mapsec name is not used as well")
end
print("[test] 7. CELADON CITY reads CELADON DEPT. inside the store")
do
-- pokefirered/src/region_map.c:3782 IsCeladonDeptStoreMapsec
local mon = { metLocation = 94, metLevel = 7, personality = 3 }
local inStore = memoLine(mon, { map = "FR_CELADON_CITY_DEPARTMENT_STORE_3F" })
check(inStore:find("CELADON DEPT.", 1, true) ~= nil,
"standing in the store the memo reads CELADON DEPT. (" .. inStore:gsub("\n", " ") .. ")")
local outside = memoLine(mon, { map = "FR_CELADON_CITY" })
check(outside:find("CELADON CITY", 1, true) ~= nil, "outside it reads CELADON CITY")
check(outside:find("DEPT", 1, true) == nil, "and not the store name")
local elsewhereMon = { metLocation = 101, metLevel = 7, personality = 3 }
local elsewhere = memoLine(elsewhereMon, { map = "FR_CELADON_CITY_DEPARTMENT_STORE_3F" })
check(elsewhere:find("ROUTE 1", 1, true) ~= nil,
"a mon met elsewhere keeps its own section in the store")
end
print("[test] 8. A script gift mon names the map it was handed over on")
do
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 session = Schema.newGame({ rngSeed = 0x3C3C })
-- pokefirered/src/pokemon.c:1816 CreateBoxMon MON_DATA_MET_LOCATION
session.regionMapSectionId = 98
-- pokefirered/src/script_pokemon_util.c:48 ScriptGiveMon
local code, mon = Party.giveMonToPlayer(session, 106, 25, "HITMONLEE")
eq(code, Party.MON_GIVEN_TO_PARTY, "the dojo prize went to the party")
eq(tonumber(mon.metLocation), 98, "stamped with the current map section")
eq(mon.metLocationName, nil, "and with no stamped place name")
local line = memoLine(mon, session)
check(line:find("SAFFRON CITY", 1, true) ~= nil,
"the memo names SAFFRON CITY (" .. line:gsub("\n", " ") .. ")")
check(line:find("PALLET TOWN", 1, true) == nil, "not PALLET TOWN")
end
print("[test] 9. A pre-round save keeps what it used to print for the player's own mons")
do
local Schema = require("src.core.game3.save_schema_firered")
-- pokefirered/include/constants/region_map_sections.h:211 KANTO_MAPSEC_START
local old = {
schemaVersion = 1, engine = "game3", version = "firered",
name = "RED", rivalName = "BLUE", gender = 0, money = 3000, trainerId = 31337,
party = {
{ species = 4, speciesId = 4, level = 9, hp = 20, maxHp = 20, metLevel = 5,
otName = "RED", otId = 31337, personality = 0x12345678 },
{ species = 63, speciesId = 63, level = 9, hp = 20, maxHp = 20, metLevel = 9,
otName = "REYLEY", otId = 1985, personality = 0x00009cae },
{ species = 25, speciesId = 25, level = 12, hp = 30, maxHp = 30, metLevel = 3,
metLocation = 101, otName = "RED", otId = 31337, otSecretId = 777,
personality = 0x22223333 },
},
dex = { seen = {}, owned = {} },
map = "FR_VIRIDIAN_CITY", x = 5, y = 6, facing = "down", flags = {}, vars = {},
}
local loaded = Schema.fromSaveTable(old)
eq(tonumber(loaded.party[1].metLocation), 88,
"an own-OT mon with no met location is stamped PALLET TOWN on load")
local own = memoLine(loaded.party[1], loaded)
check(own:find("PALLET TOWN", 1, true) ~= nil,
"and the memo reads what it read before the round (" .. own:gsub("\n", " ") .. ")")
check(own:find("a trade", 1, true) == nil, "the player's own starter is not a trade")
eq(loaded.party[2].metLocation, nil, "a foreign-OT mon is left alone")
local traded = memoLine(loaded.party[2], loaded)
check(traded:find("Met in a trade.", 1, true) ~= nil,
"and still reads as a trade (" .. traded:gsub("\n", " ") .. ")")
eq(tonumber(loaded.party[3].metLocation), 101, "a stamped met location is not overwritten")
local rolled = Schema.newGame({ rngSeed = 0x1111 })
local secretSave = {
schemaVersion = 1, engine = "game3", version = "firered",
name = "RED", trainerId = 31337, secretId = rolled.secretId,
party = {
{ species = 25, speciesId = 25, level = 5, metLevel = 5, metLocation = 101,
otName = "RED", otId = 31337, otSecretId = ((rolled.secretId or 0) + 1) % 0x10000,
personality = 1 },
{ species = 63, speciesId = 63, level = 5, metLevel = 5, metLocation = 101,
otName = "REYLEY", otId = 1985, otSecretId = 4242, personality = 2 },
},
dex = { seen = {}, owned = {} }, flags = {}, vars = {},
}
local fixed = Schema.fromSaveTable(secretSave)
eq(fixed.party[1].otSecretId, rolled.secretId,
"a stale per-boot secret id on an own mon is repaired")
local ownLine = memoLine(fixed.party[1], fixed)
check(ownLine:find("a trade", 1, true) == nil,
"so it stops reading as a trade (" .. ownLine:gsub("\n", " ") .. ")")
eq(fixed.party[2].otSecretId, 4242, "a foreign mon keeps its own secret half")
end
print(string.format("[test] %d passed, %d failed", passed, failed))
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")