Gameplay mods
A scheme doesn't have to be an AI that plays for you — it can also be a set of rules. You play in the game window yourself, and the mod sets up the start, spawns enemies, hands out rewards, gives you buttons and choice cards on screen, and decides who wins. Subclass openwar3.Mod, and one file is a whole game mode.
There are two kinds of AI schemes: kind: bot is an AI that plays for you; kind: mod is a set of rules — you play in the game window yourself, and the mod sets the challenges: how the game starts, spawning enemies on a timer or on events, what rewards to give, which buttons and choice cards to show you on screen, and when you’ve won.
Mods only use capabilities that already exist: UI & input (clickable buttons, cards, hotkeys, ground clicks), the canvas (panels, progress bars, routes), the JASS channel (spawning units, changing stats, giving items) and the event stream (deaths, level-ups, spell casts, chat).
Two examples
Pick them in Farsight under “AI schemes” → “Built-in”:
| Mod | How it plays | Capabilities used |
|---|---|---|
Hero Roguelike builtin/hero-roguelike | You have just one Paladin, and enemies close in from all sides, wave after wave; each time you level up, pick one of three upgrades in the middle of the screen (the game pauses while you choose); survive 10 waves to win, lose if your hero dies | g.ui.choice (clickable cards + pause), hero.levelup / killed / spell.cast events, chat -help, JASS for changing hero stats and giving items |
Endless Defense builtin/endless-defense | Enemies spawn at the opposite start location and charge your town hall along a red line on the ground; every wave you hold off pays gold; click the on-screen button or press F7 to call the next wave early for ×1.5 rewards; press F8 and then left-click the ground to place a free arrow tower (right-click cancels) | g.ui.button, g.ui.hotkey, g.ui.mouse (capturing ground clicks), canvas panel / progress bar / route, JASS for spawning enemies and adding gold |
Each example is about 150 lines; the code is in brains/examples/mod_hero_roguelike.py and brains/examples/mod_endless_defense.py.
python tools/play.py --bot brains/examples/mod_hero_roguelike.py --inst 9 # start a game; the mod takes over and you play in the game window
Write a mod
from openwar3 import Mod
class Survive(Mod):
name = "survive"
def on_start(self, g):
super().on_start(g) # single-player check + suppress the computer opponent
self.foe = self.wave_player(g) # an empty-slot player as the "wave player": allied with no one, no computer AI
self.every(30, self.wave) # one wave every 30 game seconds (stops while paused)
g.ui.hotkey("F7", lambda g, ev: self.wave(g))
def wave(self, g):
self.spawn_ring(g, self.foe, "ugho", 6, self.home(g), 1400, attack_to=self.home(g))
def on_event(self, g, ev):
if ev.kind == "unit.died" and ev.type == "htow":
self.finish("loss", "The town hall fell")
On top of Bot, Mod adds:
| Method / attribute | Description |
|---|---|
on_start / on_tick / on_event / on_end | Same as Bot; when you override on_start / on_tick, remember to call super() first |
every(seconds, fn, first=) / after(seconds, fn) | Timers that run on game time; the callback is fn(g) |
finish(result, reason) | Ends this game ('win' / 'loss' / 'unknown'): the runner stops on the next tick, a result panel is drawn in the middle of the screen, and the scheme’s results record it |
wave_player(g) | The first empty-slot player, used as the wave player |
spawn_ring(g, player, unit, count, center, radius, attack_to=) | Spawns units around a ring; dozens per wave without stutter; returns JASS handles |
alive_of(g, player) / attack_move_all(g, player, point) | A player’s living units / attack-move all of them to a point (call it every few seconds and the enemies will chase) |
home(g) / hud(g, title, lines) | Our town hall’s position / the info panel in the top-right corner |
neutralize_ai = True | Suppresses the computer opponent at game start: its units are paused every 5 seconds and its gold and lumber are zeroed. Melee maps always have a computer player, and when a mod sets its own rules, it shouldn’t interfere |
single_player_only = True | Refuses to run if there are other human players (world-changing JASS would desync them) |
linger_s = 6 | After the result is decided, how many seconds to stay on the result screen before ending |
finish() works on Bot too: a regular bot can also announce the end of a game itself.
Package it as a scheme and share it
Write "kind": "mod" in scheme.json and define a Mod subclass in the entry file:
{"id": "survive", "name": "Hold out for 10 waves", "kind": "mod", "entry": "survive.py", "class": "Survive"}
A mod never uses fair mode (it’s the referee setting the challenges, so it needs to see the whole map and change the world) and isn’t judged by melee rules (the result is reported through finish); fair / judge in the manifest have no effect. Exporting a zip, importing, trust and results work exactly like bot schemes; see AI schemes. A mod is code too, so someone else’s mod also needs your trust before it runs for the first time.
Measured
2026-09-25, on a test instance:
- Hero Roguelike: the first wave spawns and the top-right panel updates; level the hero to 3 → cards pop up in the middle of the screen and the game clock stops; click cards twice → both upgrades take effect (Strength 22 → 27) and the clock resumes.
- Endless Defense: the panel, the route on the ground and the button are all there; F8 + click the ground → a defense tower appears next to the town hall; click the button before the current wave is cleared → the hint “this wave isn’t cleared yet”.
Limits
- Single-player only: spawning units and changing stats go through the JASS channel, which would desync a multiplayer game. That comes from the lockstep model; multiplayer gameplay has to wait for a sync channel (see the roadmap).
- A mod sees the whole map — it’s the one setting the challenges, not a player.
- The computer opponent on melee maps is only “suppressed”, not removed (removing it would trigger the melee victory check).