Commands

Commands are the most essential player-facing functionality you can add to Ranvier and they can consist of a single function as a bare minimum. Each command gets its own file within the commands/ folder of the bundle. This guide will go over a couple examples of commands that should give a decent overview of how you can access the game's state to do what you want inside your command.

Creating a New Command

Commands in a bundle all go under the commands/ folder in your bundle's directory. So in our case we're creating two commands: inventory, and remove so our bundle will look like this:

bundles/my-commands/
  commands/
    inventory.js
    remove.js

The name of the file minus .js will be the command the user types so take that into consideration when naming the file. Commands can have multiple aliases, as we will see, so you don't have to create multiple files for the same command.

Command Structure

Similar to all bundle-loaded js files commands are files which export a configuration for the entity. This is the most basic possible command file:

'use strict';

module.exports = {
  /*
  `command` is a closure which takes in the GameState in `state` and returns a
  function which takes the arguments to the command and the player that executed
  the command. The 3rd parameter, `arg0`, is like argv[0] in many programming
  languages; it will be the full name of the alias the player used to execute
  the command. Example: Command 'shop' has an alias 'list'. If the player types 'lis',
  argv0 will be equal to 'list' so that the 'shop' command can check for it and
  react accordingly.
  */
  command: state => (args, player, arg0) => {
  }
};

Example Commands

Both of these commands happen to interact with the player but take note that you have access to the entirety of the game's state inside these commands. That includes all active players, all areas, rooms, NPCs, and items in the game, server configuration as defined in ranvier.json, and more. See the ranvier executable's GameState variable for a list of all the things you have access to. Further, the ranvier-commands default bundle which ships with Ranvier has many example commands to work from.

inventory

Inventory is one of the most used commands in most MUDs next to look. It lists out the items the player is carrying but has not equipped. In this command we simply loop over all of the items in the player's inventory and output their name.

'use strict';

// import Broadcast from core to send messages to the player
const { Broadcast } = require('ranvier');

module.exports = {
  // `usage` is shown when viewing the helpfile for a command, see the Helpfiles section for more detail
  usage: 'inventory',
  metadata: {
    someData: 'you can use the metadata config to store any extra settings'
  },
  command : state => (args, player) => {
    if (!player.inventory || !player.inventory.size) {
      /*
      A note on using Broadcast:
      `sayAt` sends a message to the player with a newline afterwards. If you need to
      concatenate multiple strings together before breaking to the next line you can
      simply use `at()` as long as you remember to end the line with a newline using
      `sayAt()`. It's recommended to use `sayAt` instead of just manually appending
      "\r\n" as it makes the code a lot cleaner to read.
      */
      return Broadcast.sayAt(player, "You aren't carrying anything.");
    }

    Broadcast.sayAt(player, "You are carrying:");

    for (const [, item ] of player.inventory) {
      Broadcast.sayAt(player, item.name);
    }
  }
};

remove

The remove command unequips a piece of gear. This command will demonstrate taking arguments to your command and using command aliases.

'use strict';

const Ranvier = require('ranvier');
const Broadcast = Ranvier.Broadcast;
// For this command we need to import the CommandParser library to parse the argument to `remove`
const { CommandParser } = Ranvier.CommandParser;

module.exports = {
  // Aliases are straightforward, just an array of other strings the player can type to call this command
  aliases: [ 'unwield', 'unequip' ],
  usage: 'remove <item>',
  command : (state) => (arg, player) => {
    if (!arg.length) {
      return Broadcast.sayAt(player, 'Remove what?');
    }

    /*
    A note on the Parser:
    The method `parseDot` from the CommandParser lib takes a search string like "3.bracer"
    and a list of items to search through. In that example "3.bracer" would find the
    3rd item that matches the keyword "bracer".  The `true` argument here just tells
    `parseDot` to also return the key of the item it found within the list as
    Character equipment is keyed by the slot the item is worn (head, wrist, etc.),
    so we would end up with ['wrist', Bracer{}] as our results, if it were found.
    */
    const [slot, item] = CommandParser.parseDot(arg, player.equipment, true);

    if (!item) {
      return Broadcast.sayAt(player, "You aren't wearing anything like that.");
    }

    player.unequip(slot);

    Broadcast.sayAt(player, `Un-equipped: ${item.name}`);

    /*
    This is a perfect example of emitting events that your custom item scripts will
    listen for, as described in the Scripting section of the Areas documentation. In
    this instance, if the item's custom script is listening for the `unequip` event, it will be
    notified that the player just unequipped the item.
    */
    item.emit('unequip', player);
  }
};

Creating Admin Commands

It is possible in Ranvier to create commands that are only useable by privileged players. This is done by use of the requiredRole property, which defaults to PLAYER. See src/PlayerRoles.js for a list of roles and their rank.

Here's an example of how you might build an admin command:

setadmin

'use strict';

const Ranvier = require('ranvier');
const { Broadcast, PlayerRoles } = Ranvier;
const { CommandParser } = Ranvier.CommandParser;

module.exports = {
  /*
   Here is where we can set the required role level.
   If this were set to PlayerRoles.BUILDER, both ADMIN and BUILDERS could use it.
   Anyone can use PLAYER commands and if 'requiredRole' is not set,
   the command defaults to a PLAYER command.
   */
  requiredRole: PlayerRoles.ADMIN,
  command: (state) => (args, player) => {
    args = args.trim();

    if (!args.length) {
      return Broadcast.sayAt(player, 'setadmin <player>');
    }

    const target = CommandParser.parseDot(args, player.room.players);

    if (!target) {
      return Broadcast.sayAt(player, 'They are not here.');
    }

    if (target.role === PlayerRoles.ADMIN) {
      return Broadcast.sayAt(player, 'They are already an administrator.');
    }

    // The role is just a property of the player, which gets saved like any other.
    target.role = PlayerRoles.ADMIN;

    Broadcast.sayAt(target, `You have been made an administrator by ${this.name}.`);
    Broadcast.sayAt(player, `${target.name} is now an administrator.`);
  }
};

Testing Your Commands

To test your command make sure the bundle the command is in is enabled in ranvier.json. The first time you add a new command you'll have to restart the server. If you have the debug-commands bundle enabled then you can use the hotfix command to reload the command from disk without having to restart the server, e.g., hotfix mycommand will load the new code for mycommand into the game without you needing to restart.