Getting started
Recipes
Short, complete answers to common modding tasks.
Each recipe uses only the documented API. Put the Python lines in your mod.py after
mod = Mod(...), and the Rhai in a script embedded with mod.script(...).
Change a tower's price
mod.set("/towers/DartMonkey/cost", 100)
Start a mode with more cash and lives
mod.set("/modes/Medium/cash", 1500)
mod.all_modes(lives=500) # every mode defined so far
A faster, harder-hitting copy of a tower
dart = mod.tower("sharp-dart", base="DartMonkey", cost=250)
dart.weapon(rate=0.4) # seconds between shots
dart.projectile(damage=2, pierce=5)
dart.shop(after="DartMonkey")
A tougher bloon
mod.bloon("armoured-red", base="Red", maxHealth=20, isCamo=True)
Hide or move shop entries
mod.existing_tower("BananaFarm").shop(visible=False)
mod.existing_tower("SniperMonkey").shop(index=0, category="primary")
Pay a growing bonus every round
fn on_new_game(ctx) { this.bonus = 50; [] }
fn on_round_end(ctx) {
if ctx.sandbox { return []; }
this.bonus += 25;
[#{op: "cash", amount: this.bonus}]
}
A shield that absorbs the first 10 health hits
fn on_new_game(ctx) { this.charges = 10; [] }
fn before_damage(ctx) {
this.charges -= 1;
#{cancel: true, unsubscribe: this.charges <= 0}
}
The last charge applies its decision and then mutes the hook, so later hits cost nothing.
Stop a tower type being sold
fn before_sell(ctx) { #{cancel: ctx.event.tower == "DartMonkey"} }
Double every projectile's damage
fn before_fire(ctx) { #{damage: ctx.event.damage * 2} }
Send an extra wave every fifth round
fn on_round_start(ctx) {
if ctx.round % 5 != 4 { return []; }
[#{op: "spawn", name: "Pink", count: 20, spacing: 0.15},
#{op: "popup", title: "Bonus wave", body: "20 Pinks incoming!"}]
}
Top up lives when they get low
fn on_round_end(ctx) {
if ctx.lives < 20 { return [#{op: "lives", value: 20}]; }
[]
}
Tip
Commands from one callback are validated together; if any is invalid, none apply and the script stops for that match.