AgendaItems

The Purpose of AgendaItems

Some NPCs can afford to be fairly passive, at most reacting to what the player character says or does, but you may want at least some of the NPCs in your game to be a bit more life-like, pursuing agendas and goals of their own. One way to implement this in the adv3Lite library is through the use of AgendaItems, objects that encapsulate some action that your NPC takes when certain conditions become true. By adding and removing AgendaItems from your actors at appropriate moments, and defining the conditions under which they are invoked, you can create the appearance of NPCs who act more independently, not just reacting to what the player character says and does.

Each actor can thus have its own "agenda," which is a list of AgendaItems. Each AgendaItem represents an action that the actor wants to perform - this is usually a goal the actor wants to achieve, or a conversational topic the actor wants to pursue.

On any given turn, an actor can carry out only one agenda item.

AgendaItems are a convenient way of controlling complex behaviour. Each agenda item defines its own condition for when the actor can pursue the item, and each item defines what the actor does when pursuing the item. AgendaItems can improve the code structure for an NPC's behaviour, since they nicely isolate a single background action and group it with the conditions that trigger it. But the main benefit of agenda items is the one-per-turn pacing - by executing at most one agenda item per turn, we ensure that the NPC will carry out its self-initiated actions at a measured pace, rather than as a jumble of random actions on a single turn.

Note that AgendaItems are not invoked when conversation has taken place on the same turn, except in the special case of a DefaultAgendaTopic which can be used to allow an NPC to pursue its own conversational agenda rather than giving a bland default response to something the player character says (this will be discussed more fully in a later section). Apart from the special cases of DefaultAgendaTopics and FollowAgendaItems (to be discussed below), AgendaItems in adv3Lite work identically to those in adv3.


Defining AgendaItems

At an absolute minimum, an AgendaItem must be defined with an invokeItem() method that defines what happens when the AgendaItem is invoked. The invokeItem() method will usually at some point set the AgendaItem's isDone property to true to indicate that the AgendaItem has now finished its work (and so can be removed from the actor's agenda). AgendaItems often also define their isReady property (which is simply true by default) to contain an expression that evaluates to true (or a method that returns true) when the time is right for the AgendaItem to be invoked. In addition, AgendaItems should always be located directly in the Actor to which they relate. For example:

guard: Actor 'burly guard; strong powerful; man; him' @doorway
    "He looks incredibly strong and powerful. "
;

+ guardWarnAgenda: AgendaItem
    isReady = Q.canSee(guard, gPlayerChar)
    
    invokeItem()
    {
        "The guard straightens up at your approach, tightening his grip on his spear
         and letting out a fierce growl, as if to say, <q>Don't you even dare 
         <i>think</i> about going through this door!</q> ";
         
         isDone = true;
    }
;

Here we've defined an AgendaItem that will be invoked as soon as the guard can see the player character, but which will only be invoked once (since isDone is set to true at the end of the first invocation). However, it won't be invoked at all unless we also do one of two things:

  1. Also define isInitiallyActive = true on the AgendaItem; or
  2. Previously call the addToAgenda(item) method on the actor, e.g. guard.addToAgenda(guardWarnAgenda), to add it to the actor's agendaList property.

Note that we could also call the addToAgenda() method indirectly via the <.agenda item> tag, e.g. <.agenda guardWarnAgenda>, though this also adds the item to the agendaLists of any DefaultAgendaTopics associated with the Actor, which may or may not be what we want to do (we shall describe DefaultAgendaTopics more fully in a later section). If for some reason we want to prevent an AgendaItem from being invoked after we've added it to the actor's agendaList we can use the actor's removeFromAgenda() method, e.g. guard.removeFromAgenda(guardWarnAgenda), for which there's also a corresponding <.remove item> tag, e.g. <.remove guardWarnAgenda>. In adv3Lite the addToAgenda(item) and removeFromAgenda(item) can also be called with a list of items (a list enclosed in square brackets); this is one difference from the adv3 library, which does not allow this (and even in adv3Lite you can't do it with the <.agenda> and <.remove> tags).

The complete list of methods and properties on AgendaItem that are likely to be of interest to game authors includes:


Other Kinds of AgendaItem

In addition to the AgendaItem class, the adv3Lite library defines three subclasses of AgendaItem for specialized use.

A DelayedAgendaItem is just like an AgendaItem, except that it is invoked a set number of turns after it is added to its actor's agendaList. The delay is set by calling the DelayedAgendaItem's setDelay(turns) method, which returns a reference to the DelayedAgendaItem. This allows you to add a DelayedAgendaItem to the actor's agendaList and set the delay in a single statement, e.g.:

    bob.addToAgenda(someDelayedAgendaItem.setDelay(3));

Note that the delay is enforced on the isReady property of the DelayedAgendaItem, so that if you want to enforce some other condition as well you need to and it with the inherited condition, e.g.:

+ guardReactionAgenda: DelayedAgendaItem
     isReady = inherited && canSee(guard, gPlayerChar)
     
     invokeItem()
     {
         isDone = true;
         "The guard hefts his spear threateningly and prods you in the stomach,
           as if to say, <q>Go now, while you still can!</q> ";
     }
;

The second specialized kind of AgendaItem is the ConvAgendaItem, which can be used to allow an NPC either to initiate a conversation or to seize the conversational initiative during a lull in the conversation. By default it is invoked when the actor can speak to the player character (they're within audible distance of each other), and there isn't a current conversation node, and either the player character hasn't conversed on the same turn or the play character has said something that triggers a DefaultAgendaTopic (for which see below). Once again, if you need to define a further condition on the isReady property of a ConvAgendaItem you need to and it with the inherited handling, for example:

+ clerkPartyAgenda: ConvAgendaItem
    isReady = inherited && gRevealed('office-party')

    invokeItem()
    {
        isDone = true;
        "<q>By the way, I hope I didn't give you the wrong impression,</q>
        the clerk tells you cautiously. <q>Our office party last week 
        wasn't <i>that</i> wild. ";
    }
;

Note that if you want something to behave as both a DelayedAgendaItem and a ConvAgendaItem you can just define it with both classes,e.g.:

+ clerkJacketAgenda: DelayedAgendaItem, ConvAgendaItem
   invokeItem()
   {
      isDone = true;
      "<q>I've been meaning to ask you,</q> says the clerk. <q>Where <i>did</i>
       you buy that jacket?</q> ";
   }
;

 ...
 
 /* Somewhere else in the code */
 
    clerk.addToAgenda(clerkJacketAgenda.setDelay(10));

The third special kind of AgendaItem is the FollowAgendaItem. This is used when an actor want the player character to follow him/her/it. To define a FollowAgendaItem you need to define its connectorList property, which is a list of connectors (which in the simplest case could just be rooms, but might need to include doors, stairways and other TravelConnectors) through which the actor wishes to lead the player character. You can optionally also override the specialDesc method to provide a description of where the actor wants to lead the player character, and an arrivingDesc method to describe the leading actor arriving in a new destination having just led the player character there; for example:

    
+ FollowAgendaItem
    initiallyActive = true
    
    connectorList = [hall, kitchen, diningRoom]
    
    specialDesc()
    {
       "Bob is waiting for you to follow him ";
       switch(getActor.getOutermostRoom)
       {
          case study:
            "north into the hall. ";
            break;
          case hall:
            "east into the kitchen. ";
            break;
          case kitchen:
            "north into the diningRoom. ";
            break;
       }
    }
    
    arrivingDesc()
    {
       "Bob crosses the room and waits for you to follow him
        <<nextDirection.departureName>>. ";
    }
;

The full list of additional properties and methods that FollowAgendaItem defines is as follows: