Skip to content

Your first plugin

This tutorial installs a community plugin, configures it and uses it from a Lua script, end to end. Part 1 uses Moongate.Plugins.Notifications with its log channel: it needs no account and sends nothing outside your machine. Part 2 repeats the steps with Moongate.Plugins.OpenAi, which needs an API key.

You need:

  • a prepared server root (mgctl init, see First start) and a game master account with a character in the world;
  • mgctl from the same release as the server: mgctl --version prints the version, and a plugin is built for exactly one;
  • an internet connection for mgctl to reach Codeberg.

The commands below use /srv/moongate as the root. Replace it with yours.

The server reads plugins/ once, at start, so stop it first. Then:

Terminal window
mgctl plugin install Moongate.Plugins.Notifications --root-directory /srv/moongate

With a plugin id, mgctl looks in the community repository and picks the newest build made for your Moongate. It shows what it found and asks before it installs:

Plugin Moongate.Plugins.Notifications 0.2.0
Built for Moongate 0.17.0
Found in Codeberg moongate/moongate-plugins
SHA-256 a483f510ed545d1bdcee50a0874e8d518375e60aaaeddf7c85a18381f263a268
A plugin runs inside the server with all its permissions: install it only from a source you trust.
Install it? [y/N]

Answer y. Check the result:

Terminal window
mgctl plugin list --root-directory /srv/moongate
Plugin Version Built for Origin
Moongate.Plugins.Notifications 0.2.0 0.17.0 package

The plugin is now the folder /srv/moongate/plugins/Moongate.Plugins.Notifications/.

Start the server and stop it again. The plugin appends its [notifications] table to config/moongate.toml at its first start, switched off. Open the file and make the table say this, keeping what the plugin appended if it differs:

[notifications]
enabled = true
[notifications.channels.audit]
type = "log"

A channel is where notifications go. log writes one line of the server log for each notification; Discord and webhook channels are configured the same way, with an address that you keep in an environment variable (Telegram and SMTP come from their own plugins). The keys are on the plugin’s own page, in its repository.

The plugin adds a Lua module, notify. To try it you need something that runs a script: a small NPC that rings the staff bell when a player says “alarm”.

Create templates/mobiles/tutorial_bell.toml:

[[mobile]]
id = "tutorial_bell"
base_id = "male"
name = "Bell Ringer"
script_id = "tutorial_bell"
notoriety = "invulnerable"

Create scripts/mobiles/tutorial_bell.lua:

tutorial_bell = {}
function tutorial_bell.on_speech(serial, speaker, text)
if not text:lower():find("alarm", 1, true) then
return
end
local sent = notify.send{
topic = "staff.tutorial",
level = "info",
title = "The bell rang",
message = "A player said the word to the bell ringer.",
}
if sent then
npc.say(serial, "The staff has been told.")
else
npc.say(serial, "Nobody wanted that notification.")
end
end

The message does not carry what the player typed: a notification leaves the server for the channel’s owner, so publish what the script knows, not what players write.

  1. Start the server and look for Disk plugin Moongate.Plugins.Notifications: ... in the log: it is the line that tells you the plugin loaded.
  2. Log in with your game master character, run .spawn tutorial_bell and target a spot.
  3. Say “alarm” within 15 cells of the NPC.
  4. The NPC answers “The staff has been told.” and the server log holds the notification: Notification staff.tutorial: The bell rang - A player said the word to the bell ringer.

When the NPC answers “Nobody wanted that notification.”, notify.send returned false:

Cause Fix
enabled = false Set enabled = true and restart
No channel wants the notification Add a channel; with topics or min_level on it, make sure staff.tutorial and info pass
An argument is bad The log has a warning that names it

The same four steps, for an NPC that answers a player in character. What players type goes to OpenAI, or to the server you point the plugin at: the plugin is off until you turn it on.

  1. Install. With the server stopped:

    Terminal window
    mgctl plugin install Moongate.Plugins.OpenAi --root-directory /srv/moongate
  2. Configure. Start once and stop, then edit the [openai] table. Put the key in an environment variable of the server, not in the file, and name the variable:

    [openai]
    enabled = true
    api_key_env = "OPENAI_API_KEY"
    model = "your-model" # required: the model your provider offers
    [openai.personas.fabbro]
    prompt = "You are Mario, the blacksmith of Britain. You are gruff and proud of your work."

    A persona is a prompt you name; the script picks one by its id. base_url points the plugin at any OpenAI-compatible server, a local model included.

  3. Use it. Create templates/mobiles/tutorial_mario.toml with id = "tutorial_mario", name = "Mario" and script_id = "tutorial_mario", as in part 1, and scripts/mobiles/tutorial_mario.lua:

    tutorial_mario = {}
    function tutorial_mario.on_speech(serial, speaker, text)
    if not text:lower():find("mario", 1, true) then
    return
    end
    npc.look_at(serial, speaker)
    openai.ask{
    persona = "fabbro",
    npc = serial,
    player = speaker,
    text = text,
    callback = function(answer, reason)
    if answer then
    npc.say(serial, answer)
    else
    npc.say(serial, "(no answer: " .. tostring(reason) .. ")")
    end
    end
    }
    end

    The callback runs later, on a turn of the game loop, with the line to say or nil and a reason. The plugin says nothing by itself: what the NPC does without an answer is the script’s choice. Printing the reason, as here, is for the trial; a shard would stay silent.

  4. Try it. Start the server, run .spawn tutorial_mario, target a spot and say “Mario, what do you forge?” within 15 cells. The reasons you can meet (disabled, persona, cooldown, rate, budget, timeout, empty, among others) are listed on the plugin’s page.

Install over the old one to update, and remove to delete; both leave the settings in config/moongate.toml:

Terminal window
mgctl plugin install Moongate.Plugins.OpenAi --root-directory /srv/moongate
mgctl plugin remove Moongate.Plugins.OpenAi --root-directory /srv/moongate

After a server update, install the build of each plugin made for the new version: the server refuses one built for another.

Made with ❤️ by Tom

Buy me a coffee