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 |
This work is not copywritten. It has been submitted into the public domain. Use, abuse, but only blame yourself.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 |
|
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 |
|
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 |
|
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 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 |
|
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 |
|
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 |
|
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. |
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 |
|
Allows the character processing time for the current round. Here, the character may register an action in the _actionList. |
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:
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:
Public Methods and Attributes
Method Name | Parameter(s) | Description |
---|---|---|
npcact_Construct |
|
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 |
|
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. |
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 |
|
Adds _monitor as an object to monitor actions in scope for the player. |
npcact_Remove |
|
Removes _monitor from the player's monitor objects. |
The processing of actions is performed by each NpcAct_Monitor subclass and the NpcAct_ActionProcessor object. The processing proceeds as follows:
If the action was canceled, then
Each time a character registers an action outside of npcact_Turn, the following occurs after the character's call-back method ends:
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
See the included example, NpcAct.inf, for usage of the library.