NPC Travel
(Adapted for Adv3Lite by Eric Eve)
Editor's Note: The adv3Lite way of handling this is sufficiently different from adv3 that little remains of MJR's original article here beyond the Introduction. The remainder is almost a complete re-write that ends up rather shorter.
Introduction
Many IF games contain people and creatures for the player to encounter. These are known as non-player characters, or NPCs. In real life, living creatures don't tend to stay forever rooted in a single location while the world carries on around them, so game authors frequently want to make their NPCs move around within the game world.
If you've ever written any IF before, you know that NPCs are inherently complicated to program, because you're trying to simulate creatures that are incredibly complex in real life. Fortunately, the basics of moving your characters around within a TADS 3 game world aren't too difficult to master. There are a few library methods that do most of the work for you; the only trick is to know which ones to call in which situations. This article covers the main methods of moving NPCs around and describes when to use each one.
moveInto
The simplest and most direct way to move actors around is with the moveInto() method.
moveInto() is a method of the actor you want to move, and you call it with the new location as the parameter:
bob.moveInto(iceCave);
You should use moveInto() when you want to move an actor "by fiat" - that is, you want complete programmatic control over what happens, and what side effects occur. This routine simply makes the actor disappear from the old location and magically reappear in the new location. The library doesn't attempt to simulate the travel when you use this routine: there's no attempt to open any doors along the way, for example, and the library doesn't generate any messages mentioning that the actor is departing or arriving.
actionMoveInto
The difference between actionMoveInto() and moveInto() is that actionMoveInto also calls the notifyRemove and notifyInsert methods on the locations being moved from and to, marks the object (here the npc) as being moved, and marks it as having been seen by the player character if the player character can see it in its new location. You may or may not want these additional side-effects. If you do, use actionMoveInto(), otherwise use moveInto().
travelVia
The travelVia() method performs more "simulated" travel than moveInto or actionMoveInto do. Unlike moveInto(), the travelVia() method does carry out all of the standard notifications involved in the travel: it calls beforeTravel() on everything nearby in the starting location; it calls it calls travelerLeaving() on the starting location, which displays a departure message if the player character can see the actor departing; it calls noteTraversal() on the TravelConnector; it calls travelerEntering() on the new location; and it calls afterTravel() on everything nearby in the new location.
travelVia(conn. announceArrival?) is a method of the actor who's traveling, where conn is the connector being traversed, and announceArrival? (which defaults to true) is a flag that determines whether or not to display a message about the actor's arrival in the new location if the actor can be seen there by the player character.
Here's an example:local conn = bob.location.east; bob.travelVia(conn);
Note that travelVia() checks any travel barries that are in force on the connector, and do typically does carry out any pre-conditions of the travel. For example, if a closed door is in the way, travelVia() will make the character attempt to open it, and travel will be interrupted if it's locked. This is a plus and a minus, depending on what you're trying to accomplish; it's less useful when you want to move your characters by fiat, without regard to the simulation implications, help avoid creating jarring, unrealistic effects for the player.
Travel with Pathfinding
The most full-featured travel method is the one that uses adv3Lite library's contains a pathfinding module. This enables you to calculate the shortest route from one location to another, such as npc's current location to where you'd like them to move to. You could use this to script a quasi-automous npc who moves around the map in a realistic manner.
To use route finding to implement NPC travel, you'd need to do first things: first use the routefinder to calculate the route, like this:
routeFinder.findPath(bob.getOutermostRoom, destinationRoom);
Then use this information to move the npc along the route, using code like that auggested in the Library Manual's section on the routeFinder (see link above) or by using using travelVia(), e.g.:
local dir; local idx = routeFinder.cachedPath.indexWhich({x: x[2] == bob.getOutermostRoom}); if(idx != nil && idx < routeFinder.cachedPath.length) dir = routeFinder.cachedPath[idx + 1][1]; if(dir) { local conn = bob.getOutermostRoom.getConnector(dir); if(conn) bob.travelVia(conn); } ;
In turn, you might put this code (or a suitable adapted vesion of it) in the invokeItem() method of an AgendaItem that remains active until Bob reaches his destination.
Choosing a Method
The four main NPC travel methods - routefinder, travelVia(), and moveInto() and actionMoveInto() - all have their uses. Which one you use depends on the situation, and you'll probably use more than one in any given game.
You should use moveInto() when you want to move an actor "by fiat," without triggering any simulation side effects. This is the one to use when you want complete control over what happens, and are willing to take complete responsibility for things like generating arrival and departure messages. This or actionMoveInto() are probably the best options when, for example, the player character's arrival in a location triggers the npc's arrival in the same location from a different direction, or you want to move the npc off-stage from the player character's location.
Use actionMoveInto() when you want to trigger a minimal subset of the notifications normally associated with travel, but you still want control over the NPC's actions during the travel. In particular, this method doesn't trigger any preconditions of the travel, so it's up to you to make sure that any doors that need to be open are open, for example (or else indicate to the player that the npc has opened and close any doors that would need to have been opened and closed).
Use travelVia() when you know (or can easily figure out) which TravelConnector you want the actor to use, and you want the library make the npc use it. This method is best when you want the NPC's travel to be fully simulated, triggering all of the same preconditions and side effects that the player would trigger with a normal "go east" command or the like. This might be the best way to go, if, for example, there might be some barrier, such as a locked door, that would prevent the npc reaching its intended destination.
Use routeFinder when you need your npc to appear to be a quasi-autonomous actor going about their own business regardless of what the player character is doing, so that the player character may or may not encounter the npc travelling along their route (and the npc may or may not react to the player character if their paths happen to cross).