Files
gen1recomp/tests/emerald_battle_rules_test.lua
T
bryanthaboi ff9cc6d172 YELLOWCARD
2026-09-28 06:45:52 -04:00

185 lines
9.1 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local GameVersion = require("src.core.GameVersion")
local Profile = require("src.core.game3.profile")
Profile.reset()
GameVersion.set("emerald")
local Dataset = require("src.core.game3.dataset")
local cache = Dataset.cache()
local function loadRel(rel)
local src = cache and cache.read and cache:read("data/generated/gba/" .. rel)
if type(src) ~= "string" then return nil end
local chunk = load(src, "@" .. rel, "t", {})
return chunk and chunk() or nil
end
local manifest = loadRel("pokemon/battle/manifest.lua")
if not (manifest and manifest.layout == "rse") then
print("emerald_battle_rules_test: skipped (no Emerald cache; set POKEPORT_IDENTITY)")
os.exit(0)
end
local Versions = require("src.import.gba.versions")
Versions.select("emerald")
local C = require("src.core.game3.constants").of("emerald")
local BattleProfile = require("src.core.game3.battle.profile")
local bp = BattleProfile.get({ version = "emerald" })
local BattleEngine = require("src.core.game3.battle.engine")
local frontierRun = { wild = false, player = {}, kinds = { frontier = true } }
local hillRun = { wild = false, player = {}, kinds = { trainerHill = true } }
local ordinaryTrainerRun = { wild = false, player = {}, kinds = {} }
eq(BattleEngine.canRun(frontierRun, {}), true, "Frontier trainer can select RUN to forfeit")
eq(BattleEngine.canRun(hillRun, {}), true, "Trainer Hill trainer can select RUN to forfeit")
local ordinaryRun, ordinaryWhy = BattleEngine.canRun(ordinaryTrainerRun, {})
eq(ordinaryRun, false, "ordinary trainer still cannot run")
check(ordinaryWhy ~= nil, "ordinary trainer refusal retains its message")
local Prize = require("src.core.game3.battle.prize")
local data = Prize.rseData()
eq(#data.pickupItems, 18, "sPickupItems has 18 rows (battle_script_commands.c:784)")
eq(#data.rarePickupItems, 11, "sRarePickupItems has 11 rows")
eq(#data.pickupProbabilities, 9, "sPickupProbabilities has 9 rows")
eq(Prize.pickupBanded(1, 0), C.items.byName.ITEM_POTION, "lv1 rand 0 -> Potion")
eq(Prize.pickupBanded(1, 30), C.items.byName.ITEM_ANTIDOTE, "lv1 rand 30 -> Antidote")
eq(Prize.pickupBanded(1, 99), C.items.byName.ITEM_HYPER_POTION, "lv1 rand 99 -> Hyper Potion")
eq(Prize.pickupBanded(1, 98), C.items.byName.ITEM_NUGGET, "lv1 rand 98 -> Nugget")
eq(Prize.pickupBanded(100, 97), C.items.byName.ITEM_MAX_ELIXIR, "lv100 rand 97 -> Max Elixir")
local pikeParty = { { species = C.species.byName.SPECIES_MEOWTH, level = 10, abilityId = Prize.ABILITY_PICKUP } }
local pickupRolls = 0
local suppressed = Prize.pickup(pikeParty, function() pickupRolls = pickupRolls + 1; return 0 end,
{ pickup = "level_bands", noPickup = true })
eq(#suppressed, 0, "Pike rule suppresses Pickup result")
eq(pickupRolls, 0, "Pike suppression consumes no random values")
eq(pikeParty[1].item, nil, "Pike suppression does not alter held item")
local Pyramid = require("src.core.game3.rse.frontier.pyramid")
local Rng = require("src.core.game3.rng")
local savedRandom = Rng.Random
Rng.Random = function() return 0 end
local pyramidSession = { version = "emerald", map = Pyramid.FLOOR_MAP, frontier = {} }
local pyramidItem = Pyramid.pickupItemId(pyramidSession)
local pyramidParty = { { species = C.species.byName.SPECIES_MEOWTH, level = 50,
abilityId = Prize.ABILITY_PICKUP } }
local pyramidPicked = Prize.pickup(pyramidParty, function() return 0 end,
{ pickup = "level_bands", pyramidSession = pyramidSession })
Rng.Random = savedRandom
eq(#pyramidPicked, 1, "Pyramid Pickup grants one floor-table item")
eq(pyramidParty[1].item, pyramidItem, "Pyramid Pickup uses GetBattlePyramidPickupItemId's table")
local Pike = require("src.core.game3.rse.frontier.pike")
local Game3 = require("src.core.Game3")
local Bag = require("src.core.game3.bag")
local ItemUse = require("src.core.game3.item_use")
local registeredBag = Bag.new()
local registeredItem = C.items.byName.ITEM_MACH_BIKE
Bag.add(registeredBag, registeredItem, 1)
local registeredSession = { version = "emerald", map = Pike.WILD_ROOM, bag = registeredBag,
registeredItem = registeredItem }
local oldUseField, registeredUses = ItemUse.useField, 0
ItemUse.useField = function() registeredUses = registeredUses + 1; return true, "bike" end
Game3._handleRegisteredItem({ session = registeredSession, input = {
wasPressed = function(_, key) return key == "select" end,
} })
ItemUse.useField = oldUseField
eq(registeredUses, 0, "SELECT registered-item use is blocked in the Battle Pike")
eq(registeredSession.registeredItem, registeredItem, "blocked Pike use keeps its registration")
registeredSession.map = Pyramid.FLOOR_MAP
Game3._handleRegisteredItem({ session = registeredSession, input = {
wasPressed = function(_, key) return key == "select" end,
} })
eq(registeredUses, 0, "SELECT registered-item use is blocked in the Battle Pyramid")
local PartyMenu = require("src.ui.game3.party_menu")
local function pressed(key)
return { wasPressed = function(_, button) return button == key end }
end
registeredSession.map = Pike.WILD_ROOM
PartyMenu.show({ { species = C.species.byName.SPECIES_MEOWTH, level = 10, moves = {} } }, {
session = registeredSession,
})
PartyMenu.handleInput(pressed("a"))
local pikeActions = table.concat(PartyMenu.ACTIONS, ",")
eq(pikeActions:find("SWITCH", 1, true), nil, "Battle Pike party menu omits SWITCH")
eq(pikeActions:find("ITEM", 1, true), nil, "Battle Pike party menu omits ITEM and MAIL")
PartyMenu.close()
registeredSession.map = "EM_ROUTE101"
PartyMenu.show({ { species = C.species.byName.SPECIES_MEOWTH, level = 10, moves = {} } }, {
session = registeredSession,
})
PartyMenu.handleInput(pressed("a"))
local fieldActions = table.concat(PartyMenu.ACTIONS, ",")
eq(fieldActions:find("SWITCH", 1, true) ~= nil, true, "ordinary field party menu keeps SWITCH")
eq(fieldActions:find("ITEM", 1, true) ~= nil, true, "ordinary field party menu keeps ITEM")
PartyMenu.close()
registeredSession.map = Pyramid.FLOOR_MAP
local BattleUi = require("src.core.game3.battle.ui")
local PyramidBag = require("src.ui.game3.rse.pyramid_bag")
local realPyramidBagShow, pyramidBagOpts = PyramidBag.show, nil
PyramidBag.show = function(opts) pyramidBagOpts = opts; return true end
BattleUi._session, BattleUi._st, BattleUi._mode = registeredSession, { link = false }, "command"
BattleUi.openBattleBag()
PyramidBag.show = realPyramidBagShow
eq(pyramidBagOpts and pyramidBagOpts.location, "battle", "Pyramid battle BAG opens its dedicated battle inventory")
eq(pyramidBagOpts and pyramidBagOpts.session, registeredSession, "Pyramid battle BAG uses the active challenge state")
BattleUi._session, BattleUi._st, BattleUi._mode = nil, nil, "none"
local Trainers = require("src.core.game3.scripting.trainers")
local calvin = C.trainers.byName.TRAINER_CALVIN_1
local row = Trainers.get(calvin)
check(row ~= nil, "Calvin row exists")
local pack = Trainers.pack()
local value = pack.money[row.class] or pack.moneyDefault
local last = row.party[#row.party].level
eq(Prize.calcRse(calvin, {}), 4 * last * value, "Calvin prize money from gTrainerMoneyTable")
print(string.format("[info] Calvin class %d value %d last lv %d -> %d", row.class, value, last, Prize.calcRse(calvin, {})))
local RomText = require("src.core.game3.rom_text")
for _, key in ipairs({ bp.firstBattle.cantRun, "STRINGID_PLAYERWHITEOUT", "STRINGID_PLAYERWHITEOUT2",
"STRINGID_PLAYERGOTMONEY", "STRINGID_WILDPKMNFLED", "STRINGID_PKMNGAINEDEXP", "gText_BattleWallyName" }) do
check(RomText.has(key), "Emerald text has " .. key)
end
check(RomText.plain(bp.firstBattle.cantRun):find("Don't leave me") ~= nil, "DONTLEAVEBIRCH reads Birch's line")
local Ai = require("src.core.game3.battle.ai")
local aiPack = Ai.loadPack({ force = true, required = true })
eq(#aiPack.table, 32, "gBattleAI_ScriptsTable has 32 scripts")
local fb = aiPack.scripts[aiPack.table[32]]
check(fb ~= nil and fb[1].op == "if_hp_equal", "AI_FirstBattle is script 31 (battle_ai_scripts.s:3233)")
local AiCmds = require("src.core.game3.battle.ai_cmds")
local seen = {}
for _, body in pairs(aiPack.scripts) do
for _, op in ipairs(body) do seen[op.op] = true end
end
for name in pairs(seen) do
check(AiCmds.CMD[name] ~= nil or name == "end" or name == "goto" or name == "call_if_always_hit"
or name:find("^nop") ~= nil, "AI op " .. name .. " has a handler")
end
local Anim = require("src.core.game3.battle.anim")
Anim._packLoaded, Anim._pack = false, nil
local reads = {}
local realRead = cache.read
cache.read = function(self, rel)
reads[#reads + 1] = rel
return realRead(self, rel)
end
local animPack = Anim.tableScript and select(2, pcall(function() return Anim.tableIndex("general", "POKEBLOCK_THROW") end))
cache.read = realRead
eq(animPack, 4, "general anim 4 is POKEBLOCK_THROW on Emerald")
local fromFr = false
for _, rel in ipairs(reads) do
if rel:find("^firered/") then fromFr = true end
end
check(not fromFr, "Emerald anim pack load never reads firered/ paths")
eq(Anim.tableIndex("general", "BAIT_THROW"), nil, "Emerald has no BAIT_THROW general anim")
local AnimTasks = require("src.core.game3.battle.anim_tasks")
AnimTasks.init()
check(AnimTasks.REGISTRY.IsBallBlockedByTrainer ~= nil, "IsBallBlockedByTrainer is registered")
GameVersion.set("firered")
T.finish()