mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 05:32:07 -04:00
494 lines
20 KiB
Lua
494 lines
20 KiB
Lua
#!/usr/bin/env luajit
|
|
-- pokefirered/src/load_save.c:160, src/script_pokemon_util.c:152, src/trainer_tower.c:733
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
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 Ctx = require("src.core.game3.scripting.ctx")
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
local Std = require("src.core.game3.scripting.stdscripts")
|
|
local Natives = require("src.core.game3.scripting.natives")
|
|
local Ops = require("src.core.game3.scripting.ops_a")
|
|
local Bag = require("src.core.game3.bag")
|
|
local Task = require("src.core.game3.task")
|
|
local Tower = require("src.core.game3.trainer_tower")
|
|
local TowerNatives = require("src.core.game3.scripting.natives_tower")
|
|
|
|
local store = Flags.newStore()
|
|
local keyText = setmetatable({}, {
|
|
__index = function(_, key) return require("src.core.game3.scripting.text_ir").fromAscii(key) end,
|
|
})
|
|
package.loaded["src.core.game3.scripting.space"] = {
|
|
store = store,
|
|
ensureBundle = function() return { text = keyText } end,
|
|
}
|
|
|
|
local function newCtx()
|
|
local ctx = Ctx.new({})
|
|
ctx.mode = "bytecode"
|
|
ctx.status = "running"
|
|
return ctx
|
|
end
|
|
|
|
local function useSession(tbl)
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return tbl end,
|
|
isActive = function() return true end,
|
|
}
|
|
return tbl
|
|
end
|
|
|
|
local function mon(species, level, opts)
|
|
opts = opts or {}
|
|
return {
|
|
species = species,
|
|
speciesId = species,
|
|
level = level,
|
|
hp = opts.hp or (level * 2),
|
|
maxHp = level * 2,
|
|
moves = { 33 },
|
|
pp = { 35 },
|
|
maxPp = { 35 },
|
|
isEgg = opts.isEgg or nil,
|
|
heldItem = opts.heldItem,
|
|
nickname = opts.nickname,
|
|
}
|
|
end
|
|
|
|
local function newSession(map)
|
|
return useSession({
|
|
name = "RED",
|
|
gender = 0,
|
|
money = 3000,
|
|
trainerId = 4242,
|
|
party = {},
|
|
bag = Bag.new(),
|
|
map = map or "FR_TRAINER_TOWER_LOBBY",
|
|
x = 9,
|
|
y = 7,
|
|
modData = {},
|
|
})
|
|
end
|
|
|
|
local function getVar(ctx, id) return Flags.getVar(store, ctx, id) end
|
|
local function setVar(ctx, id, v) Flags.setVar(store, ctx, id, v) end
|
|
|
|
local function special(ctx, id, adapters)
|
|
return Ops.dispatch({ ctx = ctx, store = store, adapters = adapters or { log = function() end },
|
|
setPc = function() end }, { op = "special", id = id })
|
|
end
|
|
|
|
-- pokefirered/asm/macros/trainer_tower.inc:2
|
|
local function towerFunc(ctx, index, adapters, temp1)
|
|
setVar(ctx, 0x8004, index)
|
|
if temp1 ~= nil then setVar(ctx, 0x4001, temp1) end
|
|
special(ctx, Std.SPECIAL.CallTrainerTowerFunc, adapters)
|
|
return getVar(ctx, 0x800D)
|
|
end
|
|
|
|
-- pokefirered/src/trainer_tower_sets.c:8956
|
|
local function towerMon(species, moveId)
|
|
return {
|
|
species = species,
|
|
heldItem = 0,
|
|
moves = { moveId, 0, 0, 0 },
|
|
level = 1,
|
|
ppBonuses = 0,
|
|
hpEV = 0, attackEV = 0, defenseEV = 0, speedEV = 0, spAttackEV = 0, spDefenseEV = 0,
|
|
otId = 0,
|
|
hpIV = 8, attackIV = 8, defenseIV = 8, speedIV = 8, spAttackIV = 8, spDefenseIV = 8,
|
|
abilityNum = 0,
|
|
personality = 7,
|
|
nickname = "",
|
|
friendship = 0,
|
|
}
|
|
end
|
|
|
|
local function towerTrainer(name, base)
|
|
local mons = {}
|
|
for i = 1, 6 do mons[i] = towerMon(base + i, 30 + i) end
|
|
return { name = name, facilityClass = 44, textColor = 0, mons = mons }
|
|
end
|
|
|
|
-- pokefirered/src/trainer_tower.c:529 gTrainerTowerFloors
|
|
local function installFloorPack()
|
|
local floors = {}
|
|
for mode = 0, Tower.NUM_CHALLENGE_TYPES - 1 do
|
|
local rows = {}
|
|
for i = 1, Tower.MAX_FLOORS do
|
|
rows[i] = {
|
|
id = i,
|
|
floorIdx = Tower.MAX_FLOORS,
|
|
challengeType = (mode == Tower.CHALLENGE_TYPE.MIXED) and Tower.CHALLENGE_TYPE.SINGLE or mode,
|
|
prize = 0,
|
|
trainers = {
|
|
towerTrainer("ALPHA", 100),
|
|
towerTrainer("BETA", 200),
|
|
towerTrainer("GAMMA", 300),
|
|
},
|
|
}
|
|
end
|
|
floors[mode] = rows
|
|
end
|
|
Tower.setPack({ header = { numFloors = Tower.MAX_FLOORS, id = 1 }, floors = floors })
|
|
end
|
|
|
|
print("[test] 1. the five party and special-battle specials are bound with pret's ids")
|
|
-- pokefirered/data/specials.inc:50
|
|
eq(Std.SPECIAL.SavePlayerParty, 0x27, "SavePlayerParty")
|
|
eq(Std.SPECIAL.LoadPlayerParty, 0x28, "LoadPlayerParty")
|
|
eq(Std.SPECIAL.ChooseHalfPartyForBattle, 0x29, "ChooseHalfPartyForBattle")
|
|
-- pokefirered/data/specials.inc:247
|
|
eq(Std.SPECIAL.StartSpecialBattle, 0xEC, "StartSpecialBattle")
|
|
-- pokefirered/data/specials.inc:259
|
|
eq(Std.SPECIAL.ReducePlayerPartyToThree, 0xF8, "ReducePlayerPartyToThree")
|
|
for _, name in ipairs({ "SavePlayerParty", "LoadPlayerParty", "ChooseHalfPartyForBattle",
|
|
"StartSpecialBattle", "ReducePlayerPartyToThree" }) do
|
|
check(Natives.ALLOW["special:" .. Std.SPECIAL[name]] ~= nil, name .. " reaches a handler")
|
|
end
|
|
|
|
print("[test] 2. SavePlayerParty stashes all six slots and LoadPlayerParty gives them back")
|
|
local session = newSession("FR_SEVEN_ISLAND_HOUSE_ROOM1")
|
|
local ctx = newCtx()
|
|
for i = 1, 6 do session.party[i] = mon(i, 10 + i, { nickname = "SLOT" .. i }) end
|
|
special(ctx, Std.SPECIAL.SavePlayerParty)
|
|
eq(#session.party, 6, "the live party is untouched by the stash")
|
|
session.party[1].hp = 0
|
|
session.party[6] = nil
|
|
session.party[5] = nil
|
|
eq(#session.party, 4, "the party was cut down after the stash")
|
|
special(ctx, Std.SPECIAL.LoadPlayerParty)
|
|
eq(#session.party, 6, "LoadPlayerParty restored six slots")
|
|
eq(session.party[6].nickname, "SLOT6", "slot 6 came back")
|
|
eq(session.party[1].hp, 11 * 2, "the stashed copy was not aliased to the live mon")
|
|
session.party[3].hp = 1
|
|
eq((Tower.savedPlayerParty(session) or {})[3].hp, 13 * 2,
|
|
"editing the live party does not edit the stash")
|
|
|
|
print("[test] 3. the stash survives a save and a reload mid-flow")
|
|
local Schema = require("src.core.game3.save_schema_firered")
|
|
local saved = Schema.toSaveTable(session)
|
|
local reloaded = useSession(Schema.fromSaveTable(saved))
|
|
reloaded.party = { mon(25, 5) }
|
|
ctx = newCtx()
|
|
special(ctx, Std.SPECIAL.LoadPlayerParty)
|
|
eq(#reloaded.party, 6, "the reloaded save still restores the six stashed mons")
|
|
eq(reloaded.party[4].nickname, "SLOT4", "the stashed nicknames survived the save")
|
|
|
|
print("[test] 4. ChooseHalfPartyForBattle offers only battle-eligible mons")
|
|
session = newSession("FR_SEVEN_ISLAND_HOUSE_ROOM1")
|
|
ctx = newCtx()
|
|
session.party = {
|
|
mon(1, 20),
|
|
mon(4, 20, { isEgg = true }),
|
|
mon(7, 20, { hp = 0 }),
|
|
mon(25, 20),
|
|
mon(133, 20),
|
|
}
|
|
local opts = TowerNatives.chooseOptions()
|
|
-- pokefirered/include/constants/party_menu.h:59
|
|
eq(opts.menuType, 4, "PARTY_MENU_TYPE_CHOOSE_MULTIPLE_MONS")
|
|
eq(opts.count, 3, "the picker asks for three slots")
|
|
eq(opts.mode, "choose_multi", "the picker runs in multi select mode")
|
|
-- pokefirered/include/constants/party_menu.h:133
|
|
eq(opts.chooseMonsBattleType, 0, "CHOOSE_MONS_FOR_CABLE_CLUB_BATTLE")
|
|
eq(opts.eligible, nil, "the eligibility rule is the menu's own, not a list the tower publishes")
|
|
local PartyMenu = require("src.ui.game3.party_menu")
|
|
opts.session = session
|
|
opts.onClose = function() end
|
|
PartyMenu.show(session.party, nil, opts)
|
|
-- pokefirered/src/party_menu.c:5674 GetBattleEntryEligibility
|
|
local function firstAction(slot) return (PartyMenu.multiActions(slot) or {})[1] end
|
|
eq(firstAction(1), "ENTER", "slot 1 can enter")
|
|
eq(firstAction(2), "SUMMARY", "the egg cannot enter")
|
|
eq(firstAction(3), "SUMMARY", "the fainted mon cannot enter")
|
|
eq(firstAction(4), "ENTER", "slot 4 can enter")
|
|
eq(firstAction(5), "ENTER", "slot 5 can enter")
|
|
PartyMenu.close()
|
|
|
|
print("[test] 5. a multi select pick fills gSelectedOrderFromParty in pick order")
|
|
local passedOpts = nil
|
|
local adaptersPick = {
|
|
log = function() end,
|
|
chooseParty = function(o, done)
|
|
passedOpts = o
|
|
done({ 5, 1 })
|
|
end,
|
|
}
|
|
special(ctx, Std.SPECIAL.ChooseHalfPartyForBattle, adaptersPick)
|
|
check(passedOpts ~= nil and passedOpts.count == 3, "the seam was called with the count option")
|
|
local order = Tower.selectedOrder(session)
|
|
eq(order[1], 5, "first pick is slot 5")
|
|
eq(order[2], 1, "second pick is slot 1")
|
|
eq(order[3], 0, "the third slot stays empty")
|
|
eq(getVar(ctx, 0x800D), 1, "VAR_RESULT = TRUE when the player confirmed")
|
|
|
|
print("[test] 6. ReducePlayerPartyToThree keeps the picked mons in the picked order")
|
|
special(ctx, Std.SPECIAL.SavePlayerParty)
|
|
special(ctx, Std.SPECIAL.ReducePlayerPartyToThree)
|
|
eq(#session.party, 2, "two picks leave a two mon party")
|
|
eq(session.party[1].species, 133, "the first pick leads")
|
|
eq(session.party[2].species, 1, "the second pick follows")
|
|
special(ctx, Std.SPECIAL.LoadPlayerParty)
|
|
eq(#session.party, 5, "LoadPlayerParty gave the whole party back")
|
|
eq(session.party[2].isEgg, true, "the egg came back with it")
|
|
|
|
print("[test] 7. cancelling the picker sets VAR_RESULT = FALSE and picks nothing")
|
|
ctx = newCtx()
|
|
special(ctx, Std.SPECIAL.ChooseHalfPartyForBattle,
|
|
{ log = function() end, chooseParty = function(_, done) done(nil) end })
|
|
eq(getVar(ctx, 0x800D), 0, "VAR_RESULT = FALSE on cancel")
|
|
eq(Tower.selectedOrder(session)[1], 0, "the order was cleared")
|
|
special(ctx, Std.SPECIAL.ReducePlayerPartyToThree)
|
|
eq(#session.party, 0, "pret empties the party when no slot was chosen")
|
|
special(ctx, Std.SPECIAL.LoadPlayerParty)
|
|
eq(#session.party, 5, "and the stash puts it back")
|
|
|
|
print("[test] 8. a single select host still yields a legal one mon order")
|
|
ctx = newCtx()
|
|
special(ctx, Std.SPECIAL.ChooseHalfPartyForBattle,
|
|
{ log = function() end, chooseParty = function(_, done) done(3) end })
|
|
eq(Tower.selectedOrder(session)[1], 4, "slot 4 (0 based 3) became the only pick")
|
|
eq(getVar(ctx, 0x800D), 1, "VAR_RESULT = TRUE for a one mon pick")
|
|
|
|
print("[test] 9. an ineligible pick is refused the way the menu refuses it")
|
|
ctx = newCtx()
|
|
special(ctx, Std.SPECIAL.ChooseHalfPartyForBattle,
|
|
{ log = function() end, chooseParty = function(_, done) done({ 2, 3 }) end })
|
|
eq(Tower.selectedOrder(session)[1], 0, "the egg and the fainted mon were dropped")
|
|
eq(getVar(ctx, 0x800D), 0, "VAR_RESULT = FALSE when nothing eligible was picked")
|
|
|
|
print("[test] 10. the picker blocks the script until the host closes it")
|
|
ctx = newCtx()
|
|
local pending = nil
|
|
local yielded = special(ctx, Std.SPECIAL.ChooseHalfPartyForBattle,
|
|
{ log = function() end, chooseParty = function(_, done) pending = done end })
|
|
check(yielded, "the special yielded while the party menu is open")
|
|
eq(ctx.status, "waiting", "the script is waiting")
|
|
eq(getVar(ctx, 0x800D), 0, "VAR_RESULT is not written before the pick")
|
|
pending({ 1 })
|
|
check(ctx.nativePoll(), "the poll finished once the host closed the menu")
|
|
eq(getVar(ctx, 0x800D), 1, "VAR_RESULT was written on close")
|
|
|
|
print("[test] 11. DoTrainerTowerBattle builds the floor party pret builds")
|
|
installFloorPack()
|
|
session = newSession("FR_TRAINER_TOWER_1F")
|
|
ctx = newCtx()
|
|
Task.clear()
|
|
session.party = { mon(1, 24), mon(4, 31), mon(7, 18) }
|
|
setVar(ctx, 0x8005, Tower.CHALLENGE_TYPE.SINGLE)
|
|
towerFunc(ctx, Tower.FUNC.START_CHALLENGE, { log = function() end })
|
|
local seenFoe, seenOpts = nil, nil
|
|
local battleAdapters = {
|
|
log = function() end,
|
|
startTrainerBattle = function(foe, done, o)
|
|
seenFoe, seenOpts = foe, o
|
|
done("win")
|
|
end,
|
|
}
|
|
eq(towerFunc(ctx, Tower.FUNC.DO_BATTLE, battleAdapters, 0), 1,
|
|
"a won tower battle reports B_OUTCOME_WON")
|
|
check(seenFoe ~= nil, "the battle reached the host seam")
|
|
eq(#seenFoe.party, 2, "a singles floor sends two mons")
|
|
-- pokefirered/src/trainer_tower.c:401 sSingleBattleChallengeMonIdxs[0] = {0, 2}
|
|
eq(seenFoe.party[1].species, 101, "floor 1 mon index 0")
|
|
eq(seenFoe.party[2].species, 103, "floor 1 mon index 2")
|
|
-- pokefirered/src/trainer_tower.c:1026 GetPartyMaxLevel
|
|
eq(seenFoe.party[1].level, 31, "the floor mons match the player's top level")
|
|
eq(seenFoe.party[2].level, 31, "both of them")
|
|
eq(seenOpts.double, false, "a singles floor is not a double battle")
|
|
eq(seenOpts.trainerId, 0, "gTrainerBattleOpponent_A = 0")
|
|
-- pokefirered/src/trainer_tower.c:717 CB2_EndTrainerTowerBattle
|
|
eq(seenOpts.noWhiteout, true, "a tower loss goes through the script, not the white out")
|
|
eq(seenOpts.trainerTower, true, "the battle is flagged BATTLE_TYPE_TRAINER_TOWER")
|
|
|
|
print("[test] 12. the knockout and doubles floors pick the mons pret picks")
|
|
session = newSession("FR_TRAINER_TOWER_3F")
|
|
ctx = newCtx()
|
|
session.party = { mon(1, 40) }
|
|
setVar(ctx, 0x8005, Tower.CHALLENGE_TYPE.KNOCKOUT)
|
|
towerFunc(ctx, Tower.FUNC.START_CHALLENGE, { log = function() end })
|
|
towerFunc(ctx, Tower.FUNC.CLEARED_FLOOR, { log = function() end })
|
|
towerFunc(ctx, Tower.FUNC.CLEARED_FLOOR, { log = function() end })
|
|
towerFunc(ctx, Tower.FUNC.DO_BATTLE, battleAdapters, 1)
|
|
eq(#seenFoe.party, 1, "a knockout battle is one mon")
|
|
-- pokefirered/src/trainer_tower.c:427 sKnockoutChallengeMonIdxs[2] = {2, 3, 1}
|
|
eq(seenFoe.party[1].species, 204, "floor 3, trainer 2, mon index 3")
|
|
session = newSession("FR_TRAINER_TOWER_2F")
|
|
ctx = newCtx()
|
|
session.party = { mon(1, 40), mon(4, 12) }
|
|
setVar(ctx, 0x8005, Tower.CHALLENGE_TYPE.DOUBLE)
|
|
towerFunc(ctx, Tower.FUNC.START_CHALLENGE, { log = function() end })
|
|
towerFunc(ctx, Tower.FUNC.CLEARED_FLOOR, { log = function() end })
|
|
towerFunc(ctx, Tower.FUNC.DO_BATTLE, battleAdapters, 0)
|
|
eq(#seenFoe.party, 2, "a doubles floor sends one mon from each trainer")
|
|
-- pokefirered/src/trainer_tower.c:414 sDoubleBattleChallengeMonIdxs[1] = {1, 3}
|
|
eq(seenFoe.party[1].species, 102, "trainer 1 mon index 1")
|
|
eq(seenFoe.party[2].species, 204, "trainer 2 mon index 3")
|
|
eq(seenOpts.double, true, "a doubles floor is a double battle")
|
|
|
|
print("[test] 13. a lost tower battle routes B_OUTCOME_LOST back to the script")
|
|
session = newSession("FR_TRAINER_TOWER_1F")
|
|
ctx = newCtx()
|
|
session.party = { mon(1, 20) }
|
|
setVar(ctx, 0x8005, Tower.CHALLENGE_TYPE.SINGLE)
|
|
towerFunc(ctx, Tower.FUNC.START_CHALLENGE, { log = function() end })
|
|
eq(towerFunc(ctx, Tower.FUNC.DO_BATTLE, {
|
|
log = function() end,
|
|
startTrainerBattle = function(_, done) done("lose") end,
|
|
}, 0), 2, "a lost tower battle reports B_OUTCOME_LOST")
|
|
eq(Tower.record(session).floorsCleared, 0, "a loss clears no floor")
|
|
|
|
print("[test] 14. the battle blocks the script until the battle ends")
|
|
ctx = newCtx()
|
|
local finishBattle = nil
|
|
yielded = towerFunc(ctx, Tower.FUNC.DO_BATTLE, {
|
|
log = function() end,
|
|
startTrainerBattle = function(_, done) finishBattle = done end,
|
|
}, 0)
|
|
eq(ctx.status, "waiting", "ttower_dobattle waits on the battle")
|
|
finishBattle("win")
|
|
check(ctx.nativePoll(), "the poll finished with the battle")
|
|
eq(getVar(ctx, 0x800D), 1, "VAR_RESULT = B_OUTCOME_WON after the battle")
|
|
|
|
print("[test] 15. with no floor data in the cache the tower loses instead of cheating")
|
|
Tower.setPack(nil)
|
|
TowerNatives._logged = {}
|
|
session = newSession("FR_TRAINER_TOWER_1F")
|
|
ctx = newCtx()
|
|
session.party = { mon(1, 20) }
|
|
setVar(ctx, 0x8005, Tower.CHALLENGE_TYPE.SINGLE)
|
|
towerFunc(ctx, Tower.FUNC.START_CHALLENGE, { log = function() end })
|
|
local reached = false
|
|
eq(towerFunc(ctx, Tower.FUNC.DO_BATTLE, {
|
|
log = function() end,
|
|
startTrainerBattle = function() reached = true end,
|
|
}, 0), 2, "no floor trainers reports B_OUTCOME_LOST")
|
|
check(not reached, "and starts no battle at all")
|
|
check(TowerNatives._logged["DoTrainerTowerBattle has no floor trainers in this cache"] == true,
|
|
"the missing gTrainerTowerFloors table is logged once")
|
|
|
|
print("[test] 16. StartSpecialBattle fights the e-reader trainer when the save holds one")
|
|
session = newSession("FR_SEVEN_ISLAND_HOUSE_ROOM2")
|
|
ctx = newCtx()
|
|
session.party = { mon(1, 30) }
|
|
TowerNatives._logged = {}
|
|
-- pokefirered/data/maps/SevenIsland_House_Room1/scripts.inc:9
|
|
special(ctx, Std.SPECIAL.ValidateEReaderTrainer, { log = function() end })
|
|
eq(getVar(ctx, 0x800D), 1, "a clean save has no e-reader trainer, so the door stays shut")
|
|
session.ereaderTrainer = { name = "EMPTY" }
|
|
special(ctx, Std.SPECIAL.ValidateEReaderTrainer, { log = function() end })
|
|
eq(getVar(ctx, 0x800D), 1, "a record with no party is an all-zero record")
|
|
eq(session.ereaderTrainer, nil, "and ClearEReaderTrainer wiped it")
|
|
setVar(ctx, 0x8004, 2)
|
|
special(ctx, Std.SPECIAL.StartSpecialBattle, { log = function() end })
|
|
eq(getVar(ctx, 0x800D), 2,
|
|
"an empty e-reader slot reports B_OUTCOME_LOST instead of hanging on waitstate")
|
|
check(TowerNatives._logged["StartSpecialBattle 2 has no opponent data in this save"] == true,
|
|
"the empty e-reader slot is logged once")
|
|
session.ereaderTrainer = {
|
|
name = "READER",
|
|
facilityClass = 44,
|
|
party = { towerMon(150, 94), towerMon(151, 60), towerMon(144, 58) },
|
|
}
|
|
seenFoe, seenOpts = nil, nil
|
|
ctx = newCtx()
|
|
setVar(ctx, 0x8004, 2)
|
|
special(ctx, Std.SPECIAL.StartSpecialBattle, battleAdapters)
|
|
check(seenFoe ~= nil, "the e-reader battle reached the host seam")
|
|
eq(#seenFoe.party, 3, "an e-reader trainer brings three mons")
|
|
eq(seenFoe.party[1].species, 150, "the first e-reader mon")
|
|
eq(seenFoe.party[1].level, 1, "e-reader mons keep their own level")
|
|
eq(seenOpts.eReader, true, "BATTLE_TYPE_EREADER_TRAINER")
|
|
eq(getVar(ctx, 0x800D), 1, "the outcome reached VAR_RESULT")
|
|
ctx = newCtx()
|
|
special(ctx, Std.SPECIAL.ValidateEReaderTrainer, { log = function() end })
|
|
eq(getVar(ctx, 0x800D), 0, "a valid card record opens the Seven Island door")
|
|
|
|
print("[test] 17. the secret base case copies the held items into the stash first")
|
|
session = newSession("FR_PALLET_TOWN")
|
|
ctx = newCtx()
|
|
session.party = { mon(1, 10, { heldItem = 0 }), mon(4, 10, { heldItem = 0 }) }
|
|
special(ctx, Std.SPECIAL.SavePlayerParty)
|
|
session.party[1].heldItem = 172
|
|
session.party[2].heldItem = 173
|
|
setVar(ctx, 0x8004, 1)
|
|
special(ctx, Std.SPECIAL.StartSpecialBattle, { log = function() end })
|
|
-- pokefirered/src/battle_tower.c:917
|
|
eq((Tower.savedPlayerParty(session) or {})[1].heldItem, 172, "slot 1 held item reached the stash")
|
|
eq((Tower.savedPlayerParty(session) or {})[2].heldItem, 173, "slot 2 held item reached the stash")
|
|
eq(getVar(ctx, 0x800D), 2, "and with no secret base trainer the battle reports B_OUTCOME_LOST")
|
|
|
|
print("[test] 18. the tower flag survives the adapters and the bridge and reaches the engine")
|
|
local captured = nil
|
|
package.loaded["src.core.game3.battle_bridge"] = nil
|
|
local realBridge = require("src.core.game3.battle_bridge")
|
|
local bridgeStart = realBridge.start
|
|
realBridge.start = function(_, _, _, o) captured = o end
|
|
local Adapters = require("src.core.game3.scripting.adapters")
|
|
local host = Adapters.host({}, { session = session }, {})
|
|
-- pokefirered/src/trainer_tower.c:735 BATTLE_TYPE_TRAINER_TOWER
|
|
host.startTrainerBattle({ party = {}, trainerId = 0, trainerName = "ALBERTO", trainerPicId = 44 },
|
|
function() end, { trainerId = 0, trainerTower = true, noWhiteout = true })
|
|
realBridge.start = bridgeStart
|
|
check(captured ~= nil, "the tower battle reached BattleBridge.start")
|
|
eq(captured and captured.trainerTower, true, "adapters.startTrainerBattle forwards trainerTower")
|
|
eq(captured and captured.trainerName, "ALBERTO", "and the tower opponent's name")
|
|
eq(captured and captured.trainerPicId, 44, "and its facility-class pic")
|
|
|
|
if not require("tests.game3_cache").mount() then
|
|
print("[skip] the battle intro names gTrainers[0] from the ROM trainer pack: "
|
|
.. tostring(require("tests.game3_cache").reason))
|
|
Task.clear()
|
|
Tower.resetPack()
|
|
os.exit(failed > 0 and 1 or 0)
|
|
end
|
|
local Battle = require("src.core.game3.battle")
|
|
Tower.resetPack()
|
|
-- include/constants/trainers.h:382 FACILITY_CLASS_SAILOR
|
|
local towerMons = { { species = 129, level = 30, hp = 40, maxHp = 40, moves = { 33 }, pp = { 35 }, trainerClass = 91 } }
|
|
Battle.start({
|
|
headless = true,
|
|
wild = false,
|
|
trainerId = 0,
|
|
trainerTower = true,
|
|
trainerName = "ALBERTO",
|
|
trainerPicId = 44,
|
|
playerParty = { { species = 1, level = 30, hp = 50, maxHp = 50, moves = { 33 }, pp = { 35 } } },
|
|
foe = towerMons[1],
|
|
foeParty = towerMons,
|
|
})
|
|
local bst = Battle.getState()
|
|
check(bst ~= nil and bst.trainerTower == true, "battle/init.lua kept BATTLE_TYPE_TRAINER_TOWER")
|
|
eq(bst and bst.trainerName, "ALBERTO", "the battle shows the tower trainer, not gTrainers[0]")
|
|
eq(bst and bst.trainerClassName, nil, "and no gTrainers[0] class name")
|
|
-- src/trainer_tower.c:447, include/constants/trainers.h:243 TRAINER_CLASS_SAILOR
|
|
eq(bst and bst.trainerClass, 60, "gFacilityClassToTrainerClass maps the floor trainer's facility class")
|
|
eq(bst and bst.trainerPicId, 44, "and the facility-class front sprite")
|
|
local BattleBg = require("src.core.game3.battle.bg")
|
|
-- pokefirered/src/battle_bg.c:1048 GetBattleTerrainOverride
|
|
eq(BattleBg.terrainId(), BattleBg.TERRAIN.LINK, "a tower battle uses TERRAIN_LINK")
|
|
if Battle.endBattle then pcall(Battle.endBattle) end
|
|
|
|
Task.clear()
|
|
Tower.resetPack()
|
|
|
|
if failed > 0 then
|
|
print(string.format("[FAIL] %d check(s) failed", failed))
|
|
os.exit(1)
|
|
end
|
|
print("[pass] trainer tower party and battles")
|