# Creating Dynamic Characters

## (Adapted for adv3Lite by Eric Eve)

Editor's Note: the adaptation required here turned out to be fairly minimal apart from the discussion of following and accomanying actors.

## Introduction

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:

- The completely static character. This is by far the most common solution to the problem: you simply build a character that doesn't do anything on its own. If the man in the tuxedo doesn't do anything but sit at the table, then you only have to write a single description, a single response to ASK MAN ABOUT BACCARAT, and so on.
- The bland, unresponsive character. In this approach, you either generalize or disable most of the things that you'd otherwise have to customize for each different behavior. You make the description so general that one size fits all. (Coming up with a description that works for both "sitting at table" and "hair on fire" would be a challenge, but the range of behaviors isn't usually so extreme.) You don't answer ASK ABOUT questions at all, but use some sufficiently generic non-response, such as "there's no reply." Likewise for the other forms of interaction.
- The "cut scene" character. In this approach, you create a character that exists only as text. This kind of character pops in between commands, says or does something, and then leaves the scene before the player has a chance to type a single command. You don't even need an "Actor" object for this technique - just text.

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 "setState(state)" method, passing the new state object as the parameter.

Following the example set by the adv3 library, the adv3Lite library implements a version of 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 ActorState class itself is 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.

- stateDesc - this is a message that's added to the actor's basic description (the "desc" property in the Actor object). Most actors will have a permanent description that never changes - a basic description of their physical appearance - along with some extra information that describes what they're doing right now. The stateDesc lets you add this extra state-dependent part.
- specialDesc - displays the actor's in-room description. This is the description displayed in the room description (for example, when entering the room, or in response to a LOOK command). By default, we'll invoke the actor's actorSpecialDesc method.
- beforeAction(), afterAction() - these give the state object a chance to react to an action. The Actor's beforeAction() and afterAction() methods (respectively) call these on the current state object, so these simply let you put reaction code with the state rather than with the actor.
- beforeTravel(traveler, connector), afterTravel(traveler, connector) - like beforeAction() and afterAction(), the Actor forwards these calls to the ActorState, to give the state object a chance to respond.
- activateState(actor, oldState) - this is called when the new state is about to become active.
- deactivateState(actor, newState) - this is called when the state is active, and another state is about to be activated for the actor.

Plus some others you can read about in the chapter on Actor States in the Adv3Lite Library Manual

### Hermit states

An ActorState can be made a Hermit state by defining its noReponse property. 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: ActorState
        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.

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.

### Agendas

One way that you can make an actor do things on its own is by programming background activities in the takeTurn() on 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, you can mix in an ActorState with an EventList class and define the random background activity messages on its eventList property. 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.

For the situation where we want the NPC to lead or guide the the Player Character, we can use a FollowAgendaItem.

If we want the NPC to follow the Actor we can call startFollowing() on the Actor object. To stop the Actor following the player character we call stopFollowing(). To describe the Actor following the player character from one location to another we can define sayFollowing(oldLoc) on the Actor's current ActorState or actorSayFollowing(oldLoc) on the Actor, where oldLoc is the location the Actor has just carried the player character from. The location the Actor has just followed the player character to can be obtained from gLocation or (if different) gOutermostRoom.

TADS 3 Technical Manual
Table of Contents | TADS 3 In Depth > Creating Dynamic Characters
Prev: Tips: A Context-Sensitive Help System Next: Choosing a Conversation System