/* == Tads 2 Exits Lister, version 1.1 == Copyright (c) 2002, Steve Breslin ==>> License: _______________________________________________________________ / \ | You can use this program however you want, but if you | | decide to distribute anything that #include's or uses | | this program in any way, you have to send a copy of | | any improvements or modify-cations you made to this | | program to its author, Steve Breslin. | | | | That way, you will help keep the program up to date for | | everybody else, and everybody else will help keep it | | up to date for you. | | | | You may redistribute this verbatim or in modified form | | only if you keep the copyright and license intact. | | | | Also, feel encouraged to release your source code along | | with your work, though this isn't a requirement. | | | | There is no implied warranty of any kind. | | | | The author can be reached at . | \______________________________________________________________*/ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INTRODUCTION, CAVEATS, & TIPS: The module is easy to use. Just include it in your game, and it will work right away. You don't need to add any extra code to your rooms and exits, except when you have a complicated exit, such as one which executes some code rather than points to another room, or perhaps when you have a hidden door. More on this below. This module lists the exits in three formats: 1) in a quick list below a verbose room description, 2) when a player goes in a bad direction, the good directions can be listed, and 3) when a player uses the 'scan' verb, the exits are listed along with the destination's sdesc. There are several options which modify the information: for example, a no-peeking option will not list the destination's sdesc unless the destination has been visited already. The new version makes it very easy to modify these lists, to misinform the player, to suppress information, to embellish upon the normal information, or to otherwise override normal handling in special cases. Any kind of exit can be told how to work with the exitslister. Be warned that this module, if used carelessly, could provide more information to the player than you might want. It might mess up some puzzles that way. But it's easy to make it work nicely. In general, whenever you have code associated with a direction (as opposed to the normal case, where the direction property points to a room or door), you should consider adding a condition which checks if the exitsLister is active and scanning the exits, or if on the other hand the player is actually moving in that direction. When you have a special exit, like a hidden door for example, you should consider making a special condition in the room's exit, to tell this module how to list it when this module displays the exit. For example, the lister could list the exit on the condition that the exit has been discovered. There's a special property determining whether the module is doing the scanning, as opposed to the player actually moving in a direction: exitsLister.scanning. exitsLister.scanning is true whenever the module is scanning for exits. This can happen when the verb 'exits' has been used by the player to scan the adjacent rooms, or when the room is listing the exits after the ldesc, or when the exits are being listed as a result of the player moving in a bad direction. You'll want to make sure any code associated with an exit doesn't change game state unless "scanning" is nil. This is because the exits listing methods are silently calling the code to find out if the direction is pointing to a room or door. In other words, the player isn't moving in this direction. This is similar to the rule that we don't change game state within verify methods; they are silently called also. So if you have a deadly cliff, and the player types 'exits', the cliff-exit might think the player leapt off the cliff, when he merely peeked over the cliff, or was being told what lies in that direction. If you make the leaping conditional on the value of exitsLister.scanning, this will not be a problem. You can also use this flag to suppress the listing of hidden rooms, or to otherwise deceive the player. For example, you might have a room like "Bridge of Safety" returned when the exitsLister.scanning is active, so that it appears that going in that direction is fine. However, when the exitsLister is not active, you might return "Chasm of Death" for that direction instead. If the return value is a door or a room, the object's sdesc is displayed along with the direction property. If the return value is a single-quoted string, that string is printed along with the direction property. Otherwise, no exit is listed for that direction. Although in this example, "Bridge of Safety" may be a room with a sdesc defined for exit listing purposes, you can handle this more elegantly by returning a single-quoted string instead of a dummy object with a sdesc. You can set the exit to return a single-quoted string when exitsLister.scanning is true. (Note that you cannot return a single-quoted string when exitsLister.scanning is nil.) Here is an example of a room which employs the exitLister.scanning flag: =================================== cliff: room sdesc = "Cliff" ldesc = "To the west lies a great dropoff. Best be careful! Back east lies the town. " west = { if(exitsLister.scanning) // just scanning, not moving. return 'It\'s a long way down! '; else { // the player is moving west. "You leap from the cliff, and die painfully. "; die(); } } east = town_room ; =================================== Note: there's a bug in the standard library, such that the isseen flag of the startroom is not automatically true. It becomes true when the room is re-visited, but it's not seen when the game begins. This occasionally produces bad output, but it's easy to fix: just switch the isseen flag to true for your initial room, whatever object that may be. (Often, 'startroom'.) Note: the simple TADS introductory three room "game" does not work with this module, since the front door assumes that we simply execute code equated with the direction. The above documentation explains this kind of problem. Note: because we've reworked the room.noexit mechanism, code like this: west = { if(exitsLister.scanning) return self.noexit; } causes undesired output and interpreter errors. The room.noexit function assumes that the player is moving in a bad direction, and thus begins producing a list of viable exits (if that option is enabled). This can lead to bad recursion, since this value is again checked during the construction of the list of viable exits. Anyhow, to achieve the behavior desired in this example, do: if(exitsLister.scanning) return 'You can\'t go that way!'; (or) if(exitsLister.scanning) return nil; (whichever is desired.) == Thanks to Per Wilhelmsson for betatesting. _____________________________________________________________________ */ /*~~~~~~~~~~~~~~~~~~~~~~~~* * The exits lister object * *_________________________*/ exitsLister: object /* By default, game begins with the lister enabled. */ enabled = true /* By default, when we're listing the exits after the room * description, we don't also list the exits when the * player goes in a bad direction. * * However, we do list the viable directions when the * room-display exits lister is disabled. * * If you always want to have the bad-exits lister enabled, * switch the overkill flag to true. */ overkill = nil /* The no peeking flag, which the user can optionally disable, * makes the rooms which haven't been visited optionally * withhold their names from the 'exits' list; unknown rooms will * be listed as "Unknown" rather than by their short description. * * If this flag is enabled, this might mess up a maze, since the * player would have an extra clue on where he is by checking * which rooms have not been visited yet. One solution would be * to disable the flag; another would be to switch the isseen * flag to true for all maze rooms. */ noPeeking = nil scanning = nil // used when we are silently calling directions getExits() = { local exitexists = nil, loc = parserGetMe.location, i; "\n[ Obvious exits: "; /* cycle through all directions */ for (i := 1 ; i <= length(loc.directionStringList) ; i++ ) { /* check if the direction has a defined link from this room */ if (defined(loc, loc.directionPropList[i], DEFINED_DIRECTLY)) { /* We have a defined link in this direction. * We go to scanning mode, and get a room or door defined for * this exit. If there is one, we list it. We will also accept * single quoted strings, if that's the return value for the * direction. */ local obj, stat; self.scanning := true; stat := outcapture(true); obj := loc.(loc.directionPropList[i]); outcapture( stat ); self.scanning := nil; if ( datatype(obj) = 3 || // single-quoted string datatype(obj) = 2 && // object ( isclass(obj, room) || isclass(obj, doorway) ) ) { "<> "; /* if we list it, we mark that there's at least one * exit. */ exitexists := true; } } } if (!exitexists) { /* we didn't find any exits, so relate this to the player */ "None "; } "]\n"; } ; /*>>>>>>>>>>>>>>* * the main verb * *<<<<<<<<<<<<<<<*/ exitsVerb: deepverb verb = 'exits' 'scan' 'showexits' sdesc = "show exits" exitsOnOffExplained = nil action(actor) = { local i, loc := actor.location; /* cycle through all the directions defined in the room */ for (i := 1 ; i <= length(loc.directionStringList) ; i++ ) { /* check if the direction has a defined link from this room */ if (defined(loc, actor.location.directionPropList[i], DEFINED_DIRECTLY)) { /* First, we figure out which object is associated with the * current direction. We turn off output while we figure * this out, in case the user has set the exit equal to a * double quoted string. (We switch the scanning so * any function associated with the exit can be executed * only on the condition that it's not the exits verb * that's doing the calling.) */ local obj, stat; exitsLister.scanning := true; stat := outcapture(true); obj := loc.(loc.directionPropList[i]); outcapture( stat ); exitsLister.scanning := nil; /* If the obj is a room, we check if it's lit. If it isn't, * we say simply that it's dark. If it is, we say the sdesc. * * If the obj is a door, we print the sdesc of the door. * * If the obj is a single-quoted string, we list this * direction also, but print the string where normally the * linked room's sdesc or the door's sdesc would be printed. */ if ( datatype(obj) = 2 && isclass(obj, room) && !obj.islit ) { "<>: darkness\n"; } else if (datatype(obj) = 2 && isclass(obj, room)) { "<>: "; if (obj.isseen || !exitsLister.noPeeking) "<>\n"; else "Unknown\n"; } else if (datatype(obj) = 2 && isclass(obj, doorway)) { /* In the following, we check noPeeking, and print the * doordest's sdesc instead of the door's sdesc, or * "Unknown," when the door is open. If the door is * closed, we print the door's sdesc. */ "<>: "; if (!obj.isopen) "<>\n"; else if (obj.doordest && obj.doordest.isseen) "<>\n"; else if (exitsLister.noPeeking) "Unknown.\n"; } else if (datatype(obj) = 3) { // single-quoted string "<>: <>\n"; } } } /* The first time this command is executed, we'll tell the * player how to disable the automatic exits display, in case * that's what they're trying to do. */ if (!exitsLister.exitsOnOffExplained) { "[ Typing 'exits on' or 'exits off' will switch the exits display.' ]\n"; exitsLister.exitsOnOffExplained := true; } } ; /*----------------------------------------* * verbs for switching exits display state * *-----------------------------------------*/ exitsOnVerb: deepverb verb = 'exitson' 'exits on' sdesc = "exits on" action(actor) = { /* turn exits display on */ exitsLister.enabled := true; "Ok, exits display is enabled. "; } ; exitsOffVerb: deepverb verb = 'exitsoff' 'exits off' sdesc = "exits off" action(actor) = { /* turn notifications off, and acknowledge the status */ exitsLister.enabled := nil; "Ok, exits display is disabled. "; } ; /*============================* * Modifications to room class * *=============================*/ modify room directionPropList = [ &north, &ne, &east, &se, &south, &sw, &west, &nw, &in, &out, &up, &down ] directionStringList = [ 'north', 'ne', 'east', 'se', 'south', 'sw', 'west', 'nw', 'in', 'out', 'up', 'down' ] /* We want exitsLister.getExits() to be called after the * conventional room material is reported, when Me looks around. * We perform a check that this hasn't been disabled. */ lookAround(verbosity) = { self.dispBeginSdesc; self.statusRoot; self.dispEndSdesc; self.nrmLkAround(verbosity); if(exitsLister.enabled) exitsLister.getExits; } /* travel attempted in a direction with no exit */ noexit = "<>" /* This shows exits when a player goes in a bad direction. */ showexits = { local retString := 'You can\'t go that way. ', dirlist := [], loc := parserGetMe.location, i; if (exitsLister.enabled && !exitsLister.overkill) return 'You can\'t go that way. '; // (else:) for (i := 1; i <= length(loc.directionStringList); i++ ) { /* check if the direction has a defined link from this * room. */ if (defined(loc, directionPropList[i], DEFINED_DIRECTLY)) { /* we have a defined link in this direction. * If there is, we check if it's a door or room, and if * so, we add that to the direction list we're * generating. */ local obj, stat; exitsLister.scanning := true; stat := outcapture(true); obj := loc.(loc.directionPropList[i]); outcapture( stat ); exitsLister.scanning := nil; if ( ( datatype(obj) = 2 && ( isclass(obj, room) || isclass(obj, doorway) ) ) || datatype(obj) = 3 // single-quoted string ) dirlist += loc.directionStringList[i]; } } if (dirlist != []) { local i, len; if (length(dirlist) = 1) retString := 'You can go '; else retString := 'The obvious exits from here are '; retString += dirlist[1]; for (i := 2, len := length(dirlist); i <= len ; i++) { /* If this isn't the first, add a comma; if this is the * last, add an "and" as well. */ if (i = len && len != 1) { retString += ', and '; } else if (i != 1) { retString += ', '; } retString += dirlist[i]; } if (length(dirlist) = 1) return (retString + ' from here. '); return (retString + '. '); } } ;