TADS3: NPC best practice

Hi guys,

I was wondering whether you can help me with some organizing problems in terms of NPCs. Just assume we have a NPC called JoeDoe who cannot follow the player but stands around in various locations. This is damn easy. I just add him to all rooms I want him to be, right? Unfortunately he behaves a bit strangely and looks a bit different in some rooms.

Is there a way to globally define a “normal” JoeDoe and use this definition to create a more detailed Person object within a single room? And if so, how do I manage to get JoeDoe to remember stuff. If I just inherit from a common JoeDoe class and override some methods all added conversation topics will be lost when the player leaves the room, right? Such an instance behaves like Joe but it still is a different instance and does not share it’s variables with the common JoeDoe instance.

So, to make it more understandable, let’s just look at this “workflow”:

  1. Player is in Room1 and Joe has a green hat
  2. ask joe about hat results in something like “Hey Joe, your hat is nice!” which joe will remember
  3. Player moves to room2 and there is a Joe in a clown costume, but he still remembers the conversation and greets like “I’ve got no hat this time, see?”

What’s the best practice way to do something like this?

Untested, but I would try customizing actorTravel(traveler, connector) on the player (the me object, unless you’ve changed it). If there’s no door between two rooms, the connector argument should refer to the destination room.

The point is, if you do it this way you only need one Joe object. When the player moves to a new room, the code can move Joe (and change his costume), but Joe can still know everything he knew before (however you’ve set that up), because there’s only one Joe object.

But this would be an ugly workaround. What I’m looking for is basically this:

[code]
class BasicJoe:
some_std_variables
some_std_behavior

class ClownJoe(BasicJoe) @room1:
override_std_kiss_behavior

class CowboyJoe(BasicJoe) @room2:
override_std_yell_behavior
…[/code]

My first question would be if something like this is possible. And my second question: if so, how can I add a shared variable to all those instances?

Of course I could write one big Joe char, but it would be damn long and hard to maintain as I had to include the whole functionality of Joe (in all rooms he appears) in it.

As far as I’m aware, ActorState objects and InConversation objects are located “inside” a single instance of the Actor object. If you want a TellTopic or an AskTellTopic to sync across multiple instances of the Actor, you’re inevitably going to have to do some housekeeping. Possibly a bunch of housekeeping, depending on what details of the story you want each instance of Joe to keep track of.

For instance, let’s say you want an AskTopic that’s also a StopEventList (a very common situation in NPC conversation building). The StopEventList has a pointer, internally, to the next item it’s going to output, and that pointer is incremented by the T3 library as needed. If you have several of these (basically identical) AskTopic/StopEventList objects embedded in different Actors, you’re going to have to override the library to manually increment the pointer in any other identical AskTopic/StopEventList (while carefully not incrementing it in THIS one). It’s going to get messy, no matter how you do it.

Possibly there’s a clever way to handle this challenge, but I don’t know what it would be.

So the commonly used way to do it would be to have one big NPC class?
Seems the least painful way to me now ^^

In this case, how do I put an NPC into a bunch of rooms?

I should have been a little more explanatory. actorTravel(traveler, connector) at least according to the doc I checked, runs after the travel action is initiated but before the actor object actually moves to the new location. So in that method you move the NPC to connector (which is the destination of the travel) using moveIntoForTravel.

However, this will fail if there’s a door (stairway, etc.) in the way, because in that case the door is (I think) the connector, not the destination room. So you need to test whether the connector is actually of the Room class. If not … hmm. Untested, but if connector is not a Room, it’s probably something that inherits from Passage. Test that next. Assuming connector is a Passage, I think you’ll need to move Joe to connector.otherside.location.

Try that, anyway. I’m wingin’ it here. Haven’t written any T3 code for a couple of years.

Looks like I got it right. Here’s a test game that works as I indicated.

[code]startRoom: Room ‘Start Room’
"This is the starting room. You can go north, or through the door to the east. "
north = otherRoom
east = bigDoor
;

  • me: Actor ‘me self’ ‘yourself’
    actorTravel(t, c) {
    if (c.ofKind(Room))
    joe.moveIntoForTravel(c);
    else if (c.ofKind(Passage))
    joe.moveIntoForTravel(c.otherSide.location);
    }
    ;

  • joe: Actor ‘joe’ ‘Joe’
    "Good old Joe. "
    isProperName = true
    ;

  • bigDoor: Door ‘door’ ‘door’
    "It’s a big door. "
    otherSide = eastBigDoor
    initiallyOpen = true
    ;

otherRoom: Room ‘Another Room’
"This is the other room. You can go south. "
south = startRoom
;

eastRoom: Room ‘East Room’
"This is the east room. You can go west through the big door. "
west = eastBigDoor
;

  • eastBigDoor: Door ‘door’ ‘door’
    otherSide = bigDoor
    masterObject = bigDoor
    ;[/code]
    This produces the transcript:

Thanks for the example! :slight_smile:

You helped me a lot with this idea! Guess I’ll do it this way.