Custom parser types - ThePix/QuestJS GitHub Wiki
The parser is built around matching commands, and then matching items within the commands. What if you want your command to match something other than items? For example, FILL GOBLET WITH WINE. FILL and WITH are fine, they are part of the command matching. The GOBLET is an item. But what about WINE? You want the command to match other liquids too, but wine is not an item.
Every command has an "objects" attribute; an array or dictionary of dictionaries. Usually this tells Quest about what sort of objects to expect - hence the name. However, you can also use it to tell Quest to expect something else altogether, using the "special" attribute.
This example is taken from the SAY command. It says the first bit will be text (the verb; SAY, SHOUT, etc.), and the second bit is also text, what is being said.
objects:[
{special:'text'},
{special:'text'},
]
This example is for pushing an object in a direction, and shows how we can mix it up.
objects:[
{special:'text'},
{scope:parser.isHere, attName:"shiftable"},
{special:'direction'},
]
})
A number of options are built-in. In the table below, the name is what you need to use in the "objects" array, as seen in the examples above. The "Returns" indicates what value will be added to the array passed to the command's "script" function (on a successful match).
Name | Matches | Returns |
---|---|---|
ignore | Anything | Nothing |
text | Anything | The full text |
direction | Any direction, as set up in lang.exit_list | The full name of the direction (eg "northwest") |
number | Any number in digits or any number as a word from zero to twenty | The number (as a number, not a string) |
fluid | Any fluid listed in settings.fluids | The fluid |
tone | Any fluid listed in settings.tones | The tone |
You can add your own types. Let us say you want to add adverbs. It is debatable if it is a good idea at all, but it does illustrate the technique.
We set up the adverbs in the "setup" function in settings.js. If you already have a set up function, you just want to insert this - without the first and last line - into that.
settings.setup = function() {
parser.generateSpecialText(
'adverb',
['carefully', 'quickly', 'elegantly'],
'Not recognising the adverb there.'
)
}
The parser.generateSpecialText
function takes three parameters; a name, an array of allowed values and a message to use when there is no match.
Then (in code.js, perhaps), add your command, setting the "objects" array to use a special with the name you used before.
new Cmd('TalkToAdverb', {
regex:/^(?:talk to|speak to|talk|speak) (.+) (.+)$/,
objects:[
{scope:parser.isNpcAndHere},
{special:'adverb'},
],
script:function(objects) {
msg('You talk to {nm:npc:the} {show:text}', {npc:objects[0][0], text:objects[1]})
return world.SUCCESS
},
})
Note that in the script, the item is an element in an array, which is itself in an array (objects[0][0]
). This is because the player can use commands with several items at once in some cases, such as GET ALL. However, the special text is just an element in an array, so is objects[1]
, not objects[1][0]
.
We can make it better! The parser.generateSpecialText
can handle arrays in the array, allowing for synonyms. The value passed to the command's "script" function will always be the first element of the array. We can also use the text processor in the error message.
settings.setup = function() {
parser.generateSpecialText(
'adverb',
[['carefully', 'cautiously', 'c'], ['quickly', 'fast', 'q'], 'elegantly'],
'Not recognising the adverb "{show:text}".'
)
}
We can also modify the command to allow the adverb at the start. This is best done using named capture groups, so the "objects" attribute is now a dictionary.
new Cmd('TalkToAdverb', {
regexes:[
/^(?:talk to|speak to|talk|speak) (?<npc>.+) (?<adverb>.+)$/,
/^(?<adverb>.+) (?:talk to|speak to|talk|speak) (?<npc>.+)$/,
],
objects:{
npc:{scope:parser.isNpcAndHere},
adverb:{special:'adverb'},
},
script:function(objects) {
msg('You talk to {nm:npc:the} {show:text}', {npc:objects[0][0], text:objects[1]})
return world.SUCCESS
},
})
What if you want something more complicated? We can add a new attribute to parser.specialText
. This has to have two function attributes, "error" and "exec", both of which will be sent the text. The "error" function should either return false
if it is not an error - the text is acceptable - or a message otherwise (it should not print anything itself).
The "exec" function should return whatever is to be added to the list of matched items. In this example (which is doing adverbs the long way), it is the text, but you could look up a value in a dictionary and return that. If this returns false
, nothing gets added to the list of items.
parser.specialText.adverb = {
error:function(text) {
if (settings.adverbs.includes(text)) return false
return processText("Hmm, I was expecting an adverb, and I got {show:text}. Not one I know.", {text:text})
},
exec:function(text) {
return text
},
}