Create a world, start it, log in.
Install the engine, then ask it for a game. What you get is a directory that belongs to you — the engine is a dependency, not something you copy and edit.
$ pip install megamoo
$ megamoo init mygame
Created /home/you/mygame
312 verb files, a starter world, and an empty game/ package
Wizard login: Wizard / winter-timber-7382
Shown once, and unique to this world -- write it down.That comes from PyPI. The same build is also on the releases page as a wheel, if you would rather pin an exact version or install without reaching PyPI at all.
Requires Python 3.10 or newer, and nothing else: the engine has no third-party dependencies.
Inside mygame:
world.db # the live world — objects, properties, players
verbs/ # the world's code, one file per verb
game/ # your own Python, imported by verbs
megamoo.toml # what to serve, and where
display_screen.txt # the splash players see before the login prompt
README.md # a reminder of the layout
.gitignore # keeps world.db and logs out of git
The starter world it copies in contains the base object library, an
out-of-character lobby, a character generator, one in-character room, and
a wizard account. Those 312 verb files are yours — edit them
freely. The engine keeps no copy you have to merge against, so upgrading
is pip install --upgrade megamoo and nothing you wrote is
touched.
The other side of that bargain used to be that improvements to the starter never reached a world already built from it. A verb fixed upstream stayed broken in your game, forever, because nothing could tell a verb you had edited from one you had simply never touched.
megamoo upgrade answers that. It compares your world against
the version it was created from and against the current starter, and sorts
every verb, object and property into what it can safely bring across and
what is yours:
megamoo upgrade world.db
World born from 0.10.0-beta21.
277 can be updated -- untouched here, changed upstream
1 can be added -- new upstream
1 dropped upstream, untouched here
Nothing was written. Re-run with --apply to make the 279 safe changes above.
It writes nothing without --apply, backs the world up before
it does, and refuses outright while a server has the world open. Anything
you changed that also changed upstream is reported as a conflict and left
exactly as you wrote it — an upgrade that overwrote your work would have
taken your world away from you, which is the one thing it must not do.
Every object carries an opaque identity from the moment it is created, so pairing survives both renumbering and renaming: rename a prototype and its fixes still reach it, and the rename is reported as yours. Worlds made before this pair by object number instead, which works until something renumbers and is refused rather than guessed at when it does.
Drop a newaccount_screen.txt beside
display_screen.txt and new players see it before
account creation asks them anything — house rules, the tone you
expect, a warning. Its last line is the prompt and keeps the
cursor, so you write the question yourself; only an answer
beginning enter goes on, and anything else ends the
connection. megamoo init writes no such file, and a
world that ships none is unaffected.
verbs/ and game/ are the source of your
world and belong in git. world.db is running state —
it holds player data and password hashes — and
megamoo init writes a .gitignore that
keeps it out for you.
From inside your game directory:
$ megamoo --dev
Server listening on 0.0.0.0:6770
API server listening on 127.0.0.1:7778Two ports are reported. The first is where players connect. The second is a private door for tooling on your own machine, which is what lets an AI assistant work inside the running world.
You do not choose either number: the server takes the first free port from 6770 for the game and 7778 for the API, and tells you what it got. If you need a specific port — because you have told people where to connect — ask for it, and you will either get that port or a clear error:
$ megamoo --dev --port 6777
Stop a world with Ctrl-C in its terminal, or from inside the game
with @shutdown.
--dev adds
It picks the database in the current directory, switches on the
tooling API with the shared token from
~/.megamoo/token, publishes a discovery file so
tooling can find the running world, and turns on verb auto-reload
so edits to command files land in the running game. For a public
world with real players, name the database instead —
megamoo world.db — the same server with none of those
four.
Two ways in. The browser client needs nothing installed —
megamoo --dev starts it and prints the address:
Play in a browser: http://127.0.0.1:8888/Or connect with any MUD client, or with telnet:
$ telnet localhost 6770The browser client is the quickest way to see your world and the easiest thing to hand somebody else on the same machine. It is the same world either way — the two are just different doors.
Log in as Wizard, with the password megamoo init printed
when it created your game. You arrive in the lobby with full authority over the
world.
$ megamoo init mygame
Created mygame
...
Wizard login: Wizard / winter-timber-7382
Shown once, and unique to this world -- write it down.
Every world gets its own, generated when it is created. There is no
default to look up and none in the released package — a shipped
password hash is a shipped secret. If you lose it before setting your
own with setpass, the quickest route is a fresh
megamoo init.
Wizard is a placeholder. That account is meant to become
your character, so the first thing worth doing is renaming it:
> @rname #100 = Malifax
Account #100:Wizard is now Malifax.
It logs in as 'Malifax'.
The login name moves with it. From the next connection you are
Malifax at the prompt and Wizard no longer
works. Set your own password at the same time, with
setpass <newpassword>.
@name
@name renames an object, and an account is two things:
the object, and the name in the login index that a player types at
the prompt. @name changes only the first, which would
leave you called by your own name while still logging in as
Wizard. @rname changes both, and refuses a
name that already belongs to another account rather than quietly
taking the login off them.
@name is still the right verb for rooms, objects and
exits — everything that nobody logs in as.
pip install --upgrade megamoo upgrades the engine, and
that part is genuinely painless: your verbs and your world are untouched,
because the engine keeps no copy of them to merge against. Restart and you
are running the new engine.
The starter world is a different matter, and it is worth being
plain about. A world is a copy taken the day you ran
megamoo init. Later releases improve the starter — a fixed
verb, a better default message, a new prototype — and none of that
reaches a world that already exists. There is no
megamoo upgrade. Bringing a world forward is a job you do by
hand, and this is it:
# 1. See what the new starter has that your world does not.
# Your world's verbs/ against the template inside the package:
$ python3 -c "import moo, pathlib; print(pathlib.Path(moo.__file__).parent / 'templates/starter/verbs')"
$ diff -rq <that path> verbs/
# 2. Copy in what is new or changed, and delete what the starter dropped.
# Skip any file you have edited yourself -- that one is yours.
# 3. Restart. Verb files are loaded from disk on the way up.
$ megamoo world.db
[autoreload] created #3:@alias from disk
[autoreload] updated #17:lock_ from diskStep 3 creates and updates, and does nothing else. A command whose
file you removed is still in world.db and still
reachable, with no source on disk to read — the worst of both. Remove
it in-game as well:
> @rmverb #17.lockUntil you do, that command survives every restart.
Those steps cover verbs, which is most of what changes. What they
do not cover is world.db itself: a corrected default message
on a prototype, a property added to #3, a fixed object in the
player pool. Those live in the database, and there is no supported way to
merge them into a world that is already running. If you want them, the
honest answer today is to make a fresh world and move your own work into
it.
The obstacle is telling a change apart from a customisation. If your world's copy of a message differs from the new starter's, that is either something you deliberately rewrote or simply the older default — and those want opposite treatment while looking identical.
Answering it needs to know what the world started as. From
beta13 a world records that: megamoo init stamps the
release it copied into the database, so a future tool can compare
three things — the original, yours, and the new one — instead of
guessing between two. Worlds created before beta13 carry no stamp and
report none, which at least lets a tool know not to guess.
Logging in sends a username and then a password, each as an ordinary line of text. On your own machine that does not matter. Once a world is reachable from anywhere else it does: anyone able to watch the traffic reads the password, and people reuse passwords.
Give the world a second, encrypted port:
$ megamoo world.db --tls-port 6771 --tls-cert cert.pem --tls-key key.pem
Server listening on 0.0.0.0:6770
TLS listening on 0.0.0.0:6771Both ports serve the same world. Which one a player is on changes nothing about the game — TLS sits underneath the connection, and everything above it is identical.
telnet cannot speak TLS, and telnet localhost 6770
is how most people first reach a world — it is the command on this page.
Encrypting the main port would break that for everyone in order to help
the people who already have a client that supports TLS. So the plain
port stays, and the encrypted one is additional. Tell players which to
use; MUD clients that support TLS have a checkbox for it.
A misconfiguration stops the server rather than quietly falling back to plaintext. A port with no certificate, a certificate with no port, a file that is not there, or a TLS port equal to the plain one — each of them refuses to start and says which it was. That is deliberate: the one outcome worth ruling out is a world that looks encrypted and is not.
For a certificate, certbot issues free ones from Let's Encrypt and renews them. They expire every ninety days, so renewal wants to be automatic; the server reads the files at startup, so a renewal takes effect on the next restart.
The web client is served over plain HTTP. If you publish it, put a
TLS-terminating proxy in front — Caddy obtains and renews certificates
on its own. It matters more than it looks: a browser will not open a
ws:// socket from an https:// page, so serving
the page over HTTPS without also terminating TLS for the socket leaves
you with a client that loads and cannot connect.
Each world is a separate process with its own file, so worlds cannot interfere with each other. Running two or three at once is normal practice: the world you care about, a scratch copy for trying things that might go badly, and perhaps last week's backup for comparison.
$ megamoo --dev world.db & # game 6770, API 7778
$ megamoo --dev scratch.db & # game 6771, API 7779
$ megamoo --dev backup.db & # game 6772, API 7780
The same command each time, with nothing to keep track of — each world takes
the next free ports and reports them. Connect to whichever you want by port:
telnet localhost 6771 reaches the scratch copy.
This is what makes copying worlds useful in practice. Testing a risky change
does not mean backing up and restoring. It means copying world.db, running the
copy alongside the original, breaking it freely, and deleting it when you are
done.
With several running it is worth being deliberate. The port you connected to tells you, and so does the world's name in its own terminal. If you are working with an AI assistant, ask it to confirm which world it is pointed at before you let it change anything — "scratch" and "the real one" are one word apart.