A TADS Tutorial: Lesson Ten
Actors
The TADS Manual
The TADS manual is usually available on-line in HTML format. Just talk to your favorite Web search engine to find it. By now, you probably have acquired enough expertise at using TADS that the manual will mostly make sense to you.One thing to keep in mind when reading the manual is that TADS supports two syntax variations (inspired by the programming languages Pascal and C). You've been compiling your programs with the C option, which tells the compiler that your programs use the C-like syntax I've been teaching you. However, the examples in the manual use the Pascal-style syntax. The differences are very small, primarily affecting the way that you set a property to a new value, and test whether two values are equal, but they are important to remember:
Pascal C Assignment := = Test for equality = == Test for inequality <> !=
Characters
You've already learned how to populate your world with rooms and objects. But to make your game truly come alive, you'll probably want to add other characters ("actors") for the player to interact with.The prospect of programming a seemingly-intelligent character may seem daunting, but for the most part, you'll find that it just requires a combination of programming techniques that you've already mastered. Just like an object, an actor can be programmed to respond in certain ways to player actions, for example:
The scripting techniques from Lesson 9 are also powerful tools for programming actors. For example, you can make an actor "fidget" and do something random every turn, or you can make the actor wander from room to room (using the actor's travelTo method).> LOOK AT RALPH Your friend Ralph has broad shoulders and big muscles. > PUSH RALPH As you lunge forward, Ralph easily sidesteps your outstretched arms and slugs you in the stomach, temporarily knocking the wind out of you. > ASK RALPH ABOUT KARATE "Oh, didn't I tell you? I'm a black belt."
Examples
Chapter 10 of the manual has an excellent section called "Non-Player Characters" toward its end that gives a lot of great examples. The sample programs presented here are strongly inspired by the examples in the manual, but I have simplified them considerably. It is recommended that you consult the manual for more advanced examples.First, let's create a simple setting for the examples:
A "fidget" example:startroom : room sdesc = "Lobby" ldesc = "You're in a large lobby, with an office to the east. A large receptionist's desk sits next to the office door. " east = office ; office : room sdesc = "Office" ldesc = "You are in a fairly ordinary office. The exit is to the west. There is a small closet to the north. " west = startroom north = closet ; closet : room sdesc = "Closet" ldesc = "You are in a closet at the north end of the office. " south = office ;Notice that the receptionist derives her behavior from the class Actor. Like all objects, the receptionist has a noun, location, sdesc, and ldesc property defined. All actors also have a property called actorDesc, which is printed whenever the room is described. This is not to be confused with ldesc, which is printed when the player looks directly at the actor. Typically, actorDesc describes the gist of what the actor is doing, while ldesc describes the details of the actor's appearance.receptionist : Actor noun = 'receptionist' sdesc = "receptionist" ldesc = "The receptionist reminds you of your third-grade teacher." location = startroom actorDesc = "A receptionist is sitting at the desk next to the door, watching you suspiciously. " script = { // Print a random fidget message when you're in the same room as // the receptionist if (self.location == Me.location) { switch(rand(5)) { case 1: "\n\tThe receptionist sharpens some pencils. "; break; case 2: "\n\tThe receptionist goes through some personal mail, holding each letter up to the light and attempting to read the contents. "; break; case 3: "\n\tThe receptionist looks through the personnel files. "; break; case 4: "\n\tThe receptionist answers the phone and immediately puts the caller on hold, cackling to herself fiendishly. "; break; case 5: "\n\tThe receptionist shuffles some papers. "; break; } } } ;The receptionist example also defines a simple script that works just like the examples in Lesson 9. When the player is in the same room as the receptionist, a random message is printed that creates an illusion that the receptionist is fiddling with things on her desk.
Characters That Follow the Player
The receptionist never moves. But what if you want to create a character that follows the player around? This actually requires a fairly simple modification of the character's script. In addition to containing several "fidgets," the script also tests to see if the player is in a different room, if so, the character is moved into the player's current location using the travelTo method. Because the character is referred to by a proper name, the default adesc and thedesc won't work, so those properties need to be changed accordingly.As you may recall, the game "The Plant" had a character (Mr. Teeterwaller) who followed you around much like this. Of course, Mr. Teeterwaller was a bit more complex. In a real game, you typically extend the script to make the character do special things in certain locations.lloyd : Actor noun = 'lloyd' 'salesman' adjective = 'insurance' sdesc = "Lloyd" adesc = "Lloyd" thedesc = "Lloyd" ldesc = "Lloyd has jet-black hair and thick glasses. " actorDesc = "An insurance salesman, with the name \"Lloyd\" clearly emblazoned on his nametag, is holding a clipboard and eyeing you intently. " location = startroom script = { if (self.location == Me.location) { switch(rand(3)) { case 1: "\n\tLloyd hums one of his favorite insurance songs. "; break; case 2: "\n\tLloyd scribbles something on his clipboard. "; break; case 3: "\n\tLloyd idly fingers his nametag. "; break; } } else { self.travelTo(Me.location); } } ;
Characters That Move Along a Fixed Path
What if you want to program a character that moves around of its own accord? Again, it just requires a modification of the script. As an example, let's consider the case of a cleaning robot that moves along a fixed path, from the closet to the office to the lobby, then back to the office, and so on. The simplest way to program this is using lists (see Lesson 9 for an introduction to lists). Of course, you could program this without relying on lists, but the resulting program would be a bit longer.tracklist is a property we've created to store a list of rooms that the robot traverses. Similarly, trackpos is a property we've created to help us keep track of where along the path the robot is. When we get to the end of the list, we start over.vacRobot : Actor sdesc = "cleaning robot" noun = 'robot' adjective = 'cleaning' ldesc = "The robot has a bright metallic sheen and numerous cleaning attachments. " actorDesc = "A cleaning robot darts from corner to corner in search of dust particles. " tracklist = [closet office startroom office] trackpos = 0 script = { // Move the robot along his path self.trackpos = self.trackpos + 1; // Loop around if we've reached the end of the tracklist if (self.trackpos > length(self.tracklist)) { self.trackpos = 1; } self.travelTo(tracklist[self.trackpos]); } sayLeaving = "\n\tThe cleaning robot rolls out of the room. " sayArriving = "\n\tThe cleaning robot rolls into the room and starts noisily moving around the room vacuuming and dusting. "; ;Actors also have default messages that are printed when they enter and leave the area where the player is. These messages can be modified by overriding the actor's sayLeaving and sayArriving properties.
Following a Character
Imagine the player is hanging out somewhere when the cleaning robot rolls into the room, and then on the next turn, rolls out of the room. A reasonable thing to type would be "FOLLOW ROBOT," but all the player would get would be the response "I don't see any robot here." Since the robot is gone, TADS can't dispatch the verb "follow" to it!Fortunately, there is a simple workaround to make the "FOLLOW" command work. We create a special, invisible object called a follower for the cleaning robot. When the robot leaves a room, this invisible follower stays behind in the room. The follower responds to the same nouns and adjectives that the robot does, so when the player types "FOLLOW ROBOT," the follow verb is dispatched to the follower, which knows where the robot went, and the player is sent in that direction.
This may sound confusing, but it's surprisingly easy to program. Here's how you create an invisible follower object for the cleaning robot:
The myactor property is key. It tells the follower object which actor it is following. Similarly, you must go back to the cleaning robot object, and add a myfollower property that states which object is following it:vacRobotFollower : follower noun = 'robot' adjective = 'cleaning' myactor = vacRobot ;myfollower = vacRobotFollower
Asking Characters About Topics
Commands of the form "ASK CHARACTER ABOUT TOPIC" occur so frequently, that TADS includes a special method for actors called askWord that allows you to easily handle all your character's responses. This method takes two inputs: the first is the word that the player is asking about, the second is a list of other words that the player typed. You will rarely use the list, but it is available to you just in case you need contextual clues to decipher the question.If the actor knows something about the word, you must print his response, and return true. Otherwise, return nil. The switch statement is the easiest way to program this, although you could also use a long chain of if else clauses. In this next example, we will be taking advantage of switch's capabilities of combining multiple cases. Notice that since we return true at the end of each case, we don't need to issue the break command as we usually do with switch statements.
As you see from this example, we can also override the disavow property to control what the character says when you ask about something not handled by the askWord method. The default is for the character to say, "I don't know anything about that."fortuneteller : Actor sdesc = "fortuneteller" noun = 'fortuneteller' 'teller' adjective = 'fortune' ldesc = "The fortuneteller is an old man with a gray beard. He is sitting cross-legged in the corner. " actorDesc = "A fortuneteller is here, waiting to tell you the future about anyone you ask about. " location = closet askWord(word, lst) = { switch (word) { case 'lloyd': case 'salesman': "\"Lloyd is a most annoying salesman. He will be killed by an irate customer before the year is out.\" "; return true; case 'robot': "\"The cleaning robot will develop a short circuit tomorrow afternoon.\" "; return true; case 'receptionist': "\"The receptionist will have heartburn later tonight.\" "; return true; case 'teller': case 'fortuneteller': case 'himself': "\"I was trained at an accredited forecasting college.\" "; return true; case 'elvis': "\"Elvis is dead.\" "; return true; } return nil; } disavow = "\"The future of that is not clear to me.\" " ;Most of the words that we can ask the fortuneteller about are nouns associated with objects in the game (e.g., 'lloyd', 'salesman', 'robot', 'receptionist', 'teller', and 'fortuneteller'.) But two of the words are not associated with any objects in the game ('himself' and 'Elvis'). This poses a problem -- those two words won't be recognized by the parser!
To make the parser recognize these words, we must add an object, any object, that uses these words in its noun property. So let's create one:
Now those words will be recognized and our askWord method will work as we expect it to.conversationTopics : thing noun = 'elvis' 'himself' ;
Starting the Scripts
As was discussed in Lesson 9, you have to explicitly start the scripts or they will not work. For most of the examples in Lesson 9, we started the script when the player entered the relevant room, and ended the script when the player left the room.Another option for actors is to start their respective scripts at the beginning of the game, and let them continue to run, even when the player isn't around. This is particularly important for characters that wander around. So how do you do something at the beginning of the game?
The game is initialized in a function init contained in std.t, one of the files you include at the beginning of all your lessons. Unlike adv.t, which needs to be modified with caution, std.t contains a lot of code that you're expected to modify. In fact, most games make their own copy of std.t (typically giving it a different name to avoid confusion) and modify that.
So for this lesson, I made a copy of std.t and named it stdex10.t. Then I added the following lines to the init function:
Then, I changed the first two lines of exercise10.t to:// put introductory text here "Welcome to Exercise 10.\b "; // Start all the actor scripts notify(receptionist, &script, 0); notify(lloyd, &script, 0); notify(vacRobot, &script, 0);Other commonly modified functions/objects from std.t are:#include <adv.t> #include "stdex10.t"die()
scoreRank()
pardon()
version.sdesc
Things to Try
Just as with objects, there are many ways you can override methods for an actor to create specialized behavior. There are simply too many possibilities to give examples for all of them. But here are some ideas of common ways that actors tend to be customized in games. Try some or all of these techniques in your own project.> TELL RALPH ABOUT HAMBURGER
(to program something like this, override the verDoTellAbout and doTellAbout methods for the actor).
> GIVE HAMBURGER TO RALPH
(to program something like this, override the ioGiveTo method for Ralph).
> TAKE FRENCH FRIES FROM RALPH
(to program something like this, override the verGrab method for Ralph).
Coming Soon to a Lesson Near You
In TADS games, you can also issue commands to other characters in an attempt to persuade them to do something. Next lesson, we'll look at how this is programmed.See stdex10.t
See the Sample Source Code
Go on to Lesson Eleven
Go Back to Lesson Nine
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