misc.t

documentation
#charset "us-ascii"

/* 
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
 *   Adapted for adv3Lite by Eric Eve
 *   
 *   adv3Lite 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 "advlite.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()
    {
        /*   Create an action context in case any startup code needs it */
        gAction = Look.createInstance();
        gActor = initialPlayerChar;

        
        /* 
         *   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 */
        DMsg(note main restore, 'Game restored.<.p>');

        /* try restoring it */
        succ = Restore.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.outermostVisibleParent().roomTitle + '; ';
        
        /* 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

    
    /*  
     *   If this flag is true then room description listings and examine
     *   listings use a parenthetical style to show subcontents (e.g. "On the
     *   table you see a box (in which is a brass key)") instead of showing each
     *   item and its contents in a separate paragraph.
     */    
    useParentheticalListing = nil
    
    /* 
     *   If this flag is true then room description listings will include a
     *   paragraph break between each set of subcontents listings (i.e. the
     *   listing of the contents of each item in the room that has visible
     *   contents). If it is nil the subcontents listings will all be run into a
     *   single paragraph. Note that the global setting defined here can be
     *   overridden on individual rooms.
     */
    paraBrksBtwnSubcontents = 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

    
    /*
     *   Should the "before" notifications (beforeAction, roomBeforeAction, and
     *   actorAction) run before or after the "check" phase?
     *
     *   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 = nil
    
    /* 
     *   Flag, should this game be in the past tense. By default the game is in
     *   the present tense.
     *
     *   For a wider selection of tenses override Narrator.tense instead.
     */
    usePastTense = nil
        
    /*   
     *   The AGAIN command could be interpreted in two different ways. It could
     *   repeat the resolved action (using precisely the same objects as
     *   before), or it could act as if the player had retyped the command and
     *   then parse it again from scratch (which might result in a different
     *   interpretation of the command, or different objects being selected).
     *   The former interpretation is used if againRepeatsParse is nil; the
     *   latter if it's true.
     */    
    againRepeatsParse = true
    
    /*   
     *   Flag. If this is true the game attempts to switch the againRepeatsParse
     *   flag between true and nil to give the contextually better
     *   interpretation of AGAIN. This should be regarded as somewhat
     *   experimental for now.
     */
    autoSwitchAgain = true    
    
    /* 
     *   Is this game in verbose mode? By default we make it so, but players can
     *   change this with the BRIEF/TERSE command.
     */
    verbose = true
    
    /* 
     *   Is this game in fast GO TO mode? By default we make it not, so that the
     *   GO TO command moves the player character only one step of the way at a
     *   time, but if this is set to true the GO TO command will keep moving the
     *   player until either the destination is reached or an obstacle is
     *   encountered.
     */
    fastGoTo = nil
      
;

/* ------------------------------------------------------------------------ */
/*
 *   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()
{    /* clear the screen */
    aioClearScreen();
}




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

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

        

        /* 
         *   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(cquoteOutputFilter);

        mainOutputStream.addOutputFilter(commandSequencer);

        /* 
         *   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);


        statusLeftOutputStream.addOutputFilter(styleTagFilter);
        statusLeftOutputStream.addOutputFilter(cquoteOutputFilter);
        statusRightOutputStream.addOutputFilter(styleTagFilter);

    }


;
//
/* ------------------------------------------------------------------------ */
/*
 *   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.
 *
 *   We define Script in misc.t rather than eventList.t so that other parts of
 *   the library can safely test whether something is ofKind(Script) even it
 *   eventList.t is not present. The various types and subclasses of script are
 *   defined in eventList.t to allow them to be optionally excluded from the
 *   build if they're not needed in a particular game.
 */
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
;
/* ------------------------------------------------------------------------ */
/*
 *   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    
   
    /*
     *   The current player character. If it hasn't already been defined, we call getPlayerChar to
     *   identify it then set ourself to the value it returns.
     */
    playerChar() 
    {
        /* Get our player character */
        local pc = getPlayerChar();
        
        /* Set ourself to the player character */
        playerChar = pc;
        
        /* Return the player character. */
        return pc;
    }
  

    /*   The name of the current player character */
    playerCharName = nil
    
    /* 
     *   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 = ''
    
    /*   The text of the last command to be repeated by Again */    
    lastCommandForAgain = ''

    /*
     *   Current command information.  We keep track of the current
     *   command's actor and action here.  
     */
    curActor = nil
    curIssuingActor = nil
    curAction = nil
    
    /* The current Command object */
    curCommand = nil

    /* The last action to be performed. */
    lastAction = nil
    
    /* The previous Command object */
    lastCommand = nil
    
    /* the exitLister object, if included in the build */
    exitListerObj = nil

    /* the hint manager, if included in the build */
    hintManagerObj = nil
    
    /* the extra hint manager, if included in the build */
    extraHintManagerObj = 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;
    }

    /* 
     *   The last location visited by the player char before a travel action.
     *   Noted to allow travel back.
     */    
    lastLoc = nil
    
   
    /* 
     *   A lookup table to store information about the destinations of direction
     *   properties not connected to objects (i.e. direction properties defined
     *   as strings or methods
     */    
    extraDestInfo = static [ * -> unknownDest_ ]

    /* 
     *   Add an item to the extraDestInfo table keyed on the source room plus
     *   the direction taken, with the value being the destination arrived at
     *   (which most of the time will probably be the same as the source, since
     *   in most cases where we create one of these records, no travel will have
     *   taken place.
     */    
    addExtraDestInfo(source, dirn, dest)
    {
        if(extraDestInfo == nil)
            extraDestInfo = [ * -> unknownDest_ ];
        
        /* 
         *   Record the extra dest info in the extraDestInfo table unless it's
         *   already set to nil, which is a signal that we don't want the
         *   pathfinder or other code to use this information.
         */
        
        if(extraDestInfo[[source, dirn]] not in (nil, varDest_))
           extraDestInfo[[source, dirn]] = dest;
            
    }
    
    /* 
     *   Flag: do we want revealing something (through setRevealed or <.reveal> to update PC and NPC
     *   knowledge? By default we do, but games that want to strictly separate Player and Player
     *   Character knowledge may wish to set this to nil.
     */
    informOnReveal = true
    
    /*
     *   Mark a tag as revealed.  This adds an entry for the tag to the revealedNameTab table and to
     *   the informedNamedTab of the player character (who might, in principle, change during the
     *   course of the game). We simply set the table entry to 'true'; the presence of the tag in
     *   the table constitutes the indication that the tag has been revealed.
     *
     *   (Games and library extensions can use 'modify' to override this and store more information
     *   in the table entry.  For example, you could store the time when the information was first
     *   revealed, or the location where it was learned.  If you do override this, just be sure to
     *   set the revealedNameTab entry for the tag to a non-nil and non-zero value, so that any code
     *  
     testing the presence of the table entry will see that the slot is indeed set.)
     *
     *   We put the revealedNameTab table and the setRevealed method here rather than on
     *   conversationManager so that it's available to games that don't include actor.t.
     */
        
    setRevealed(tag, arg?)
    {
        local val = revealedNameTab[tag];
                       
        /* We don't want to overwrite an existing value by accident. */
        if(val == nil)            
            /* Add the tag to our revealedNameTab */
            revealedNameTab[tag] = (arg == nil ? true : arg);
        
        else if(tag != nil)
            revealedNameTab[tag] =  arg;
        
        /* If we're in a conversation, update the last fact mentioned. */
        if(gPlayerChar.currentInterlocutor)
            lastFactMentioned = tag;
        
        /* 
         *   Add the tag to the playerCharacter's informedNameTab, provided we want revealing to
         *   update PC and NPC knowledge.
         */
        if(informOnReveal)
            gPlayerChar.setInformed(tag, arg);
    }
    
    /* 
     *   Do we want the getRevealed(tag) method to return only true or nil, or do we want it to
     *   return the value associated with tag in the revealedNameTab. By default we opt for the
     *   latter, but we provide the other option in case it's needed for backward compatibility.
     */
    revealedTrueOrFalseOnly = nil
    
    /* 
     *   The same option is provided for getInformed(); by default we use the same value as for
     *   revealedTrueOrFalseOnly
     */
    informedTrueOrFalseOnly = revealedTrueOrFalseOnly 
    
    /* Get the value associated with tag. */
    getRevealed(tag) 
    {
        /* Obtained the value associated with tag in the revealedNameTab */
        local val = revealedNameTab[tag];
        
        /* 
         *   Return eiher whether val is not nil, or val itself, according to the value of
         *   revealedTrueOrFalseOnly.
         */
        return revealedTrueOrFalseOnly ? (val != nil) : val;
    }

    /*
     *   Mark a tag as unrevealed.  This removes the entry for the tag from our    
     *   revealedNameTab table.  
     *
     *   We put the revealedNameTab table and the setRevealed method here rather
     *   than on conversationManager so that it's available to games that don't
     *   include actor.t.
     */
    setUnrevealed(tag)
    {
        revealedNameTab.removeElement(tag);
        
//        local tab = gPlayerChar.informedNameTab;
//        
//        if(tab)
//            tab.removeElement[tag];        
        
    }
    
    
    /* 
     *   The global lookup table of all revealed keys.  This table is keyed
     *   by the string naming the revelation; the value associated with
     *   each key is not used (we always just set it to true).  
     */
    revealedNameTab = static new LookupTable(32, 32)
    
    /*  
     *   The symbol table for every game object.
     */    
    objectNameTab = nil
 
    /* The thought manager object, if it exists. */    
    thoughtManagerObj = nil
    
    /* The object last written on */
    lastWrittenOnObj = nil
       
    /* The object last typed on */
    lastTypedOnObj = nil
    
    /* The last (latest) topic mentioned in the current conversation. */
    lastTopicMentioned = nil
    
    /* The last fact mentioned in the current conversation */
    lastFactMentioned = nil
    
    /* The most recent reason a ConvAgendaItem was invoked. */
    reasonInvoked = nil
    
    /* The most recently active agendaItem */
    agendaItem = nil
    
    /* 
     *   our name table for parameter substitutions - a LookupTable that we set
     *   up during preinit
     */
    nameTable_ = static new LookupTable()  
    
    /* 
     *   Flag determining whether inventory listing should be in the wide (nil) or tall (true)
     *   format. By default we start out with the wide format (inventoryTall = nil), although game
     *   code could override this.
     */
    inventoryTall = nil     
    
    /*
     *   Flag: do we wish to present the player with an enumerated list of disambiguation options
     *   (e.g. "Which coin do you mean:(1) the gold coin or (2) the silver coin? to which they can
     *   simply reply 1 or 2). By default we do but game authors can disable this behaviour by
     *   setting this flag to nil.
     */
    enumerateDisambigOptions = true   
    
    /*  
     *   The current number of disambiguation options to choose from. This is for use by the
     *   DisambigPreparser to prevent acceptance of a number out of range.
     */
    disambigLen = 0
   
;



/* object representing an unknown destination */
unknownDest_: Room 'unknown'
;


/* object representing a variable destination */
varDest_: Room 'unknown'
;

/* ------------------------------------------------------------------------ */
/*
 *   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 = BMsg(finish death, 'YOU HAVE DIED');

/* 'victory' - the player has won the game */
ftVictory: FinishType finishMsg = BMsg(finish victory,'YOU HAVE WON');

/* 'failure' - the game has ended in failure (but not necessarily death) */
ftFailure: FinishType finishMsg = BMsg(finish failure, 'YOU HAVE FAILED');

/* 'game over' - the game has simply ended */
ftGameOver: FinishType finishMsg = BMsg(finish game over, 'GAME OVER');

/*
 *   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;

    
    /*
     *   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)
    {
        "<.p>";
        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)
        DMsg(show finish msg, '\b*** {1} ***\b\b', 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>";
    }
    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.show(lst, 0);

        /* 
         *   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();


        /* 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.  
         */       
        DMsg(invalid finish option, '<q>{1}</q> was not one of the
            options.<.p>', 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.  
     */
    listed = 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 
         */
        throw new QuittingException;
    }
    listOrder = 100
    
;

/*
 *   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 (Restore.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
             */

            abort;
        }
        else
        {
            /* it failed - tell the caller to keep looping */
            return true;
        }
    }
    
    listOrder = 90
;

/*
 *   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 
         */
        Restart.doRestartGame();
    }
    
   listOrder = 10
;

/*
 *   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 (Undo.execAction(nil))
        {
            
            
            gPlayerChar.outermostVisibleParent().lookAroundWithin();
            
            
            /* 
             *   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;
        }
    }
    listOrder = 20
;

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

        /* run the Full Score action */
        FullScore.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
    listOrder = 30
   
;

/*
 *   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 */
    listed = nil

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

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

        /* run the Credits action */
        versionInfo.showCredit();

        /* 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;
    }
    
    listOrder = 50
;  

/*
 *   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()).  
     */
   listOrder = 60
;

firstPersonObj: Thing
    person = 1
    name =  BMsg(first person pronoun, 'I')
    globalParamName = 'fpo'
;


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

/* 
 *   Try converting val to an integer. If this results in an integer value,
 *   return it, otherwise return nil.
 */

tryInt(val)
{
    /* 
     *   If the value passed to the function is neither an integer nor a string
     *   nor a BigNumber, return nil, since there can be no valid integer
     *   representation of it.
     */    
    if(dataType(val) not in (TypeInt, TypeSString, TypeObject)
       || (dataType(val) == TypeObject && !(val.ofKind(BigNumber))))
        return nil;
    
    /* Try converting val to an integer. */
    local res = toInteger(val);
   
    /*   
     *   If val is a string then res is a valid number if val is a string that
     *   contains one or more zeroes perhaps preceded by + or -.
     */
    
    if(dataType(val) == TypeSString)    
    {
        /* 
         *   Strip out all the spaces from val.         
         */
        val = val.findReplace(' ', '').trim();
        
        if(val.match(R'(<plus>|-)?<digit>+$'))
            return res;       
        
    }
    
       
    /* 
     *   If val is a BigNumber or an integer, this is also a valid result, so
     *   return it. Note that we need only test for whether val is an object,
     *   since if it was any other kind of object than a BigNumber, this
     *   function would have returned nil at the first test.
     */     
    if(dataType(val) is in (TypeInt, TypeObject))
        return res;
    
   
    
    /* 
     *   We can't find a valid interpretation of val as a number, so return nil.
     */
    return nil;
}

/* 
 *   Try converting val to a number (integer or BigNumber); return the number if
 *   there is one, otherwise return nil.
 */
tryNum(val)
{
     /* 
     *   If the value passed to the function is neither an integer nor a string
     *   nor a BigNumber, return nil, since there can be no valid numerical
     *   representation of it.
     */    
    if(dataType(val) not in (TypeInt, TypeSString, TypeObject)
       || (dataType(val) == TypeObject && !val.ofKind(BigNumber)))
        return nil;
    
    /* If val is already a BigNumber, return it unchanged. */
    if(dataType(val) == TypeObject && val.ofKind(BigNumber))
        return val;
    
   

    /*  
     *   If val is a string then test whether it matches a valid numerical
     *   pattern.
     */
    if(dataType(val) == TypeSString)
    {
        val = stripQuotesFrom(val.findReplace(' ',''));
        
        /* Try converting val to a number */
        local res = toNumber(val);
        
        if(val.match(R'(<plus>|-)?<digit>+$'))
            return res;
        
        if(val.match(R'(<plus>|-)?<digit>*<dot><digit>+$'))
            return res;
        
        if(val.match(R'(<plus>|-)?<digit>+(<dot><digit>+)?[eE]<digit>?+$'))
            return res;
    }
    

    /* Otherwise use the tryInt() function to return the result */
    return tryInt(val);
}





/*
 *   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 : []);
}

/*      
 *   val to list - convert any value to a list. If it's already a list, simply
 *   return it. If it's nil return an empty list. If it's a singleton value,
 *   return a one-element list containing it.
 */
valToList(val)
{
    switch (dataType(val))
    {
    case TypeNil:
        return [];
    case TypeList:
        return val;
    case TypeObject:
        if(val.ofKind(Vector))
            return val.toList;
        else
            return [val];
    default:
        return [val];
    }
}

/*  
 *   Set the mentioned property of obj to true. If obj is supplied as a list,
 *   set every object's mentioned property in the list to true. This can be used
 *   in room and object descriptions to mark an object as mentioned so it won't
 *   be included in the listing.
 */
makeMentioned(obj)
{
    foreach(local cur in valToList(obj))
        cur.mentioned = true;
}


/* 
 *   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;
}


/* 
 *   Find an existing Topic whose vocab is voc. If the cls parameter
 *   is supplied it can be used to find a match in some other class, such as
 *   Thing or Mentionable.
 */
findMatchingTopic(voc, cls = Topic)
{
    for(local cur = firstObj(cls); cur != nil; cur = nextObj(cur, cls))
    {
        if(cur.vocab == voc)
            return cur;
    }
    
    return nil;
}

/* 
 *   Set the player character to another actor. If the optional second parameter
 *   is supplied, it sets the person of the player character; otherwise it
 *   defaults to the second person.
 */
setPlayer(actor, person = 2)
{    
    /* Note the old player character */
    local other = gPlayerChar;
    
    /* Note the name of the actor the pc is about to become */
    local newName = actor.theName;
    
    /* Change the player character to actor */
    gPlayerChar = actor;
    
    /* Change the player character person to person. */
    gPlayerChar.person = person;
    
    /* Change the person of the previous player character to 3 */
    other.person = 3;
     
    /* 
     *   Change the names of both actors involved in the swap to nil, so that
     *   they can be reinitialized.
     */
    other.name = nil;
    gPlayerChar.name = nil;
    
    /*   
     *   Reinitialize the names of both actors, so that the player character can
     *   become 'I' or 'You' as appropriate, and the previous PC acquires
     *   his/her third-person name.
     */
    other.initVocab();
    gPlayerChar.initVocab();
    
    /*   Note the name (e.g. 'Bob' or 'Mary') of the new player character */
    libGlobal.playerCharName = newName;
    
    /*   Make the current actor the new player character */
    gActor = gPlayerChar;
    gCommand.actor = gPlayerChar;
    
    
    /* 
     *   Reset the familiarity of rooms in regions familiar to the new actor, if its knownProp
     *   differs from that of the previous player character.
     */
    local prop = actor.knownProp;
    
    if(prop != other.knownProp)
    {
    for(local reg = firstObj(Region); reg != nil;  reg = nextObj(reg, Region))
        reg.setFamiliarRooms(prop);
    }
    
    /* 
     *   If the new player character defines its own ThoughtManagr object, set this up on libGlobal,
     *   so that any ThinkAbout commands will use the Thoughts belonging to the new Player
     *   Character, otherwise leave it unchanged,
     */
    if(actor.myThoughtManager)
        libGlobal.thoughtManagerObj = actor.myThoughtManager;
    
    /* Return the (third-person) name of the new player character */
    return newName;
    
    
}

/* ------------------------------------------------------------------------ */
/*
 *   Add some methods to the base Object that make it *somewhat*
 *   interchangeable with lists and vectors.  Certain operations that are
 *   normally specific to the collection types have obvious degenerations
 *   for the singleton case.  In particular, a singleton can be thought of
 *   as a collection consisting of one value, so operations that iterate
 *   over a collection degenerate to one iteration on a singleton.  
 */
modify Object
    /* mapAll for an object simply applies a function to the object */
    mapAll(func)
    {
        return func(self);
    }

    /* forEach on an object simply calls the function on the object */
    forEach(func)
    {
        return func(self);
    }

    /* create an iterator */
    createIterator()
    {
        return new SingletonIterator(self);
    }

    /* 
     *   create a live iterator (this allows 'foreach' to be used with an
     *   arbitrary object, iterating once over the loop with the object
     *   value) 
     */
    createLiveIterator()
    {
        return new SingletonIterator(self);
    }

    /*
     *   Call an inherited method directly.  This has the same effect that
     *   calling 'inherited cl.prop' would from within a method, but allows
     *   you to do this from an arbitrary point *outside* of the object's
     *   own code.  I.e., you can say 'obj.callInherited(cl, &prop)' and
     *   get the effect that 'inherited c.prop' would have had from within
     *   an 'obj' method.  
     */
    callInherited(cl, prop, [args])
    {
        delegated cl.(prop)(args...);
    }
;

/*
 *   A SingletonIterator is an implementation of the Iterator interface for
 *   singleton values.  This allows 'foreach' to be used with arbitrary
 *   objects, or even primitive values.  The effect of iterating over a
 *   singleton value with 'foreach' using this iterator is simply to invoke
 *   the loop once with the loop variable set to the singleton value.  
 */
class SingletonIterator: object
    /* construction: save the singleton value that we're "iterating" over */
    construct(val) { val_ = val; }

    /* get the next value */
    getNext()
    {
        /* note that we've consumed the value */
        more_ = nil;

        /* return the value */
        return val_;
    }

    /* is another item available? */
    isNextAvailable() { return more_; }

    /* reset: restore the flag that says the value is available */
    resetIterator() { more_ = true; }

    /* get the current key; we have no keys, so use a fake key of nil */
    getKey() { return nil; }

    /* get the current value */
    getCurVal() { return val_; }
    
    /* the singleton value we're "iterating" over */
    val_ = nil
    
    /* do we have any more values to fetch? */
    more_ = true
;

/*
 *   Add some convenience methods to String. 
 */
modify String
    /*
     *   Trim spaces.  Removes leading and trailing spaces from the string.
     */
    trim()
    {
        return findReplace(trimPat, '');
    }

    /* regular expression for trimming leading and trailing spaces */
    trimPat = R'^<space>+|<space>+$'

    /* get the first character */
    firstChar() { return substr(1, 1); }

    /* get the last character */
    lastChar() { return substr(length()); }

    /* remove the first character */
    delFirst() { return substr(2); }

    /* remove the last character */
    delLast() { return substr(1, length() - 1); }

    /* leftmost n characters; if n is negative, leftmost (length-n) */
    left(n) { return n >= 0 ? substr(1, n) : substr(1, length() + n); }

    /* rightmost n characters; if n is negative, rightmost (length-n) */
    right(n) { return n >= 0 ? substr(-n) : substr(n + length()); }    
    ;

/* A string is empty if it's nil or if when trimmed it's '' */
isEmptyStr(str) {  return (str == nil || str.trim() == ''); }


/*
 *   Add a couple of handy utility functions to Vector 
 */
modify Vector
    /* is the vector empty? */
    isEmpty() { return length() == 0; }

    /* clear the vector */
    clear() 
    {
        if (length() > 0)
            removeRange(1, length()); 
    }

    /* get the "top" item, treating the vector as a stack */
    getTop() { return self[length()]; }

    /* push a value (append it to the end of the vector) */
    push(val) { append(val); }

    /* pop a value (remove and return the value at the end of the vector) */
    pop()
    {
        local l = length();
        if (l > 0)
        {
            /* get the last value */
            local ret = self[l];

            /* remove the element */
            removeElementAt(l);

            /* return it */
            return ret;
        }
        else
        {
            /* intentionally cause an out-of-bounds error */
            return self[1];
        }
    }

    /* unshift a value (insert it at the start of the Vector) */
    unshift(val) { prepend(val); }

    /* shift a value (remove and return the first value) */
    shift()
    {
        local l = length();
        if (l > 0)
        {
            /* get the first value */
            local ret = self[1];

            /* remove the element */
            removeElementAt(1);

            /* return it */
            return ret;
        }
        else
        {
            /* intentionally cause an out-of-bounds error */
            return self[1];
        }
    }

    /*
     *   Perform a "group sort" on the vector.  This sorts the items into
     *   groups, then sorts by an ordering value within each group.
     *   
     *   The groups are determined by group keys, which are arbitrary
     *   values.  Each group is simply the set of objects with a like value
     *   for the key.  Within the group, we sort by an integer ordering
     *   key.
     *   
     *   'func' is a function that takes two parameters: func(entry, idx),
     *   where 'entry' is a list element and 'idx' is an index in the list.
     *   This returns a list, [group, order], giving the group key and
     *   ordering key for the entry.  
     */
    groupSort(func)
    {
        /* note our length */
        local len = length();

        /* 
         *   set up a lookup table for the group keys - we want to assign
         *   each one an arbitrary integer value so that we can sort by it 
         */
        local groups = new LookupTable(16, 32);
        local gnxt = 1;
        
        /* decorate each entry with its group index and ordering key */
        for (local i = 1 ; i <= len ; ++i)
        {
            /* get this element */
            local ele = self[i];

            /* get the group info via the callback */
            local info = func(ele, i);

            /* look up or assign this group key's number */
            local gnum = groups[info[1]];
            if (gnum == nil)
                groups[info[1]] = gnum = gnxt++;

            /* store the group number and sorting order in the list */
            self[i] = [gnum, info[2], ele];
        }

        /* do the sort */
        sort(SortAsc, new function(a, b) {
            /* 
             *   if the groups are the same, sort by the order within the
             *   group; otherwise sort by the group number 
             */
            if (a[1] == b[1])
                return a[2] - b[2];
            else
                return a[1] - b[1];
        });

        /* remove the extra information from the list */
        for (local i = 1 ; i <= len ; ++i)
            self[i] = self[i][3];
    }

    /* find a list element - synonym for indexOf */
    find(ele) { return indexOf(ele); }

    /* shuffle the elements of the vector into a random order */
    shuffle()
    {
        /*
         *   The basic algorithm for shuffling is that we put all of the
         *   elements into a bag, and one by one we withdraw an element at
         *   random and add it to the result list.  To withdraw a random
         *   element, we simply pick a random number from 1 to the number
         *   of items left in the bag.
         *   
         *   With a vector, we can do this without allocating any more
         *   memory.  We partition the vector into two parts: the "result"
         *   part and the "bag" part.  Initially, the whole vector is the
         *   bag, and the result part is empty.  We next pick a random
         *   element from the bag, and swap it with element #N.  This
         *   effectively deletes the chosen element from the bag and fills
         *   in the hole with the bag element that was formerly at slot #N.
         *   (If we chose the element at slot #N, that's fine - it just
         *   stays put.)  Slot #N is now part of the result set, and the
         *   bag is now slots #1 to #N-1.  We next pick a random element
         *   from the bag - 1..N-1 - and swap it with slot #N-1.  Now slot
         *   #N-1 is in the result set, and the bag is from #1 to #N-2.
         *   Repeat until we've chosen slot 2.  (We don't have to
         *   explicitly pick anything for slot 1, since at that point we're
         *   down to a single element in the bag, and it's already at the
         *   proper position to just redefine it as a result.)  
         */
        for (local len = length(), local n = len ; n > 1 ; --n)
        {
            /* the bag is slots 1..n - pick a random element in that range */
            local r = rand(n) + 1;

            /* swap the random element with element #n */
            local val = self[r];
            self[r] = self[n];
            self[n] = val;
        }

        /* in case the caller wants the shuffled object, return self */
        return self;
    }
;


/* ------------------------------------------------------------------------ */
/*
 *   Add some utility methods to List. 
 */
modify List
    /*
     *   Check the list against a prototype (a list of data types).  This
     *   is useful for checking a varargs list to see if it matches a given
     *   prototype.  Each prototype element can be a TypeXxx type code, to
     *   match a value of the given native type; an object class, to match
     *   an instance of that class; 'any', to match a value of any type; or
     *   the special value '...', to match zero or more additional
     *   arguments.  If '...' is present, it must be the last prototype
     *   element.  
     */
    matchProto(proto)
    {
        /* compare each value against the prototype */
        local plen = proto.length(), vlen = length();
        for (local i = 1 ; i <= plen ; ++i)
        {
            /* get this prototype element (i.e., a type code) */
            local t = proto[i];

            /* if this is a varargs indicator, we have a match */
            if (t == '...')
                return true;

            /* if we're past the end of the values, it's no match */
            if (i > vlen)
                return nil;

            /* get the value */
            local v = self[i];

            /* check the type */
            if (t == 'any')
            {
                /* 'any' matches any value, so this one is a match */
            }
            else if (dataType(t) == TypeInt)
            {
                /* check that we match the given native type */
                if (dataTypeXlat(v) != t)
                    return nil;
            }
            else
            {
                /* otherwise, we have to match the object class */
                if (dataType(v) not in (TypeObject, TypeList, TypeSString)
                    || !v.ofKind(t))
                    return nil;
            }
        }

        /* 
         *   We reached the end of the prototype without finding a
         *   mismatch.  The only remaining check is that we don't have any
         *   extra arguments in the value list.  As long as the lengths
         *   match, we have a match. 
         */
        return plen == vlen;
    }

    /* toList() on a list simply returns the same list */
    toList() { return self; }

    /* find a list element - synonym for indexOf */
    find(ele) { return indexOf(ele); }

    /* 
     *   shuffle the list: return a new list with the elements of this list
     *   rearranged into a random order 
     */
    shuffle()
    {
        /* 
         *   Since a list is immutable, we can't shuffle the elements in
         *   place, which means we have to construct a new list.  One way
         *   would be to use the Vector.shuffle algorithm, appending each
         *   element chosen from the bag to a new result list under
         *   construction.  That would construct length()-1 intermediate
         *   lists, though, so it's pretty inefficient memory-wise.  The
         *   easier and more efficient way is to create a Vector with the
         *   same elements as the list, shuffle the Vector, and then
         *   convert the result back to a list.  This creates only the one
         *   intermediate value (the Vector), and it's very simple to code,
         *   so we'll take that approach.  
         */
        return new Vector(length(), self).shuffle().toList();
    }
    
    /* Determine whether this list has any elements in common with lst */    
    overlapsWith(lst)
    {
        return intersect(valToList(lst)).length > 0;
    }
        
    /*  Returns the ith member of the list if there is one, or nil otherwise */
    element(i)
    {
        return length >= i ? self[i] : nil;
    }
    
    /*  
     *   Compare two lists of strings using the cmp StringComparator; return
     *   true if all the corresponding strings in the two lists are the same
     *   (according to cmp) and nil otherwise.
     */
    strComp(lst, cmp)
    {
        if(lst.length != length)
            return nil;
        
        for(local i in 1 .. lst.length)
        {
            if(cmp.matchValues(self[i], lst[i]) == 0)
                return nil;
        }
        return true;
    }
;

/* Add a method to Date as a workaround for a library bug */
modify Date
    /* 
     *   Get the Hours, Minutes, Seconds and Milliseconds of the current time as
     *   a four-element list; Date.getClockTime() is meant to do this, but
     *   doesn't work properly.
     */
    getHMS()
    {
        local hh = toInteger(formatDate('%H'));
        local mm = toInteger(formatDate('%M'));
        local ss = toInteger(formatDate('%S'));
        local ms = toInteger(formatDate('%N'));
        
        return [hh, mm, ss, ms];
    }
    
;

/* ------------------------------------------------------------------------ */
/*
 *   Library error.  This is a base class for internal errors within the
 *   library.  
 */
class LibraryError: Exception
    construct()
    {
        /* do the inherited work */
        inherited();

        /* 
         *   As a debugging aid, break into the debugger, if it's running.
         *   This makes it easier during development to track down where
         *   errors are occurring.  This has no effect during normal
         *   execution in the interpreter, since the interpreter ignores
         *   this call when the debugger isn't present. 
         */
        t3DebugTrace(T3DebugBreak);
    }
    display = "Library Error"
;

/*
 *   A generic "argument mistmatch" error.  The library uses this for
 *   functions that use matchProto() to handle multiple argument list
 *   variations: when none of the allowed argument lists are found, the
 *   function throws this error. 
 */
class ArgumentMismatchError: LibraryError
    display = "Wrong arguments in function or method call"
;



/* ------------------------------------------------------------------------ */
/*
 *   LCS - class that computes the Longest Common Subsequence for two lists
 *   or vectors.
 *   
 *   The LCS is most frequently used as a differencing tool, to compute a
 *   description of how two data sets differ.  This is at the core of tools
 *   like "diff", which shows the differences between two versions of a
 *   file.  The LCS is the part of the two sets that's the same, so
 *   everything in one of the sets that's not in the LCS is unique to that
 *   set.  The standard diff algorithm computes the LCS, then generates a
 *   list of edits by specifying a "delete" operation on each item in the
 *   "new" set that's not in the LCS, and an "insert" operation on each
 *   item in the "old" set that's not in the LCS.  Merge and sort the two
 *   edit lists and you have basically the standard Unix "diff" output.
 *   (Some diff utilities make the report more readable by combining
 *   overlapping edit and insert operations into "update" operations.  But
 *   it's really the same thing, of course.)
 *   
 *   The constructor does all the work: use 'new' to create an instance of
 *   this class, providing the two lists to be compared as arguments.  The
 *   resulting object contains the LCS information.
 *   
 *   Note that you can use this class to generate a character-by-character
 *   LCS for two strings, simply by using toUnicode() to convert each
 *   string to a list of character values.  
 */
class LCS: object
    construct(a, b)
    {
        local i, j, ka, kb;

        /* get the input list lengths */
        local alen = a.length(), blen = b.length();

        /* set up the length array, alen x blen, initialized with 0s */
        local c = new Vector(alen+1).fillValue(nil, 1, alen+1);
        c.applyAll({ ele: new Vector(blen+1).fillValue(0, 1, blen+1) });

        /* set up the arrow array, alen x blen */
        local arr = new Vector(alen+1).fillValue(nil, 1, alen+1);
        arr.applyAll({ ele: new Vector(blen+1).fillValue(nil, 1, blen+1) });

        /* apply the standard LCS algorithm */
        for (i = 1 ; i <= alen ; ++i)
        {
            for (j = 1 ; j <= blen ; ++j)
            {
                if (a[i] == b[j])
                {
                    /* up-left */
                    c[i+1][j+1] = c[i][j] + 1;
                    arr[i+1][j+1] = 3;
                }
                else if (c[i][j+1] >= c[i+1][j])
                {
                    /* up */
                    c[i+1][j+1] = c[i][j+1];
                    arr[i+1][j+1] = 2;
                }
                else
                {
                    /* left */
                    c[i+1][j+1] = c[i+1][j];
                    arr[i+1][j+1] = 1;
                }
            }
        }

        /* build the LCS list */
        local la = new Vector(alen), lb = new Vector(blen);
        for (i = alen+1, j = blen+1, ka = alen, kb = blen ; i > 0 && j > 0 ; )
        {
            if (arr[i][j] == 3)
            {
                la[ka--] = i-1;
                lb[kb--] = j-1;
                --i;
                --j;
            }
            else if (arr[i][j] == 2)
                --i;
            else
                --j;
        }

        /* save the LCSs, truncating the used portions */
        lcsA = la.toList().sublist(ka+1);
        lcsB = lb.toList().sublist(kb+1);
    }

    /* the LCS, as lists of character indices into the respective strings */
    lcsA = nil
    lcsB = nil
;


/* ------------------------------------------------------------------------ */
/*
 *   Change the case (upper/lower) of a given new string to match the case
 *   pattern of the given original string.
 *   
 *   We recognize four patterns:
 *   
 *   - If the original string has at least one capital letter and no
 *   minuscules, we convert the new string to all caps.  For example,
 *   matchCase('ALPHA-1', 'omicron-7') yields 'OMICRON-7'.
 *   
 *   - If the original string has at least one lower-case letter and no
 *   capitals, we convert the new string to all lower case.  E.g.,
 *   matchCase('alpha-1', 'OMICRON-7') yields 'omicron-7'.
 *   
 *   - If the original string starts with a capital letter, and has at
 *   least one lower-case letter and no other capitals, we capitalize the
 *   first letter of the new string and lower-case everything else. E.g.,
 *   matchCase('Alpha-1', 'OMICRON-7') yields 'Omicron-7'.
 *   
 *   - Otherwise, we match the case pattern of the input string letter for
 *   letter: for each upper-case letter in the original, we capitalize the
 *   letter at the corresponding character index in the new string, and
 *   likewise with lower-case letters in the original.  We leave other
 *   characters unchanged.  E.g., matchCase('AlPhA-1', 'omicron-7') yields
 *   'OmIcRon-7'.  
 */
matchCase(newTok, oldTok)
{
    /* 
     *   If the old token is all lower-case or all upper-case, it's easy.
     *   Only assume all upper-case if the original token has at least two
     *   capitals - for something like "I" we can't assume we want an
     *   all-caps word. 
     */
    if (rexMatch(R'<^upper>*<lower>+<^upper>*', oldTok) != nil)
        return newTok.toLower();
    if (rexMatch(R'<^lower>*<upper>+<^lower>*<upper>+<^lower>*', oldTok) != nil)
        return newTok.toUpper();
    
    /* another common and easy pattern is title case (initial cap) */
    if (rexMatch(R'<upper><^upper>*<lower>+<^upper>*', oldTok) != nil)
        return newTok.firstChar().toUpper() + newTok.delFirst().toLower();
    
    /* do everything else letter by letter */
    local ret = '';
    for (local i = 1, local len = newTok.length() ; i <= len ; ++i)
    {
        local cn = newTok.substr(i, 1);
        local co = oldTok.substr(i, 1);
        
        if (rexMatch(R'<upper>', co) != nil)
            ret += cn.toUpper();
        else if (rexMatch(R'<lower>', co) != nil)
            ret += cn.toUpper();
        else
            ret += cn;
    }
    
    /* return the result */
    return ret;
}


/* ------------------------------------------------------------------------ */
/*
 *   Static object and class initializer.
 *   
 *   During startup, we'll automatically call the classInit() method for
 *   each class object, and we'll call the default constructor for each
 *   static object instance.  ("Static" objects are those defined directly
 *   in the source code, as opposed to objects created dynamically with
 *   'new'.)  This makes it easier to write initialization code by making
 *   the process more uniform across static and dynamic objects.
 *   
 *   The first step is to call classInit() on each class.  We call this
 *   method only each class that *directly* defines the method (i.e., we
 *   don't call it on classes that only inherit the method from another
 *   class).  We cycle through the objects in arbitrary order.  However,
 *   you can control the relative order when there's a dependency by
 *   setting the 'classInitFirst' property to a list of one or more classes
 *   to initialize first.  When we encounter a class with this property,
 *   we'll call the listed classes' classInit() methods before calling the
 *   given class's classInit().
 *   
 *   The second step is to call constructStatic() or construct() on each
 *   regular (non-class) object.  We only call this on *static* objects:
 *   objects defined directly in the source code, as opposed to created
 *   dynamically with 'new'.  As with classInit(), we visit the objects in
 *   arbitrary order.  You can control dependencies using the
 *   'constructFirst' method: set this to a list of objects to be
 *   initialized before self.
 *   
 *   If an object defines or inherits a constructStatic() method, we'll
 *   call it instead of construct().  Otherwise, if it defines or inherits
 *   a construct() method with no arguments, we'll call it.  Otherwise
 *   we'll do nothing.
 *   
 *   Note that it's possible for a base class to have a compatible
 *   zero-argument constructor, but for a subclass to override this with a
 *   constructor that takes arguments.  In this case, we'll search the
 *   class tree for an inherited zero-argument constructor.  If we find
 *   one, we'll call the inherited constructor.
 *   
 *   We can only call zero-argument construct() methods because we have no
 *   basis for providing other arguments.  
 */
libObjectInitializer: PreinitObject
    execBeforeMe = []
    execute()
    {
        /* build the reverse symbol table (indexed by object value) */
        local gtab = t3GetGlobalSymbols();
        local otab = new LookupTable(128, 256);
        gtab.forEachAssoc({ key, val: otab[val] = key });

        /* save it in the PreinitObject class */
        PreinitObject.reverseGlobalSymbols = otab;

        /* create a lookup table tracking which objects we've initialized */
        _initedTab = new LookupTable(256, 1024);

        /* call classInit() on all classes */
        for (local o = firstObj(TadsObject, ObjClasses) ; o != nil ;
             o = nextObj(o, TadsObject, ObjClasses))
        {
            /* if this class directly defines a classInit() method, call it */
            if (o.propDefined(&classInit, PropDefGetClass) == o)
                callConstructor(o, &classInit, &classInitFirst);
        }

        /* call construct() or constructStatic() on all object instances */
        for (local o = firstObj(TadsObject, ObjInstances) ; o != nil ;
             o = nextObj(o, TadsObject, ObjInstances))
        {
            /* 
             *   Only call static objects - these will all have
             *   sourceTextOrder properties assigned by the compiler.
             *   
             *   Note that modified objects will inherit sourceTextOrder
             *   from a class - they're the only objects that do this,
             *   since the compiler only assigns sourceTextOrder initially
             *   to ordinary objects, but then class-ifies the base object
             *   when modifying it.  The only way that an object can
             *   inherit sourceTextOrder from a class is that the class is
             *   the original modified object, and the instance is the
             *   modifier.  
             */
            local cl = o.propDefined(&sourceTextOrder, PropDefGetClass);
            if (cl == o || cl != nil && cl.isClass())
            {
                /* 
                 *   It's a static object.  If it has a constructStatic()
                 *   method, call that.  Otherwise, if it has a construct()
                 *   method, call that.
                 */
                if (o.propDefined(&constructStatic))
                {
                    /* it has constructStatic() */
                    callConstructor(o, &constructStatic, &constructFirst);
                }
                else if (o.propDefined(&construct))
                {
                    /* call construct() */
                    callConstructor(o, &construct, &constructFirst);
                }
            }
        }

        /* 
         *   done with the lookup table - explicitly remove it so that it
         *   doesn't take up space in the final compiled image 
         */
        _initedTab = nil;

        /* likewise the reverse global symbol table */
        reverseGlobalSymbols = otab;
    }

    /* call the given object's constructor */
    callConstructor(obj, conProp, preProp)
    {
        /* if obj has already been initialized, skip it */
        if (_initedTab[obj])
            return;

        /* 
         *   mark this object as visited (do this first, before handling
         *   prerequisites, to break circular dependencies: if a
         *   prerequisite of ours lists us as a prerequisite, we'll see
         *   that we've already been initialized and stop the loop) 
         */
        _initedTab[obj] = true;

        /* call constructors on any prerequisites */
        if (obj.propDefined(preProp))
        {
            foreach (local p in obj.(preProp))
                callConstructor(p, conProp, preProp);
        }

        /* 
         *   if the given constructor is zero-argument constructor, call it
         *   directly; otherwise, look for an inherited constructor 
         */
        if (obj.getPropParams(conProp) == [0, 0, nil])
        {
            /* call the constructor */
            obj.(conProp)();
        }
        else
        {
            /* 
             *   Search the class tree for an inherited version of the
             *   constructor that takes zero arguments.  
             */
            for (local cl = obj.propDefined(conProp, PropDefGetClass) ;
                 cl != nil ;
                 cl = obj.propInherited(conProp, obj, cl, PropDefGetClass))
            {
                /* if this is a zero-argument version, call it */
                if (cl.getPropParams(conProp) == [0, 0, nil])
                {
                    /* invoke it */
                    obj.callInherited(cl, conProp);

                    /* we're done looking */
                    break;
                }
            }
        }
    }

    /* table of objects we've already initialized */
    _initedTab = nil
;

/*
 *   Our static object and class initializer should generally run before
 *   any other initializers.  
 */
modify PreinitObject
    /* execute the basic library initializer before any other initializers */
    execBeforeMe = [libObjectInitializer]

    /* 
     *   class property: reverse lookup symbol table (a version of the
     *   global symbol table keyed by value, yielding the name of each
     *   global object, function, etc) 
     */
    reverseGlobalSymbols = nil
;

emumTabInitializer: PreinitObject
    execute()
    {
        local gTab = t3GetGlobalSymbols();
            
        local lst=gTab.keysToList();
        
        foreach(local key in lst)
        {
            local value = gTab[key];
            if(dataType(value) == TypeEnum && enumTabObj.enumTab[value] == nil)
                enumTabObj.enumTab[value] = key;
        }
    }   
;

/* 
 *   Service function primarily intended for use with the symcomm extension to facilitate the use of
 *   a string template, defined in advlite.h, allowing a string to vary by room. The arg parameter
 *   should be supplied as a list of strings (symcomm expects a list of two, bur other uses could do
 *   something different).
 *
 *   We need the function because a string template can't directly be defined in relation to a
 *   method, so the byRoomFunc() function calls the byRoom() method on the object that invokes it.
 */
 byRoomFunc(arg)
{
    /* 
     *   We meed to get at the effective self object by using the stack frame. First obtain the
     *   stack frame one level back from us.
     */
    local frame = t3GetStackTrace(2, T3GetStackDesc).frameDesc_;
    
    /* Then get the self object in that stack frame. */
    local obj = frame.getSelf();
    
    /* Then call and return the byRoom() method on that object. */
    return obj.byRoom(arg);
}


/* Service function to determine whether obj is ofKind cls when obj might not be an object. */
objOfKind(obj, cls) { return dataTypeXlat(obj) == TypeObject && obj.ofKind(cls); }


/* Failsafe Function to get the player character object */
getPlayerChar()
{
    /* 
     *   If gPleyarChar is not nil, return gPlayerChar, otherwise if gameMain.initialPlayerChar is
     *   not nil, return gameMain.initialPlayerChar, otherwise call the findPlayerChar() function
     *   and return whatever that comes up with.
     */    
    return (gameMain.initialPlayerChar) ?? findPlayerChar();
}

/* 
 *   The findPlayerChar() function is intended for internal library use only, to be called by
 *   getPlayerChar() nothing else has yet defined the player character. The findPlayerChar()
 *   function first iterates through every Thing defined in the game until it finds one that defines
 *   isInitialPlayChar =  true. If it finds one it sets gPlayerChar to this value and returns the
 *   player char object. Otherwise it creaates a new Player object, moves it into the first room it
 *   finds, sets gPlayerChar to the new Player object, issues a warning to the game author that no
 *   player char object has been explicitly defined in game code, and returns the new Player object.
 *   This enaures that the calling function, getPlsyerChar(), will always be able to return a
 *   non-nil object.
 */     
findPlayerChar()    
{   
    /* Start by looking at objects of the Player class, and if that fails, look through Things. */
    for(local cls in [Player, Actor, Thing])
    {
        /* loop over every Thing till we find the one that defines isInitialPlayerChar = true */
        for (local obj = firstObj(cls) ; obj != nil ; obj = nextObj(obj, cls))
        {
            /* If we've found the initial player character */
            if(obj.isInitialPlayerChar)
            {
                /* Store the PC's identity in gameMain */
                gameMain.initialPlayerChar = obj;
                
                /* Set the player char to the objec we've found. */
//                gPlayerChar = obj;
                
                /* Return our player char object. */
                return obj;
            }
        }
    }
    
    /* If all else fails, create a new player character object */    
    local pc = new Player;
    
    /* Find a room */
    local loc = firstObj(Room);
    
    /* But not the dummy locations unknownDsst_ and varDest_ defined in the library. */
    while(loc is in (unknownDest_, varDest_))
        loc = nextObj(loc, Room);
    
    /* Move our new player character into that room */
    pc.moveInto(loc);
    
    /* Set gPlayerChar to our new player character. */
//    gPlayerChar = pc;
    
#ifdef __DEBUG 
    say('<.p><FONT COLOR=RED><b>WARNING!</b></FONT> No Player Character Defined.<.p>
        The library has defined a Player Character object and placed it in <<loc.roomTitle>>.<.p>');
#endif  
    
    
    /* Return our new player character */
    return pc;
}

/* Determine whether str is a valid identifier */
isValidIdentifierName(str)
{
    /* 
     *   First, strip out any underscores, since these can appear anywhere in valid identifier name.
     */
    str = str.findReplace('_', '');
    
    /* 
     *   If there are any non-alphanumeric characters in what's left, str is not a valid identifier
     *   name.
     */
    if(str.find(R'<^AlphaNum>'))
        return nil;
    
    /* If str starts with a digit, it's not a valid identifier name */
    if('1234567890'.find(str.substr(1,1)))
       return nil;
    
    /* If we reach this far, str has passed all the tests for being a valid identifier name. */
    return true;
}
Adv3Lite Library Reference Manual
Generated on 25/04/2024 from adv3Lite version 2.0