Special Verbs
Introduction
Consider the case where your game contains a single doorbell object implemented as a Button that can be pushed. The player can ring the bell by using the command PUSH BELL, but what if they type RING BELL? You probably don't want to modify VerbRule(Push) to make 'ring' a global synonym for 'push', since there would be too many situations in which this could incongrous, for example if the playher tried RING LEVER or RING AUNT MAUDE. On the other hand it would seem a lot of work just to cover this one case if to define a new Ring Action and VerbRule(Ring) and dobjFor(Ring) blocks on both Thing and the doorbell object. The SpecialVerb class allows you to solve this problem by letting you define:
SpecialVerb 'ring' 'push' @doorbell;
In brief, this means that a command beginning RING should be translated into one beginning PUSH when the direct object is doorbell. The attempt to ring anything else, such as the lever or Aunt Maude, will be met with the response "You can't ring that. "
If there's a second bell in the game that's rung by pulling a chain, then it's perfectly okay to define a second SpecialAction to cover that case too:
SpecialVerb 'ring' 'pull' @chainbell;
Provided doorbell and chainbell are never in scope at the same time, the game will be perfectly capable of working out which SpecialVerb to apply (which it has to do before the parser has resolved the objects involved in the command, since it first needs to know what the action is before it can choose appropriate objects for it). The game should normally be able to work out which SpecialVerb to apply even if both bells were in scope at the same time, provided the player's input unambiguously referred to one of them (e.g., RING DOOR BELL or RING CHAINED BELL), although in the event that the player just typed RING BELL, which could refer to either bell, it would arbitrarily choose one or other SpecialVerb, since it wouldn't have enough to go on to make the correct choice.
Suppose finally that somewhere in the game we have a bridge that the player might try to CROSS or WALK ACROSS or GO ACROSS (even though this could be achieved simply by issuing an ordinary travel commamnd like E or W):
orchard: Room 'Orchard' "This orchard is full of apple trees. A path leads back to the east. To the north lies a small hut. Over to the west a small wooden footbridge crosses a stream. " west = meadow ... ; meadow: Room 'Meadow' "This huge meadow stretches as far as you can see. A small footbridge cross the stream running along its eastern boundary. " east = orchard ; MultiLoc, Decoration 'stream' initialLocationList = [meadow, orchard] ; footbridge: MultiLoc, Fixture 'footbridge; small wooden foot; bridge' initialLocationList = [orchard, meadow] ;
Suppose this is the only object (or only one of a very few objects) that can be crossed in the game, and we'd rather not have to go to the trouble of define a Cross Action and a VerbRule(Cross) and default dobjFor(Cross) handling on Thing just to allow the player to type CROSS BRIDGE or WALK ACROSS BRIDGE. We could perhaps make footbridge a Passage and then define:
SpecialVerb 'cross|walk across|go aoross' 'go through' @footbridge;
And this would just about get the job done, except that GO THROUGH BRIDGE does not seem a natural way of phrasing a command to cross it, so player who try GO THROUH BRIDGE might find its equivalent to CROSS rather odd (GO THROUGH BRIDGE might more naturally mean going under it when navigating the stream). An alternative might be to make the bridge an Enterable and divert CROSS to ENTER, but again players who type ENTER BRIDGE probably wouldn't expect it to be equivalent to crossing it (ENTER BRIDGE more naturally suggests walking onto it and stopping, or going under it when navigating the stream). But then seem to be left without an existing action to divert CROSS to.
To meet this problem, we can divert CROSS into a SpecialAction by using the verb 'sp#act' (which players are unlikely to type at the keyboard unless they've read this manual and are going out of the way to break your game; but if try it their attempt will be rebuffed with "I really refuse to understand that command.").
We can then handle this case by using the following:
SpecialVerb 'cross|walk across|go over' 'sp#act' [footbridge]; footbridge: MultiLoc, Fixture 'footbridge; small wooden foot; bridge' initialLocationList = [orchard, meadow] dobjFor(SpecialAction) { verify() {} action() { "You <<gAction.specialPhrase>> {the dobj}. "; local dest = gActor.isIn(orchard) ? meadow : orchard; dest.travelVia(gActor); } } ;
There are a few points to note here:
- SpecialAction is an action that's defined on Thing to do nothing but fail at the verify stage, but which can be made to do whatever you like on individual objects or (custom classes). It should only ever be triggered as a result of a SpecialVerbm as in this example.
- The command phrase(e.g., 'cross' or 'walk across') from its most recent invocation from a SpecialVerb can be obtained from SpecialAction.specialPhrase, which in this case is equivalent to gAction.specialPhrase since here gAction must be SpecialAction.
- Various different verb phrases can be defined for a SpecialVerb in the first part of the template separated by a vertical bar character (|) as in the example above: 'cross|walk across|go over' .
- As that example also shows, a SpecialVerb can match a list of direct objects (although there it's a list of only one item) as well as single direct object. Indeed, it can also match a class or a list of objects and/or classes, but this shouldn't be abused. If you had a couple of bridges in your class and defined a Bridge class to handle their common behaviour, it would be absolutely fine to have a SpecialVerb matching Bridge as its direct object. But this should not be overdone: SpecialVerbs are intended only to handle a few otherwise awkward cases; there is no way you should have SpecialVerb applying to Thing.
- All the examples we've given of SpecialVerbs concern TActions. This is not accidental; it's really only TActions that SpecialVerbs are designed to handle. In some cases you may be able to get a SpecialVerb to word with a TIAction, but it only if the action you're diverting from and the one you're diverting to share the same preposition (e.g. 'throttle dobj with iobj' -> 'attack dobj with iobj', but there may not be that many cases where this would be useful.
- Finally, when defining a SpecialVerb, make sure that the verb phrase you are giving your SpecialVerb, its specVerb property (the first string in the template), does not clash with that of any existing verb defined in the library or elsewhere in the game. For example, defining SpecialVerb 'take' 'go through' @PathPassage; would mask (and hence disable) the normal meaning of TAKE throughout your game. If the verb phrase you give your SpecialVerb is part of a longer verb phrase defined in the library (e.g. 'pick' which is the first word of PICK UP, meaning TAKE, then you need to define an exceptions list, e.g. ['pick up'], on your SpecialVerb in order to prevent it from blocking the normal meaning of the longer verb phrase (PICK UP in this example).
This covers all or most of what you need to know about SpecialVerbs to use them in your own game. At that follows in this chapter is pretty much the small print.
Elaborations
So far we have used the SpecialVerb template without explaining what properties it refers to. The SpecialVerb template is defined as:
SpecialVerb template 'specVerb' 'stdVerb' @matchObjs | [matchObjs];
We are now in a position to list the properties and methods of SpecialVerb that are of direct relevance to game authors (the remainder are all for internal use by the library):
- specVerb: The verb phrase, or list of verb phrases separated by the vertical bar character (|), that this SpecialVerb will match (e.g. 'ring' or 'cross|go across|walk across).
- stdVerb: The verb phrase (matching a standard verb already defined in the library) that we want this SpecialVerb to use instead (e.g. 'push' or 'go through')
- matchObjs: The direct object, or list of direct objects, we want this SpecialVerb to apply to. This can also be specified as a class or list of classes (or mixed list of objects and classes), but it should not match too many, and will work best if none of the objects listed is in scope at the same time as any of the others.
- exclusions: A list of verb phrases you don't want this SpecialVerb to match, e.g. ['pick up'] if specVerb is 'pick' (without this exceptioms list any attempt to PICK UP WHATEVER would generate the response "You see no up whatever here.")
- where: The room or Region, or a list of rooms and or Regions one of which, the actor must be in for this SpecialVerb to be active. Note that the SpecialVerb in any case checks whether(at least one of) the object(s) specified on its matchObjs property is in scope, so specifying where may seldom be needed.
- during: A Scene, or list of Scenes, one of which must currently be happening for this SpecialVerb to be active (I can't immediately think of a use for this, but you may be able to).
- when: Any other condition you care to specify for this SpecialVerb to be active (by default this is simply true. Note, however, that any condition specified here cannot refer to the actual objects of the command (dobj, etc.), since these won't have been determined when this property is first checked. You can, however, use the tentativeDobj and tentativeDobjList properties of SpecialVerb at this point. You can also use the objChecks() method for this purpose:
- objChecks(dobj, iobj, aobj): A method that is called once the parser has resolved the action and objects involved in the command. the parameters dobj, iobj, and aobj are respectively the direct, indirect, and auxiliary objects of the command, but for most SpecialVerbs, only the first of these will be present. By this stage, the game has already checked that the direct object of the command is one of those specified in our matchObjs property, so there's no need to do that, but we might want to inspect the state of our actual direct object and react accordingly. An example is given below.
- tentativeDobj: SpecialVerb's best guess at what the direct object of the command will be, based on matching the noun phrase the player entered to matchObjs that are in scope (this will be nil if there are no such matches).
- tentativeDobjList: A list of possible direct objects of the command, based on matching the noun phrase the player entered to matchObjs that are in scope (this will be an empty list if there are no such matches).
- verbPhrase: The verb phrase (e.g. 'ring' or 'go through') that resuted in this SpecialVerb being invoked. This could be used, for example, to customize any message we might want to display in objChecks().
- priority: The parser uses a scope check plus the where, during, and when properties to determine which SpecialVerb to use if more than one matches the verb phrase entered by the player. The priority property can be used as a tiebraker in the event that everything else works out equal. A higher priority (larger value of priority) SpecialVerb will then be favoured over a lower priority. The default priority is 100.
With the possible exception of objChecks() (and even then probably not often), you should only very nearly need to override any of these properties or methods (apart from the three defined via the template). Here is an example of how you might use objChecks() to rule out slamming a closed door and reporting that an open door has been slammed:
SpecialVerb 'slam' 'close' @Door
objChecks(dobj, iobj, aobj)
{
if(!dobj.isOpen)
{
"It would surely be exceptionally petulant to attempt to <<verbPhrase>> <<dobj.theName>>
when it isn't even open. ";
abort;
}
dobj.slammed = true;
}
;
modify Door
slammed = nil
dobjFor(Close)
{
action()
{
if(slammed)
"You slam {the dobj} shut with unreasonable force. ";
/* Reset our slammed property. */
slammed = nil;
inherited();
}
}
;
If you're having to do too much tweaking of a SpecialVerb to make it do what you want, it may be that you'd be better off using a different means of getting the effect you're after (i.e., defining a new Action, or modifying an existing VerbRule, or using a Doer or replaceAction()). Using SpecialVerb should be fine for the kinds of cases we've illustrated here, but overusing them on too many objects risks producing code that's less robust than doing things the long way.
Finally, if you are having trouble with a SpecialVerb it may help to know a little about how the library handles them internally. Once a player has entered a command, the parser follows the following steps in relation to SpecialVerbs:
- After tokenizing the player's input and carrying out some preliminary processing with the tokens, the parser calls specialVerbMgr.matchSV(toks) to give it a chance to change the toks that are passed to it.
- The specialVerbMgr then looks for SpecialVerbs that match the toks passed to it.
- Assuming any SpecialVerbs are found to match, each matching SpecialVerb calculates a score based on whether any of the objects in its matchObjs property are in scope and how well any of these match the noun phrase entered by the player (these are by far the most dominant considerations) and then any conditions defined on its where, during, and when properties (if any). The priority is added to the final score but is likely to make a difference only in the event of what would otherwise be a tie.
- The matching SpecialVerbs are then sorted in descending order of score to allow the highest scoring one (the first entry in the sorted list) to be selected as the one to be used.
- The toks entered by the player that match the SpecialVerb's specVerb property are then replaced by those defined on its stdVerb property, and the resulting toks returned to Parser.parse() to continue on its way with the revised list of toks.If no matching SpecialVerbs were found, the list of toks will be returned to Parser.parse() unchanged.
- Just prior to invoking the Doer that will execute the command, the new Command object (which at this point knows the action and objects involved) calls the current SpecialVerb's checkSV() method, which will prevent the action from going ahead if the SpecialVerb's matchObjs doesn't match the direct object of the command, or any of its where, during, or when properties rule it out.
- Finally, the SpecialVerb's objChecks() is given a chance to intervene.