# misc.t | documentation

    #charset "us-ascii"

    /*
     *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved.
     *
     *   TADS 3 Library - miscellaneous definitions
     *
     *   This module contains miscellaneous definitions that don't have a
     *   natural grouping with any larger modules, and which aren't complex
     *   enough to justify modules of their own.
     */

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

    /* ------------------------------------------------------------------------ */
    /*
     *   When a call is made to a property not defined or inherited by the
     *   target object, the system will automatically invoke this method.  The
     *   method will be invoked with a property pointer as its first argument,
     *   and the original arguments as the remaining arguments.  The first
     *   argument gives the property that was invoked and not defined by the
     *   object.  A typical definition in an object would look like this:
     *
     *   propNotDefined(prop, [args]) { ... }
     *
     *   If this method is not defined by the object, the system simply
     *   returns nil as the value of the undefined property evaluation or
     *   method invocation.
     */
    property propNotDefined;
    export propNotDefined;

    /* ------------------------------------------------------------------------ */
    /*
     *   We refer to some properties defined primarily in score.t - that's an
     *   optional module, though, so make sure the compiler has heard of these.
     */
    property calcMaxScore, runScoreNotifier;

    /* ------------------------------------------------------------------------ */
    /*
     *   The library base class for the gameMain object.
     *
     *   Each game MUST define an object called 'gameMain' to define how the
     *   game starts up.  You can use GameMainDef as the base class of your
     *   'gameMain' object, in which case the only thing you're required to
     *   specify in your object is the 'initialPlayerChar' property - you can
     *   inherit everything else from the GameMainDef class if you don't
     *   require any further customizations.
     */
    class GameMainDef: object
        /*
         *   The initial player character.  Each game's 'gameMain' object MUST
         *   define this to refer to the Actor object that serves as the
         *   initial player character.
         */
        initialPlayerChar = nil

        /*
         *   Show the game's introduction.  This routine is called by the
         *   default newGame() just before entering the main command loop.  The
         *   command loop starts off by showing the initial room description,
         *   so there's no need to do that here.
         *
         *   Most games will want to override this, to show a prologue message
         *   setting up the game's initial situation for the player.  We don't
         *   show anything by default.
         */
        showIntro() { }

        /*
         *   Show the "goodbye" message.  This is called after the main command
         *   loop terminates.
         *
         *   We don't show anything by default.  If you want to show a "thanks
         *   for playing" type of message as the game exits, override this
         *   routine with the desired text.
         */
        showGoodbye() { }

        /*
         *   Begin a new game.  This default implementation shows the
         *   introductory message, calls the main command loop, and finally
         *   shows the goodbye message.
         *
         *   You can override this routine if you want to customize the startup
         *   protocol.  For example, if you want to create a pre-game options
         *   menu, you could override this routine to show the list of options
         *   and process the user's input.  If you need only to customize the
         *   introduction and goodbye messages, you can simply override
         *   showIntro() and showGoodbye() instead.
         */
        newGame()
        {
            /*
             *   Show the statusline before we display our introductory.  This
             *   will help minimize redrawing - if we waited until after
             *   displaying some text, we might have to redraw some of the
             *   screen to rearrange things for the new screen area taken up by
             *   the status line, which could be visible to the user.  By
             *   setting up the status line first, we'll probably have less to
             *   redraw because we won't have anything on the screen yet when
             *   figuring the layout.
             */
            statusLine.showStatusLine();

            /* show the introduction */
            showIntro();

            /* run the game, showing the initial location's full description */
            runGame(true);

            /* show the end-of-game message */
            showGoodbye();
        }

        /*
         *   Restore a game and start it running.  This is invoked when the
         *   user launches the interpreter using a saved game file; for
         *   example, on a Macintosh, this happens when the user double-clicks
         *   on a saved game file on the desktop.
         *
         *   This default implementation bypasses any normal introduction
         *   messages: we simply restore the game file if possible, and
         *   immediately start the game's main command loop.  Most games won't
         *   need to override this, but you can if you need some special effect
         *   in the restore-at-startup case.
         */
        restoreAndRunGame(filename)
        {
            local succ;

            /* mention that we're about to restore the saved position */
            gLibMessages.noteMainRestore();

            /* try restoring it */
            succ = RestoreAction.startupRestore(filename);

            /* show a blank line after the restore result message */
            "<.p>";

            /* if we were successful, run the game */
            if (succ)
            {
                /*
                 *   Run the command loop.  There's no need to show the room
                 *   description, since the RESTORE action will have already
                 *   done so.
                 */
                runGame(nil);

                /* show the end-of-game message */
                showGoodbye();
            }
        }

        /*
         *   Set the interpreter window title, if applicable to the local
         *   platform.  This simply displays a <TITLE> tag to set the title to
         *   the string found in the versionInfo object.
         */
        setGameTitle()
        {
            /* write the <TITLE> tag with the game's name */
            "<title><<versionInfo.name>></title>";
        }

        /*
         *   Set up the HTML-mode about-box.  By default, this does nothing.
         *   Games can use this routine to show an <ABOUTBOX> tag, if desired,
         *   to set up the contents of an about-box for HTML TADS platforms.
         *
         *   Note that an <ABOUTBOX> tag must be re-initialized each time the
         *   main game window is cleared, so this routine should be called
         *   again after any call to clearScreen().
         */
        setAboutBox()
        {
            /* we don't show any about-box by default */
        }

        /*
         *   Build a saved game metadata table.  This returns a LookupTable
         *   containing string key/value pairs that are stored in saved game
         *   files, providing descriptive information that can be displayed to
         *   the user when browsing a collection of save files.  This is called
         *   each time we execute a SAVE command, so that we store the current
         *   context of the game.
         *
         *   Some interpreters display information from this table when
         *   presenting the user with a list of files for RESTORE.  The
         *   contents of the table are intentionally open-ended to allow for
         *   future extensions, but at the moment, the following keys are
         *   specifically defined (note that capitalization must be exact):
         *
         *   UserDesc - descriptive text entered by the user (this should
         *   simply be the contents of the 'userDesc' parameter).  This is
         *   treated as ordinary plain text (i.e., no HTML or other markups are
         *   interpreted in this text).
         *
         *   AutoDesc - descriptive text generated by the game to describe the
         *   saved position.  This text can contain the simple HTML markups
         *   <b>..</b>, <i>..</i>, and <br> for formatting.
         *
         *   Return nil if you don't want to save any metadata information.
         *
         *   'userDesc' is an optional string entered by the user via the Save
         *   Game dialog.  Some interpreters let the user enter a description
         *   for a saved game via the file selector dialog; the descriptive
         *   text is separate from the filename, and is intended to let the
         *   user enter a more free-form description than would be allowed in a
         *   filename.  This text, if any, is passed to use via the 'userDesc'
         *   parameter.
         */
        getSaveDesc(userDesc)
        {
            /* create the lookup table */
            local t = new LookupTable();

            /* store the user description, if provided */
            if (userDesc != nil)
                t['UserDesc'] = userDesc;

            /* start our auto description with the current room name */
            desc = gPlayerChar.getLookAroundName() + '; ';

            /* if we're keeping score, include the score */
            if (libGlobal.scoreObj != nil)
                desc += toString(libGlobal.scoreObj.totalScore) + ' points in ';

            /* add the number of turns so far */
            desc += toString(libGlobal.totalTurns) + ' moves';

            /* add the auto description */
            t['AutoDesc'] = desc;

            /* return the table */
            return t;
        }

        /*
         *   The gameMain object also specifies some settings that control
         *   optional library behavior.  If you want the standard library
         *   behavior, you can just inherit the default settings from this
         *   class.  Some games might want to select non-default variations,
         *   though.
         */

        /*
         *   The maximum number of points possible in the game.  If the game
         *   includes the scoring module at all, and this is non-nil, the SCORE
         *   and FULL SCORE commands will display this value to the player as a
         *   rough indication of how much farther there is to go in the game.
         *
         *   By default, we initialize this on demand, by calculating the sum
         *   of the point values of the Achievement objects in the game.  The
         *   game can override this if needed to specify a specific maximum
         *   possible score, rather than relying on the automatic calculation.
         */
        maxScore()
        {
            local m;

            /* ask the score module (if any) to compute the maximum score */
            m = (libGlobal.scoreObj != nil
                 ? libGlobal.scoreObj.calcMaxScore : nil);

            /* supersede this initializer with the calculated value */
            maxScore = m;

            /* return the result */
            return m;
        }

        /*
         *   The score ranking list - this provides a list of names for
         *   various score levels.  If the game provides a non-nil list here,
         *   the SCORE and FULL SCORE commands will show the rank along with
         *   the score ("This makes you a Master Adventurer").
         *
         *   This is a list of score entries.  Each score entry is itself a
         *   list of two elements: the first element is the minimum score for
         *   the rank, and the second is a string describing the rank.  The
         *   ranks should be given in ascending order, since we simply search
         *   the list for the first item whose minimum score is greater than
         *   our score, and use the preceding item.  The first entry in the
         *   list would normally have a minimum of zero points, since it
         *   should give the initial, lowest rank.
         *
         *   If this is set to nil, which it is by default, we'll simply skip
         *   score ranks entirely.
         */
        scoreRankTable = nil

        /*
         *   Verbose mode.  If this is on, the full room description is
         *   displayed each time the player enters a room, regardless of
         *   whether or not the player has seen the room before; if this is
         *   nil, the full description is only displayed on the player's first
         *   entry to a room, and only the short description on re-entry.  Note
         *   that the library provides VERBOSE and TERSE commands that let the
         *   player change this setting dynamically.
         *
         *   We use a BinarySettingsItem to store the current mode, so that
         *   this setting's default will be taken from the user's global
         *   cross-game preferences.
         */
        verboseMode = verboseModeSettingsItem

        /*
         *   Option flag: allow the player to use "you" and "me"
         *   interchangeably in referring to the player character.  We set this
         *   true by default, so that the player can refer to the player
         *   character in either the first or second person, regardless of how
         *   the game refers to the PC.
         *
         *   If desired, the game can set this flag to nil to force the player
         *   to use the correct pronoun to refer to the player character.  We
         *   define "correct" in the case of first or second person as the
         *   complement of what the game uses: if the game calls the PC "me",
         *   the player must say "you", and vice versa.  In a third-person
         *   game, the player must also refer to the PC in the third person.
         *
         *   We set the default to allow using "you" and "me" interchangeably
         *   because (a) this will create no confusion in most games, and (b)
         *   many players would be annoyed otherwise.  For one thing, most
         *   experienced IF players will be rather set in their ways; they'll
         *   be accustomed to using either "me" or "you" (but usually "me") to
         *   refer to the PC, and will tend out of habit to do so even in games
         *   that don't use the traditional second-person narration format.
         *   For another thing, different players have different ideas about
         *   whether the PC is "you" or "me" in input, even in a conventional
         *   second-person game.  Some players think in terms of a conversation
         *   with the narrator, in which case the narrator's "you" is the
         *   player's "me", and vice versa; other players are rather more
         *   literal-minded, assuming that if the game talks about "you" then
         *   so should the player.
         *
         *   Even in games that use first-person or third-person narration, it
         *   seems unlikely that there will be a separate second-person element
         *   to the narration, and as long as that's true, it should cause no
         *   confusion for the game to accept "you" and "me" as equivalent in
         *   commands.  However, the library provides this option in case such
         *   as situation does arise.
         */
        allowYouMeMixing = true

        /*
         *   Option flag: filter plural phrase matches exclude the most obvious
         *   illogicalities, such as trying to TAKE an object that's already
         *   being held, or trying to OPEN an object that's already open.
         *
         *   This is set to true by default, which means that we exclude an
         *   object from matching a plural phrase when the object's "verify"
         *   routine for the verb has an "illogical-already" or an
         *   "illogical-self" result.
         *
         *   If you would prefer that plural words are simply matched to
         *   everything present that matches the vocabulary, without any
         *   filtering at all, override this and set it to nil.
         */
        filterPluralMatches = true

        /*
         *   Option flag: allow ALL to be used for every verb.  This is true by
         *   default, which means that players will be allowed to use ALL with
         *   any command - OPEN ALL, EXAMINE ALL, etc.
         *
         *   Some authors don't like to allow players to use ALL with so many
         *   verbs, because they think it's a sort of "cheating" when players
         *   try things like OPEN ALL.  This option lets you disable ALL for
         *   most verbs; if you set this to nil, only the basic inventory
         *   management verbs (TAKE, TAKE FROM, DROP, PUT IN, PUT ON) will
         *   allow ALL, and other verbs will simply respond with an error
         *   ("'All' isn't allowed with that verb").
         *
         *   If you're writing an especially puzzle-oriented game, you might
         *   want to set this to nil.  It's a trade-off though, as some people
         *   will think your game is less player-friendly if you disable ALL.
         */
        allVerbsAllowAll = true

        /*
         *   When a command fails, should we continue processing any remaining
         *   commands on the same command line, or simply ignore them?  The
         *   reason we might want to ignore additional commands is that they
         *   might not do what the player was expecting if an earlier command
         *   failed; this can sometimes create confusing situations, because
         *   the player expected one effect but got something quite different.
         *   On the other hand, *not* executing all the commands on the command
         *   line could be confusing in its own way, since the game's
         *   assessment of what constitutes "failure" might not be clear to the
         *   player; from the player's perspective, the game might appear to be
         *   inexplicably skipping commands.
         *
         *   There's no perfect solution.  As always, the ideal is to
         *   understand the player's intentions and act accordingly.  But when
         *   a command fails, it's usually because the player's idea of what's
         *   going on is out of sync with the game's - in other words, if we're
         *   in this situation to start with, it's probably because our best
         *   effort to understand the player's intentions has already failed.
         *   This isn't always the case; sometimes we understand the player's
         *   intentions perfectly well, but the command fails anyway because of
         *   some surprising new development.  In these cases, aborting the
         *   rest of the command is arguably the right approach, because the
         *   player will need a chance to reconsider the pre-typed commands in
         *   light of the new information.  In other cases, though, it's not so
         *   clear.  For many players, the prime virtue for the parser is to be
         *   predictable, and the most predictable thing to do is to simply
         *   plow through the rest of the command line in all cases.
         *
         *   Our traditional approach (from the early adv3 versions, and even
         *   in tads 2) has been the simple-minded approach - just keep going
         *   in all cases.  So, we make this the default.  You can abort
         *   remaining commands on a command failure by setting this to true.
         */
        cancelCmdLineOnFailure = nil

        /*
         *   Should we use distinguishers when generating action object
         *   announcement messages?  If this is set, announcement messages that
         *   list objects by name will add distinguishing details to indicate
         *   specifically which objects are being referred to.  This applies to
         *   messages announcing default objects, vaguely matched objects, and
         *   multiple objects.
         *
         *   A distinguisher is a parser object that tells two objects apart by
         *   some feature that's different in the two objects.  The key thing
         *   is that the difference has some natural language phrasing
         *   associated with it, both on the input side and the output side.
         *   For example, the locational distinguisher can tell two objects
         *   apart if they have different containers, since it can describe the
         *   objects by adding containment phrases like "in the box" or "on the
         *   table".
         *
         *   When this flag is turned on, the parser will try to pick a
         *   distinguisher that can tell apart the specific objects mentioned
         *   in the announcement, from one another or from other objects in
         *   scope, depending on the context.  The point is to help make it
         *   clearer to the player the exact objects being referred to.
         *
         *   Even when this flag is set, the parser tries to minimize the use
         *   of additional distinguishing detail.  The goal is to be natural in
         *   the usage, adding detail only when a human speaker would, which is
         *   when the phrasing would otherwise be ambiguous.
         *
         *   Setting this flag to nil makes the parser simply use the basic
         *   name of each object in an announcement.  You can use this setting
         *   if you find that the distinguisher mode generates too much fussy
         *   detail for your taste.
         */
        useDistinguishersInAnnouncements = true

        /*
         *   How should we handle object announcements when an object is
         *   automatically disambiguated?  This controls how an action is
         *   described when the parser uses the logicalness rules to narrow
         *   down the object for a noun phrase when the noun phrase could refer
         *   to multiple in-scope objects.  There are three options:
         *
         *   AnnounceUnclear - Make a parenthetical announcement only when the
         *   choice is *not* clear (as described below).  This is the original
         *   library behavior, from before this option was added.
         *
         *   AnnounceClear - Make a parenthetical announcement (for example,
         *   "(the red door)") for all disambiguated objects, whether clear or
         *   unclear.  We don't make an announcement when there's only one
         *   in-scope object matching the noun phrase - the announcement is
         *   only when multiple objects match the words.
         *
         *   DescribeClear - For *unclear* disambiguation, make a parenthetical
         *   announcement, to emphasize that the parser had to make a choice.
         *   For *clear* disambiguation, skip the announcement, but *do* use a
         *   verbose version of the library message in place of one of the
         *   terse default replies.  For example, for >TAKE BOX, instead of
         *   "Taken", we would reply "You take the green box."  The longer
         *   reply in these cases always mentions the involved object by name,
         *   to make it clear exactly which object we chose to use.
         *
         *   The default setting is DescribeClear.
         *
         *   This only applies when the disambiguation choice is clear - that
         *   is, when there's exactly one in-scope object that passes the
         *   logicalness tests.  For example, if the current location contains
         *   a red door that's open and a green door that's closed, CLOSE DOOR
         *   clearly refers to the red door because the other one is already
         *   closed - it's not logical.  There are other cases where the
         *   disambiguation is a best guess rather than a clear choice, such as
         *   when there are multiple logical objects but there's one that's
         *   more likely than the others due to the logicalRank results.  In
         *   those best-guess situations, the parser always announces its
         *   decision, because it's entirely plausible that the player meant
         *   one of the other logical, but less likely, choices.
         */
        ambigAnnounceMode = DescribeClear

        /*
         *   Should the "before" notifications (beforeAction, roomBeforeAction,
         *   and actorAction) run before or after the "check" phase?
         *
         *   The library traditionally ran the "before" notifiers first, so
         *   this is the default.  However, in many ways it's more logical and
         *   useful to run "check" first.  That way, you can consider the
         *   action to be more or less committed by the time the "before"
         *   notifiers are invoked.  Of course, a command is never *truly*
         *   committed until it's actually been executed, since a "before"
         *   handler could always cancel it.  But this is relatively rare -
         *   "before" handlers usually carry out side effects, so it's very
         *   useful to be able to know that the command has already passed all
         *   of its own internal checks by the time "before" is invoked - that
         *   way, you can invoke side effects without worrying that the command
         *   will subsequently fail.
         */
        beforeRunsBeforeCheck = true
    ;

    /*
     *   The VERBOSE mode settings item.
     */
    verboseModeSettingsItem: BinarySettingsItem
        /* VERBOSE mode is on by default */
        isOn = true

        /* our configuration file variable ID */
        settingID = 'adv3.verbose'

        /* show our description */
        settingDesc = (gLibMessages.shortVerboseStatus(isOn))
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   Clear the main game window.  In most cases, you should call this
     *   rather than calling the low-level clearScreen() function directly,
     *   since this routine takes care of a couple of chores that should
     *   usually be done at the same time.
     *
     *   First, we flush the transcript to ensure that no left-over reports
     *   that were displayed before we cleared the screen will show up on the
     *   new screen.  Second, we call the low-level clearScreen() function to
     *   actually clear the display window.  Finally, we re-display any
     *   <ABOUTBOX> tag, to ensure that the about-box will still be around;
     *   this is necessary because any existing <ABOUTBOX> tag is lost after
     *   the screen is cleared.
     */
    cls()
    {
        /* flush any captured transcript output */
        if (gTranscript != nil)
            gTranscript.flushForInput();

        /* clear the screen */
        aioClearScreen();
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Run the game.  We start by showing the description of the initial
     *   location, if desired, and then we read and interpret commands until
     *   the game ends (via a "quit" command, winning, death of the player
     *   character, or any other way of terminating the game).
     *
     *   This routine doesn't return until the game ends.
     *
     *   Before calling this routine, the caller should already have set the
     *   global variable gPlayerChar to the player character actor.
     *
     *   'look' is a flag indicating whether or not to look around; if this is
     *   true, we'll show a full description of the player character's initial
     *   location, as though the player were to type "look around" as the first
     *   command.
     */
    runGame(look)
    {
        /* show the starting location */
        if (look)
        {
            /* run the initial "look around" in a dummy command context */
            withActionEnv(EventAction, gPlayerChar,
                          {: gPlayerChar.lookAround(true) });
        }

        /* run the scheduling loop until the game ends */
        runScheduler();
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Main program entrypoint.  The core run-time start-up code calls this
     *   after running pre-initialization and load-time initialization.  This
     *   entrypoint is called when we're starting the game normally; when the
     *   game is launched through a saved-position file, mainRestore() will be
     *   invoked instead.
     */
    main(args)
    {
        libGlobal.commandLineArgs = args;
        mainCommon(&newGame);
    }

    /*
     *   Main program entrypoint for restoring a saved-position file.  This is
     *   invoked from the core run-time start-up code when the game is launched
     *   from the operating system via a saved-position file.  For example, on
     *   Windows, double-clicking on a saved-position file on the Windows
     *   desktop launches the interpreter, which looks in the save file to find
     *   the game executable to run, then starts the game and invokes this
     *   entrypoint.
     */
    mainRestore(args, restoreFile)
    {
        libGlobal.commandLineArgs = args;
        mainCommon(&restoreAndRunGame, restoreFile);
    }

    /*
     *   Common main entrypoint - this handles starting a new game or restoring
     *   an existing saved state.
     */
    mainCommon(prop, [args])
    {
        try
        {
            /* restore the global default settings */
            settingsManager.restoreSettings();
        }
        catch (Exception exc)
        {
            /*
             *   ignore any errors restoring defaults - it's not critical that
             *   we restore this file automatically
             */
        }

        try
        {
            /* at the start of the session, set up the UI subsystem */
            if (mainGlobal.restartID == 0)
            {
                /* initialize the UI */
                initUI();

                /*
                 *   tell the system library to call our UI shutdown function
                 *   at program exit
                 */
                mainAtExit.addHandler(terminateUI);
            }

            /* initialize the display */
            initDisplay();

            /* call the appropriate gameMain method */
            gameMain.(prop)(args...);
        }
        catch (QuittingException q)
        {
            /*
             *   This exception is a signal to quit the game, which we will now
             *   proceed to do by returning from this function, which exits the
             *   program.
             */
        }
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Determine if the given object overrides the definition of the given
     *   property inherited from the given base class.  Returns true if the
     *   object derives from the given base class, and the object's definition
     *   of the property comes from a different place than the base class's
     *   definition of the property.
     */
    overrides(obj, base, prop)
    {
        return (obj.ofKind(base)
                && (obj.propDefined(prop, PropDefGetClass)
                    != base.propDefined(prop, PropDefGetClass)));
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Library Pre-Initializer.  This object performs the following
     *   initialization operations immediately after compilation is completed:
     *
     *   - adds each defined Thing to its container's contents list
     *
     *   - adds each defined Sense to the global sense list
     *
     *   This object is named so that other libraries and/or user code can
     *   create initialization order dependencies upon it.
     */
    adv3LibPreinit: PreinitObject
        execute()
        {
            /* save each SettingsItem's factory default settings */
            forEachInstance(SettingsItem,
                            {i: i.factoryDefault = i.settingToText()});

            /* set the initial player character, as specified in gameMain */
            gPlayerChar = gameMain.initialPlayerChar;

            /*
             *   visit every VocabObject, and run its vocabulary initializer
             *   (this routine will be defined in the language-specific part
             *   of the library to enter each object's vocabulary words into
             *   the dictionary)
             */
            forEachInstance(VocabObject, { obj: obj.initializeVocab() });

            /* visit every Thing, and run its general initializer */
            forEachInstance(Thing, { obj: obj.initializeThing() });

            /* initialize SpecialTopic objects */
            forEachInstance(SpecialTopic, { obj: obj.initializeSpecialTopic() });

            /*
             *   Initialize each MultiInstance object.  Do this after
             *   initializing the Thing objects, because we'll be dynamically
             *   constructing new Thing objects for the instances.  Those new
             *   Things will be initialized by their constructors, so we don't
             *   want to initialize them redundantly with explicit
             *   initializeThing calls.
             */
            forEachInstance(MultiInstance, { obj: obj.initializeLocation() });

            /* add every Sense to the global sense list */
            forEachInstance(Sense, { obj: libGlobal.allSenses += obj });

            /*
             *   initialize each ActorState - do this before initializing
             *   actors, since we want each actor's initial state to plug
             *   itself into its actor before we initialize the actors
             */
            forEachInstance(ActorState, { obj: obj.initializeActorState() });

            /* initialize each Actor */
            forEachInstance(Actor, { obj: obj.initializeActor() });

            /*
             *   initialize the AltTopics first, to set up their parents' lists
             *   of their AltTopic children
             */
            forEachInstance(AltTopic, { obj: obj.initializeAltTopic() });

            /* initialize the topic database entries */
            forEachInstance(TopicEntry, { obj: obj.initializeTopicEntry() });

            /* initialize the suggested topics */
            forEachInstance(SuggestedTopic,
                { obj: obj.initializeSuggestedTopic() });

            /* initialize the master direction list */
            Direction.initializeDirectionClass();

            /* initialize the noise/odor notification daemon */
            local d = new Daemon(SensoryEmanation, &noteSenseChanges, 1);

            /*
             *   give it a later-than-default event order, so that it runs
             *   after most other daemons and fuses
             */
            d.eventOrder = 500;

            /* set up a daemon for the current location */
            new Daemon(BasicLocation, &dispatchRoomDaemon, 1);

            /*
             *   Initialize the status line daemon.  Set this daemon's event
             *   order to a high value so that it runs last, after all other
             *   daemons - we want this to be the last prompt daemon so that
             *   the status line is updated after any other daemons have done
             *   their jobs already, in case any of them move the player
             *   character to a new location or affect the score, or make any
             *   other changes that should be reflected on the status line.
             */
            local sld = new PromptDaemon(statusLine, &showStatusLineDaemon);
            sld.eventOrder = 1000;

            /*
             *   Attach the command sequencer output filter, the
             *   language-specific message parameter substitution filter, the
             *   style tag formatter filter, and the paragraph filter to the
             *   main output stream.  Stack them so that the paragraph manager
             *   is at the bottom, since the library tag filter can produce
             *   paragraph tags and thus needs to sit atop the paragraph
             *   filter.  Put the command sequencer above those, since it
             *   might need to write style tags.  Finally, put the sense
             *   context filter on top of those.
             */
            mainOutputStream.addOutputFilter(typographicalOutputFilter);
            mainOutputStream.addOutputFilter(mainParagraphManager);
            mainOutputStream.addOutputFilter(styleTagFilter);
            mainOutputStream.addOutputFilter(langMessageBuilder);
            mainOutputStream.addOutputFilter(commandSequencer);
            mainOutputStream.addOutputFilter(conversationManager);
            mainOutputStream.addOutputFilter(senseContext);

            /*
             *   Attach our message parameter filter and style tag filter to
             *   the status line streams.  We don't need most of the main
             *   window's filters in the status line.
             */
            statusTagOutputStream.addOutputFilter(styleTagFilter);
            statusTagOutputStream.addOutputFilter(langMessageBuilder);

            statusLeftOutputStream.addOutputFilter(styleTagFilter);
            statusLeftOutputStream.addOutputFilter(langMessageBuilder);

            statusRightOutputStream.addOutputFilter(styleTagFilter);
            statusRightOutputStream.addOutputFilter(langMessageBuilder);
        }

        /*
         *   Make sure the output streams we depend on are initialized before
         *   me (so that they set up properly internally).  Also, make sure
         *   that the message builder object (langMessageBuilder) is set up
         *   first, so that we can add entries to its parameter substitution
         *   table.
         */
        execBeforeMe = [mainOutputStream, statusTagOutputStream,
                        statusLeftOutputStream, statusRightOutputStream,
                        langMessageBuilder]
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   Library Initializer.  This object performs the following
     *   initialization operations each time the game is started:
     *
     *   - sets up the library's default output function
     */
    adv3LibInit: InitObject
        execute()
        {
            /*
             *   Set up our default output function.  Note that we must do
             *   this during run-time initialization each time we start the
             *   game, rather than during pre-initialization, because the
             *   default output function state is not part of the load-image
             *   configuration.
             */
            t3SetSay(say);
        }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   Generic script object.  This class can be used to implement a simple
     *   state machine.
     */
    class Script: object
        /*
         *   Get the current state.  This returns a value that gives the
         *   current state of the script, which is usually simply an integer.
         */
        getScriptState()
        {
            /* by default, return our state property */
            return curScriptState;
        }

        /*
         *   Process the next step of the script.  This routine must be
         *   overridden to perform the action of the script.  This routine's
         *   action should call getScriptState() to get our current state, and
         *   should update the internal state appropriately to take us to the
         *   next step after the current one.
         *
         *   By default, we don't do anything at all.
         */
        doScript()
        {
            /* override to carry out the script */
        }

        /*
         *   Property giving our current state.  This should never be used
         *   directly; instead, getScriptState() should always be used, since
         *   getScriptState() can be overridden so that the state depends on
         *   something other than this internal state property. The meaning of
         *   the state identifier is specific to each subclass.
         */
        curScriptState = 0
    ;

    /*
     *   Random-Firing script add-in.  This is a mix-in class that you can add
     *   to the superclass list of any Script subclass to make the script
     *   execute only a given percentage of the time it's invoked.  Each time
     *   doScript() is invoked on the script, we'll look at the probability
     *   settings (see the properties below) to determine whether we really
     *   want to execute the script this time; if so, we'll proceed with the
     *   scripted event, otherwise we'll just return immediately, doing
     *   nothing.
     *
     *   Note that this must be used in the superclass list *before* the Script
     *   subclass:
     *
     *   myScript: RandomFiringScript, EventList
     *.    // ...my definitions...
     *.  ;
     *
     *   This class is especially useful for random atmospheric events, because
     *   it allows you to make the timing of scripted events random.  Rather
     *   than making a scripted event happen on every single turn, you can use
     *   this to make events happen only sporadically.  It can often feel too
     *   predictable and repetitious when a random background event happens on
     *   every single turn; firing events less frequently often makes them feel
     *   more realistic.
     */
    class RandomFiringScript: object
        /*
         *   Percentage of the time an event occurs.  By default, we execute an
         *   event 100% of the time - meaning every time that doScript() is
         *   invoked.  If you set this to a lower percentage, then each time
         *   doScript() is invoked, we'll randomly decide whether or not to
         *   execute an event based on this percentage.  For example, if you
         *   want an event to execute on average about a third of the time, set
         *   this to 33.
         *
         *   Note that this is a probabilistic frequency.  Setting this to 33
         *   does *not* mean that we'll execute exactly every third time.
         *   Rather, it means that we'll randomly execute or not on each
         *   invocation, and averaged over a large number of invocations, we'll
         *   execute about a third of the time.
         */
        eventPercent = 100

        /*
         *   Random atmospheric events can get repetitive after a while, so we
         *   provide an easy way to reduce the frequency of our events after a
         *   while.  This way, we'll generate the events more frequently at
         *   first, but once the player has seen them enough to get the idea,
         *   we'll cut back.  Sometimes, the player will spend a lot of time in
         *   one place trying to solve a puzzle, so the same set of random
         *   events can get stale.  Set eventReduceAfter to the number of times
         *   you want the events to be generated at full frequency; after we've
         *   fired events that many times, we'll change eventPercent to
         *   eventReduceTo.  If eventReduceAfter is nil, we won't ever change
         *   eventPercent.
         */
        eventReduceAfter = nil
        eventReduceTo = nil

        /*
         *   When doScript() is invoked, check the event probabilities before
         *   proceeding.
         */
        doScript()
        {
            /* process the script step only if the event odds allow it */
            if (checkEventOdds())
                inherited();
        }

        /*
         *   Check the event odds to see if we want to fire an event at all on
         *   this invocation.
         */
        checkEventOdds()
        {
            /*
             *   check the event odds to see if we fire an event this time; if
             *   not, we're done with the script invocation
             */
            if (rand(100) >= eventPercent)
                return nil;

            /*
             *   we're firing an event this time, so count this against the
             *   reduction limit, if there is one
             */
            if (eventReduceAfter != nil)
            {
                /* decrement the limit counter */
                --eventReduceAfter;

                /* if it has reached zero, apply the reduced frequency */
                if (eventReduceAfter == 0)
                {
                    /* apply the reduced frequency */
                    eventPercent = eventReduceTo;

                    /* we no longer have a limit to look for */
                    eventReduceAfter = nil;
                }
            }

            /* indicate that we do want to fire an event */
            return true;
        }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   An "event list."  This is a general-purpose type of script that lets
     *   you define the scripted events separately from the Script object.
     *
     *   The script is driven by a list of values; each value represents one
     *   step of the script.  Each value can be a single-quoted string, in
     *   which case the string is simply displayed; a function pointer, in
     *   which case the function is invoked without arguments; another Script
     *   object, in which case the object's doScript() method is invoked; a
     *   property pointer, in which case the property of 'self' (the EventList
     *   object) is invoked with no arguments; or nil, in which case nothing
     *   happens.
     *
     *   This base type of event list runs through the list once, in order, and
     *   then simply stops doing anything once we pass the last event.
     */
    class EventList: Script
        construct(lst) { eventList = lst; }

        /* the list of events */
        eventList = []

        /* cached length of the event list */
        eventListLen = (eventList.length())

        /* advance to the next state */
        advanceState()
        {
            /* increment our state index */
            ++curScriptState;
        }

        /* by default, start at the first list element */
        curScriptState = 1

        /* process the next step of the script */
        doScript()
        {
            /* get our current event state */
            local idx = getScriptState();

            /* get the list (evaluate it once to avoid repeated side effects) */
            local lst = eventList;

            /* cache the length */
            eventListLen = lst.length();

            /* if it's a valid index in our list, fire the event */
            if (idx >= 1 && idx <= eventListLen)
            {
                /* carry out the event */
                doScriptEvent(lst[idx]);
            }

            /* perform any end-of-script processing */
            scriptDone();
        }

        /* carry out one script event */
        doScriptEvent(evt)
        {
            /* check what kind of event we have */
            switch (dataTypeXlat(evt))
            {
            case TypeSString:
                /* it's a string - display it */
                say(evt);
                break;

            case TypeObject:
                /* it must be a Script object - invoke its doScript() method */
                evt.doScript();
                break;

            case TypeFuncPtr:
                /* it's a function pointer - invoke it */
                (evt)();
                break;

            case TypeProp:
                /* it's a property of self - invoke it */
                self.(evt)();
                break;

            default:
                /* do nothing in other cases */
                break;
            }
        }

        /*
         *   Perform any end-of-script processing.  By default, we advance the
         *   script to the next state.
         *
         *   Some scripts might want to override this.  For example, a script
         *   could be driven entirely by some external timing; the state of a
         *   script could vary once per turn, for example, or could change each
         *   time an actor pushes a button.  In these cases, invoking the
         *   script wouldn't affect the state of the event list, so the
         *   subclass would override scriptDone() so that it does nothing at
         *   all.
         */
        scriptDone()
        {
            /* advance to the next state */
            advanceState();
        }
    ;

    /*
     *   An "external" event list is one whose state is driven externally to
     *   the script.  Specifically, the state is *not* advanced by invoking the
     *   script; the state is advanced exclusively by some external process
     *   (for example, by a daemon that invokes the event list's advanceState()
     *   method).
     */
    class ExternalEventList: EventList
        scriptDone() { }
    ;

    /*
     *   A cyclical event list - this runs through the event list in order,
     *   returning to the first element when we pass the last element.
     */
    class CyclicEventList: EventList
        advanceState()
        {
            /* go to the next state */
            ++curScriptState;

            /* if we've passed the end of the list, loop back to the start */
            if (curScriptState > eventListLen)
                curScriptState = 1;
        }
    ;

    /*
     *   A stopping event list - this runs through the event list in order,
     *   then stops at the last item and repeats it each time the script is
     *   subsequently invoked.
     *
     *   This is often useful for things like ASK ABOUT topics, where we reveal
     *   more information when asked repeatedly about a topic, but eventually
     *   reach a point where we've said everything:
     *
     *.  >ask bob about black book
     *.  "What makes you think I know anything about it?" he says, his
     *   voice shaking.
     *
     *   >again
     *.  "No! You can't make me tell you!"
     *
     *   >again
     *.  "All right, I'll tell you what you want to know!  But I warn you,
     *   these are things mortal men were never meant to know.  Your life, your
     *   very soul will be in danger from the moment you hear these dark secrets!"
     *
     *   >again
     *.  [scene missing]
     *
     *   >again
     *.  "I've already told you all I know."
     *
     *   >again
     *.  "I've already told you all I know."
     */
    class StopEventList: EventList
        advanceState()
        {
            /* if we haven't yet reached the last state, go to the next one */
            if (curScriptState < eventListLen)
                ++curScriptState;
        }
    ;

    /*
     *   A synchronized event list.  This is an event list that takes its
     *   actions from a separate event list object.  We get our current state
     *   from the other list, and advancing our state advances the other list's
     *   state in lock step.  Set 'masterObject' to refer to the master list
     *   whose state we synchronize with.
     *
     *   This can be useful, for example, when we have messages that reflect
     *   two different points of view on the same events: the messages for each
     *   point of view can be kept in a separate list, but the one list can be
     *   a slave of the other to ensure that the two lists are based on a
     *   common state.
     */
    class SyncEventList: EventList
        /* my master event list object */
        masterObject = nil

        /* my state is simply the master list's state */
        getScriptState() { return masterObject.getScriptState(); }

        /* to advance my state, advance the master list's state */
        advanceState() { masterObject.advanceState(); }

        /* let the master list take care of finishing a script step */
        scriptDone() { masterObject.scriptDone(); }
    ;

    /*
     *   Randomized event list.  This is similar to a regular event list, but
     *   chooses an event at random each time it's invoked.
     */
    class RandomEventList: RandomFiringScript, EventList
        /* process the next step of the script */
        doScript()
        {
            /* check the odds to see if we want to fire an event at all */
            if (!checkEventOdds())
                return;

            /* get our next random number */
            local idx = getNextRandom();

            /* cache the list and its length, to avoid repeated side effects */
            local lst = eventList;
            eventListLen = lst.length();

            /* run the event, if the index is valid */
            if (idx >= 1 && idx <= eventListLen)
                doScriptEvent(lst[idx]);
        }

        /*
         *   Get the next random state.  By default, we simply return a number
         *   from 1 to the number of entries in our event list.  This is a
         *   separate method to allow subclasses to customize the way the
         *   random number is selected.
         */
        getNextRandom()
        {
            /*
             *   Note that rand(n) returns a number from 0 to n-1 inclusive;
             *   since list indices run from 1 to list.length, add one to the
             *   result of rand(list.length) to get a value in the proper range
             *   for a list index.
             */
            return rand(eventListLen) + 1;
        }
    ;

    /*
     *   Shuffled event list.  This is similar to a random event list, except
     *   that we fire our events in a "shuffled" order rather than an
     *   independently random order.  "Shuffled order" means that we fire the
     *   events in random order, but we don't re-fire an event until we've run
     *   through all of the other events.  The effect is as though we were
     *   dealing from a deck of cards.
     *
     *   For the first time through the main list, we normally shuffle the
     *   strings immediately at startup, but this is optional.  If shuffleFirst
     *   is set to nil, we will NOT shuffle the list the first time through -
     *   we'll run through it once in the given order, then shuffle for the
     *   next time through, then shuffle again for the next, and so on.  So, if
     *   you want a specific order for the first time through, just define the
     *   list in the desired order and set shuffleFirst to nil.
     *
     *   You can optionally specify a separate list of one-time-only sequential
     *   strings in the property firstEvents.  We'll run through these strings
     *   once.  When we've exhausted them, we'll switch to the main eventList
     *   list, showing it one time through in its given order, then shuffling
     *   it and running through it again, and so on.  The firstEvents list is
     *   never shuffled - it's always shown in exactly the order given.
     */
    class ShuffledEventList: RandomFiringScript, EventList
        /*
         *   a list of events to go through sequentially, in the exact order
         *   specified, before firing any events from the main list
         */
        firstEvents = []

        /*
         *   Flag: shuffle the eventList list before we show it for the first
         *   time.  By default, this is set to true, so that the behavior is
         *   random on each independent run of the game.  However, it might be
         *   desirable in some cases to always use the original ordering of the
         *   eventList list the first time through the list.  If this is set to
         *   nil, we won't shuffle the list the first time through.
         */
        shuffleFirst = true

        /*
         *   Flag: suppress repeats in the shuffle.  If this is true, it
         *   prevents a given event from showing up twice in a row, which could
         *   otherwise happen right after a shuffle.  This is ignored for lists
         *   with one or two events: it's impossible to prevent repeats in a
         *   one-element list, and doing so in a two-element list would produce
         *   a predictable A-B-A-B... pattern.
         *
         *   You might want to set this to nil for lists of three or four
         *   elements, since such short lists can result in fairly
         *   un-random-looking sequences when repeats are suppressed, because
         *   the available number of permutations drops significantly.
         */
        suppressRepeats = true

        /* process the next step of the script */
        doScript()
        {
            /* cache the lists to avoid repeated side effects */
            local firstLst = firstEvents;
            local firstLen = firstLst.length();
            local lst = eventList;
            eventListLen = lst.length();

            /* process the script step only if the event odds allow it */
            if (!checkEventOdds())
                return;

            /*
             *   States 1..N, where N is the number of elements in the
             *   firstEvents list, simply show the firstEvents elements in
             *   order.
             *
             *   If we're set to shuffle the main eventList list initially, all
             *   states above N simply show elements from the eventList list in
             *   shuffled order.
             *
             *   If we're NOT set to shuffle the main eventList list initially,
             *   the following apply:
             *
             *   States N+1..N+M, where M is the number of elements in the
             *   eventList list, show the eventList elements in order.
             *
             *   States above N+M show elements from the eventList list in
             *   shuffled order.
             */
            local evt;
            if (curScriptState <= firstLen)
            {
                /* simply fetch the next string from firstEvents */
                evt = firstEvents[curScriptState++];
            }
            else if (!shuffleFirst && curScriptState <= firstLen + eventListLen)
            {
                /* fetch the next string from eventList */
                evt = lst[curScriptState++ - firstLen];
            }
            else
            {
                /* we're showing shuffled strings from the eventList list */
                evt = lst[getNextRandom()];
            }

            /* execute the event */
            doScriptEvent(evt);
        }

        /*
         *   Get the next random event.  We'll pick an event from our list of
         *   events using a ShuffledIntegerList to ensure we pick each value
         *   once before re-using any values.
         */
        getNextRandom()
        {
            /* if we haven't created our shuffled list yet, do so now */
            if (shuffledList_ == nil)
            {
                /*
                 *   create a shuffled integer list - we'll use these shuffled
                 *   integers as indices into our event list
                 */
                shuffledList_ = new ShuffledIntegerList(1, eventListLen);

                /* apply our suppressRepeats option to the shuffled list */
                shuffledList_.suppressRepeats = suppressRepeats;
            }

            /* ask the shuffled list to pick an element */
            return shuffledList_.getNextValue();
        }

        /* our ShuffledList - we'll initialize this on demand */
        shuffledList_ = nil
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   Shuffled List - this class keeps a list of values that can be returned
     *   in random order, but with the constraint that we never repeat a value
     *   until we've handed out every value.  Think of a shuffled deck of
     *   cards: the order of the cards handed out is random, but once a card is
     *   dealt, it can't be dealt again until we put everything back into the
     *   deck and reshuffle.
     */
    class ShuffledList: object
        /*
         *   the list of values we want to shuffle - initialize this in each
         *   instance to the set of values we want to return in random order
         */
        valueList = []

        /*
         *   Flag: suppress repeated values.  We mostly suppress repeats by our
         *   very design, since we run through the entire list before repeating
         *   anything in the list.  However, there's one situation (in a list
         *   with more than one element) where a repeat can occur: immediately
         *   after a shuffle, we could select the last element from the
         *   previous shuffle as the first element of the new shuffle.  If this
         *   flag is set, we'll suppress this type of repeat by choosing again
         *   any time we're about to choose a repeat.
         *
         *   Note that we ignore this for a list of one element, since it's
         *   obviously impossible to avoid repeats in this case.  We also
         *   ignore it for a two-element list, since this would produce the
         *   predictable pattern A-B-A-B..., defeating the purpose of the
         *   shuffle.
         */
        suppressRepeats = nil

        /* create from a given list */
        construct(lst)
        {
            /* remember our list of values */
            valueList = lst;
        }

        /*
         *   Get a random value.  This will return a randomly-selected element
         *   from 'valueList', but we'll return every element of 'valueList'
         *   once before repeating any element.
         *
         *   If we've returned every value on the current round, we'll
         *   automatically shuffle the values and start a new round.
         */
        getNextValue()
        {
            local i;
            local ret;
            local justReshuffled = nil;

            /* if we haven't initialized our vector, do so now */
            if (valuesVec == nil)
            {
                /* create the vector */
                valuesVec = new Vector(valueList.length(), valueList);

                /* all values are initially available */
                valuesAvail = valuesVec.length();
            }

            /* if we've exhausted our values on this round, start over */
            if (valuesAvail == 0)
            {
                /* shuffle the elements */
                reshuffle();

                /* note that we just did a shuffle */
                justReshuffled = true;
            }

            /* pick a random element from the 'available' partition */
            i = rand(valuesAvail) + 1;

            /*
             *   If we just reshuffled, and we're configured to suppress a
             *   repeat immediately after a reshuffle, and we chose the first
             *   element of the vector, and we have at least three elements,
             *   choose a different element.  The first element in the vector is
             *   always the last element we return from each run-through, since
             *   the 'available' partition is at the start of the list and thus
             *   shrinks down until it contains only the first element.
             *
             *   If we have one element, there's obviously no point in trying to
             *   suppress repeats.  If we have two elements, we *still* don't
             *   want to suppress repeats, because in this case we'd generate a
             *   predicatable A-B-A-B pattern (because we could never have two
             *   A's or two B's in a row).
             */
            if (justReshuffled && suppressRepeats && valuesAvail > 2)
            {
                /*
                 *   we don't want repeats, so choose anything besides the
                 *   first element; keep choosing until we get another element
                 */
                while (i == 1)
                    i = rand(valuesAvail) + 1;
            }

            /* remember the element we're returning */
            ret = valuesVec[i];

            /*
             *   Move the value at the top of the 'available' partition down
             *   into the hole we're creating at 'i', since we're about to
             *   reduce the size of the 'available' partition to reflect the
             *   use of one more value; that would leave the element at the top
             *   of the partition homeless, so we need somewhere to put it.
             *   Luckily, we also need to delete element 'i', since we're using
             *   this element.  Solve both problems at once by moving element
             *   we're rendering homeless into the hole we're creating.
             */
            valuesVec[i] = valuesVec[valuesAvail];

            /* move the value we're returning into the top slot */
            valuesVec[valuesAvail] = ret;

            /* reduce the 'available' partition by one */
            --valuesAvail;

            /* return the result */
            return ret;
        }

        /*
         *   Shuffle the values.  This puts all of the values back into the
         *   deck (as it were) for a new round.  It's never required to call
         *   this, because getNextValue() automatically shuffles the deck and
         *   starts over each time it runs through the entire deck.  This is
         *   provided in case the caller has a reason to want to put all the
         *   values back into play immediately, before every value has been
         *   dealt on the current round.
         */
        reshuffle()
        {
            /*
             *   Simply reset the counter of available values.  Go with the
             *   original source list's length, in case we haven't initialized
             *   our internal vector yet.
             */
            valuesAvail = valueList.length();
        }

        /*
         *   Internal vector of available/used values.  Elements from 1 to
         *   'valuesAvail', inclusive, are still available for use on this
         *   round.  Elements above 'valuesAvail' have already been used.
         */
        valuesVec = nil

        /* number of values still available on this round */
        valuesAvail = 0
    ;

    /*
     *   A Shuffled Integer List is a special kind of Shuffled List that
     *   returns integers in a given range.  Like an ordinary Shuffled List,
     *   we'll return integers in the given range in random order, but we'll
     *   only return each integer once during a given round; when we exhaust
     *   the supply, we'll reshuffle the set of integers and start over.
     */
    class ShuffledIntegerList: ShuffledList
        /*
         *   The minimum and maximum values for our range.  Instances should
         *   define these to the range desired.
         */
        rangeMin = 1
        rangeMax = 10

        /* initialize the value list on demand */
        valueList = nil

        /* construct with the given range */
        construct(rmin, rmax)
        {
            rangeMin = rmin;
            rangeMax = rmax;
        }

        /* get the next value */
        getNextValue()
        {
            /*
             *   If we haven't set up our value list yet, do so now.  This is
             *   simply a list of integers from rangeMin to rangeMax.
             */
            if (valueList == nil)
            {
                local ele = rangeMin;
                valueList = List.generate({i: ele++}, rangeMax - rangeMin + 1);
            }

            /* use the inherited handling to select from our value list */
            return inherited();
        }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   Library global variables
     */
    libGlobal: object
        /*
         *   The current library messages object.  This is the source object
         *   for messages that don't logically relate to the actor carrying out
         *   the comamand.  It's mostly used for meta-command replies, and for
         *   text fragments that are used to construct descriptions.
         *
         *   This message object isn't generally used for parser messages or
         *   action replies - most of those come from the objects given by the
         *   current actor's getParserMessageObj() or getActionMessageObj(),
         *   respectively.
         *
         *   By default, this is set to libMessages.  The library never changes
         *   this itself, but a game can change this if it wants to switch to a
         *   new set of messages during a game.  (If you don't need to change
         *   messages during a game, but simply want to customize some of the
         *   default messages, you don't need to set this variable - you can
         *   simply use 'modify libMessages' instead.  This variable is
         *   designed for cases where you want to *dynamically* change the
         *   standard messages during the game.)
         */
        libMessageObj = libMessages

        /*
         *   Sense cache - we keep SenseInfo lists here, keyed by [pov,sense];
         *   we normally discard the cached information at the start of each
         *   turn, and disable caching entirely at the start of the "action"
         *   phase of each turn.  We leave caching disabled during each turn's
         *   action phase because this is the phase where simulation state
         *   changes are typically made, and hence it would be difficult to
         *   keep the cache coherent during this phase.
         *
         *   When this is nil, it indicates that caching is disabled.  We only
         *   allow caching during certain phases of execution, when game state
         *   is not conventionally altered, so that we don't have to do a lot
         *   of work to keep the cache up to date.
         */
        senseCache = nil

        /*
         *   Can-Touch cache - we keep CanTouchInfo entries here, keyed by
         *   [from,to].  This cache is the touch-path equivalent of the sense
         *   cache, and is enabled and disabled
         */
        canTouchCache = nil

        /*
         *   Connection list cache - this is a cache of all of the objects
         *   connected by containment to a given object.
         */
        connectionCache = nil

        /*
         *   Actor visual ambient cache - this keeps track of the ambient light
         *   level at the given actor.
         */
        actorVisualAmbientCache = nil

        /* enable the cache, clearing any old cached information */
        enableSenseCache()
        {
            /* create a new, empty lookup table for the sense cache */
            senseCache = new LookupTable(32, 64);

            /* create the can-touch cache */
            canTouchCache = new LookupTable(32, 64);

            /* create the actor visual ambient cache */
            actorVisualAmbientCache = new LookupTable(32, 64);

            /* create a connection list cache */
            connectionCache = new LookupTable(32, 64);
        }

        /* disable the cache */
        disableSenseCache()
        {
            /* forget the cache tables */
            senseCache = nil;
            canTouchCache = nil;
            actorVisualAmbientCache = nil;
            connectionCache = nil;
        }

        /*
         *   Invalidate the sense cache.  This can be called if something
         *   happens during noun resolution or verification that causes any
         *   cached sense information to become out of date.  For example, if
         *   you have to create a new game-world object during noun-phrase
         *   resolution, this should be called to ensure that the new object's
         *   visibility is properly calculated and incorporated into the cached
         *   information.
         */
        invalSenseCache()
        {
            /* remember whether or not caching is currently enabled */
            local wasEnabled = (senseCache != nil);

            /* clear the cache by disabling it */
            disableSenseCache();

            /* if the cache was previously enabled, re-enable it */
            if (wasEnabled)
                enableSenseCache();
        }

        /*
         *   List of all of the senses.  The library pre-initializer will load
         *   this list with a reference to each instance of class Sense.
         */
        allSenses = []

        /*
         *   The current player character
         */
        playerChar = nil

        /*
         *   The current perspective actor.  This is the actor who's performing
         *   the action (LOOK AROUND, EXAMINE, SMELL, etc) that's generating
         *   the current description.
         */
        pointOfViewActor = nil

        /*
         *   The current perspective object.  This is *usually* the actor
         *   performing the current command, but can be a different object when
         *   the actor is viewing the location being described via an
         *   intermediary, such as through a closed-circuit TV camera.
         */
        pointOfView = nil

        /*
         *   The stack of point of view objects.  The last element of the
         *   vector is the most recent point of view after the current point
         *   of view.
         */
        povStack = static new Vector(32)

        /*
         *   The global score object.  We use a global for this, rather than
         *   referencing libScore directly, to allow the score module to be
         *   left out entirely if the game doesn't make use of scoring.  The
         *   score module should set this during pre-initialization.
         */
        scoreObj = nil

        /*
         *   The global Footnote class object.  We use a global for this,
         *   rather than referencing Footnote directly, to allow the footnote
         *   module to be left out entirely if the game doesn't make use of
         *   footnotes.  The footnote class should set this during
         *   pre-initialization.
         */
        footnoteClass = nil

        /* the total number of turns so far */
        totalTurns = 0

        /*
         *   flag: the parser is in 'debug' mode, in which it displays the
         *   parse tree for each command entered
         */
        parserDebugMode = nil

        /*
         *   Most recent command, for 'undo' purposes.  This is the last
         *   command the player character performed, or the last initial
         *   command a player directed to an NPC.
         *
         *   Note that if the player directed a series of commands to an NPC
         *   with a single command line, only the first command on such a
         *   command line is retained here, because it is only the first such
         *   command that counts as a player's turn in terms of the game
         *   clock.  Subsequent commands are executed by the NPC's on the
         *   NPC's own time, and do not count against the PC's game clock
         *   time.  The first command counts against the PC's clock because of
         *   the time it takes the PC to give the command to the NPC.
         */
        lastCommandForUndo = ''

        /*
         *   Most recent target actor phrase; this goes with
         *   lastCommandForUndo.  This is nil if the last command did not
         *   specify an actor (i.e., was implicitly for the player character),
         *   otherwise is the string the player typed specifying a target
         *   actor.
         */
        lastActorForUndo = ''

        /*
         *   Current command information.  We keep track of the current
         *   command's actor and action here, as well as the verification
         *   result list and the command report list.
         */
        curActor = nil
        curIssuingActor = nil
        curAction = nil
        curVerifyResults = nil

        /* the exitLister object, if included in the build */
        exitListerObj = nil

        /* the hint manager, if included in the build */
        hintManagerObj = nil

        /*
         *   The game's IFID, as defined in the game's main module ID object.
         *   If the game has multiple IFIDs in the module list, this will store
         *   only the first IFID in the list.  NOTE: the library initializes
         *   this automatically during preinit; don't set this manually.
         */
        IFID = nil

        /*
         *   Command line arguments.  The library sets this to a list of
         *   strings containing the arguments passed to the program on the
         *   command line.  This list contains the command line arguments
         *   parsed according to the local conventions for the operating system
         *   and C++ library.  The standard parsing procedure used by most
         *   systems is to break the line into tokens delimited by space
         *   characters.  Many systems also allow space characters to be
         *   embedded in tokens by quoting the tokens.  The first argument is
         *   always the name of the .t3 file currently executing.
         */
        commandLineArgs = []

        /*
         *   Retrieve a "switch" from the command line.  Switches are options
         *   specifies with the conventional Unix "-xxx" notation.  This
         *   searches for a command option that equals the given string or
         *   starts with the given substring.  If we find it, we return the
         *   part of the option after the given substring - this is
         *   conventionally the value of the switch.  For example, the command
         *   line might look like this:
         *
         *.    t3run mygame.t3 -name=MyGame -user=Bob
         *
         *   Searching for '-name=' would return 'MyGame', and searching for
         *   '-user=' would return' Bob'.
         *
         *   If the switch is found but has no value attached, the return value
         *   is an empty string.  If the switch isn't found at all, the return
         *   value is nil.
         */
        getCommandSwitch(s)
        {
            /* search from argument 2 to the last switch argument */
            local args = commandLineArgs;
            for (local i in 2..args.length())
            {
                /*
                 *   if this isn't a switch, or is the special "-" last switch
                 *   marker, we're done
                 */
                local a = args[i];
                if (!a.startsWith('-') || a == '-')
                    return nil;

                /* check for a match */
                if (a.startsWith(s))
                    return a.substr(s.length() + 1);
            }

            /* didn't find it */
            return nil;
        }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   FinishType objects are used in finishGameMsg() to indicate what kind
     *   of game-over message to display.  We provide a couple of standard
     *   objects for the most common cases.
     */
    class FinishType: object
        /* the finishing message, as a string or library message property */
        finishMsg = nil
    ;

    /* 'death' - the game has ended due to the player character's demise */
    ftDeath: FinishType finishMsg = &finishDeathMsg;

    /* 'victory' - the player has won the game */
    ftVictory: FinishType finishMsg = &finishVictoryMsg;

    /* 'failure' - the game has ended in failure (but not necessarily death) */
    ftFailure: FinishType finishMsg = &finishFailureMsg;

    /* 'game over' - the game has simply ended */
    ftGameOver: FinishType finishMsg = &finishGameOverMsg;

    /*
     *   Finish the game, showing a message explaining why the game has ended.
     *   This can be called when an event occurs that ends the game, such as
     *   the player character's death, winning, or any other endpoint in the
     *   story.
     *
     *   We'll show a message defined by 'msg', using a standard format.  The
     *   format depends on the language, but in English, it's usually the
     *   message surrounded by asterisks: "*** You have won! ***".  'msg' can
     *   be:
     *
     *.    - nil, in which case we display nothing
     *.    - a string, which we'll display as the message
     *.    - a FinishType object, from which we'll get the message
     *
     *   After showing the message (if any), we'll prompt the user with
     *   options for how to proceed.  We'll always show the QUIT, RESTART, and
     *   RESTORE options; other options can be offered by listing one or more
     *   FinishOption objects in the 'extra' parameter, which is given as a
     *   list of FinishOption objects.  The library defines a few non-default
     *   finish options, such as finishOptionUndo and finishOptionCredits; in
     *   addition, the game can subclass FinishOption to create its own custom
     *   options, as desired.
     */
    finishGameMsg(msg, extra)
    {
        local lst;

        /*
         *   Adjust the turn counter to take into account the action currently
         *   in progress, if any, and to reflect any turns that the player
         *   character has already completed and which aren't yet reflected in
         *   the turn counter.  If we're processing a daemon, the PC's next
         *   schedulable run time will already reflect the last turn the PC
         *   completed, but the global turn counter won't be there yet, since
         *   we're still scheduling daemons that were ready to run on the same
         *   turn as the player's last action.
         */
        libGlobal.totalTurns = gPlayerChar.nextRunTime + gAction.actionTime;

        /*
         *   Explicitly run any final score notification now.  This will ensure
         *   that any points awarded in the course of the final command that
         *   brought us to this point will generate the usual notification, and
         *   that the notification will appear at a reasonable place, just
         *   before the termination message.
         */
        if (libGlobal.scoreObj != nil)
            libGlobal.scoreObj.runScoreNotifier();

        /* translate the message, if specified */
        if (dataType(msg) == TypeObject)
        {
            /* it's a FinishType object - get its message property or string */
            msg = msg.finishMsg;

            /* if it's a library message property, look it up */
            if (dataType(msg) == TypeProp)
                msg = gLibMessages.(msg);
        }

        /* if we have a message, display it */
        if (msg != nil)
            gLibMessages.showFinishMsg(msg);

        /* if the extra options include a scoring option, show the score */
        if (extra != nil && extra.indexWhich({x: x.showScoreInFinish}) != nil)
        {
            "<.p>";
            libGlobal.scoreObj.showScore();
            "<.p>";
        }

        /*
         *   Since we need to interact directly with the player, any sense
         *   context currently in effect is now irrelevant.  Reset the sense
         *   context by setting the 'source' object to nil to indicate that we
         *   don't need any sense blocking at all.  We can just set the context
         *   directly, since this routine will never return into the
         *   surrounding command processing - we always either terminate the
         *   program or proceed to a different game context (via undo, restore,
         *   restart, etc).  By the same token, the actor we're talking to now
         *   is the player character.
         */
        senseContext.setSenseContext(nil, sight);
        gActor = gPlayerChar;

        /* start with the standard options */
        lst = [finishOptionRestore, finishOptionRestart];

        /* add any additional options in the 'extra' parameter */
        if (extra != nil)
            lst += extra;

        /* always add 'quit' as the last option */
        lst += finishOptionQuit;

        /* process the options */
        processOptions(lst);
    }

    /* finish the game, offering the given extra options but no message */
    finishGame(extra)
    {
        finishGameMsg(nil, extra);
    }

    /*
     *   Show failed startup restore options.  If a restore operation fails at
     *   startup, we won't just proceed with the game, but ask the user what
     *   they want to do; we'll offer the options of restoring another game,
     *   quitting, or starting the game from the beginning.
     */
    failedRestoreOptions()
    {
        /* process our set of options */
        processOptions([restoreOptionRestoreAnother, restoreOptionStartOver,
                        finishOptionQuit]);
    }

    /*
     *   Process a list of finishing options.  We'll loop, showing prompts and
     *   reading responses, until we get a response that terminates the loop.
     */
    processOptions(lst)
    {
        /* keep going until we get a valid response */
    promptLoop:
        for (;;)
        {
            local resp;

            /* show the options */
            finishOptionsLister.showListAll(lst, 0, 0);

            /* switch to before-command mode for reading the interactive input */
            "<.commandbefore>";

            /*
             *   update the status line, in case the score or turn counter has
             *   changed (this is especially likely when we first enter this
             *   loop, since we might have just finished the game with our
             *   previous action, and that action might well have awarded us
             *   some points)
             */
            statusLine.showStatusLine();

            /* read a response */
            resp = inputManager.getInputLine(nil, nil);

            /* switch to command-after mode */
            "<.commandafter>";

            /* check for a match to each of the options in our list */
            foreach (local cur in lst)
            {
                /* if this one matches, process the option */
                if (cur.responseMatches(resp))
                {
                    /* it matches - carry out the option */
                    if (cur.doOption())
                    {
                        /*
                         *   they returned true - they want to continue asking
                         *   for more options
                         */
                        continue promptLoop;
                    }
                    else
                    {
                        /*
                         *   they returned nil - they want us to stop asking
                         *   for options and return to our caller
                         */
                        return;
                    }
                }
            }

            /*
             *   If we got this far, it means that we didn't get a valid
             *   option.  Display our "invalid option" message, and continue
             *   looping so that we show the prompt again and read a new
             *   option.
             */
            gLibMessages.invalidFinishOption(resp);
        }
    }

    /*
     *   Finish Option class.  This is the base class for the abstract objects
     *   representing options offered by finishGame.
     */
    class FinishOption: object
        /*
         *   The description, as displayed in the list of options.  For the
         *   default English messages, this is expected to be a verb phrase in
         *   infinitive form, and should show the keyword accepted as a
         *   response in all capitals: "RESTART", "see some AMUSING things to
         *   do", "show CREDITS".
         */
        desc = ""

        /*
         *   By default, the item is listed.  If you want to create an
         *   invisible option that's accepted but which isn't listed in the
         *   prompt, just set this to nil.  Invisible options are sometimes
         *   useful when the output of one option mentions another option; for
         *   example, the CREDITS message might mention a LICENSE command for
         *   displaying the license, so you want to make that command available
         *   without cluttering the prompt with it.
         */
        isListed = true

        /* our response keyword */
        responseKeyword = ''

        /*
         *   a single character we accept as an alternative to our full
         *   response keyword, or nil if we don't accept a single-character
         *   response
         */
        responseChar = nil

        /*
         *   Match a response string to this option.  Returns true if the
         *   string matches our response, nil otherwise.  By default, we'll
         *   return true if the string exactly matches responseKeyword or
         *   exactly matches our responseChar (if that's non-nil), but this
         *   can be overridden to match other strings if desired.  By default,
         *   we'll match the response without regard to case.
         */
        responseMatches(response)
        {
            /* do all of our work in lower-case */
            response = response.toLower();

            /*
             *   check for a match the full response keyword or to the single
             *   response character
             */
            return (response == responseKeyword.toLower()
                    || (responseChar != nil
                        && response == responseChar.toLower()));
        }

        /*
         *   Carry out the option.  This is called when the player enters a
         *   response that matches this option.  This routine must perform the
         *   action of the option, then return true to indicate that we should
         *   ask for another option, or nil to indicate that the finishGame()
         *   routine should simply return.
         */
        doOption()
        {
            /* tell finishGame() to ask for another option */
            return true;
        }

        /*
         *   Flag: show the score with the end-of-game announcement.  If any
         *   option in the list of finishing options has this flag set, we'll
         *   show the score using the same message that the SCORE command
         *   uses.
         */
        showScoreInFinish = nil
    ;

    /*
     *   QUIT option for finishGame.  The language-specific code should modify
     *   this to specify the description and response keywords.
     */
    finishOptionQuit: FinishOption
        doOption()
        {
            /*
             *   carry out the Quit action - this will signal a
             *   QuittingException, so this call will never return
             */
            QuitAction.terminateGame();
        }
    ;

    /*
     *   RESTORE option for finishGame.
     */
    finishOptionRestore: FinishOption
        doOption()
        {
            /*
             *   Try restoring.  If this succeeds (i.e., it returns true), tell
             *   the caller to stop looping and to proceed with the game by
             *   returning nil.  If this fails, tell the caller to keep looping
             *   by returning true.
             */
            if (RestoreAction.askAndRestore())
            {
                /*
                 *   we succeeded, so we're now restored to some prior game
                 *   state - terminate any remaining processing in the command
                 *   that triggered the end-of-game options
                 */
                throw new TerminateCommandException();
            }
            else
            {
                /* it failed - tell the caller to keep looping */
                return true;
            }
        }
    ;

    /*
     *   RESTART option for finishGame
     */
    finishOptionRestart: FinishOption
        doOption()
        {
            /*
             *   carry out the restart - this will not return, since we'll
             *   reset the game state and re-enter the game at the restart
             *   entrypoint
             */
            RestartAction.doRestartGame();
        }
    ;

    /*
     *   START FROM BEGINNING option for failed startup restore.  This is just
     *   like finishOptionRestart, but shows a different option name.
     */
    restoreOptionStartOver: finishOptionRestart
    ;

    /*
     *   RESTORE ANOTHER GAME option for failed startup restore.  This is just
     *   like finishOptionRestore, but shows a different option name.
     */
    restoreOptionRestoreAnother: finishOptionRestore
    ;

    /*
     *   UNDO option for finishGame
     */
    finishOptionUndo: FinishOption
        doOption()
        {
            /* try performing the undo */
            if (UndoAction.performUndo(nil))
            {
                /* act as though UNDO were the last actual command, for AGAIN */
                AgainAction.saveForAgain(gPlayerChar, gPlayerChar,
                                         nil, UndoAction);

                /*
                 *   Success - terminate the current command with no further
                 *   processing.
                 */
                throw new TerminateCommandException();
            }
            else
            {
                /*
                 *   failure - show a blank line and tell the caller to ask
                 *   for another option, since we couldn't carry out this
                 *   option
                 */
                "<.p>";
                return true;
            }
        }
    ;

    /*
     *   FULL SCORE option for finishGame
     */
    finishOptionFullScore: FinishOption
        doOption()
        {
            /* show a blank line before the score display */
            "\b";

            /* run the Full Score action */
            FullScoreAction.showFullScore();

            /* show a paragraph break after the score display */
            "<.p>";

            /*
             *   this option has now had its full effect, so tell the caller
             *   to go back and ask for a new option
             */
            return true;
        }

        /*
         *   by default, show the score with the end-of-game announcement when
         *   this option is included
         */
        showScoreInFinish = true
    ;

    /*
     *   Option to show the score in finishGame.  This doesn't create a listed
     *   option in the set of offered options, but rather is simply a flag to
     *   finishGame() that the score should be announced along with the
     *   end-of-game announcement message.
     */
    finishOptionScore: FinishOption
        /* show the score in the end-of-game announcement */
        showScoreInFinish = true

        /* this is not a listed option */
        isListed = nil

        /* this option isn't selectable, so it has no effect */
        doOption() { }
    ;

    /*
     *   CREDITS option for finishGame
     */
    finishOptionCredits: FinishOption
        doOption()
        {
            /* show a blank line before the credits */
            "\b";

            /* run the Credits action */
            CreditsAction.execSystemAction();

            /* show a paragraph break after the credits */
            "<.p>";

            /*
             *   this option has now had its full effect, so tell the caller
             *   to go back and ask for a new option
             */
            return true;
        }
    ;

    /*
     *   AMUSING option for finishGame
     */
    finishOptionAmusing: FinishOption
        /*
         *   The game must modify this object to define a doOption method.  We
         *   have no built-in way to show a list of amusing things to try, so
         *   if a game wants to offer this option, it must provide a suitable
         *   definition here.  (We never offer this option by default, so a
         *   game need not provide a definition if the game doesn't explicitly
         *   offer this option via the 'extra' argument to finishGame()).
         */
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   The settings user interface.  This is a subclass of the Settings
     *   Manager that adds a command-line user interface, particularly to allow
     *   the user to view, save, and load the default settings.
     *
     *   Our user interface consists mainly of a pair of special commands: SAVE
     *   DEFAULTS and RESTORE DEFAULTS.  The SAVE DEFAULTS command tells the
     *   library to write out all of the current settings (at least, all of
     *   those that participate in this framework) to a file.  RESTORE DEFAULTS
     *   explicitly reads that same file and puts the stored settings into
     *   effect.  Finally, we'll also read the file and activate its stored
     *   settings when we start (or RESTART) the game.
     *
     */
    settingsUI: settingsManager
        /* display all of the current settings */
        showAll()
        {
            local first = true;

            /* loop over all SettingsItem instances */
            forEachInstance(SettingsItem, function(item)
            {
                /* include only items that want to participate in the listing */
                if (item.includeInListing)
                {
                    /* add a separator if this isn't the first one */
                    if (!first)
                        gLibMessages.settingsItemSeparator;

                    /* show this item's description */
                    item.settingDesc;

                    /* it's no longer the first */
                    first = nil;
                }
            });
        }

        /*
         *   Save settings, and display an acknowledgment message (or an error
         *   message, if necessary) for the user's edification.
         */
        saveSettingsMsg()
        {
            /* catch any errors */
            try
            {
                /* save the settings */
                saveSettings();

                /* if we got this far, declare success */
                gLibMessages.savedDefaults();
            }
            catch (Exception exc)
            {
                /* we couldn't open the file */
                gLibMessages.defaultsFileWriteError;
            }
        }

        /*
         *   Restore settings, and display an acknowledgment or error message,
         *   as appropriate.
         */
        restoreSettingsMsg()
        {
            /* catch any errors */
            try
            {
                /* restore the settings */
                restoreSettings();

                /* if we got this far, declare success */
                gLibMessages.restoredDefaults();
            }
            catch (SettingsNotSupportedException sns)
            {
                /* this interpreter doesn't support the settings file */
                gLibMessages.defaultsFileNotSupported;
            }
            catch (Exception exc)
            {
                /* display other errors */
                gLibMessages.defaultsFileReadError(exc);
            }
        }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   Utility functions
     */

    /*
     *   nilToList - convert a 'nil' value to an empty list.  This can be
     *   useful for mix-in classes that will be used in different inheritance
     *   contexts, since the classes might or might not inherit a base class
     *   definition for list-valued methods such as preconditions.  This
     *   provides a usable default for list-valued methods that return nothing
     *   from superclasses.
     */
    nilToList(val)
    {
        return (val != nil ? val : []);
    }

    /*
     *   partitionList - partition a list into a pair of two lists, the first
     *   containing items that match the predicate 'fn', the second containing
     *   items that don't match 'fn'.  'fn' is a function pointer (usually an
     *   anonymous function) that takes a single argument - a list element -
     *   and returns true or nil.
     *
     *   The return value is a list with two elements.  The first element is a
     *   list giving the elements of the original list for which 'fn' returns
     *   true, the second element is a list giving the elements for which 'fn'
     *   returns nil.
     *
     *   (Contributed by Tommy Nordgren.)
     */
    partitionList(lst, fn)
    {
        local lst1 = lst.subset(fn);
        local lst2 = lst.subset({x : !fn(x)});

        return [lst1, lst2];
    }

    /*
     *   Determine if list a is a subset of list b.  a is a subset of b if
     *   every element of a is in b.
     */
    isListSubset(a, b)
    {
        /* a can't be a subset if it has more elements than b */
        if (a.length() > b.length())
            return nil;

        /* check each element of a to see if it's also in b */
        foreach (local cur in a)
        {
            /* if this element of a is not in b, a is not a subset of b */
            if (b.indexOf(cur) == nil)
                return nil;
        }

        /*
         *   we didn't find any elements of a that are not also in b, so a is a
         *   subset of b
         */
        return true;
    }

TADS 3 Library Manual
Generated on 8/21/2012 from TADS version 3.1.2