
                                                        
                            NPC Engine Documentation
                            ========================

                             written by Volker Lanz

                             Release 3, 31. 5. 1999


-------------------------------------------------------------------------------
    Index
-------------------------------------------------------------------------------

     0. Disclaimer
     1. What the NPC Engine does
     2. Quick how-to to include the NPC Engine in your game
     3. Advanced features
     4. Functions
     5. NPC_Engine properties
     6. Entry points
     7. Notes
     8. How it works
     9. Possible enhancements
    10. Demo Story
    11. Changes to the NPC Engine

-------------------------------------------------------------------------------
0.  Disclaimer
-------------------------------------------------------------------------------

    NPC Engine is a library extension for the Inform interactive fiction
    authoring tool. It is provided "as is". The author takes no responsibility
    whatsoever for any malfunctioning of this code.
    
    All files included are Copyright (c) 1999 Volker Lanz. You may use these
    files in your own games freely. You may NOT alter any of the files and
    publish or distribute these modified versions.

    If you wish, contact the author via:
    
        volker.lanz@gmx.net

    Comments, bug reports, criticism, questions and suggestions are always 
    welcome.
                

-------------------------------------------------------------------------------
1.  What the NPC Engine does
-------------------------------------------------------------------------------

    The NPC Engine started out about a year ago when I was learning Inform
    and trying to make NPCs move in a complex way like in Infocom games such as
    Deadline and Suspect. I stumbled over the MoveClass library extension, which
    I found capable of doing many of the things I wanted to do. However, I
    still (as always) wanted more. So I began writing this one. Initially, there
    were lots and lots of routines scattered across various files, working, but
    nasty. I cleaned up a lot of it for the game Intruder which I published
    early in 1999. Again, I wasn't satisfied and reworked the whole thing. This
    library extension is now the result of that.
    
    NPC Engine does everything that MoveClass does (with the exception of random
    NPC movement, which I never had any use for). It also nearly does anything
    that follower.h can do, though not quite as sophisticated (following NPCs in
    cars, and example given in follower.h, wouldn't be possible with the NPC
    Engine right now, but could easily be implemented, if need be).
    
    That means, if you would like to have NPCs in your game that are moving
    through the playfield in a more sophisticated manner than just moving them
    from one room to another each turn on a predefined path and if you want the
    player to have the opportunity of following NPCs, this one is the right
    library extension for you.

    Additionally, it offers these features (I'm sure you can guess where the
    examples are taken from):
    
    *   Complex descriptions of NPCs moving somewhere in the distance:
    
            Mrs. Robner is off to the north, heading towards you.
            Mr. Baxter, off to the south, opens a door and ducks into a room
            to the west.
            
        and even:
            
            You can hear footsteps on the staircase.
            
    
    *   Asking the game itself and other NPCs where an NPC is:
    
            > MRS. ROBNER, WHERE IS GEORGE?
            "I last saw him just a minute ago. I don't know where he went, 
            though."
            
        or
        
            > WHERE IS MS. DUNBAR?
            You haven't seen her yet.
            
        Note that you'll have to remove question marks from the player's
        input in BeforeParsing() to make this last one work.
            
    
    *   Following NPCs you see moving in the distance.
    
            ...
            Mr. McNabb, off to the northeast, leaves your view to the east.
            
            > FOLLOW MCNABB
            
            Orchard Path
            ...
            Mr. McNabb is off to the east, heading to the southeast.
            
    
    *   Looking through windows:
    
            Guest Room
            ...
            
            > LOOK THROUGH WINDOW
            ...
            To the east, Mr. Baxter comes into sight from the south.
            Mr. McNabb is to the east, examining his work.
            
            
    *   Faster path finding
    
            The NPC Engine is quite fast in its finding of paths for NPCs.
            It uses a modified version of E. Dijkstra's shortest path algorithm
            which produces very little overhead. Additionally, the NPC Engine
            does not use objectloops to go through its classes but sets up a
            chained list of objects in each class to speed up loops.

        
    *   Flexibility
    
            Various entry points allow the story author to modify the behaviour of
            the NPC Engine in many ways.    


-------------------------------------------------------------------------------
2.  Quick how-to to include the NPC Engine in your game
-------------------------------------------------------------------------------

Assuming that you already have some skeleton of a game, follow these steps
to intergrate the NPC Engine into your story:

    *   Include the npc_engine.h file in your main game file. Do this right
        after including verlib.h. You probably know that the Inform Library
        is a bit picky about when you include what, so make sure to include
        npc_engine.h right there.
        
    *   Call the function NPC_Initialise() from your game's Initialise()
        routine. This will set up all necessary variables and start the
        NPCs' daemons. Do not stop an NPC's daemon in your game unless you
        remove the NPC. You may pass one argument to NPC_Initialise() to set
        the type of following used. See "Advanced features" below.
        
    *   Make all your NPCs inherit the NPC_Engine class.
    
    *   Make all your rooms inherit the NPC_Room class. If you're sure that
        a certain room will never be walked into, through or out of by
        any NPC throughout the whole game, you may wish not to make it of
        the NPC_Room class. In large games with lots of rooms, this may
        speed up path finding.
        
    *   If you do not already have a player object of your own, create one
        (copy the selfobj code from the library file parserm.h into your
        main game file and rename it to something useful, e.g. player_obj).
        Include the following lines to its each_turn property:
        
            ...
            each_turn
                [o;
                    o = npc_first_npc;
                    while (o)
                    {
                        if (TestScope(o, self))
                            o.npc_met = turns;
                        o = o.npc_next;
                    }
                ],
            ...
            
    *   Set the global variable player to your player object in your 
        Initialise() routine.
        
    *   Be careful with your doors! Everything that applies to doors in code
        using MoveClass applies here. That is, you must not use the location
        global variable in your door code. Thus, instead of saying:
        
            ...
            door_to
                [;
                    if (location == Hallway)
                        return Living_Room
                    return Hallway;
                ],
            ...
        
        you will have to say (equally working, but different to the NPCs):
        
            ...
            door_to
                [;
                    if (self in Hallway)
                        return Living_Room;
                    return Hallway;
                ],
            ...

    *   Also (and again like in MoveClass) you may never use (direction)_to
        properties of rooms to print strings. If you have something like
        this:
        
            ...
            e_to    "There is only a short, blind corridor there.",
            ...
            
        you will have to do it this way to make the NPC Engine work:
        
            ...
            before
                [;
                    Go:
                        if (noun == e_obj)
                            "There is only a short, blind corridor there.";
                ],
            ...
            
        without any e_to property.
        

    *   Now you can move your NPCs by calling the NPC_Path() function. An
        NPC's npc_arrived property may deal with his arrival at
        a given destination.


-------------------------------------------------------------------------------
3.  Advanced features
-------------------------------------------------------------------------------


    *   Finding NPCs

        The NPC Engine includes a "find npc" verb. If the player ever met
        Mrs. Robner before,
        
            > FIND MRS. ROBNER
            
        will tell him when he last saw Mrs. Robner, or, if she is visible 
        right now, tell him where she is.
        
        The player may also ask other NPCs where an NPC is:
        
            > GEORGE, WHERE IS MRS. ROBNER
            
        George will reply what he knows: If he met Mrs. Robner, say, an hour
        before, he'll say "I last saw her about an hour ago. I don't know
        where she went, though." If she's in the same room, he'll simply
        say "Ahem...". If he hasn't met her before, he will reply "I haven't
        seen her today."


    *   Following NPCs

        The player may type
        
            > FOLLOW MRS. ROBNER
            
        if he can see her at the moment to follow her. The NPC Engine knows
        three different types of following:
        
            1. Normal - The same technique used in the "follower.h" library
               extension: The same command is executed for the player that
               the NPC took the last move. Not useful if the player can see
               the NPC somewhere in the distance.
               
            2. JUMP_FOLLOW_TYPE - If the player has seen the NPC moving the
               last move, he is directly taken to the location the NPC is now.
               Note that this is probably not so useful, since it will move
               the player through closed and even locked doors etc etc. This
               is, however, the way Infocom did implement the following in
               games like Deadline (check it out by following Mrs. Robner on
               her way from the living room to the master bedroom when she's
               going there to call back Steven - you can indeed walk through
               closed doors).
               
            3. PATH_FOLLOW_TYPE - Probably the best solution to the problem.
               The player will move one location toward where he sees the
               NPC in question right now.
        
        A game may set which type to use by passing either JUMP_FOLLOW_TYPE or
        PATH_FOLLOW_TYPE to the NPC_Initialise() routine. Passing nothing will
        set the follow type to normal.
        

    *   Looking through windows
    
        The simple action of looking through a window is actually quite hard to
        implement. The main problem about this is that if the player looks
        through a window, the response is printed and THEN the NPCs are
        moved, thus the player always gets information about what was true
        the last move. If accurate timing is important, this becomes 
        unacceptable. The solution I chose to this may seem a bit awkward
        at first: The NPC_LookThroughWindow() doesn't print anything, but
        simply sets a property in all NPCs to the window that was looked
        through. When the NPC is moved aftwards, this property is checked
        and a message is printed, if the NPC is visible through the window.
        
        Follow these steps to allow looking through a window:
        
            -   Add a looks_to property to the window, listing all the
                rooms this window lets the player see into:

                ...
                looks_to    Corridor_1 Corridor_2 Corridor_3 Corridor_4,
                ...
                
            -   Add a door_dir to the window, saying in which direction the
                windows looks:
                
                ...
                door_dir    e_to,
                ...
                
            -   Add a door_to property, saying which is the room directly
                on the other side of the window:
                
                ...
                door_to     Corridor_4,
                ...
                
            -   Finally, call NPC_LookThroughWindow() in the before() property
                for ##Search or ##LookThrough or what have you:
                
                ...
                before
                    [;
                        Search:
                            print "You can see the corridor from here.^";
                            return (NPC_LookThroughWindow(self));
                    ],
                ...
                
                Note that since the NPC_LookThroughWindow() routine doesn't
                know yet if anything will be printed at all, you should always
                print some text not related to the NPCs moving, like in the
                example above. If you don't and if no NPC is visible, 
                nothing will be printed else.

        This sounds probably quite complicated for such a simple thing, but if
        you go through these steps slowly and think about why they're all
        necessary, I'm sure you will get the idea behind it.
        Note: An NPC's daemon must be active to make this NPC visible through
        a window if he is not moving. This is one of the reasons why NPC's
        daemons should never be stopped as long as the NPCs are still on the
        visible playfield.
        

    *   Stopping NPCs on their path to somewhere

        If you want some action of the player to stop the NPC temporarily on
        his path, you can achieve this goal by setting the NPC's npc_stopped
        property to a value higher than 1. E.g., if you wish to stop an NPC
        if the player says
            
            > HELLO MRS. ROBNER
            
        you would add the following to Mrs. Robner's before() property:
        
            ...
            before
                [;
                    ...
                    Hello:
                        self.npc_stopped = 2;
                        "Mrs. Robner is facing you.";
                        
                    ...
                ],
            ...
            
        This will stop Mrs. Robner from following her set path for two moves.
        The NPC Engine will print a default message ("Mrs. Robner starts to move
        about distractedly.") one move before she'll walk off again. If you want
        the player to be able to stop her from moving by talking to her, you may
        wish to add a self.npc_stopped = n; to her life's property ##Ask reaction,
        too.


    *   Altering the default NPC Engine messages

        You can change any message the NPC Engine prints by replacing the routine
        NPC_Msg() from npc_engine.h with your own, modified version. Every message
        printed by the NPC Engine is in this routine (apart from error messages).

    
-------------------------------------------------------------------------------
4.  Functions
-------------------------------------------------------------------------------

    *   NPC_Initialise (type_of_following)

        Used to initialise the NPC Engine. You must call this from your game's
        Initialise() routine. You may pass one argument: The type of following
        you want to use. If you do not pass an argument, normal following is
        used.
        

    *   NPC_Move (npc, destination, action, object)

        This moves an NPC to a destination, using a certain action and a certain
        object (exactly like MoveNPC in follower.h works). It also prints a
        description of the NPC moving. If you want to remove an NPC from your
        game, call NPC_Move(npc, 0);.
            

    *   NPC_Path (npc, destination)

        Calculates and sets an NPC on its path to a destination; keep in mind that
        the NPC will only start moving if its daemon is active.
        Note that calling NPC_Path with the arguments used in moveclas.h is
        still valid. The arguments ifblocked and use_best, however, don't do
        anything.
        This one may be replaced by game code, because it only calls another
        function called NPC__Path() (note the two underscores). Replacing this
        is particularly useful if you have a set of pre-defined paths that you
        want to be checked first and call NPC_PrePath() if any of them can
        be used; only if none of them fits you then call NPC__Path().
    

    *   NPC_PrePath (npc, array, number_of_entries)

        This works exactly like NPCPrePath in MoveClass. You must pass an NPC, an
        array of stored destinations (e.g. "e_obj, e_obj, n_obj" and so on) and
        the number of entries in said array.
            

    *   NPC_Schedule (npc, destination, delay)

        Schedules the movement of an NPC to a certain destination using the NPC's
        timer. Give the delay in moves until you want the NPC to begin moving.
            

    *   NPC_LookThroughWindow (window)

        Used to look through windows. See "Advanced Features".  


    *   NPC_FindPath (npc, destination)

        If you should ever have to, you can make use of NPC Engine's path finding
        capabilities without actually starting a movement by calling this one.
        Pass npc and destination. The npc object will have the directions necessary
        in its npc_dirs property IN REVERSE ORDER. The npc_path_size property
        contains the number of entries.
            

    *   NPC_Msg (action, message_number, object1, object2)

        NPC_Msg() prints all the NPC Engine messages. Replace it to modify the
        messages, if you want to.
        
    
    *   NPC_Error (error_number, object1, object2)

        By replacing this routine with an empty stub-routine, you can keep the
        NPC Engine from printing any error messages. May be useful in release
        versions of games (MAY be...).  


-------------------------------------------------------------------------------
5.  NPC_Engine properties
-------------------------------------------------------------------------------

    The following is a list of all the important properties each object of the
    NPC_Engine class has:
    

    *   npc_before_action()
    
        Called each time before the movement of an NPC. Should return true if
        it printed something to help notify waittime.
        
    
    *   npc_after_action()
    
        Called each time after the movement of an NPC. Should return true if
        it printed something to help notify waittime.
        
    
    *   npc_arrived(room)
    
        This one is being called when the NPC arrives in it target room. It
        gets passed the room the NPC is now in. Usually, this one is used to
        schedule the next path the NPC takes.
        
    
    *   npc_walkon()
    
        Called when an NPC enters the room the player is in to notify of his
        movement.
    

    *   npc_walkoff()
    
        Called when an NPC leaves the room the player is in to notify of his
        movement.
        
    *   npc_door_open()
    
        This isn't a property of the NPC_Engine class. Instead, each door in your
        game that an NPC may walk through needs to have this. It just has to
        open the door, if need be, and return true if it did just that:

                ...     
                npc_open
                    [npc;
                        if (self hasnt locked)
                        {
                            npc.npc_door_passed = self;
                            give self open;
                            rtrue;
                        }
                        rfalse;
                    ],
                ...
                
        As you can see, the property also stores the NPC it was passed in the
        npc_door_passed property of that NPC. This is necessary to enable
        NPC Engine to notify the player of this door opening.


-------------------------------------------------------------------------------
6.  Entry points
-------------------------------------------------------------------------------

    These are the entry points the NPC Engine calls. Replace any of these
    functions in your own code to modify the behaviour of the engine. Note
    that some of these already do some stuff, so you should always copy the
    originals from the npc_engine.h file and modify these instead of writing
    completely new functions.


    *   NPC_AfterPrintVisible (npc, current_room)

        Is called if the NPC Engine tried to print a movement description for
        an NPC, but couldn't since it concluded that the NPC is not visible
        to the player. This one could be used for "You can hear footsteps on
        the staircase." stuff. Arguments passed are the NPC in question and the
        room he is in. Must return true if it printed something.
        

    *   NPC_BeforeCheckVisible (current_room, test_room)

        Before the visibility of a certain room from a certain location is
        checked, this routine is called. If it returns true, the NPC Engine
        will assume the room is not visible. current_room is the room the
        visibility is checked from, test_room is the room for which it is
        checked.
    
    
    *   NPC_BeforeFollowing (npc)

        Is called before an NPC is followed. If it returns true, following is
        not allowed.
            
    
    *   NPC_AfterFollowing (npc, previous_location_of_player, success)

        After following an NPC, this routine is called. Arguments are the NPC
        in question, the previous location of the player before following and
        1, if the following was successful or 0 if not.


-------------------------------------------------------------------------------
7.  Notes
-------------------------------------------------------------------------------

    *   The maximum number of NPCs the NPC Engine can deal with is 32. This could
        be extended if need be. I just couldn't think of a game that needed
        more.
    
    *   The NPC Engine uses the NewRoom() Inform Library entry point. If you
        need this entry point yourself in your game, rename the NewRoom() 
        function to something else meaningful (e.g. NPC_NewRoom()) and
        include a call to this renamed function in your own NewRoom().

    *   If you use timewait.h, delete or comment out the tw_waiting object
        definition in npc_engine.h. Because timewait.h has to be included
        after npc_engine.h, the header file cannot know if you're going to
        include timewait.h or not. So this is the only possible solution.

    *   The NPC Engine doesn't care whether you use time or moves in your
        story. The standard replies to the "where is"/"find" question,
        however, use one turn for one minute. If time passes quicker or
        slower in your game, you will have to alter this.

    *   If you really use calculated paths extensively, there may be still a
        certain delay in your game on slower computers. However, because most
        of your NPCs aren't moving most of the time anyway (or, to be
        precise, paths needn't to be calculated for these NPCs every move, but
        only at the beginning of each new movement), using ten to fifteen
        NPCs shouldn't even cause a noticable delay on fairly decent machines.
        I used MoSlo to test the Engine, set the speed of my p2-333 to 10% and
        played a demo game with seven NPCs that are moving quite a lot. It
        was slower, sure, but it wasn't too annoying (waiting four and a half
        hours in the game took about thirty seconds while it took over
        three minutes with MoveClass version 7).

    *   At the moment, the player can never see through doors, may they be
        open or closed. This is an intended behaviour. (Mail me if you can't
        live with that.)

    *   The NPC Engine uses the daemon property of each NPC object. This, of
        course, means that you cannot use this property in your game. You may
        want to use the npc_before_action() and npc_after_action() properties
        instead.

    *   No descriptions are printed for NPCs that aren't moving. The NPC's
        own describe property may do that. On the other hand, while NPCs are
        moving, they're given the concealed attribute to prevent describe
        from being called by the library. This avoids things like:
        
            Living Room
            ...
            Mrs. Robner is sitting here knitting.
            ...
            Mrs. Robner heads off to the east.
            ...

    *   At the moment, the NPC Engine does not support pluralname objects in its
        standard messages (does anyone use pluralname objects with NPCs?).
        

-------------------------------------------------------------------------------
8.  How it works
-------------------------------------------------------------------------------

    *   Finding paths
    
        Finding paths in NPC Engine is achieved using an algorithm by the
        Scandinavian scientist E. Dijkstra. The idea behind it is that you
        start off with a source room, mark it explored and give it a label
        of 0. Then you go through all the rooms reachable from the first
        room, mark them explored and give them a label of 1. The next loop,
        you go through all neighbours of rooms labelled 1, mark them explored
        and label them 2 and so on, until one of the rooms you find on your
        way is the destination room. If you store the direction you came
        from and room name with each explored room, you get the shortest
        path to follow simply by going from the destination room
        backwards. This is the reason why npc_dir entries are stored in reverse
        order by the NPC Engine.
        

    *   Following NPCs with the PATH_FOLLOW_TYPE

        A fake NPC is created and the shortest path to the room the NPC who
        is being followed is calculated. Then, one step is taken on this
        path.
            
    
    *   Looking through windows

        To allow for this, a fake room is moved next to the room on the other
        side of the window that is looked through and it is pretended as if
        there was a passage between these two rooms. The NPC movements
        are then described as if the player was in that room.

    *   Optimising speed
    
        One problem with calculating paths in larger games is the 
        
            objectloop(x ofclass Y)
        
        command in Inform. This command goes through all objects in the game 
        and tests them against the class in question. If you have some five-
        hundred objects, this becomes really slow if it's done a few times 
        between player inputs. The solution I chose was to cascade all rooms 
        and all NPCs once at the beginning of the game and go through these 
        only if necessary. If found that this heavily improved performance.


-------------------------------------------------------------------------------
9.  Possible enhancements
-------------------------------------------------------------------------------

    *   Already finished, tested and fully working is an enhancement that needs
        some hacking of the original Inform Library files and therefore wasn't
        included in this library extension: The ability for the parser to
        remember which NPC you currently talk to, offering the possibility
        to say:
        
            > HELLO MRS. ROBNER
            Mrs. Robner is facing you.
            
            > TELL ME ABOUT GEORGE
            (said to Mrs. Robner)
            "A child. He may be a man in age..."

        Should you really need this, please let me know.
        
            
    *   Something I tried and didn't get to work the way I really wanted it to
        was the addition of "fake windows". Imagine a balcony looking onto a
        garden. You would of course want to have NPC movement descriptions
        for the NPCs in that garden. This could be achieved by a fake window
        on the balcony that has an each_turn property calling 
        NPC_LookThroughWindow(). The problem is that you get descriptions of
        non-moving NPCs each turn with this technique, certainly quite annoying.
        I toyed around with this for a while, but didn't find any satisfying
        solution that was easy to implement without hacking too much of the
        original library code.


    *   As well finished is an enhancement of waittime.h that allows to wait
        for an NPC. This may be released in the near future if I find the time
        to get in touch with the various authors of waittime / timewait.


-------------------------------------------------------------------------------
10. Demo Story
-------------------------------------------------------------------------------

    This release of the NPC Engine includes a small demo story in source
    and binary format to show the use of the engine. Note that, although
    this demo game features rooms from the game "Intruder" (which can be
    downloaded from ftp.gmd.de/if-archive/games/zcode/intruder.zip), it
    doesn't have any objects other than the rooms themselves and the NPCs
    aren't from "Intruder".


-------------------------------------------------------------------------------
11. Changes to the NPC Engine
-------------------------------------------------------------------------------

    3.990531    *   Now works with Inform 6.21 and Library 6/9; complies to
                    strict error checking rules
                *   Minor modifications and enhancements to the manual
                *   Demo game added
                *   NPC Engine now uses TestScope() instead of comparing
                    ScopeCeiling()s where possible
    1.990423    First public release
-------------------------------------------------------------------------------

    Did you find this manual easy to understand? Did it answer all the
    questions you had regarding the NPC Engine? If not, please mail me
    your questions regarding the NPC Engine and suggestions for improvements
    both to the engine itself and to this document.
