Configuration¶
config/battletower-server.toml: which backends handle Battle Factory. Restart to apply.
server.eventServer(defaultevents): backend for modes that use temporary Pokemon.server.rewardAuthorityServer(defaultmain): the only backend allowed to pay Factory rewards.
config/battletower/towers/<id>.json: one file per tower. Apply with /bt admin reload.
Tower file overview¶
{
"id": "example_tower",
"displayName": "Example Tower",
"enabled": false,
"rulesetId": null,
"entry": {
"command": "bt enter example_tower",
"permission": "battletower.enter.example_tower",
"limit": { "count": 3, "window": "PER_DAY" },
"cost": null
},
"between_battles": { "healPlayerTeam": true, "delayTicks": 20, "denyMenus": true },
"stages": [
{
"arenaId": "tower_arena_1",
"trainer": { "displayName": "Trainer Mira", "aiMode": null, "canMega": false,
"canDynamax": false, "bossTier": null, "team": [ ... ] },
"onWin": { "rewards": [ { "type": "MONEY", "amount": 200 } ] },
"onLoss": { "endRun": true }
}
],
"onComplete": { "rewards": [ { "type": "ITEM", "amount": 5, "item": "pixelmon:rare_candy" } ] }
}
Picking trainers¶
Trainers are picked once, when the run starts, and stay fixed for that run.
| Mode | How stages get trainers | Rules |
|---|---|---|
(no trainerPool) |
Each stage has a fixed trainer |
One non-null trainer per stage |
shared_random |
Distinct trainers drawn from one shared trainers list |
Shared list size >= stage count; stages have trainer: null |
shuffle_all_trainers |
Shared list shuffled, one per stage | Shared list size == stage count |
per_stage_random |
Each stage picks from its own trainerPool list |
Every stage needs a non-empty pool |
pick_random (legacy) |
Draw count whole stage definitions from trainerPool.stages |
count > 0 and <= pool size; top-level stages unused |
shuffle_all (legacy) |
All trainerPool.stages in random order |
Top-level stages unused |
Example, shared_random:
{
"stages": [
{ "arenaId": "tower_1", "trainer": null, "onWin": { "rewards": [] }, "onLoss": { "endRun": true } },
{ "arenaId": "tower_2", "trainer": null, "onWin": { "rewards": [] }, "onLoss": { "endRun": true } }
],
"trainerPool": {
"mode": "shared_random",
"trainers": [
{ "displayName": "Trainer A", "team": [] },
{ "displayName": "Trainer B", "team": [] },
{ "displayName": "Trainer C", "team": [] }
]
}
}
Example, per_stage_random: set "trainerPool": { "mode": "per_stage_random" } at the top, and
give each stage "trainer": null plus its own "trainerPool": [ {...}, {...} ].
The tower is rejected at load if: the mode is unknown or blank, legacy pool stages are mixed with
top-level static stages, a rule in the table above is broken, or an arenaId is not a registered
arena. The editor GUI keeps pool fields intact but cannot edit them; write pools in JSON.
Between battles¶
| Key | Meaning |
|---|---|
healPlayerTeam |
Heal the party after a stage win |
delayTicks |
Ticks before the next battle (default 20 if the block is missing; minimum 1 in practice) |
denyMenus |
Block menus between battles (not implemented yet) |
Rewards¶
| Type | Gives | amount means |
|---|---|---|
MONEY |
Vault money (through Battle Core) | Currency |
ITEM |
An item (item field) |
Item count |
XP |
Experience levels | Levels |
COMMAND |
Runs a console command (command field) |
Ignored |
MONEYis the only allowed entrycosttype. Old Pixelmon-bank costs and rewards are converted toMONEY1:1 on load, and the file is rewritten.- Where rewards go:
stages[].onWin(first clear this period),stages[].onWinRepeat(later clears),onCompleteandonCompleteRepeat(whole tower).
Reward reset periods (resetWindow)¶
Calendar periods in Eastern time. Everyone resets at the same moment.
resetWindow |
Resets at |
|---|---|
PER_HOUR |
Top of the next hour |
PER_DAY |
Next 12AM ET |
PER_WEEK |
Next Monday 12AM ET |
CUSTOM_DURATION, unset, unknown |
Never tracked: every run pays first-run rewards |
Attempt limits work differently. entry.limit.window is a rolling lookback in Battle Core, so
PER_DAY there means "in the last 24 hours". With a limit of N, an attempt frees up at the Nth most
recent attempt plus the window length. The names match; the behavior does not.
A late clear buys a short period: a first clear at 11:50 PM ET resets ten minutes later. Times say "12AM ET" because the boundary follows daylight saving time.
Battle Factory¶
An optional factory block turns a tower into the rental format. The player is handed off to the
events backend, picks 3 rentals out of 6 random offers, and may swap one rental for one used by each
defeated non-final trainer. Battles use detached Pokemon, so the real party is never replaced or
healed.
"factory": {
"enabled": true,
"choiceCount": 6,
"teamSize": 3,
"allowSwaps": true,
"rentalPool": [
{ "species": "pikachu", "level": 50, "moves": ["thunderbolt", "nuzzle", "protect"] }
]
}
- The pool needs at least
choiceCountentries. Use 7 stages and 3 Pokemon per trainer for the classic format. - A Factory tower must be free to enter (no
cost). - Source and events backends must share Battle Core's MariaDB and have the same Battle Tower, Battle
Core, tower, ruleset, and arena configs. Rewards are paid on
rewardAuthorityServer.
Trainer team fields¶
Wrong formats do not fail at load. Pixelmon logs a warning at battle time and drops the value.
| Field | Format | Example |
|---|---|---|
moves |
Title Case with spaces | "Shadow Ball" |
ability |
lowercase, no spaces | "magicguard" |
heldItem |
full item id | "pixelmon:life_orb" |
species |
name, any case, namespace optional | "Alakazam" |
form |
lowercase form name | "alolan", "megay" |
nature |
lowercase | "timid" |
gender |
MALE, FEMALE, GENDERLESS |
"FEMALE" |
ivs / evs |
6 numbers: HP, Atk, Def, SpA, SpD, Spe | [31, 0, 31, 31, 31, 31] |
aiMode:pixelmon:random,pixelmon:no_op,pixelmon:aggressive,pixelmon:advanced,pixelmon:raid. Unknown values fall back to random with a warning.bossTier:notboss,common,uncommon,rare,epic,legendary,ultimate,drowned,haunted,equal.- Mega Evolution: give the Pokemon its stone as
heldItemand set"canMega": trueon the trainer. Leaveformnull, or it starts the battle already Mega.