Skip to content
Thomas E Enebo edited this page Jan 5, 2014 · 9 revisions

Example

[Note: Stream of consciousness documentation on command subsystem...rough draft++]

Purugin has several ways of writing commands. The simplest version is to use the block-based format

    public_player_command('nick', 'change displayed name', '/nick {name}') do |me, *args|
      nick = error? args[0], "Must specify a name"
      me.display_name = nick
      me.msg "Display name changed to #{nick}"
    end

This example makes a public command. A public command is a command which does not check permissions. Any user can execute public commands. More on permissions and commands later.

It is also a 'player' command. Commands in Purugin can be either player commands, console commands (typed into your server console), or both.

The first argument gives the commands name. In this case it is 'nick'.

The second argument specifies the description which will be shown in /help.

The third argument is a syntax description. When this argument is provided with a block it is just another documentation field. As we will see in a bit it is much more powerful than this when we specify commands without using a block.

The block itself is the actual logic of the command. This command just makes sure we have given any argument for our new nickname and then sets it. It then give a message back to the player saying that their nickname has been changed.

Command methods

player_command, console_command - commands that can be executed by either the player of the console but not both. In the case of player_command this also means we need to satisfy permissions to execute (console is already a god so it has no permissions check).

public_player_command - A player command with no permissions check required (example in previous section)

command - Generic version of a command where you need to specify all fields

TODO: Break these down and explain all fields

Purugin Syntax Language (PSL)

The block-version illustrated at the top of the page is perfect for simple commands. Once your commands start having a more varied or complicated syntax there is a better way. You can use the Purugin Syntax Language (PSL). This language was created to combat 1/3+ of a plugin being if/else logic. This language requires a little knowledge but once learned you can reduce the redundant boilerplate of command line processing logic.

First example

Let's use a rich example and break it down:

    public_player_command('track', 'track to a waypoint', 
         '<status> | help | stop | {waypoint}')

First thing to notice is that no block is supplied. This is because argument 3 (of command syntax) will end up being executed to figure out which method in your plugin to execute. Magic? Perhaps a bit, but by the end of this example you will see the beauty of PSL.

The first concept in the language is alternations (the '|' or OR). We can see this track command has four of them:

  1. <status>
  2. help
  3. stop
  4. {waypoint}

So this command has four diferent sub-command patterns in it. If you wanted to add more then you just add more alternations ('|').

Let's look at 'help' and 'stop' first since they are both examples of barewords. This will match '/track help' and '/track stop'. When the match is made it will execute the methods track_help and track_stop respectively.

Not let's look at the last one {waypoint}. This represents a variable. So it says I will accept any single value and I will call a method called track and supply this variable's value to that method.

Finally we will look at the first alternation <status>. The syntax <status> itself is a name specifier. Name specifiers allow us to override which method name the command dispatches to. In this case, when this first alternation matches then we will call track_status. So what is the match here? There is nothing but the name specifier? So we will match this rule when no values are submitted (aka '/track'). This empty alternation is generally very common in commands.

A question you may ask yourself is why did we bother to use a name specifier to the empty match alternation? Or perhaps what would happen if you did not give a name specifier? The answer to the second question is we would dispatch to track. Notice, that we would have then been dispatching <status> and {waypoint} alternations to the same method. It is nicer to not do that so we picked to give one rule their own name specifier. Namely, the empty alternation will dispatch to track_status. Syntactically it even makes the empty alternation actually look like a rule.

== Making the Ruby methods

In this previous example we would need to define:

  def track(me, waypoint_name)
    me.msg "You start tracking '#{waypoint_name}'"
    @tracks[me] = [waypoint_name, location(me, waypoint_name)] 
  end

  def track_help(me)
    me.msg "/track name", "/track stop"
  end

  def track_stop(me)
    if @tracks.delete(me)
      me.msg "Tracking stopped" 
    else
      me.msg "No Track is active"
    end  
  end

  def track_status(me)
    if @tracks[me]
      me.msg "You are tracking to waypoint '#{@tracks[me][0]}'"
    else
      me.msg "You are not tracking anything"
    end
  end

Notice that the first argument to all of these is the issuer of the command ('me' is convention we use for sender because it is short).

== Another example

We are just going to add a variable to the stop alternation from the previous example:

    public_player_command('track', 'track to a waypoint', 
         '<status> | help | stop {waypoint} | {waypoint}')

In this case it will dispatch to _track_stop but pass the variable for waypoint into the method.

== Typing Variables

Variables by themselves are useful for checking argument counts (e.g. arity splitting) and dispatching to different methods, but you can give them a lot more value by adding a type constraint to the variable. Let's take our original waypoint command and type the waypoint:

    command_type('valid_wp') do |me, waypoint_name|
      loc = location(me, waypoint_name)
      error? loc, "No such waypoint: '#{waypoint_name}'"
      waypoint_name
    end

    public_player_command('track', 'track to a waypoint', 
         '<status> | help | stop | {waypoint:valid_wp}')

Here we can see in the last alternation we specify that the waypoint is of type valid_wp. Immediately above this command declaration we can see the type declaration for valid_wp.

You should notice two things about this type declaration:

  1. It can validate a types value and throw an error if the type is invalid (using error?)
  2. You can convert string inputted value into a real one if you so desire