WelcomeWhat's NewsORLibraryDownloadsLinksWebMaster@OnyxRing.com

This site
produced
656719
pageviews since
8/19/2004

Today's date:
9/26/2017






Author: Jim Fisher
Title: Advanced NPCs, Part 3: NPC Actions

Creation Date: 8/9/2004 6:09:53 PM
Last Updated: 8/9/2004 6:09:53 PM


Introduction
Redirecting To Verb Routines
Actor, Good; Player, Bad
Morping Library Messages
Initiating NPC Actions
A Peek Inside Pandora's Box
A Note on the Code
Advanced NPC Implementation, Part 3
NPC Actions


erb subroutines do it all. They enforce gravity; they differentiate between light and darkness; they decide where a character can travel. These little routines implement the laws of physics in our game world and govern what can and cannot be done. Yet for some reason, historically they only affect the player character. The game world laws that restrict the player do not bind NPCs. The following code exemplifies:


 object room "room" 
   has light 
   with description "This is an empty room."
 ;
 object->"guard" has animate
   with name 'guard'
   , orders [; 
      take: move noun to self; 
         print_ret (The)self," takes ",(the)noun,".";   
   ]
 ;
 object->"table" has static supporter with name 'table';
 object->->"apple" has edible with name 'apple'; 

The guard has now been coded to respond to the "take" order and does so too well. In the above example, he will successfully "take" pretty much anything in scope, even things that he should not be able to:


 room
 This is an empty room.

 You can see a guard and a table here.   

 >take table
 That's fixed in place.

 >guard, take table
 The guard takes the table.
 
 >guard, take self
 The guard takes yourself.

 It is now pitch dark in here!

Obviously this sort of "empowered taking" holds a very narrow slice of the "desired NPC abilities" pie. By convention, the filtering of what an NPC cannot do is also coded in the appropriate routine:

  
 ... replace the guard's previous orders routine
   , orders [; 
      take: 
         if(noun hasnt static && noun~=player){
            move noun to self; 
            print_ret (The)self," takes ",(the)noun,".";   
         }
         print_ret (The)self," cannot take that.";
      ]

For the majority of Inform works, that's the way NPC action is accomplished. Piles and piles of example source code and sample NPCs follow this convention of manipulating the game world directly. There are some drawbacks to this, however:
  • This technique is repetitious. Putting the same rules in the NPC's reaction routines duplicates code that exists in the library's verb routines.

  • This technique can be overly complex. The library considers numerous variables in its efforts to implement the game world. It is very easy to overlook details when writing this sort of code. For instance, in the above example, the guard cannot pickup the player or objects with the "static" attribute, but there is nothing stopping him from taking either "untouchable" objects, or other animate objects. For instance, the guard could take a key contained inside of a locked, glass box (transparent) without breaking the glass. He could also take another NPC behind a locked, cell door. To imitate the library, we would have to code for these circumstances as well.

  • New verbs, such as "cast spell" or "mount horse" are unavailable to the NPC without additional coding too.

  • This technique does not respect an object's "before" routines, which may interrupt an action.

  • By its very nature, this technique limits NPC design, since everything that an NPC can do must be coded for explicitly.
  • Fear not, for there is another, more powerful way...

    Redirecting to Verb Routines

    A more intuitive solution is to leverage the verb routines themselves. Successful implementation of this method addresses all of the above drawbacks and actually makes the code more readable. At first glance, it seems a deceptively simple means of implementing NPC actions. Many new developers have posted questions to the RAIF newsgroup asking what was wrong with their code and why redirecting actions doesn't seem to work. The following is an example or redirecting an ordered action using the library's "action and return" operator (<<...>>):

       
     object->"guard" has animate
       with name 'guard'
       , orders [; take: <<take noun>>;]
     ;   
    

    In the initial test of this example, everything appears to work fine:

       
     room
     This is an empty room.
    
     You can see a guard and a table (on which is an apple) here.   
    
     >guard, get apple
     Taken.
    

    It is not until you look with a little more scrutiny that the flaw in this approach appears:

       
     >i
     You are carrying:   
       an apple
    

    The library's verb routines presume that the player carries out all actions. The player is therefore the one that actually picked up the apple. This is unfortunate, but not insurmountable.

    Actor, Good; Player, Bad

    The standard library is glorious. It is, perhaps, the most useful collection of IF routines ever created. To look upon the Library's code is to be humbled.

    Now then, having appropriately praised the library, we can now proceed with a slightly more critical discussion of it. In particular, if there is one area where I feel the library should have been written differently, it is library verbs and messages. Specifically, these two aspects of the library rely upon the "player" variable, rather than the "actor" variable. See how forcing the player variable to momentarily equal the actor variable almost fixes the above behavior:

       
     ... replace the guard's previous orders routine   
       , orders [save retval;
          save=player;
          player=actor;
          <take noun>;
          player=save;
          return retval;
       ]
    


       
     >guard, get apple
     Taken.
    
     >i
     You are carrying nothing.   
    

    Of course this is simply a patch, and it prevents us from completing our implementation of NPC actions, as we'll see below. A legitimate solution would replace the library's verb and verb-helper routines with slightly modified versions that check the actor variable rather than the player variable. We can do this pretty easily to the "take" and "eat" verbs by cutting-and-pasting the appropriate routines from verblibm.h and then running a search-and-replace against them to change references to "player" into references to "actor":

       
     !only slightly changed from the standard library
     [ TakeSub;
       if (onotheld_mode==0 || noun notin actor)
       if (AttemptToTakeObject(noun)) rtrue;
       if (AfterRoutines()==1) rtrue;
       notheld_mode=onotheld_mode;
       if (notheld_mode==1 || keep_silent==1) rtrue;
       L__M(##Take,1);
     ];
     [ EatSub;
       if (ObjectIsUntouchable(noun)) return;
       if (noun hasnt edible) return L__M(##Eat,1,noun);
       if (noun has worn){   
          L__M(##Drop,3,noun);
          <Disrobe noun>;
          if (noun has worn && noun in actor) rtrue;
       }
       remove noun;
       if (AfterRoutines()==1) rtrue;
       if (keep_silent==1) rtrue;
       L__M(##Eat,2,noun);
     ];   
     [ AttemptToTakeObject item   ancestor after_recipient i j k;   
       ...also needs to be updated since it is called from TakeSub 
     ];
     [ ObjectIsUntouchable item flag1 flag2 ancestor i;
       ...also needs to be updated since it is called from EatSub 
     ];
    

    As shown above, both TakeSub and EatSub call additional routines that also need to be updated in the same fashion. These are AttemptToTakeObject and ObjectIsUntouchable. We won't reprint the modified versions of these two routines since they are long and the changes are minute, but the point should be made that a simple search-and-replace fixes these as well.

    Adding the four modified routines to your source code and putting the appropriate replace directives at the top makes the take and eat verbs available to the NPC without having to change the value of the player variable.

       
     replace  TakeSub;
     replace  EatSub;
     replace  AttemptToTakeObject;
     replace  ObjectIsUntouchable;   
    

    Also, ObjectIsUntouchable is called by numerous other verbs, and TakeSub is called by derivatives verbs. By replacing these four routines, we have also automatically made available to NPCs, all of the following routines and thus their corresponding verb forms:
    TakeSub
    EatSub
    PullSub
    PushSub
    TurnSub
    LockSub
    UnlockSub
    SqueezeSub
    SwitchOnSub
    SwitchOffSub
    EmptySub
    RemoveSub
    DisrobeSub
    WakeOtherSub
    Since so many verbs are available to the NPC we will now drop the use of the library's "action" operators (<...> and <<...>>) in favor of the library's more generic routine, ActionPrimitive. This allows us to forego addressing the implied switch(action) statement that exists in all "reaction" routines (such as orders, life, before, etc...) and redirect all orders to the appropriate verbs with a single line of code:

       
     ... replace the guard's previous orders routine   
       ,   orders [; return ActionPrimitive();]
    

    As we now have the ability to have the guard act for us, we can proceed with ordering him about and testing the affect of using "actor" instead of "player" in verb routines. Notice how drop, which has not yet been updated, does not function correctly:

       
     >guard, get apple
     Taken.
    
     >guard, drop apple
     You haven't got that.
    
     >guard, eat apple
     You eat the apple. Not bad.   
    

    What's with "YOU eat the apple?" It's understandable that the guard could not drop the apple since DropSub has not been updated, but why didn't the guard eat the apple?

    Morphing Library Messages

    Actually, the guard did eat the apple, but the default library message for the verb "eat" was written expecting the PC to be the only character eating. It, and many more library messages, will need to be changed to accommodate this whole new world of NPC actions. Usually, the recommend way of overloading a library message is to declare a LibraryMessages object between "Parser.h" and "Verblib.h" and implement it in the before routine. The following replacement for the "eat" message demonstrates this approach:

       
     object LibraryMessages
       with before[;
             Eat: 
                if(lm_n==2){
                   if(actor==player) print "You eat ";
                   else print (The)actor," eats ";
                   print (the) noun, ".";
                   if(actor==player) print" Not bad.";   
                   return;
                }
             ];
    

    Notice that the message now changes form depending on who is performing the action:

       
     >guard, take apple then eat it   
     Taken.
     The guard eats the apple.
    

    or,

       
     >take the apple then eat it   
     Taken.
     You eat the apple. Not bad.
    

    The number of library messages that need to be changed in order to implement NPC actions is somewhat excessive. Because of this, duplicating the language definition file (English.h) and making modifications there (specifying the modified file on the Inform command line or in the ICL file) may be a preferable method to instantiating a LibraryMessages object.

    Initiating NPC Actions

    For the examples given above, we have been calling ActionPrimitive() from within the NPC's orders routine. This has been convenient because all of the necessary library variables have already been set up for us. When the player is not issuing orders at run-time, however, we need to set these values ourselves. Additionally, there are another couple of complications that we must address:

    1) The ActionPrimitive() routine does not honor the "before" property routines like "action notation" does (<<action>>); In order to achieve this we must make the call ourselves.

    2) Floating objects may not be in scope for the actor.

    These issues can be addressed in a common routine, which seems to be most at home as part of an NPC base class:

       
     class NPC
       with DoVerb[act n s svAL svActr svActn svN svS svInp1 svInp2 retval;   
             svAL=actors_location;
             svActr=actor;
             svActn=action;
             svN=noun;
             svS=second;
             svInp1 = inp1;
             svInp2 = inp2;
             actors_location=parent(self);
             actor=self;
             action=act;
             noun=n;
             second=s;
             inp1 = noun;
             inp2 = second;
             MoveFloatingObjects();
             retval=BeforeRoutines();
             if(retval==false) retval=ActionPrimitive();
             actors_location=svAL;
             actor=svActr;
             action=svActn;
             noun=svN;
             second=svS;
             inp1 = svInp1;
             inp2 = svInp2;
             MoveFloatingObjects();
             return retval;
     ];    
    

    Additionally, BeforeRoutines() and AfterRoutines(), and MoveFloatingObjects() should all be modified in the same fashion as was discussed above. One exception to this "player equals actor" rule is in the call to the player's ORDERS property in the BeforeRoutines() method. This section of code should only be run if the player and actor are the same, otherwise an endless recursion results the will blow the interpreter's stack. A slightly modified BeforeRoutine() which addresses this follows:

       
    [BeforeRoutines;
       if(GamePreRoutine()~=0) rtrue;
       if(actor==player && RunRoutines(player,orders)~=0) rtrue; 
       if(location~=0 && RunRoutines(parent(actor),before)~=0) 
              rtrue;
       scope_reason=REACT_BEFORE_REASON; parser_one=0;
       SearchScope(ScopeCeiling(actor),actor,0); 
       scope_reason=PARSING_REASON;
       if(parser_one~=0) rtrue;
       if(inp1>1 && RunRoutines(inp1,before)~=0) rtrue;
       rfalse;
    ];
    

    Assuming our NPC is derived from the NPC class, this addition will allow us to direct NPC actions from any section of code (like a controlling daemon):

    
     !equates to "guard, put apple on table"   
     guard.DoVerb(##PutOn, apple, table);
    

    A Peek Inside Pandora's Box

    Perhaps the reason this technique is not often seen is the number of convoluted changes that need to be made in order to implement it. At times, implementation of this technique can seem horribly daunting.

    The examples given above are far from complete. In addition to the excessive number of messages that must be modified, a quick scan through the verblibm.h file uncovers the following verb routines that need to be updated (only two of these were done in the above examples):
    InvSub
    TakeSub
    DropSub
    PutOnSub
    InsertSub
    TransferSub
    GiveSub
    ShowSub
    EnterSub
    GetOffSub
    ExitSub
    GoSub
    LookSub
    ExamineSub
    LookUnderSub
    ExitSub
    TouchSub
    WaveSub
    PushSub
    KissSub
    ThrowAtSub
    TellSub
    AskSub
    OpenSub
    WearSub
    EatSub
    There are also several additional routines what are called by these verb subs that also need to be updated. Further, in many of these routines, there are references to the library variables "location" and "real_location." These need to be changed to the library variable "actors_location."

    Obviously a complete implementation of this technique requires major modifications to the library, but once done allows for greater flexibility with simplified, yet more powerful, coding capabilities. Additionally, since the "actor" variable and the "player" variable are normally the same, these changes are transparent. That is, the new behavior only occurs when the new functionality is used and does not affect existing code. Without calling NPC actions, games run as they always have.

    A Note on Code

    The examples above were given to demonstrate this technique, but they do not necessarily represent the best way to implement it. The definition of what is "best" is left up to the reader. As a suggestion, however, rewriting messages to change form can be a convoluted and monotonous task. The print rules discussed in ยง1 of this article series "Pronouns on Steroids" are particularly useful.

    As a final point, it should be noted that a complete implementation of the techniques discussed in this article can be found at the
    ORLibrary in the entries ORNPCVerb.h, OREnglish.h, and ORNPC_doverb.h.

    A special thanks goes out to Stephen Robert Norris, whose input and bug fixing in the ORLibrary necessarily impacted this article.


    Next:
    Wandering and NPC Movement

    Previous:
    Conversation and Learning



    Copyright 2004 - 2017 : Jim Fisher
    OnyxRing.com has been given the potentially non-exclusive right to display the content of this article. However the original author retains all rights. Permission to reproduce this article -- either in part or in whole -- is left strictly to the discretion of the original author.

    Table of ContentsAuthorsSearchIndex
    Would you
    recommend this
    article to
    someone else?
    You bet I would! Heck No!