/*

  == 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 <[email protected]>.        |
   \______________________________________________________________*/


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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)
                       )
                  ) {

                   "<<loc.directionStringList[i]>> ";

                   /* 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 ) {
                   "<<room.directionStringList[i]>>: darkness\n";
               }
               else if (datatype(obj) = 2 && isclass(obj, room)) {
                   "<<room.directionStringList[i]>>: ";
                   if (obj.isseen || !exitsLister.noPeeking)
                       "<<obj.sdesc>>\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.
                */
                   "<<room.directionStringList[i]>>: ";
                   if (!obj.isopen)
                       "<<obj.sdesc>>\n";
                   else if (obj.doordest && obj.doordest.isseen)
                       "<<obj.doordest.sdesc>>\n";
                   else if (exitsLister.noPeeking)
                       "Unknown.\n";
               }
               else if (datatype(obj) = 3) { // single-quoted string
                   "<<room.directionStringList[i]>>: <<obj>>\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 = "<<self.showexits>>"

   /* 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 + '. ');
       }
   }
;