About NPC-Actions

A Library for Interactive NPCs for Inform

(version 1.0 - 19-Sep-2001)

by Matt Albrecht

Table of Contents

1 Copyright Notice
2 Overview
3 Convensions
4 Concept
5 Design
6 Implementation Details
7 Processing Actions
8 Using the Library
9 Implementation Notes
10 Future Enhancements

1. Copyright Notice

This work is not copywritten. It has been submitted into the public domain. Use, abuse, but only blame yourself.


2. Overview

I have always thought that player oriented games, where everything in the game bow downs before the player, have been rather fishy and contrived. Very rarely in any realistic setting do other characters in stories revolve their life around the player / main character.

This library is designed to allow NPCs (non-player characters) in Inform games to live a life of their own. The framework allows NPCs to act and react to both the player and other NPCs in the exact same model. This model has been explicitly made wide-open to allow it to be extended by other frameworks. This framework only sets a few limitations (or "rules of play") on NPCs, which I believe to be valid limitations.


3. Convensions

Throughout the code, as well as this manual, there has been a consious attempt to keep the global namespace free from clutter. To do this, I have designated all named items to begin with "npcact_", in various capitalization schemes.

I refer to "methods" as object properties which are routines. I designate a library private property, variable, routine, etc. with two leading underscores in the name ("npcact__"). These are designed to be used only by the library, yet are used outside just a single object, and as such prevent me from using the Inform "private" keyword.


4. Concept

I have extended the standard Inform action process by developing the concept of an NpcAct_Action. Each character in the game may only perform a single action per round. Each action, however, may be cancelled by any object which is so coded (and within scope). Also, each character has the ability to react to any given action by prepending or appending the action under question with its own action.

Thus, a system not too dissimilar from the original Inform action sequence has been developed, but this time in an object-oriented fashion, and not restricted to player actions. Also, the player just becomes another contributor to the chain of actions that develop each turn.

I have dubbed this view as a character-oriented game, as opposed to the standard player-oriented games.


5. Design

The following is a general discussion of the design of the framework. It starts by discussing the various pieces of the architecture, followed by a brief overview of how they work together. See Section 6 for details on the software implementation of the framework and the components.

The design presented here focuses on three major components: Actions, Monitors, and Characters, which is believed to be the minimum requirements to implement a character-oriented game.

As a unit of time, each character may execute one action per turn. A complete game round is the cycle starting with the start of the player's turn (the parser waiting for user input), and ending just before the start of the player's turn.


Action

An action is a unit of work any character may perform per turn. Actions are objects encapsulating the standard Inform concept of actions, and thus relate to the action numbers, such as ##Look and ##Take, as well as false actions.

An action is responsible for maintaining all necessary state information required for execution of the action, and for executing the action. This moves action information from out of the global space, into an object's data, allowing multiple actions to be pending at once.

An action is pending if it has been registered by a character, but not yet executed. Pending actions may be canceled, preventing them from executing. An action is successful or complete if it has executed. All actions are disposed when a round has completed.

Some actions may have a canceled response, which is executed instead of the standard execution method, if the action was canceled. This allows for an action to make an appropriate statement if it was not able to execute.

An action is in scope of character A if character B, who registered the action, is in scope of character A.

See implementation details below for more information.


Monitor

Generally speaking, a Monitor is any object in a game which is able to react to actions. The reaction may cancel an action, or any other programatic response.

Monitors are aware of all stages of actions which are to be executed in scope. Announcements include pending actions, canceled actions, and executed actions. Only during the announcement of a pending action may a monitor cancel that action.

Monitors may also register actions during the call-back methods.

See implementation details below for more information.


Character

Technically, the only special feature of a character is the registration of actions, and only one action per round may be registered by a character.

Characters are allowed a deamon-like concept, called a turn. During this turn, the character may perform all needed processing, and may register an action. An action registered during the turn is called a proactive action.

Characters may react to outside stimuli by being a monitor. As a monitor, characters gain all the benefits of being a monitor (awareness of actions, and ability to cancel pending actions), as well as being able to register an action in response to stimuli. Actions registered during the turn are called reactive actions.

See implementation details below for more information.


6. Implementation Details

There are two primary data structures that are needed to create this framework: a Character List and an Action List. These singletons maintain a consistent flow of execution for the processing of the framework.


Character List

The programmer creates all the characters for the story, and sprinkles them about the game in their correct starting locations. Upon initialization of the game, all characters are arranged in a linked list to maintain a consistent flow of action throughout the game.

The character list has no public methods.


Action List

At each turn, the characters' and player's actions are stored and maintained in an Action List. The list is initially empty at the beginning of the turn. The player is the first character to have the opportunity to have an action added to the list, as they always start out the turn.

Public Methods

Method Name Parameter(s) Description
npcact_Register
_action
the action to register for execution
Registers _action as a pending action for the current turn. only valid during a character's npcact_Turn or npcact_Canceled methods.
npcact_CurrentAction none Returns the current action under question. Valid for all monitor phases (any other stage will return nothing).
npcact_Cancel none Cancels the action returned by npcact_CurrentAction. Valid only during the monitor phase npcact_Before.
npcact_RegisterBefore
_action
the action to register for execution
Registers _action as a pending action for the current turn, to be executed before the action returned by npcact_CurrentAction. Only valid during a character's npcact_Before method.

Note: if a character already has a registered action, that previous action will be canceled as if npcact_Cancel was invoked on it.

npcact_RegisterAfter
_action
the action to register for execution
Registers _action as a pending action for the current turn, to be executed after the aaction returned by npcact_CurrentAction. Only valid during a character's npcact_Before or npcact_After method.

Note: if a character already has a registered action, that previous action will be canceled as if npcact_Cancel was invoked on it.



Monitors

Monitors may have up to three call-back methods used for listening to the actions lifetime. These methods will only be invoked if the character that registered the action is in scope of the monitor.

Monitors may only listen to action events if they have the npcact_att_monitor attribute. Through this attribute, monitors may turn on and off their listening capabilities. An object, then, is only a monitor if it has the npcact_att_monitor attribute. This allows for such things as devices which only respond to actions when turned on. Note that removed monitors will not receive any action events, either.

Monitors should subclass NpcAct_Monitor to correctly respond to player actions.

Public Methods

Method Name Parameter(s) Description
npcact_Before
_actionList
the action list from which the current action can be retrieved and canceled.
Invoked after an action is registered, and before it is canceled or executed. The action may be canceled from this method. Even if the action has been canceled by another monitor, all monitors in scope will receive this method. Monitors may register an action at this time. This method will not be invoked on the character that registered the action.

If a Monitor's response is in response to a player action, you may want to return true if you want to cancel the player's action, without generating a Canceled call on everyone. This is useful if the action caused by the player is on the noun, not the player (i.e. for the player moving, you should call Cancel(), whereas "turn tv on" affects the TV, not the player, and so true should be returned, since the action wasn't canceled, but we want to prevent the standard inform library from displaying "You cannot switch that on."

npcact_After
_actionList
the action list from which the current action can be retrieved.
Invoked after an action has completed, and before the end of the turn. No modifications may be done to the list or action at this point. This method will be invoked on all monitors in scope.
npcact_Canceled
_actionList
the action list from which the current action can be retrieved and canceled.
_canceller
the monitor which canceled the current action.
Invoked whenever an action has been canceled by some monitor (this method will not be invoked on the monitor that canceled the action). No actions may be registered or canceled from this method.


Character

Characters have one call-back method (npcact_Turn), and are allowed to register actions with the action list.

In order for npcact_Turn to be invoked, the character must have the npcact_att_character attribute. Thus, a character may enable and disable its "proactiveness" through this attribute.

All characters must be of class NpcAct_Character in order to be listed in the character list*.

If a character is to be "shut down" (such as through death), then it needs to have its npcact_att_character and npcact_att_monitor attributes removed.

Public Methods

Method Name Parameter(s) Description
npcact_Turn
_actionList
the action list from which an action may be registered.
Allows the character processing time for the current round. Here, the character may register an action in the _actionList.


Action

An action is a unit of work any character may perform per turn. They have an action id which correlates to the action numbers in standard Inform (i.e. ##Look and ##Take). Whenever an action is added to the Action List, all objects in scope of the character which generated the action have the opportunity to react to the action. Any object may cancel the action, but all objects in scope are guaranteed to be announced of the action (this allows for AI to know an intent), as well as announced of the cancellation.

An action may have the following attributes:

concealed
The action is not announced to other characters. This may be useful if the character is being sneaky, or if the action is one that is not noticed. Since the action is not announced (i.e. the npcact_Before routine is never called on characters), the action is not cancelable. Be very careful with this attribute, as it virtually eliminates the framework presented here.
locked
The action cannot be canceled. This may be useful for actions such as speaking, although even in this case situations may arise when speaking needs to be canceled (such as a "hush" command). As this keeps part of the framework intact, it is less harsh of an attribute than concealed, but it is still a powerful attribute which needs to be fully considered.

Actions are implemented differently than most objects in Inform. Due to the dynamic nature of actions (a character may perform many different actions), and that characters only perform one action at a time, it is more efficient to have only one Action object per character, rather than a fleet of Actions, each subclassed from a specific action type.

In order to implement this dynamic nature, action templates are created by the programmer. These templates define the behavior and base settings of a specific kind of action. When a character is to register an action, the action object assigned to the character needs to be constructed through an action template. This construction transforms the Action object into an action with the same properties as the template.

As an added bonus, second actions can be generated by an action. A second action is an action directed to the npcact_second object (if one exists), with a reverse action. If a npcact_secondVerb is defined as non-negative, the npcact_second property is not nothing, and the action was not canceled, then, after the last listening monitor received the npcact_After call:

  1. npcact_action is changed into npcact_secondVerb;
  2. npcact_noun and npcact_second reverse;
  3. npcact_Execute and npcact_CanceledResponse are set to nothing.
This allows actions such as ##ThrowAt to be "magically" transformed into ##ThrownAt. Note that this causes more action traffic (slowing down the processing), and can be avoided through careful programming of characters. Since this feature can be disabled by never touching the npcact_secondVerb property, this is an optional feature.

Public Methods and Attributes

Method Name Parameter(s) Description
npcact_Construct
_template
the action template to base the current action object on.
_npc
(optional) the character which invoked the action. If not specified (or set to nothing), then it is assigned to the parent of the action object.
Cleans all data in the action, and changes the methods and other properties to match those of _template. The _npc is set as the actor and action source of the action. This method has protections to disallow a character from re-constructing an action after it has been registered, and before it has completed the processing tasks.

This method is not copied from _template.

npcact_PlayerInScope none Returns true if the player is in scope of the action's source. Allows the action to quickly determine if text should be printed.

This method is not copied from _template.

npcact_Setup template specific Allows the template to complete construction of the action, and to initialize properties with programmer-defined state.

Copied from _template.

npcact_Execute none Performs the template-specific action.

Copied from _template.

npcact_CanceledReponse
_canceller
the character who canceled the action.
If this property is not nothing and a Routine, then it is executed if the action was canceled, instead of the npcact_Execute method.

Copied from _template.

npcact_IsCanceled none Returns true if the action has been canceled, otherwise false.

This method is not copied from _template.

npcact_IsPending none Returns true if the action is pending, otherwise false.

This method is not copied from _template.

npcact_IsComplete none Returns true if the action has executed, otherwise false.

This method is not copied from _template.

npcact_IsRegistered none Returns true if the action has been registered, otherwise false.

This method is not copied from _template.

npcact_IsReversed none Returns true if the action has been "reversed" (the action has already executed, and is now on its second time around with the npcact_secondVerb now executing), otherwise false.

This method is not copied from _template.


Property Name Description
npcact_source The character source of the action.
npcact_actor Equivalent to standard Inform global variable actor.
npcact_verb Equivalent to standard Inform global variable action.
npcact_noun Equivalent to standard Inform global variable noun.
npcact_second Equivalent to standard Inform global variable second.
npcact_secondVerb An optional action that can execute after the primary action completes without being canceled, and if the npcact_second is not nothing.
npcact_data Additional data that is by default copied over from the _template, or may be filled by the Setup.


Player

Even though the player is now considered just another character, I have maintained its original workings in this framework (thus, it is not a subclass of NpcAct_Character). The difference is with the execution of reactions to the player's actions, and the ability for the player to respond to NPC actions.

The player is given an invisible daemon (called here the Imp), which follows the player around. The Imp acts as the player's representative in the action processing. It is the only object now designed to respond to react_before and orders methods (the other standard player response methods are meaningless in this framework). Note, however, that the Imp will allow the standard library to execute on a player's command if the NPC reaction framework allows the action - hence, the standard player interaction may still be used (unexpected behavior may occur, though, since the ordering of react_before is somewhat indeterminate). Actions generated by the player still reference player as the source of the action.

The Imp may also have monitor objects assigned to it. This allows the player to have commands such as "block the door", which may prevent other characters from entering the room. A system of passive actions could be developed for a game, to allow the player to react to NPC actions, while at the same time allowing the player to perform other actions. As an example, the player could "defend maiden" (disallowing attacks against the maiden), while trying to unlock a door. The Imp will only allow one such action to happen per turn, however.

Imp Public Methods

Method Name Parameter(s) Description
npcact_Add
_monitor
the monitor to add.
Adds _monitor as an object to monitor actions in scope for the player.
npcact_Remove
_monitor
the monitor to remove.
Removes _monitor from the player's monitor objects.


7. Processing Actions

The processing of actions is performed by each NpcAct_Monitor subclass and the NpcAct_ActionProcessor object. The processing proceeds as follows:

  1. The round begins. Inform waits for player input.
  2. The player action is performed:
    1. Standard Inform passes the player action around to all monitors in scope via react_before. if the monitor is not active (the npcact_att_monitor attribute is not present), then the react_before method returns.
    2. The react_before is translated into an action, and the action is added to the Action List.
    3. The monitor has its npcact_Before method invoked.
    4. If the player action is canceled, the action is added to the beginning of the Action List (registered by the Imp), and the react_before returns true.
  3. The NpcAct_ActionProcessor daemon is invoked.
  4. The player's action is passed through all active monitors in scope.

    If the action was canceled, then

    1. npcact_Before is called on those monitors which should have been aware of the react_before call-back, but was never invoked due to a cancel.
    2. npcact_Canceled is invoked on the monitors.
    otherwise,
    1. npcact_After is invoked on the monitors.

  5. For Each active character (those with the npcact_att_character attribute present):
    1. The character has its npcact_Turn method invoked. The character is allowed to proactively register an action.
    2. For each active monitor in scope of the registered action, the npcact_Before method is invoked:
      • Each monitor is allowed here to cancel the pending action. This will not disrupt the passing of the npcact_Before event to all monitors of the action, but will set the IsCanceled flag in the action.
      • Character monitors are allowed to register a new action before or after the current action, only if they have not yet registered an action.
    3. If the action was canceled, then the action's npcact_CanceledResponse method is invoked (if not nothing and a Routine), followed by invoking the npcact_Canceled method on each in-scope active monitor. Character monitors may register an action, which will happen when the canceled action was supposed to take place.
    4. If the action was not canceled, then:
      1. The action executes, becoming completed.
      2. The npcact_After method is invoked on each in-scope active monitor. Character monitors may register an action to occur after the executed action.
      3. If the action is reversable, then it is reversed, and it is executed as if the owning character just registered the action during its turn.
  6. The Action List is cleared, and the NpcAct_ActionProcessor daemon process returns, ending the round.

Each time a character registers an action outside of npcact_Turn, the following occurs after the character's call-back method ends:

  1. If the character has already registered an action the registration fails. This occurs during the character's call-back method, during registration.
  2. If the new action is registered to execute after the current action (or the current action was canceled), then the new action is inserted into the Action List after the current action, and the register-during-callback logic is exited.
  3. (At this point, the registered action was marked to execute before the current action). The action processing steps are executed for the newly registered action (above, steps 5.2 through 5.4).

If a character registers multiple actions in the same round, then the character cannot use the same Action object for each registration. If such a situation can arrise in one of your characters, then you will need to have two action objects for this to work right.

Since this dual registration is allowed to occur, steps are taken to prevent a recursive calling technique inside a monitor's call-back method. However, the NpcAct_ActionProcessor may recurse, allowing monitor npcact_Before methods being called several times on several actions before a Cancel or After method is ever invoked.

The ordering of monitor call-backs is such that the following is guaranteed

Other than that, anything may be possible.


8. Using the Library

See the included example, NpcAct.inf, for usage of the library.


9. Implementation Notes


10. Future Enhancements