More on commands - ThePix/QuestJS GitHub Wiki
There are a lot of options available to you when you create a command - so many that it may be over-whelming at first. This page presents some of the more obscure points, but first a list of what can be found on other pages.
- To learn more about command matching, see here.
- To learn about adding exceptions to commands and intercepting them, see here.
- For the values a command "script" function should return, see here.
- Meta-commands are discussed here.
- If you want to modify an existing command, see here.
- If you have no text input for your game, you will still be using commands via the side panes or hyperlinks. More on that here.
- There is a full description of the process of creating a complex command here.
- A shortcut for creating simple commands is here.
- To match things other than items, see custom parser types.
- To get a list of commands QuestJS recognises, type CMDS in a game.
The PARSER command
In "dev" mode, you can use the PARSER command to see debugging information from the parser. This is useful if the parser is getting the wrong command or wrong item. You can check if it thinks your command is a match at all, and then see how it scores.
The parser pre-processor
You can add a pre-processor to the parser - a function that will be run when the player types a command, but before anything else happens (except parser.override). This simple example just echoes the command to the console.
settings.parserPreprocessor = function(inputText) {
log(inputText)
return inputText
}
This could be useful if you have items with full stops (periods) in their name, as usually this will cause Quest to split the text into different commands. Let us suppose a character called Dr. Jones. The user might type LOOK AT DR. JONES. The parser will see that as two commands, LOOK AT DR and JONES. One solution would be to call him Dr Jones, but alternatively, you could strip the full stops from the input text. This example also does it for "mr." and "mrs.".
settings.parserPreprocessor = function(inputText) {
return inputText.replace(/dr\./g, 'dr').replace(/mr\./g, 'mr').replace(/mrs\./g, 'mrs').replace(/ms\./g, 'ms')
}
Using directions
In some situations, an alternative to creating a new command is to create a new direction. For example, CLIMB is better done as a new direction as, like a direction, it is one word and moves the player. This is discussed in detail here.
Item command counting
Quest will track how often that command is used on an item - as long as the command uses the default "script". The count is only incremented if the function returns true
(or world.SUCCESS_NO_TURNSCRIPTS
).
The count will be added as an attribute with the standard name with "_count" added. For the EXAMINE command, the count is therefore stored in "examine_count".
Note that the attribute is only added when used, so is initially undefined
. If the command is used, but the function returns false because it fails, the value will be zero. The attribute will be set to zero, if necessary, before the function is called, but the value is not incremented until after.
Here is an example function for "talkto". The default "talkto" for NPCs checks if the player and the NPC can talk before this is called. Let us suppose we have a rabbit. The player tries to talk to the rabbit, and count will be set to zero. However, it fails because rabbits cannot talk, so the count is not incremented. But then the player casts a spell allowing talking, and again tries to talk to the rabbit. The "talkto" function notes that that is allowed, so calls the "talk" function on the rabbit seen below. At this point count is zero, so the conversation happens. Then "talkto" returns true
talk:function() {
switch (this.talkto_count) {
case 0 :
msg("You say 'Hello,' to the rabbit, 'how is it going?'");
msg("The rabbit looks at you. 'Need carrots.' It looks plaintively at it round tummy. 'Fading away bunny!");
break;
default: msg("You wonder what you can talk to the rabbit about."); break;
}
return true
},
If you want to check if something has been examined, you can use the fact that JavaScript considers both undefined
and zero to be false
. In this example, the code block will be executed if the rabbit has been examined (the count is 1 or more); it will not otherwise (the count is zero or undefined
).
if (w.rabbit.examine_count) {
//...
If you want your custom command to do this too, you need to have this line in the "script" function before deciding if the command can be used, to initialise the attribute (assuming the item is held in a temporary varible called "obj").
if (!obj[this.attName + '_count']) obj[this.attName + '_count'] = 0
And this line just before you return world.SUCCESS
.
obj[this.attName + '_count']++
If you want to handle multiple items, these need to go inside the loop where you iterate through them, at the start and end. It does get ratrher more complicated, and you should see how it is done in the default "script" function in lib/_command.js.
Extended Scope
At the start of each turn, Quest will determine what is in scope - anything the player can see or reach, plus the current room. When a command is checked, Quest will use this reduced list of items to check against. Occasionally, however, you want to look further afield. To tell Quest to do that, set "extendedScope" to true
.
For example, the MAKE command requires the user to reference an item that does not exist yet - if the user types MAKE HAT, then presumably the hat is not present. We need to tell Quest to look outside the normal scope when searching for this object.
new Cmd('Make', {
objects:[
{scope:parser.isUnconstructed, extendedScope:true},
],
script:function(objects) {
const obj = objects[0][0]
return obj.build({char:player, item:obj}) ? world.SUCCESS : world.FAILED
},
}),
One-word commands
Quest has a number of one-word commands that are actually handled by one command behind the scenes. They all do nothing by default, but give a little message to the player. For the most part they are there because other games have hem, and some players expect them to be understood.
listen
smell/sniff
jump
sing
dance
whistle
pray
You can change the default response by modifying the string in lang
. This is "no_" with the verb appended. For example:
lang.no_smell = "{pv:char:can't:true} smell anything here.",
You can have a specific effect occur in a location by giving the location an attribute with the name of the verb.
createRoom("lounge", {
desc:'A curious room with an old settee and a tv. There is a tatty rug on the floor.',
smell:'There is a faint musty smell, with just a hint of polish.',
}
You can also give a region an attribute in the same way.
You can have NPCs react to these strange actions. NPC reactions are discussed here. The important point for us is that you can check the player's "_hasJust" attribute (note the underscore), which will be set to theverb for one turn.
{
name:'after dance',
test:function() {
return player._hasJust === 'dance'
},
script:function(params) {
msg("Kyle looks at you strangely.")
},
},
You can add your own one-word commands - though whether this is easier that creating new commands is debatable.
const cmd = findCmd("OneWord")
cmd.regexes.push(/^(act)$/)
lang.no_act = "You do a quick scene from Hamlet because... it seems a good idea?"
If you are adding several, the first line only needs to be done once. You can add synonyms too.
const cmd = findCmd("OneWord")
cmd.regexes.push(/^(act)$/)
cmd.regexes.push(/^(perform)$/)
lang.no_act = "You do a quick scene from Hamlet because... it seems a good idea?"
lang.oneWordSubsts.perform = 'act'
Now you can add "act" attributes to your locations, and have NPCs react as well.
verbify
You can use the verbify
function to convert a command name to its attribute; this will ensure consistency.
It converts to lower case and strip out all non-alphanumeric characters.
Aborting a concatenated command
QuestJS allows the user to put in several commands at once, like this:
get book, then read it.e.drop book
Sometimes it is a good idea to interrupt that. Suppose reading the book inadvertently summons a demon? It is just possible the user will want to think again. Just add this line:
parser.abort()
The user will see a message saying which commands were skipped (so you might want to consider where you put the above line; I suggest it go after the message about the ravenous demon.
I appreciate this will require some careful forethought by the author; I am not entirely convinced any author will actually think to use it, but the option is there.