The Data module (F7) changes what things are for the length of one match.
Give a crossbowman more range, make a farm cost nothing, clone the ironguard
into a new unit with different armour and a different name. It is the
object editor of this engine.
A change like this is called a data override. Overrides are stored in the
map under defOverrides, and a mod can carry the same
thing to apply on top of any map.
What can be overridden
Key in defOverrides | What it changes | Edited in the Data module? |
|---|---|---|
units | Unit definitions | Yes |
buildings | Building definitions | Yes |
items | Item definitions | Yes |
spells | Spell definitions | Yes |
upgrades | Research and tech upgrades | JSON only |
heroes | Hero definitions (their id field is heroId) | JSON only |
sounds | Custom sound ids for playSound | JSON only |
models | Named model URLs for mod: references | JSON only |
"JSON only" means the Data module's panels do not have a form for it yet. Author it in a file and bring it in with File → Import from AI / JSON… (the Merge Data Overrides mode), or ship it in a mod.
Patch or clone
Every entry has an id and a patch, and optionally a baseId.
A patch has no baseId. It changes the existing definition with that id.
Everything you do not mention stays as it is.
{
"units": [
{ "id": "dwarf_crossbowman", "patch": { "range": 8, "cost": { "gold": 90 } } }
]
}A clone has a baseId. It creates a new definition with the new id, copies
everything from the base, then applies the patch. The new id can be placed in
the palette and referenced by triggers like any other.
{
"units": [
{ "id": "dwarf_marksman", "baseId": "dwarf_crossbowman", "patch": { "name": "Marksman", "range": 10, "hp": 60 } }
]
}Field names are the game's own. For units the combat fields are hp,
damage, armor, range, attackSpeed and speed. Buildings use
attackDamage and attackRange instead. When in doubt, clone something in the
Data module and look at the field list it shows you. The validator checks patch
field names against the fields the definition really has: a misspelled field is
an unknown-patch-field warning that names the closest real field, so rnage
comes back as "did you mean range?". It stays a warning because the entry still
installs; the unknown field is simply ignored.
A clone is not automatically trainable. Something has to produce it: add it to
a building's produces list (see the $append example below), or create it
with a trigger.
Clones of clones
A clone's baseId can name another clone, which can name another, up to 16
clones deep. Each clone starts from its base exactly as the match sees it,
patch included, so a change to a clone carries down to every clone built from
it that does not change the same field again.
{
"units": [
{ "id": "frost_guard", "baseId": "dwarf_ironguard", "patch": { "name": "Frost Guard", "armor": 8 } },
{ "id": "frost_captain", "baseId": "frost_guard", "patch": { "name": "Frost Captain", "hp": 900 } }
]
}frost_captain has the Frost Guard's armour of 8 and its own 900 HP. Anything
neither of them mentions comes from the ironguard.
A clone of a shipped definition is different: it starts from the definition
as the game ships it, and does not pick up a patch that the same set makes to
that definition. Patching dwarf_ironguard's armor changes every ironguard in
the match, but not frost_guard. To have clones follow a change, make the
change on a clone and build the others from it.
The order the entries are listed in does not matter. Nor does which file a clone came from: a mod can clone a unit that the map or another mod added, and it works in any match where both are loaded. Validated on its own, that mod reports the base as unknown, because the validator only sees the one file.
A chain that loops back on itself (a based on b, b based on a), a chain
more than 16 clones deep, and a clone built from a clone that has an error are
all errors. None of the clones involved is added to the match.
How a patch merges
- Numbers, strings and booleans replace.
- Nested objects merge deeply.
"cost": { "gold": 90 }changes the gold cost and keeps lumber, stone and oil. - A plain array replaces the whole array.
"produces": ["dwarf_miner"]means exactly that one entry, not "add this one". That is deliberate: a silently appended list is harder to notice than an obviously replaced one.
To edit a list instead of replacing it, use an operator object, an object
whose keys all start with $:
| Operator | What it does |
|---|---|
$append | Add elements to the end |
$prepend | Add elements to the front |
$remove | Drop elements equal to, or whose id is, a listed value |
$replaceById | Swap the element with the same id (adds it if absent) |
$idKey | Which field counts as the id (default: id, abilityId, heroId, spellId, unitTypeId, typeId or level) |
$replace | Replace the value wholesale, objects included |
$unset | Delete the field ({ "$unset": true }) |
$ops | Apply several patch values in order |
List operators apply in a fixed order: remove, then replace-by-id, then
prepend, then append. Mixing $ keys with ordinary keys in one object is an
error, because it is never clear which one you meant. This makes the marksman
clone from above trainable at the barracks:
{
"units": [
{ "id": "dwarf_marksman", "baseId": "dwarf_crossbowman", "patch": { "name": "Marksman", "range": 10, "hp": 60 } }
],
"buildings": [
{ "id": "dwarf_barracks", "patch": { "produces": { "$append": ["dwarf_marksman"] } } }
]
}A unit's spells
A unit's spell list is edited with patch.spells, as a plain array or with
operators. A clone inherits its base's spells. This is from the bundled
sos_frost_warden sample mod, which swaps a runesmith clone's spells for one
new spell:
{
"spells": [
{ "id": "frost_warden_glacial_bolt", "baseId": "rune_blast", "patch": { "name": "Glacial Bolt", "damage": 45 } }
],
"units": [
{
"id": "frost_warden",
"baseId": "dwarf_runesmith",
"patch": {
"spells": {
"$remove": ["rune_blast", "flame_lance", "rune_burn", "anvilfall", "rune_of_collapse"],
"$prepend": ["frost_warden_glacial_bolt"]
}
}
}
]
}A caster has six spell hotkeys; more than six is a warning, not an error, and the extras are click-only.
Upgrades and heroes
Upgrades are cloned and patched like anything else. This adds a new armour
research to the dwarf barracks (from the sos_balance_ironclad sample mod,
trimmed):
{
"upgrades": [
{
"id": "dwarf_tempered_plate",
"baseId": "dwarf_attack_1",
"patch": { "name": "Tempered Plate", "researchedAt": "dwarf_barracks", "cost": { "gold": 125, "stone": 25 } }
}
],
"buildings": [
{ "id": "dwarf_barracks", "patch": { "researches": { "$append": ["dwarf_tempered_plate"] } } }
],
"heroes": [
{ "id": "borin", "patch": { "statOverrides": { "hp": 100 } } }
]
}What gets checked
The validator runs on every override set, in the editor, in the validation tools and on publish.
- Shape. Every entry needs an
idand apatchobject. A missingpatchis an error, and the entry is not installed. - Ids. An
idorbaseIdmust be 1 to 64 letters, digits,_or-. - Reserved names.
__proto__,constructorandprototypeare errors wherever they appear: as an id, as a patch key at any depth, or as a sound or model key. - References. An unknown
baseId, and any id a patched field names (aproducesentry,requires,researches,researchedAt, a spell, a hero's base unit) is an error with the closest real id suggested. A clone may reference another clone in the same set, and may be based on one. - Clone chains. A
baseIdloop, a chain more than 16 clones deep, and a clone built from a clone that has an error are errors (see Clones of clones). - Assets.
spriteKey,iconKeyandportraitKeymust name art the game already ships; an unknown key is an error, because it would draw an invisible unit.modelIdis not checked. A custom sound must be anhttps://URL or adata:URI; anhttp://sound is an error. - Unknown sections (a key other than those in the table above) are a warning.
- Patch field names. A field the definition does not have (
rnageforrange), at the top of a patch or one level down, is anunknown-patch-fieldwarning with the closest real name, and a hint when the field belongs to another kind (attackDamageis a building field, not a unit field).
An entry with an error is skipped when the match starts rather than installed half-formed.
Art and models
A custom definition can reuse any shipped art through spriteKey,
portraitKey and iconKey. A clone that names none of them draws its base's
sprite, portrait and icons throughout the game, which is usually what you
want; see
sprite art for a clone.
A clone of a clone works the same way up its chain: each of its sprite, portrait
and icon comes from the nearest clone that names one, and otherwise from the
shipped definition at the top of the chain. Its 3D model is likewise the nearest
modelUrl up the chain, downloaded once however many clones share it. If a
clone sets its own modelUrl while a clone above it set animationsUrl, set
animationsUrl too (to the same URL, if the new model carries its own animation
clips); otherwise it plays the other model's clips, and the validator warns.
It can also point at a 3D model hosted elsewhere with modelUrl and
animationsUrl, or at an entry in the set's models map with
"modelUrl": "mod:<key>". Models are limited to 40,000 triangles and 2048px
textures; anything over the limit, or anything that fails to load, falls back
to the unit's shipped sprite and never affects the match itself. Players can also
turn off third-party models in their Graphics settings. The whole story is in
Custom Units & Models.
When overrides are applied
Overrides install once, immediately after the map is read and before anything spawns. That ordering matters because unit health and damage are copied onto each unit when it spawns, so a unit created before the override installed would carry the old numbers for the rest of the match.
They are a per-match layer that the game consults, never an edit to the shipped tables. Those tables are shared by every match in the browser tab and by the campaign, so changing them would leak your map's balance into the next match played in that tab. The layer is cleared when the match ends.
Map-level rules
A few settings that feel like data live on the map instead, in rules and
meta. The other map rules are covered in
Players & Teams; these are set in
the JSON:
rules.allowedBuildings: the only building ids players may construct. The game refuses any other building, not just the build menu; an empty list allows none. It does not limit in-place upgrades (a town hall to its next tier), and computer seats are not restricted by it.rules.disableAi: computer seats do not run their economy or build a base, so they can act purely as trigger-driven waves or bosses.rules.city: runs a city on the map, as Cities of Stone does, andrules.city.tuningchanges the city's own numbers (services, needs, taxes, demand, tiers, seasons, which hall opens which tool, which peoples may come) for this map alone. Build a city builder lists every field.meta.loadingScreen: the image shown on the loading screen of a custom map. It must be a path to an image the game already ships, such as/assets/artwork/splash_runesmith.png. Anything else, including a URL on another site, shows the default loading art.
What to change and what to leave
Patch rather than clone when the change is a number. A patched crossbowman still reads as a crossbowman to a player who knows the game, and the tech tree, the AI's build orders and the tooltips all keep working.
Clone when the change is an identity. If it has a different role, a different name and a different place in the tech tree, it should be a different unit rather than a familiar one that behaves unexpectedly.
Next: Cutscenes.