An extensible MUSH / MUD server written in Java.
Extensions are ways to add new features to a Grounds universe. You can add behaviors to an extension by referring to plugins in its attributes. Extensions can react to events occurring in the universe, and can offer new functionality through plugin commands.
Let’s learn about how extensions work using a couple of examples.
An extension can offer one or more plugin commands for use by players. The name of a plugin command starts with a dollar sign $
to distinguish it from built-in Grounds commands. Suppose that we want to introduce a command to flip a coin. Using it will look like this:
$ $coinflip
The first step is to create an extension where the plugin command will live. Only some wizard roles (and GOD) are allowed to create extensions.
# build extension random
Created 5bbde998-59e8-4513-abfd-b9a20633c567
Extensions are things in the universe, just like players and other objects; however, an extension that only offers plugin commands usually does not have a location. When a player tries to execute a plugin command, Grounds looks through all extensions in the universe for one that has an attribute that matches the command name. So, for a “$coinflip” command, an extension needs an attribute named “$coinflip”.
The type of the attribute must be ATTRLIST, and Grounds looks for specific attributes to be present in that list.
The only required attributes in the attribute list for a plugin command are “pluginPath” and “pluginMethod”.
A plugin is an executable separate from Grounds that performs whatever computations and actions a plugin command needs. You can write a plugin in any language you like!
Grounds talks to plugins using JSON RPC 2.0. So, the pluginMethod attribute for a plugin command is the JSON RPC method that Grounds uses in the request that it sends to the plugin.
Grounds ships with a “random” plugin that implements the “$coinflip” command (and other commands) in bash. Here’s an overview of how it works.
jq
to extract interesting information from the request: the method, the request ID, and the plugin call ID.getCallerName
to find the name of the player who is executing the plugin command.exec
to execute the POSE command, informing the calling player of the result of the coin flip.A plugin command may return a string message to be emitted to the calling player. In this example, since the POSE command handles that, the command returns nothing on success.
See Writing Plugins for much more information on how to write a plugin.
Since a plugin command is an attribute, it is convenient to maintain it in a JSON or YAML file that can be loaded using the SET_ATTR command. Here is the entire “$coinflip” command as YAML; this is the value of the “$coinflip” attribute of the “random” extension created at the beginning.
- name: pluginPath
type: STRING
value: plugins/random/plugin_random.sh
- name: pluginMethod
type: STRING
value: coinflip
- name: commandHelp
type: ATTRLIST
value: |
- name: $COINFLIP
type: ATTRLIST
value: |
- name: syntax
value: $COINFLIP
- name: summary
value: Flips a coin
- name: description
value: |
The outcome of the flip is posed by the command in the caller's name.
To add this to the extension:
# set_attr random $coinflip[ATTRLIST]=@coinflip.yaml
In order for a player to execute a plugin command, they must have the USE permission for the command’s extension. By default, newly created things, including extensions, grant the USE permission to all player roles except guests. You can alter command permissions with the CHANGE_POLICY command. For example, it’s safe for guests to flip coins, so:
# change_policy random u+g
An extension itself is implemented as a player, so it may have its own roles. This is helpful if a plugin command should be allowed to perform an action that the player executing it is not allowed to do according to their own roles; that is, an extension role is a form of privilege escalation. To grant a role to an extension, use the role add
command. (The random extension does not actually require any additional roles; this is merely an example.)
# role add adept random
Be sure to trust a plugin command before granting it roles.
The RUN command executes a series of commands in a batch. It is a convenient mechanism for loading extensions and plugin commands (among other uses). The RUN command takes in a file with the commands to run and feeds them to your shell in order. Comments are supported, and empty lines are ignored.
# Adds a $coinflip plugin command.
#
# Run as GOD in the ORIGIN of a universe. Adjust the paths to YAML files as
# necessary for them to load.
build extension random
change_policy random w-dBA
set_attr random $coinflip[ATTRLIST]=@coinflip.yaml
say $coinflip installed.
The RUN command may only be executed by GOD.
# run etc/ext/random/random.cmd
Running command:
build extension random
Created 61f79128-32fc-414a-895f-8b71812a96a4
Running command:
change_policy random w-dBA
GENERAL: [THAUMATURGE, OWNER, BARD, ADEPT, DENIZEN]
READ: [THAUMATURGE, OWNER, BARD, ADEPT, DENIZEN]
WRITE: [THAUMATURGE, OWNER]
USE: [THAUMATURGE, OWNER, BARD, ADEPT, DENIZEN]
Running command:
set_attr random $coinflip[ATTRLIST]=@coinflip.yaml
Running command:
say $coinflip installed.
> GOD says: $coinflip installed.
An extension can have attributes that define responses to events that occur in the game universe. These are called listener attributes, because they listen for events and handle them. The name of a listener attribute starts with a caret ^
to clearly distinguish it from other attributes.
Let’s use a listener attribute to create a magic fiddle in the game that plays a tune whenever a player enters the fiddle’s location. The result will look like this when a player shows up.
: The magic fiddle plays a ditty as Ahalish arrives.
Ahalish arrives.
An extension with listener attributes may need to be placed at a particular location that is relevant to what it does. In this example, we want the fiddle to only play when a player enters a specific location, so the extension should live there.
# build extension magic_fiddle_ext
Created 03bba33e-350b-45e6-9b56-8c1f14cf166b
# yoink magic_fiddle_ext here
magic_fiddle_ext arrives.
An extension starts out without a location, so the YOINK command can be used to forcibly relocate it to the desired location.
Next, a listener attribute must be defined. The name of the attribute is not visible to players; here, we’ll use “^welcome”. The type of a listener attribute must be ATTRLIST, and Grounds looks for specific attributes to be present in that list.
The only required attributes in the attribute list for a listener attribute are “pluginPath” and “pluginMethod”.
As with plugin commands, you can write plugins for listener attributes in any language.
Grounds ships with a “magicfiddle” plugin that implements the “^welcome” listener attribute in bash. Here’s an overview of how it works.
While a plugin command runs on behalf of a player who executes it, a plugin for a listener attribute does not normally run on behalf of anyone in particular; so, there is no obvious player whose roles it should use. Instead, the plugin can execute commands and perform other tasks with the roles of its extension (which is represented as a player in Grounds). In this example, it allows the script to pose in the extension’s location.
The script returns nothing. Unlike plugin commands, there is no particular player to respond to.
See Writing Plugins for much more information on how to write a plugin.
The optional “eventType” attribute in the attribute list for a listener attribute names the type of event that the extension should respond to, by running the plugin. See Events for a list of available event types.
For the magic fiddle, the event type of interest is “TeleportArrivalEvent”, which is fired whenever something arrives at a location.
If a listener has no event type, then it can respond to any type of event.
The optional “localized” attribute in the attribute list for a listener attribute indicates whether the extension should respond to events that occur only in its location (true) or from anywhere (false). An extension without any location cannot respond to only local events.
Since the magic fiddle should only play when something arrives at its own location, its localized attribute should be true. This is also why the extension has a location.
If a listener has no localized attribute, then it can respond for events that occur anywhere.
Since a listener attribute is an attribute, it is convenient to maintain it in a JSON or YAML file that can be loaded using the SET_ATTR command. Here is the entire “^welcome” listener attribute as YAML; this is the value of the “^welcome” attribute of the “magic_fiddle_ext” extension created at the beginning.
- name: pluginPath
type: STRING
value: plugins/magicfiddle/plugin_magicfiddle.sh
- name: pluginMethod
type: STRING
value: _listen
- name: eventType
type: STRING
value: TeleportArrivalEvent
- name: localized
type: BOOLEAN
value: true
To add this to the extension:
# set_attr magic_fiddle_ext ^welcome[ATTRLIST]=@magicfiddle.yaml
Players do not invoke plugins in listener attributes directly, so there is no need to set up policies on extensions containing them.
As described above, an extension should be granted roles that are necessary for its listener attributes to work. To grant a role to an extension, use the role add
command.
# role add bard magic_fiddle_ext
Be sure to trust a listener attribute before granting it roles.
The magic fiddle extension and listener attribute developed so far work fine, but it may be odd for players to see their effects without there being a magic fiddle visible in the extension’s location. Extensions are always invisible to non-wizard players, so instead, let’s create an ordinary magic fiddle to go along with it.
# build thing "magic fiddle"
# change_policy "magic fiddle" g-d
The CHANGE_POLICY command prevents non-wizard players from taking the magic fiddle into their possession.
This pattern is completely optional. In other situations, it may not make sense to represent the effects of a listener attribute visibly.
As with plugin commands, the RUN command is a convenient mechanism for loading extensions and listener attributes.
# Creates an extension representing a magic fiddle that greets arriving players.
# This includes:
# - the magic_fiddle_ext extension
# - the ^welcome listener attribute
# - the proxy magic fiddle thing
# Run as GOD where you want the magic fiddle to live. Adjust the paths to YAML
# files as necessary for them to load.
build extension magic_fiddle_ext
yoink magic_fiddle_ext here
set_attr magic_fiddle_ext ^welcome[ATTRLIST]=@plugins/magicfiddle/magicfiddle.yaml
role add bard magic_fiddle_ext
build thing "magic fiddle"
# stop anyone from picking it up
change_policy "magic fiddle" g-d
describe "magic fiddle" "This faintly sparkling, faintly golden musical instrument hovers steadily in the corner, its bow trained across its strings."
say Magic fiddle installed.