A TADS Tutorial: Lesson Eleven
Commanding Actors
Giving Commands
In Lesson 10, we talked about how much of the behavior of non-player characters can be programmed using the same techniques as was used to program other objects. Scripts can be used to make actors move around and fidget, and verb methods can be overridden to allow the player to do things to the actor.But there is one way that actors are totally different than other objects in the game. Unlike other objects, actors can be commanded to do something by the player. For example,
From a game design standpoint, IF authors frequently like to create a problem that the player can't solve by himself; however, another character in the game has the necessary skill to overcome the obstacle. To solve the puzzle, the player must command the other character to perform the action. Of course, characters in the game don't have to cooperate with the player.> RALPH, HIT ME Ralph eagerly obliges, punching you in the stomach and knocking the wind out of you.
Example
Okay, let's start programming a simple example to demonstrate how to make an actor receptive to player commands. As usual, we need to begin with a setting, let's say a jail cell:Here's the concept: the player shares a jail cell with a really strong guy. There is a boulder in the room that is too heavy for the player to lift, but he can direct the other guy to lift it. Here's the beginning of a program for the cellmate, Bobby. It starts off like most of the actors you've seen so far. As was discussed in the past lesson, since this character is referred to by a proper name, the adesc and thedesc properties must be modified accordingly since the defaults wouldn't make much sense.startroom : room sdesc = "Jail Cell" ldesc = "You are in a small, spartan jail cell that you share with your cellmate, Bobby. Long metal bars stand between you and freedom. There is a small red button on one wall. " ;Of course, we need to give Bobby a ldesc, so that the player can get a detailed description of Bobby when he types "LOOK AT BOBBY." Because the player is going to be asking Bobby to pick up a boulder, it would be nice if the description of Bobby also mentioned what Bobby is currently carrying. This requires some special treatment.bobby : Actor noun = 'bobby' 'cellmate' sdesc = "Bobby" adesc = "Bobby" thedesc = "Bobby" location = startroom actorDesc = "Bobby is standing in the corner of the cell, chewing his fingernails. "If Bobby isn't carrying anything, we need not mention it. But if he is carrying one or more items, we need to list those. Bobby's property contents stores a list of all the things that he is carrying. We can count how many items he is carrying with the function itemcnt (short for item count). If we find that he is in fact carrying something, we can list his contents with the function listcont.
Now comes the tricky part. We have to program which commands Bobby will listen to, and which ones he will ignore. This is done by overriding a method called actorAction. Whenever a command is issued to Bobby, his actorAction method is invoked with all the relevant parts of speech passed as arguments. If the method calls the function exit, then the player's command fails.ldesc = { "Bobby is a tall, burly man with large muscles. At first glance, he seems quite intimidating, but you've come to know him as a relatively friendly guy. "; if (itemcnt(bobby.contents) > 0) { "He's carrying <<listcont(bobby)>>. "; } }Notice in the above example the use of a special case label in the switch statement, default. The default case will pick up all verbs not explicitly handled by previous cases in the switch statement.actorAction(verb, dobj, prep, iobj) = { // Disallow any commands to this actor by invoking "exit" // Doing nothing, or invoking "return" will allow the command. // First, let's disallow Bobby from doing anything to the player. if (dobj == Me || iobj == Me) { "Bobby says, \"I don't want to do that. I might accidentally hurt you, and you're my friend.\" "; exit; } // Let's allow the player to command Bobby to take, drop, and push // things, and disallow all other commands. switch(verb) { case takeVerb: case dropVerb: case pushVerb: return; default: "Bobby shrugs his shoulders and ignores you. He's clearly not interested in doing that. "; exit; } } ;Now we need to create the heavy boulder, and make it so that the player can't pick it up, but Bobby can. As you'd expect, since we are creating specialized behavior for taking the boulder, this is done by overriding the doTake method. Remember how doTake always takes actor as an argument? This may have seemed superfluous before; in previous examples, the player was always the actor performing the action. But now this extra argument is going to come in handy. It makes it very simple for us to determine who is actually taking the boulder. If it is Bobby, the program can do one thing; if it is the player, the program can do something else.
Now that we've programmed Bobby and the boulder, let's watch the player interact with them:boulder : item location = startroom sdesc = "heavy boulder" noun = 'boulder' adjective = 'heavy' ldesc = "The boulder is not particularly large, but it looks massive nonetheless. It would clearly take someone with muscle to lift it. " doTake(actor) = { if (actor == bobby) { "Bobby lifts the boulder effortlessly. "; boulder.moveInto(bobby); } else { "You struggle to lift the boulder, but fail. "; } } ;Jail Cell You are in a small, spartan jail cell that you share with your cellmate, Bobby. Long metal bars stand between you and freedom. There is a small red button on one wall. You see a heavy boulder here. Bobby is standing in the corner of the cell, chewing his fingernails. >LOOK AT BOBBY Bobby is a tall, burly man with large muscles. At first glance, he seems quite intimidating, but you've come to know him as a relatively friendly guy. >LOOK AT BOULDER The boulder is not particularly large, but it looks massive nonetheless. It would clearly take someone with muscle to lift it. >BOBBY, PUSH ME Bobby says, "I don't want to do that. I might accidentally hurt you, and you're my friend." >TAKE BOULDER You struggle to lift the boulder, but fail. >BOBBY, GET THE BOULDER Bobby lifts the boulder effortlessly. >LOOK AT BOBBY Bobby is a tall, burly man with large muscles. At first glance, he seems quite intimidating, but you've come to know him as a relatively friendly guy. He's carrying a heavy boulder. >GET BOULDER Bobby is carrying the heavy boulder and won't let you have it. >BOBBY, DROP THE BOULDER Dropped.
Format Strings
In the above example, we looked at how to program an object to respond differently to different actors. But what if you want the object to always react the same way, regardless of who is performing the action? For example, in the jail cell there is a red button. Presumably, the same thing should happen regardless of whether the player or Bobby pushes the button.Unfortunately, you still need to take cases on which actor pushes the button, because even though the same action results, it needs to be expressed differently. For example,
Note that the messages are absolutely identical, but the pronouns are different, and some of the verbs are conjugated a little differently to fit the pronouns. This is a bit frustrating, because:redbutton : fixeditem location = startroom sdesc = "red button" noun = 'button' adjective = 'red' ldesc = "This button supposedly rings for a guard. " verDoPush(actor) = {} doPush(actor) = { if (actor == bobby) { "He pushes the button, and a bell rings in the distance, but no guards come. He isn't surprised. "; } else { "You push the button, and a bell rings in the distance, but no guards come. You aren't surprised. "; } } ;We have to type twice as much.It sure would be nice if there were some sort of shortcut to make it possible to write just one message that automatically modified the pronouns and verbs depending on who was performing the action.
If we later make a change, we may forget to change it in both places.
If we make a mistake in one of the messages but not the other, we may never notice the bug.Fortunately, there is just such a shortcut, called format strings. Here is how the same program looks, using format strings:
Notice all the % symbols bracketing each player-dependent pronoun, and portions of each player-dependent verb. For example, %You% will be replaced by You if the actor is the player, and by He if the actor is Bobby. The %es% at the end of push%es% will be replaced by nothing if the actor is the player (turning push%es% into push), and by es if the actor is Bobby (turning push%es% into pushes). Similarly, %are% turns into are, if the actor is the player, and is, if the actor is Bobby.redbutton : fixeditem location = startroom sdesc = "red button" noun = 'button' adjective = 'red' ldesc = "This button supposedly rings for a guard. " verDoPush(actor) = {} doPush(actor) = { "%You% push%es% the button, and a bell rings in the distance, but no guards come. %You% %are%n't surprised. "; } ;Aside from the format strings used in this example (%You%, %are%, and %es%), several other format strings are available to be used in your messages. For a full list, look at the beginning of adv.t.
Let's verify that these format strings work as we intended by testing the program:
You may wonder how TADS knew what pronouns to use for Bobby. The answer is that each actor can have a property which specifies which pronoun to use for each possible format string. For bobby we would set a property called isHim to true.>LOOK AT BUTTON This button supposedly rings for a guard. >PUSH BUTTON You push the button, and a bell rings in the distance, but no guards come. You aren't surprised. >BOBBY, PUSH BUTTON He pushes the button, and a bell rings in the distance, but no guards come. He isn't surprised.If the actor were female, set isHer to true. If the character were genderless (like a robot), you could either set both properties to true or not create either one.isHim = true
Wrap-Up
This will be the final lesson. TADS is a deep language, full of possibilities, and we have only scratched the surface. However, many subtleties of TADS are outside the scope of what I set out to accomplish with these tutorials.I hope that you have found this introduction to TADS useful, and that you are well on your way to creating your own entertaining worlds.
See the Sample Source Code
Go Back to Lesson Ten
See the Table of ContentsThe Text Adventure Development System
Version 2.2
This page is part of Mark Engelberg's TADS Tutorial
Copyright © 1999 Mark Engelberg