Map Editor Documentation

Data Overrides

Per-map changes to units, buildings, items, spells, upgrades and heroes: patching, cloning, merge rules and array operators, and what gets checked.

Units & data · Page 11 of 49

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 defOverridesWhat it changesEdited in the Data module?
unitsUnit definitionsYes
buildingsBuilding definitionsYes
itemsItem definitionsYes
spellsSpell definitionsYes
upgradesResearch and tech upgradesJSON only
heroesHero definitions (their id field is heroId)JSON only
soundsCustom sound ids for playSoundJSON only
modelsNamed model URLs for mod: referencesJSON 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 $:

OperatorWhat it does
$appendAdd elements to the end
$prependAdd elements to the front
$removeDrop elements equal to, or whose id is, a listed value
$replaceByIdSwap the element with the same id (adds it if absent)
$idKeyWhich field counts as the id (default: id, abilityId, heroId, spellId, unitTypeId, typeId or level)
$replaceReplace the value wholesale, objects included
$unsetDelete the field ({ "$unset": true })
$opsApply 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 id and a patch object. A missing patch is an error, and the entry is not installed.
  • Ids. An id or baseId must be 1 to 64 letters, digits, _ or -.
  • Reserved names. __proto__, constructor and prototype are 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 (a produces entry, 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 baseId loop, 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, iconKey and portraitKey must name art the game already ships; an unknown key is an error, because it would draw an invisible unit. modelId is not checked. A custom sound must be an https:// URL or a data: URI; an http:// 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 (rnage for range), at the top of a patch or one level down, is an unknown-patch-field warning with the closest real name, and a hint when the field belongs to another kind (attackDamage is 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, and rules.city.tuning changes 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.