******** Please note this is a rough draft of how verbs work. ******************** Until this has beeks and the rest of the lima admin ******************** approval it will be considered just that a rough ************ ******** draft. The purpose of this is to give someone a very ************ ******** high level picture of what is happening in the verbs. ************
One of the most often asked questions is what is the difference between a verb and a command.
The "major" difference between verbs and commands is the way in which they are parsed and the 'tokens' the recognize.
Commands use tokens like cfile etc which parse filenames etc.
Verbs use tokens like OBJ, LIV and are suited for game like stuff.
Verbs are used for game type stuff ( i.e. look, get, drop etc.), and most verbs have heavy interaction with other objects.
The first part of this document is a post on Lima Bean from Beek regarding the parser.
________________________________________________________________________________ ___ …. [ snipped for the reason that it wasnt necessary ] ….
First of all, I'm not guaranteeing that the current interface will be the final one, so the given examples are a good indication of what things will probably look like, but not necessarily. [ more snipped ].
Basically, if you are thinking of writing 100s of verbs, you're jumping the gun a bit as the parser isn't finished yet. If you don't mind making small tweaks to them as design changes roll through, though, you'll be fine.
________________________________________________________________________________ _____
Basically the idea is that once lima is “complete”, many of the verbs you are trying to add will more than likely already be part of the distribution. Until which time the parser is complete you may very well have to go back and change things anyway so the best bet is to wait.
for those that are impatient and want to code their own verbs the following is the spec that beek supplied. (keep in mind that the specs may have changed slightly but the functionality is still basically the same).
The efun interface:
void parse_init()
This efun alerts the driver that this_object() is an object that the parser should recognize as a 'game' object. All objects that do not call parse_init() are ignored by the parser. Nothing is actually done at this time; the internal parser structures for the object are set up, and the object is marked as being a parseable object, and is marked as being in a non-setup state.
“/domains/config/help/cre/coding/verbs” (1-60 23%) [h]:
Objects that are not setup will have the parse_* functions called on them *once* at a later point when the driver decides it needs the information for that object. After that, the driver remembers what the functions returned. See parse_refresh().
void parse_refresh()
Notifies the driver that the result of the parse_* functions may have changed. Basically, the driver throws away it's saved information and marks the object as not being set up.
void parse_add_rule(string verb, string rule, int flags)
Adds rule 'rule' for verb 'verb' to the internal parse tables. Rule is in the form 'OBJ in OBJ'. 'flags' is reserved for future use.
mixed parse_sentence(string input)
Asks the driver to handle the input string 'input'. Return values are 1 for everything went spiffy, 0 for 'I haven't a clue what the hell that is' and a string error message if 'input' doesn't make sense, but the parser has a good idea what was intended. parse_sentence("put belboz in trashcan") -> "Belboz vehemently refuses to be put anywhere."; many of these messages are the result of negotiation with the mudlib, altough there are some canned ones (parse_sentence("get foo") -> "There is no foo here.").
This is by far the most complex one. The others are bookkeeping/interface more or less. The algorithm is more or less as follows:
1. Find the first word and lookup the appropriate verb.
2. For each rule for the verb, match it to the remaining input string and attempt to fit parse the sub parts (i.e. for 'belboz in bag' and 'OBJ in OBJ' try to parse 'belboz' and 'bag' as OBJs) -> at this moment, support for OBJ, OBS, LIV, STR, WRD is planned. Probably more to specified stuff like 'an object I am holding' etc.
3. The basic algorithm for parsing an object is to first check if the object table has been loaded (from a previous attempt to parse an OBJ rule). If not, parsing information is loaded from the objects in environment(this_player()).
4. The substring is then iterated over, checking the grammatical types of the words, and seeing if it makes sense/matches an object
5. Assuming a rule is properly matched (there is a non-ambiguous object that matches each OBJ rule, etc) the parser then attempts to check if the rule make sense. It calls the following: -> can_verb() in the zorker object e.g. can_look() { if (!light) return "It is dark"; }
6. Actually execute the verb. verb_obj->do_verb(object io, object do)
There are some details about imprecise matches, error recovery, and error priorities which have been left out here for simplicity. They only improve the error messages, not the functionality.
-Beek
________________________________________________________________________________ ___
One of the most important things to remeber to do in your verb handler ( the file in
/cmds/verbs/) is to
inherit VERB_OB
the important functions are as follows:
can_(verb)() --
This function should do nothing other than tell the driver if it is
possible
for this to happen.
do_(verb)() --
This is where the functionality of the verb takes place.
query_verb_info()
this function is very important, it tells the driver what 'rules' can be associated with the verb i.e. OBJ, LIV, STR etc. Also in the future this is where you will also return the aliases for a particular verb. i.e. get and take will be aliases so that there is only one verb for
those.
****** PLEASE NOTE THAT ALIASES ARE NOT IMPLEMENTED YET *******
The following 2 functions are used in the direct/indirect object respecitvely.
direct_(verb)..()
called when there is a direct object.
indirect_(verb)..()
indirect called when there is a indirect object.
________________________________________________________________________________ __ Lets look at a very simple verb: xyzzy (from the original ZORK adventures).
#include <mudlib.h>
inherit VERB_OB;
mixed can_xyzzy() { return 1; }
mixed do_xyzzy() { write("A hollow voice says: Fool!\n"); }
mixed * query_verb_info() { return ({ ({ "" }), ({ "plugh", "plover" }) }); }
This is a very simple verb. The can_xyzzy by always returning 1 tells the driver OK, this action can happen. the do_xyzzy does the action for the verb.
the query_verb_info returns the optional arguements for the verb (in this case only the verb xyzzy by itself is valid). plugh and plover will be aliases in the future.
________________________________________________________________________________ _____
Lets look at another example of a verb.
#include <mudlib.h>
inherit VERB_OB;
mixed can_give_obj_to_liv(object ob, object liv) { return 1; }
mixed do_give_obj_to_liv(object ob, object liv) { if (!try_to_acquire(ob)) return; this_body()->targetted_action("$N $vgive $o to $t.\n", liv, ob); ob->move(liv); }
mixed * query_verb_info() { return ({ ({ "OBJ to LIV" }), ({ "hand" }) }); }
In the above example, notice the difference in the function names. They correspond to the rule/rules in query_verb_info().
Also notice that the do_give_obj_to_liv() doesnt necessarily have to do the actual action. The do…() can make calls to other functions as it did in this case. The do_give_obj_to_liv() actually has a hook in it to move() in the object.
please note the existence of the following in /std/living.c:
indirect_give_obj_to_liv() { ...... } LIV is the indirect object in this case.
direct_give_obj_to_liv is in /std/object/vsupport.c: (which is where things
go
that dont really have a home yet)
Obj is the direct object in this case.
- more examples coming soon ***********************************
The Natural Language Parser written by George Reese
The Problem
A gut reaction to this entire system is to be overwhelmed by its apparent complexity, comparing it to the good-old days of using add_action(). A discussion of the natural languageparsing system therefore needs to start
by answering the question, "Why bother?".
The old way of handling user input was to define commands and tie them to functions using add_action. Each user object kept track which commands it had available to it, and of which functions each command would trigger. The list of commands changed each time the player object (really, any object which had enable_commands() called in it) moved. In addition, each time an object moved inside the player object or left its inventory, this list changed.
This led to two basic problems:
1.The same command might have slightly different uses in different parts of the mud. Or worse, the player could have two similar objects which define the same command in slightly different ways. 2.The complexity of syntax varied based on creator abilities and was limited by CPU.
For example, one creator could have created a rock in their area that added the ’throw’ command. This creator is a newbie creator, and thus simply put the following code in their throw_rock() function:
int throw_rock(string str) {
object ob;
if( !str ) return 0; ob = present(str, this_player()); if( !ob ) { notify_fail("You have no rock!"); return 0; } if( ob != this_object() ) return 0; if( (int)ob->move(environment(this_player())) != MOVE_OK ) { write("You cannot throw it for some reason."); return 1; } write("You throw the rock."); say((string)this_player()->query_cap_name() + " throws the rock."); return 1;
}
In this case, “throw rock” will work, but “throw the granite rock at tommy” will not. But another creator also defined a throw command in their spear. This creator however, is a good coder and codes their throw_spear() command to handle ’throw the red spear at tommy’ as well as ’throw spear’. Explain to a player why both syntaxes only work for the spear, and not for the rock. Then explain to that player why ’throw rock’ for a rock built in yet another area yields ‘What?’.
An early attempt to get around this problem was the parse_command(). “/domains/config/help/cre/coding/verbs.txt” (1-60 17%) [h]:
Unfortunately it was buggy spaghetti that was way too complex for anyone to understand. The MudOS attempt tosolve this problem is its new natural language command parser. The parser is based on the following assumptions:
All commands should behave in a consistent manner across the mud. Similar objects should respond as expected to the same command line. A player should only see 'What?' (or its equivalent) when they make a typo. It should enable creators to handle the complex command processing required by the above assumption.
Overview of the MudOS System
The MudOS natural language parser is based on a philosophy of centralized command parsing. In other words, creators have no control over which commands exist nor over what syntax rules existing commands follow. Instead, creators are tasked with defining what those commands mean to their objects. Unlike with add_action() where commands are registered to the driver, the MudOS system registers verbs (the command) and rules with the driver. In this example, a simple “smile” verb is registered with a single rule, “at LIV”.
With the old way of doing things, commands were executed either when a player entered in a command or when the command() efun was called. With this new system, a command may be executed at any time via either the parse_sentence() or parse_my_rules() efuns.
When one of those efuns is called, the driver searches through the list of verbs for a verb and rule which matches the command string. In order to do that, however, it needs to make several calls of all the objects involved to determine what the sentence means. For any given command string, the following objects are relevant to the parsing of that command string:
the verb handler This object contains the functions used to see if the command is valid and to execute it. It is also the one that creates the rules for the verb. the subject This is the object from which parse_sentence() is called, or the object mentioned as the first argument in parse_my_rules(). It is the object considered to be executing the command in questin, the logical subject of the sentence. the master object This object keeps track of global information, such was what literals (mistakenly referred to as prepositions) exist across the mud. In general, a literal is a preposition. the direct object This is not the logical direct object of the sentence. Rather, this is the first object at which the verb action is targetted. the indirect object Again, this is not the logical indirect object of the sentence. Rather, it is the second object at which the verb action is targetted. For example, in "give the book to the elf", the elf will be both the logical indirect object and the parser indirect object. But if you allow "give the elf the book", the elf naturally still remains the logical indirect object, but the book is the indirect object to the parser since it is the second object targetted by the verb (the first being the elf).
“/domains/config/help/cre/coding/verbs.txt” (61-120 35%) [h]:
Each object involved in he parsing of a sentence, except the subject, is responsible for handling certain driver applies that help the driver in parsing the sentence. The subject, unlike the other objects, is responsible for initiating the command. Although this document treats all of these objects as if they were completely distinct, it is possible to have the same object performing multiple roles for the same command. For example, your subject could also be direct object and verb handler. The next section discusses the objects and the applies they are responsible for in detail.
The Objects
Before studying each object in detail, it is important to keep in mind that each object which can be involved in any role must call parse_init() before doing anything related to verb parsing. The only exception is the master object.
The subject
The subject is simply the initiator of a command. A command is typically initiated by a call to parse_sentence() inside the object’s process_input() apply. This example shows how a player object might use parse_sentence() to initiate a command. This efun will return 1 if the command successfully matched a known verb and rule and successfully executed it. If it found the verb in question, but it did not match any rule, then 0 is returned. If it found the verb in question and matched a rule, but the execution of the rule failed, it will return an errorstring describing why it failed. Finally, if no verb matched the command, then -1 is returned.
Take for example a mud with this one rule:
parse_add_rule(“smile”, “at LIV”)
The efun parse_sentence() would return the following values for the following command lines:
smile at descartes
Returns: 1
smile happily
Returns: 0
smile at the red box
Returns: "The Box is not a living thing!"
eat the red box
Returns: -1
The master object
The master object is responsible for a single apply, parse_command_prepos_list(). This apply returns a list of literal strings which may be used in a rule. A literal string is simply one that appears in the rule exactly as a player types it. In the smile example above, “at” is a literal. In most all cases, literals are prepositions, thus the name of the apply.
The verb handler
The verb handler object is responsible for setting up the rules for a verb and handling the test “/domains/config/help/cre/coding/verbs.txt” (121-180 53%) [h]:
and execution of those rules. This example demonstrates a simple verb handler for the smile verb described above. As you can see, each rule is divided up into three parts:
1.initialization 2.testing 3.execution
The intialization is the call to parse_add_rule(). This associates a rule with a verb. The first argument is the verb (this verb may have spaces in it, like “look at”) and the second argument is one of the rules being handled by this verb handler. This list defines the valid tokens for a rule.
The testing portion is a “can” apply. In testing a rule, the driver calls the applies can_<verb_rule> to determine if the execution of the verb even makes sense in this situation. The test apply is called when the driver has valid arguments to a rule, but it wants to see if those valid arguments make sense right now. For example, you might check a player here to see if they have enough magic points for casting the spell this verb/rule represents. If not, you might return “You are too tired right now.”. If the rule match up in question makes completely no sense at all, like for example, they tried to throw a house for the (“throw”, “OBJ”) rule, you should return 0. The parser will guess well an error message from the situation. In this case, it will have parse_sentence() return “You cannot throw the thing.”.
Finally execution is where the verb actually happens. You know you have a verb/rule match and you know all the arguments to it are valid. Now it is time to do something. You almost always want to return 1 from this function except in extreme circumstances.
The direct and indirect objects
As stated above, the directness or indirectness of an object has nothing to do with the linguistic meaning of those terms. Instead it has to do with what position in the token list the object takes. The direct object is the first object in the token list, and the indirect object is the second object in the token list.
These objects basically answer the question “Can I be the direct/indirect object for this verb and rule?”. Like the testing and execution applies for verb handlers, the applies that answer this question may return 1, 0, or an error string. Also like the testing and execution applies for the verb handler, these come in the form of (in)direct_
Inventory visibility
Some objects are subjects, direct objects, or indirect objects of verbs which require access to things in their inventory. For example, living things, bags, chests, etc. all need to allow other things access to their inventories for some commands. Two applies handle this situation:
1.inventory_accessible() 2.inventory_visible()
The first returns 1 if verbs can have access to an object’s inventory, the second returns 1 if verbs simply can take into account an object’s inventory. An example of the difference might be a glass chest. When closed, you want its inventory to be visible, but not accessible.
It is important to remember that is the return value for any of these special applies, including is_living(), you need to make an explicit call to the parse_refresh() efun. Unless the parse_refresh() efun is called, these special applies are only called once with no guarantee as to when that one call will actually occur.
“/domains/config/help/cre/coding/verbs.txt” (181-240 71%) [h]:
Creating a New Verb
Currently, the Lima and Nightmare mudlibs use this parser system. Both mudlibs provide inheritable objects which make it simpler to interface with the MudOS parser system. Nightmare specifically has the inheritable LIB_VERB with methods for defining a new verb.
This verb example comes from the Nightmare mudlib. The simple Nightmare verb requires the following steps:
1.Name the verb 2.State the verb rules 3.Name any synonyms 4.Set an error message for display when the command is wrongly used 5.Create help text for the command
Naming the verb is done through the SetVerb() method. You simply specify the name of the verb.
The rules are passed to the SetRules() method. You may specify as many rules as are needed for the verb.
Like rules, synonyms are set as a list for the SetSynonyms() method. A synonym is simply any verb which is exactly synonymous with any possible rule for the verb in question. The player is able to access help for the verb and get error messages for the verb through the verb or any of its synonyms.
The error message is a string displayed to the user when they use the verb in an incorrect manner. For example, if I typed ’eat’ when the rule is ’eat OBJ’, the error message would be ‘Eat what?’.
Finally, like with any object, the help text can be set through the SetHelp() method. Help is very important for verbs.
All of these methods only are able to take care of verb initalization. It is up to the verb creator to give meaning to a new verb. This is done first off by writing can_*() and do_*() applies in the verb handler. These methods should be very simplistic in nature. For example, a can method almost always simply returns 1. A do method generally finds its target and triggers some sort of event in that object. The event does the real command handling.
In addition to can and do applies, you need also to write any direct and indirect applies in approperiate objects. Nightmare centralizes this sort of processing through inheritables geared towards responding to particular verbs. A good example of this is LIB_PRESS which responds to the “press” command. Thus any object which should be pressable needs only to inherit this object to become a pressable object.
The can, do, direct, and indirect applies all have the same argument set for the same verb/rule pair, but it is important to know when the parser knows certan things. Take for example the verb/rule “press OBJ on OBJ”. The parser takes the following actions:
1.Call can_press_obj_on_obj() in verb handler 2.Call direct_press_obj_on_obj() in all accessible and visible objects 3.Call indirect_press_obj_on_obj() in all accessible and visible objects 4.Call do_press_obj_on_obj() in the verb handler
The arguments to all methods called in this process are:
1.object direct_object 2.object indirect_object 3.string direct_object_as_entered_on_command_line
“/domains/config/help/cre/coding/verbs.txt” (241-300 89%) [h]:
4.string indirect_object_as_entered_on_command_line
But how can can_press_obj_on_obj() know what the direct and indirect objects are if they have not been identified yet? The answer is that it cannot. For the command “push the button on the wall”, in a room with me and you in it and we carry nothing, the sequence looks like this (return in parens):
1.verb->can_press_obj_on_obj(0, 0, "the button", "the wall"); (1) 2.me->direct_press_obj_on_obj(0, 0, "the button", the wall"); (0) 3.you->direct_press_obj_on_obj(0, 0, "the button", "the wall"); (0) 4.room->direct_press_obj_on_obj(0, 0, "the button", "the wall"); (1) 5.me->indirect_press_obj_on_obj(room, 0, "the button", "the wall"); (0) 6.you->indirect_press_obj_on_obj(room, 0, "the button", "the wall"); (0) 7.room->indirect_press_obj_on_obj(room, 0, "the button", "the wall"); (1) 8.verb->do_press_obj_on_obj(room, room, "the buton", "the wall"); (1)
This assumes, of course, that the room responds positively with the id’s “button” and “wall”.
People familiar with the parser might say, “Hey, wait, there is a lot more that happens than just that.” In fact, there are many more possible permutations of this sequence. The most interesting is the ability to simply ignore the difference between prepositions like “in” and “into” which are often used interchangeably in colloquial speech. For example, if you had “put OBJ in OBJ” and “put OBJ into OBJ” verb/rules, you could handle them in a single place for each of the applies respectively liek this:
can_put_obj_word_obj() direct_put_obj_word_obj() indirect_put_obj_word_obj() do_put_obj_word_obj()
If the parser found no can_put_obj_in_obj() defined, it then searches for a more generic handler, can_put_obj_word_obj(). In fact the real order it searches for a can handler is:
1.can_put_obj_in_obj() 2.can_put_obj_word_obj() 3.can_put_rule() 4.can_verb_rule()