Skip to content

Overview

Moongate logo

CI Security Audit Container image .NET 10 AGPL-3.0-or-later

An Ultima Online server written in C# on .NET 10, with reusable libraries for networking, persistence, scripting and internal APIs.

Terminal window
curl -fsSL https://moongate.sh/install.sh | sh

Installs the latest release into /opt/moongate and links it as moongate. See Install on Linux for the options, upgrades and removal.

Use First start to build and configure the server, or follow the Docker guide below. The configuration reference lists all TOML settings, CLI options and current implementation limits.

Every release publishes a linux/amd64 image to GitHub Container Registry. See Run with Docker for first-start configuration, persistent storage, Docker Compose, logs, and upgrades.

Moongate is under active development. The transport, packet pipeline, scripting, persistence and internal API infrastructure are in place; account login, realm selection and a playable world are not. Implementation status lists what works today, area by area.

Shard content runs in an embedded Lua 5.2 runtime (Moongate.Scripting, on LuaCSharp) that lives entirely on the game loop thread. Scripts sit under scripts/ in the server root; init.lua runs at startup, and require resolves only inside that directory, symbolic links included.

-- scripts/init.lua
log.info("booted {Engine} {Version}", engine.name, engine.version)
timer.every(30, function()
log.info("tick")
wait(2) -- parks this coroutine on the timer wheel
log.info("two seconds later")
end)
  • Modules: engine (name, version, codename, platform), log (debug, info, warning, error, Serilog templates), timer (after, every, cancel) and the global wait(seconds); print goes to the server log. Host modules are C# classes marked [ScriptModule] / [ScriptFunction] / [ScriptConstant], registered with AddScriptModule<T>() in Program.cs.
  • Budget: a deterministic instruction count, not a wall clock. A coroutine resume may run 150,000 instructions and a top-level chunk 10,000,000 before it is aborted with a script error; string.rep refuses results longer than 16,777,216 characters. The budget bounds VM execution; C# bindings must avoid blocking, and total memory is not capped.
  • Sandbox: base, string, table, math, coroutine and package only; no io, os, debug, dofile, loadfile, rawset or script-created coroutines.
  • Errors: every failure is logged with file and line and published as ScriptErrorEvent on the event bus; only an init.lua failure refuses the start.
  • Tooling: at startup the engine writes scripts/definitions.lua and scripts/.luarc.json, so an editor with the Lua language server completes every bound module, function, constant and enum. The console offers script reload <file> and script metrics.
[scripting]
bootstrap_file = "init.lua"
max_instructions_per_resume = 150000
max_instructions_per_chunk = 10000000
hook_interval = 1000
write_definitions = true
max_string_length = 16777216

Start with Writing Lua scripts. The package README documents the binding model and sandbox.

  • Writing a plugin: an assembly under plugins/ that registers services, commands, Lua modules and metric providers before the server starts.
  • Writing a Lua module: a C# class with [ScriptModule] and [ScriptFunction] that scripts call as a read-only table.
  • Registering a metric provider: an IMetricProvider whose samples join the diagnostics snapshot.
  • Loading TOML templates: an IDataLoader<TEntity> that reads shard content once at startup, plus EnumValueSpec<TEnum> for fields that resolve randomly.

The first three are shown by one compiled sample, samples/Moongate.Sample.Plugin, which the test suite loads through the real plugin loader.

The eight library packages have their own English READMEs and runnable examples. See NuGet libraries and package verification for the package list, dependencies, and the local verification command.

The documentation website includes the changelog, server guides, and library documentation. Releases publish the site automatically, and the same workflow can be run by hand between releases. See Writing documentation for local preview commands and how to contribute a page.

Read CONTRIBUTING.md for development setup, coding conventions, validation commands, and the pull request workflow. Contributions target develop.