Tutorial 8: Commands - ThePix/QuestJS GitHub Wiki

... Continued from Locks.

So we want a battery charger in the garage, and we want the player to CHARGE BATTERY... we need a CHARGE command. This will be a very simple command, for more complicated commands, see this page.

There are two parts to this. First the command itself, and second the response for the torch.

The Command

The code for the command looks like this.

new Cmd('Charge', {
  regex:/^(?:charge|recharge|power) (.+)$/,
  objects:[
    {scope:parser.isHeld}
  ],
  defmsg:"{pv:item:'be:true} not something you can charge.",
})

The code needs to go in the "code.js" file (I suggest keeping commands together, either at the top or bottom of the file; it just makes it easier to find them when your game gets big).

What the code does is create a new command, giving it a name and a dictionary of data - just as we do when creating objects. The dictionary has three elements; "regex", "objects" and "defmsg". The "regex" is a regular expression; what Quest matches to the text the user typed. The regular expression has two bits in brackets, "(?:charge|recharge|power)" and "(.+)"; these are called capture groups, and allow Quest to match against different texts. The first will match "charge", "recharge" or "power". The bit at the start, ?: says this is a capture group we are not interested in it - capture it and then throw it away.

The second part will match anything (the dot indicates any character, the plus indicates one or more of them).

The "objects" attribute tells Quest what to do with each capture group it has not discarded. In this case it is an object, and for an object, we need to tell Quest where to look for it. parser.isHeld is a built-in function; this will look for objects the player is holding first. Note that if a command fails to match an object in the scope you give, it will default to trying to find a match from all the visible objects, so you cannot assume an item is in a certain place just because you set its scope that way - it is just suggesting where to look first.

By default, a command will look for an attribute in the object that is the same as the name of the command, but lower case, so in this case "charge" (you can override this by setting "attName"; this is useful if your command's name is more than one word, as the attribute has to be a single word). We will add a "charge" attribute to the torch later, but we also need the command to handle any object that does not have a "charge" attribute, this is what the "defmsg" function is for. This is a string telling the player the item cannot be charged. It uses a text processor directive - "nv" - to modify the text depending on the item; eg "The book is not something you can charge." or "The boots are not something you can charge." (more on that here).

The Response

So now we should be able to type CHARGE TORCH, but it will say it cannot be charged. We need to go to the torch and give it its own charge response. We can add this after the "power" attribute (and I am only showing the torch from there downwards):

  power:3,
  charge:function() {
    if (player.loc != "garage") return falsemsg("There is nothing to charge the torch with here.")

    msg("You charge the torch - it should last for hours now.")
    this.power = 20
    return true
  },
})

A good general approach to writing functions for commands is to think of all the ways that would stop the player being able to do it. We know the user has specified the torch at this point, but she might be in the kitchen, and therefore unable to charge the torch. The first thing the code does, therefore, is check where the player is. If she is not in the garage, we print a message and exit the function, returning a value of false. Quest 6 allows us to do all that in one line.

Sometimes there can be half a dozen different possible reasons why a command might fail, and it should go though each one in turn. But if the situation is right we get to the second half of the function. This typically does three things, as illustrated here - tell the user it worked, modify something in the game world, and then return true to let Quest know a turn has passed.

Get An NPC To Do It

In the next section we will look at how to implement talking to NPCs, but before that it would be cool if we could tell an NPC to do our custom command. For simple commands involving a verb and an object, we can get Quest to do that for us. Just flag the command with the "npcCmd" attribute.

To get it to work properly - that is, have appropriate responses - you need your scripts to handle a "char" parameter, which could be the player or the NPC. In the "default" function below, we use the "sb" text processor directive to insert the subjective pronoun of the character into the string.

new Cmd('Charge', {
  npcCmd:true,
  regex:/^(?:charge|power) (.+)$/,
  objects:[
    {scope:parser.isHeld}
  ],
  defmsg:"pv:item:'be:true} not something {sb:char} can charge.",
})

And also in the response. Now we need to use the "options" parameter that Quest will send. This is a dictionary with various attributes we can access, the important ones being "char", the character doing it, and "item" the object being manipulated.

  power:3,
  charge:function(options) {
    if (options.char.loc != "garage") return falsemsg("There is nothing to charge the torch with here.")

    msg("{pv:char:charge:true} the torch - it should last for hours now.", options)
    this.power = 20
    return true
  },
})

Finally, it is worth mentioning that you can modify the "getAgreement" function on the NPC to determine whether the NPC agrees to do it or not, though we will not bother in this tutorial.

Continue to Complex mechanism...