Map Editor Documentation

Mods

What a .sosmod package is, the two sample mods line by line, applying a mod in single player, multiplayer and by link, dependencies, and exporting one from The Forge.

Mods & assets · Page 16 of 46

A map is a playable scenario: terrain, placed units, players, triggers. A mod is a portable package of changes that applies on top of a map: data overrides, triggers, custom sounds and 3D model references, with no terrain and nothing placed. One balance mod can be played on every map; one map can be played with or without it.

A mod is a single JSON file with the extension .sosmod.

What is in a .sosmod

This is a complete, valid package with the fields you will normally set, and the smallest content that does something.

{
  "$schema": "https://www.shardsofstone.com/schemas/v1/mod-package.json",
  "formatVersion": 2,
  "id": "my_balance_patch",
  "name": "My Balance Patch",
  "version": "1.0.0",
  "author": "Your Name",
  "description": "Cheaper goblin brawlers.",
  "minEngineVersion": "0.3.0",
  "dependencies": [],
  "defOverrides": {
    "units": [{ "id": "goblin_brawler", "patch": { "cost": { "gold": 55 } } }]
  },
  "triggers": { "version": 1, "vars": [], "regions": [], "triggers": [] },
  "sounds": {},
  "models": {}
}

A triggers value is a whole trigger script, so even an empty one needs version, vars, regions and triggers.

FieldRequiredMeaning
idYes1–64 characters: lowercase letters, digits, _ and -, starting with a letter or digit
nameYesDisplay name
versionYesMAJOR.MINOR.PATCH. Anything else is a warning, and breaks version ranges in dependencies
authorYesWho made it
descriptionNoShown with the mod
formatVersionNoWhich version of this file format it uses. Absent means 1; current is 2
minEngineVersionNoThe oldest game version that can load it
dependenciesNoOther mods that must be applied first, see below
defOverridesNoData overrides
triggersNoA trigger script
soundsNo{ "id": "https://…" or "data:…" } for playSound
modelsNo{ "key": "https://…" or "data:…" }, referenced as "modelUrl": "mod:<key>"
tags, category, homepage, createdNoMetadata

Unknown top-level fields are rejected by the JSON Schema. A package with no overrides, triggers, sounds or models is valid but gets a warning, since it does nothing.

Size. A package can be at most 4 MB, and a single embedded data: URI in the top-level sounds or models at most 256 KB. Host larger models and sounds on an https:// server and reference them by URL instead.

Sounds and models. Each value in the top-level sounds and models is an https:// URL or a data: URI; an http:// URL is an error. Keys are letters, digits, _ and -, and never __proto__, constructor or prototype.

The two sample mods

Both ship with the game in public/mods/, so you can read them, validate them and play them.

sos_balance_ironclad

A small balance patch that shows each kind of change:

  • Patches dwarf_ironguard to 70 HP and 4 armour, and goblin_brawler to cost 55 gold. The brawler patch is { "cost": { "gold": 55 } }, and because objects merge deeply, its lumber, stone and oil costs are untouched.
  • Clones a new upgrade, dwarf_tempered_plate, from dwarf_attack_1: +1 armour for ironguard and royal guard, researched at the barracks for 125 gold and 25 stone.
  • Appends that upgrade to the barracks' research list with { "researches": { "$append": ["dwarf_tempered_plate"] } }, so it appears without replacing the barracks' existing research.
  • Patches a hero: borin gets statOverrides.hp of 100.

sos_frost_warden

A content mod that adds a unit, and depends on the first mod:

  • "dependencies": [{ "id": "sos_balance_ironclad", "version": "^1.0.0" }].
  • Models: two entries in models, frost_warden_body and frost_warden_anims. (They point at the runesmith's own model files on the game's CDN, so the sample works anywhere; your mod would point at your own.)
  • A sound: frost_warden_cry, a URL to an .mp3.
  • A spell clone: frost_warden_glacial_bolt from rune_blast, 45 damage, 30 mana, on the q hotkey.
  • A unit clone: frost_warden from dwarf_runesmith, 60 HP, 180 gold, with "modelUrl": "mod:frost_warden_body" and a spells patch that removes the runesmith's spells and prepends the glacial bolt.
  • Trainable: the runic library's produces gets { "$append": ["frost_warden"] }.
  • A trigger: on map initialisation, once, show "The Frost Wardens have answered the call…" and play frost_warden_cry.

Applying a mod

A mod is applied when a custom map is launched. It is never baked into a map you save.

  • The Mod picker. The map browser and the multiplayer lobby's custom-map panel have a Mod control: Load .sosmod to pick a file, Clear to remove it. The chosen mod stays active for the browser tab.
  • A link. /play?customMap=<map>&mod=<id> or /play?communityMap=<id>&mod=<id> loads the mod from /mods/<id>.sosmod on the site. That only works for mods hosted on the site, such as the two samples: /play?customMap=tower-defense-showcase&mod=sos_balance_ironclad. A mod value that is not a valid mod id is ignored without fetching anything.
  • The validator. /tools/validator can test a mod directly: paste it, and Test in Game launches it on a blank map.
  • An embed. A page embedding the game can send a mod with its map; see Web APIs.

Mods only apply to custom maps. The campaign and ordinary skirmish are never modded.

In multiplayer

Only the host needs the mod. The host's game resolves the mod and its dependencies, merges them into the map, and sends the merged map to everyone along with a checksum of the map and the mod's id and version. Every player's game checks that checksum before the match starts, and again at the start signal. If anything differs, the match does not start, and the message says Map integrity check failed with both checksums, rather than the match desyncing later.

Guests see a read-only Mod: name vversion line in the lobby.

How a mod merges into a map

Mods apply in order: dependencies first, then the mod itself, each on top of the result so far.

  • defOverrides. Entries are matched by id within each kind. A mod entry for an id the map already overrides is applied after the map's patch, as if both patches ran in sequence. If the mod gives that id a different baseId, the mod's entry replaces the map's entirely.
  • triggers. Triggers and regions are matched by id; variables (vars), functions and hashtables by name. A mod entry with the same id or name replaces the map's whole entry; anything else is added, and the map's other entries are kept.
  • sounds and models merge by key, the mod winning.

Dependencies and versions

A dependency is { "id": "...", "version": "..." }. The version accepts:

RequirementMatches
1.2.3Exactly 1.2.3
^1.2.01.x.x, at least 1.2.0 (for 0.x, the same minor)
~1.2.01.2.x, at least 1.2.0
>=1.2.01.2.0 or anything newer
*Any version

When a mod launches, its dependencies are loaded from /mods/<id>.sosmod. A missing dependency, a version that does not match, or a cycle refuses the launch with a message naming the problem, and so does the same problem in a dependency's own dependencies. The validator reports all of these before you ship, as long as it has the packages involved (see below).

Using what a dependency defines

Dependencies apply first, so a mod can build on their content: copy a dependency's unit ("baseId": "their_unit"), train it, cast its spell, research its upgrade, use a model from its models as mod:<key>, or create its unit and play its sound from a trigger. A dependency's dependencies count too. Copies of copies work across packages with the same limits as inside one package: no loops, and at most 16 copies deep.

The validator checks each such name against the dependency itself when it has that package, merged exactly as the match merges them. When it does not, it says what it could not check:

The name is…Result
defined by a dependency the validator hasChecked for real, like anything in the mod itself
not found, and a declared dependency is not availableWarning: "provided by dependency 'x' — not checked"
not found, and no declared dependency is missingError: the id is unknown. If another package the validator has defines it, the suggestion is the dependencies entry to add

A warning means the mod may be fine, but nothing about that name was checked. To check it, give the validator the dependency: name it on the command line with your mod (npx tsx tools/validate.ts my_mod.sosmod their_mod.sosmod), put their_mod.sosmod beside your file, add it in the web validator, or send it in dependencies to the HTTP endpoint. Mods hosted in the game's /mods/ folder are found automatically by the command line and the web validator. See Validation & Tooling.

minEngineVersion is compared with the game's version. An older game refuses the package with "This package needs game version X or newer".

See Compatibility & Versioning for how versions should move when you change a mod.

Making a mod in The Forge

  1. Build the changes on a test map: data overrides in the Data module (F7), triggers in the Trigger module (F6).
  2. File → Export Standalone Mod (.sosmod)… writes a package of the map's data overrides, triggers, sounds and models. The id comes from the map name (lowercased, other characters turned into _), with version 1.0.0 and author Forge Modder.
  3. Open the file in a text editor and set id, name, version, author and description properly. Add dependencies and minEngineVersion if you need them.
  4. Validate it: npx tsx tools/validate.ts my_mod.sosmod, or paste it into /tools/validator.

The export takes the map's whole trigger script, regions included, because triggers reference regions by id. Region coordinates come from your test map, so a mod whose triggers use regions only makes sense on maps laid out the same way. When the mod is applied, a region with the same id on the target map is replaced by the mod's copy. (The trigger script is only exported when it has at least one trigger or variable.)

To bring a mod's changes into a map you are editing, use File → Import Standalone Mod (.sosmod)…. It merges the package's data overrides, triggers, sounds and models into the map by the same rules, so mod:<key> model references keep working in the saved map, in Test Map and when published.

Sharing a mod

There is no mod upload or mod gallery yet. Share the .sosmod file however you share files; players load it with Load .sosmod. Mods hosted in the game's own /mods/ folder, which today means the two samples, also work with ?mod=<id> links.

Next: Compatibility & Versioning.