MOD API 1 · EARLY PREVIEW
Your code.
Your game.
Write mods in files. Load them from the Mods menu. No DLL injection, no client restart, no in-game editor.
This is RustTD6's own mod format. BTD6 MelonLoader mods do not run in it.
Make your first mod
- Install RustTD6 and Python 3.10 or newer. The SDK is included at
tools/modsdk.py. - Save this as
mods/quickstart/mod.py, next to your game:
# The SDK supplies Mod when it builds this script.
mod = Mod("quickstart", "Quick Start", description="A custom dart, bloon and mode.")
dart = mod.tower("dart", base="DartMonkey", cost=100)
dart.projectile(damage=3, pierce=4)
dart.weapon(rate=0.5)
mod.bloon("durable-red", base="Red", maxHealth=5)
mod.mode("training", base="Medium", cash=2000, lives=300)
- From the installation folder, build its manifest:
python tools/modsdk.py build mods/quickstart/mod.py
- Enable Settings → Advanced → UI → Show Mods Button. Open Mods, press Reload Mods, select Quick Start, then Enable Mod.
- Start a new local game. Your new tower appears in the shop. Your new bloon appears in Sandbox. The mode screen has a custom-mode selector.
Your script runs only when you explicitly build it. The game reads its generated mod.json and compiles the definitions into its native simulation. You can generate the same JSON using another language.
Reload, don't restart
Edit your code, build again, then reload the mod list. Changes apply to the next new game or restart of a game. An existing match keeps its current definitions; changing live entities underneath it is not supported yet.
Enable/disable is separate from selection. Optional per-mod settings save locally. The manager is for installed mods and settings, not authoring.
The content API
A manifest has api_version, a unique lowercase id, name, version, and a content list. Keep your ID stable between releases. The SDK prefixes your content IDs with your mod ID.
Towers and projectiles
mod.tower(name, base="DartMonkey", **patch) clones the tower, its upgrade tiers and shared visual references, then adds it to the shop. Custom towers currently reuse an exported model and icon; a custom art importer is future work. Heroes cannot be used as clone bases.
tower.projectile(damage=3, pierce=4) edits the base tier's projectile. tower.weapon(rate=0.5) sets its shot interval in seconds. Pass tier="100", attack=0 or weapon=0 to edit another tier or weapon. Upgraded tiers keep their inherited values unless edited separately.
Reuse another native projectile with a build-time template:
bomb = mod.projectile("big-bomb", base_tower="BombShooter", damage=10)
dart.projectile(bomb, pierce=30)Projectiles are compiled native data attached to weapons. They are not Python objects or callbacks during play.
Bloons and rounds
mod.bloon("durable-red", base="Red", maxHealth=5) creates a new bloon using an existing visual. speed, radius, children, isCamo and supported native behaviors can be edited. Child references must exist and cannot form cycles.
Use mod.set to replace a round group with one referencing your new bloon, or send it from Sandbox. New arbitrary behaviors require an engine API addition.
Modes and global changes
mod.mode("training", base="Medium", cash=2000) adds a new mode. It is available in the custom-mode selector below the ordinary modes.
mod.all_modes(lives=500) patches every mode present at that point in the content list. To change only an existing mode:
mod.set("/modes/Medium/cash", 1500)
mod.set("/towers/DartMonkey/cost", 100)These changes work in ordinary local games, not just Sandbox. Official events keep their own rules. In private co-op the room's mod set replaces each player's local selection.
Menus and statistics
mod.menu(page, path, text="...", visible=False) changes an existing menu node when that scene loads. Page names match exported prefab filenames, such as MainMenuUI. Paths match the node hierarchy in your local ui/prefabs export. Use text or visibility separately.
Native tower/bloon/weapon statistics are editable through content paths. New profile counters, scripted UI widgets and custom runtime callbacks are not implemented yet. Menu values that the game updates dynamically may overwrite a load-time edit.
Raw data and validation
mod.set(path, value) replaces an existing field using a JSON Pointer. Roots are towers, tower_tiers, bloons, modes, start, rounds, round_sets and freeplay. Inspect your own local_extracted/game/game.json for native field names and supported behaviors. Do not bundle extracted game data with a mod.
Manifests are limited to 64 KiB and 256 operations. Unknown operations, duplicate IDs, missing fields, child cycles and conflicting writes to the same path are rejected. Mods compile in sorted ID order; operations within a mod run in written order.
Mods with friends
Host a private iroh room, then open Room Mods. The host chooses the set. Guests can inspect it, and receive its data directly from the host even if it is not installed locally. Received manifests stay isolated in the room; they do not replace installed mods or run author scripts.
The current transfer supports up to 16 data mods, totaling 24 KiB of manifest text. Custom art packs and large file transfers are not supported yet. Each package is hash-checked, changes clear everyone's Ready state, and all players must have the same client build and base game data. The normal load barrier also checks the compiled initial game state before play.
Room mods are locked once a match starts. Official-server interoperability is not implemented. Use a private RustTD6 room.
Client and asset updates
Installed builds check GitHub through the launcher. A code-only compatible update installs automatically. If a release needs new extracted assets, you can keep playing the installed version or run the setup DLL inside BTD6 again. The launcher stages new assets and keeps the old build intact if extraction fails. Offline play uses the installed build.