mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-20 21:11:43 -04:00
504 lines
18 KiB
Lua
504 lines
18 KiB
Lua
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:1 and
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:27.
|
|
|
|
local Breeding = require("src.core.gen2.Breeding")
|
|
local HallOfFame = require("src.core.gen2.HallOfFame")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
local Save = require("src.core.gen2.Save")
|
|
|
|
local BattleTower = {}
|
|
|
|
-- ../pokecrystal/constants/battle_tower_constants.asm:1-7
|
|
BattleTower.PARTY_LENGTH = 3
|
|
BattleTower.STREAK_LENGTH = 7
|
|
BattleTower.NUM_UNIQUE_MON = 21
|
|
BattleTower.NUM_UNIQUE_TRAINERS = 70
|
|
BattleTower.TRAINERDATALENGTH = 36
|
|
|
|
-- ../pokecrystal/constants/battle_tower_constants.asm:47 GS_BALL_AVAILABLE
|
|
BattleTower.GS_BALL_AVAILABLE = 0x0b
|
|
|
|
-- ../pokecrystal/constants/battle_tower_constants.asm:55-61
|
|
BattleTower.NO_CHALLENGE = 0
|
|
BattleTower.SAVED_AND_LEFT = 1
|
|
BattleTower.CHALLENGE_IN_PROGRESS = 2
|
|
BattleTower.WON_CHALLENGE = 3
|
|
BattleTower.RECEIVED_REWARD = 4
|
|
|
|
-- ../pokecrystal/constants/battle_tower_constants.asm:63-67
|
|
BattleTower.REWARD_QUANTITY = 5
|
|
BattleTower.MIN_REWARD = "HP_UP"
|
|
BattleTower.MAX_REWARD = "CALCIUM"
|
|
BattleTower.SKIPPED_REWARD = "LUCKY_PUNCH"
|
|
BattleTower.FALLBACK_REWARD = "POTION"
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:1471-1492 bit 0,
|
|
-- :978-1008 bit 1 of sBattleTowerSaveFileFlags.
|
|
BattleTower.SAVEFILE_REGISTERED = 1
|
|
BattleTower.SAVEFILE_EXPLANATION = 2
|
|
|
|
-- ../pokecrystal/constants/battle_tower_constants.asm:11-43
|
|
BattleTower.ACTIONS = {
|
|
CHECK_EXPLANATION_READ = 0,
|
|
SET_EXPLANATION_READ = 1,
|
|
GET_CHALLENGE_STATE = 2,
|
|
SAVE_AND_QUIT = 3,
|
|
CHALLENGECANCELED = 4,
|
|
ACTION_05 = 5,
|
|
ACTION_06 = 6,
|
|
SAVELEVELGROUP = 7,
|
|
LOADLEVELGROUP = 8,
|
|
CHECKSAVEFILEISYOURS = 9,
|
|
ACTION_0A = 10,
|
|
GSBALL = 11,
|
|
ACTION_0C = 12,
|
|
ACTION_0D = 13,
|
|
EGGTICKET = 14,
|
|
ACTION_0F = 15,
|
|
ACTION_10 = 16,
|
|
ACTION_11 = 17,
|
|
ACTION_12 = 18,
|
|
ACTION_13 = 19,
|
|
ACTION_14 = 20,
|
|
ACTION_15 = 21,
|
|
ACTION_16 = 22,
|
|
ACTION_17 = 23,
|
|
LEVEL_CHECK = 24,
|
|
UBERS_CHECK = 25,
|
|
RESETDATA = 26,
|
|
GIVEREWARD = 27,
|
|
ACTION_1C = 28,
|
|
ACTION_1D = 29,
|
|
CHOOSEREWARD = 30,
|
|
SAVEOPTIONS = 31,
|
|
}
|
|
BattleTower.NUM_ACTIONS = 32
|
|
|
|
-- ../pokecrystal/ram/wram.asm:1703 wNrOfBeatenBattleTowerTrainers, which
|
|
-- ../pokecrystal/maps/BattleTowerBattleRoom.asm:38 reads back with `readmem`.
|
|
BattleTower.WRAM_NR_BEATEN = 0xcf64
|
|
|
|
-- ../pokecrystal/constants/battle_tower_constants.asm:49-53 `battletowertext`.
|
|
BattleTower.TEXT_INTRO = 1
|
|
BattleTower.TEXT_WIN = 2
|
|
BattleTower.TEXT_LOSS = 3
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:39-55, in the order
|
|
-- BattleTower_ExecuteJumptable prints them.
|
|
BattleTower.RULE_HEADER_TEXT = "_ExcuseMeYoureNotReadyText"
|
|
BattleTower.RULE_FAIL_TEXTS = {
|
|
"_OnlyThreeMonMayBeEnteredText",
|
|
"_TheMonMustAllBeDifferentKindsText",
|
|
"_TheMonMustNotHoldTheSameItemsText",
|
|
"_YouCantTakeAnEggText",
|
|
}
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:61-68
|
|
BattleTower.RULE_TAIL_TEXT = "_BattleTowerReturnWhenReadyText"
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:28-31 wStringBuffer2.
|
|
BattleTower.RULE_PARTY_COUNT_TEXT = "3"
|
|
|
|
-- ../pokecrystal/mobile/mobile_46.asm:3936-3958 BattleTower_UbersCheck.
|
|
BattleTower.UBERS = {
|
|
MEWTWO = true, MEW = true, LUGIA = true, HO_OH = true, CELEBI = true,
|
|
}
|
|
BattleTower.UBER_MIN_LEVEL = 70
|
|
|
|
-- ../pokecrystal/mobile/mobile_46.asm:3869-3887 Strings_L10ToL100 and
|
|
-- Strings_Ll0ToL40.
|
|
BattleTower.MAX_LEVEL_GROUP = 10
|
|
BattleTower.PRE_HOF_LEVEL_GROUPS = 4
|
|
|
|
local function counter(value)
|
|
return math.max(0, math.floor(tonumber(value) or 0))
|
|
end
|
|
|
|
-- ../pokecrystal/ram/sram.asm:147-172, on top of Save.battleTowerState.
|
|
function BattleTower.state(save)
|
|
local tower = Save.battleTowerState(save)
|
|
-- ../pokecrystal/ram/sram.asm:156 sBTChoiceOfLevelGroup
|
|
tower.levelGroup = counter(tower.levelGroup)
|
|
-- ../pokecrystal/ram/sram.asm:159 sBattleTowerSaveFileFlags
|
|
tower.saveFileFlags = counter(tower.saveFileFlags) % 256
|
|
-- ../pokecrystal/ram/sram.asm:158 sBTTrainers, $ff for an unused slot
|
|
if type(tower.trainers) ~= "table" then tower.trainers = {} end
|
|
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:1449-1469 s5_aa8d
|
|
if tower.reentry == nil then tower.reentry = false end
|
|
return tower
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:1471-1483 and
|
|
-- :984-989: `and 1` / `and 2`, so the answer is the MASKED byte, not a boolean.
|
|
function BattleTower.saveFileFlag(save, mask)
|
|
local tower = BattleTower.state(save)
|
|
return (math.floor(tower.saveFileFlags / mask) % 2 == 1) and mask or 0
|
|
end
|
|
|
|
function BattleTower.setSaveFileFlag(save, mask)
|
|
local tower = BattleTower.state(save)
|
|
if math.floor(tower.saveFileFlags / mask) % 2 == 0 then
|
|
tower.saveFileFlags = tower.saveFileFlags + mask
|
|
end
|
|
return tower.saveFileFlags
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:890-904
|
|
function BattleTower.resetTrainers(save)
|
|
local tower = BattleTower.state(save)
|
|
tower.trainers = {}
|
|
tower.streak = 0
|
|
return tower
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:1016-1022
|
|
function BattleTower.setChallengeState(save, state)
|
|
local tower = BattleTower.state(save)
|
|
tower.challenge = counter(state)
|
|
return tower.challenge
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:206-211
|
|
function BattleTower.partyCountOk(party)
|
|
return #party == BattleTower.PARTY_LENGTH
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:218-277
|
|
-- CheckPartyValueIsUnique: eggs are skipped on both sides and a zero value
|
|
-- never collides, because `ld a, [hl] / and a / jr z, .next` drops it first.
|
|
local function valuesUnique(party, get)
|
|
local count = #party
|
|
for i = 1, count - 1 do
|
|
local mon = party[i]
|
|
if not Breeding.isEgg(mon) then
|
|
local value = get(mon)
|
|
if value ~= nil and value ~= 0 then
|
|
for j = i + 1, count do
|
|
local other = party[j]
|
|
if not Breeding.isEgg(other) and get(other) == value then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function speciesOf(mon) return mon and mon.species or nil end
|
|
local function itemOf(mon) return mon and mon.item or nil end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:213-216
|
|
function BattleTower.speciesUnique(party)
|
|
return valuesUnique(party, speciesOf)
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:279-282
|
|
function BattleTower.itemsUnique(party)
|
|
return valuesUnique(party, itemOf)
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:284-299
|
|
function BattleTower.partyHasEgg(party)
|
|
for _, mon in ipairs(party) do
|
|
if Breeding.isEgg(mon) then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/rules.asm:27-37 and :94-103: every
|
|
-- check runs, the first failure prints the header first, and a tail line
|
|
-- follows any failure at all. Returns the text labels in printing order.
|
|
function BattleTower.checkRules(party)
|
|
party = party or {}
|
|
local failed = {
|
|
not BattleTower.partyCountOk(party),
|
|
not BattleTower.speciesUnique(party),
|
|
not BattleTower.itemsUnique(party),
|
|
BattleTower.partyHasEgg(party),
|
|
}
|
|
local lines, any = {}, false
|
|
for index = 1, #BattleTower.RULE_FAIL_TEXTS do
|
|
if failed[index] then
|
|
if not any then
|
|
any = true
|
|
lines[#lines + 1] = BattleTower.RULE_HEADER_TEXT
|
|
end
|
|
lines[#lines + 1] = BattleTower.RULE_FAIL_TEXTS[index]
|
|
end
|
|
end
|
|
if any then lines[#lines + 1] = BattleTower.RULE_TAIL_TEXT end
|
|
return lines, any
|
|
end
|
|
|
|
-- ../pokecrystal/mobile/mobile_46.asm:1156-1166: the Hall of Fame flag is what
|
|
-- opens rooms above L40, and the last row is always CANCEL.
|
|
function BattleTower.levelGroupCount(save)
|
|
if HallOfFame.hasEntered(save) then return BattleTower.MAX_LEVEL_GROUP end
|
|
return BattleTower.PRE_HOF_LEVEL_GROUPS
|
|
end
|
|
|
|
function BattleTower.levelGroupRows(save)
|
|
local rows = {}
|
|
for group = 1, BattleTower.levelGroupCount(save) do
|
|
rows[group] = { group = group, level = group * 10 }
|
|
end
|
|
return rows
|
|
end
|
|
|
|
-- ../pokecrystal/mobile/mobile_46.asm:1264-1267 `dec a / and $fe / srl a`, the
|
|
-- BATTLE ROOM pair the hallway walks to (../pokecrystal/maps/BattleTowerHallway.asm:40-47).
|
|
function BattleTower.roomOf(group)
|
|
return math.floor((math.max(1, counter(group)) - 1) / 2)
|
|
end
|
|
|
|
-- ../pokecrystal/mobile/mobile_46.asm:3892-3934 BattleTower_LevelCheck
|
|
function BattleTower.levelCheck(party, group)
|
|
local cap = counter(group) * 10
|
|
for _, mon in ipairs(party or {}) do
|
|
if (tonumber(mon.level) or 0) > cap then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- ../pokecrystal/mobile/mobile_46.asm:3936-3989 BattleTower_UbersCheck: below
|
|
-- the L70 rooms an uber under L70 is refused, and its name goes in wcd49.
|
|
function BattleTower.ubersCheck(party, group)
|
|
if counter(group) >= BattleTower.UBER_MIN_LEVEL / 10 then return nil end
|
|
for _, mon in ipairs(party or {}) do
|
|
if BattleTower.UBERS[mon.species]
|
|
and (tonumber(mon.level) or 0) < BattleTower.UBER_MIN_LEVEL then
|
|
return mon.species
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:955-976: a
|
|
-- `maskbits` roll over HP_UP..CALCIUM that folds the overshoot back to the
|
|
-- bottom of the range and rerolls LUCKY_PUNCH.
|
|
function BattleTower.rollReward(order, random)
|
|
if type(order) ~= "table" then return nil end
|
|
local low, high
|
|
for index, name in pairs(order) do
|
|
if name == BattleTower.MIN_REWARD then low = index end
|
|
if name == BattleTower.MAX_REWARD then high = index end
|
|
end
|
|
if not (low and high) then return nil end
|
|
local span = high - low + 1
|
|
local mask = 1
|
|
while mask < span do mask = mask * 2 end
|
|
for _ = 1, 64 do
|
|
local roll = random(mask) % mask
|
|
if roll >= span then roll = roll - span end
|
|
local item = order[low + roll]
|
|
if item ~= BattleTower.SKIPPED_REWARD then return item end
|
|
end
|
|
return order[low]
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:906-933: five of
|
|
-- the reward only fit when the ITEM pocket has a free slot, or already holds
|
|
-- the reward with room for five more. Otherwise the desk hands over a POTION,
|
|
-- which the script reads as "your PACK is stuffed full".
|
|
function BattleTower.rewardFits(slots, capacity, held)
|
|
if counter(slots) < counter(capacity) then return true end
|
|
if held == nil then return false end
|
|
return counter(held) < 99 - BattleTower.REWARD_QUANTITY + 1
|
|
end
|
|
|
|
-- The opponent draw, all of it ../pokecrystal/engine/events/battle_tower/
|
|
-- load_trainer.asm, over RomExtractorGen2's Crystal-only `battleTower` block.
|
|
|
|
-- data/generated/trainers.lua `battleTower`, absent on Gold and Silver.
|
|
function BattleTower.roster(data)
|
|
local trainers = data and (data.gen2Trainers or data.trainers)
|
|
local roster = trainers and trainers.battleTower
|
|
if type(roster) ~= "table" then return nil end
|
|
if type(roster.trainers) ~= "table" or type(roster.groups) ~= "table" then
|
|
return nil
|
|
end
|
|
return roster
|
|
end
|
|
|
|
-- ../pokecrystal/ram/sram.asm:162-173 sBTMonOfTrainers: the last team's three
|
|
-- species and the one before that's, which the draw refuses to repeat.
|
|
function BattleTower.prevTeams(save)
|
|
local tower = BattleTower.state(save)
|
|
local teams = tower.prevTeams
|
|
if type(teams) ~= "table" then
|
|
teams = {}
|
|
tower.prevTeams = teams
|
|
end
|
|
if type(teams.prev) ~= "table" then teams.prev = {} end
|
|
if type(teams.prevPrev) ~= "table" then teams.prevPrev = {} end
|
|
return teams
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/load_trainer.asm:104-105 reads the
|
|
-- room back as `ld a, [wBTChoiceOfLvlGroup] / dec a`, so group 0 indexes
|
|
-- BEFORE the table; battle_tower.asm:1129-1141 is what can only save 1..10.
|
|
function BattleTower.opponentGroup(levelGroup, roster)
|
|
local groups = (roster and roster.levelGroups) or BattleTower.MAX_LEVEL_GROUP
|
|
local group = counter(levelGroup)
|
|
if group < 1 then return 1 end
|
|
if group > groups then return groups end
|
|
return group
|
|
end
|
|
|
|
-- load_trainer.asm:24-38 and :101-166 reroll until the roll passes; one draw
|
|
-- from the survivors is the same distribution and cannot spin. An empty
|
|
-- survivor set is where the cart's own loop would hang, so it draws unfiltered.
|
|
local function drawFiltered(count, random, accept)
|
|
local pool = {}
|
|
for index = 1, count do
|
|
if accept(index) then pool[#pool + 1] = index end
|
|
end
|
|
if #pool == 0 then return random(count) end
|
|
return pool[random(#pool)]
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/load_trainer.asm:22-60. The roll
|
|
-- is refused while it names anybody already in sBTTrainers, and the winner is
|
|
-- written into the slot sNrOfBeatenBattleTowerTrainers points at.
|
|
function BattleTower.chooseTrainer(save, roster, random)
|
|
local tower = BattleTower.state(save)
|
|
-- :29-37, the ceiling read out of the cart: Crystal 1.0 masks with
|
|
-- BATTLETOWER_NUM_UNIQUE_MON and can only ever draw the first 21 rows.
|
|
local ceiling = counter(roster.sampleTrainers)
|
|
if ceiling < 1 or ceiling > #roster.trainers then ceiling = #roster.trainers end
|
|
local seen = {}
|
|
for slot = 1, BattleTower.STREAK_LENGTH do
|
|
local held = tonumber(tower.trainers[slot])
|
|
if held then seen[held] = true end
|
|
end
|
|
local index = drawFiltered(ceiling, random, function(row)
|
|
return not seen[row - 1]
|
|
end)
|
|
tower.trainers[math.min(counter(tower.streak), BattleTower.STREAK_LENGTH - 1)
|
|
+ 1] = index - 1
|
|
return roster.trainers[index]
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/load_trainer.asm:94-208. Three
|
|
-- draws out of the chosen level group, each refusing a species this team
|
|
-- already holds, an ITEM this team already holds, and any species from the
|
|
-- last two teams. wBT_OTTrainer was zero-filled and its three item slots set
|
|
-- to $ff first (:7-17), which is why an unfilled slot collides with nothing.
|
|
function BattleTower.chooseTeam(save, roster, group, random)
|
|
local rows = roster.groups[group] or {}
|
|
local teams = BattleTower.prevTeams(save)
|
|
local picked, species, items = {}, {}, {}
|
|
for _ = 1, BattleTower.PARTY_LENGTH do
|
|
local index = drawFiltered(#rows, random, function(row)
|
|
local mon = rows[row]
|
|
if not mon then return false end
|
|
if species[mon.species] then return false end
|
|
if mon.item ~= nil and items[mon.item] then return false end
|
|
for _, seen in ipairs(teams.prev) do
|
|
if seen == mon.species then return false end
|
|
end
|
|
for _, seen in ipairs(teams.prevPrev) do
|
|
if seen == mon.species then return false end
|
|
end
|
|
return true
|
|
end)
|
|
local mon = rows[index]
|
|
if not mon then break end
|
|
picked[#picked + 1] = mon
|
|
species[mon.species] = true
|
|
if mon.item ~= nil then items[mon.item] = true end
|
|
end
|
|
-- :195-206, after all three: this team becomes sBTMonPrevTrainer and the
|
|
-- one it displaces becomes sBTMonPrevPrevTrainer.
|
|
local prev = {}
|
|
for slot, mon in ipairs(picked) do prev[slot] = mon.species end
|
|
teams.prevPrev = teams.prev
|
|
teams.prev = prev
|
|
return picked
|
|
end
|
|
|
|
-- The whole of `special LoadOpponentTrainerAndPokemon`, as one record. The
|
|
-- cart keeps it in wBT_OTTrainer, which is WRAM bank 3 and is NOT saved --
|
|
-- only the sBTTrainers slot and the two previous teams this walk writes are.
|
|
function BattleTower.drawOpponent(data, save, random, levelGroup)
|
|
local roster = BattleTower.roster(data)
|
|
if not roster then return nil end
|
|
local group = BattleTower.opponentGroup(levelGroup, roster)
|
|
local trainer = BattleTower.chooseTrainer(save, roster, random)
|
|
if not trainer then return nil end
|
|
local rows = BattleTower.chooseTeam(save, roster, group, random)
|
|
return {
|
|
index = trainer.index,
|
|
name = trainer.name,
|
|
class = trainer.class,
|
|
classId = trainer.classId,
|
|
sprite = roster.classSprites and roster.classSprites[trainer.classId],
|
|
group = group,
|
|
rows = rows,
|
|
}
|
|
end
|
|
|
|
-- ../pokecrystal/engine/events/battle_tower/battle_tower.asm:549-570
|
|
-- CopyBTTrainer_FromBT_OT_TowBT_OTTemp, which is the first thing
|
|
-- ReadBTTrainerParty does: the challenge is now in progress and the streak
|
|
-- counter steps BEFORE the battle, which is why beating the seventh opponent
|
|
-- leaves the counter on BATTLETOWER_STREAK_LENGTH.
|
|
function BattleTower.beginBattle(save)
|
|
local tower = BattleTower.state(save)
|
|
tower.challenge = BattleTower.CHALLENGE_IN_PROGRESS
|
|
tower.streak = counter(tower.streak) + 1
|
|
return tower.streak
|
|
end
|
|
|
|
-- ReadBTTrainerParty's .otpartymon_loop (battle_tower.asm:349-376) copies the
|
|
-- whole party_struct into wOTPartyMon, so nothing here is rolled: the stored
|
|
-- stats go on top of Mon.new's, which agree with them anyway.
|
|
-- The ROM nicknames never show: load_trainer.asm:171-190 overwrites each one
|
|
-- with GetPokemonName, so the rows carry none.
|
|
function BattleTower.battleParty(data, rows)
|
|
local party = {}
|
|
for _, row in ipairs(rows or {}) do
|
|
local moves = nil
|
|
if row.moves and #row.moves > 0 then
|
|
moves = {}
|
|
for slot, id in ipairs(row.moves) do
|
|
local def = data and data.moves and data.moves[id]
|
|
local max = (def and def.pp) or 0
|
|
moves[slot] = {
|
|
id = id,
|
|
pp = (row.pp and row.pp[slot]) or max,
|
|
maxPp = max,
|
|
}
|
|
end
|
|
end
|
|
local dvs = row.dvs or {}
|
|
local statExp = row.statExp or {}
|
|
local mon = Mon.new(data, row.species, row.level, {
|
|
moves = moves,
|
|
item = row.item,
|
|
dvs = { attack = dvs.attack, defense = dvs.defense,
|
|
speed = dvs.speed, special = dvs.special },
|
|
statExp = { hp = statExp.hp, attack = statExp.attack,
|
|
defense = statExp.defense, speed = statExp.speed,
|
|
special = statExp.special },
|
|
happiness = row.happiness,
|
|
})
|
|
if mon then
|
|
if row.stats then
|
|
mon.stats = {
|
|
hp = row.stats.hp, attack = row.stats.attack,
|
|
defense = row.stats.defense, speed = row.stats.speed,
|
|
specialAttack = row.stats.specialAttack,
|
|
specialDefense = row.stats.specialDefense,
|
|
}
|
|
end
|
|
mon.maxHp = row.maxHp or mon.maxHp
|
|
mon.hp = row.hp or mon.maxHp
|
|
mon.experience = row.experience or mon.experience
|
|
party[#party + 1] = mon
|
|
end
|
|
end
|
|
return party
|
|
end
|
|
|
|
return BattleTower
|