Skip to content

Game modes

Alexandre 'kidev' Poumaroux edited this page Oct 21, 2026 · 7 revisions

Game modes

A game mode is a plugin's own rules for ProLeak's local server. The mode sets who holds each lane, switches whole systems of the game off, replaces single rules (policies), reacts to what happens on the server, and drives the game through a server-side game object. Everything it does not change plays as the official game.

Modes run on the local server only, which for now needs developer mode (dev: true in Settings).

Contents

A first mode

from proleak import modes

@modes.mode("half", title="Half damage", subtitle="Every hit does half its damage.")
def setup(game):
    """Runs on the server once, before the player joins."""
    game.lanes[1].human()
    game.lanes[2].bot(modes.bots.basic())
    game.lanes[5].idle()
    game.lanes[6].idle()
    game.mercenaries.enabled = False
    game.waves.build_seconds = lambda wave, seconds: 20

@modes.on("unit.damaged", pre=True)
def halve(ev):
    ev.damage *= 0.5

@modes.on("wave.ended")
def bonus(ev):
    for player in ev.game.players:
        player.gold += 10

The game's Solo & Co-op screen shows each mode as a card after the game's own cards. The card takes these from modes.mode:

Argument The card shows
title required its name
subtitle required the line under the name
image a PNG or JPG of at most 2 MB in the plugin's folder, such as "art/card.png"; without one, ProLeak's helmet
tooltip a text over the card while the pointer is on it
hue the colour of the glow round the card under the pointer, in degrees on the colour wheel (0 red, 120 green, 240 blue); purple (275) without one
info with info_popup a link under the card, as the game's Ranked FAQ
info_popup with info the text the link opens in a popup: a blank line starts a paragraph, and a paragraph whose lines all start with -  is a list
@modes.mode("tower_rush", title="Tower rush", subtitle="Twice the gold, half the time.",
            tooltip="Build phases last 15 seconds.", hue=20, info="Rules",
            info_popup="Every wave pays double.\n\n- 15 s build phases\n- No mercenaries")
def setup(game):
    ...

The cards wrap into rows once they no longer fit in one. modes.play("half") starts a mode from the plugin's own code; it raises ModeError with the reason when the game cannot start.

Client scope and server scope

A mode plugin runs code in two places of the same game process:

  • Client scope is the rest of the plugin: proleak.on, ui, camera, actions and the others, under the permissions of a local server. It changes what this player sees and sends.
  • Server scope is the code ProLeak calls for the mode: the setup given to modes.mode, the handlers given to modes.on, and the policies and timers they set. It changes the game itself.

Only the server scope of the mode the server is playing reaches the server. A call from client scope, from another plugin, or with no mode playing raises NotAllowedHere. Server handlers receive server units and players, never the client's world objects.

Lanes

In setup, game.lanes[slot] (slots 1 to 8; 1 to 4 are the west team) takes one of:

Call The lane
human() the player at this computer (slot 1)
bot(modes.bots.basic()) ProLeak's bot (see below)
idle() plays: its waves come and nobody builds
closed() is no lane at all

A team with nobody on it has no king. The lanes are fixed once the game starts.

ProLeak's basic bot plays the Mastermind with a random playstyle. Each build phase it builds toward the wave's recommended value, upgrades and buys workers, sends mercenaries for income, and buys King Regen after a wave that hurt its king. modes.bots.basic(king_heal=0.2) makes it heal only after a wave that took more than a fifth of the king's life (by default, after any damage).

Systems

Each system has a switch, game.<system>.enabled, on by default. A switch can change at any time from server scope.

System Switched off
economy no income, no bounty, no mythium gathered
building placing and upgrading fighters are refused
waves no wave comes; each build phase ends as if its wave were cleared
movement units stand where they are
combat nobody attacks
mercenaries hiring is refused
spells no legion spell is offered
notices no banners, pings or music
bots bots do nothing
gates no corridor gate is placed (2v2 map): the waves walk straight to their king

Policies

A policy is one decision of the server, replaced by a function: game.combat.damage = fn. A policy that returns None lets the server decide as the official game does, so it can change only the cases it cares about. Setting a policy to None restores the official rule. Units are server units, players Player.

System Policy Arguments Answer
economy income player, the income it would pay the gold paid when a wave is cleared
economy bounty dead unit, killer, the bounty the gold the kill pays
economy mythium_rate player, its gather rate the workers' rate (1 is the game's)
building can_build player, unit type, x, z whether it may be placed there (True lifts the lane and grid rules)
building cost player, unit type, its gold cost what placing it costs
waves units wave, lane the unit types that come down the lane, in order
waves build_seconds wave, its build seconds the build phase before it
waves next_wave the wave cleared, the next one the next wave; 0 ends the game, won by the player
movement route unit the points it walks instead of its lane's route
combat damage attacker, target, the damage the damage dealt
combat target unit, the units it could attack the one it attacks
mercenaries stock player, mercenary type, its stock how many can be hired now
mercenaries delivery player, the lane it would go to the lane a mercenary goes to
spells offer player, the legion spells offered the ones offered instead
gates holds unit whether a gate waits for it and sends it on when it opens (default: every wave unit the mode neither controls nor routes)
gates opens side (west, east), the units the gate waits for whether it opens now (default: once they are all through the corridor top)

A wave number or lane a policy names that the game does not have stops the mode.

Server events

modes.on(event, pre=False, priority=0) subscribes a server handler. ev.game is the game. Pre handlers may change the editable fields or cancel.

Event Fields Pre
game.started
game.ended won_side (west, east), reason
game.tick tick, the server clock in milliseconds
wave.started wave
wave.ended wave
unit.spawned unit, type, owner, lane
unit.died unit, killer
unit.damaged unit, source, damage (editable) yes, cancellable
unit.attacking unit, target; raised as a unit is about to attack a new target yes, cancellable
player.income player, amount (editable) yes
lane.leaked lane, wave; once a wave, when the lane's fighters have all fallen
gate.opened side, reason (wave, mode or broken)

game.tick and unit.damaged are raised only while a handler is subscribed.

A cancelled unit.attacking holds that swing only. The unit keeps the target, so the event is raised again on the next server tick (every 100 ms) for as long as the target stays the unit's best. To keep a unit off a target, call unit.ignore(target): the unit then picks another target, and the event is not raised for that pair while the ignore holds. The target policy chooses among every candidate instead.

The game

Member
game.lanes[slot] the lanes, set in setup
game.players each lane's player: slot, gold, mythium, income, workers (settable, shown at once), value
game.units(owner=, lane=, type=) the units alive, filtered
game.unit(id) one unit
game.wave, game.tick the wave, the server clock in milliseconds
game.random(n), game.random_float() the game's own seeded stream
game.after(ms, fn), game.every(ms, fn), game.cancel(timer) timers on the server clock; none runs after the game ends
game.spawn(type, owner, x, z) a new unit, which stands until ordered
game.gate(side) a corridor gate, west or east (see below)

A unit has id, type, owner, lane, x, z, alive, life (settable), and:

  • controlled = True: the mode drives it; it stops, leaves its route and attacks only what it is ordered to.
  • order_move(x, z), order_attack(unit). An attack order lifts an ignore on that unit.
  • ignore(unit), unignore(unit): it never takes unit as its own target, and drops it at once if it is attacking it. The ignore holds for that unit only: a later unit given the same id is not ignored.
  • kill(): it dies at once and nobody earns its bounty.

Corridor gates

On the 2v2 map each side's wave waits behind a gate at the bottom of its corridor; by default the gate opens once the wave is through the corridor top, and closes again when the next wave has come. A mode changes this with the gates system and its policies, or drives a gate directly:

  • gate.is_open, gate.open(), gate.close().
  • gate.life = 500 on a closed gate: the gate becomes a target of the wave waiting behind it (armoured, without an attack) and opens when its life is gone. A gate with a life ignores the default opening; the opens policy and gate.open() still open it.
@modes.on("wave.started")
def sturdy_gates(ev):
    for side in ("west", "east"):
        ev.game.gate(side).life = 400

Randomness and time

Use game.random, game.after, game.every and game.tick in a mode rather than Python's random and time modules. They follow the game's own seed and clock, so the same game played again from its commands gives the same result. A recording replays exactly whatever the mode does.

Chat commands

The server answers the game's chat commands: a line that starts with -, in any case, such as -gold 500. The cheats among them work only while the game's debug mode is on, and the mode decides when that is with game.commands.cheats:

Value Debug mode
"off" (the default) never: -debug is refused, and each cheat tells its player that debug mode is off
"debug" once a player types -debug, for the rest of the game, as in an official custom game
"on" from the start

game.commands.allow(fn) decides who may cheat and type -debug: fn gets each Player and returns True or False. game.commands.disable(name) switches a command off (typed, it does nothing, as an unknown command) and enable(name) brings it back. A game without a mode has its cheats on in a sandbox and off otherwise. The game's Codex leaves out -ratings, -next, -info and the type and wave kind commands; the server answers them all the same.

Command Cheat What it does
-debug turns debug mode on, when the mode lets the player
-ready votes to start wave 1 early
-ff votes to forfeit
-abandon leaves the game, lost
-keep 1 2 3 4 5 6 keeps those of a Mastermind roll
-kills, -dps, -lifetime in the build phase, shows over each fighter its kills this game, its damage per second in the last fight, or how long it lasted; once each 8 s
-ratings answers that the command is disabled, as the game always does
-next, -info X shows the next wave, or wave X: its units with their life, types and gold, its total gold and its recommended value
-pierce (or -piercing), -impact, -magic, -pure shows what the attack type does to each defense, and the waves that attack with it
-immaterial, -swift, -natural, -arcane, -fortified shows what each attack type does to the defense, and the waves that defend with it
-ranged, -air (or -flying), -boss, -miniboss lists the waves of that kind
-builder X yes, once a legion is picked picks legion X
-greedisgood yes +10000 gold and mythium, supply cap at the sandbox's
-gold X, -mythium X, -income X yes sets the player's gold, mythium or income to X
-goldall X, -mythiumall X yes sets every player's gold or mythium to X
-supply X yes sets the supply cap to X, at most the sandbox's
-workers X yes sets the player's workers to X
-nodouble yes switches double workers on or off (each town trains a double worker once wave 15 is cleared)
-wave X yes sets the wave to X
-start yes starts the wave now
-clear yes clears the wave, once it has finished spawning
-clearwest, -cleareast yes clears one side's wave at any time: its fighters warp to their catch area and its gate opens
-win, -lose yes sends thirty Dire Toads at the east king or the west one
-heal yes fills both kings' life and mana
-healwest, -healeast yes fills one king's life
-autoheal yes switches on or off a king about to die being healed whole
-smite X yes sets the selected unit's life to X percent; 0 kills it, paying nothing
-powerup yes offers every legion spell, at any time; the player may pick again
-maphack yes shows the whole map for the rest of the game
-mana X yes sets the selected unit's mana to X percent
-lockwave yes the wave comes again with the same sends and resources until typed again

Budgets and faults

Each handler, policy and timer has the plugin's handler budget (50 ms by default), and all of a mode's server code shares 20 ms in each server tick. A mode that raises, runs past either budget, or loses its plugin (turned off or reloaded) stops: the game ends at once with the reason across the screen.


Previous: Cosmetics | Next: Game updates

Clone this wiki locally