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

174 lines
7.9 KiB
Lua

#!/usr/bin/env luajit
-- pokefirered/src/battle_main.c:2611, pokefirered/src/battle_script_commands.c:4526, pokefirered/src/battle_script_commands.c:9463, pokefirered/src/battle_script_commands.c:9497, pokefirered/src/battle_script_commands.c:9617, pokefirered/src/pokemon.c:3686, pokefirered/src/pokemon.c:3692, pokefirered/src/new_game.c:56, pokemon.c:3692
package.path = "./?.lua;./?/init.lua;" .. package.path
require("tests.game3_cache").mountOrSkip("game3_scenario_capture_test")
require("tests.fixture_data.game3_items").install()
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 finish()
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
local GameVersion = require("src.core.GameVersion")
GameVersion.set("firered")
local Dex = require("src.core.game3.dex")
local Pokemon = require("src.core.game3.pokemon")
local Bag = require("src.core.game3.bag")
local Catching = require("src.core.game3.battle.catching")
local BattleItems = require("src.core.game3.battle.items")
local Battle = require("src.core.game3.battle")
Pokemon.install(nil)
-- battle_script_commands.c:9463
local function rigRoll(lo)
return lo
end
local function newSession()
return {
name = "ASH",
id = 4242,
trainerId = 4242,
party = {
{ species = 1, name = "BULBASAUR", level = 8, hp = 25, maxHp = 25, moves = { 33 }, pp = { 35 } },
},
dex = Dex.new(),
bag = Bag.new(),
}
end
local session = newSession()
Bag.add(session.bag, 4, 3)
print("[test] 1. Wild Rattata appears: battle opens and the dex marks it seen")
local okStart = Battle.start({
wild = true,
headless = true,
autoFight = false,
playerParty = session.party,
foe = { species = 19, level = 3, hp = 6, maxHp = 15 },
session = session,
rng = rigRoll,
})
check(okStart == true, "the wild battle started")
check(Battle.isActive() == true, "the battle is active and waiting (not auto-run)")
local st = Battle.getState()
check(st ~= nil and st.wild == true, "battle state flags the encounter as wild")
-- battle_main.c:2611, battle_script_commands.c:4526
check(Dex.isSeen(session.dex, 19) == true, "Rattata registered as seen on encounter")
check(Dex.isCaught(session.dex, 19) == false, "Rattata is not caught yet")
check(Dex.countSeen(session.dex, "kanto") == 1, "kanto seen count is 1")
check(Dex.countCaught(session.dex, "kanto") == 0, "kanto caught count is 0")
check(st.enemy.mon.otId == 4242, "wild mon stamped with the trainer id (pokemon.c:3692 contract)")
check(type(session.secretId) == "number", "playerSecretId minted a session secret id")
check(st.enemy.mon.otSecretId == session.secretId, "the wild mon carries that secret id")
check(Catching.playerSecretId(session) == session.secretId, "playerSecretId is stable across calls")
print("[test] 2. Before throwing: ball identification and catch odds (battle_script_commands.c:9463)")
local foe = st.enemy
check(Catching.isBall(4) == true, "item 4 (POKE BALL) is a ball")
check(Catching.isBall(13) == false, "item 13 (POTION) is not a ball")
check(BattleItems.needsPartySelect(4) == false, "a ball throw needs no party target")
check(BattleItems.needsPartySelect(13) == true, "a potion asks which mon to heal")
check(Catching.ballMultiplier(2, foe, st, session) == 20, "Ultra Ball bonus is 2.0x (x10 scale)")
check(Catching.ballMultiplier(4, foe, st, session) == 10, "Poke Ball bonus is 1.0x")
check(Catching.catchOdds(1, foe, st, session) == 255, "Master Ball odds are always 255")
local curHp = foe.mon.hp
foe.mon.hp = foe.mon.maxHp
local fullOdds = Catching.catchOdds(4, foe, st, session)
foe.mon.hp = curHp
local curOdds = Catching.catchOdds(4, foe, st, session)
foe.mon.hp = 3
local lowOdds = Catching.catchOdds(4, foe, st, session)
foe.mon.hp = curHp
check(fullOdds < curOdds and curOdds < lowOdds,
string.format("odds improve as HP drops (%d < %d < %d)", fullOdds, curOdds, lowOdds))
check(fullOdds < 255 and curOdds < 255, "under 255, so the rigged roll is what decides this throw")
print("[test] 3. Throw #1: the Poke Ball is consumed and the rigged roll holds")
check(Bag.get(session.bag, 4) == 3, "three Poke Balls in the bag before the throw")
local result1, msgs1, endsTurn, endsBattle =
BattleItems.use(st, Battle._adapter, session.bag, session, 4)
check(result1 == "catch", "the throw caught the wild mon (got " .. tostring(result1) .. ")")
check(endsTurn == true and endsBattle == true, "a successful catch ends the turn and the battle")
check(Bag.get(session.bag, 4) == 2, "exactly one Poke Ball consumed")
local text1 = table.concat(msgs1, "\n")
check(text1:find("Gotcha", 1, true) ~= nil, "the Gotcha line was printed")
check(text1:find("added to the POK", 1, true) ~= nil,
"the first-time catch announces the Pokedex entry (got: " .. text1:gsub("\n", " / ") .. ")")
print("[test] 4. The capture lands: dex caught + party gains the mon (battle_script_commands.c:9617)")
check(#session.party == 2, "party grew from 1 to 2")
local caughtMon = session.party[2]
check(caughtMon.species == 19, "party slot 2 holds the Rattata")
check(caughtMon.pokeball == 4, "recorded with the Poke Ball it was caught in")
check(caughtMon.ot == "ASH", "OT name is the player")
check(caughtMon.otId == 4242, "OT id is the trainer id (pokemon.c:3692)")
check(caughtMon.otSecretId == session.secretId, "OT secret id matches the session's")
check(Dex.isCaught(session.dex, 19) == true, "Rattata marked caught in the dex")
check(Dex.countCaught(session.dex, "kanto") == 1, "kanto caught count is 1")
check(Dex.registerEncounter(session.dex, 19) == true,
"meeting it again reports it was already seen")
print("[test] 5. Second encounter: Pidgey, thrown at with the one Ultra Ball")
Battle.abort("caught")
check(Battle.isActive() == false, "the first battle is finished")
Bag.add(session.bag, 2, 1)
local okStart2 = Battle.start({
wild = true,
headless = true,
autoFight = false,
playerParty = session.party,
foe = { species = 16, level = 4, hp = 15, maxHp = 18 },
session = session,
rng = rigRoll,
})
check(okStart2 == true, "the second wild battle started")
local st2 = Battle.getState()
check(Dex.isSeen(session.dex, 16) == true, "Pidgey registered as seen")
check(Dex.countSeen(session.dex, "kanto") == 2, "kanto seen count is 2")
check(Bag.get(session.bag, 2) == 1, "one Ultra Ball in the bag before the throw")
local beforeCount = #session.party
local result2, msgs2 = BattleItems.use(st2, Battle._adapter, session.bag, session, 2)
check(result2 == "catch", "the Ultra Ball caught Pidgey (got " .. tostring(result2) .. ")")
check(Bag.get(session.bag, 2) == 0, "the Ultra Ball stack is emptied (slot removed)")
check(Bag.get(session.bag, 4) == 2, "the Poke Balls are untouched")
check(#session.party == beforeCount + 1, "the party gained another mon")
local pidgey = session.party[#session.party]
check(pidgey.species == 16, "the new party slot holds Pidgey")
check(pidgey.pokeball == 2, "recorded with the Ultra Ball")
check(Dex.countCaught(session.dex, "kanto") == 2, "kanto caught count is 2")
local text2 = table.concat(msgs2, "\n")
check(text2:find("added to the POK", 1, true) ~= nil,
"a first Pidgey still announces its dex entry (got: " .. text2:gsub("\n", " / ") .. ")")
print("[test] 6. Out of Ultra Balls: the throw is refused, nothing changes")
local result3, msgs3 = BattleItems.use(st2, Battle._adapter, session.bag, session, 2)
check(result3 == "error", "an empty pocket refuses the item (got " .. tostring(result3) .. ")")
check(table.concat(msgs3, "\n"):find("don't have", 1, true) ~= nil, "the player is told they have none")
check(#session.party == 3, "party unchanged: starter + two catches")
check(Bag.get(session.bag, 2) == 0, "still zero Ultra Balls")
check(Bag.get(session.bag, 4) == 2, "Poke Balls still untouched")
check(Dex.countCaught(session.dex, "kanto") == 2, "no phantom dex entry from the refused throw")
Battle.abort("caught")
check(Battle.isActive() == false, "battles closed down; nothing left open")
finish()