Skip to content

Creating TOML content

Author shard content in TOML files under templates/ in your server root. The server reads these files at startup; adding content to an existing directory requires a restart. mgctl copies the shipped templates into that root. You can add your own files alongside them with unique ids. Basic TOML knowledge is enough; TOML value types explains dice, ranges, hues and enums.

Use a game or standalone shard and a character with GameMaster or Administrator access. Create templates/items/tutorial_blue_stone.toml relative to the server root:

[[item]]
id = "tutorial_blue_stone"
name = "blue greeting stone"
item_id = 0x0ED4
hue = 0x0055
movable = true
weight = 1.0
  1. Restart the server. It must finish startup without a template error.
  2. In the game client, say .add tutorial_blue_stone and target a nearby ground tile. The command should report Added blue greeting stone with its serial and location.
  3. Single click the stone to see its name, then drag it into your backpack. It has no custom double-click action yet; Item scripts adds an interaction through a separate, complete tutorial.

add explains permissions and targeting. If it reports Unknown item template, check the file path, [[item]], the exact id and startup errors. Editing a template or running script reload does not replace a restart. For an NPC use a file under templates/mobiles/ and the spawn command. The reference below lists the TOML keys you can add.

Items, mobiles, loot and carve tables, NPC lists, spawn regions and decorations load at game and standalone startup. Shard data files covers the separate files under data/. Migrate from UOX3 converts existing content. See Implementation status for remaining limits.

The server reads every *.toml under templates/items/, subfolders included, when the game server starts, and resolves base_id once:

  • a field that can be left unset (name, layer, weight, amount, stackable, …) and is left unset takes the parent’s value, up the chain;
  • item_id = 0 takes the parent’s graphic;
  • hue inherits when unset; without a value anywhere in the chain, it uses 0;
  • rarity always has a value, so the template’s own is kept;
  • tags, when set, replace the parent’s; they are not merged.

The server stops when an id is empty or used twice (the message names both files), a base_id names no template, base_id loops back (a → b → a), or a resolved template has an invalid field.

For the C# services and factory lifecycle, see Loader reference.

The server reads every *.toml under templates/mobiles/, subfolders included, after the item templates, and resolves base_id once, as UOX3’s GET does (the child inherits everything and overrides what it sets):

  • a field left unset takes the parent’s value, up the chain;
  • skills, resistances and sounds are inherited key by key: the child’s entries override, the parent’s other entries stay;
  • tags merge: the child’s keys add to and override the parent’s;
  • equipment and loot, when set, replace the parent’s; carve, when unset, is the parent’s, and carve = "" is none;
  • everything inherited is copied, never shared between templates.

The server stops when an id is empty or used twice, a base_id names no template or loops back, a template has an invalid field, a name_list names no list of data/names.toml ({gender} needs the male and female lists), an equipment item is not an item template, a loot id is not a loot table, or a carve id is not a carve table.

For the C# services and factory lifecycle, see Loader reference.

The server reads every *.toml under templates/loots/, subfolders included, before the mobile templates. The server stops when an id is empty or used twice, an entry sets both item_id and loot_template_id, an item_id is not an item template, a loot_template_id names no table, a weight is below 1, an amount can roll outside 1 to 65535, nested tables loop, or an item template’s loot names a table that does not exist. A table with no entries is kept, logged as a warning, and gives nothing: two shipped UOX3 tables (randomwands, random_useless_junk) are empty in UOX3’s own data.

For the C# services and factory lifecycle, see Loader reference.

The server reads every *.toml under templates/carves/, subfolders included, after the item templates and before the mobile templates, which name the tables with carve. The server stops when an id is empty or used twice, an entry’s item is not an item template, an amount can roll outside 1 to 65535, a chance is outside 1 to 100 or a message is below 0. A table with no entries is kept and gives nothing. What the tables mean, and what happens to a corpse, is in Carving.

For the C# services and factory lifecycle, see Loader reference.

An item is one [[item]] entry under templates/items/. The table uses TOML keys:

Field Purpose
id The stable name a loot table, a spawn or add names this template by
base_id Another template’s id to inherit unset fields from; the loader resolves the chain
item_id The base client graphic; runtime physical properties come from the client tiledata unless overridden
name, comment A display name override, and a designer note nobody reads at runtime
rarity common, uncommon, rare, epic, legendary, random_of or random_of:rare,epic; each new item picks one of the names with the same chance, so a name written twice weighs double (random_of:common,common,rare is two in three common). The gems use it. Vendors pay more for a rarer item, see Vendors
script_id The global Lua table, defined by scripts/items/<script_id>.lua, whose functions handle what happens to the item (on_use, on_move_over, on_npc_move_over, on_speech, on_equip, on_unequip, on_pickup, on_drop, on_create, on_timer, on_darkness) and answer the questions asked before a move (can_pick_up, can_drop, can_equip, can_insert); a lower-case Lua identifier, empty for none. See Item scripts
movable Unset uses tiledata: movable unless the tiledata weight is 255, the client’s “cannot be lifted”. Players cannot pick up what is not movable; game masters and administrators can
weight Stones to two decimals (weight = 0.02 for a coin); unset uses the whole-stone tiledata weight
amount An integer or a quoted inclusive range: the stack size of a new item, fixed or "10-20"; unset is 1
stackable Unset uses the tiledata Generic flag
layer A LayerType name such as one_handed; unset uses the tiledata layer
two_handed_weapon two_handed_weapon = true on a weapon held in both hands (bows, polearms, staves), as POL’s TwoHanded: worn on two_handed, it leaves no hand free. Anything else on two_handed (shields, torches) goes in the other hand, with a one-handed weapon. Tiledata cannot tell them apart: it marks shields as weapons and bows as one-handed
weapon_type, damage_min, damage_max, speed The weapon fields of combat: weapon_type is sword, axe, pole_arm, mace, staff, fencing, bow, crossbow or thrown, as UOX3 types a weapon by graphic, and decides the skill, the sounds and the swing; damage_min and damage_max (0 to 65535) are what a hit does before the bonuses; speed (1 to 500) gives the swing delay 15000 / ((stamina + 100) * speed). A worn item with a damage_max above 0 is a weapon. Inherited by base_id
armor_rating, strength_required, max_hits armor_rating (0 to 500) is the armor of a piece of armor (a shield’s counts for nothing yet); max_hits (durability) gives a worn piece the hits it starts with, see Durability; strength_required is read and kept, not used yet. Inherited by base_id
dyeable dyeable = true on what a dye tub can give its hue, as UOX3’s dyeable: the converted clothing has it from base_clothing, and a template takes it away with dyeable = false (a death robe). Unset is not dyeable. See dyes.lua and dye_tub.lua
buy_price, sell_price Read and validated (0 or more, inherited by base_id), but no service uses them yet: a vendor’s prices come from its shop template, see vendors
decays, decay_minutes Whether the item decays on the ground, and after how many minutes; unset decays when movable, after 60 minutes, as ModernUO. An item that cannot be picked up decays only when its template has both decays = true and decay_minutes, as the treasure chests. An item decays when it lies on the ground, is movable (or has both of those) and its template is visible to players. The countdown (DecayAt, saved with the item, so downtime counts) starts when it lands on the ground, again when a lifted item bounces back there, and stops when it is picked up, moved into a container or worn; the rest of a split stack keeps the stack’s time. A check every 5 seconds deletes the due items, a container with its contents, whether or not a player is near
use_range How many tiles away a player may double click the item lying on the ground to run its on_use, from 1 to 24; unset is 2, as ModernUO’s reach of an item. The archery butte has 6, since it is shot from five or six tiles. A base template’s is inherited
watch_range How many tiles around it the item watches for mobiles, from 0 to 8: a player or an NPC that comes within this range of the item raises on_mobile_near of its script, once for each entry. Unset and 0 watch nothing and cost nothing; the server keeps an index of the watching items by sector and asks it only when a mobile moves. Only items lying on the ground watch. A base template’s is inherited. The range is read from the template when the item is put on the ground, which includes loading it at the server’s start: templates are read at start, so a change shows after a restart, and an item already lying keeps the range it had until it is picked up and dropped again or the server restarts
loot, gold What a container holds when a spawn region of items makes it: loot = ["reagents", "reagents"] rolls each loot table once (list one twice to roll it twice) and gold = "1d100+29" puts that much gold inside, in piles of at most 65,535. Unset takes the base template’s, else nothing. A spawn region and the .add command fill the container; Lua item.create and item.give make it empty
loot_type regular, newbied, blessed or cursed: what happens when the owner dies; unset is regular
tags Free script values in an [item.tags] table; a child’s explicit tags replace the entire base map
visibility The lowest account type that sees the item: regular, game_master or administrator, as realm_directory.minimum_account_type. Unset by default, so a template inherits it through base_id; an item with none anywhere is visible to everyone.
hue HueSpec, 0 meaning the art’s native coloring; a quoted "min-max" range picks one per spawn
max_items, max_weight Optional; set only on a container template. max_weight is the stones a player may put in the container, counting what is in the containers inside it: 400 when unset, no limit for 0. The staff is not limited, nor is a bank box; an item already inside moves around whatever the container weighs. max_items is the items a player may put in the container, counted with what is inside its bags and against every container around it; joining a pile adds none, the staff is exempt, and unset means no limit. A bank box takes ultima.bank.max_items instead.

Fields that the client’s tiledata.mul also carries (weight, stackability, layer, movability) are overrides: unset means tiledata, as in POL and ModernUO. Template validation rejects a negative weight or price, a weight with more than two decimals, an amount below 1, gold that can roll below 0, a decay time below one minute, a use_range outside 1 to the maximum, a watch_range outside 0 to 8, a damage_min or damage_max outside its range (or damage_min above damage_max), a speed, armor_rating, strength_required or max_hits outside its range, an empty tag key and a malformed script_id.

Spawners, for example, are for staff only, and their children inherit it:

[[item]]
id = "base_spawner"
base_id = "base_item"
item_id = 7956
visibility = "game_master"
[[item]]
id = "orcspawn"
base_id = "base_spawner"
item_id = 7956
name = "Orc Spawner"

A mobile, creature or human NPC is one [[mobile]] entry under templates/mobiles/. Every field but id may be unset: it is inherited through base_id, and with none anywhere the default below applies. Numbers that vary per mobile are dice (DiceSpec): strength = "1d25+95" rolls 96 to 120; a constant is a bare integer.

Field Purpose
id, base_id, comment As for items
name A fixed name such as an orc; unset draws one from name_list
name_list A list id in data/names.toml; {gender} becomes male or female, the gender the mobile gets
title Shown after the name, such as the guard
body The body the client draws; unset uses the race body for the gender
gender male, female or random (50/50 for every mobile); unset is male
race human, elf or gargoyle: skin, hair and beard come from races.toml unless set here; unset for a creature
skin_hue, hair_hue, beard_hue HueSpec; unset uses the race hues
hair, beard Item ids, one picked; unset uses the race styles for the gender, and females get no beard
strength, dexterity, intelligence Dice; unset is 10
hits, mana, stamina Dice; unset is the strength, the intelligence and the dexterity
damage, armor Dice for an unarmed hit and the natural armour; unset is 1d4 and 0
resistances [mobile.resistances] with physical, fire, cold, poison, energy, dice in percent; unset is 0
skills [mobile.skills], skill names such as resisting_spells or tactics, dice in whole points 0 to 120
notoriety innocent, ally, attackable, criminal, enemy, murderer or invulnerable, the name colour; unset is innocent for a human and attackable for any other body
karma, fame Dice; karma may be negative
equipment [[mobile.equipment]] entries: items (item template ids, one picked), hue, and gender to equip only one gender
loot, gold Loot template ids and gold dice rolled into the backpack at spawn; on death, the corpse receives the loot; see Death and loot
carve The id of a carve table of templates/carves/: what a knife takes off the corpse of this mobile; unset takes the base template’s, else nothing; an empty string takes the base template’s table away. A pet’s or a summoned creature’s corpse keeps none
sounds [mobile.sounds] with start_attack, idle, attack, hurt, death; a mobile script plays them by kind with npc.play_sound(serial, "idle")
blood_hue The hue of the blood the creature leaves when it is hit: 0 for red, -1 for one that does not bleed, as ServUO’s blood_hue and Source-X’s BLOODCOLOR; a base template’s is inherited. Unset: red. The undead and the golems have -1. See Combat
flee_at The percent of its hit points (0 to 100) under which a creature that fights runs from the fight, -1 for one that never does, as UOX3’s FLEEAT; a base template’s is inherited. Unset: the script’s own, 20 for a monster and 10 for an animal. See Creature scripts
spell_circles The highest circle of Magery (1 to 8) the creature casts from, as UOX3’s SPATTACK; unset or 0: it casts nothing. A creature with it, some Magery, some mana and a script that uses common/creature.lua (monster, animal, scared_animal) casts at what it fights from up to 12 tiles away, with its mana and no reagents; a creature with no script, a shopkeeper or a mana of 0 casts nothing; a base template’s is inherited. See Creature scripts
low_health_at The percent of its hit points (0 to 100) under which the NPC’s script is called with on_health_low, 0 for one that never is; a base template’s is inherited. Unset: 25. See Mobile scripts
control_slots How many followers the creature counts for once a player owns it, 1 to 10, as UOX3’s CONTROLSLOTS. Only the creatures the spells of Magery summon (templates/mobiles/magicsummon.toml) set it; a tamable creature takes its slots from data/taming.toml. A base template’s is inherited. Unset: 1. See Magery
npc_guild The guild this guildmaster takes members for: mages, warriors, thieves, rangers, healers, miners, merchants, tinkers, tailors, fishermen, bards or blacksmiths. Unset: the NPC is no guildmaster. See Guildmasters
script_id The global Lua table, defined by scripts/mobiles/<script_id>.lua, whose on_think, on_speech, on_spawn, on_mobile_in_range, on_death and on_drag_drop handle the NPC; a lower-case Lua identifier. See Mobile scripts
visibility As for items
opens_doors Whether it opens the closed doors in its way when it walks to a place. Unset: a human or a monster body does, an animal or a sea creature does not
movement land, water (a dolphin: it spawns and swims on the water only) or both (a walrus: it walks and swims, and spawns on land else on the water); unset is land
tags Free script values; child keys add to and override parent keys

resistances, sounds, skills and tags are inherited key by key, so a base such as base_orc sets the five sounds once and every orc keeps them; every other field a child sets replaces the base’s. Template validation rejects dice that can roll below 0 (karma apart), an unknown skill, a skill above 120, a resistance above 100, a negative sound, an equipment entry with no item or an empty item id, an empty tag key, a blood_hue outside -1 to 65535, a flee_at outside -1 to 100, a low_health_at outside 0 to 100, a spell_circles outside 0 to 8, a control_slots outside 1 to 10 and a malformed script_id.

[[mobile]]
id = "base_orc"
body = 0x11
name = "an orc"
notoriety = "murderer"
[mobile.sounds]
start_attack = 0x1B0
idle = 0x1B1
attack = 0x1B2
hurt = 0x1B3
death = 0x1B4
[[mobile]]
id = "orc"
base_id = "base_orc"
strength = "1d25+95"
karma = -2500
loot = ["randomgems"]
[mobile.skills]
tactics = "1d26+54"
[[mobile]]
id = "guard"
gender = "random"
race = "human"
name_list = "{gender}"
title = "the guard"
notoriety = "invulnerable"
script_id = "wander"
[[mobile.equipment]]
items = ["skirt", "short_pants"]
gender = "female"

The shipped templates/mobiles/ holds UOX3’s NPCs, converted by moongate-convert uox. They are loaded at game and standalone startup .

A loot table is a [[loot]] entry under templates/loots/; its outcomes are [[loot.entries]] entries:

Field Purpose
id The stable name a loot_template_id or an NPC’s death loot names this table by
comment A designer note nobody reads at runtime
entries The table’s weighted outcomes
weight This entry’s share of the table, relative to every other entry’s; 1 by default
item_id The id to drop; unset when loot_template_id is set instead
loot_template_id Another table’s id to pick from instead of a direct item
comment What item_id or loot_template_id is, for a human reading the file
amount An integer or a quoted inclusive range, how many of item_id to create

To produce these files from an existing UOX3 shard, see Migrate from UOX3.

templates/npc_lists/ holds lists of mobile templates a spawn picks from, converted from UOX3’s [NPCLIST name] blocks. An entry names a mobile template or another list, and is picked in proportion to its weight (1 by default); an entry naming a list then picks from that list:

[[npc_list]]
id = "jungle"
entries = [{ mobile_id = "gorilla", weight = 20 }, { npc_list_id = "all_trolls", weight = 7 }]

templates/spawns/<map>/ holds the spawn regions, converted from UOX3’s [REGIONSPAWN n] blocks, plus ModernUO’s spawners (modernuo_*.toml and trammel/town_new_haven.toml); the folder is the map, and a map key in a region is ignored. A spawn picks from one pool, as UOX3: its mobile_ids (weight 1 each) and the entries of its npc_list_ids with their weights:

[[spawn]]
id = "felucca_0" # unique
name = "The Hammer And Anvil"
mobile_ids = ["weaponsmith"] # mobile templates, picked at random with the lists' entries
npc_list_ids = [] # npc lists
# item_ids = [] # item templates spawned on the ground instead of NPCs; see Spawns
max = 1 # NPCs alive at once
min_minutes = 480 # a new one every min_minutes to max_minutes
max_minutes = 600
call = 1 # NPCs that come at a time
areas = [{ x1 = 1422, y1 = 1547, x2 = 1426, y2 = 1550 }] # both corners included
exclude = [] # parts of the areas where nothing spawns
only_outside = false # true: never under a roof
# pref_z = 18 # how high above the ground a spot may be
# z = 36 # the highest a spot may be, instead of ground + pref_z

Both load at startup, after the mobile templates, and a mistake in them stops the server. How the regions spawn at runtime, water mobiles included, is in NPC spawns.

templates/decorations/ holds the world decoration the client’s map files do not: doors, signs, lights, furniture, teleporters and the like, about 32,800 placements in the 108 loaded files (115 files in all). It was converted once from ModernUO’s Data/Decoration, plus ServUO’s New Haven (trammel/newhaven.toml, havenisland.toml, havenmine.toml, which ModernUO lacks) and the shop and world signs of ModernUO’s signs.cfg (signs.toml, written by moongate-convert modernuo-signs) and the world and dungeon teleporters of its teleporters.json (teleporters.toml, written by moongate-convert modernuo-teleporters), one TOML file per source file, in one folder per map: britannia/ (Trammel and Felucca), trammel/, felucca/, ilshenar/, malas/, tokuno/, termur/, and the special sets _ruined_magincia_tram/, _ruined_magincia_fel/, _old_magincia/ and _bounty_boards/. A folder or a file whose name starts with _ is not loaded: rename it without the _ to place its decoration. _old_magincia/ is the furniture of Magincia as it was before its destruction: the map of a current client holds New Magincia, rebuilt without those buildings, so the set stays out; with an old client, move its file to britannia/.

The old Haven of Trammel is set aside the same way: trammel/_haven.toml, trammel/_haven_additions.toml and trammel/_signs.toml, 631 placements. On the map of a current client that town is a ruin, with a third of its buildings gone or broken, so its doors, chests and bookcases would stand with no walls around them; Felucca keeps the town at the same place. With an old client, rename the three files without the _. ModernUO names some of its files with a leading underscore (_covetous.cfg): here they are covetous.toml, or despise_additions.toml where a plain despise.toml exists too, so that the underscore only ever means “not loaded”. A world decorated before keeps the items it already has: .decorate tells what is placed by where it stands, not by the file, so the renamed files place nothing twice, and the old Haven’s items stay until they are removed or the world is decorated anew.

[[decoration]]
comment = "metal door"
type = "MetalDoor" # the kind, kept as ModernUO names it
item_id = 0x0675
props = { facing = "west_cw" } # the kind's settings; facing is a DoorFacingType
locations = [[1411, 1621, 30], [1411, 1622, 30]]

A block without item_id is an addon built from several graphics. extras, present in some shipped files, is not read: a block’s settings are its props, the same for every location.

.decorate places them with the templates of templates/items/decorations.toml: decoration, fixed and never decaying, for most kinds; decoration_door, the same with script_id = "door", for the kinds whose name contains Door or Gate; decoration_light, with script_id = "light", for ModernUO’s light kinds (candles, candelabras, lanterns, lamp posts, sconces, torches, braziers); and decoration_teleporter, with script_id = "teleporter" and visibility = "game_master", for the kind Teleporter. Each item takes the block’s graphic, hue and name; its other settings stay in the item’s props, with decoration_type = the kind for a door or a light. A light also gets its light shape (the block’s or the kind’s) and protected unless the block says unprotected; the graphic already says whether it is lit. A door block with locked = true in its props places doors that only staff open. An item with a label_number prop, such as a LocalizedSign, shows that text of the client as its name, unless the item has a name of its own. A teleporter’s point_dest = [x, y, z] becomes the props teleport.x, teleport.y and teleport.z, and its map_dest the prop teleport.map, a MapType number. A KeywordTeleporter takes the template decoration_keyword_teleporter, with script_id = "keyword_teleport" and visibility = "game_master", and keeps its substring, keyword, range and delay as props. A BulletinBoard takes the template bulletin_board, with script_id = "bulletin_board", and opens on a double click: see Bulletin boards. A Clock takes the template decoration_clock, with script_id = "clock", and tells the time on a double click; one placed as plain decoration by an earlier run becomes a clock where it stands. A Blocker is plain decoration whose graphic draws nothing and cannot be walked through: players are stopped by it unseen, and game masters and administrators see a gravestone in its place, as ModernUO shows it. A Fillable... kind (crate, box, chest, barrel) or a LibraryBookcase takes the template decoration_fillable, with script_id = "fillable": a container that fills up when it is opened. Its content_type (Inn, ThiefGuild) is kept as the name of its table (inn, thief_guild), a bookcase is a library, and one placed as plain decoration by an earlier run becomes fillable where it stands. A PublicMoongate takes the template decoration_public_moongate, with script_id = "public_moongate" and the light circle300 in its props unless the data gives another; .decorate also places one on every destination of moongates.toml. A TrainingDummy and an ArcheryButte take the templates decoration_training_dummy and decoration_archery_butte, with the scripts that make them train a weapon. Spawners, mark containers, addons and every other kind whose name ends in Teleporter, those that ask for a skill, a quest or a double click (SkillTeleporter, InteractionTeleporter, …), are not placed yet. The doors of the towns are in no file: .decorate reads them from the map’s door frames.

For C# implementation details, see The loader contract.

For C# implementation details, see Registering a loader.

For C# implementation details, see Running the loaders.

For C# implementation details, see Fields that resolve randomly.

For C# implementation details, see Fields that resolve to a fresh number.

For C# implementation details, see Registering a TOML converter.

Made with ❤️ by Tom

Buy me a coffee