Home › Modding Info › Make a mutator
Chapter 03

Make a mutator

A small .oamut layered over the base game or any mod: the folder, the manifest, the plan mutate returns, options, the sandbox and the order a stack applies.

This chapter summarises docs/mutators/authoring.md and docs/mutators/oamut-standard.md.

What a mutator is

A mutator is a small .oamut package layered over the base game or any mod. It changes one match. The Library installs it into the Mutators folder under the manifest's id, and installing does not switch it on. The player switches it on for a skirmish, a battle room or a campaign. works-with says which mods it fits, and a star means any mod and the base game.

The package

The top of the folder holds oamut.yaml. The entry script sits beside it: mutator.lua, unless entry names another, and never under files/, rules/ or lua/. lua/ holds further Lua source, the only files require reads. files/ holds the bytes the Files layer may add. rules/ holds rule files whose names end in .sm. An icon is the PNG the manifest names. A note whose name ends in .md or .txt is carried for people, and the game does not load it. A file outside that list is refused. The comparison ignores the case of ASCII letters.

my-mutator.oamutthe file a player gets├─ oamut.yamlrequired the manifest├─ mutator.luathe entry script, mutate(ctx)├─ lua/optional; Lua modules, the only files require reads├─ files/optional; models, scripts and other bytes├─ rules/optional; .sm rule files├─ icon.pngoptional; the PNG named by icon└─ README.mda note; the game does not load it

The manifest

Strict YAML, the same rules as a mod profile: one document, one top-level mapping, no anchors and no block scalars. The file starts with oamut set to the integer 1. A key the standard does not list is refused. Quote a value that starts with a greater-than sign, and quote a star, or the reader takes them for something else. requires.engine is quoted for that reason. A version is quoted too: a plain number is refused.

KeyRequiredWhat it does
oamutyesThe format. The integer 1. 1.0, and a quoted 1, are refused.
idyesLower-case kebab-case, 1 to 64 bytes. No leading, trailing or doubled hyphen. The install folder's name.
nameyes1 to 64 bytes, no control character. The name players see.
versionyesQuoted, at most 32 bytes. One to three parts joined by a dot, each a number from 0 to 65535 with no leading zero.
authoryesA mapping. name is required, 1 to 128 bytes. email is optional.
descriptionnoOptional, 1 to 2,000 bytes. A line feed is allowed. The first line, cut to 200 bytes, is the summary the catalogue shows.
homepagenoOptional, 1 to 256 bytes. An http or https address.
iconnoOptional. The package path of a square PNG, ending in .png. The installer checks the picture.
tagsyesOne to four ids, none repeated. The eight the Library draws are below.
works-withyesOne to 32 conditions, no id twice. A star means any mod and the base game, and it is then the only entry. ta-3.1c means the base game with no mod. Any other id is a mod, with an optional version range.
requiresnoOptional. Only engine and mutators. engine is a quoted range against this build. mutators names packages that must be on; they apply before this one, in the order written.
conflicts-withnoOptional. Mutators this one refuses to run beside. An id here is not one requires.mutators names, and not this mutator's own id.
layersyesOne to nine names, none repeated: files, units, weapons, features, sides, tables, menus, maps and hacks. A plan may write only a layer named here.
clashesnoOptional, add or replace: what this mutator does with a unit or weapon whose name is already in the game. The player's choice comes first, then this key, then the preference mutators.clash-default, which is replace when unset.
optionsnoOptional, at most 16. Settings the player picks before the match. ctx.options reads them.
stringsnoOptional, at most 16 language tags. Translations of the name, the description and the option labels.
entrynoOptional. The entry script, a package path ending in .lua and not under files/, rules/ or lua/. mutator.lua when left out.
legacynoOptional, 1 to 16 records of TA: Mutation mutators this package replaces. Each record is name, prefix, compatibility and files. A bare mapping is refused.

The eight tags

The reader accepts any lower-case kebab-case tag of 1 to 32 bytes, at most four. The Library draws an icon and a translated label for these eight ids, matched exactly. Any other id is shown as it was written, with no icon. When the manifest names no icon, the badge uses the first tag's icon when that id is one of the eight.

KeyWhat it does
statisticsStatistics
economyEconomy
commandersCommanders
new-unitsNew Units
disableDisable
gameplayGameplay
aiAI
mapsMaps

The plan

The entry defines mutate. The engine calls mutate(ctx) once, in the plan phase, and that call does not change the game by itself. mutate returns one table. An empty table changes nothing. A key the engine does not know is refused.

files is a table of add and mask. Each is a list of text, and either may be left out. add names folders in the package, in order, and a later folder wins a path both hold. mask hides paths. rules is a list of finished blocks, made with where, all, named or on and then :change, and of sm paths that name a .sm file. A change is set, scale, lock, remove or append. scale can take a low and a high bound, and that bound clamps only that one factor. The hooks are functions. They run in this order: units, weapons, features, sides, tables, then menus. maps runs after every mutator, and only when the match has chosen a map. hacks sets, for this one match, a parameter the registry opens to mutators. Declaring the layer is what allows the write.

Options and ctx

During mutate, ctx holds the mod (its id, version and game), the engine version, the options and ctx.incompatible. ctx.mod.id is ta-3.1c when no mod is on. Calling ctx.incompatible with a reason stops this mutator, and the reason is the text the player sees. The collections are not loaded yet. A hook may read units, weapons, features, sides, tables and menus on ctx without declaring the layer. Writing one needs the layer.

ctx.options holds one field per option id. A toggle is true or false. A number, and a choice whose values are numbers, is a number. A choice whose values are identifiers is text. An option the player left is the default. type is toggle, choice or number. A choice lists 2 to 16 values, all numbers or all identifiers, and labels when the words differ from the values. A number has min, max, step and default. The player can pick only a value from min to max that lies on a step, so the script need not test the range again: a value off a step is refused before the script runs. The option id is a Lua name, and a Lua keyword is refused.

The sandbox

The script runs as Lua in one sandbox. It has no file, no network, no clock, no environment and no process. io, os, debug, package and coroutine are not opened, and a precompiled chunk is refused. Every chunk is source. Two mutators share no state.

Each phase starts again from the full instruction allowance, 50,000,000, and from the full real-time limit of 10 seconds. The memory cap is 64 MiB and is not handed out again for the next phase. One string is at most 65,536 bytes. Use fixed for a stat the script computes. It counts 65,536ths, so the same script writes the same text on every machine. pairs walks keys in a fixed order. The result does not depend on the machine.

Stacking

The player lists mutators in an order, applied top to bottom. The first applies first. Each later one sees the game as the ones above it left it. Mutators named in requires.mutators are switched on with this one and apply before it, in the order that list writes them. More than 32 mutators, an id listed twice, a requirement nothing above it meets, or two that conflict, refuses the whole list, and nothing runs.

Every mutator's files are layered first, in list order, and a later mutator wins a path. Then each mutator, in that same order, reads its own unit, weapon and download files as records, applies its rules, then runs its hooks. Rules apply one change at a time, in a fixed order. A factor multiplies the current number, and a clamp bounds only that factor, not a later one. An assignment replaces the current value. A lock sets the value and keeps every later mutator from changing that field. Inside one Lua block the changes run by field name after ASCII lower-casing, not in the order the table was written. There is no averaging between mutators. After every mutator has applied, a unit or weapon number past the width the game stores, and a feature number past the range the game reads, is held at the end of that range, once.