Creating Dynamic Characters

This article's title makes it sound like one of those how-to books for novice writers, doesn't it? Make your characters more exciting, dynamic, and lifelike! Make them leap off the page with their dynamic and vibrant personalities! Bring them to life with riveting, dynamic dialog!

Well, this article is about those sorts of things, but not in quite the same way as those how-to books. Remember that we're talking about Interactive Fiction, not the ordinary kind of fiction, so the term "dynamic" has another meaning in this context. I'm not talking about "dynamic" in the sense of exciting, vibrant, and outgoing; I'm talking about it in the sense of changing over time. This article describes some TADS 3 library features that are designed to make it a lot easier to create dynamic characters.

This article is presented in three parts. This first part is about making your characters do things other than just sit around and wait for the player to ASK ABOUT something. The second part turns to the design of conversation systems; it surveys some of the systems that authors have used in the past, and offers recommendations on designing your own game's conversations. The third part gets into the specifics of programming conversations with the TADS 3 library.

What's so good about dynamic characters?

It happens that there's a connection between the "changing over time" sense of "dynamic" and the "exciting" sense, so in a way I really am talking about how to make your characters more lifelike and exciting. In IF, the best way to create a vibrant, exciting, and, above all, realistic character is to make the character move around, do things, and react to the unfolding story, rather than sitting in one place doing one thing for the whole game. In our everyday experience, in our basic psychology, the key feature that distinguishes living creatures from everything else is that living things are animated - they move, they have their own will, they're aware of their surroundings, they react to events.

Now, I'm well aware that this is a very tall order. If we strip away all the facades of the library classes and the object-oriented programming and so on, and we think about it reductively, IF characters are made of dead, inanimate matter in the form of bits on a computer disk. Despite all their complexity, computers just don't fool anyone into thinking they're living creatures. Our animate/inanimate sensors were built into our brains long ago, and finely honed through millions of years of the urgent necessity to avoid becoming another living creature's lunch. We're simply not fooled - not for long, anyway - even by something as elaborate as a modern computer. It's no wonder that most characters in interactive fiction fail to seem lifelike.

The situation isn't hopeless, though. True, we're not going to be able to create a character simulation good enough to truly fool anyone; that's the very definition of the legendary Turing Test, and if we could actually beat the Turing Test, we'd be spending our time collecting accolades and royalty checks rather than fooling around with adventure games. So let's accept this reality and move on. Fortunately, passing a Turing Test, exciting as it would be, isn't necessary for our purposes. Remember what the "F" in "IF" stands for. Humans are maddeningly good at telling animate from inanimate, but they're also pretty good at what's known as "suspending disbelief": accepting a story on its own terms, buying into it emotionally, even believing in the story, while all along knowing perfectly well that it's completely made up.

People love to suspend their disbelief. They pay good money to publishers and film studios to give them an excuse to do it - and if you've seen a Hollywood movie lately, it's pretty obvious that it doesn't even have to be a good excuse. You don't have to fool people into doing it or coerce them into doing it; you get it for free just by announcing that you're going to tell them a story. Once you've got a reader willingly suspending her disbelief, the game is yours to lose. The easiest way to lose the player's cooperation is to violate your story's own rules, by doing something blatantly inconsistent with the expectations the story has created.

(Okay, before you complain that I'm being too simplisitic, I'll happily concede that upsetting expectations is a basic tool for any good writer, that the only rule in writing is that there are no rules, etc. But there are good sorts of surprises and bad sorts. The good kinds are usually the ones that are surprising within the context of the story; the bad kinds are usually the ones that blow away the whole context. Finding out that the first-person protagonist is actually the killer - that's a good kind. Learning that the whole story was a dream - bad kind. The difference is that the first kind doesn't change anything about the story-telling framework, while the second kind does; the second kind has the author intruding by telling you that the story-telling was just a story, which is not something you accepted going in. In very rare cases, and in the hands of a very skillful author, the meta-story surprise can work to great effect - but these tricks are lame a lot more often than they're great. And anyway, that's not the sort of well-thought-out surprise twist kind of device I'm talking about. When I talk about blatant inconsistency and violating your own rules, I'm just talking about things that will look to the player like programming bugs.)

How not to create dynamic characters

Okay, let's get more specific about the sorts of inconsistencies that plague characters in IF. Let's look at a character in a hypothetical game:

  >look
  Baccarat Table (on the chair)
  Here in the high-stakes room, they don't put any tacky
  markings on the violet felt of the Baccarat tables.  The
  air is just as smoky as where the rabble play, of course,
  so you can't see far down the aisle that extends east and
  west through the card tables.

  On the table are the dealer's cards, your cards, the
  tuxedo man's cards, and a martini.

  The dealer stands behind the table.

  A man in a tuxedo is sitting at the table.

  >x man
  He's dressed suavely in a black tuxedo, and he's smoking
  a long cigarette.

  >ask man about baccarat
  He takes a drag on his cigarette, and blows a puff of smoke
  to one side.  "Baccarat, like life, is all about risk."

Now suppose that this is going to be a cautionary tale about the dangers of mixing drinking, gambling, and smoking.

  The man in the tuxedo takes a sip of his martini, spilling
  a little on one of his cards.  With his other hand, he
  reaches out to put some chips on the table - but he's also
  holding his cigarrete with that hand, and some ashes fall
  off right on the spot where the martini spilled.  Alcohol
  and paper are a flammable combination, as everyone knows,
  so it bursts into flames!  The man tries to put out the
  fire, but his hands are so full that he spills the martini
  all over himself.  Now his hair's on fire!

So far, so good (well, maybe the prose could be spruced up a bit, but anyway). Let's run through some of the same commands from above now that the man's hair is on fire:

  >look
  Baccarat Table (on the chair)
  Here in the high-stakes room, they don't put any tacky
  markings on the violet felt of the Baccarat tables.  The
  air is just as smoky as where the rabble play, of course,
  so you can't see far down the aisle that extends east and
  west through the card tables.

  On the table are the dealer's cards, your cards, the
  tuxedo man's cards, and a martini.

  The dealer stands behind the table.

  A man in a tuxedo is sitting at the table.

  The man in the tuxedo is running around screaming, "Ow! My
  hair is on fire! Help!"

  >x man
  He's dressed suavely in a black tuxedo, and he's smoking
  a long cigarette.

  The man in the tuxedo's hair is on fire!  He's yelling, "Ow!"

  >ask man about baccarat
  He takes a drag on his cigarette, and blows a puff of smoke
  to one side.  "Baccarat, like life, is all about risk."

  The man in the tuxedo yells, "Ow!"  His hair is on fire!

Ever played a game like this? I have. I don't mean to make fun of authors who make these kinds of mistakes, and I'm certainly not parodying any specific game - I'm not even thinking of a specific example. I'm just trying to give you a clear picture of why it's so darned hard to program convincing characters in IF.

The problem is that characters have a lot of special, custom code to implement all of their different aspects, and all of that custom code has to be further customized for each different thing the character does. It's a multiplicative problem: just to write a basic character that does nothing, you have to write ten different bits of code; to write a character that does two different things (sits at table, runs around with hair on fire), you have to write those same ten bits again for the second case, so now you have twenty bits; three different behaviors, thirty bits. In the example above, we see four separate things that we have to customize for the "hair on fire" situation: the character's room description message; the "examine" description; the response to ASK ABOUT BACCARAT; and the "daemon" routine that lets the character do something by itself on each turn. That's not even the end of it. To be really thorough, we'd also have to customize giving the character orders (MAN, GO NORTH), all of the other ASK ABOUT topics, HELLO, GIVE TO, SHOW TO, and TELL TO.

How to create dull, lifeless, static characters

I have a lot of friends who work in the software industry. One of the interesting things about software companies is that they're all relatively young as corporations go; as a result, when it comes to their business processes, a lot of them essentially make it up as they go along. One amusing story I've heard, from sources at more than one company, is about an ingenious solution to the problem of schedule slips. It seems that certain companies have discovered that the development process tends to get bogged down in the testing phase; the QA department keeps finding bugs, forcing development to go back and spend a bunch of time fixing things - frequently, this time was not budgeted in the release schedule, so each bug discovered makes the software slip further past its announced release date. Nobody likes these delays. These several companies realized that the true source of this recurring problem is that the QA department keeps finding bugs. Dump the QA department, and you stop finding bugs. Stop finding bugs, and you ship on time! I'm not making this up.

Unfortunately, IF authors are all too often moved by a similar impulse when it comes to dealing with the exploding complexity of making characters do things: it's not the QA department that gets dumped, it's the interactivity. It's fully understandable, and I've fallen victim to this myself, so I'm hardly in a position to impugn the moral rectitude of anyone else who's done it. I can think of three main ways this manifests itself:

I don't want to suggest that these techniques are never appropriate. On the contrary: these are all perfectly good ways to handle characters who are nothing more than scenery, or who exist only to provide some necessary bit of plot advancement. You'd obviously go insane trying to do better than these for a crowd of supernumeraries in a street scene - and there'd be no point anyway, as few players would ever notice the extra detail. But a lot of games have at least one or two important non-player characters. If an NPC is important to the story, it's vital that the NPC rises above these expedients.

How to program dynamic characters before TADS 3

By now, I hope I've persuaded you that building dynamic IF characters is hard work, and that you should do it anyway. If you've ever written a game before, then you're probably feeling like you're playing the choir to my preacher on the "it's hard" part; and if you've played many text games, then likewise on the "you should" part. Bear with me; we're almost to the interesting "how-to" material, but there's one more bit of problem-framing I want to do first.

Looking at the problem description so far, and seeing it reduced to a list of things that have to be customized for each behavior, you might be starting to wonder if IF authors aren't just a bunch of big babies for thinking NPCs are so hard. After all, we can make a list of things that have to be done; why not just sit down and write the code? Shouldn't it just be a matter of going down the list and cranking out the if-then-else branches? Well, yes, that's pretty much it; but in practice, that's a lot worse than it might sound.

The thing that really makes it so hard, I think, is that the straightforward approach to handling this - the big pile of if-then-else branches - turns out to be a really poor way of organizing code. We have to write a bunch of routines that all have a similar form:

  desc()
  {
    if (hairOnFire)
      // do one thing
    else
      // do another
  }
  actorHereDesc()
  {
    if (hairOnFire)
      // etc
    else
      // etc etc
  }
  dobjFor(AskAbout)
  {
    action()
    {
      if (hairOnFire)
        // etc
      else
        // &c.
    }
  }

...and so on for all of the different things that need to be customized. One obvious problem with this approach is that it's just plain tedious; it's a lot of repetitive typing, and it's a lot of steps to remember every time you add a new behavior or change an existing one. Yes, you could use a checklist to make sure you remember everything, but who really wants to program that way? If you can't keep it all in your head, or copy-and-paste from a previous example, it's too much overhead. And the busy-work aspect is, I think, a much bigger obstacle than it might seem at first glance. My guess is that a lot of authors have found themselves thinking of a great new thing for an NPC to do, but then they picture the dozen or so if-then-else branches they'd have to modify, and the cool new thing doesn't seem so important any more.

There's another, bigger problem, though. If you want to find out what happens in the "hair on fire" case, you have to look all over the place. An important principle of good software design is that you shouldn't have to look all over the place for related code; good software keeps related code together.

How to program dynamic characters in TADS 3

This, finally, brings us to the matters that I actually set out to talk about: what the TADS 3 library can do to help. Past IF libraries (TADS 2's included) have been remarkably skimpy in the area of NPC support. The TADS 3 library tries to break from this tradition by providing some programming patterns, along with supporting classes, designed to make it a lot less work to create well-developed NPCs.

Actor state objects

One of the key benefits that's made object-oriented programming so popular is that it provides a pretty decent framework for keeping related code together. As we saw above, the usual approach to writing dynamic NPCs - a huge network of if-then-else tests - breaks up related code and scatters it all over the place. TADS 3 is supposedly an object-oriented language, but we still have this mess; so has OOP failed us? Well, no: we just have to rethink the problem into a more object-oriented form.

The problem would seem to be that we have a bunch of different pieces of code that are related, but are all stuck in the "if (hairOnFire)..." branches of "if" statements, which are scattered across multiple methods. OOP's answer to everything is to make an object; so it seems that what we'd like to do is create an "if (hairOnFire)" object, and refactor all of those "if" fragments as methods in our new object. Something like this:

  ifHairOnFireObj: object
    desc() { ... }
    actorHereDesc() { ... }
    askAbout() { ... }
    // and so on
  ;

Obviously, this doesn't help us much if all we're doing is moving the fragments from the "if" branches into the new object, and replacing the branches with calls to the object, like so:

  // in the original object
  desc()
  {
    if (hairOnFire)
      ifHairOnFireObj.desc();
    else
      // etc
  }

This isn't much better than we had before. The code for the "hair on fire" cases is at least grouped a little better, but we still have all of the tedious if-then-else statements to type in, and we still have to remember to add a new if-then-else branch every time we add a new behavior.

Once again, object-oriented programming can come to the rescue. We've already used one key aspect of OOP - encapsulation, the grouping of related code into an object. Now we can apply another key OOP concept - polymorphism, the ability to dispatch calls dynamically to different objects at run-time. The trick is to decide that we're going to store a reference to the "ifHairOnFireObj" object in the Actor object, by putting it in a property of the Actor. Not only that, but we're going to be able to store other objects there as well. The idea is that we're going to store the correct one of these objects there, depending on the actor's current behavior. So, when we have "hair on fire," we'll store "ifHairOnFireObj"; when we're "sitting at table," we'll store "ifSittingAtTableObj". And so on for other states. Now, our Actor code is reduced to this:

  curState = ifHairOnFireObj
  desc() { curState.desc(); }
  actorHereDesc() { curState.actorHereDesc(); }
  dobjFor(AskAbout) { action() { curState.askAbout(); } }

See how this works? The actor code becomes completely freed of special cases relating to the current behavior - it doesn't have to know anything about the current behavior except that there is a current behavior. For all of the "customization points" that vary according to the actor's behavior, the Actor code doesn't do anything except call the current "state object," which encapsulates all of the custom code relating to the behavior. This always works the same way, no matter how many different behaviors the actor has. To add a new behavior, you just define a new object along the lines of the "ifHairOnFireObj" object - we don't have to touch the Actor itself. To activate the behavior, call the actor's "setCurState(state)" method, passing the new state object as the parameter.

The TADS 3 library implements exactly this set-up. The nice thing is that all of the basic wiring is pre-defined in the Actor object - you won't have to write all of those calls to curState.thisAndThat() yourself.

The ActorState class

The class that defines an actor's current behavior is called ActorState. The library pre-defines several subclasses of ActorState for special situations. The base ActorState class itself can be used directly, and provides a suitable set of defaults that will work for most actors without further customization. Of course, the whole point is to make further customization easier, but the defaults let you customize only the parts you want to customize.

An ActorState object is associated with an actor via the ActorState's location property. ActorState objects aren't simulation objects (for example, they'll never be listed as part of a room description, and players can't refer to them in commands), so this use of the location property doesn't imply containment in the usual sense. The reason we use location is that it makes it extremely convenient to define an actor's state objects - simply use the "+" notation to put the state objects "inside" the actor:

  bob: Person
    // ... bob's properties
  ;

  + bobDefault: ActorState
    isInitState = true
    specialDesc = "Bob is here. "
  ;

  + bobSweeping: ActorState
    specialDesc = "Bob is sweeping the porch. "
  ;

Here's a summary of most of the important customization points in the ActorState class, and what they do.

Hermit states

The library defines a subclass of ActorState called HermitActorState. This is useful in a situation that comes up frequently: an actor becomes temporarily unresponsive, because the actor is busy doing something else.

The hermit state is extremely easy to use. Just customize the normal description messages, and add a message to respond to any sort of interaction (ASK, TELL, GIVE, SHOW, HELLO, etc).

  + bobStackingCans: HermitActorState
    stateDesc = "He's stacking cans into a pyramid. "
    specialDesc = "Bob is here, carefully building a pyramid of cans. "
    noResponse = "He seems to be deep in concentration; you
      probably shouldn't disturb him right now. "
  ;

The Actor.curState property

An actor's current state object is given by the Actor object's curState property. This must always be set to a non-nil ActorState object. To change an actor's state, simply call the actor's setCurState(state) method, passing the new state object as the parameter.

Setting an actor's initial state

If you define the property isInitState = true in an ActorState object, then the library will automatically set the corresponding actor's curState property to point to that ActorState during pre-initialization. This means you don't have to worry about setting the actor's curState property explicitly; just set isInitState to true in the state object, and the library will take care of initializing the actor.

Stateless Actors

If all of this sounds like it's way too complex for your simpler NPCs, don't worry. ActorState objects are completely optional.

If you never give an Actor an explicit ActorState object, then the library will automatically create a default ActorState object for the Actor during pre-initialization. This means that you don't have to create an ActorState object at all for simple, static actors.

Furthermore, you can always customize an Actor directly in the Actor. Reading the description of ActorState, you might have been worried that, since so much functionality is farmed out to the ActorState object, you'd be required to create an ActorState object in order to customize trivial things like the description. Not so. The general strategy is that the base ActorState class reflects most calls back to the corresponding Actor for processing, by default. Since the library automatically creates a default ActorState object for any Actor that doesn't explicitly define an ActorState of its own, and since this default ActorState object will simply send most calls right back to the Actor, you can do all of your customizations by overriding the appropriate Actor methods.

Agendas

One way that you can make an actor do things on its own is by programming background activities in the takeTurn() method in an ActorState object, or in the idleTurn() method in the Actor itself. For the bulk of NPCs, the only thing you want the actor to do on its own is display some random background activity messages; for such simple actors, the takeTurn() approach is fine. In some cases, though, we want an actor to show some more complex type of initiative, and procedural scripting can quickly become unwieldy for this. The library has a mechanism called "agendas" that can help.

Agendas are for situations where you want an actor to pursue a goal on its own.

The way agendas work is fairly simple. Each goal is represented by an AgendaItem object. The AgendaItem encapsulates two key bits of information: first, whether it's ready to execute or not, indicated by the method isReady(); and second, how to carry out the agenda item, in the form of a method called invokeItem(). Each actor keeps a vector of AgendaItem objects, called the actor's "agenda list." On each turn, the actor checks its agenda list; if it can find an item that's ready to execute, it executes that item. Only one agenda item is executed on an actor's turn, which aids realism by ensuring that the actor only tries to do one thing at a time.

You must always nest AgendaItem objects inside their Actor object using the "+" notation. This associates the AgendaItem objects with their actor, but it doesn't add them to the actor's agenda list. You must explicitly add agenda items by calling the actor's addToAgenda() method. Agenda items must be added explicitly to the actor's agenda list because an actor's motivation typically changes over time.

Separately from the readiness condition, each item has a "done" condition that indicates if the item has been accomplished. This is the isDone() method. Once an item is marked as done (that is, once its isDone() method returns true), the actor will automatically remove the item from the agenda list. If you're creating an agenda item that only has to be executed once, then you can simply set isDone to true in the item's invokeItem() method; this will ensure that the item is removed from the agenda list as soon as it's been executed. Alternatively, you might want the actor to repeatedly try the item until the item's goal has been accomplished; for these cases, you can override the item's isDone so that it returns the result of checking your success condition. Note that isDone takes precedence over isReady: if both isDone and isReady return true, the item will not be executed, because the actor will remove the item from the agenda list before looking for "ready" items in the list.

One good situation for an agenda is where an NPC wants to get the player character to talk about something. For example, an NPC might want to get a YES or NO answer to some question, but doesn't want to trap the conversation at that question. The ConvAgendaItem is ideal for this sort of situation. A ConvAgendaItem is a subclass of AgendaItem that provides a special isReady method that makes the agenda item ready only when the actor hasn't already engaged in conversation on the same turn. This ensures that the NPC will wait for an opening in the conversation before posing its question.

  bob: Person
    // ... definitions for bob ...
  ;

  + lighthouseAgenda: ConvAgendaItem
    isReady = (inherited() && me.location == bob.location)
    invokeItem()
    {
      "Bob clears his throat. <q>Did you say you've been to the
      lighthouse today?</q> he asks, his voice a little shaky. ";

      bob.setConvNode('lighthouse?');
      me.noteConversation(bob);
    }
  ;

(We'll see more on "conversation nodes" and other aspects of the library's conversation system features in part three of this article.)

Note that the agenda item doesn't come into play automatically. You have to explicitly add it to the actor's active agenda list by calling bob.addToAgenda(lighthouseAgenda). You'd do this whenever the agenda item first enters the NPC's motivation, so in this example, we'd add the agenda item when Bob first hears about the lighthouse from the player character, perhaps via a TELL ABOUT command.

Accompanying Travel

A situation that occurs frequently is something I call "accompanying travel." This is where you have one or more actors traveling along with the player character, but where it's still up to the player to actually initiate the travel. For example, a lot of games have a "sidekick" character who spends most of the game following the player character everywhere. This is how sidekicks are usually implemented:

  >north
  Hallway
  The dark wood paneling makes this hallway seem even
  narrower than it really is.  The hall extends east
  and west; a door leads south.

  Brent enters from the south.

In other words, the sidekick literally follows the player around. For sidekicks, this is a little clunky but not terrible. For other situations, it can be far less palatable.

  "I told you to keep walking north, human scum" the lead 
  Blurg guard says, poking you again with his hyperlaser.

  >north
  Cell Block
  This is a narrow north-south corridor, lined on
  either side by closed cell doors.

  The guard enters from the south.

For any situation where the accompanying character is supposed to be escorting or leading the player character, having the other character follow is unsatisfatory. Even for sidekicks and other situations where characters are traveling together in a group, it would be much nicer to have the travel actually described as group travel, rather than looking as though the others are just happening to straggle in after the player character.

The TADS 3 library provides a mechanism that improves this situation greatly. A couple of actor state subclasses, provided by the library, help out with this.

The "accompanying travel" mechanism in the library works by sending the accompanying actor on ahead of the player character, rather than having the actor straggle in afterwards. This might seem like it's substituting one inelegant sequence of messages for another, but the advantage is that it lets you customize both the "before" and "after" messages. For our Blurg guard example, we could get this much better effect:

  >north
  The lead guard prods you on.

  Cell Block
  This is a narrow north-south corridor, lined on
  either side by closed cell doors.

  The guard scowls.  "Keep going, puny earthling."

What we've done is taken over the actor-departing and actor-is-here messages. These messages are handled by the state object, and for accompanying travel, we activate a special state object instance (based on the library-defined AccompanyingInTravelState subclass) for the duration of the travel.

The first message, before the room description appears, is actually the actor-departing message; rather than seeing "The Blurg guard leaves to the north," we see this special custom message, courtesy of our state object. The second message, "The guard scowls," is the actor-is-here message, also from the state object.

Setting up accompanying travel is pretty straightforward. There are two parts: first, triggering the accompanying travel in the first place; and second, customizing the before and after messages generated during the group travel.

Triggering accompanying travel

Accompanying travel is controlled by the ActorState object of the NPC who's traveling with the player character. So, in our Blurg example above, we'd have to customize the ActorState object of the Blurg guard.

To make an actor accompany another actor, the actor's state must inherit from AccompanyingState. You can use AccompanyingState as a mix-in to other ActorState subclasses - we could, for example, combine AccompanyingState with HermitActorState to define an escort state.

In our state object, we have to define a couple of methods that control how the accompanying travel is carried out. First, we define accompanyTravel(), which decides whether or not we should accompany a particular actor for a particular travel action. This method returns true to accompany the travel, nil to ignore it. Second, we define getAccompanyingTravelState(), which gives us a state object to use temporarily during the travel operation.

In most cases, we'll want to accompany the player character, so our accompanyTravel() method will check to see if the actor doing the traveling is the player, and accompany the travel only if it is. The method can also look at the travel connector that will be used for the departure, so it can selectively choose to travel with the other actor only to certain places.

Here's how an escort state object might look:

  blurgEscortState: AccompanyingState, HermitActorState
    specialDesc = "The Blurg guard is keeping his hyperlaser
      trained on you. "
    stateDesc = "He has his hyperlaser pointed straight at you. "

    accompanyTravel(leadActor, conn) { return leadActor == gPlayerChar; }
    getAccompanyingTravelState(leadActor, conn)
      { return new BlurgEscortTravelState(location, leadActor, self); }
    
    noReponse = "Silence, feeble human! "
  ;

We'll see shortly why we defined getAccompanyingTravelState().

Customizing the travel messages

Once you've overridden accompanyTravel() in the ActorState to return true, your NPC will accompany the player character automatically. However, there's usually one extra thing you want to customize, which is the set of messages displayed during the group travel. To do this, you need to create a custom subclass of another library class, AccompanyingInTravelState.

By default, if you don't override getAccompanyingTravelState() in the state object that initiates the travel, you'll get an instance of the base AccompanyingInTravelState class. This uses generic messages for the group travel. For the "before" message, it displays something like "The Blurg guard comes with you," and for the "after" message, it displays the ordinary specialDesc for the NPC.

To override these, create your own custom subclass of AccompanyingInTravelState, and override a couple of methods:

   class BlurgEscortTravelState: AccompanyingInTravelState
     sayDeparting(conn) { "The Blurg guard prods you onward. "; }
     specialDesc = "<q>Keep moving, human scum!</q> the 
       Blurg guard growls. "
   ;

The "before" message is sayDeparting(), and the "after" message is specialDesc.

Guided Tours

The library has a pre-defined actor state subclass that makes it easy to set up "guided tours," where an NPC escorts the player character through a series of locations. To set up a guided tour, you create one GuidedTourState object for each location on the tour. At the appropriate point, you call your NPC's setCurState() method to activate your special GuidedTourState object for the first location on the tour; the guided tour class will take it from there.

In defining each GuidedTourState, you need to define two properties: escortDest, which gives the travel connector that connects to the next location on the tour; and stateAfterEscort, which gives your special GuidedTourState object for the next stop on the tour.

The library class uses a set of messages that's suitable for a wide range of guided tours, but you might want to customize these. To use your own messages, you simply need to create your own subclass of GuidedTravelState - this is a subclass of AccompanyingInTravelState that the library uses by default during guided tours. You can use your own custom subclass of GuidedTravelState instead by setting the property escortStateClass in the GuidedTourState object to refer to your custom subclass.

Extending accompanying travel

Accompanying travel can be as customized or general as you want. For a sidekick character, you probably won't want to worry about customizing the travel too much, since the sidekick will likely visit almost every location in the game with the player character. For this kind of thing, the defaults work fine; the defaults are really set up for sidekicks more than anything else, in fact. Likewise, the base "guided tour" class is suitable without customization for a wide range of guided tour situations.

There are times when you might want to customize group travel so much that every move is special. This isn't hard; it's just a matter of creating a series of custom state objects, one for each travel action.