Configuration
The host loads <root>/config/moongate.toml with ConfigHelper.Load. A missing
file and its parent directory are created with defaults. Existing files are
deserialized and validated without being rewritten; omitted properties keep
their model defaults. Invalid TOML, invalid settings and filesystem errors fail
startup. Changes take effect at the next start; there is no configuration reload.
The welcome text shown when a character enters the world lives in a separate
data/motd.toml file, with its own variable and validation rules.
Complete default configuration
Section titled “Complete default configuration”TOML keys use snake_case. Keep mode before the first table header.
The server owns mode and the sections [shard], [network], [redis],
[persistence], [realm_directory], [world_save], [sql_backup], [diagnostics] and
[scripting]. Plugins own the others: [ultima] belongs to the Ultima plugin and
[admin_api] to the Administration plugin. A new root written by mgctl holds the server’s
sections (and [admin_api] when it sets up a certificate); at the first start each
plugin appends its missing section,
with the defaults, to the end of the file. See
Add a config section.
mode = "standalone" # Runs login and game services together.
[shard]shard_name = "Moongate"
[network]login_port = 2593game_port = 2595listen_address = "0.0.0.0"enable_ping_server = true # Answers UDP pings on ping_port.ping_port = 12000
[network.encryption]mode = "Disabled"client_version = ""
[redis]connection_string = "$MOONGATE_REDIS_CONNECTION_STRING"handoff_secret = "$MOONGATE_HANDOFF_SECRET"
# Administration plugin.[admin_api]enabled = false# Use "*" or "0.0.0.0" for all IPv4 interfaces (TLS required).listen_address = "127.0.0.1"port = 2590session_lifetime_minutes = 30# The longest a session lives from its login, renewals included (AdminSession.Refresh); never below the lifetime.session_max_age_minutes = 480max_receive_message_bytes = 65536max_concurrent_calls = 64allow_insecure_loopback = falsecertificate_path = ""certificate_password = ""
# Ultima plugin.[ultima]ultima_path = "ChangeMe" # Replace with your client data directory.show_motd_gump = true # Native Updates scroll; false uses private journal messages.
[ultima.localization]language = "eng" # Reads <root>/data/messages/eng.toml and eng/*.toml.
[ultima.line_of_sight]max_distance = 25 # Farthest cells along X or Y a point can see.
[ultima.world]view_range = 18 # How far players see mobiles and items, in cells along X or Y.seconds_per_uo_minute = 5 # Real seconds a game minute lasts: 5 makes a game day last 2 real hours.day_light = 0 # Light level of the day, from 0 (brightest) to 31.night_light = 12 # Light level of the night, from 0 (brightest) to 31.dungeon_light = 26 # Light level inside a dungeon region, from 0 (brightest) to 31.jail_light = 9 # Light level inside a jail region, from 0 (brightest) to 31.lamp_post_light = 6 # Light level from which the town lamp posts are lit, from 0 to 31.season_rotation = false # true: the maps change season every days_per_season game days.days_per_season = 12 # Game days a season lasts when they rotate, from 1 to 365.pathfinding_range = 38 # How far apart the two ends of a path search may be, in tiles, from 8 to 64.pathfinding_max_nodes = 1000 # Places a path search looks at before it gives up, from 50 to 20000.
[ultima.items]backpack_template = "backpack_2" # Item template of the backpack of new characters and spawned NPCs.gold_template = "gold_coin" # Item template of gold coins.
[ultima.starting_items]best_skills = 3 # How many of the highest skills pick skill sets.
[ultima.characters]max_per_account = 7 # Characters an account may hold: 1, 5, 6 or 7.deletion_delay_hours = 24 # Hours before a deleted character may be removed.
[ultima.npcs]think_interval_ms = 500 # Milliseconds between two thinks of an NPC near a player.sense_range = 8 # Cells within which an NPC's script senses another mobile.
[ultima.regeneration]hits_seconds = 11.0 # Seconds between two hit points coming back.stamina_seconds = 7.0 # Seconds between two points of stamina coming back.mana_seconds = 7.0 # The same for mana, with no intelligence and no Meditation.hunger_enabled = true # Players get hungry; starving, they get no hit points back.thirst_enabled = true # Players get thirsty; parched, they get no stamina back.hunger_minutes = 5 # Minutes between two points of hunger, and of thirst, lost by a player.fatigue_enabled = true # Running and moving overloaded cost a player stamina.
[ultima.skills]total_cap = 700 # Points the skills of a player add up to at most.gain_enabled = true # A skill, and a stat, may rise when a skill is tried.stat_max = 100 # The most a player's strength, dexterity or intelligence reaches by use.stat_cap = 225 # The most the three stats of a player add up to.stat_gain_minutes = 10 # Minutes a stat waits after it was tried before it is tried again.
[ultima.combat]global_attack_speed = 1.0 # Divides the delay between swings: 2 swings twice as often.attack_stamina = 0 # Stamina a swing costs a player; 0 costs nothing.npc_damage_rate = 1.0 # Divides the damage an NPC does to a player.max_range = 1 # Tiles a melee swing reaches.combatant_seconds = 60 # Seconds a fighter keeps its target without swinging.display_damage_numbers = true # The damage shows over the one hit.blood_enabled = true # A hit that does damage leaves blood on the ground.blood_pieces = 2 # Most pieces around the one under the victim, from 0 to 8.blood_seconds = 5 # Seconds a piece lies on the ground, from 1 to 60.item_wear_enabled = true # Weapons and armor wear out in a fight.archery_stand_still_seconds = 1.0 # Seconds a player must have stood still before it shoots; 0 for none.special_moves = false # The AOS weapon special moves: the client selects one, the next hit does it.resistances = false # The five resistances reduce the damage, as the AOS era does.
[ultima.crime]criminal_seconds = 120 # How long a mobile stays a criminal after its last criminal act.guards_enabled = true # Saying "guards" in a guarded region brings a guard beside a criminal.guard_template = "guard" # The mobile template of a guard that is called.archer_guard_template = "archerguard" # The one called in Ilshenar and Malas.guard_seconds = 40 # How long a called guard stays.loot_right_seconds = 120 # Seconds a creature's corpse is lootable with no crime only by those who earned it; 0 turns it off.
[ultima.murder]short_term_hours = 8 # Hours before a short-term murder is forgotten.long_term_hours = 40 # Hours before a reported kill is forgotten.report_delay_seconds = 4 # Seconds after a death before the victim is asked to report.recently_reported_minutes = 10 # Minutes before the same victim can report the same killer again.aggressor_seconds = 120 # Seconds an act keeps its attacker in the aggressor lists.
[ultima.spawns]initial_fill = true # The first spawn of each region after the start fills it to its max.
[ultima.jail]fine_gold = 500 # Gold coins taken from a prisoner when its jail sentence ends; 0 takes nothing.max_days = 30 # The longest sentence the jail gump accepts, in real days.
[ultima.help]stuck_wait_seconds = 5 # The seconds a character must stand still before "I am stuck" moves it.stuck_cooldown_minutes = 10 # The minutes before a player can use "I am stuck" again; 0 allows it at once.page_cooldown_seconds = 60 # The seconds between two requests of one player to the game masters; 0 allows it at once.page_history_days = 30 # The days a closed request is kept before it is deleted at startup.
[ultima.schedule]time_zone = "" # The IANA time zone of the hours in data/schedule.toml, such as Europe/Rome; empty is the zone of the system.
[ultima.bulletin_boards]expire_days = 7 # A thread of a bulletin board goes this many days after its last reply; 0 keeps it.max_messages = 50 # The messages a board holds; its oldest thread goes when it is full.thread_seconds = 120 # The wait between two new threads of one character on a board.reply_seconds = 30 # The wait between two posts of one character on a board.
[ultima.bank]max_items = 125 # Items a bank box holds, bags included; 0 for no limit.max_withdraw = 60000 # Coins a banker hands out at one time.min_check = 5000 # The smallest bank check a banker writes.max_check = 1000000 # The largest.
[ultima.party]max_members = 10 # Players a party holds, invitations waiting included, from 2 to 10.invite_timeout_seconds = 30 # Seconds an invitation waits for an answer.invite_range = 8 # Tiles away the invited player may stand.chat_max_length = 128 # Characters of a chat message; a longer one is cut.
[ultima.pets]max_followers = 5 # Followers a player may have, from 1 to 50: the slots of the creatures it tamed that are in the world, and of the one it rides.loyalty_drain_minutes = 60 # Minutes between two drains of the loyalty of the pets in the world, from 1 to 1440.loyalty_drain = 10 # Loyalty a pet loses at each drain, from 1 to 100; at 0 it goes wild.food_gain = 10 # Loyalty a pet gains for each item of food it eats, from 1 to 100.obey_gain = 1 # Loyalty a pet gains whenever it obeys an order, from 0 to 100.disobey_loss = 3 # Loyalty a pet loses when it refuses one, from 0 to 100.bonding_days = 7 # Days between the first food of its owner and the food that bonds a pet, from 0 to 365.hire_pay_minutes = 30 # Minutes of one period a hireling is paid for, from 1 to 1440: its gold pays one period of its daily pay at the end of each.
[ultima.stable]max_pets = 10 # Pets a player may leave with the stablemasters, from 1 to 50.fee = 30 # Gold a pet costs when it is stabled, from the backpack and then the bank; 0 makes it free.
[ultima.vendors]uncommon_multiplier = 1.25 # What a vendor pays for an uncommon item, as a multiple of the price of its shop, from 1 to 100.rare_multiplier = 1.5 # The same for a rare item.epic_multiplier = 2.0 # The same for an epic item.legendary_multiplier = 3.0 # The same for a legendary item. A common item is paid the price of the shop.
[persistence]auto_sync_schema = falseauto_generate_migrations = false # true: a development start writes and applies additive SQL; needs migrations_directory.migrations_directory = "${MOONGATE_ROOT}/migrations"auto_apply_migrations = false # true: a start adds the bundled core SQL and applies what is pending.
[persistence.accounts]connection_string = "postgres://moongate:moongate@localhost:5432/auth"
[persistence.realm]connection_string = "postgres://moongate:moongate@localhost:5432/world"
[realm_directory]realm_id = ""name = ""server_index = 0advertised_address = ""advertised_port = 0minimum_account_type = "regular"heartbeat_interval_seconds = 5lease_duration_seconds = 15max_realms = 128
[world_save]enabled = true # Enables periodic saves; manual/final saves remain available.interval_seconds = 300
[sql_backup]enabled = false # Scheduled SQL backups; the sql_backup command works either way.interval_minutes = 1440directory = "backups" # Relative to the server root.keep = 5 # Copies kept for each database.
[diagnostics]enabled = trueinterval_seconds = 5log_metrics = false
[scripting]bootstrap_file = "init.lua" # Relative to <root>/scripts.max_instructions_per_resume = 150000max_instructions_per_chunk = 10000000hook_interval = 1000write_definitions = truemax_string_length = 16777216Only the databases for the active role must already exist and accept connections:
Accounts for login, Realm for game, and both for standalone.
The defaults use local development credentials moongate / moongate; an existing
configuration file is not rewritten. For deployment, set each connection_string
to a secret-provider environment reference such as $MOONGATE_ACCOUNTS_DATABASE
or $MOONGATE_REALM_DATABASE. Merely exporting those variables does not override
a literal URI in the TOML file.
MoongatePersistenceService opens each active database and runs SELECT 1. The Redis service also checks its connection before accepting clients. Each PostgreSQL success
logs Postgres connection successful with the target and endpoint, without credentials.
A connection or ping failure throws and prevents other services from starting.
Moongate does not create missing databases; schema and migration checks run after
the connection checks. See PostgreSQL persistence.
Settings and validation
Section titled “Settings and validation”| Setting | Meaning and limits |
|---|---|
mode |
login, game or standalone; default standalone. Login runs account authentication, a login packet listener and realm directory; Game runs world services and publishes its realm to Redis; Standalone runs both roles and publishes its local realm to Redis. See TOML value types. |
shard.shard_name |
Shard display metadata; used as the standalone list name when it fits the 32-character ASCII wire limit. Otherwise the local list name defaults to Moongate. |
network.login_port |
Login TCP listener port; default 2593. Used in login and standalone modes. |
network.game_port |
Game TCP listener port; default 2595. Used in game and standalone modes. Standalone rejects equal login and game ports. |
network.listen_address |
IP literal, not a DNS hostname. 0.0.0.0 makes the host enumerate local unicast addresses and create an endpoint for each active role on every address, including IPv6 addresses; it is not a single wildcard listener. Standalone therefore starts two listeners per address. Use a specific IP to restrict binding. |
network.enable_ping_server |
Default true. Starts the UDP ping server in every mode: it sends each datagram of at most 64 bytes back to its sender, so a client can measure the latency of the shard; a larger datagram gets no answer, and neither does one that comes from the ping port itself (the echo of another ping server). It binds network.ping_port on the addresses of network.listen_address. An address or port that cannot be bound is logged as a warning and the server still starts. It does not affect the UO ping packet handler on the game connection. |
network.ping_port |
UDP port of the ping server; default 12000, the port ModernUO uses. Must be between 1 and 65535 when the ping server is enabled. |
network.encryption.mode |
Disabled (default), Optional or Required; applies to both UO listeners. See UO client encryption. |
network.encryption.client_version |
Raw POL wire version used to derive login keys and select the game cipher family. Required for Optional and Required; ignored when Disabled. Default empty. |
ultima.ultima_path |
Existing, readable client data directory. Path and environment expansion apply; relative paths use the process working directory. It must contain tiledata.mul, the map and statics files of every map in data/maps.toml, and MultiCollection.uop or multi.idx with multi.mul; the server stops at startup when one is missing. |
ultima.show_motd_gump |
Defaults to true: show the MOTD in the native Updates scroll on each entry. false keeps private Unicode journal messages. Restart to apply. |
persistence.auto_sync_schema |
Defaults to false. Normal startup checks versioned SQL history; when false it also fails if registered entities require DDL. Generate and review SQL, then apply it with the separate migration runner. Enable only as an explicit development convenience. |
persistence.auto_generate_migrations |
Defaults to false. A development convenience: a start writes additive SQL from the registered entities into migrations_directory and applies it. It requires an explicit migrations_directory and is exclusive with auto_sync_schema: either one stops the start. Keep it off in deployment. See Generate, review and apply. |
persistence.migrations_directory |
Optional unless auto_generate_migrations is on. The directory of the reviewed SQL; environment variables and ~ are expanded. Without it the migrations are looked for in migrations of the root. mgctl init writes the absolute path into the new config. |
persistence.auto_apply_migrations |
Defaults to false. When true, before the schema check a start does what mgctl init and mgctl migrate apply do for the migrations: it adds to the migrations directory the core SQL this release ships and the root lacks, replacing nothing, then applies the pending reviewed SQL of the databases this process uses, the SQL of the plugin bundles in plugins/ included. It never writes SQL from the entities. The database role of the connection must be allowed to change the schema, and mgctl must be beside the server, as in every distribution. A file of the root with the number of a shipped one and another name or content stops the start. It conflicts with auto_sync_schema. See Apply at startup. |
persistence.accounts.connection_string |
Accounts/login PostgreSQL URI, or $NAME / ${NAME} environment reference. Resolved only when registered entities use Accounts. |
persistence.realm.connection_string |
This realm’s PostgreSQL URI, or $NAME / ${NAME} environment reference. Resolved only when registered entities use Realm. |
redis.connection_string |
Shared Redis endpoint and password in StackExchange.Redis format, or a $NAME / ${NAME} environment reference. Required by every runtime role. |
redis.handoff_secret |
Separate cluster-wide proof secret or environment reference. Required by every runtime role; at least 32 bytes after UTF-8 encoding. Never reuse the Redis password. |
realm_directory.realm_id |
Stable ID for a game realm and its Redis lease/ticket namespace. Standalone defaults to local; give each independently running realm a distinct ID. |
realm_directory.name, server_index |
ASCII list name (at most 32 characters) and unique index (0–65535). Standalone defaults to the shard name and index zero; set a distinct index for each realm sharing Redis. |
realm_directory.advertised_address, advertised_port |
Client-facing IPv4 literal and port. Required in game mode; standalone defaults to loopback and network.game_port. 0xA8 carries the address; 0x8C carries the selected realm port. |
realm_directory.minimum_account_type |
Lowest account level allowed to see the realm; regular, game_master or administrator. See TOML value types. |
realm_directory.heartbeat_interval_seconds, lease_duration_seconds, max_realms |
Defaults 5, 15 and 128. Lease duration must exceed two heartbeats; the Redis-backed directory caps realms at 128. |
world_save.enabled |
Starts periodic autosaving when true. Does not disable explicit saves or the eligible final shutdown save. |
world_save.interval_seconds |
Positive integer seconds, validated even when autosaving is disabled. |
sql_backup.enabled |
Runs a SQL backup on the schedule when true. The sql_backup command works either way. See Database backups. |
sql_backup.interval_minutes |
Minutes between two scheduled backups, from 1 to 71582; default 1440. The first one runs a full interval after startup. Validated even when disabled. |
sql_backup.directory |
Where the files go; default backups. A relative path is resolved against the server root; environment variables are expanded. |
sql_backup.keep |
Files kept for each database, 1 or more; default 5. |
diagnostics.enabled |
Starts the periodic diagnostic collector when true. |
diagnostics.interval_seconds |
Positive integer seconds; must fit the timer range (at most 4,294,967 seconds). |
diagnostics.log_metrics |
Logs periodic collected metrics when true. |
scripting.bootstrap_file |
Nonblank path resolved within the scripts root; must satisfy the script path restrictions. Missing file is a warning, execution failure aborts startup. |
scripting.max_instructions_per_resume |
Positive instruction budget for one coroutine resume. |
scripting.max_instructions_per_chunk |
Positive instruction budget for top-level chunk execution. |
scripting.hook_interval |
Positive instruction-check interval, no greater than either instruction budget. |
scripting.write_definitions |
Generates definitions.lua and .luarc.json for editor support. |
scripting.max_string_length |
Positive maximum result length enforced by string.rep, measured in UTF-16 characters; not a global Lua memory limit. |
ultima.localization.language |
Code of ASCII letters naming the texts file data/messages/<language>.toml and the directory data/messages/<language>/ of toml files; default eng. Shipped: eng, ita, ger, fre, spa, por, pol, cze. The English texts must also exist: a message missing from the chosen language falls back to English. Used in game and standalone modes. See Localization. |
ultima.line_of_sight.max_distance |
From 1 to 255; default 25. The farthest a point can see along X or Y, as ModernUO; farther points are never in sight. Used in game and standalone modes. |
ultima.world.view_range |
From 5 to 24; default 18, as ModernUO and POL. How far players see mobiles and ground items along X or Y; the client’s 0xC8 request is answered with it. Used in game and standalone modes. |
ultima.world.seconds_per_uo_minute |
From 1 to 3600; default 5, as ModernUO: a game day lasts 2 real hours. The time of day is counted from ModernUO’s world start and needs no save; each map runs 320 game minutes after the previous one (Felucca, Trammel, Ilshenar, Malas, Tokuno, Ter Mur), and the time moves one minute later every 16 tiles east. |
ultima.world.day_light, ultima.world.night_light |
From 0 (brightest) to 31; defaults 0 and 12, as ModernUO. Night from 00:00 to 03:59, brightening until 06:00, day until 21:59, darkening until midnight. Players are sent their light at login and, when it changes, every 5 seconds; .globallight overrides it for everyone. |
ultima.world.dungeon_light, ultima.world.jail_light |
From 0 (brightest) to 31; defaults 26 and 9, as ModernUO. The light inside a region of type dungeon or jail, whatever the time of day; sent as soon as a player walks in or out. .globallight still wins, so a game master can see. |
ultima.world.lamp_post_light |
From 0 to 31; default 6. Every 30 seconds the town lamp posts placed by .decorate (kinds LampPost1 to LampPost3) are lit where the light of the time of day (or .globallight) is at least this level, and doused where it is below; with the default night of 12 that is about 23:00 to 05:00, game time. Regions do not count. |
ultima.world.season_rotation, ultima.world.days_per_season |
false and 12 (1 to 365). When on, every map whose maps.toml season is not desolation goes spring, summer, fall, winter from that season, changing every days_per_season game days of its clock; a region with its own season keeps it. Players get the new season within a minute. See Seasons. |
ultima.world.pathfinding_range, ultima.world.pathfinding_max_nodes |
38 (8 to 64) and 1000 (50 to 20000), as ModernUO. The bounds of a path search: how far apart its two ends may be along X or Y, and how many places it looks at before it gives up. A search that gives up costs about a millisecond per hundred places |
ultima.items.backpack_template, ultima.items.gold_template |
Item template ids; defaults backpack_2 and gold_coin. Used for the backpack of new characters and spawned NPCs and for the gold of spawned NPCs; the starting gold of new characters is an item of the common set in starting_items.toml. Both must exist in templates/items/, and the gold template must stack, or the game server stops at startup. See Starting items. |
ultima.starting_items.best_skills |
At least 1; default 3, as UOX3 (four with its extended starting skills). How many of a new character’s highest skills pick skill sets. |
ultima.characters.max_per_account |
1, 5, 6 or 7, the slot counts the client can show; default 7. How many characters an account may hold. The game-login character list shows this many slots, and creating a character beyond it is refused with a popup and a disconnect. A new character goes in the slot the client chose when it is free, otherwise in the first free one. Lowering it keeps existing characters: those beyond the new count are listed in the first free slots, while the rest stay stored but hidden. |
ultima.characters.deletion_delay_hours |
At least 1; default 24. When a player deletes a character (packet 0x83) it is only marked: it leaves the character list, gives up its slot and no longer counts toward max_per_account, and staff can restore it with character restore (into the first free slot). After this many hours it becomes eligible for removal; the job that removes it is not built yet. |
ultima.npcs.think_interval_ms |
From 50 to 60000; default 500, ModernUO’s passive speed. How often an NPC near a player thinks. Only NPCs within the 5×5 sectors around a player have a think timer; the others sleep and cost nothing. See NPC tick. |
ultima.npcs.sense_range |
From 1 to 24; default 8. How near, in cells along X or Y, another mobile must come for an NPC’s mobile script to sense it with on_mobile_in_range. See Mobile scripts. |
ultima.regeneration.hits_seconds, stamina_seconds, mana_seconds |
Each from 0.1 to 3600; defaults 11, 7 and 7, ModernUO’s classic rates. The seconds between two points coming back, one point at a time. Mana is the slowest it gets: intelligence and Meditation shorten it by ModernUO’s classic curve, down to about a tenth of it. The players in the world are checked every second and an NPC while it thinks, so one asleep far from every player does not regenerate; a rate faster than the check gives the points of the whole interval at once, five at most. A mobile’s props regen.hits, regen.mana and regen.stamina (seconds) replace the rate for that mobile. |
ultima.regeneration.hunger_enabled, hunger_minutes |
Default true and 5 (from 1 to 1440), ModernUO’s food decay. Every hunger_minutes, counted for each player from when it entered the world (one timer looks at the players every minute), a player in the world loses a point of hunger, from 20 (full) to 0; game masters and administrators do not, and never starve. At 5 the player reads that it is hungry, at 0 that it starves, and while at 0 its hit points do not come back. Off, hunger neither drops nor stops anything. NPCs are never hungry. |
ultima.regeneration.thirst_enabled |
Default true. Thirst is counted as hunger is, from 20 (quenched) to 0 and at the same pace (hunger_minutes): a player in the world loses a point of each together, and the staff none. At 5 the player reads that it is thirsty, at 0 that it is parched, and while at 0 its stamina does not come back, as in UOX3. Off, thirst neither drops nor stops anything. NPCs are never thirsty. |
ultima.regeneration.fatigue_enabled |
Default true. What moving costs a player, with ModernUO’s numbers. A player carries what it wears and everything inside, the bank left out, and may carry 40 stones and 3.5 a point of strength; its status bar shows both. Carrying more, every step takes 5 points of stamina and one more every 25 stones over, twice as much running; with none left it does not move and reads the client’s “too fatigued to move, because you are carrying too much weight”. Running takes a point every 16 steps, and a point a step below a tenth of the stamina; with none left a player walks but does not run. Lifting is never refused: a player that puts down something while overloaded is told so. The staff and NPCs pay nothing. Off, a step costs nothing and is never refused. |
ultima.skills.total_cap, ultima.skills.gain_enabled |
700 (from 1 to 100000) and true. The points the skills of a player add up to at most, ModernUO’s 700.0: the nearer the total, the more often a gain lowers another skill whose lock is down, and at the cap a skill rises only when one could be lowered. A total already above a lowered cap is left as it is. With gain_enabled = false no skill rises: the checks still pass and fail. NPCs never gain. |
ultima.skills.stat_max, ultima.skills.stat_cap, ultima.skills.stat_gain_minutes |
100 (from 1 to 65535), 225 (from 30 to 100000) and 10 (from 0 to 1440; 0 tries a stat at every successful skill). ModernUO’s classic stat gain: the most a stat reaches by use, the most the three add up to, and the minutes between two tries of one stat. Over a lowered stat_cap or stat_max a stat is left as it is, but one locked down still gives a point each time another stat is tried. gain_enabled = false stops these too. NPCs never gain. |
ultima.combat.global_attack_speed, npc_damage_rate |
1.0 each, above 0 and at most 100. The factor that divides the delay between two swings (2 swings twice as often, 0.5 half as often) and the number that divides the damage an NPC does to a player (2 halves it). |
ultima.combat.attack_stamina |
0 (from 0 to 100). The stamina a swing costs a player; ModernUO takes none, UOX3 takes 2. NPCs pay nothing. |
ultima.combat.max_range, combatant_seconds |
1 (from 1 to 24) and 60 (from 1 to 3600). How many tiles a melee swing reaches, and how long a fighter keeps its target without swinging, as ModernUO’s minute. |
ultima.combat.archery_stand_still_seconds |
1.0 (from 0 to 60). The seconds a player holding a bow or a crossbow must have stood still, since its last step, before it can shoot; a turn does not count. 0 asks for nothing. See Archers |
ultima.combat.display_damage_numbers |
true. The damage of a hit shows over the one hit (packet 0x0B), to the players in the fight. |
ultima.combat.blood_enabled, blood_pieces, blood_seconds |
true, 2 (from 0 to 8) and 5 (from 1 to 60). A hit that does damage leaves a piece of blood under the one hit and from one to blood_pieces around it; each piece goes after blood_seconds, which the check of the ground items every 5 seconds can stretch. |
ultima.combat.special_moves |
false. The AOS-era weapon special moves: a player selects the move of the held weapon with the client’s ability icon and the next hit that lands spends the mana and does it. Off, as the classic ruleset, the selection is read and ignored. |
ultima.combat.resistances |
false. The five resistances (physical, fire, cold, poison, energy) of a mobile and its worn armor reduce the damage it takes, up to 70 percent for a player. Off, as the classic ruleset, armor absorbs a blow and the damage of a spell ignores the resistances. |
ultima.combat.item_wear_enabled |
true. The weapon and the armor of a player lose hits in a fight, see Durability. Off, nothing wears and nothing is destroyed; the pieces keep the hits they have. |
ultima.crime.criminal_seconds |
Default 120 (from 1 to 86400), ModernUO’s and UOX3’s two minutes. A mobile that did a criminal act is a criminal for this long, its name in grey for those who see it; another act starts the time again, and a murderer stays red. The time is saved with a character, so leaving the world does not clear it; an NPC comes back innocent after a restart. A player reads the client’s “You’ve committed a criminal act!!” when it becomes one. A player becomes one by attacking an innocent or by looting an innocent’s corpse; scripts do with mobile.set_criminal, the staff with set criminal. |
ultima.murder.* |
The murder counts, as ModernUO’s. short_term_hours (default 8) and long_term_hours (default 40), from 1 to 8760: a short-term murder and a reported kill are each forgotten after that time, one by one, in real time, offline hours included (ModernUO counts online time), while the player is in the world or when it comes back. report_delay_seconds (default 4), recently_reported_minutes (default 10) and aggressor_seconds (default 120), from 1 to 86400: how long after its death a player is asked to report who attacked it, how long before it can report the same killer again, and how long an act keeps its attacker in the aggressor lists, which also decide who is reportable. See Murder counts. |
ultima.crime.archer_guard_template |
Default archerguard. The mobile template of a guard that is called in Ilshenar and Malas, as ModernUO’s DefaultGuardType: a guard with a bow, which shoots what it goes for from the bow’s range instead of striking from beside it (see guard.lua). Everywhere else the guard of guard_template comes. |
ultima.crime.guards_enabled, guard_template, guard_seconds |
Default true, guard and 40 (from 1 to 86400). A player that says “guards” in a guarded region, by the keyword of its client in any language or by the plain word, calls the guards: for every criminal within 14 tiles that stands in a guarded region itself, the staff left out, an NPC of guard_template appears beside it, on a free tile a step away (on it when none is free), with the teleport effect and sound and says its line (message 30138); a criminal gets one guard at a time. The guard leaves the same way after guard_seconds. A guard kills what it goes for with one blow, and an archer guard shoots it. ModernUO is stricter about who can be called on: there, only who did its crime in that town in the last 15 seconds, and a murderer; here, anyone who is a criminal. A called guard bears the prop guard.summoned: one a stopped server left in the world is removed at the next start. Off, nobody comes. |
ultima.crime.loot_right_seconds |
Default 120 (from 0 to 86400), ModernUO’s two minutes; 0 turns the rule off. For this long after a creature dies only the players that earned the right, and the master of a tamed creature, loot its corpse with no crime; anyone else who takes an item from it is a criminal, and a creature nobody hurt enough is a crime for everyone until the time is over. Staff are free. See Loot rights of a creature. |
ultima.spawns.initial_fill |
Default true. The first spawn of each spawn region after the start fills it to its max at once, so an empty world is full in about 10 minutes; false keeps UOX3’s way, where the first spawn also brings only call NPCs. Used in game and standalone modes. See NPC spawns. |
ultima.jail.fine_gold, ultima.jail.max_days |
Defaults 500 (from 0 to 1,000,000,000) and 30 (from 1 to 3650). The gold taken from a prisoner when its sentence ends, from its backpack and then its bank box, and the longest sentence .jail accepts, in real days; see Jail. |
ultima.help.stuck_wait_seconds, ultima.help.stuck_cooldown_minutes |
Defaults 5 (from 1 to 60) and 10 (from 0 to 1440). The wait and the pause of the “I am stuck” button of the help gump. |
ultima.help.page_cooldown_seconds, ultima.help.page_history_days |
Defaults 60 (from 0 to 3600) and 30 (from 1 to 3650). The seconds a player waits between two requests to the game masters, and the days a closed request is kept before startup deletes it; see Help. |
ultima.schedule.time_zone |
Empty (the zone of the system) or an IANA id such as Europe/Rome. An unknown id stops the startup. A Docker container is in UTC unless it has TZ set or this setting; on Linux the zones need the tzdata package. See Schedule. |
ultima.bulletin_boards.expire_days, max_messages, thread_seconds, reply_seconds |
Defaults 7 (from 0 to 3650; 0 keeps threads forever), 50 (from 1 to 200), 120 and 30 (from 0 to 86400). How long a thread of a bulletin board lasts after its last reply, how many messages a board holds, and how long a character waits between two new threads and between two posts on one board. |
ultima.bank.max_items, max_withdraw, min_check, max_check |
Defaults 125 (from 0 to 10000; 0 for no limit), 60000 (from 1 to 60000), 5000 and 1,000,000 (min_check from 1 to max_check, max_check up to 2,000,000,000). The items a bank box holds, counted with what is inside its bags; the coins a banker hands out for one withdraw; the worth of the checks a banker writes. |
ultima.party.max_members, invite_timeout_seconds, invite_range, chat_max_length |
Defaults 10 (from 2 to 10), 30 (from 1), 8 (from 1) and 128 (from 1). The players a party holds, counting those invited who have not answered; the seconds an invitation waits before it is declined; the tiles away the invited player may stand; the characters of a chat message. |
ultima.stable.max_pets, ultima.stable.fee |
Defaults 10 (from 1 to 50) and 30 (from 0 to 100000). The pets a player may leave with an animal trainer, and what each costs when it is stabled: see the stable. |
ultima.pets.max_followers |
Default 5 (from 1 to 50). The followers a player may have: see animal taming. |
ultima.pets.loyalty_drain_minutes, loyalty_drain, food_gain, obey_gain, disobey_loss, bonding_days, hire_pay_minutes |
Defaults 60 (from 1 to 1440), 10 (1 to 100), 10 (1 to 100), 1 (0 to 100), 3 (0 to 100), 7 (0 to 365) and 30 (1 to 1440). How often and how much a pet’s loyalty falls, what a piece of food gives back and what an obeyed order gives whenever the pet obeys, and what a refusal costs, the days of feeding that bond a pet, and the minutes of a period paid to a hireling: see animal taming and bonding. |
The gameplay settings live under [ultima] as sub-tables ([ultima.world],
[ultima.characters], …). The starting gold is not a setting: it is an item of the
common set in starting_items.toml.
Redis is required at runtime in all three modes, including standalone. redis.connection_string is a StackExchange.Redis configuration string or an environment reference resolved at startup; the Docker example uses redis:6379,password=... on its private bridge. redis.handoff_secret is an independent cluster-wide secret, also supplied through an environment reference. Give the login and every game process the same values. The Docker example reads both from separate Compose secrets; keep the actual values out of TOML and the repository. A Redis connection failure prevents startup. A later Redis outage stops new realm lists and handoffs while existing game sessions continue; pending tickets are lost on Redis restart and game processes republish their leases. Configure Redis with maxmemory-policy noeviction.
Game-loop queue limits, timer-wheel resolution and packet dispatch limits use C# option objects rather than additional TOML sections. See Game loop and timers, Packets and the Docker topology.
Plugin sections
Section titled “Plugin sections”Some sections of config/moongate.toml belong to plugins rather than to the server:
[ultima] (with its sub-tables) to the Ultima plugin, [admin_api] to the
Administration plugin, and any section a disk plugin
adds. They are edited like any other section; what differs is who reads them and when.
- The server reads the file and keeps its own sections, ignoring the rest.
- Each plugin, while it registers, reads its section and checks its values. A value
that is not allowed stops the start with an error that names the setting,
reported as the cause of
Plugin '<id>' failed during registration. - A plugin whose section is missing uses its defaults and appends them to the end of the file. The lines already there, comments included, are not changed. Edit the appended values and restart to change them. If the file is read-only, the server logs a warning and runs with the defaults.
So a new root starts with the server’s sections only, and after the first start the
file lists every setting of every installed plugin. Removing a plugin leaves its
section in the file; the server ignores it. Two owners cannot share a name: a plugin
that claims network, or a name another plugin already took, stops the start.
Command line and root directory
Section titled “Command line and root directory”Inspect the installed executable with --help. From a checkout:
dotnet run --project src/Moongate.Server -c Release -- --helpdotnet run --project src/Moongate.Server -c Release -- \ --root-directory /absolute/path/to/moongate-data --pid-file-name moongate.pid| Option | Default | Current behavior |
|---|---|---|
--root-directory <path> |
Unset | Overrides MOONGATE_ROOT; with neither set the server refuses to start |
--pid-file-name <name> |
moongate.pid |
PID filename under the chosen root; use a plain filename |
--log-level <level> |
Information |
Parsed into server arguments, but currently not applied to the Serilog level policy |
--log-to-file |
true |
File logging is enabled; the generated parser only accepts this as a presence flag |
--log-packets |
false |
Sets the argument to true; currently no packet-tracing consumer |
--show-header |
true |
Shows the startup banner; presence flag |
--persistence-schema <mode> |
None |
preview prints draft PostgreSQL DDL; generate writes a draft file. The old apply mode directs you to mgctl migrate apply |
--migration-target <target> |
Unset | Required by generate: auth or world |
--migration-output <path> |
Unset | Required by generate: new NNNN_description.sql file; refuses overwrite |
--initialize-root |
false |
Prepare config/directories/bundled migrations offline, without starting the server |
--generate-admin-certificate |
false |
With --initialize-root, create or reuse the administration TLS identity and enable [admin_api] |
--admin-certificate-hosts <names> |
Unset | Comma-separated DNS/IP SANs in addition to localhost; requires certificate generation |
--version |
— | Prints executable version |
-h, --help |
— | Prints usage |
Although help displays <bool> for the default-true options, the current CLI
does not accept --show-header false, --show-header=false or corresponding
file-logging forms. There is no CLI switch to turn these two options off yet.
Root precedence is command line → MOONGATE_ROOT. With neither set the root would
be the directory holding the binary, and the server refuses to start with exit code 2,
naming --root-directory. The same refusal applies when either resolves to that
directory, because an upgrade replaces it wholesale and would delete config/, logs/,
save/ and world-saves/ with it.
The chosen root expands home/environment references and becomes an absolute path;
a relative root starts from the working directory. Prefer explicit absolute paths
in service managers and containers. Docker sets MOONGATE_ROOT=/data by default.
The schema command loads plugin persistence registrations but does not acquire the normal PID, start listeners/services, or generate runtime files. Stop the affected runtime before applying reviewed DDL. See First start for PID ownership, logs and troubleshooting, PostgreSQL persistence for connection, schema and world-save semantics, and Lua scripting for budgets and sandbox boundaries.
Versioned SQL is applied by mgctl migrate status|apply --target auth|world, a process
of its own. In released artifacts its default root is its own directory, the server’s; --root-directory and MOONGATE_ROOT override
it. See Generate, review and apply.
Administration endpoint
Section titled “Administration endpoint”[admin_api] configures the embedded gRPC plugin. It is disabled by default and uses server TLS on port 2590 when enabled. Certificate paths resolve relative to the root; password environment references are resolved only for enabled endpoints. Use mgctl certificate setup to generate a passwordless PFX and enable the endpoint offline. See Administration API for roles, permissions, first-admin provisioning and all limits.
UO client encryption
Section titled “UO client encryption”MoongateServerConfig.Network.Encryption configures both UO listeners. Mode is the
NetworkEncryptionMode enum: Disabled (default), Optional, or Required.
| Mode | Connections accepted |
|---|---|
Disabled |
Existing plaintext protocol; no encryption middleware is installed. |
Optional |
Valid plaintext login packets, or login packets that decrypt correctly with the configured profile. |
Required |
Rejects plaintext login packets; requires a valid login packet after decryption with the configured profile. |
Enabled modes require an explicit POL client version and select one encrypted
profile per configuration; they do not try multiple versions. Login keys are
derived from that version, while game encryption is selected as OldBlowfish,
Blowfish12536, Blowfish, BlowfishTwofish, or Twofish. Versions in the same modern
Twofish family share the game cipher, so this setting is not an exact client-version
check on the game listener. The special version 2.0.0x is supported. Empty or
malformed versions and the no-crypt aliases none, ignition and uorice prevent
startup in Optional and Required modes.
Optional first checks the initial login packet with the normal packet parser.
If it is not valid plaintext, the server attempts decryption and validates the
result; failed validation rejects the connection. It does not fall back to
plaintext after failed decryption. For a legacy four-byte login seed, enabled
encryption supplies the configured version to the login handler because the
client has not sent a version with that seed.
For Enhanced Client 67.0.117.0, use:
[network.encryption]mode = "Optional"client_version = "67.0.117.0"Use the wire version, including the Enhanced Client’s 60 major-version offset.
The inspected Enhanced Client executable with file version 4.0.117.0 uses the
keys for wire version 67.0.117.0; using 4.0.117.0 or 7.0.117.0 produces
different login keys. Keep the original client encryption enabled when testing
this configuration. A launcher that removes encryption needs Optional or
Disabled. Changing the setting requires a restart; startup reports, for example:
Client encryption: Optional; client 67.0.117.0; login XOR; game Twofish / MD5-XORFor separate login and game processes, configure the same policy and profile on
each and restart both after changes. See Network for handshake and
transport details.
The feature provides POL protocol interoperability, not TLS or authenticated
transport. It does not implement the old Kingdom Reborn AES/E3 negotiation.
Automated tests compare against original POL C++ vectors and exercise real TCP
framing and compression. An interactive login was made with Enhanced Client 4.0.117
(67.0.117.0); another build still requires a client-side test. See
Enhanced Client for what works after the login.
Made with ❤️ by Tom
