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()