# exits.t | documentation

    #charset "us-ascii"

    /*
     *   Based on Copyright (c) 2002, 2006 by Michael J. Roberts
     *
     *   Based on exitslister.t, copyright 2002 by Steve Breslin and
     *   incorporated by permission.
     *
     *   TADS 3 Library - Exits Lister
     *
     *   This module provides an automatic exit lister that shows the apparent
     *   exits from the player character's location.  The automatic exit lister
     *   can optionally provide these main features:
     *
     *   - An "exits" verb lets the player explicitly show the list of apparent
     *   exits, along with the name of the room to which each exit connects.
     *
     *   - Exits can be shown automatically as part of the room description.
     *   This extra information can be controlled by the player through the
     *   "exits on" and "exits off" command.
     *
     *   - Exits can be shown automatically when an actor tries to go in a
     *   direction where no exit exists, as a helpful reminder of which
     *   directions are valid.
     */

    /* include the library header */
    #include "advlite.h"

    /* ------------------------------------------------------------------------ */
    /*
     *   The main exits lister.
     */
    exitLister: PreinitObject
        /* preinitialization */
        execute()
        {
            /* install myself as the global exit lister object */
            gExitLister = self;
        }

        /*
         *   Flag: use "verbose" listing style for exit lists in room
         *   descriptions.  When this is set to true, we'll show a
         *   sentence-style list of exits ("Obvious exits lead east to the
         *   living room, south, and up.").  When this is set to nil, we'll use
         *   a terse style, enclosing the message in the default system
         *   message's brackets ("[Obvious exits: East, West]").
         *
         *   Verbose-style room descriptions tend to fit well with a room
         *   description's prose, but at the expense of looking redundant with
         *   the exit list that's usually built into each room's custom
         *   descriptive text to begin with.  Some authors prefer the terse
         *   style precisely because it doesn't look like more prose
         *   description, but looks like a separate bit of information being
         *   offered.
         *
         *   This is an author-configured setting; the library does not provide
         *   a command to let the player control this setting.
         */
        roomDescVerbose = nil

        /*
         *   Flag: show automatic exit listings on attempts to move in
         *   directions that don't allow travel.  Enable this by default,
         *   since most players appreciate having the exit list called out
         *   separately from the room description (where any mention of exits
         *   might be buried in lots of other text) in place of an unspecific
         *   "you can't go that way".
         *
         *   This is an author-configured setting; the library does not provide
         *   a command to let the player control this setting.
         */
        enableReminder = true

        /*
         *   Flag: enable the automatic exit reminder even when the room
         *   description exit listing is enabled.  When this is nil, we will
         *   NOT show a reminder with "can't go that way" messages when the
         *   room description exit list is enabled - this is the default,
         *   because it can be a little much to have the list of exits shown so
         *   frequently.  Some authors might prefer to show the reminder
         *   unconditionally, though, so this option is offered.
         *
         *   This is an author-configured setting; the library does not provide
         *   a command to let the player control this setting.
         */
        enableReminderAlways = nil

        /*
         *   Flag: use hyperlinks in the directions mentioned in room
         *   description exit lists, so that players can click on the direction
         *   name in the listing to enter the direction command.
         */
        enableHyperlinks = true

        /* flag: we've explained how the exits on/off command works */
        exitsOnOffExplained = nil

        /*
         *   Determine if the "reminder" is enabled.  The reminder is the list
         *   of exits we show along with a "can't go that way" message, to
         *   reminder the player of the valid exits when an invalid one is
         *   attempted.
         */
        isReminderEnabled()
        {
            /*
             *   The reminder is enabled if enableReminderAlways is true, OR if
             *   enableReminder is true AND exitsMode.inRoomDesc is nil.
             */
            return (enableReminderAlways
                    || (enableReminder && !exitsMode.inRoomDesc));
        }

        /*
         *   Get the exit lister we use for room descriptions.
         */
        getRoomDescLister()
        {
            /* use the verbose or terse lister, according to the configuration */
            return roomDescVerbose
                ? lookAroundExitLister
                : lookAroundTerseExitLister;
        }

        /* perform the "exits" command to show exits on explicit request */
        showExitsCommand()
        {
            /* show exits for the current actor */
            showExits(gActor);

            /*
             *   if we haven't explained how to turn exit listing on and off,
             *   do so now
             */
            if (!exitsOnOffExplained)
            {
                DMsg(explain exits on off,
                     '<.p>Exit Listing can be adjusted with the following
            commands:\n
            EXITS ON -- show exits in both the status line and in room
            descriptions.\n
            EXITS OFF -- show exits neither in the status line nor in room
            descriptions.\n
            EXITS STATUS -- show exits in the status line only.\n
            EXITS LOOK -- show exits in room descriptions only.\n
            EXITS COLOR ON -- show unvisited exits in a different colour.\n
            EXITS COLOR OFF -- don\'t show unvisited exits in a different colour.\n
            EXITS COLOR RED / BLUE / GREEN / YELLOW -- show unvisted exits in the
            specified colour.
            <.p>');

                exitsOnOffExplained = true;
            }
        }

        /*
         *   Perform an EXITS ON/OFF/STATUS/LOOK command.  'stat' indicates
         *   whether we're turning on (true) or off (nil) the statusline exit
         *   listing; 'look' indicates whether we're turning the room
         *   description listing on or off.
         */
        exitsOnOffCommand(stat, look)
        {
            /* set the new status */
            exitsMode.inStatusLine = stat;
            exitsMode.inRoomDesc = look;

            /* confirm the new status */
            DMsg(exits on off okay, 'Okay. Exit listing in the status line is now
                <<stat ? 'ON' : 'OFF'>>, while exit listing in room descriptions is
                now <<look ? 'ON' : 'OFF'>>. ');

            /*
             *   If we haven't already explained how the EXITS ON/OFF command
             *   works, don't bother explaining it now, since they obviously
             *   know how it works if they've actually used it.
             */
            exitsOnOffExplained = true;
        }

        /* show the list of exits from an actor's current location */
        showExits(actor)
        {
            /* show exits from the actor's location */
            showExitsFrom(actor, actor.getOutermostRoom);
        }

        /* show an exit list display in the status line, if desired */
        showStatuslineExits()
        {
            /* if statusline exit displays are enabled, show the exit list */
            if (exitsMode.inStatusLine)
                showExitsWithLister(gPlayerChar, gPlayerChar.getOutermostRoom,
                                    statuslineExitLister,
                                    gPlayerChar.location
                                    .wouldBeLitFor(gPlayerChar));
        }

        /*
         *   Calculate the contribution of the exits list to the height of the
         *   status line, in lines of text.  If we're not configured to display
         *   the exits list in the status line, then the contribution is zero;
         *   otherwise, we'll estimate how much space we need to display the
         *   exit list.
         */
        getStatuslineExitsHeight()
        {
            /*
             *   if we're enabled, our standard display takes up one line; if
             *   we're disabled, we don't contribute anything to the status
             *   line's vertical extent
             */
            if (exitsMode.inStatusLine)
                return 1;
            else
                return 0;
        }

        /* show exits as part of a room description */
        lookAroundShowExits(actor, loc, illum)
        {
            /* if room exit displays are enabled, show the exits */
            if (exitsMode.inRoomDesc)
                showExitsWithLister(actor, loc, getRoomDescLister, illum);
        }

        /* show exits as part of a "cannot go that way" error */
        cannotGoShowExits(actor, loc)
        {
            /* if we want to show the reminder, show it */
            if (isReminderEnabled())
                showExitsWithLister(actor, loc, explicitExitLister,
                                    loc.wouldBeLitFor(actor));
        }

        /* show the list of exits from a given location for a given actor */
        showExitsFrom(actor, loc)
        {
            /* show exits with our standard lister */
            showExitsWithLister(actor, loc, explicitExitLister,
                                loc.wouldBeLitFor(actor));
        }

        /*
         *   Show the list of exits using a specific lister.
         *
         *   'actor' is the actor for whom the display is being generated.
         *   'loc' is the location whose exit list is to be shown; this need
         *   not be the same as the actor's current location.  'lister' is the
         *   Lister object that will show the list of DestInfo objects that we
         *   create to represent the exit list.
         *
         *   'locIsLit' indicates whether or not the ambient illumination, for
         *   the actor's visual senses, is sufficient that the actor would be
         *   able to see if the actor were in the new location.  We take this
         *   as a parameter so that we don't have to re-compute the
         *   information if the caller has already computed it for other
         *   reasons (such as showing a room description).  If the caller
         *   hasn't otherwise computed the value, it can be easily computed as
         *   loc.wouldBeLitFor(actor).
         */
        showExitsWithLister(actor, loc, lister, locIsLit)
        {
            local destList;

            local options;

            /* we have no option flags for the lister yet */
            options = 0;

            /* run through all of the directions used in the game */
            destList = new Vector(Direction.allDirections.length());

            foreach(local dir in Direction.allDirections)
            {
                local conn = nil;

                switch(loc.propType(dir.dirProp))
                {
                case TypeNil:
                case TypeSString:
                case TypeDString:
                    break;

                case TypeObject:
                    conn = loc.(dir.dirProp);
                    if(conn.isConnectorVisible && conn.isConnectorListed)
                        destList.append(new DestInfo(dir, conn.getDestination(gRoom)));
                    break;

                    /*
                     *   If the property points to code then presumably something
                     *   happens if the player goes this way, so we'll list the
                     *   exit, unless the extraDestInfo table explicitly lists the
                     *   destination as nil.
                     */

                case TypeCode:
                    if(locIsLit)
                    {
                        local dest = libGlobal.extraDestInfo[[loc, dir]];
                        if(dest != nil)
                           destList.append(new DestInfo(dir, dest));
                    }
                    break;
                }

            }

            /* show the list */
            lister.showListAll(destList.toList(), options, 0);
        }

    ;

    ExitLister: Lister
        showListAll(lst, options, indent)
        {
            local cnt = lst.length;

            if(cnt == 0)
            {
                showListEmpty(nil, nil);
                return;
            }

            showListPrefixWide(1, nil, nil);

            for(local obj in lst, local i = 1 ; ; ++i)
            {
                showListItem(obj, nil, nil, nil);
                showListSeparator(nil, i, cnt);

            }

            showListSuffixWide(cnt, nil, nil);

        }

        listerShowsDest = nil

        exitsPrefix = BMsg(exits, 'Exits:');
    ;

    statuslineExitLister: ExitLister
        showListEmpty(pov, parent)
        {
            "<<statusHTML(3)>><b><<exitsPrefix>></b> <<BMsg(status line noexits, '<i>None</i>')>>
            <<statusHTML(4)>>";
        }
        showListPrefixWide(cnt, pov, parent)
        {
            "<<statusHTML(3)>><b><<exitsPrefix>></b> ";
        }
        showListSuffixWide(cnt, pov, parent)
        {
            "<<statusHTML(4)>>";
        }
        showListItem(obj, options, pov, infoTab)
        {
            if(highlightUnvisitedExits && (obj.dest_ == nil || !obj.dest_.seen))
                htmlSay('<FONT COLOR="<<unvisitedExitColour>>">');
            "<<aHref(obj.dir_.name, obj.dir_.name, 'Go ' + obj.dir_.name,
                     AHREF_Plain)>>";
            if(highlightUnvisitedExits && (obj.dest_ == nil || !obj.dest_.seen))
                htmlSay('</FONT>');
        }
        showListSeparator(options, curItemNum, totalItems)
        {
            /* just show a space between items */
            if (curItemNum != totalItems)
                " &nbsp; ";
        }

        /* this lister does not show destination names */
        listerShowsDest = nil

        highlightUnvisitedExits = true

        unvisitedExitColour = 'green'
    ;

    lookAroundExitLister: ExitLister
        showListEmpty(pov, parent)
        {
            DMsg(no exits from here, 'There {plural} {are} no exits from {here}. ');
        }

        showListPrefixWide(cnt, pov, parent)
        {
            "<<exitsPrefix>> ";
        }
        showListSuffixWide(cnt, pov, parent)
        {
            ".";
        }
        showListItem(obj, options, pov, infoTab)
        {

            "<<obj.dir_.name>>";

        }
        showListSeparator(options, curItemNum, totalItems)
        {
            if(curItemNum == totalItems - 1)
                " and ";
            if(curItemNum < totalItems - 1)
                ", ";
        }
    ;

    lookAroundTerseExitLister: ExitLister
        showListEmpty(pov, parent)
        {
            DMsg(no exits, 'Exits: none. ');
        }

        showListPrefixWide(cnt, pov, parent)
        {
            "<<exitsPrefix>> ";
        }
        showListSuffixWide(cnt, pov, parent)
        {
            ".";
        }
        showListItem(obj, options, pov, infoTab)
        {
             htmlSay('<<aHref(obj.dir_.name, obj.dir_.name, 'Go ' + obj.dir_.name,
                     0)>>');

        }
        showListSeparator(options, curItemNum, totalItems)
        {
            if(curItemNum == totalItems - 1)
                " and ";
            if(curItemNum < totalItems - 1)
                ", ";
        }
    ;

    explicitExitLister: ExitLister
        showListEmpty(pov, parent)
        {
            DMsg(no clear exits, 'It{dummy}{\'s} not clear where {i} {can} go from
                {here}. ');
        }

        showListPrefixWide(cnt, pov, parent)
        {
            DMsg(exits from here, 'From {here} {i} could go ');
        }
        showListSuffixWide(cnt, pov, parent)
        {
            ".";
        }
        showListItem(obj, options, pov, infoTab)
        {

          htmlSay('<<aHref(obj.dir_.name, obj.dir_.name, 'Go ' + obj.dir_.name,
                     0)>>');

        }
        showListSeparator(options, curItemNum, totalItems)
        {
            if(curItemNum == totalItems - 1)
                " or ";
            if(curItemNum < totalItems - 1)
                ", ";
        }

    ;

    /*
     *   A destination tracker.  This keeps track of a direction and the
     *   apparent destination in that direction.
     */
    class DestInfo: object
        construct(dir, dest, destName?, destIsBack?)
        {
            /* remember the direction, destination, and destination name */
            dir_ = dir;
            dest_ = dest;
        }

        /* the direction of travel */
        dir_ = nil

        /* the destination room object */
        dest_ = nil

        /* the name of the destination */
        destName_ = nil

        /* flag: this is the "back to" destination */
        destIsBack_ = nil

        /* list of other directions that go to our same destination */
        others_ = []
    ;

    /*
         *   Settings item - show defaults in status line [SettingsItem)
     */
    exitsMode: object
        /* our ID */
        settingID = 'adv3.exits'

        /* show our description */
        settingDesc()
        {
            DMsg(current exit settings, 'Exits are listed
                <<if(inStatusLine && inRoomDesc)>>
                both in the status line and in room descriptions.
                <<else if(inStatusLine && !inRoomDesc)>>
                in the status line only.
                <<else if(!inStatusLine && inRoomDesc)>>
                in room descriptions only.
                <<else if(!inStatusLine && !inRoomDesc)>>
                "neither in the status line nor in room descriptions. <<end>>');
        }

        /* convert to text */
        settingToText()
        {
            /* just return the two binary variables */
            return (inStatusLine ? 'on' : 'off')
                + ','
                + (inRoomDesc ? 'on' : 'off');
        }

        settingFromText(str)
        {
            /* parse out our format */
            if (rexMatch('<space>*(<alpha>+)<space>*,<space>*(<alpha>+)',
                         str.toLower()) != nil)
            {
                /* pull out the two variables from the regexp groups */
                inStatusLine = (rexGroup(1)[3] == 'on');
                inRoomDesc = (rexGroup(2)[3] == 'on');
            }
        }

        /*
         *   Our value is in two parts.  inStatusLine controls whether or not
         *   we show the exit list in the status line; inRoomDesc controls the
         *   exit listing in room descriptions.
         */
        inStatusLine = true
        inRoomDesc = nil
    ;

Adv3Lite Library Reference Manual
Generated on 01/03/2024 from adv3Lite version 1.6.2