NPC Guard

I’m looking to code a guard that moves back and forth between two rooms every 5 turns or so. If the player enters the room when the guard is there or the player is in the room when the guard enters, I would like the player to be caught ending the game. So far, a lot of the information I’ve read has given me bits and pieces of what I need to know, but I haven’t been able to piece together how to make this work. Does anyone know of any examples of coding I can look it or any reference material regarding this kind of specific handling?

**edit, to whom it may concern, I’m somewhat annoyed that I had to create a new email address to sign up here because my standard email address could not be accepted.

You’re working with TADS 3/3.1? (whoa, windows flashback…)

Consider making two AgendaItems for the guard – one AgendaItem to walk back and forth, and another that triggers when self.location == gPlayerChar.location, and ends the game.

Conrad.

AgendaItems are a little tricky. According to the docs, they don’t trigger when the Actor is in conversation, so you could potentially talk to the guard for several turns without ending the game. In my recent experiments, it also seems that an AgendaItem fails to fire in a turn when the NPC is following the PC. In your situation this may not matter, but it’s a reason why careful testing of AgendaItems is necessary.

Oh, sorry I didn’t specify, yes I am working with Tads 3. Thanks for the suggestion with agenda items, it seems I am at least moving in the right direction. However, my logic is faulty somewhere and I’m not sure where. He will move from the starting room to second room, but not back. Also, my syntax is incorrect for the guard description, perhaps you can assist me with that as well?

Also, thank you for the heads up on agenda items. Fortunately, this guard is just a trap and really doesn’t have to do anything else. Except maybe look handsome, but I can let the prose take care of that. :slight_smile:

[code]+Guard: Person ‘patrolling guard’ ‘Patrolling Guard’
"There is a patrolling guard <<Guard.roomLocation>>. "
isHim = true
;

++ GuardPatrolAgenda: AgendaItem
initiallyActive = true
agendaOrder = 10
routeList = [NorthGuardRoom, GuardRoom]
invokeItem()
{
local idx = routeList.indexOf(Guard.getOutermostRoom);
if(idx && idx < routeList.length())
{
local dest = routeList[++idx];
Guard.scriptedTravelTo(dest);
if(idx >= routeList.length())
idx = 0;
}
}
;[/code]

I’ve never used roomLocation, but I don’t think you can use it that way. At the very least, you haven’t accessed anything that can print. I would try (untested) something like this:

"There is a patrolling guard in <<getOutermostRoom.destName>>. "

… or perhaps:

desc { "There is a patrolling guard in "; say (getOutermostRoom.destName); ". "; }
The other problem you’re hitting is probably (I haven’t looked it up) because TADS lists are zero-indexed. routeList.length will be 2, but idx will never be higher than 1. Or rather, it will go to 2, but there’s no list item at index 2. At that point, scriptedTravel will try to find a route from the guard’s current location to nil – which of course it can’t do. So the guard won’t go anywhere.

Your solution may well be better; I’d do it this way because it’s easier for me to see what’s happening, and so that I can more easily splice new locations into the guard’s path…

Set up two AgendaItems. One travels from room A to room B, the other travels from room B to room A.

The basic logic would look like this:

If I am in room A, travel to room B.

Chill out in room B for a few turns, presumably with different text descriptions: The guard walks past the soda machine, the guard walks past the TV, the guard circles past the exit, the guard walks past the window, and the guard walks toward the hall.

Then, this AgendaItem switches itself off.

–That should allow the other AgendaItem to kick in.

If need be, you can create a control variable in the guardNPC object – guardNPC.whatImDoingNow, or such – and set it to one thing when one AgendaItem should be firing, and to the other when the other should be. That is, each AgendaItem, when it’s done, sets the flag to awaken the other. You shouldn’t really need to do it this way, because the AgendaItems should know when they’re up, but as Jim says, AgendaItems are tricky.

Conrad.

–If this approach makes more trouble than it’s worth, someone pleas speak up. This is how I generally use AgendaItems, though.

Lists start at index 1, not zero, and the issue is the line that sets the index to zero - which is not a valid offset.

OK, that’s not quite right either. Setting idx to zero doesn’t matter since it’s a local; the value is lost after the method returns.

    invokeItem()
    {
        local idx = routeList.indexOf(Guard.getOutermostRoom);
        local dest;
        if (idx && idx < routeList.length())
            dest = routeList[++idx];
        else
            dest = routeList[1];
        Guard.scriptedTravelTo(dest);
    }

This should do what you’re after; when the guard is in the last room on the route list, he will pick the first room in the list again and travel back there.

EDIT: Except scriptedTravelTo only works with adjacent rooms, and there’s no guarantee that the first room will be adjacent to the last one. So in the general case you’ll want to reverse the list when you reach the last room.

Thank you Conrad for your logic flow. Ultimately, coding that logic is well beyond my ability at this moment. Would you be willing to give this a look? I now have the guard pacing back and forth (though I suppose I will have to work on getting him to stop for a while) but he does not end the game when the player and the guard are in the same room. Any ideas?

[code]+Guard: Person ‘patrolling guard’ ‘Patrolling Guard’
"There is a patrolling guard in <<getOutermostRoom.destName>>. "
isHim = true
;

++ GuardCaptureAgenda: AgendaItem
InitiallyActive = true
agendaOrder = 10
invokeItem()
{ if(Guard.location == gPlayerChar.location)
{
“You have been captured!”;
finishGameMsg(ftDeath, [finishOptionUndo]);
}
}
;

++ GuardPatrolAgenda: AgendaItem
initiallyActive = true
agendaOrder = 10
routeList = [NorthGuardRoom, GuardRoom]
invokeItem()
{
local idx = routeList.indexOf(Guard.getOutermostRoom);
local dest;
if (idx && idx < routeList.length())
dest = routeList[++idx];
else
dest = routeList[1];
Guard.scriptedTravelTo(dest);
}
;[/code]

**edit Thank you for the help, bc. I see now why the previous code was not working properly and this makes a lot of sense. Fortunately he only patrols two rooms right now so I don’t have to worry about reversing the travel list but I do understand what you mean.

Only one AgendaItem per Actor will run per turn. Could that be the problem?

I thought that might be the problem. When I removed the patrolling agenda from the code entirely and compiled, he still did not capture me when I moved into HIS room, the north room where he starts. I then came up with the idea to add an if statement to invokeItem(), if the player location and the guard location are the same, you are captured. This almost worked, only the end game function was called when the guard entered the room on the same turn, OR if you moved into the room because the guard had not yet moved north.

*edit my mistake. I must have put the if statement before scriptedTravelTo function, because after playing with it a bit, he seems to be patrolling and capturing in a basic functional sense. Thank you everyone for your help.

Yes, at most one AgendaItem executes per character per turn.

When two AgendaItems qualify to execute, which will trump the other? – This is determined by agendaOrder. The AgendaItem with the lower agendaOrder wins.

So, set your “arrest the PC”'s agendaOrder to a lower number than your “patrol” agendaOrder, and as soon as the arresting criteria are fulfilled, the guard will stop patrolling to arrest the PC.

(Right now you’ve got them both set to 10, which doesn’t do anything, because they only have meaning in relation to other AgendaItems in the same NPC.)

–You might also comment out the patrol AgendaItem, start the guard in the patrolled room, and see if he arrests the PC. Once you have the arrest behavior working correctly, then add back in the patrol behavior.

Conrad.

Apparently my guard is still screwy just when I thought it was all making sense. I was experimenting and changing the code to try and figure out what different things I could accomplish and discovered that something is going on that I can’t put my finger on.

I added another room for him to patrol, and he walks to the second, then the third… and then paces back and forth between the second and third never returning to the first room. I’ve tried fixing this in several ways. I tried changing dest = routelist[1] to [0] (in case they were numbered with the first entry as 0), I tried adding NorthGuardRoom as a “5th” room to routeList (thinking maybe it skipped the first room in the list via some oddity in the parsing), and then tried both of these changes together. Including the original, all four of these scenarios results in the same behavior.

++ GuardPatrolAgenda: AgendaItem initiallyActive = true agendaOrder = 10 routeList = [NorthGuardRoom, GuardRoom, EntranceToCourtyard, GuardRoom] invokeItem() { local idx = routeList.indexOf(Guard.getOutermostRoom); local dest; if (idx && idx < routeList.length()) dest = routeList[++idx]; else dest = routeList[1]; Guard.scriptedTravelTo(dest); if(Guard.location == gPlayerChar.location) { "You have been captured!"; } } ;

As a side note, why is this so complicated? I feel like I can’t be the first person to come up with a patrolling guard, yet I can’t find any examples. Hmmm.

You’ve listed GuardRoom twice, and routeList.indexOf returns the first matching item in the list. EntranceToCourtyard will send the guard to GuardRoom; his agenda thinks he’s on the second step instead of the last, and sends him to EntranceToCourtyard.

You should have pathfind.t in your lib/extensions folder. It’s probably worth scrapping your current approach and setting up two AgendaItems: one which takes the guard from A to B, finding a path along the way; and a second which takes the guard from B to A.

I will see if I can put something together real quick.

OK, here goes. This requires you to add extensions/pathfind.t to your project.

lab: Room 'lab'
	"Welcome to the TADS Laboratory! "
	east = hallway
;

hallway: Room 'hallway'
	"Just another hallway. "
	west = lab
	east = entrance
;

entrance: Room 'entrance'
	"Just another entrance. "
	west = hallway
;

DistanceConnector [lab, hallway, entrance];

guard: Person 'guard' 'guard' @lab
	"Just another guard. "
	isHim = true

	sightSize = large

	afterTravel(traveler, connector) {
		/*
		if (location == gPlayerChar.location) {
			"You have been captured! ";
			finishGameMsg(ftDeath, [finishOptionUndo]);
		}
		*/
	}
	enroute = nil
;

+ PatrolAgenda
	origin = lab
	dest = entrance
;

+ PatrolAgenda
	origin = entrance
	dest = lab
;

class PatrolAgenda: AgendaItem, roomPathFinder
	initiallyActive = true

	invokeItem() {
		if (!getActor.enroute) {
			path = findPath(getActor, origin, dest);
			getActor.enroute = self;
			step = 1;
		}
		if (path) {
			getActor.scriptedTravelTo(path[++step]);
			if (getActor.getOutermostRoom == dest)
				getActor.enroute = nil;
		}
	}

	isReady() {
		return (getActor.enroute == self ||
			(getActor.getOutermostRoom == origin &&
			 getActor.getOutermostRoom != dest)
		);
	}

	origin = nil
	dest = nil
	path = nil
	step = nil
;

You can add waypoints by adding more PatrolAgenda items to the guard. The DistanceConnector and sightSize lines are just for debugging; they let us keep track of where the guard is without having to follow him around.

Wow, that is impressive. I’m going use that as place to start for my NPC guard, thank you so much! I’ll be sure to give you proper credit for that. I’m going to play with this for a few days, maybe see if I can get him to wait a turn or two before letting him move to the next room. This would make for a good addition to the technical manual.

**edit: It’s funny that you mention distance connectors. I just found those the other day and added that to my code so I could do the same thing; watch him move from room to room without having to follow him!