Migrate from UOX3
moongate-convert uox converts UOX3 .dfn item
definitions and loot lists into Moongate’s ItemTemplate and LootTemplate TOML, and
UOX3 NPCs, NPC lists, spawn regions and name lists into MobileTemplate, NPC list and
spawn TOML and names.toml. More commands convert ModernUO’s
signs, teleporters,
named places, treasure chests and
spawners and book texts. uox-crafts converts
UOX3’s create menus into data/crafts, and modernuo-guildmasters, modernuo-taming and
modernuo-vendors convert ModernUO’s guildmasters, tameable creatures and shops, and uox-carves converts
UOX3’s carve tables (below).
The shapes it writes are described in Loading TOML templates;
the server loads them at startup from templates/.
Run it
Section titled “Run it”The converters are a Python tool, moongate-convert, in tools/convert of the repository. It needs
uv and Python 3.13 or later, and no .NET. Run every command of this page from that folder:
cd tools/convertuv run moongate-convert uox \ --source <file-or-directory> --destination <dir> [--loot-destination <dir>] \ [--mobile-source <dfndata> --mobile-destination <dir> --names-destination <file>] \ [--starting-items-destination <file>] [--scripts-source <js-dir>] \ [--npc-lists-destination <dir> --spawns-destination <dir>]uv run moongate-convert --help lists the commands and uv run moongate-convert <command> --help the options of one.
The paths of the examples below are those of the repository, seen from tools/convert. The image of Docker does not
carry the tool: see UOX3 content conversion.
--source is a single .dfn file or a directory scanned recursively for every
.dfn under it. --destination receives one <name>.toml per source .dfn, at the
same relative path, holding one [[item]] per block that has an id= of its own.
--loot-destination is optional; without it, LOOTLIST blocks are skipped. A bare
invocation, or a missing required argument, exits 2 with the usage.
The three mobile arguments go together (see Mobiles and name lists).
--scripts-source is UOX3’s data/js folder. With it, an item whose UOX3 script has a
Moongate Lua script gets its script_id: the script of the block’s script=, else the one
jse_objectassociations.scp ([ENVOKE]) gives its graphic, looked up by number in
jse_fileassociations.scp ([SCRIPT_LIST]). Today item/lights.js becomes light
(scripts/items/light.lua); other scripts are left out. With or without it, what UOX3 calls food
(item type 14, on the block or on the one it gets its fields from) takes script_id = "food"
(scripts/items/food.lua), but for what UOX3 files under food and nobody eats as it is: the bowl of
flour (bowl_of_flour) and the magic fish (base_magic_fish). What UOX3 calls a drink (item
type 105) takes script_id = "drink" (scripts/items/drink.lua) the same way, in place of UOX3’s own
pitchers.js, but for the jar of honey (jar_of_honey).
A folder missing either file exits 2.
What combat reads is converted too: damage=min max, spd, str, def and hp=min max of an item become damage_min,
damage_max, speed, strength_required, armor_rating and max_hits (a value that is not a number, or is 0, is left
out), and the kind of weapon (weapon_type) follows the graphic of a block with an id=, by the table of UOX3’s own
GetWeaponType (a graphic UOX3 does not list is fought with fists). The eras of UOX3 (t2a, lbr, aos, tol) keep
their own numbers, and the kind goes on the item that has the graphic, which they inherit. See Combat.
The dyes (item type 208) take script_id = "dyes" and the dye tub script_id = "dye_tub"
(scripts/items/dyes.lua, scripts/items/dye_tub.lua). UOX3 types the tub by its graphic 0x0FAB
in itemtypes.dfn, not in its block, so the converter does the same: a block named by that graphic.
--npc-lists-destination and --spawns-destination go together and need --mobile-source.
They convert the [NPCLIST name] blocks under npc/ into templates/npc_lists (entries
20|gorilla keep their weight, NPCLIST=trolls becomes a nested list) and the
[REGIONSPAWN n] blocks under spawn/ into templates/spawns/<map>/, one folder per map from
the region’s own WORLD= (0 Felucca, 1 Trammel, 2 Ilshenar), else the source folder. A region’s
GET= takes the fields of the region it names, its own winning, but never its map, NPCs, lists
or eras, as UOX3; a header defined twice keeps its last definition. An unweighted NPCLIST=x
inside a list brings x’s entries in (UOX3 splices it); a weighted n|NPCLIST=x stays one pick.
Regions whose ERAS= leave out tol (the modern era; UOX3’s default lbr keeps the same
ones) are skipped, reversed exclude corners and MINTIME/MAXTIME are put in order, and the
output is read back and checked as the server’s loaders do.
MINTIME/MAXTIME stay in minutes as written (UOX3 itself truncates them to a byte). Regions
spawning only items, and NPCs or lists that do not resolve, are left out and counted.
Every block from every source file is read before any get= chain is resolved,
because a chain’s target can live in another file: UOX3’s own data keeps a sword’s
facing variants beside its base definition but a shared base_item elsewhere. A
trailing //comment is stripped from every line first, as the UOX3 engine does;
real data glues one straight onto a block’s opening brace ({//approximately 1%).
Text after the opening brace ({ Random Hair) is a label, not a line of the block.
What maps
Section titled “What maps”Verified against real UOX3 data:
| UOX3 | ItemTemplate | Note |
|---|---|---|
The block’s own id= |
ItemId |
A list (id=0x0c4f 0x0c50, one picked at random in UOX3) keeps its first graphic; typos such as 0x0x04FC and 0x15b6] are forgiven. A block with no id= of its own converts only when it has one parent (below), with item_id = 0: the server’s loader takes the parent’s graphic |
name= when the header is a bare hex (the header itself when the name says nothing: #, a single letter, a bare number), else the block header |
Id |
Run through StringUtils.ToSnakeCase; name= is free text (“pitcher of wine”). UOX3 reuses a name across many variants and snake case also collapses distinct headers, so the first block scanned keeps the bare id and the ones after it take _2, _3, … |
name= |
Name |
Carried as-is; UOX3 does not separate an identifier from display text |
A single-target get= (else getlbr=) |
BaseId |
Only when that target itself converted; get=a b, a random alias with no id= of its own, converts nothing |
movable=1 or 3 / 2 |
Movable = true / false |
0 or absent leaves it unset, so tiledata decides |
weight= |
Weight |
Divided by 100: UOX3 weighs in hundredths of a stone |
amount= |
Amount |
A fixed stack size |
pileable= |
Stackable |
|
layer= |
Layer |
The UOX3 layer number as a LayerType name |
layer=2 without type=107 (shield) or dir= (light) |
two_handed_weapon = true |
As UOX3 decides at equip time. An unlit torch (0x0F64) has neither, so UOX3, and the converter, treat it as two-handed; the shipped templates leave it off |
dyeable= or dye= |
Dyeable |
The same tag in UOX3; 0 writes dyeable = false, which takes it away from what a base gave |
value=buy sell |
BuyPrice, SellPrice |
One number sets both |
decay= |
Decays |
1 is true, anything else false |
newbie or newbie=1 |
LootType = newbied |
|
custominttag=name value, customstringtag=name text |
Tags |
Every line, so a block can set several |
color= or colour= |
Hue |
A fixed value, not a range; unset writes no hue, so the server’s loader takes the base template’s |
weightmax= |
MaxWeight |
UOX3 counts it in hundredths of a stone, as weight=: weightmax=40000 is 400 stones, whole and rounded up |
visible=1, 2 or 3 / visible=0 |
Visibility = game_master / regular |
Hidden, magically invisible or GM hidden all keep the item from players; visible=0 is written out so it overrides a hidden parent; absent leaves it unset |
Everything else has no home in ItemTemplate yet and is dropped: the combat stat
fields, colorlist, script=, and the multi and geometry fields. BaseId is a pointer only: the
converter does not flatten a parent’s fields into its children; the loader will
resolve the chain once it exists. A parent block with no id= of its own, such as
[base_coin], has its lines inlined into every child that get= it, as UOX3 does, with
the child’s own lines winning. A coin so gets weight = 0.02 and stackable = true, and
its base_id is the first ancestor that has an id= (base_item). An inherited name=
becomes the template’s name but never part of its id: ids come from each block’s own
lines only.
Numbers are read as UOX3 reads them (stoi(value, nullptr, 0)): hex with 0x or
decimal, so layer=0x08 is a ring. A tag with no value, such as baseitem.dfn’s bare
decay=, is ignored as UOX3 ignores it. A header defined twice keeps its last
definition, as UOX3 does, and source files are read in ordinal order so the result does
not depend on the filesystem. [BESTSKILL n] follows UOX3’s own skill numbers, where
Imbuing is 55 and Mysticism 56 (the other way round from SkillType).
A block with no id= of its own but a single parent (get=x, else getlbr=x) that
converted is a template too, id = its header: UOX3’s magic items, journals and other
variants ([glacialstaff] get=0x0df1 name=glacial staff color=0x0480) and single-target
aliases. It keeps item_id = 0, which the server’s loader fills from its base_id, and
the chain is resolved over repeated passes, so a variant of a variant converts too. The
shipped data has 9737 item templates this way; loot entries and NPC equipment that name
such a block now resolve to it.
Loot tables
Section titled “Loot tables”UOX3’s [LOOTLIST name] { ... } blocks are weighted loot tables. They convert into
--loot-destination (templates/loots/, next to templates/items/) as one
<id>.toml per table, named after the table’s own Id rather than the source file:
real UOX3 data defines all 71 tables in one lootlists.dfn, and reviewing one has
no reason to load every other table alongside it. Each entry line is:
weight|entry[,amount]weight defaults to 1 when the weight| prefix is absent. entry is an item
header, resolved through the same map get= uses; LOOTLIST=other, a nested
weighted pick from another table; or the literal blank, a real weighted chance of
dropping nothing. amount is a single count or min max (a space, not a dash) and
maps onto LootEntry.Amount, a RangeValueSpec<int>.
| UOX3 | LootTemplate / LootEntry | Note |
|---|---|---|
The block header’s name, after LOOTLIST |
LootTemplate.Id |
Also through StringUtils.ToSnakeCase; real names are camelCase (“eartheleLoot”) |
An entry’s weight| prefix |
LootEntry.Weight |
Defaults to 1 |
| An item header entry | LootEntry.ItemId |
Also fills Comment with that item’s own name=, when it had one |
LOOTLIST=other |
LootEntry.LootTemplateId |
Only when other itself converted |
blank |
Neither ItemId nor LootTemplateId set |
A real, weighted chance of nothing |
A trailing ,amount |
LootEntry.Amount |
RangeValueSpec<int>; min max (space) becomes a range |
ITEMLIST=, UOX3’s “spawn every entry” sibling, is a different mechanic, not a
weighted pick, and never appears in real lootlists.dfn data; it is dropped, as is
any entry the converter cannot resolve.
Mobiles and name lists
Section titled “Mobiles and name lists”With --mobile-source set to UOX3’s dfndata folder, the same run converts, after the
items:
- every block under
npc/(notnpc/npclists) into a[[mobile]]template, one file per source file under--mobile-destination, id = the header in snake_case; - the twenty
[RANDOMNAME n]lists ofnpc/namelists.dfninto--names-destination.
It also reads creatures/creatures.dfn (sounds, and MOVEMENT=WATER or BOTH as
movement), colors/colors.dfn (colour lists) and
../dictionaries/dictionary.ENG (numeric names and titles). Equipment and loot are
resolved against the items and loot tables of the same run.
Every npc block is converted, so inheritance stays a base_id instead of being copied
in: GET=x and GETLBR=x become base_id = "x". LBR is UOX3’s default era; the other
era tags (GETUO, GETAOS, …) are ignored, although the blocks they name are converted
too.
| UOX3 | Template | Note |
|---|---|---|
ID=0x0190 / 0x0191 |
race = "human", gender |
elf 0x25D/0x25E and gargoyle 0x29A/0x29B likewise; the race gives the body |
ID= other |
body |
|
RACE=0/1/2 |
race human / elf / gargoyle |
only when the block sets no non-human body (UOX3’s [giantrat] has RACE=2 on a rat); UOX3’s other races are dropped |
NAME=, TITLE= |
name, title |
a number is a dictionary id; # takes the line’s // comment |
NAMELIST=n |
name_list |
1 male, 2 female, 3 orc, 5 daemon, …; male/female follow the gender, as UOX3 data has female NPCs on the male list |
STR, DEX, INT |
strength, … |
96 120 becomes the die 1d25+95; one value a constant |
HPMAX (else HP), MANAMAX, STAMINAMAX |
hits, mana, stamina |
dice |
DAMAGE, DEF |
damage, armor |
dice |
RESISTFIRE/COLD/POISON/LIGHTNING, ELEMENTRESIST |
resistances |
lightning is energy |
skill tags (MAGERY=500 700) |
[mobile.skills] |
tenths to points, capped at 120; MAGICRESISTANCE is resisting_spells |
KARMA, FAME, GOLD |
karma, fame, gold |
dice |
FLAG=INNOCENT/NEUTRAL/EVIL |
notoriety innocent / attackable / murderer |
|
EQUIPITEM=x, EQUIPITEM=listobjectN |
[[mobile.equipment]] |
a list gives every item of [ITEMLIST N], one picked at random; its weights and blank lines are dropped; the hair and beard lists 13–15 are skipped. An item block with no id= of its own is followed: getlbr=x gives x, get=a b gives both |
COLOR, COLORLIST after an EQUIPITEM |
that entry’s hue |
a colour list only when it is one unbroken run of hues |
LOOT=list,n |
loot |
the loot table, n times |
CUSTOMINTTAG, CUSTOMSTRINGTAG |
tags |
|
[CREATURE id] sounds |
[mobile.sounds] |
on the template whose own block sets the body |
GET=m_guard f_guard, a male and a female of the same race, becomes one guard with
gender = "random", name_list = "{gender}" and the equipment only one of them wears
filtered by gender. Sounds the two set differently (humans die with a male or a female
scream) are left unset rather than giving a female the male sound; any other field set
differently takes the male value and is reported. An f_ or m_ NPC whose body is the
other gender’s (UOX3’s [f_scribe] has ID=0x0190) takes the prefix’s gender, so its
pair still merges. Any other two-target GET ([dragon] GET=graydragon reddragon, a
random pick in UOX3) becomes a template whose base_id is the first target, and is
counted; a pair whose targets do not exist (shepherd) is skipped.
Two known mistakes in UOX3’s item data are corrected as the blocks are read
(DATA_FIXES in tools/convert/src/moongate_convert/uox_data.py): necro_sleeves and necro_leggings name the leather gloves and a
leather tunic as their parents; they get the leather sleeves (0x13cd) and leggings
(0x13cb).
FLEEAT becomes the flee_at of the template (from 0 to 100, or -1 for a creature that never runs; the undead, the
elementals and the daemons have -1); a value outside is left out.
SPATTACK (the circles of Magery the creature casts from, 1 to 8) becomes the spell_circles of the template; 0 and a
value outside are left out. A creature with no Magery of its own or of a base is left without it (the drakes that breathe
fire, the lava serpent, the kraken and the ratman mage’s base, which would fizzle every cast); the run counts them as
spell_circles without Magery. The shipped templates have it on 99 templates, 113 creatures with those based on them; 90 of those run the monster
script and cast, while the 9 mounts (a mana of 0), the 4 mage vendors (shopkeeper) and 10 creatures with no script do not.
A mobile Moongate has a script for gets its script_id: a banker (NPCAI=8) takes banker
(scripts/mobiles/banker.lua), a town guard (NPCAI=4) takes guard (scripts/mobiles/guard.lua), the creatures
that go for everyone (NPCAI=2 evil, 11 evil caster and 88 chaotic, and the undead of the graveyards by name:
skeleton, zombie, ghoul, headless, wraith, spectre, lich) take monster
(scripts/mobiles/monster.lua; the casters cast from their spell_circles; the good fighters and casters, 5
and 10, fight criminals only and have no script yet), an animal (NPCAI=6) takes animal
(scripts/mobiles/animal.lua) and a scared animal (NPCAI=12) takes scared_animal
(scripts/mobiles/scared_animal.lua); the templates based on them take the script through base_id.
Dropped, no home yet: the rest of AI and wandering (the other NPCAI values, NPCWANDER, FX*, speeds),
taming and bard skills (TOTAME, CONTROLSLOTS, TOPROV, TOPEACE), shops
(SHOPKEEPER, SHOPLIST), PACKITEM, FOOD, PRIV, SCRIPT and the other
tags without a field (CARVE is set by uox-carves). The run prints how often each kind of value was dropped.
The written mobiles are read back as well: ids unique, every base_id, equipment item,
loot table and name list resolves, and every template passes MobileTemplate.Validate().
Starting items
Section titled “Starting items”With --starting-items-destination <file> (which needs --mobile-source), the run also
converts newbie/newbie.dfn into one starting_items.toml of [[set]]s, after the
mobiles and against the same item ids.
| UOX3 | Set | Note |
|---|---|---|
[BESTSKILL n] |
skill |
the skill with id n; [BESTSKILL X] and empty sections are skipped |
[DEFAULT ALL] |
common = true |
every character gets it |
[DEFAULT MALE], [DEFAULT FEMALE] |
race = "human", gender |
UOX3 gives these to the human bodies only |
[DEFAULT ELF MALE], [DEFAULT GARG FEMALE], … |
race, gender |
|
PACKITEM=item,amount,newbie |
[[set.items]], equip = false |
amount 1 is left unset; newbie 0 or 1 becomes false or true |
EQUIPITEM=item,hue,newbie |
[[set.items]], equip = true |
Items resolve as npc equipment does: listobjectN gives every item of [ITEMLIST N],
an item block with no id= of its own is followed. Items that resolve to nothing are
dropped and counted. The file is read back and every item must exist. UOX3’s own rules
(the three best skills, four with extended starting skills, and STARTGOLD) are not data
and are not converted. Set ultima.starting_items.best_skills in the server
configuration. The converter adds Moongate’s own entries to the common set, for the items the
source has: 1000 gold coins first, in place of STARTGOLD, then three loaves of bread and a
pitcher of water last (see the shipped file).
Carve tables of UOX3
Section titled “Carve tables of UOX3”uox-carves converts carve/carve.dfn, what a knife takes off the corpse of a creature, into the carve tables of
Carving, and sets the carve field of the mobile templates of the NPCs that name a table:
uv run moongate-convert uox-carves \ --source <dfndata> --items <templates/items> --mobiles <templates/mobiles> --destination <templates/carves>Each [CARVE n] block is the table uox3_<n> of uox3.toml, replaced whole by every run. An
ADDITEM=graphic,amount line is an entry: the graphic is resolved as the item pass resolves it (0xC002 is the spined
hide of carve_items.dfn), the amount is a number from 1 to 65535 and no amount is 1, and the text the carver is told is
the client’s for meat (500467), hides (500471), feathers (500479) and wool (500483); scales and the rest say nothing.
Left out, and counted: an item with no template in --items, a graphic that resolves to nothing, an amount out of range
(the 0x09f1,0 of the drake), an ADDITEM that names an item list (listobject71, which picks one of several) and
[CARVE HUMAN], the torso and limbs. A table with no entry left is not written.
An NPC block with CARVE=n sets carve = "uox3_<n>" on the mobile template with its id (the header in snake_case). The
field is set in place in the files of --mobiles, because the shipped mobile templates are tuned by hand and are not
written again: the line goes after loot, as the server writes a template, and nothing else of the file changes. A
template that has a carve of its own keeps it (the cow, the bull, the pig, the sheep and the chicken of
templates/carves/animals.toml), and a uox3_ carve that names a table no longer written is taken off. uox does not
write the field, so run uox-carves after it.
A header that UOX3 files more than once (a block per era, in several files) counts when any of its blocks has a CARVE:
the first block that sets one wins. A few mistakes of the UOX3 data are corrected (CORRECTIONS in uox_carves.py): the
mongbat names the bird table, [CARVE 2], though [CARVE 74] is the mongbat’s, and gets that one, which the greater
mongbat inherits; each is counted in the report. A mobile templates file may have CRLF line ends, a byte order mark or a
comment after [[mobile]]; it is written back with LF and no mark, as every file the converters write.
A template with carve = "" has its own carve and keeps it: that is how the ethereal mounts, the fire steed, the
giant beetle and Exodus, whose parent has a table, carry none.
The shipped data has 100 tables; 110 NPCs name one, and 105 mobile templates got the field (the farm animals keep
their own). The server stops at startup when a carve names no table or an entry names no item template.
Behavior of the converters
Section titled “Behavior of the converters”A source that is not what it should be (an inverted loot range, two headers that become the same id, a hue out
of range, an unreadable file or folder) stops the run with exit code 2 and a message, and nothing is written. modernuo-books does not check that a book fits
the client’s packets (the server’s tests of the shipped catalog do), modernuo-vendors also makes a shop for the thief and the
ranger, and modernuo-locations fixes Cell 7 of the jail, which ModernUO puts on the spot of Cell 6.
Some shipped files are older than the converter or were edited by hand, so converting again does not give them back
byte for byte: the tests of tools/convert name them. The tests (uv run pytest, and with the checkouts of UOX3 and
ModernUO the golden and end to end ones) are described in tools/convert/README.md.
Signs of ModernUO
Section titled “Signs of ModernUO”The shop and world signs come from ModernUO’s signs.cfg, the file its [SignGen places:
uv run moongate-convert modernuo-signs \ --source <ModernUO>/Distribution/Data/signs.cfg --destination ../../moongate_root/templates/decorationsIt writes one signs.toml per decoration folder (britannia for the
signs of both Trammel and Felucca), replacing that of a previous run. The signs of Trammel alone
are those of the old Haven, a ruin on the map of a current client: they are written to
trammel/_signs.toml, which is not loaded. In each file a text of the client becomes
a LocalizedSign with label_number, a written one a Sign with name, and the signs of Luna and
Umbra keep the hue of their town. A line that is not a sign stops the run and names itself.
.decorate places them.
Teleporters of ModernUO
Section titled “Teleporters of ModernUO”The world and dungeon teleporters come from ModernUO’s teleporters.json, the file its [TelGen
places:
uv run moongate-convert modernuo-teleporters \ --source <ModernUO>/Distribution/Data/teleporters.json --destination ../../moongate_root/templates/decorationsIt writes one teleporters.toml per map folder of the
decorations (felucca, trammel, ilshenar, malas, tokuno,
termur), replacing that of a previous run: one Teleporter block per destination, with
map_dest when the destination is on another map. An entry with back also gets the teleporter
from its destination to its source, and a later entry replaces an earlier one on the same cell
within 12 of height, as [TelGen does. An entry that is not a teleporter stops the run and names
itself, and nothing is written. .decorate places them.
Named places of ModernUO
Section titled “Named places of ModernUO”The places of ModernUO’s [Go gump, one JSON file per map with categories inside categories,
become locations.toml, which .go lists and
travels to:
uv run moongate-convert modernuo-locations \ --source <ModernUO>/Distribution/Data/Locations --destination ../../moongate_root/data/locations.tomlIt reads felucca.json, trammel.json, ilshenar.json, malas.json, tokuno.json and
termur.json, those that are there, and writes one [[location]] per place with its map, its
categories joined by /, its name and its spot, replacing the file of a previous run. A place
without a name or without three numbers stops the run and names itself, and nothing is written.
Treasure chests of ModernUO
Section titled “Treasure chests of ModernUO”The dungeon chests are entries of ModernUO’s spawners, TreasureChestLevel1 to 4, beside the
creatures or alone. This command takes the chests only, on every map, and leaves the creatures to
the spawn converters:
uv run moongate-convert modernuo-chests \ --source <ModernUO>/Distribution/Data/Spawns --destination ../../moongate_root/templates/spawnsIt reads the shared and post-uoml eras and writes one treasure_chests.toml per map folder
that has chests, replacing that of a previous run: a
spawn region of items per spawner with chests,
which picks among the levels the spawner lists, with the spawner’s delays, its home range as the
area and the caps of its chest entries together as max, the spawner’s count at most. A chest of
another level is counted in the report and left out. With no chest at all it fails and writes nothing.
Spawns of ModernUO
Section titled “Spawns of ModernUO”UOX3 has no spawns for Malas, Tokuno and TerMur. The modernuo-spawns command takes them from
ModernUO’s spawners:
uv run moongate-convert modernuo-spawns \ --source <ModernUO>/Distribution/Data/Spawns --maps malas,tokuno,termur \ --mobiles ../../moongate_root/templates/mobiles --destination ../../moongate_root/templates/spawnsIt reads the shared and post-uoml eras of each map (the world of a modern client) and writes
the spawn regions into <map>/modernuo_<file>.toml, such as malas/modernuo_doom.toml, replacing
the modernuo_ files a previous run wrote in the folders of the maps it converts; the other files
of the folder are left alone, and a map with nothing to write keeps its files. A region’s id names the era, the file and
the spawner’s index in it (malas_modernuo_post_uoml_south_12), so it stays the same when a later
run, with more templates, resolves more mobiles. With --only Guildmaster it converts only the entries of the classes whose name ends with that, into
modernuo_guildmasters.toml, and leaves the other modernuo_ files alone (the npc lists are read from the npc_lists folder beside the --mobiles one, and the thieves’ guildmaster, which the town spawns of UOX3 place already, is skipped): that is how the guildmasters of Trammel, Felucca and
Ilshenar are placed without ModernUO’s other spawns on top of UOX3’s. Use it for maps UOX3 does not cover: on Felucca or
Trammel it would add ModernUO’s spawns on top of UOX3’s. A region takes the map its spawner
names, which is not always its folder’s, and goes into the folder of that map, since the server
takes a region’s map from its folder: the Yomotsu Mines and the Fan Dancer’s Dojo lie in
ModernUO’s tokuno folder and on the Malas map, so their regions are in
malas/modernuo_yomutso_mines.toml and malas/modernuo_fan_dancers_dojo.toml, with ids that
still start with tokuno_. A spawner becomes:
mobile_ids: its entries, each ModernUO class found among the--mobilestemplates. The command tries an alias of its table first (Minterisbanker,GreatHartishart, guildmasters are their trade’s vendor), then the class in snake case (GreatHartisgreat_hart), then the class with the ids’ underscores ignored, then UOX3’s short name of an elemental (DullCopperElementalisdullcopperele). A class with no template is counted asunknown mobile <Class>, and a spawner with none left is skipped.max: itscount. An entry whosemaxCountis below the count, such as a single wandering healer among the beasts, becomes a region of its own with that cap (its id ends with the mobile); the other entries share one region with the rest of the count, less the share of the entries no template matches.min_minutesandmax_minutes: its delays, a minute at least;call1.areas: itsspawnBoundswhen it has them, else the square of its home range around its spot, the spot alone when it has none, as in ModernUO.z: the top of itsspawnBounds, else 16 above the spot, so a spawner in a cave does not spawn on the land over it. ModernUO looks at any height, so a wide outdoor spawner here keeps to the ground about its own level. ItswalkingRangeis left out: the NPCs’ Lua script decides how they wander.
An unknown map name exits 2, as do a missing --source and a --mobiles folder without
templates, which would otherwise replace the shipped files with nothing. The shipped Malas, Tokuno and TerMur
spawns come from this command; the classes it reports unknown are the NPCs still to write.
Verifying the output
Section titled “Verifying the output”After writing every file, the converter reads all of it back from disk, as a real
loader would, and checks it: no two items or loot tables share an Id, and every
BaseId, LootEntry.ItemId and LootEntry.LootTemplateId names something that
was written. This catches a TOML round-trip going wrong and two headers, say
Base-Item and base_item, that collide only once both go through ToSnakeCase.
Any problem exits 1 and lists every one, prefixed Verification failed:; a clean
run prints Verified <N> item(s) and <M> loot table(s) read back from disk.
Made with ❤️ by Tom
