WIKI

모드

Modding Torreta — GLB / Full 3D

모드 가이드는 게임에 포함된 그대로 영어로 작성되어 있습니다.

Torreta now draws the board and cockpit from the same real 3D scene. Use glTF 2 binary .glb files for model replacements. The old raymarching shader and spritesheet examples no longer describe the default renderer.

A mod changes data and art, and may carry a script that adds rules through a fixed API, after the player allows it: see SCRIPTS.md. Restart after installing, editing, enabling or disabling a mod: the Mods screen's Restart now, shown as soon as a mod is switched, closes the game and opens it again.

Start here

Two samples ship with the game, disabled in Options → Mods: Sample mod - Traditional path and Sample mod - All resources. The others are on the Steam Workshop (the Mods screen's Workshop button opens it). To see a complete model mod, subscribe to Sample mod - Flame as A dragon and restart: Flame upgrades change the dragon from red to green to black, with an ember/gold fusion. The steps below are for authoring your own mod.

Sample mod - Ptero flyers, also on the Workshop, is another complete example: an alien pterosaur replaces all flying families. It includes its editable Blender source. See flying creature models for the rig and wingbeat settings.

  1. Open Options → Mods → Open Mods Folder.
  2. Copy _example_glb_towers to my_towers (without the leading underscore).
  3. Give it a unique id and name in mod.json.
  4. Restart. The Gun is now blue at all three levels and in its fused form; the board, cockpit, construction and tower icons use the replacement models.
  5. Import one of the example GLBs into Blender, edit it, and export glTF 2.0 → GLB, with materials/textures embedded. Replace the file and restart.

To change a number, copy _example_balance_towers. Its balance.json contains:

{
  "towers": {
    "stat_overrides": {
      "Gun": {"1": {"damage": 40, "range": 260, "price": 15}}
    }
  }
}

This works with both balance profiles. {"Gun":{"damage":40}} from the old README was incorrect: damage lives in a level's stats, not at the tower root.

Projectile accuracy is a profile table, not a per-level stat. The default is 70% on Gun, Cannon, Missile, MachineGun, Bolt, Frost, Laser and their fusions (Sniper, Tesla, Blizzard, Railgun). A miss still flies to the nearby miss point and can hit whoever is there. Mods can raise a type, or omit it so that tower never misses:

{
  "towers": {
    "accuracy": {"Gun": 0.9, "Sniper": 1.0},
    "accuracy_miss_spread": 32
  },
  "cards": {
    "effects": {"Accuracy+": 8}
  }
}

Other example/reference folders start with _ and are ignored. Never activate the vanilla reference: it is documentation, not a mod. Existing player copies are preserved during upgrades. Returning installations also receive README_3D_v1.md and _vanilla_reference_3d_v1, alongside their older files.

Scenery sequences

Sample mod - Traditional path ships disabled in Options → Mods. Enable it and restart to play a classic board: one fixed path through the pit and raised mesas beside it for the towers. It names the game's internal traditional scene, so it ships no model. Authors can supply their own GLBs and arbitrary wave thresholds in scenarios.json. See SCENARIOS.md for the format, built-in scenes, Blender coordinates, seamless runtime swaps and startup loading.

GameplayTiles/Walkable and GameplayTiles/Buildable Empty groups in the GLB define independent cell permissions. A tile may allow towers while blocking insects, and a Buildable marker's height raises its tower onto a mesa. The same guide covers marker coordinates and safe wave swaps.

Sample mod - Tidal Coast is a separate beach example on the Steam Workshop. It starts on a coast in all difficulties: animated waves to the north, shallow-water insect landings, rocky boundaries and a forest edge to the south. Its editable Blender source demonstrates tile permissions and the built-in OceanWater shader without executable mod scripts.

Cursor images

Add cursor.json to replace the mouse and controller cursor PNG, with a scaled click point and optional images per color. _example_cursor starts from the current insect-leg artwork. See CURSORS.md; restart after changes.

Ambient birds

Add birds.json to change which birds cross the arena, when, how many and how they fly, and ship Birds/<Name> GLBs through models.json to add species or replace the eight stock ones. Birds are scenery only, so a birds-only mod keeps the vanilla profile. See BIRDS.md and example_birds.

Ambient microfauna

Add microfauna.json to change the small colonies in the pit and on the plain: which species live together, how many colonies each preset holds, and how each species moves (an ant's trail, a beetle's wander, a cricket's hops, a butterfly's flight). Ship Microfauna/<Name> GLBs through models.json to add species or replace the stock ones. The microfauna is scenery only, so a microfauna-only mod keeps the vanilla profile. See MICROFAUNA.md and example_microfauna.

Plain shrubs

The dry shrubs on the plain above the pit are one model, Environment/PlainShrubs: a mesh per species named Saltbush, Creosote, Burrobush and Brittlebush (any other names are used in order), each standing on its root at the origin, Y up, in world units (a tower tile is 64; the stock ones are 28–42 across). An optional <Species>_Far mesh casts the sun's shadow and is drawn only when a shrub is a few pixels wide; keep it inside the full crown, or the crown sits in its own shadow. Without one the full mesh does both. Replacing it through models.json is cosmetic and keeps the vanilla profile. Pigment is the vertex colour (COLOR_0) times the material's albedo colour and texture (UV0). The wind rig is UV2 (TEXCOORD_1): x is the flex, 0 at the ground and 1 at the loosest tip, plus 2.0 on leaves (which also tremble); y is a phase shared by a stem and what grows on it. A mesh without it sways by height. Where they stand (Data/plain_shrubs.json) was measured on the stock plain and is not moddable: a mod's arena or scenario stage hides them. They bend with the weather's wind, wind_gust_strength and storms (WEATHER.md).

Files in a mod

Rain, sandstorms, three random intensities, reflective puddles, wind gusts and night lightning are configurable through balance.json. See WEATHER.md, SANDSTORMS.md, _example_weather and _example_sandstorm. Vanilla rolls 20% rain / 10% sand / 70% clear per map. The separate guides ship to returning installations without overwriting mods.

my_towers/
  mod.json                 required: identity and version
  models.json              optional: GLB asset replacements
  scenarios.json           optional: arena GLBs by difficulty and wave
  cursor.json              optional: cursor PNG, hotspot and color variants
  birds.json               optional: ambient bird species, flocks and timing
  bots.json                optional: lets PvP bots play under a gameplay mod
  balance.json             optional: balance/profile changes
  tower_data.json           optional: tower definitions/audio/icons
  map_data.json             optional: layout, prices, purchasable stats
  material_data.json        optional: factory materials and recipes
  enemy_data.json           optional: legacy wave colors / built-in scene mapping
  Models/Towers/Gun_1.glb   a self-contained model
  Assets/...               PNG/JPG icons and WAV/OGG/MP3 audio
{
  "id": "my_towers",
  "name": "My Towers",
  "author": "Your name",
  "version": "1.0.0",
  "description": "Blue ceramic Gun models.",
  "enabled": true
}

A mod that turns Torreta into another game adds "total_conversion": true; see Total conversions.

Use the in-game folder button; user://mods resolves to Godot's application-data folder for the current build/platform. Do not put mods inside the executable or .app bundle. Steam Workshop subscriptions are copied into that same mods directory as workshop_<id>, including items subscribed to while the game runs, and become active on the next restart. Unsubscribing removes the copy.

Rubble and biological remains

New runs scatter 3–10 natural rubble piles. Override MapCoords.rubble.initial_min / initial_max in map_data.json (0 disables; maximum 256, limited by safe space). The generator keeps entrance/exit routes open. Natural rubble and worm-created tower wrecks block construction until a Collector clears them within range: level 2 or 3 for natural rock, any level for a wreck.

The two kinds route differently. Natural rock blocks ground insects: it is scattered before the first wave and is part of the board the player reads when choosing where to build, and the generator refuses any pile that would seal a corridor. A tower's wreck is walked over, losing no time: a devoured tower is meant to open a shortcut, and a wreck that blocked would rebuild the wall the player just lost. Set MapCoords.rubble.blocks_enemies to true and wrecks join the rocks as walls. Flying insects pass above both, either way.

Models: Environment/Rubble_Natural_0..2, Environment/Rubble_<Tower>, Environment/Debris_0..2, Factory/Items/debris, and Environment/CollectorClaw (Jaw0, Jaw1, Jaw2 pivots). The magnet carries large rubble; the claw carries biological remains. All keys are in the model catalog. Changing count settings never rerolls an existing saved run. For tower levels 1 and 2, an explicit Environment/Rubble_<Tower>_<level> mod override takes priority over Environment/Rubble_<Tower>. Without that explicit override, a mod's unsuffixed wreck applies to all levels. Level 3 uses the unsuffixed key; unmodified towers retain their stock level-specific wrecks.

Clearing a pile stone by stone

The Collector's magnet takes a pile apart over several trips, one piece per trip, and the tile stays closed to construction (and natural rock to ground insects) until the last trip. Two things decide how, and they live in different places on purpose:

How many trips is gameplay, so it is data in map_data.json:

{"MapCoords": {"rubble": {"removal": {
  "natural": {"trips": 4, "trip_time_scale": 1.0},
  "wreck":   {"trips": 1, "trip_time_scale": 1.0},
  "models":  {"Environment/Rubble_Natural_2": {"trips": 6, "trip_time_scale": 0.75}}
}}}}

natural covers the terrain piles and wreck the ten tower wrecks; models overrides one rubble model key. trips is 1–32 and trip_time_scale (0.25–4) stretches the Collector's own cycle for those trips, so 0.5 makes each trip twice as fast. The values shown are the shipped defaults (the models entry is an example): natural rock takes four trips at the Collector's normal pace, a wreck leaves in one. A difficulty's rubble section in balance.json may carry its own removal, which replaces the map's whole removal block for that difficulty in solo; multiplayer uses the map's. The count is written into each pile when it appears, so a save, the co-op client and the spy all agree on it.

Which stones leave on each trip is art: name nodes Piece_<n> in the rubble GLB. Every mesh at or under such a node belongs to piece n, so a piece can be one mesh node or an empty grouping several. Pieces leave in ascending n; numbers need not be contiguous; text after the number is allowed (Piece_2_top), and nodes sharing a number leave together. Geometry outside every Piece_ node is the pile's footing and leaves with the last piece. The magnet lands on the top of the piece it takes and carries that piece away.

A rubble model is a cosmetic asset, so it never changes the number of trips: the data does. When the two disagree, the pieces are shared across the trips in order. More pieces than trips means some trips lift several. Fewer pieces than trips (including a model with no Piece_ nodes, which counts as one piece) means some trips only chip the next piece: the magnet carries a half-size copy of it and the pile keeps its look until the trip that takes the piece itself. An unmarked replacement for a natural pile therefore still takes four trips and disappears whole on the fourth. Set that model's trips to 1 in models for the old single lift.

The stock piles Rubble_Natural_0..2.glb are the reference: four pieces each, one large block with the loose stones beside it, taken tallest first. Their Blender source is Assets/Models/Source/Salvage.blend. However many pieces a model has, the game draws what remains of a pile as a single mesh.

GLB model mapping

An entry can opt into "generate_lods": true. At preload, the runtime generates the same mesh LODs as the editor's default scene importer, preserving the full base mesh and all named rigid pivots. The camera's existing quality and projected size thresholds choose the detail level. This is useful for dense rigid models such as the Iron Kingdom bays, mechs and wrecks. It defaults to false so existing mods retain their geometry behavior; review fine UV seams and any vertex-driven deformation before enabling it. The result is cached per file and configuration, not rebuilt per unit or during combat.

Dense rigid assemblies can additionally set "lod_mode": "rigid_parts" with generate_lods enabled. This fills large gaps before the first ordinary LOD by simplifying disconnected pieces independently, then recombining their indices into the same material surfaces. It preserves the full base arrays and every ordinary LOD, adds at most three intermediate buffers per surface, and does not add draw calls. The default "surface" mode keeps ordinary importer behavior. Custom vertex channels, skinned surfaces and blend shapes retain their ordinary LODs. As with any simplification, inspect the result at your intended zooms.

models.json has an API version and an explicit mapping. Keys are case-sensitive; file paths are relative to the mod folder.

{
  "version": 1,
  "models": {
    "Towers/Gun_1": {"file": "Models/Towers/Gun_1.glb", "material": "imported"},
    "Towers/Gun_2": "Models/Towers/Gun_2.glb",
    "Towers/Gun_3": "Models/Towers/Gun_3.glb",
    "Towers/Gun_3_Fused": "Models/Towers/Gun_3_Fused.glb",
    "Buildings/Factory_1": "Models/Buildings/Factory_1.glb",
    "Enemies/night_garden_flyer": "Models/Enemies/night_garden_flyer.glb"
  }
}

The string shorthand means material: "imported". Omitted models retain their vanilla art. A level-1 replacement does not automatically replace levels 2/3 or a fusion. Give each key its own model or deliberately point keys at the same file if its rig satisfies every key's contract.

Never ship a copy of one of the game's models (owner, 12/10: no mod shares the game's tower or scenery models). A slot you leave out keeps the game's own. To dress a slot in a game model kept under another key, name that model with game_model instead of a file:

"Towers/MachineGun_1": {"game_model": "Towers/Hangar_1"},
"Allies/Hero": {"game_model": "Allies/Commander"}

The slot then wears the game's own file, built the way the game builds it (authored PBR models keep their materials), and it must still carry the slot's pivots. The key is a path under the game's Assets/Models/ without .glb: the catalogue's keys and the Mech Hangar's Allies/Sentinel, Allies/Ranger, Allies/Commander, Towers/Hangar_1..3 and Environment/Rubble_Hangar*. Iron Kingdom wears the Mech Hangar's models this way. Only a model of your own goes in as a file. tools/mods/check_no_game_models.py refuses a sample that ships a byte-for-byte copy of a game model, and the release checks run it.

Ordinary enemy entries may set "locomotion": "float" for upright, gently bobbing models such as the Banana Brigade's fruit. Keep the catalog's named joints as empty compatibility nodes; walking feet no longer require geometry. This mode skips insect gait and foot contacts, without changing combat roles, pathfinding or health. Omit it (or use "rigged") for the existing animation. The worm uses its separate burrowing rig and does not accept this option.

vanilla_reference/model_catalog.json is generated from the actual shipping GLBs. It lists all shipping model keys, their source paths, pivot hierarchy, mesh and triangle counts, and automatically refreshed icon paths. Categories include:

  • 30 ordinary tower levels and six fusions.
  • Factory levels, barracks and radar; animated assembler/splitter/conveyor pieces.
  • The zoomable factory room's floor and shell: Factory/Room. See FACTORY_ROOM.md for the independent interior coordinate system.
  • Factory cargo/material models, fences, drills/barriers, debris and arena scenery.
  • 36 original insect families × walker/flyer, ten additional alien species with three difficulty palettes, plus the worm.

The additive caste keys are listed in vanilla_reference/enemy_caste_model_catalog.json. Existing Enemies/<family> overrides still take precedence. Use Enemies/Castes/<caste> to replace one new species across difficulty palettes, including tiger, tortoise, cricket, rhino, termite, stag, dragonfly, hornet, jewel and atlas. The original colony GLBs and Blender source remain active. Per-species keys such as Enemies/razor_reaver take precedence over the shared Enemies/Castes/tiger. All aliases are listed in the supplemental catalogue.

enemy_data.json.visual_species controls their appearance: enabled (true), first_wave (6), every_nth_enemy (4) and every_nth_boss_wave (3). Selection uses existing role flags and the authoritative ant_species field. It changes appearance, not HP, speed, rewards, spawn counts or combat RNG. The original 36 numeric species IDs remain stable; 30 difficulty-specific entries are appended.

  • 36 insect families × walker/flyer, plus the worm.
  • Tile/partner cursors, tower range rings and radar route modules. See MARKERS.md for the six GLB keys, reference sizes and required MarkerSignal material. example_glb_markers demonstrates textured replacements.

New factory material IDs may additionally use Factory/Items/<material_id>. Define the material/recipe in material_data.json and provide that model key. Unknown material models fall back to the factory's colored parcel. A replacement changes visual geometry, not its build footprint, pathfinding, range or damage. Keep arena cave/exit coordinates and fence/barrier dimensions compatible with the existing map. Keep barrel lengths and sockets consistent with the gameplay rig.

Blender contract

Start by editing an example or the source GLB listed in the catalog. Preserve its scale, origin, named empty pivots, hierarchy and local axes. Godot uses Y up; Blender exports with +Y Up enabled. Towers point along +X at rest. Tower Yaw/Pitch and Muzzle_0, Muzzle_1, etc. drive rotation, recoil and the actual 3D shot origin. Collectors need their Boom, Hook and Cable. Buildings have moving parts and lamp sockets; barriers need Drill_0…Drill_4 and may add more spindles as Drill_5, Drill_6… (the stock barriers carry six on their side arms): the game spins every Drill_N it finds, alternating direction. The game stretches every barrier to the exit (1.5× across, 2× up). The stock barriers are modelled in true proportions and carry the inverse of that stretch on their mesh node and Drill_* pivots, so their round cutter heads stay round while they spin. A replacement authored against the stretch still works. The electric levels may name their lightning in the root's optional barrier_arcs extra: JSON with paths (lists of [x, y, z] points in true Godot units, z towards the attackers) and steady (how many of the first paths never flicker). Without it the original rail-following discharge is drawn.

For any tower replacement (a dragon, creature, cannon or other design), see Tower models and firing points. It lists the required sockets for every level/fusion, explains how to put a firing point in a mouth or eye, and distinguishes the visual origin from aim direction and gameplay collision.

For insects, keep Body/Head, six Leg pivots, JawL/R, AntennaL/R, four Wing pivots, EyesL/R, Armor, Shield/ShieldHead and Abdomen as listed in the catalog. Empty unused wing pivots on walkers are intentional. Walking support legs need actual foot geometry. The existing game animates these rigid parts, keeps walking synchronized, applies hit/status/death effects, and builds cockpit triangle collision from the same posed geometry. Original family overrides retain their four supporting legs (Leg1/2/4/5); the new species preserve that same alien rig. Leg0/3 remain predator chelae. Original species keep their original models. New species add anatomy inspired by other insects, with alien paint/proportions. Shield depletion removes outer plates and preserves the underlying insect.

Optional TraitRegenerator, TraitSplitter, TraitShielded, TraitHaste and TraitNecromancer pivots under Body show the corresponding champion organ; ElytronL/ElytronR under Body can hold rigid beetle wing covers. The existing required pivots are unchanged. Imported materials stay intact, and stock LOD vertex indices are never applied to mod geometry.

The worm retains Body and its six child pivots Jaw0 through Jaw5. An optional Mouth empty directly under Body places the swallowed tower at the top of its lift. Its local position is in the model's Godot coordinates; its X rotation gives the prey a small lean. This also selects the burrower's paired-sickle motion for Jaw0 and Jaw3; the other jaws are inner maxillae. Omit Mouth to retain the original six-jaw motion and vertical swallowing path.

No armatures, skins, exported animation clips, cameras, lights or collision nodes. Tower creatures can use the optional CreatureWingL, CreatureWingR and CreatureJaw rigid pivots for wingbeats and an opening jaw during attacks. See the tower rig guide for axes, direct parents and the complete trio requirement. Use indexed triangles with normals (Blender exports these by default). Textures and buffers must be embedded: no external .gltf dependencies, absolute paths or ../ references. Runtime loading does not require Blender, the editor, .import, .godot, .tscn, .gd or .pck. GLBs over 64 MiB are rejected; keep meshes/textures close to vanilla complexity.

Single-mesh debris/conveyor pieces are batched; keep exactly one MeshInstance. The fence batch specifically reads Rails*_Mesh and PostLeft*_Mesh. Other parts must remain under the pivots that animate them. Optional secondary UVs (TEXCOORD_1, UV2 in Godot) encode the game's staged construction: retain them when editing a shipping model. Without them the model still rises from the floor, but its pieces share the default construction phase. Imported materials may instead use this second UV chart for glTF occlusion. Only the shipping piece-tag encoding enables staged construction. Ordinary texture coordinates use the default phase, including when the glTF importer reorders UV charts on a surface that samples only one texture.

Materials and performance

imported (default) preserves the usual glTF metallic/roughness PBR appearance: base color/texture and vertex colors, metallic/roughness texture, emission and normal and occlusion maps. Occlusion may use TEXCOORD_0 or TEXCOORD_1; its channel and direct-light influence are retained. For articulated models, bake contacts within each rigid assembly so moving limbs cannot leave permanent shadows on other parts. These surfaces gain the game's construction, hit, fracture and rigid insect animation hooks. Transparent surfaces use an alpha cutout, not sorted glass; advanced glTF material extensions are not implemented. Use emission on insect eyes. The imported path does not add the vanilla eye halo automatically.

game uses Torreta's vertex-painted materials instead. Export COLOR_0 for every mesh; this mode is useful for reshaping/recoloring the current art and preserving the native insect chitin/eye effects. Environment assets retain their GLB surfaces. Insects in game mode may opt into baked contact shading with a Boolean glTF custom property baked_chitin_contact on their root. In that case, COLOR_0.a stores ambient visibility: 1 is exposed and values down to 0.38 describe deep recesses. RGB still supplies the colony pigment. Bake each rigid part separately so hidden shields or ability organs cannot leave permanent shadows on the body. Without the tag, vertex alpha does not change the game's chitin shading. This tag does not replace the material contract of imported PBR surfaces. Floor markers use their own signal-aware material in both modes; the reserved MarkerSignal slot preserves validity colors and radar animation.

Insects still use batched GPU articulation. Custom geometry does not reuse vanilla LOD indices (which refer to different vertices); it retains full geometry. Limit materials and triangle counts, particularly for enemies. Model complexity can reduce frame rate; a mod is not guaranteed to meet the vanilla performance target.

Tower levels/fusions, supported factory item/build icons and barrier icons are rendered from replacement models at startup. Barracks troop portraits use the replacement insect directly. Fixed tutorial screenshots and other illustrated UI art are not re-rendered. The public game uses Full 3D; legacy internal hybrid spritesheets are not regenerated from these GLBs.

Current examples

  • example_factory_room — a cobalt 3D factory room with gridded plates and inset lighting. Published on the Steam Workshop as Sample mod - Cobalt factory room. Includes editable Blender source; changes the zoomable interior, not the interface.
  • example_flame_dragon — original sculpted dragon for Flame levels 1/2/3 and fusion, animated wings/jaw, mouth fire and editable Blender source. Published on the Steam Workshop as Sample mod - Flame as A dragon; a subscription installs it. Built with tools/mods/build_flame_dragon.py.
  • example_birds — Greater Flamingos crossing in V skeins at dusk and night, with an editable GLB and Blender source, plus two retuned vanilla species. Published on the Steam Workshop as Sample mod - Flamingos at dusk. Built with tools/mods/build_bird_example.py; see BIRDS.md.
  • example_microfauna — a desert scorpion wandering out from under its stone in the pit and on the plain, with an editable GLB and Blender source, a group of its own, a place in the ants' mixed nest and a retuned butterfly. Published on the Steam Workshop as Sample mod - Desert scorpions. Built with tools/mods/build_microfauna_example.py; see MICROFAUNA.md.
  • example_glb_towers — blue Gun levels 1/2/3 and Sniper fusion, embedded striped PBR material, working yaw/pitch/muzzle and construction.
  • example_glb_industry — orange factory levels, barracks/radar, assembler, splitter, metal cargo, fences, barrier drills and debris.
  • example_glb_insects — violet day/dusk/night first families, both walkers and flyers, emissive cyan eyes, and worm. Species change every five waves; difficulty sets have 12 families each. Replace the other catalog keys to skin their later wave blocks as well.
  • example_all_resources — a sandbox: every New Game starts with 10,000 gold, 99 lives, 999 of every material, troop and ammunition, and every tower and feature unlocked, the Mech Hangar's batteries, beacons and pods included; its profile is level 99 with every skill point the tree can spend. Ships as Sample mod - All resources, disabled by default in Options → Mods. Data only; see Run-start grants.
  • example_veteran_commander — the Mech Hangar's Commander walks out at level 5 with its jetpack (one start_level in tower_data.json). Published on the Steam Workshop as Sample mod - Veteran Commander; with All resources everything the hangar does can be tried.
  • example_ammo_factory — ammunition as the source of the shot: only the level 1 Gun fires for free, every other tower fires on one of six kinds of ammunition the factory makes from hardware, metal and copper, and holds fire without it. The Collector and the factory open on the first wave. Ships as Sample mod - Factory ammunition, disabled by default. Data only; see Towers that fire on ammunition.
  • example_script_bounty — a script mod: a bounty every tenth kill, veteran towers that hit harder, and a static spark in the rain. Ships as reading material (_example_script_bounty); see SCRIPTS.md.
  • example_lua_bounty — the same script in Lua. Ships as reading material (_example_lua_bounty).
  • example_balance_towers — Gun/Cannon stat changes using the profile-aware path.
  • example_balance_hard_mode — economy/enemy/weakness changes, plus the two newer knobs: economy.tower_upkeep.level_weight, which charges a tower's level and not only its existence (at 0.5 a level-three tower costs like two level-one towers, and 0 is the shipping behaviour), and difficulty.levels.<n>.rubble.initial_min/initial_max, which gives a difficulty its own rubble scatter. Omitting the rubble section keeps the map's own MapCoords.rubble numbers, so a mod only writes the difficulties it wants to change. Each difficulty can also take a layout: a list of piles with their own cells, {"x": 12, "y": 6, "variant": 0, "heading": 1.57}. With one present the random draw does not run and the field is exactly what the mod wrote, the same on every seed -- which is how a mod builds a scene out of the rubble blocks instead of accepting the vanilla scatter. variant picks which of the three natural GLBs is used and heading its spin; both are optional. A pile that would seal the enemy route is refused and named in the log, so a mod cannot ship a board with no path -- and the author learns which one fell. The rubble GLBs themselves were already replaceable through models.json: Environment/Rubble_Natural_0/1/2 and the nine tower wrecks are all in the model catalog.

Source for regenerating the GLB examples is tools/mods/build_glb_examples.py in the repository. It runs in Blender's background mode and edits only example files. The procedural wall-shader example has been retired from the shipping examples. Old player-installed copies are not deleted or silently enabled.

Run-start grants

A mod can hand a run admin's ACCESS and stock through data, without turning admin on. Starting gold and lives keep their own keys; run_start adds the rest, read once per New Game (the profile keys also when the skill tree opens):

{
  "general": {"starting_gold": 10000, "starting_lives": 99},
  "multiplayer": {"starting_gold": 10000},
  "cockpit": {"unlimited_time": true},
  "run_start": {
    "unlock_everything": true,
    "all_materials": 999,
    "materials": {"debris": 9999, "overclock_chip": 99},
    "player_level": 99,
    "skill_points": "all"
  }
}
  • unlock_everything — every tower (the Mech Hangar included), the wall at level three, fusion, elemental links, the cockpit, the radar at full reveal and strategies, plus the barrack in PvP (it sends troops to a rival, so solo and co-op never show it). Leave it out to keep the normal card unlocks.
  • all_materials — this stock of every material in material_data.json, troops, ammunition and chips included. 0 or absent leaves the pantry alone. A PvP bot takes no troops from it, only those materials names: a bot sends what it holds by itself, and 999 of each flooded the first waves.
  • materials — per-material amounts, over all_materials.
  • player_level — the profile's level is raised to this (up to 99), so the skill tree's nodes that ask a level can be bought.
  • skill_points: "all" — the profile's unspent points are topped up to buy whatever the tree has left.

A Continue keeps what its run was given and is not handed more. The two profile keys write the mod profile, which every gameplay mod shares: points and levels given there stay there when this mod is switched off, and the vanilla profile never sees them.

A board's own rules

A mod can also reshape the run itself. Sample mod - Traditional path uses all of these:

{
  "cards": {"enabled": false},
  "towers": {"disabled": ["Collect"], "upgrades_need_skills": false},
  "run_start": {"towers": ["Gun", "Cannon", "MachineGun"], "cockpit": true},
  "factory": {"tower_material_mult": 0}
}
  • cards.enabled: false — no cards between waves: nothing is drawn and the next wave joins straight on. Everything a card would have given stays locked unless the mod grants it another way.
  • towers.disabled — tower keys that are never unlocked, offered or built. The Collector's absence also keeps the factory closed.
  • towers.upgrades_need_skills: false — levels 2 and 3 no longer wait for the skill tree's node, for a mod whose towers should not wait on the tree.
  • run_start.towers — towers already unlocked when the run starts (and on Continue). run_start.cockpit: true opens the cockpit the same way.
  • factory.tower_material_mult: 0 — towers cost money only, to build and to upgrade; the material rows disappear from the palette, wheel and tooltips. Set the prices with towers.stat_overrides.<Tower>.<level>.price.

Combining a mode with tuning mods

Partial JSON overlays merge recursively; a money-only mod does not need to know the mode's other parameters. Unknown or unused settings remain inert until a system consumes them. An active mod's cards.enabled: false takes precedence over other mods' card tuning, and towers.disabled combines the excluded types from all active mods. These capability exclusions are independent of load order; numeric conflicts for systems that remain active still use the last loaded value. Disable the excluding mod to restore the feature.

For example, example_banana_brigade and example_all_resources combine into a fixed-route game with 10,000 starting gold and no cards or Collector. Two total conversions never combine: only one plays at a time (below). Another mod's card pool or reroll settings do not reopen the card screen or apply card effects in that mode.

cloaked_waves.detection_towers optionally replaces the detector roster with an object of tower key to minimum level. cloaked_waves.shared_detection: true lets every tower target/damage a cloaked enemy while it is in a detector's range. Omitting these keys preserves vanilla's level-3 self-only detection. Optional cloaked_waves.tutorial_description is an object of language code to description, with en as fallback; it also supplies the guide's camouflage explanation.

cockpit.unlimited_time (default false) is a balance key, not a grant: with it on, the cockpit has no stopwatch, as the co-op gunner and admin mode already had. It applies to every run while the mod is active.

Gameplay mods play on their own profile. With a mod that changes the game active, the game reads and writes savegame_mods.save and seeds_mods.json instead of savegame.save and seeds.json: progression, the Continue run, the solo pantry and factory (which carry over between runs) and the seed history. Switching those mods off brings the vanilla profile back exactly as it was left. prefs.cfg (language, cursor, volume) is shared.

Only a purely cosmetic mod keeps the vanilla profile. A mod changes the game when it ships any of tower_data.json, map_data.json, material_data.json, enemy_data.json, balance.json or scenarios.json, or a models.json entry the cockpit measures against: Enemies/* (the model is the insect's hitbox), Towers/* (the cockpit fires from its muzzle) or Environment/Arena (terrain collision and gameplay tiles). Cursors, sounds, legacy shaders and sheets, and Buildings/*, Factory/*, Markers/*, Allies/* (the units' looks: the hangar's Sentinels, Rangers and Commander) and the other Environment/* models are cosmetic. Of the examples, example_cursor, example_factory_room, example_glb_industry and example_glb_markers are cosmetic.

Achievements, leaderboard scores, XP and crystal drops stay off while a gameplay mod is active. Skill points and permanent cards still count, on the mod profile: bosses and the Skill Coin card pay as in vanilla, and the vanilla tree never sees those points. A purely cosmetic mod plays exactly like vanilla: same profile, same progression.

A tower of your own, derived from a game tower

A mod adds a tower (or more) by naming a base among the game's ten in tower_data.json. The new slot plays through its base's scene, behaviour and model contract; its entry is the base's, merged under what the mod writes, so a mod writes only what differs: a name, a description, prices, stats, soldiers, "fires": false, ammo, income. The game's own Mech Hangar is written this way in the game's tower_data.json (see vanilla_reference/tower_data.json), so a mod cannot use the key Hangar for a tower of its own; a mod's entry under that key changes the game's hangar instead.

{"Bunker": {"base": "MachineGun",
            "name": {"en": "Bunker", "pt_br": "Casamata"},
            "levels": {"1": {"price": 350, "fires": false,
                             "soldiers": {"count": 3, "health": 450, "damage": 30}}}}}
  • Its soldiers stop the insects they fight (the mod TD kit's barracks). With "holds": false they fight them on the move instead, as the game's hangar does: a soldier walks up to an insect, keeps beside it and strikes it, the insect walks on at its own pace and strikes back now and then. "model": "Allies/<Key>" dresses the soldiers in another ally model, such as the game's Allies/Sentinel.
  • skill names the skill-tree nodes that open levels 2 and 3 (<skill>2, <skill>3, which map_data.json must hold); without it they are the base's. max_count caps how many may stand on a board.
  • start_supply ({"<material>": amount}) tops the factory's stock up to those amounts when the tower's card is taken. The hangar brings 4 batteries, an orbital beacon and a ranger pod.
  • Batteries. A soldier spec's battery_seconds makes each unit on the field drain one battery every that many seconds of a wave. Nothing drains between waves.
    • A fallen unit comes back only by spending one.
    • With none in stock the units walk back into their tower and wait there until the factory makes one.
    • battery is an ordinary factory material: change its recipe in material_data.json, or leave battery_seconds out for no drain.
  • A hero of its own (hero, the hangar's Commander). It comes out of the tower's door with the soldiers once the tree node skill is bought (no node: at once), comes back there after a death, and leaves with the tower. One hero per board; a mod's allies.hero keeps the place. Its keys:
    • the unit keys (health, damage, rof, speed, respawn, model, battery_seconds) and name (text, a dictionary by language, or a localization key);
    • manual: true: it never seeks or strikes by itself. The player drives it (WASD or the stick) and strikes with a left click or A, held to keep striking, at the walking insect within strike_reach nearest the aim;
    • damage_wave_share: a blow does that share of the wave's insect health when that is more than damage;
    • struck_share: the insect it struck strikes back for that share of its health per life the insect is worth (at most 35%), when that is more than the insect's melee; less its defense. The game's Commander has 0.04. In its eyes each strike reddens the screen's edge on its side and jolts the view;
    • regen a second while it rests within rest_radius of its tower, nothing having hurt it for rest_after seconds;
    • levels:
      • xp: cumulative, in insects' worth of damage dealt;
      • points_per_level;
      • damage_per_point, health_per_point, defense_per_point and defense_cap. The player spends the points on its action ring;
      • a death sends it back to level 1, or to start_level;
    • start_level: the level it walks out at, with the points the levels below it bring, and comes back at after a fall (admin uses the jetpack's level). example_veteran_commander sets 5 on the game's hangar;
    • jetpack (level, distance, seconds, cooldown, height): a hop with Shift or Y over the towers in its way onto the next tile it may stand on, at that tile's centre, at most distance away; seconds and height are a full-length hop's, and a shorter one is quicker and lower;
    • icons (drive, damage, health, defense): its ring's pictures.
  • Its card is unlock_<key in lower case> (unlock_hangar), added to the pool with cards.pool_add (below). Taking it adds the tower to the palette, the controller's wheel and the guide.
  • Its models are Towers/<Key>_1, _2 and _3 in models.json, held to the base's pivots (Towers/MachineGun_1...). Without them it wears its base's. With them and no icon of its own, the palette, the card, the tower window and the gold feed show a picture baked from each level's model.
  • Its upgrades wait for the base's skill-tree nodes (MachineGun2, MachineGun3), or for its own skill nodes.
  • What it does not get: a number key (the keys 1-0 are the game's ten), the cockpit when it does not fire, fusions, and a weakness group or crystal of its own. A base that is not one of the game's ten drops the slot, with a line in the log. A save holding the tower skips it once its mod is off.

Towers that fire on ammunition

By default the factory's ammunition is a bonus: while its group has stock, each hit of a Gun, Cannon, Machine Gun or Missile (Ammo Lv.1), Flame, Poison or Frost (Lv.2), Bolt or Laser (Lv.3) deals more damage, and a tower without it still fires. A mod can make the ammunition the shot itself, per tower and per level, in tower_data.json:

{
  "Gun":    {"levels": {"1": {"ammo": ""}, "2": {"ammo": "ammo_rounds"}, "3": {"ammo": "ammo_rounds"}}},
  "Cannon": {"levels": {"1": {"ammo": "ammo_shells", "ammo_per_shot": 2}}}
}
  • ammo is the material that level fires on. Each firing cycle (one reload: a volley, a pair of shells, a stretch of a jet) spends ammo_per_shot units, 1 when omitted; without them the tower holds fire, and a Flame or Poison jet goes out when its last paid cycle ends.
  • An empty ammo fires for free.
  • A level that declares ammo never takes the special-ammunition bonus. A level without the key keeps the vanilla rule, so a mod can convert some towers only.
  • A fused tower uses its type's level 3 entry.
  • The tower menu's switch reads Ammo for such a tower and still decides whether it may spend: switched off, the tower stops firing. It is on by default, and the cockpit's switch follows it.
  • The recipe tooltip names the towers that fire on each ammunition.

Pair it with material_data.json to make the ammunition: new materials and recipes merge over the stock ones, and three keys help a factory built around it:

  • null removes a stock material, recipe or tower cost ("biological": null). Cards whose recipe is gone are not offered, the barracks card needs a troop recipe, and the Advanced tier's tutorial needs a tier2 recipe.
  • A material's name and description may be a dictionary by language code ({"en": "Rifle rounds", "pt_br": "Balas de fuzil"}); a missing language falls back to English, then to the first given.
  • A recipe's default_cap raises its default stock limit (otherwise 20, or one batch). max_stock holds it to that many whatever the player sets, as the hangar's beacon and pod are held to one. requires_tower keeps it shut until that tower's card.
  • A new material gets a counter in the HUD's materials bar, in the second row, or the first with "hud_row": 1.

Fusions pay tower_fusion.cost_materials in balance.json: its projectile, liquid and energy kinds are hardware, biological matter and copper, any other key is a material id, and a kind set to 0 is no cost. A mod that removes biological matter sets "liquid": 0.

example_ammo_factory does all of it.

Writing another tower defense: waves, layers, paths and income

Torreta's own game stays as it is; these keys let a mod rebuild a different tower defense on top of it. Each is off until a mod writes it.

Naming your towers (tower_data.json)

A tower slot can carry its own name and description: a text, or a dictionary by language code ({"en": "Mech bay", "pt_br": "Baía de mechs"}; a missing language falls back to English), like a material's. The palette, its tooltip, the controller's wheel, the tower window, the unlock card and the guide use them. The tooltip also says what the tower does under the kit: a level with "fires": false shows that it does not fire, a barracks its units, a level's income and ammo, and a tower with paths its paths instead of levels 2 and 3.

{"MachineGun": {"name": {"en": "Mech bay", "pt_br": "Baía de mechs"},
                "description": {"en": "Does not fire: keeps three mechs on the road."}}}

Hand-written waves (waves.json)

Vanilla generates every wave from formulas. A waves.json beside balance.json replaces the waves it lists with your own, and defines the enemy types they use:

{
  "after_last": "victory",
  "health_by_difficulty": {"1": 1.0, "2": 1.5, "3": 2.0},
  "types": {
    "red":  {"health": 8, "speed": 30, "reward": 1, "species": "fire"},
    "blue": {"health": 8, "speed": 40, "reward": 1, "species": "harvester",
             "children": [{"type": "red", "count": 1}]},
    "lead": {"health": 45, "speed": 30, "kind": "armored", "immune": ["projectile"],
             "resist": {"frost": 0.5}, "children": [{"type": "blue", "count": 2}]}
  },
  "list": [
    {"groups": [{"type": "red", "count": 12, "spacing": 0.9}]},
    {"groups": [{"type": "red", "count": 10, "spacing": 0.6},
                {"type": "blue", "count": 6, "spacing": 0.9, "delay": 1.0}], "reward": 3}
  ]
}
  • A type has health, speed (vanilla's are about 25 to 95), reward (gold per kill), shield, species (an id from Data/ant_species.json or its index: the model and colour), kind (worker, fast, armored, swarm, shield, flyer, boss, flyer_boss), scale (a boss's size), cloaked and life_damage (what a leak costs; by default one life for it and one for every layer still inside, like a Bloons leak).
  • Layers. children are born where the enemy died and walk on from its step of the route; the wave waits for them. Keep counts low: every layer is a real insect, and a tree that doubles at each layer floods the board.
  • Immunity and resistance go by damage group, the weakness groups of weakness_system.tower_to_weakness_map: projectile (Gun, Cannon, Machine Gun), explosive (Missile), flame, frost, bolt, laser, poison, and wall for the barrier. immune takes no damage and no status; resist multiplies the hit.
  • A wave is a list of groups, each a type, a count, the spacing in seconds and an optional delay before it starts; plus reward (the wave's gold per kill for anything without its own), boss, weakness and coins. Listed waves keep no generated double waves or boss rush.
  • After the list, "procedural" (the default) carries on with vanilla's waves; "victory" ends the run as won.
  • health_by_difficulty scales every type's health and shield per difficulty.

The waves join the profile under waves, so a mod with only waves.json is a gameplay mod: its own save profile, and players with different waves do not meet online.

Upgrade paths (tower_data.json)

A tower type with paths grows by up to three paths of tiers, bought in place, instead of by level:

{
  "Gun": {"paths": {
    "caps": [5, 2, 0],
    "visual_levels": [2, 4],
    "list": [
      {"name": {"en": "Strength", "pt_br": "Força"}, "icon": "res://Assets/Icons/tower_upgrade.png",
       "tiers": [{"price": 40, "name": {"en": "Firmer grip"}, "description": {"en": "+30% damage."},
                  "damage_mult": 1.3}]},
      {"name": {"en": "Speed"}, "tiers": [{"price": 50, "rof_mult": 0.85}]},
      {"name": {"en": "Canopy"}, "requires": {"path": 0, "tier": 1},
       "tiers": [{"price": 60, "range_mult": 1.15}, {"price": 120, "detects_camo": true}]}
    ]}}
}
  • caps is the crosspath rule: sorted from the highest, the bought tiers may not pass it ([5, 2, 0]: one path to 5, a second to 2, the third shut). [3, 3, 0] with requires on paths 1 and 2 is a level-4 specialisation: a base path to 3, then one of two branches.
  • visual_levels are the highest tier at which the tower wears its level 2 and level 3 model, and stands on that level's numbers in levels.
  • Tier effects: damage_mult, rof_mult (below 1 throws faster), range_mult, splash_radius with splash_ratio (neighbours within the radius take that share of each hit), detects_camo and income_add. Multipliers multiply, additions add.
  • The tower's window shows a card per path with a pip per tier, and its ring a sector per path it can still grow. Selling returns the level 1 price and the tiers at the usual ratio. Tiers are saved with the run and replayed for a co-op partner. Such a tower does not fuse.

Soldiers, a hero and powers (Kingdom Rush style)

  • A barracks. A tower level with soldiers keeps that many units at a rally point: the cell of the insects' walk nearest the tower within its range. On a fixed route that is the route; on the game's pit it is the maze's current path, and the posts follow the maze within a second of a tower going up or being sold. "fires": false keeps it from shooting.
  {"MachineGun": {"levels": {"1": {"fires": false,
    "soldiers": {"count": 3, "health": 70, "damage": 5, "rof": 0.9, "speed": 70,
                 "engage_range": 80, "respawn": 10}}}}}

A unit meets a walking insect that comes within engage_range of its post. On contact the insect stops and the two trade blows: the unit's damage every rof, and the insect's melee damage every second. That is a type's melee in waves.json, or allies.enemy_dps_per_life (4) for each life it would cost. One unit holds one insect; flyers and unrevealed camouflage pass. A fallen unit comes back after respawn seconds, and a sold barracks takes its units with it.

  • A hero (allies.hero in balance.json, the same keys plus name and regen) stands on the board from the first wave. It heals while idle. A click or A on it selects it (a green ring under its feet); WASD or the left stick then walk it, while the camera stays put, and it stays where it is let go. Escape, a right click or B lets it go.
  • Powers (allies.abilities), each with an id, name (text, a dictionary by language, or a localization key), icon (a game res:// path), cooldown and kind, and optionally:
    • requires_tower: the power belongs to that tower. It is cast only while one stands, from its action ring and its window, never the bar, F1-F4 or the wheel, and its damage counts as the tower's;
    • charge: each cast spends one of that factory material (the hangar's orbital_beacon and ranger_pod);
    • damage_wave_share (strikes): a share of the wave's insect health when that is more than damage.

Each kind:

  • strike hits every insect within radius for damage (hits_flyers, default true). Optional impact_delay (0–5 simulation seconds, default 0) defers that single hit until impact. Targets are evaluated at impact, so an insect can walk into or out of the circle. visual: "orbital" adds a conforming target marker, descending penetrator, flash, shock wave, soil, torn casing and smoke. Iron Kingdom uses a 0.9-second warning. Pause and game speed apply to both the animation and the hit; run reset cancels it. Legacy strikes without these fields remain immediate;
  • reinforce drops count units of unit at the point for duration seconds.

Cast them with F1 to F4, the bar at the bottom of the screen or the controller's radial, and aim with a click or A.

  • Models. Allies/Soldier, Allies/Reinforcement and Allies/Hero in models.json dress them; the game ships its own, and the hangar's Allies/Sentinel, Allies/Ranger and Allies/Commander. A unit spec's model picks any of them. They are cosmetic: a mod that only reskins them keeps the vanilla profile. Iron Kingdom supplies three industrial mechs with energy sabres. Its optional MechRig, stride and knee pivots add cosmetic walking and strikes; ordinary ally models keep the existing pose. Its hero's optional ViewWrist and ViewElbow markers under Weapon and Shield enable the shared mech layout's complete first-person arms. Place them at the wrist and elbow in the shoulder's local space, keeping rigid panels disconnected and the arm as one mesh under each shoulder. This presentation extension does not change the catalogue's mandatory pivots; older rigs without the markers keep their hand-only view. The orbital payload and casing are replaceable with Environment/OrbitalPayload and Environment/OrbitalFragment. See the orbital model contract.

Co-op halves (Bloons TD style)

"multiplayer": {"coop": {"build_zones": "left_right"}} (or "top_bottom") splits a co-op board between the two players:

  • The host builds on the left (or top) half, the partner on the other, and an amber line on the ground marks the split.
  • Each upgrades, sells and fuses only the towers on their own half. Both may look at and pilot any tower, and the lives stay shared.
  • A tower's income goes to the half it stands on. Without halves, each player gets half of every tower's income.

Towers that earn (income)

A level with "income": 40 (or a tier's income_add) pays that much gold at the end of every wave, one row per tower in the gold feed and the report.

A living route

"route_growth" in balance.json fixes the insects' way on the stock board and lengthens it after every wave. It has two modes.

"mode": "frontier": the board is built with the trail.

"route_growth": {"mode": "frontier", "start_cells": 6, "cells_per_wave": 3,
                 "margin": 1, "max_cells": 130, "after_wave": 1, "visible": true}
  • The run opens on start_cells cells straight out of the cave. The insects, flyers too, come out of the trail's far end (its head), not the nest.
  • After each wave from after_wave on, the head walks on cells_per_wave cells, up to max_cells. It winds: stretches of two to five cells, away from the walls, into ground the trail has not opened yet, and never into a pocket of its own windings while a roomier step exists. It never steps on a tower, rubble or wall, and never beside another stretch of itself.
  • Only the ground within margin cells of the trail is open (0 to 6, square). Every other cell of the pit floor lies under a rock pile, and a tower is refused there. When the trail reaches a pile, it sinks into the floor in a cloud of dust. The piles are the stock natural rubble models; they block nothing but placement.
  • When the pit has no room left for a step, the trail stops growing. On the stock pit it ends at about 50 to 100 cells.

"mode": "meander" (the default) keeps the open pit:

"route_growth": {"cells_per_wave": 6, "max_cells": 130, "start_cells": 0,
                 "depth": [1, 3], "after_wave": 1, "visible": true}
  • The run opens on the shortest walk from the nest to the cave (start_cells grows it at once).
  • After each wave it gains at least cells_per_wave cells: a straight stretch of three cells is pushed sideways into a U depth cells deep, leaving a free pocket between its two legs. Where you build, it cannot go.

In both modes:

  • The insects walk the trail cell by cell, a tower on it is refused, and it is drawn in full without a radar (visible).
  • Each growth draws from the run's seed and the wave alone. A Continue keeps the way the run grew; in co-op the host's way is the board's; each PvP board, bots included, grows its own around its own towers.
  • It applies only on a board without an authored Route, and never to the menu's replay.

Cores

"cores" in balance.json turns the lives into energy cores kept in the cave:

"cores": {"count": 20, "drift_speed": 25, "pickup_radius": 30}
  • The run starts with count cores as its lives; the top bar calls them cores.
  • An insect that reaches the cave takes a core, if one is left there, and walks home to the nest the way it came: backwards along a fixed route, through the maze otherwise. With none left it walks home empty-handed and costs nothing.
  • A carrier that gets home costs its core: one life.
  • A carrier killed on the way drops the core where it fell. A dropped core drifts back to the cave at drift_speed board pixels a second, and an insect passing within pickup_radius of it takes it home.
  • The cave's cores stand in rows before its mouth; a carried one rides on its carrier, a dropped one floats over the ground.
  • A card or repair that gives a life gives a core. A Continue keeps the lives, and the cores that were lying on the ground are back in the cave. In co-op the host counts them, as it counts lives.

The core has an authored GLB and a model slot, Environment/Core. Override it in models.json with {"file": "models/MyCore.glb", "material": "imported"}. It replaces stored, carried and dropped cores together. Centre the model at the origin, about 18 units wide and 20 units high, Y up; no named pivots are required. Embed its PBR textures in the GLB. The game supplies placement and rotation. This visual slot does not enable the gameplay rule by itself. example_cores includes an amber replacement and the full authoring contract.

example_banana_brigade uses the waves, layers, paths and co-op halves: fifty rounds of layered bananas and three paths for each primate. example_iron_kingdom uses the soldiers, the hero, the powers and level-4 specialisations. example_living_trail uses the living route, and example_cores the cores.

Total conversions

A mod that turns Torreta into another game -- its own towers, waves, board or rules -- says so in its mod.json:

{"id": "my_kingdom", "name": "My Kingdom", "version": "1.0.0", "total_conversion": true}
  • Only one plays at a time. Ticking one in Options → Mods unticks the one that was on, and the screen names it. When several are on at startup (two Workshop subscriptions, say), the one the player ticked plays and the others are switched off, so the list always shows what plays.
  • Visual and gameplay mods still combine with it: All resources on Banana Brigade, a cursor or a sound pack on any of them.
  • It is always a gameplay mod: its own profile, no achievements, XP, crystals or leaderboards, and every player online needs it.
  • The Mods screen badges it Total conversion and lists it under a filter of its own; a Workshop upload is tagged Total conversion.

The flag is declared, not worked out from the files: Banana Brigade rebuilds every tower and wave, Living Trail changes a single rule, and both are total conversions because each is a game of its own. Declaring it only limits the mod. The samples that carry it are Banana Brigade, Iron Kingdom, Traditional path, Living Trail and Cores.

Skill tree node art

The image on a skill-tree node is replaceable two ways, and a mod usually wants the second one without knowing it.

  1. Name your own file. Patch PurchasableStats.<node>.icon in the mod's map_data.json with a path relative to the mod, e.g. Assets/skill_dmg.png (example_balance_towers does this for Dmg+). Relative asset paths in a patch resolve to the mod's folder, new keys included.
  2. Replace the tower and get its node for free. A mod that swaps a tower's GLB has its icons re-baked and registered under the VANILLA path, and the tree asks for that path -- so Gun3 on the tree follows the Gun you shipped without a line of map_data.json.

Both go through GameData.load_tower_icon, which asks the mod first and falls back to the file. The re-bake wins over a named file when both exist.

Data changes and precedence

Files merge recursively; arrays replace the whole array (the card pool has an addition by id, below). Missing fields inherit base values, and keys starting with _ are comments. Asset paths under Assets/ are resolved relative to the mod, including inside nested arrays/dictionaries. Use res:// only to refer to a known built-in asset.

Active mod folders (local and Workshop together) load alphabetically. Later folders win conflicts. IDs must be unique. Disabled mods and folders starting with _ are ignored. Invalid GLBs are reported in the game log and through the mod-error signal; the previous valid override or vanilla asset remains usable.

To scale a factory batch through a balance patch, use factory.recipe_output_multipliers, for example {"factory":{"recipe_output_multipliers":{"ammo_2":2}}} in balance.json. This produces 40 liquid rounds from the existing 20-round recipe, retaining its ingredients and work time. Output quantities round to positive integers. The default is 1; invalid/nonpositive multipliers are ignored. Other recipes keep their own outputs, and profile switching does not mutate material_data.json.

The only shipped balance is the promoted v29 profile (internal ID experimental); a save pins its profile. A mod supplies balance.json patches over it:

  1. tower_data.json and map_data.json patches merge over base definitions.
  2. The selected balance profile plus mod balance.json patches supplies final tower towers.stat_overrides and skill-tree costs/limits/minimum levels.

Therefore use balance.json.towers.stat_overrides.<Tower>.<Level> for final numerical tower changes. A tower_data number may otherwise be replaced by the selected profile. Cards live in balance.json.cards, not a separate card file.

Adding a card without restating the pool. cards.pool.<group> is an array, so writing it replaces the game's whole group. cards.pool_add.<group> lists cards by id instead: each joins that group, or replaces the game's entry with the same id, and several mods add side by side.

{"cards": {"pool_add": {"tower_cards": [{"id": "unlock_hangar", "rarity": "rare", "min_wave": 20}]}}}

The current balance also exposes cockpit, multiplayer, time of day, fusion, elemental combos, champions/mutations, factory tiers/production and barracks. Refer to the real keys and comments in the reference instead of inventing keys.

Tower maintenance uses economy.tower_upkeep: start_wave, free_towers, fraction_per_tower and max_fraction. The optional fixed_per_tower_per_kill defaults to zero and adds an operating cost for each tower above the allowance. For an ordinary kill, it adds that many gold per excess tower; actual reward bonuses and reduced minion rewards scale the amount proportionally to the wave's ordinary reward. The percentage plus fixed component is capped by max_fraction of the actual kill reward. A cap above 1 allows maintenance to consume banked gold, down to zero. This option does not change placement prices or charge idle towers between kills.

vanilla_reference/balance.json is the promoted v29 default (its internal profile ID remains experimental for save compatibility); balance_rebalanced.json is v3 for comparison only (rename patches to balance.json to load them). The other reference JSONs mirror their shipping definitions. Generate them after source changes with python3 tools/update_mod_reference.py.

Renamed keys. cockpit.dano_base_pct and cockpit.coop_dano_base_pct are now cockpit.damage_base_pct and cockpit.coop_damage_base_pct. Every key a mod can reach is English; these two had carried a Portuguese name over from v3. The old spellings are still read, so a mod published against them keeps working, but they no longer appear in the reference and new mods should use the English ones. balance_rebalanced.json (v3) is frozen and still shows the old name.

enemy_data.wave_colors is legacy visual metadata; it does not recolor modern GLBs. Replace the corresponding Enemies/<family> model. scene_mapping names existing game behaviors, not mod scripts. Data/ant_species.json is an authoring catalog; mods do not add species or change the five-wave family cadence with it. New code, new tower behaviors, networking, UI layout and input logic are outside the declarative mod API.

Multiplayer / Workshop

Only gameplay mods have to match online. A visual mod (cursors, sounds, birds, snakes, microfauna, buildings, factory pieces, markers and the rest of the scenery; see the Mods screen's Visual badge) keeps nobody out: a player with only visual mods plays with players without mods or with other visual mods, and each sees their own art. Gameplay mods are matched by IDs, versions, raw content hashes and effective order, so changing a GLB, JSON, image, audio or shader of one changes the signature even if the author forgets to bump the version; every seat needs the same gameplay mods in the same order. Older game builds use an older signature format and are not compatible. This is a local compatibility check, not a claim that a Workshop upload or online match has been tested.

PvP bots

The host's game fills a PvP lobby's empty seats with bots, which were tuned for the vanilla rules. Visual mods keep them. A gameplay mod has no bots unless it ships a bots.json with "enabled": true, which suits a mod that changes numbers but not mechanics (example_balance_hard_mode); the same file may give the bots its own mazes and card values. See BOTS.md.

Publishing on the Steam Workshop

Publish from the game, with Steam running: Options → Mods, then Publish at the right of your mod's row (on a controller, right from the row, then A).

  • The form starts with the name and description of your mod.json. Add a change note for subscribers and choose the visibility (Public, Friends only, Private or Unlisted).
  • The first publish creates a new Workshop item and writes its id to workshop_item.json beside your mod.json. Keep that file: every later Update goes to the same item, and subscribers receive it automatically. Delete it only to publish the folder as a separate item.
  • A preview.png (or .jpg/.gif, up to 1 MB) beside mod.json becomes the item's picture.
  • The upload is a copy of the folder without Blender backups (.blend1), .import files, programs or scripts (.exe, .dll, .so, .dylib, .sh, .bat and similar), hidden files and workshop_item.json. Include the raw GLBs and their JSON mapping; a .blend source goes up too, for others to learn from (the game never loads it), so leave it out if you want to keep it.
  • The item is tagged Gameplay or Cosmetic, by the same rule the Mods screen's filter uses.
  • A first item may ask you to accept the Steam Workshop agreement before other players can see it: the form shows the button.

Workshop copies (workshop_<id>) belong to their authors and offer no Publish. Neither do Torreta's own samples, whose ids start with example_: to start a mod from one, copy its folder and give mod.json an id of your own. To change someone else's mod, copy its folder under a new name and publish that.

Authors who prefer a command line can publish with Valve's SteamCMD instead, from an account that owns Torreta and has accepted the Workshop agreement, using a my_mod.vdf like this one (publishedfileid 0 creates the item; put the id SteamCMD reports there to update it later; visibility 0 is public, 2 private):

"workshopitem"
{
  "appid"           "2998660"
  "publishedfileid" "0"
  "contentfolder"   "C:/Mods/my_mod"
  "previewfile"     "C:/Mods/my_mod_preview.png"
  "visibility"      "2"
  "title"           "My mod"
  "description"     "What it changes"
  "changenote"      "First version"
}
steamcmd +login <account> +workshop_build_item C:/Mods/my_mod.vdf +quit

Legacy shader compatibility

Existing Shaders/*.gdshader canvas-item replacements, legacy building sheets, and baked 2D wall/tower icons remain supported for their old consumers. They do not replace the meshes shown by Full 3D. Spatial shader replacement is not a public mod hook; choose imported GLB materials or the game material mode.

Runtime model import uses Godot's GLTFDocument. Runtime WAV support uses AudioStreamWAV.

Final commander and camouflaged waves

See _example_final_encounters for the final boss's model/statistics, late-wave schedules per difficulty, camouflage material, detection level and the run-only epic Adaptive Optics card. None of these requires lowering the permanent skill-tree unlock levels.