# thing.t | documentation

    #charset "us-ascii"
    #include "advlite.h"

    property subLocation;
    property lookAroundShowExits;

    /*
     *   Mentionable is the base class for objects that the player can refer to
     *   in command input.  In order for the parser to recognize an object, the
     *   object must have vocabulary words in the dictionary.  This class's
     *   main function, then, is to set up the dictionary for an object, so
     *   that its words are recognizable to the parser.
     *
     *   This class is based on LMentionable, which is defined in the language
     *   module.  LMentionable provides implementations for certain methods
     *   that we rely upon for functionality that varies by language.
     */
    class Mentionable: LMentionable
        /*
         *   'vocab' is a string that we use to initialize the object's short
         *   name, dictionary words, grammatical gender, and grammatical number
         *   (singular/plural).  This is designed to make it as convenient as
         *   possible to describe the object's name and grammatical behavior
         *   for input and output purposes.
         *
         *   The syntax is language-specific - see initVocab() for details.
         */
        vocab = nil

        /*
         *   The object's short name, for display in lists and announcements.
         *   By default, this is automatically derived from 'vocab', so you
         *   usually don't have to set it directly.  If you define a non-nil
         *   'name' value manually, though, it takes precedence (i.e., the
         *   library won't replace it with the name implied by 'vocab').
         */
        name = nil

        /*
         *   My room title.  This is displayed as the start of the room
         *   headline, which is the first line of the room description when
         *   'self' is the outermost visible container of the point-of-view
         *   actor.  The headline is also conventionally shown on the status
         *   line whenever the player is in the location.
         *
         *   The room title essentially serves as a label for the room on the
         *   player's mental and/or paper map of the game's geography.  It's
         *   usually something short and pithy that sums up the room's essence,
         *   function, or appearance, and is usually written in title case: Ice
         *   Cave, Bank Lobby, Front of House, Transporter Room 3, etc.
         *
         *   The room headline sometimes adds more status information to the
         *   title, such as the point-of-view actor's direct container when the
         *   actor is inside an intermediate container within the room, such as
         *   a chair.
         */
        roomTitle = nil

        /*
         *   The object's disambiguation name.  This is a more detailed version
         *   of the name, for situations where the short name is ambiguous.
         *   For example, the parser displays this name in "Which do you mean"
         *   questions when it would help tell two of the listed objects apart.
         *
         *   By default, this is the same as the short name.  It's uncommon to
         *   override this, since short names are typically already detailed
         *   enough for most purposes.  Every so often, though, you'll want to
         *   keep the short name very terse, so you'll leave out some
         *   distinguishing detail that it *could* have had.  In such cases,
         *   you can add the distinguishing detail here, so that it's displayed
         *   only when it's really needed.
         */
        disambigName = (name)

        /*
         *   Disambiguation prompt grouping.  When the parser generates a
         *   disambiguation question ("Which did you mean, the red book, or the blue
         *   book?"), it'll group the objects in the list by common disambigGroup
         *   values.  The group is just an arbitrary value that keeps like objects
         *   together in the list.  You can use a string, a class, or whatever you
         *   like for this, as long as grouped objects have the same value in
         *   common. We give this property a default value of 0 so that the
         *   disambigOrder will work by default without the need to specify a
         *   disambigGroup.
         */
        disambigGroup = 0

        /*
         *   Disambiguation prompt sorting.  This gives the display order of
         *   this item within its disambiguation group, if it has one.  The
         *   parser sorts objects within each group in ascending order of this
         *   value when generating the object list for a disambiguation
         *   question.  This is simply an integer; the default is 1 for every
         *   object, which makes the ordering arbitrary.
         *
         *   This property is useful for grouped objects with a natural
         *   ordering, such as items with sequential labels of some sort
         *   (numbers, letters, etc).  You can use this property to ensure that
         *   lists of these items will be displayed in the natural order:
         *   "button 1, button 2, or button 3?" or "the door on the left, the
         *   door in the middle, or the door on the right?"
         */
        disambigOrder = (listOrder)

        /*
         *   Is this object's short name a proper name?  A proper name is the
         *   name of a person, place, or other unique entity with its own name.
         *
         *   This property controls how the library shows the object's name in
         *   generated messages.  In English, for example, articles (a, the)
         *   aren't used with a proper name.
         *
         *   The English library tries to infer whether the object has a proper
         *   name based on the 'vocab' string: if all the words in the short
         *   name are capitalized, we'll consider it a proper name.  This rule
         *   of thumb doesn't always apply, though, so you can override it:
         *   simply set 'proper' explicitly in an individual object, and the
         *   setting will take precedence over whatever the name's
         *   capitalization would otherwise imply.  (Other languages might have
         *   different rules for inferring 'proper', and some might not be able
         *   to infer it at all.)
         */
        proper = nil

        /*
         *   The object's name is "qualified" grammatically, meaning that it
         *   can't be combined with articles (a, the) or possessives.  Proper
         *   names are considered to be qualified, but it's possible for a name
         *   to be qualified but not proper, such as a name that incorporates a
         *   possessive.
         */
        qualified = (proper)

        /*
         *   The grammatical person of narration relative to this object.  Use
         *   1 for first person ("I am in a cave"), 2 for second person ("You
         *   are in a cave"), and 3 for third person ("Bob is in a cave").
         *
         *   Usually, every object in the game will be in the third person -
         *   *except* the player character object, which is usually in the
         *   second person.  The library doesn't care which person you use for
         *   the player character, though - you're free to use first or third
         *   person if you prefer.  First-person and third-person IF are
         *   relatively uncommon, but not unheard of.
         *
         *   This property is used for verb agreement when library messages are
         *   generated.  This ensures that library messages adapt to the
         *   correct narrative person of the story automatically.  To write a
         *   first-person game, you don't have to replace all of the default
         *   messages, but simply set person=1 in your PC object.
         */
        person = 3

        /*
         *   The object's grammatical gender(s).  This information is used to
         *   determine which pronouns can match the object as an antecedent,
         *   which pronouns should represent it in output, and (for some
         *   languages) which articles and other gender-agreement words should
         *   be used in conjunction with the object name in output.
         *
         *   The default is neuter.  Use isHim and isHer to give an object
         *   masculine and/or feminine gender.  Use isIt to explicitly give an
         *   object neuter gender.  By default, we infer isIt from isHim and
         *   isHer: we assume the object is neuter if it's not masculine or
         *   feminine.
         *
         *   Languages that have grammatical gender will almost certainly want
         *   to parse articles in the 'vocab' property to make it more
         *   convenient to encode each object's gender.  For example, a French
         *   implementation could parse 'le' or 'la' at the start of a vocab
         *   string and set isHim and isHer accordingly.
         *
         *   The English library sets isHim and isHer if 'him' and 'her'
         *   (respectively) are found in the object's pronoun list.  (This is
         *   the most convenient way to represent gender via the vocab string
         *   in English, since English doesn't have gendered articles.)
         *
         *   Note that we define the genders as three separate properties, so
         *   the genders are NOT mutually exclusive - an object can be a "him",
         *   a "her", and an "it" at the same time.  This is because a single
         *   object can have multiple grammatical genders in some languages.
         *   In English, for example, an animal can be referred to as gendered
         *   (matching its physical gender) or neuter; and a few inanimate
         *   objects have a sort of optional, idiomatic gender, such as
         *   referring to a ship or country as "her".
         *
         *   Some languages might need additional genders.  When needed,
         *   LMentionable should simply define suitable additional properties.
         *
         *   In most gendered languages, the grammatical gender is an attribute
         *   of the noun, not of the object.  In particular, if an object has
         *   two nouns in its vocabulary, the two nouns might be of different
         *   genders.  The language module might therefore limit the use of
         *   isIt et al to the gender of the object's name string as it appears
         *   in output (e.g., for selecting an article when showing the name in
         *   a list, or selecting a pronoun to represent the object), and use a
         *   completely different scheme to tag the gender of individual
         *   vocabulary words.  One approach would be to use separate mNoun and
         *   fNoun token properties (and more if needed) to distinguish the
         *   gender of individual nouns in the dictionary.
         */
        isIt = (!(isHim || isHer))
        isHim = nil
        isHer = nil

        /*
         *   The object's name's grammatical number.  This specifies singular
         *   or plural usage for the object's name when it appears in generated
         *   messages.  By default, an object has singular usage, so it'll
         *   appear as (for example) "an orange".  Some objects have names with
         *   plural usage, either because they're words that always appear in
         *   the plural (such as "scissors"), or because the game object
         *   represents a group of items that are too numerous and unimportant
         *   to the game to actually implement as separate Thing objects.  For
         *   example, the books in a library might be implemented collectively
         *   as a single "books" object.
         *
         *   The English library sets this to true if 'them' is listed as a
         *   pronoun for the object in the 'vocab' property.
         */
        plural = nil

        /*
         *   Some objects, such as a pair of shoes or a flight of stairs could be
         *   regarded as either singular or plural and referred to as either 'it' or
         *   'them'. Set ambiguouslyPlural to true for such objects.
         */
        ambiguouslyPlural = nil

        /*
         *   The object's name is a mass noun.  A mass noun is a word that has
         *   singular form but represents an indeterminate quantity or group of
         *   something, such as "sand" or "furniture".
         *
         *   In English, mass nouns use "some" as the indefinite article rather
         *   than "a" (some sand, not a sand).  Their plural usage tends to
         *   differ from regular nouns, in that they already carry a sense of
         *   plurality; if you have two distinct piles of sand, the two
         *   together are usually still just "sand", not "two sands".
         *
         *   When a mass noun is awkward as an object's name, you can often
         *   make it into a regular noun by naming its overall form.  "Sand" is
         *   a mass noun, but recasting it as "pile of sand" makes it an
         *   ordinary noun.  (The generic way to do this for a homogeneous
         *   substance is to add "quantity of".)
         */
        massNoun = nil

        /*
         *   My nominal contents is the special contents item we can use in
         *   naming the object.  This is useful for containers whose identities
         *   come primarily from their contents, such as a vessel for liquids
         *   or a box of loose files.  Returns an object that qualifies the
         *   name: a "water" object for BOX OF WATER, a "files" object for BOX
         *   OF FILES.  Nil means that the object isn't named by its contents.
         *
         *   Note that this is always a single object (or nil), not the whole
         *   list of contents.  We can only be named by one content object.
         *   (So you can't have a "box of books and papers" by having separate
         *   nominal contents objects for the books and the papers; although
         *   you could fake it by creating a "books and papers" object.)
         */
        nominalContents = nil

        /*
         *   Can I be distinguished in parser messages by my contents?  If so,
         *   we can be distinguished (in parser messages) from similar objects
         *   by our contents, or lack thereof: "bucket of water" vs "empty
         *   bucket".  If this is true, our nominalContents property determines
         *   the contents we display for this.
         */
        distinguishByContents = nil

        /*
         *   Match the object to a noun phrase in the player's input.  If the given
         *   token list is a valid name for this object, we return a combination of
         *   MatchXxx flag values describing the match.  If the token list isn't a
         *   valid name for this object, we return 0.
         *
         *   By default, we call simpleMatchName(), which matches the name if all of
         *   the words in the token list are in the object's vocabulary list,
         *   regardless of word order.
         *
         *   In most cases, an unordered word match works just fine.  The obvious
         *   drawback with this approach is that it's far too generous at matching
         *   nonsense phrases to object names - DUSTY OLD SPELL BOOK and BOOK DUSTY
         *   SPELL OLD are treated the same.  In most cases, users won't enter
         *   nonsense phrases like that anyway, so they'll probably never notice
         *   that we accept them.  If they enter something like that intentionally,
         *   we can plead Garbage In/Garbage Out: a user who willfully types a
         *   nonsense command has only himself to blame for a nonsense reply.
         *
         *   Occasionally, though, there are reasons to be pickier.  When these come
         *   up, you can override matchName() to be as picky as you like.
         *
         *   The most common situation where pickiness is called for is when two
         *   objects happen to share some of the same vocabulary words, but certain
         *   words orderings clearly refer to only one or the other. With the
         *   unordered approach, this can be a nuisance for the player because it
         *   can trigger disambiguation questions that seem unnecessary.  Overriding
         *   matchName() to be picky about word order for those specific objects can
         *   often fix this. In this implementation the matchPhrases property can be
         *   used for this purpose.
         *
         *   Another example is ensuring the user knows the correct full name of an
         *   object as part of a puzzle: you can override matchName() to make sure
         *   the user doesn't accidentally stumble on the object by using one of its
         *   vocabulary words to refer to something else nearby.  Another example is
         *   matching words that aren't in the vocabulary list, such as a game
         *   object that represents a group of apparent objects that have a whole
         *   range of labels ("post office box 123", say).
         */
        matchName(tokens)
        {
            return matchNameCommon(tokens, matchPhrases, matchPhrasesExclude);
        }

        /*
         *   Match a name against a list of tokens entered by the player. phrases is
         *   the list of match phrases defined on the object (either for initial
         *   matching or for disambiguation) and excludes should be true or nil
         *   depending on whether failure to match phrases should exclude a match
         *   overall.
         */
        matchNameCommon(tokens, phrases, excludes)
        {
            /*
             *   If an item is hidden, the player character either shouldn't know of
             *   its existence, or at least shouldn't be able to interact with it,
             *   so it shouldn't match any vocab.
             */
            if(isHidden && !gCommand.action.unhides)
                return 0;

            /*
             *   First try the phrase-match matcher; if this fails return 0 to
             *   indicate that we don't match. If it succeeds in matching a phrase
             *   of more than one word, return MatchPhrase (we found a match).
             */
            local phraseMatch = 0;

            /*
             *   We only need to test for phrase matching if there are any phrases
             *   to match,
             */
            if(phrases != nil)
            {
                /* See if our list of tokens matches any of our phrases. */
                phraseMatch = phraseMatchName(phrases, tokens);

                /*
                 *   If there's a mismatch between our phrases and our tokens,
                 *   return 0 to indicate we don't have a match overall. A mismatch
                 *   only occurs if one or more of our tokens appears in one or more
                 *   of our phrases but there's no match between a phrase and the
                 *   succession of tokens.
                 */
                if(phraseMatch is in (0, nil) && excludes)
                    return 0;

                /*
                 *   A return type of true means there was no overlap between the
                 *   tokens and the matchPhrases, so the matchPhrases should have no
                 *   effect on the match.
                 */
                if(dataType(phraseMatch) != TypeInt)
                    phraseMatch = 0;
            }

            /*
             *   Now compute our simple match score (based on our individual tokens
             *   without regard to their ordering or to their matching any phrases).
             */
            local simpleMatch = simpleMatchName(tokens);

            /*
             *   If the simpleMatchName routine fails to match anything, consider
             *   the match a failure
             */
            if(simpleMatch == 0)
                return 0;

            /*
             *   Otherwise boost the simpleMatch score with the result of the phrase
             *   match.
             */
            return phraseMatch | simpleMatch;
        }

        /*
         *   Match the object to a noun phrase in *disambiguation* input.  This
         *   checks words in the player's reply to a "Which one did you
         *   mean...?" question from the parser.  When the player replies to
         *   this kind of question, they usually don't respond with the full
         *   name, but with just an adjective or two.
         *
         *   Now, you might think we should handle these replies by just
         *   appending them to the original noun phrase in the input.  But we
         *   can't just do that: matchName() *could* care about the order of
         *   the words in the noun phrase, so we can't just assume that we can
         *   stick them in somewhere and still have a valid name for the
         *   object.  So, instead of doing that, we call this routine with the
         *   phrase from the player's answer to the "Which one" question.
         *   Since this routine knows that the new words aren't part of the
         *   original phrase, it can deal with them as it sees fit with respect
         *   to word order.
         *
         *   The default here, of course, is to do the same thing as the
         *   default matchName(): we simply call simpleMatchName() to match the
         *   input to the object vocabulary, ignoring word order.  This will
         *   usually work even when matchName() is overridden to care about
         *   word order, since the added words here are just serving to
         *   distinguish one object from another.
         */
        matchNameDisambig(tokens)
        {

            /*
             *   If disambigMatchPhrases is defined then we must match it
             *   exclusively; i.e. a fail to match any relevant phrase must result
             *   in a failure overall; otherwise we'll just keep getting the same
             *   disambiguation question over and over.
             */
            return matchNameCommon(tokens, disambigMatchPhrases, true);

        }

        /*
         *   Simple implementation of matchName(), which simply checks to see
         *   if all of the tokens are associated with the object.  The "simple"
         *   aspect is that we don't pay any attention to the order of the
         *   words - we simply check that they're all in the object's
         *   vocabulary list, in any order.
         */
        simpleMatchName(tokens)
        {
            /* if the token list is empty, it's no match */
            if (tokens.length() == 0)
                return 0;

            /* we haven't found any strength demerits yet */
            local strength = MatchNoTrunc | MatchNoApprox;

            /* we haven't found any part-of-speech matches yet */
            local partOfSpeech = 0;

            /* remember the vocabulary word list and the string comparator */
            local vw = vocabWords, cmp = Mentionable.dictComp;

            /*
             *   if we're distinguishable by contents, add either the
             *   vocabulary for our contents object, if we have one, or the
             *   special 'empty' vocabulary words
             */
            if (distinguishByContents)
            {
                vw += nominalContents != nil
                    ? nominalContents.vocabWords : emptyVocabWords;
            }

            /* note the number of states we have */
            local stateCnt = states.length();

            /* scan the token list */
            for (local i = 1, local len = tokens.length() ; i <= len ; ++i)
            {
                /* get the word */
                local tok = tokens[i];

                /* match this word */
                local match = matchToken(tok, vw, cmp);

                /*
                 *   if we didn't match it from our own vocabulary, try
                 *   matching it against any states we have
                 */
                if (match == 0)
                {
                    /* try each state until we find a match or run out of states */
                    for (local j = 1 ; j <= stateCnt ; ++j)
                    {
                        /* try this state - stop searching if it matches */
                        local state = states[j];
                        if ((match = state.matchName(
                            tok, self.(state.stateProp), cmp)) != 0)
                            break;
                    }
                }

                /*
                 *   if we didn't find a match for this token, the whole phrase
                 *   fails (even if we've already matched other tokens)
                 */
                if (match == 0)
                    return 0;

                /*
                 *   We found a match for this token, so combine it into the
                 *   running totals for the overall phrase.  The overall phrase
                 *   strength is the WEAKEST of the individual token strengths.
                 *   The overall phrase part-of-speech mix is the union of the
                 *   individual token part-of-speech matches.
                 */
                strength = min(strength, match & MatchStrengthMask);
                partOfSpeech |= match;
            }

            /*
             *   Omit prepositions from the results.  We don't want to reject
             *   prepositions that are in our vocabulary, which is why we've
             *   kept them in the results thus far, but we also don't want to
             *   match the whole phrase on the strength of just a preposition -
             *   "of" just isn't sufficiently specific to match "pile of
             *   paper".  Also mask out the match-strength bits we've
             *   accumulated, so that we can tell specifically which parts of
             *   speech we've matched.
             */
            partOfSpeech &= MatchPartMask & ~MatchPrep;

            /*
             *   we need at least one actual part-of-speech match - if we
             *   didn't find any, we must have had a string of nothing but
             *   prepositions
             */
            if (partOfSpeech == 0)
                return 0;

            /* return the overall results, combined into a single bit vector */
            return strength | partOfSpeech;
        }

        /*
         *   If we have any phraseMatches defined, check whether we fail to match
         *   any of them. This will be the case if we find a phraseMatch containing
         *   one of our tokens but not the rest in the right order.
         */
        phraseMatchName(phrases, tokens)
        {
            /* Start by assuming we won't find a mismatch */
            local ok = true;

            /* Note the number of tokens to check */
            local tokLen = tokens.length;

            /* Note the string comparator to use. */
            local cmp = Mentionable.dictComp;

            /*
             *   Go through each phraseMatch in turn to see if the tokens either
             *   fail to match it or succeed in matching it.
             */
            foreach(local pm in valToList(phrases))
            {
                /* Split the phraseMatch into a list of words */
                local pmList = pm.split(' ');

                /*
                 *   If the list of words from the phraseMatch contains no words in
                 *   common with the token list, there's nothing to test; but if it
                 *   does, we need to test it.
                 */
                if(pmList.overlapsWith(tokens))
                {
                    /*
                     *   See if we can find a list equivalent to the phraseMatch
                     *   list as a sublist of the tokens list.
                     */
                    local pmLength = pmList.length();
                    for(local i in 1 .. tokLen - pmLength + 1)
                    {
                        /*
                         *   If we can we've succeeded in finding a phrase match, so
                         *   we can return true straight away.
                         */
                        if(tokens.sublist(i, pmLength).strComp(pmList, cmp))
                        {
                            return pmLength > 1 ? MatchPhrase : MatchAdj;
                        }
                    }
                    /*
                     *   If we don't find a phrase match, note the failure, but
                     *   there may be other phrases to try matching, so carry on
                     *   looking.
                     */
                    ok = 0;
                }
            }

            /* Return the result */
            return ok;
        }

        /*
         *   A single-quoted string, or a list of single-quoted strings containing
         *   exact phrases (i.e. sequences of words) that must be matched by the
         *   player input if any of the words in the phrase matches appear in the
         *   player input. Note that words defined here should also be defined in
         *   the vocab property; the purpose of the matchPhrases property is to
         *   limit matches. Note also that object will be matched if any of the
         *   phrases in the list is matched.
         */
        matchPhrases = nil

        /*
         *   Do we want to test for phrase matches when disambiguating? We'll assume
         *   that by default we do since the same reasons for wanting the phrase
         *   match are likely to apply when disambiguating, and that we'll use the
         *   same set of phrases. This can be overridden to supply a different set
         *   of phrases or none.
         */
        disambigMatchPhrases = matchPhrases

        /*
         *   If failing to match any of the match phrases (when the player's input
         *   includes at least one word used in any of them) excludes a match, then
         *   return nil
         */
        matchPhrasesExclude = true

        /*
         *   On dynamically creating a new object, do the automatic vocabulary
         *   and short name initialization.
         */
        construct()
        {
            /* do the vocabulary initialization */
            initVocab();

            /* build the list of applicable states */
            foreach (local s in State.all)
            {
                if (s.appliesTo(self))
                    states += s;
            }
        }

        /*
         *   Vocabulary word list.  This is a vector of VocabWord objects that
         *   we build in initVocab(), giving the individual words that this
         *   object uses for its noun phrase vocabulary.
         */
        vocabWords = []

        /* the State objects applying to this object */
        states = []

        /*
         *   The filterResolveList method allows this object to remove itself or
         *   other objects from the list of resolved objects.
         *
         *   np is the noun phrase, so np.matches gives the current list of matches,
         *   and np.matches[i].obj gives the ith object match. To change the list of
         *   matches, manipulate the np.matches list.
         *
         *   cmd is the command object, so that cmd.action gives the action about to
         *   be executed.
         *
         *   mode is the match mode.
         *
         *   By default we do nothing here.
         */
        filterResolveList(np, cmd, mode) { }
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   Match a token from the player's input against a given vocabulary list.
     *   Returns a set of MatchXxx flags for a match, or 0 if there's no match.
     *
     *   'tok' is the token string to match.  'words' is the list of words to
     *   match, as VocabWords objects.  'cmp' is the StringComparator object
     *   that we use to compare the strings.
     */
    matchToken(tok, words, cmp)
    {
        /* we don't have a match for this token yet */
        local strength = 0, partOfSpeech = 0;

        /* try matching this token against our vocabulary list */
        for (local len = words.length(), local i = 1 ; i <= len ; ++i)
        {
            /* get this vocabulary word entry */
            local entry = words[i];

            /* check this token against the dictionary word */
            local match = cmp.matchValues(tok, entry.wordStr);

            /* if there's no match, keep looking */
            if (match == 0)
                continue;

            /*
             *   Figure the result flags for this match.  Note that any
             *   bits in the String Comparator match value above 0x80
             *   are character approximation flags.
             */
            local result =
                (match & StrCompTrunc ? 0 : MatchNoTrunc)
                | (match & ~0xFF ? 0 : MatchNoApprox);

            /*
             *   Check the required match-strength flags to see if this
             *   match is allowed.  If a MatchNoXxx flag is set in the
             *   required flags in the dictionary entry, it means that
             *   the match itself MUST have that flag.  So, if a flag
             *   is set in the dictionary, and it's not set in the
             *   result, reject this match.
             */
            if (entry.strengthFlags & ~result)
                continue;

            /*
             *   Okay, it's a match.  There are three possibilities for how
             *   it relates to other matches we've already found:
             *
             *   - It's stronger, meaning that it's not truncated while the
             *   earlier match was, or not approximated while the earlier
             *   match was.  We only want to keep the strongest matche(es),
             *   so if we have a prior match, forget it.
             *
             *   - It's equally strong.  We might have found another
             *   part-of-speech usage for the word at the same match
             *   strength.  Combine the part-of-speech flags into the
             *   running total.
             *
             *   - It's weaker.  We only want to keep the best matches, so
             *   reject this one.
             */
            if (result > strength)
            {
                /* it's stronger - replace any past match with this one */
                strength = result;
                partOfSpeech = entry.posFlags;
            }
            else if (result == strength)
            {
                /* equally strong - add this entry's part of speech */
                partOfSpeech |= entry.posFlags;
            }
        }

        /* return the combined MatchXxx flags */
        return strength | partOfSpeech;
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   A VocabWord is an entry in a Mentionable object's list of noun phrase
     *   words.
     */
    class VocabWord: object
        construct(w, f)
        {
            /* remember the word string */
            wordStr = w;

            /* separate out the part-of-speech and match-strength flags */
            posFlags = f & MatchPartMask;
            strengthFlags = f & MatchStrengthMask;
        }

        /* the word string (the text of this vocabulary word) */
        wordStr = nil

        /* the part-of-speech flags (MatchNoun, etc) */
        posFlags = 0

        /* the required match strength flags (MatchNoTrunc, MatchNoApprox) */
        strengthFlags = 0
    ;

    /* ------------------------------------------------------------------------ */
    /*
     *   A State represents a changeable condition of a Mentionable that can be
     *   used as part of the object's name in command input.  For example, a
     *   state could be used to represent whether a match is lit or unlit: the
     *   words 'lit' and 'unlit' could then be used to describe the object,
     *   according to its current condition.
     *
     *   The actual current condition of a given object is given by a property
     *   of the Mentionable, which we define as part of the State object.  So
     *   testing whether an object is lit or unlit is just a matter of checking
     *   the corresponding property of the object.
     *
     *   The parser considers an object to have the state, for parsing
     *   purposes, if the object defines any value for the state property.
     *
     *   Most of the State object's definition is its vocabulary, which is
     *   obviously language-specific.  We therefore leave it to the language
     *   modules to define the individual State instances.  Games can also add
     *   new states as needed, of course.
     */
    class State: LState
        /*
         *   The Mentionable property that indicates the current condition of
         *   an object that has this State.  The range of values that this
         *   property takes on in the Mentionable is up to the State to define.
         *   For some states, this will be a simple boolean: Lit/Unlit,
         *   Open/Closed, On/Off, etc.  For others, this might be an integer
         *   range or a set of string values.
         */
        stateProp = nil

        /*
         *   Does this state apply to the given object?  By default, we
         *   consider any object that defines the state property to exhibit the
         *   state.
         */
        appliesTo(obj) { return obj.propDefined(stateProp); }

        /*
         *   Match a token from the object name for the given state value.
         *   Mentionable.matchName() calls this to see if a token applies
         *   because of the object's current conditdion.  'tok' is the token
         *   string; 'state' is the object's value for the state property; and
         *   'cmp' is the string comparator to use for the string comparisons.
         *   Returns a combination of MatchXxx flags, or zero if the token
         *   doesn't match the current condition.
         *
         *   For example, a Lit/Unlit state would return MatchAdj for 'lit' if
         *   'state' is true, 0 otherwise.
         */
        matchName(tok, state, cmp)
        {
            /* get the vocabulary for the state; if none, there's no match */
            local v = getVocab(state);
            if (v == nil)
                return 0;

            /* compare the token against the list for the current state */
            return matchToken(tok, v, cmp);
        }

        /*
         *   Get the vocabulary words that apply to the given state.  For
         *   example, a Lit/Unlit object might return 'lit' if state is true
         *   and 'unlit' if state is nil.
         */
        getVocab(state)
        {
            /* return the list for this state */
            return vocabTab[state];
        }

        /* state vocabulary lookup table (built automatically during preinit) */
        vocabTab = nil

        /*
         *   State/adjective initializer list.
         *
         *   States are generally represented in names by adjectives added to
         *   the object name, both in displaying output and in parsing input.
         *   For example, a Lit/Unlit state would add 'lit' in the lit state
         *   and 'unlit' in the unlit state.  So we provide an easy way of
         *   initializing a state object: just list the states and their
         *   corresponding adjectives.
         *
         *   Make one entry in this list for each possible state; the entry is
         *   a list, [stateval, [adjectives]], where 'stateval' is the state
         *   variable value, and [adjectives] is a list of strings giving the
         *   corresponding adjectives.  The first adjective in the list is the
         *   display adjective - this is the one that addToName() will use to
         *   generate an object name for display.  The rest are used to parse
         *   input; they'll all be matched to the state.
         */
        adjectives = []

        /*
         *   *Full* vocabulary initializer list.  If the 'adjectives' list
         *   isn't sufficiently flexible for your needs, you can use this
         *   initializer list instead.  This consists of a list of sublist
         *   entries, [stateval, word, flags].  'stateval' is a state value,
         *   'word' is a string giving a vocabulary word to match, and 'flags'
         *   is a combination of MatchXxx flags for the word.
         *
         *.     [[nil, 'unlit', MatchAdj],
         *.      [true, 'lit', MatchAdj]]
         */
        vocabWords = []

        /* class property: master list of all State objects */
        all = []

        /* construction */
        construct()
        {
            /* create the vocabulary table */
            local tab = vocabTab = new LookupTable(8, 16);

            /* do the inherited work */
            inherited();

            /* load the vocabulary table from the adjectives list, if present */
            foreach (local a in adjectives)
            {
                /* make sure there's a list for this state */
                local st = a[1];
                if (tab[st] == nil)
                    tab[st] = [];

                /* add a VocabWord for each adjective for this state */
                foreach (local adj in a[2])
                {
                    initWord(adj);
                    tab[st] += new VocabWord(adj, MatchAdj);
                }
            }

            /* load the vocabulary table from the vocabWords list, if present */
            foreach (local w in vocabWords)
            {
                /*
                 *   create an empty list for the state if this is the first
                 *   word we've seen for this state
                 */
                local st = w[1];
                if (tab[st] == nil)
                    tab[st] = [];

                /* add a VocabWord for this word to the state list */
                initWord(w[2]);
                tab[st] += new VocabWord(w[2], w[3]);
            }
        }

        /* class initialization */
        classInit()
        {
            /* build the master list of State objects */
            forEachInstance(State, { s: State.all += s });
        }
    ;

    /*
     *   A ReplaceRedirector is a Redirector that uses replaceAction (or its
     *   execNestedAction equivalent) to redirect one action to another.
     */
    class ReplaceRedirector: Redirector

        /*
         *   User code should normally call this method via doInstead rather than
         *   directly. cmd is the current command object, altAction is the action we
         *   want to perform instead of the current action, dobj and iobj are the
         *   direct and indirect objects of the new action, and isReplacement
         *   determines whether the new action replaces the original one (if true)
         *   or merely takes place during the execution of the original one, which
         *   then resumes when the new action is complete (if isReplacement is nil).
         */
        redirect(cmd, altAction, dobj:?, iobj:?, aobj:?, isReplacement: = true)
        {
            if(iobj != nil && dobj != nil && aobj != nil)
                execNestedAction(isReplacement, gActor, altAction, dobj, iobj, aobj);
            else if(iobj != nil && dobj != nil)
                execNestedAction(isReplacement, gActor, altAction, dobj, iobj);
            else if(dobj != nil)
                execNestedAction(isReplacement, gActor, altAction, dobj);
            else
                execNestedAction(isReplacement, gActor, altAction);
        }
    ;

    /*
     *   Thing is the base class for all game objects that represent physical
     *   objects which can be interacted with in the game world. All such physical
     *   objects are either Things or based on a subclass of Thing.
     */
    class Thing:  ReplaceRedirector, Mentionable

        /*
         *   Most of the following properties and methods down to the next dashed
         *   line are usually only relevant on Room, but they have been moved to
         *   Thing in case the player char finds itself in a closed Booth.
         */

        /*
         *   The title of this room to be displayed at the start of a room
         *   description, or in the status line.
         */
        roomHeadline(pov)
        {
            /*
             *   start with the room title; if this room is illuminated use the
             *   standard roomTitle, otherwise use our darkName.
             */
            say(isIlluminated ? roomTitle : darkName);

            /* if the actor is in an intermediate container, add the container */
            if (pov.location not in (self, nil))
                pov.location.roomSubhead(pov);
        }

        /*
         *   Can the player character recognize this room (enough to know its name
         *   and have a rough idea of its location) in the dark? (If so then looking
         *   around in this room in the dark makes it visited and familiar,
         *   otherwise it doesn't).
         */
        recognizableInDark = nil

        /* The name to display at the head of a room description */
        roomTitle = name

        /* The name to display at the head of a room description when it's dark */
        darkName =  BMsg(dark name, 'In the dark')

        /* The description of the room when it's dark */
        darkDesc()
        {
            DMsg(dark desc, 'It{dummy} {is} pitch black; {i} {can\'t} see a thing.
                ');
        }

        /*
         *   The "inside" description.  This is displayed when an actor LOOKS AROUND
         *   from within this object.  Note that this applies not only to top-level
         *   rooms but also to things like chairs, platforms, and booths that can
         *   contain an actor.  By default, we simply show the ordinary EXAMINE
         *   description (or the darkDesc if there's no illumination).  Non-room
         *   containers such as chairs or booths should usually override this to
         *   provide the view from inside the object, which usually differs from the
         *   ordinary EXAMINE description.  For a top-level room, you don't usually
         *   override this, since the only description needed for a room is normally
         *   the LOOK AROUND perspective.
         */
        interiorDesc = (desc)

        /*
         *   If we're a room, are we illuminated (is there enough light for an actor
         *   within us to see by)?
         */
        isIlluminated()
        {
            /*
             *   If the room itself is lit, then it's self-illuminating and we don't
             *   need to check anything else.
             */
            if(isLit)
                return true;

            /*
             *   Otherwise we need to see if there's anything visible in the room's
             *   contents that's lit.
             */
            return isThereALightSourceIn(contents);
        }

        /*
         *   Determine (recursively) whether lst contains a light source; i.e.
         *   whether any of the items within list is lit or whether any of the
         *   visible contents of any of the items in lst it lit.
         */
        isThereALightSourceIn(lst)
        {
            foreach(local obj in lst)
            {
                /* If we find an object that's lit, return true. */
                if(obj.isLit)
                    return true;

                /*
                 *   If we have any contents and our contents are visible from
                 *   outside us, return true if there's a light source among our
                 *   contents.
                 */
                if(obj.contents.length > 0
                   && (obj.isOpen || obj.contType != In || obj.isTransparent)
                   && isThereALightSourceIn(obj.contents))
                    return true;

            }

            /* If we get this far we haven't found a light source. */
            return nil;
        }

        /*
         *   The contents lister to use for listing this room's miscellaneous
         *   contents. By default we use the standard lookLister but this can be
         *   overridden to use a CustomRoomLister (say) to provide just about any
         *   wording we like.
         */
        roomContentsLister = lookLister

        /*
         *   The contents lister to use for listing this room's miscellaneous
         *   subcontents. By default we use the standard lookContentsLister but this
         *   can be overridden.
         */
        roomSubContentsLister = lookContentsLister

        /*
         *   Look around within this Thing (Room or Booth) to provide a full
         *   description of this location as seen from within, including our
         *   headline name, our internal description, and a listing of our visible
         *   contents.
         */
        lookAroundWithin()
        {
             /* Reset everything in the room to not mentioned. */
            unmention(contents);

            /* Reset everything in any remote rooms we're connected to */
            unmentionRemoteContents();

            /* Begin by displaying our name */
            "<.roomname><<roomHeadline(gPlayerChar)>><./roomname>\n";

            /* If we're illuminate show our description and list our contents. */
            if(isIlluminated)
            {
                /* Display our interior description. */
                if(gameMain.verbose || !visited || gActionIs(Look))
                    "<.roomdesc><<interiorDesc>><./roomdesc><.p>";

                /* List our contents. */
                "<.roomcontents>";
                listContents();
                "<./roomcontents>";

                /* Note that we've been seen, examined and visited. */
                setSeen();
                visited = true;
                examined = true;
            }

            /*
             *   Otherwise, if there's not enough light to see by, just show our
             *   dark description.
             */
            else
            {
                /* Display the darkDesc */
                "<.roomdesc><<darkDesc>><./roomdesc>";

                /*
                 *   If this location is recognizable to the player character in the
                 *   dark despite the poor lighting (for example, the PC knows it's
                 *   a cellar because s/he's just descended a flight of steps that
                 *   clearly lead to a cellar), note that we've been visited and
                 *   that we're now known about (the pc knows of our existence).
                 */
                if(recognizableInDark)
                {
                    visited = true;
                    setKnown();
                }

            }
            "<.p>";

            /* If the game includes an exit lister, list our exits. */
            if(gExitLister != nil)
                gExitLister.lookAroundShowExits(gActor, self, isIlluminated);
        }

        /* List the contents of this object using lister. */
        listContents(lister = &roomContentsLister)
        {

            /* Don't list the contents if we can't see in */
            if(!canSeeIn())
                return;

            /*
             *   Set up a variable to contain the list of objects with specialDescs
             *   to be shown before the list of miscellaneous contents.
             */
            local firstSpecialList = [];

            /* Set up a variable to contain of list of miscellaneous contents. */
            local miscContentsList = [];

            /*
             *   Set up a variable to contain the list of objects with specialDescs
             *   to be shown after the list of miscellaneous contents.
             */
            local secondSpecialList = [];

            /*
             *   First mention the actor's immediate container, if it isn't the
             *   object we're looking around within. Then list the oontainer's
             *   contents immediately after.
             */
            local loc = gActor.location;

            /*
             *   If we're not the pc's immediate container and we're looking around,
             *   start by describing the pc's immediate container and listing its
             *   contents.
             */
            if(loc != self && lister == &roomContentsLister)
            {
                /*
                 *   If there isn't a current action (e.g. because we're showing a
                 *   room description before the first turn) create a Look Action to
                 *   provide an action context for the gMessageParams() call that
                 *   follows.
                 */
                if(gAction == nil)
                    gAction = Look.createInstance();

                /* Create a message parameter substitution. */
                gMessageParams(loc);

                /* Start by mentioning the PC's immediate container. */
                DMsg(list immediate container, '{I} {am} {in loc}. <.p>');

                /* Note that the pc's immediate container has been mentioned. */
                loc.mentioned = true;

                /*
                 *   If the pc's immediate container is a subcomponent (of a complex
                 *   container object), note that its lexical parent has been
                 *   mentioned.
                 */
                if(loc.ofKind(SubComponent) && loc.lexicalParent != nil)
                    loc.lexicalParent.mentioned = true;

                /* List the contents of the pc's immediate container. */
                listSubcontentsOf([loc]);
            }

            /* List every listable item in our contents. */
            foreach(local obj in contents)
            {
                /* Don't include any hidden items in the listing */
                if(obj.isHidden)
                    continue;

                /*
                 *   If the object has an initSpecialDesc or a specialDesc which is
                 *   currently in use, add it to the appropriate list.
                 */
                if((obj.propType(&initSpecialDesc) != TypeNil &&
                   obj.useInitSpecialDesc()) ||
                   (obj.propType(&specialDesc) != TypeNil && obj.useSpecialDesc()))
                {
                    /*
                     *   If the specialDesc should be shown before the list of
                     *   miscellaneous items, add this object to the first list of
                     *   specials.
                     */
                    if(obj.specialDescBeforeContents)
                        firstSpecialList += obj;

                    /* Otherwise add it to the second list of specials. */
                    else
                        secondSpecialList += obj;
                }
                /*
                 *   Otherwise add it to the list of miscellaneous items, provided
                 *   it should be listed when looking around.
                 */
                else if(obj.lookListed)
                    miscContentsList += obj;

                /* Note that the object has been seen by the pc. */
                obj.noteSeen();
            }

            /* Sort the first list of specials in order of their specialDescOrder */
            firstSpecialList = firstSpecialList.sort(nil, {a, b: a.specialDescOrder -
                                                     b.specialDescOrder});

            /* Sort the second list of specials in order of their specialDescOrder */
            secondSpecialList = secondSpecialList.sort(nil, {a, b: a.specialDescOrder -
                                                     b.specialDescOrder});

            /*
             *   Show the specialDesc (or initSpecialDesc) of all the objects in the
             *   first specials list.
             */
            foreach(local obj in firstSpecialList)
                obj.showSpecialDesc();

            /*
             *   If we're listing the contents of a room, then show the specialDescs
             *   of any items in the other rooms in our SenseRegions, where
             *   specialDescBeforeContents is true
             */
            if(lister == &roomContentsLister)
                showFirstConnectedSpecials(gPlayerChar);

            /*
             *   Remove any items from the miscellaneous list that have already been
             *   mentioned.
             */
            miscContentsList = miscContentsList.subset({o: o.mentioned == nil});

            /*
             *   Display the list of miscellaneous items using the lister passed as
             *   parameter to this method.
             */
            self.(lister).show(miscContentsList, self);

            /*   List the contents of our contents. */
            listSubcontentsOf(contents, &roomSubContentsLister);

             /*
              *   If we're not putting paragraph breaks between each subcontents
              *   listing sentence, insert a paragraph break after the lot before we
              *   list anything else.
              *
              */
                if(!paraBrksBtwnSubcontents)
                    "<.p>";

            /*
             *   If we're listing the contents of a room, then show the
             *   miscellaneous contents of other rooms in our sense regions
             */
            if(lister == &roomContentsLister)
                showConnectedMiscContents(gPlayerChar);

            /*
             *   Show the specialDesc (or initSpecialDesc) of every object in our
             *   second list of specials.
             */
            secondSpecialList = secondSpecialList.subset({o: o.mentioned == nil});

            foreach(local obj in secondSpecialList)
                obj.showSpecialDesc();

            /*
             *   Show the specialDescs of any items in the other rooms in our
             *   SenseRegions, where specialDescBeforeContents is nil
             */
           if(lister == &roomContentsLister)
               showSecondConnectedSpecials(gPlayerChar);
        }

        /*
         *   List the contents of every item in contList, recursively listing the
         *   contents of contents all the way down the containment tree. The
         *   contList parameter can also be passed as a singleton object.
         */
        listSubcontentsOf(contList, lister = &examineLister)
        {

            /*
             *   If contList has been passed as a singleton value, convert it to a
             *   list, otherwise retain the list that's been passed.
             */
            contList = valToList(contList);

            /*
             *   Ensure the contents of any associated remapXX items are included in
             *   the list of items whose contents are to be listed.
             */

            /* Initialize an empty list to collect the remapXXX items. */
            local lst = [];

            /*
             *   Go through every item in the contList to see if it has any remapXXX
             *   objects attached. If so add the remapXXX object to our list.
             */
            foreach(local cur in contList)
            {
                foreach(local prop in remapProps)
                {
                    local obj = cur.(prop);
                    if(obj != nil)
                        lst += obj;
                }
            }

            /*
             *   Append the list of remapXXX objects to the list of items whose
             *   contents are to be listed.
             */
            contList = contList.appendUnique(lst);

            /*
             *   Sort the contList in listOrder. Although we're listing the contents
             *   of each item in the contList, it seems good to mention each item's
             *   contents in the listOrder order of the item. Amongst other things
             *   this helps give a consistent ordering for the listing of
             *   SubComponents.
             */
            contList = contList.sort(nil, {a, b: a.listOrder - b.listOrder});

            foreach(local obj in contList)
            {
                /*
                 *   We don't explicitly list things in actors' inventory, but we
                 *   should note them as seen if the player can see them.
                 */
                if(obj.contType == Carrier && markInventoryAsSeen)
                    obj.allContents.subset({o: gPlayerChar.canSee(o) }).forEach( {o:
                        o.noteSeen() });

                /*
                 *   Don't list the inventory of any actors, or of any items that
                 *   don't want their contents listed, or any items we can't see in,
                 *   or of any items that don't have any contents to list.
                 */
                if(obj.contType == Carrier
                   || obj.(obj.(lister).contentsListedProp) == nil
                   || obj.canSeeIn() == nil
                   || obj.contents.length == 0)
                    continue;

                /*
                 *   Don't list any items that have already been mentioned or which
                 *   are hidden.
                 */
                local objList = obj.contents.subset({x: x.mentioned == nil
                                                    && x.isHidden == nil
                                                    && x != gPlayerChar});

                /*
                 *   Extract the list of items that have active specialDescs or
                 *   initSpecial Descs
                 */
                local firstSpecialList = objList.subset(
                    {o: (o.propType(&specialDesc) != TypeNil && o.useSpecialDesc())
                    || (o.propType(&initSpecialDesc) != TypeNil &&
                        o.useInitSpecialDesc() )
                    }
                    );

                /*
                 *   Remove items with specialDescs or initSpecialDescs from the
                 *   list of miscellaneous items.
                 */
                objList = objList - firstSpecialList;

                /*
                 *   From the list of items with specialDescs, extract those whose
                 *   specialDescs should be listed after any miscellaneous items
                 */
                local secondSpecialList = firstSpecialList.subset(
                    { o: o.specialDescBeforeContents == nil });

                /*
                 *   Remove the items whose specialDescs should be listed after the
                 *   miscellaneous items from the list of all items with
                 *   specialDescs to give the list of items with specialDescs that
                 *   should be listed before the miscellaneous items.
                 */
                firstSpecialList = firstSpecialList - secondSpecialList;

                /*
                 *   Sort the list of items with specialDescs to be displayed before
                 *   miscellaneous items by specialDescOrder
                 */
                firstSpecialList = firstSpecialList.sort(nil, {a, b: a.specialDescOrder -
                    b.specialDescOrder});

                /*
                 *   Sort the list of items with specialDescs to be displayed after
                 *   miscellaneous items by specialDescOrder
                 */
                secondSpecialList = secondSpecialList.sort(nil, {a, b: a.specialDescOrder -
                    b.specialDescOrder});

                /*
                 *   Show the specialDescs of items whose specialDescs should be
                 *   shown before the list of miscellaneous items.
                 */
                firstSpecialList = firstSpecialList.subset({o: o.mentioned == nil});
                foreach(local cur in firstSpecialList)
                    cur.showSpecialDesc();

                objList = objList.subset({o: o.mentioned == nil});
                /*   List the miscellaneous items */
                if(objList.length > 0)
                {
                    obj.(lister).show(objList, obj, paraBrksBtwnSubcontents);
                    objList.forEach({o: o.mentioned = true });
                }

                /*
                 *   If we're not putting paragraph breaks between each subcontents
                 *   listing sentence, insert a space instead.
                 */
                if(!paraBrksBtwnSubcontents && secondSpecialList.indexWhich({o:o.isListed}))
                    " ";

                /*
                 *   Show the specialDescs of items whose specialDescs should be
                 *   shown after the list of miscellaneous items.
                 */
                secondSpecialList = secondSpecialList.subset({o: o.mentioned == nil});
                foreach(local cur in secondSpecialList)
                    cur.showSpecialDesc();

                /*
                 *   Recursively list the contents of each item in this object's
                 *   contents, if it has any; but don't list recursively for an
                 *   object that's just been opened (for which the lister's
                 *   listRecursively property should be nil) or for which there are
                 *   no listable contents.
                 */
                local lstr = obj.(lister);

                if(obj.contents.length > 0 && lstr.listRecursively
                   && obj.contents.countWhich({x: lstr.listed(x)}) > 0)
                    listSubcontentsOf(obj.contents, lister);

            }

        }

        /*
         *   Do we want paragraph breaks between the listings of subcontents (i.e.
         *   the contents of this item's contents)? By default we take our value
         *   from the global setting on gameMain.
         */
        paraBrksBtwnSubcontents = (gameMain.paraBrksBtwnSubcontents)

        /*
         *   Mark everything item in lst as not mentioned , and carry on down the
         *   containment tree marking the contents of every item in lst as not
         *   mentioned.
         */
        unmention(lst)
        {
            foreach(local obj in lst)
            {
                obj.mentioned = nil;

                /* If obj has any contents, unmention every item in is contents */
                if(obj.contents.length > 0)
                    unmention(obj.contents);
            }
        }

        /*
         *   The next four methods are provided so that listContents() can call
         *   them, but they do nothing in the core library. They are overridden in
         *   senseRegion.t (for use if senseRegion.t is included in the build).
         */
        unmentionRemoteContents() {}
        showFirstConnectedSpecials(pov) {}
        showConnectedMiscContents(pov) {}
        showSecondConnectedSpecials(pov) {}

        /*
         *   Display the "status line" name of the room.  This is normally a
         *   brief, single-line description.
         *
         *   By long-standing convention, each location in a game usually has a
         *   distinctive name that's displayed here.  Players usually find
         *   these names helpful in forming a mental map of the game.
         *
         *   By default, if we have an enclosing location, and the actor can
         *   see the enclosing location, we'll defer to the location.
         *   Otherwise, we'll display our roo interior name.
         */
        statusName(actor)
        {
            /*
             *   use the enclosing location's status name if there is an
             *   enclosing location and its visible; otherwise, show our
             *   interior room name
             */
            if (location != nil && Q.canSee(actor, location))
                location.statusName(actor);
            else
            {
                roomHeadline(actor);
            }
        }

        /*
         *   Get the estimated height, in lines of text, of the exits display's
         *   contribution to the status line.  This is used to calculate the
         *   extra height we need in the status line, if any, to display the
         *   exit list.  If we're not configured to display exits in the status
         *   line, this should return zero.
         */
        getStatuslineExitsHeight()
        {
            if (gExitLister != nil)
                return gExitLister.getStatuslineExitsHeight();
            else
                return 0;
        }

        /* Show our exits in the status line */
        showStatuslineExits()
        {
            location.showStatuslineExits();
        }

        /*
         *   Would this location be lit for actor. By default it would if it's
         *   illuminated.
         */
        wouldBeLitFor(actor)
        {
            return getOutermostRoom.isIlluminated;
        }

    //------------------------------------------------------------------------------
        /*
         *   From here on we have properties and methods relating to Things in
         *   general rather than just Rooms and Booths.
         */

        /*
         *   The description of this Thing that's displayed when it's examined.
         *   Normally this would be defined as a double-quoted string, but in more
         *   complicated cases you could also define it as a method that displays
         *   some text.
         */
        desc = ""

        /*
         *   The state-specific description of this object, which is appended to its
         *   desc when examined. This is defined as a single-quoted string to make
         *   it easy to change at run-time.
         */
        stateDesc = ''

        /*
         *   Additional information to display after our desc in response to an
         *   EXAMINE command.
         */
        examineStatus()
        {
            /* First display our stateDesc (our state-specific information) */
            display(&stateDesc);

            /*
             *   Then display our list of contents, unless we're a Carrier (an actor
             *   carrying our oontents) or our contentsListedInExamine is nil.
             */
            if(contType != Carrier && contentsListedInExamine)
            {
                /*
                 *   Start by marking our contents as not mentioned to ensure that
                 *   they all get listed.
                 */
                unmention(contents);

                /* Then list our contents using our examineLister. */
                listSubcontentsOf(self, &examineLister);
            }
        }

        /* The lister to use to list an item's contents when it's examined. */
        examineLister = descContentsLister

        /*
         *   Attempt to display prop appropriately according to it data type
         *   (single-quoted string, double-quoted string, integer or code). The prop
         *   parameter must be provided as a property pointer.
         */
        display(prop)
        {
            local str;
            switch(propType(prop))
            {
                /*
                 *   If prop is a single-quoted string or an integer, simply display
                 *   it.
                 */
            case TypeSString:
            case TypeInt:
                say(self.(prop));
                break;

                /* If prop is a double-quoted string, display it by executing it. */
            case TypeDString:
                self.(prop);
                break;

                /* if prop is a method, execute it. */
            case TypeCode:
                /*
                 *   In case prop is a method that returns a single-quoted string,
                 *   note the return value from executing prop.
                 */
                str = self.(prop);

                /* If it's a string, display it. */
                if(dataType(str) == TypeSString && str > '')
                    say(str);
                break;
            default:
                /* do nothing */
                break;
            }
        }

        /*
         *   Attempt to display the message defined in the property prop, and return
         *   true if anything is displayed. Otherwise, if the altMsg parameter is
         *   supplied (either as a single-quoted string or as a property pointer)
         *   display it instead, and then in any case return nil to tell the caller
         *   that nothing was displayed by prop.
         *
         *   This method is primarily for use with properties such as smellDesc and
         *   listenDesc for which alternatives may need to be displayed if they
         *   don't display anything.
         */

        displayAlt(prop, altMsg?)
        {
            /*
             *   If attempting to display the prop property results in some output,
             *   return true to inform our caller of the fact.
             */
            if(gOutStream.watchForOutput({: display(prop) }))
                return true;

            /*
             *   If we reach this point, prop failed to produce any output, so if
             *   altMsg has been provided as a single-quoted string, display it.
             */
            if(dataType(altMsg) == TypeSString)
                say(altMsg);

            /*
             *   Otherwise, if altMsg has been provided as a property pointer,
             *   display it using the display() method.
             */
            if(dataType(altMsg) == TypeProp)
                display(altMsg);

            /*
             *   Tell our caller that there was no output from attempting to display
             *   prop.
             */
            return nil;
        }

        /*
         *   Check if displaying prop could possibly produce any output. The only
         *   tests we apply here is that prop is not defined as nil.
         */
        checkDisplay(prop)
        {
            return propType(prop) != TypeNil;
        }

        /*
         *   Has this item been mentioned yet in a room description. Note that this
         *   flag is used internally by the library; it shouldn't normally be
         *   necessary to manipulate it directly from game code.
         */
        mentioned = nil

        /*
         *   Do we want this object to report whether it's open? By default we do if
         *   it's both openable and open.
         */
        openStatusReportable = (isOpenable && isOpen)

        /*
         *   If present, a description of this object shown in a separate paragraph
         *   in the listing of the contents of a Room. If specialDesc is defined
         *   then this paragraph will be displayed regardless of the value of
         *   isListed.
         */
        specialDesc = nil

        /*
         *   Should the specialDesc be used? Normally we use the specialDesc if we
         *   have one, but we may want to override this for particular cases. For
         *   example, if we want an item to have a paragraph to itself until it's
         *   moved we could define useSpecialDesc = (!moved) [making it equivalent
         *   to initSpecialDesc]. Note that the useSpecialDesc property only
         *   has any effect if specialDesc is not nil.
         */
        useSpecialDesc = true

        /* A specialDesc that's shown until this item has been moved */
        initSpecialDesc = nil

        /*
         *   By default we use the initSpecialDesc until the object has been moved,
         *   but this can be overridden with some other condition.
         */
        useInitSpecialDesc = (!moved)

        /*
         *   The specialDescOrder property controls where in a series of specialDesc
         *   paragraphs this item is mentioned: the higher the number, the later it
         *   will come relative to other items. Note that this does not override the
         *   specialDescBeforeContents setting.
         */
        specialDescOrder = 100

        /*
         *   Is this item listed before or after the list of miscellaneous contents
         *   in the room. By default we'll show the specialDesc of items before
         *   miscellaneous items in a room description but afterwards otherwise:
         *   this places specialDescs in a more logical order in relation to the
         *   text of listers used to list the contents of obejcts other than rooms.
         */
        specialDescBeforeContents = (location && location.ofKind(Room))

        /* For possible future use; at the moment this doesn't do anything */
        specialDescListWith = nil

        /* Show our specialDesc or initSpecialDesc, as appropriate */
        showSpecialDesc()
        {
            /*
             *   If we've already been mentioned in the room description, don't show
             *   us again. Otherwise note that we've now been mentioned.
             */
            if(mentioned)
                return;

            /*
             *   If we have an initSpecialDesc and useInitSpecialDesc is true, show
             *   our initSpecialDesc, otherwise show our specialDesc.
             */
            if(propType(&initSpecialDesc) != TypeNil && useInitSpecialDesc)
            {
                initSpecialDesc;

                mentioned = true;
            }
            else if(propType(&specialDesc) != TypeNil)
            {
                specialDesc;

                mentioned = true;
            }

            /*  Add a paragraph break. */
            if(mentioned)
                "<.p>";

            /* Note that we've been seen. */
            noteSeen();
        }

        /*
         *   Flag to indicate whether this item is portable (nil) or fixed in place
         *   (true). If it's fixed in place it can't be picked up or moved around
         *   (by player commands).
         */
        isFixed = (isDecoration)

        /*
         *   Is this item listed in room descriptions and the like. We tend to list
         *   portable items but not those fixed in place, so we make this the
         *   default.
         */

        /*
         *   A global isListed property that can be used to set the value of all the
         *   others. By default we're listed if we're not fixed in place.
         */
        isListed = (!isFixed)

        /*
         *   Flag: is this item listed in a room description (when looking around).
         */
        lookListed = (isListed)

        /* Flag: is this item listed in an inventory listing. */
        inventoryListed = (isListed)

        /* Flag: is this item listed when its container is examined. */
        examineListed = (isListed)

        /*
         *   Flag: is this item listed when is container is searched (or looked in).
         */
        searchListed = (isListed)

        /*
         *   Flag: should this item's contents be listed? This can be used to
         *   control both contentsListedInLook and contentsListedInExamine.
         */
        contentsListed = true

        /*
         *   Flag: should this item's contents be listed as part of a room
         *   description (when looking around).
         */
        contentsListedInLook = (contentsListed)

        /*
         *   Flag: should this item's contents be listed when its container is
         *   examined.
         */
        contentsListedInExamine = (contentsListed)

        /*
         *   Flag, should this item's contents be listed when it is searched (by
         *   default this is simply true, since it would be odd to have a container
         *   that failed to reveal its contents when searched).
         */
        contentsListedInSearch = true

        /*
         *   Flag, if our contType is Carrier (i.e. we're an Actor), should our
         *   contents be marked as seen even though it hasn't been listed in a room
         *   description? By default this is set to true, on the basis that the
         *   inventory (and parts) of an actor would normally be in plain sight.
         */
        markInventoryAsSeen = true

        /*
         *   The text we display in response to a READ command. This can be nil
         *   (if we're not readable), a single-quoted string, a double-quoted string
         *   or a routine to display a string.     */

        readDesc = nil

        /* The description displayed in response to a SMELL command */
        smellDesc = nil

        /*
         *   Is the this object's smellDesc displayed in response to an intransitive
         *   SMELL command? (Only relevant if smellDesc is not nil)
         */
        isProminentSmell = true

        /*   The description displayed in response to a FEEL command */
        feelDesc = nil

        /*   The description displayed in response to a LISTEN command */
        listenDesc = nil

        /*
         *   Is the this object's listenDesc displayed in response to an
         *   intransitive LISTEN command? (Only relevant if listenDesc is not nil)
         */
        isProminentNoise = true

        /*   The description displayed in response to a TASTE command */
        tasteDesc = nil

        /*  The subset of our contents that should be listed. */
        listableContents = (contents.subset({x: x.lookListed}))

        /* The subset of the contents of cont that should be listed. */
        listableContentsOf(cont)
        {
            local lst = [];
            foreach(local obj in cont.contents)
            {
                if(obj.isListed)
                    lst += obj;
            }
            return lst;
        }

        /*
         *   Our globalParamName is an arbitrary string value that can be used to
         *   refer to this thing in a message substitution parameter; for code
         *   readability it may be a good idea to make this a string representation
         *   of our programmatic name (where we want to define it at all).
         */
        globalParamName = nil

        /*
         *   Is this object lit, i.e. providing sufficient light to see not only
         *   this object but other objects in the vicinity by.
         */
        isLit = nil

        /* Make this object lit or unlit */
        makeLit(stat) { isLit = stat; }

        /*
         *   Is this object visible in the dark without (necessarily) providing
         *   enough light to see anything else by, e.g. the night sky.
         */
        visibleInDark = nil

        /*
         *   An optional description to be displayed instead of our normal desc and
         *   any status information (such as our contents) if we're examined in a
         *   dark room and visibleInDark is true. Note that if visibleInDark is nil
         *   inDarkDesc will never be used.
         */
        inDarkDesc = nil

        /*
         *   Is this object lightable (via a player command)? Note that setting this
         *   property to true also automatically makes the LitUnlit State applicable
         *   to this object, allowing it to be referred to as 'lit' or 'unlit' as
         *   appropriate.
         */
        isLightable = nil

        /*
         *   The preposition that should be used to describe containment within this
         *   thing (e.g. 'in', 'on' , 'under' or 'behind'). By default we get this
         *   from our contType.
         */
        objInPrep = (contType.prep)

        /*
         *   The preposition that should be used to describe movement to within this
         *   thing (e.g. 'into', 'onto' , 'under' or 'behind'). By default we get
         *   this from our contType.
         */
        objIntoPrep = (contType.intoPrep)

        /*
         *   This object's bulk, in arbitrary units (game authors should devise
         *   their own bulk scale according to the needs of their game).
         */
        bulk = 0

        /*
         *   The maximum bulk that can be contained in this Thing. We set a very
         *   large number by default.
         */
        bulkCapacity = 10000

        /*
         *   The maximum bulk that a single item may have to be inserted into (onto,
         *   under, behind) this object; by default this is the same as the bulk
         *   capacity, but you could set a lower value, e.g. to model a bottle with
         *   a narrow neck.
         */
        maxSingleBulk = (bulkCapacity)

        /*   Calculate the total bulk of the items contained within this object. */
        getBulkWithin()
        {
            local totalBulk = 0;
            foreach(local cur in contents)
                totalBulk += cur.bulk;

            return totalBulk;
        }

        /*
         *   Calculate the total bulk carried by an actor, which excludes the bulk
         *   of any items currently worn or anything fixed in place.
         */
        getCarriedBulk()
        {
            local totalBulk = 0;
            foreach(local cur in directlyHeld)
            {
                totalBulk += cur.bulk;
            }

            return totalBulk;
        }

        /*
         *   Check whether an item can be inserted into this object, or whether
         *   doing so would either exceed the total bulk capacity of the object or
         *   the maximum bulk allowed for a single item.
         */
        checkInsert(obj)
        {
            /* Create a message parameter substitution. */
            gMessageParams(obj);

            /*
             *   If the bulk of obj is greater than the maxSingleBulk this Thing can
             *   take, or greater than its overall bulk capacity then display a
             *   message to say it's too big to fit inside ue.
             */
            if(obj.bulk > maxSingleBulk || obj.bulk > bulkCapacity)
                DMsg(too big, '{The subj obj} {is} too big to fit {1} {2}. ',
                     objInPrep, theName);

            /*
             *   Otherwise if the bulk of obj is greater than the remaining bulk
             *   capacity of this Thing allowing for what it already contains,
             *   display a message saying there's not enough room for obj.
             */
            else if(obj.bulk > bulkCapacity - getBulkWithin())
                DMsg(no room, 'There {dummy} {is} not enough room {1} {2} for {the
                    obj}. ', objInPrep, theName);
        }

        /* The list of possible remap props */
        remapProps = [&remapOn, &remapIn, &remapUnder, &remapBehind]

        /*
         *   If remapIn is not nil, a LOOK IN, PUT IN, OPEN, CLOSE, LOCK or UNLOCK
         *   command performed on this Thing will be redirected to the object
         *   specified on remapIn. In other words, remapIn specifies the object that
         *   acts as our proxy container.
         */
        remapIn = nil

        /*
         *   If non-nil, remapOn speficies the object that acts as our proxy
         *   surface, in other words the object to which PUT ON will be redirected.
         */
        remapOn = nil

        /*
         *   If non-nil, remapUnder specified the object that acts as our proxy
         *   underside, i.e. the object to which any PUT UNDER or LOOK UNDER action
         *   directed at us will be redirected.
         */
        remapUnder = nil

        /*
         *   If non-nil, remapUnder specified the object that acts as our proxy
         *   rear, i.e. the object to which any PUT BEHIND or LOOK BEHIND action
         *   directed at us will be redirected.
         */
        remapBehind = nil

        /*
         *   Our notional total contents is our normal contents plus anything
         *   contained in any of our remapXX objects representing our associated
         *   proxy container, surface, underside and rear, excluding anything in a
         *   closed opaque container (which would not be visible).
         */
        notionalContents()
        {
            local nc = [];

            if(isTransparent || !enclosing)
                nc = contents;
            if(remapIn != nil && (remapIn.isTransparent || !remapIn.enclosing))
                nc = nc + remapIn.contents;
            if(remapOn != nil)
                nc = nc + remapOn.contents;
            if(remapUnder != nil)
                nc = nc + remapUnder.contents;
            if(remapBehind != nil)
                nc = nc + remapBehind.contents;

            return nc;
        }

        /*
         *   A list of objects that are treated as hidden under this one. A LOOK
         *   UNDER command will list them and move them into the enclosing room. It
         *   follows that objects placed in this property should not be given an
         *   initial location. This should deal with the most common reason for
         *   wanting items to be placed under things (i.e. to hide them). Note, the
         *   items in the hiddenUnder property should also be revealed when the
         *   player moves the hiding item.
         */
        hiddenUnder = []

        /*
         *   A list of objects that are treated as hidden behind this one. A LOOK
         *   BEHIND command will list them and move them into the enclosing room. It
         *   follows that objects placed in this property should not be given an
         *   initial location. This should deal with the most common reason for
         *   wanting items to be placed behind things (i.e. to hide them). Note, the
         *   items in the hiddenBehind property should also be revealed when the
         *   player moves the hiding item.
         */
        hiddenBehind = []

        /*
         *   A list of objects that are treated as hidden inside this one. A LOOK IN
         *   command will list them and move them into the enclosing room (or in
         *   this one if we're a container). It follows that objects placed in this
         *   property should not be given an initial location.
         */
        hiddenIn = []

        /*
         *   The maximum bulk that can be hidden under, behind or in this object,
         *   assuming that the player can put anything there at all. Note that this
         *   only affects what the player can place there with PUT IN, PUT UNDER and
         *   PUT BEHIND commands, not what can be defined there initially or moved
         *   there programmatically.
         */
        maxBulkHiddenUnder = 100
        maxBulkHiddenBehind = 100
        maxBulkHiddenIn = 100

        /* The total bulk of items hidden in, under or behind this object */
        getBulkHiddenUnder = (totalBulkIn(hiddenUnder))
        getBulkHiddenIn = (totalBulkIn(hiddenIn))
        getBulkHiddenBehind = (totalBulkIn(hiddenBehind))

        /* Calculate the total bulk of the items in lst */
        totalBulkIn(lst)
        {
            local totBulk = 0;
            for(local item in valToList(lst))
                totBulk += item.bulk;

            return totBulk;
        }

        /*
         *   Flag, do we want to treat this object as hidden from view (so that the
         *   player can't interact with it)?
         */
        isHidden = nil

        /*
         *   Make a hidden item unhidden. If the method is called with the optional
         *   parameter and the parameter is nil, i.e. discover(nil), the method
         *   instead hides the object.
         */
        discover(stat = true)
        {
            isHidden = !stat;

            /*
             *   If the player character can see me when I'm hidden, note that the
             *   player character has now seen me.
             */
            if(stat && Q.canSee(gPlayerChar, self))
                noteSeen();
        }

        /*
         *   The lockability property determines whether this object is lockable and
         *   if so how. The possible values are notLockable, lockableWithoutKey,
         *   lockableWithKey and indirectLockable.
         */
        lockability = keyList == nil ? notLockable : lockableWithKey

        /*
         *   Flag: is this object currently locked. By default we start out locked
         *   if we're lockable.
         */
        isLocked = lockability not in (nil, notLockable)

        /*
         *   Make us locked or ublocked. We define this as a method so that
         *   subclasses such as Door can override to produce side effects (such as
         *   locking or unlocking the other side).
         */
        makeLocked(stat)
        {
            isLocked = stat;
        }

        /*
         *   Can this object be switched on and off?
         */
        isSwitchable = nil

        /* is this item currently switched on? */
        isOn = nil

        /* switch this item on or off */
        makeOn(stat) { isOn = stat; }

        /* is this object something that can be worn */
        isWearable = nil

        /*
         *   If this object is currently being worn by someone, the wornBy property
         *   contains the identity of the person wearing it.
         */
        wornBy = nil

        /*
         *   Make this object worn or not worn. If this object is worn, note who
         *   it's worn by. If stat is nil the object is no longer being worn.
         */
        makeWorn(stat)  { wornBy = stat; }

        /* are we directly held by the given object? */
        isDirectlyHeldBy(obj) { return location == obj && !isFixed && wornBy == nil; }

        /*
         *   Get everything I'm directly holding, which is everything in my
         *   immediate contents which is neither fixed in place nor being worn.
         */
        directlyHeld = (contents.subset({ obj: !obj.isFixed &&
                obj.wornBy == nil }))

        /* are we worn by the given object, directly or indirectly? */
        isWornBy(obj)
        {
            return (location == obj ? wornBy == obj :
                    location != nil && location.isWornBy(obj));
        }

        /* are we directly worn by the given object? */
        isDirectlyWornBy(obj) { return location == obj && wornBy == obj; }

        /* get everything I'm directly wearing */
        directlyWorn = (contents.subset({ obj: obj.wornBy == self }))

        /*
         *   Flag: can under objects be placed under us? By default they can if our
         *   contType is Under. If this is set to true and our contType is not
         *   Under, anything placed under us will be treated as hidden under.
         */
        canPutUnderMe = (contType == Under)

        /*
         *   Flag: can under objects be placed behind us? By default they can if our
         *   contType is Behind. If this is set to true and our contType is not
         *   Behind, anything placed behind us will be treated as hidden behind.
         */
        canPutBehindMe = (contType == Behind)

        /*
         *   Flag: can under objects be placed inside us? By default they can if our
         *   contType is In. If this is set to true and our contType is not
         *   In, anything placed in us will be treated as hidden in.
         */
        canPutInMe = (contType == In)

        /*
         *   Can an actor enter (get in or on) this object. Note that for such an
         *   action to be allowing the contType must also match the proposed action.
         */
        isBoardable = nil

        /* Flag: Can this thing be eaten */

        isEdible = nil

        /*
         *   My nominal contents is the special contents item we can use in
         *   naming the object.  This is useful for containers whose identities
         *   come primarily from their contents, such as a vessel for liquids
         *   or a box of loose files.  Returns an object that qualifies the
         *   name: a "water" object for BOX OF WATER, a "files" object for BOX
         *   OF FILES.  Nil means that the object isn't named by its contents.
         *
         *   Note that this is always a single object (or nil), not the whole
         *   list of contents.  We can only be named by one content object.
         *   (So you can't have a "box of books and papers" by having separate
         *   nominal contents objects for the books and the papers; although
         *   you could fake it by creating a "books and papers" object.)
         */
        nominalContents = nil

        /*
         *   Can I be distinguished in parser messages by my contents?  If so,
         *   we can be distinguished (in parser messages) from similar objects
         *   by our contents, or lack thereof: "bucket of water" vs "empty
         *   bucket".  If this is true, our nominalContents property determines
         *   the contents we display for this.
         */
        distinguishByContents = nil

          /*
           *   This object's containment type - that is, the locType for direct
           *   children.  This is given as one of the spatial relation types (In,
           *   On, Under, Behind etc).
           */
        contType = Outside

        /* The list of things directly contained by this object */
        contents = [ ]

        /*
         *   The location of this object, i.e. this object's immediate container
         *   (which may be another Thing, a Room, or an Actor such as the player
         *   char). Note that while you should specify the initial location of an
         *   object via this property you should never directly alter this property
         *   in game code thereafter; to change the location on object during the
         *   the course of a game use the moveInto(loc) or actionMoveInto(loc)
         *   method.
         */
        location = nil

        /*
         *   Add an item to this object's contents. Normally this method is used
         *   internally in the library than directly by game code. If the vec
         *   parameter is supplied, the object added to our contents is also added
         *   to vec; again this is intended primarily for internal use by the
         *   library.
         */
        addToContents(obj, vec?)
        {
            contents = contents.appendUnique([obj]);
            if(vec != nil)
                vec.appendUnique(self);
        }

        /*
         *   Remove an item to this object's contents. Normally this method is used
         *   internally in the library than directly by game code. If the vec
         *   parameter is supplied, the object removed from our contents is also
         *   removed from vec; again this is intended primarily for internal use by
         *   the library.
         */
        removeFromContents(obj, vec?)
        {
            local idx = contents.indexOf(obj);
            if(idx != nil)
                contents = contents.removeElementAt(idx);

            if(vec != nil)
                vec.removeElement(self);
        }

        /*
         *   Basic moveInto for moving an object from one container to another by
         *   programmatic fiat.
         */
        moveInto(newCont)
        {
            /* If we have a location, remove us from its list of contents. */
            if(location != nil)
                location.removeFromContents(self);

            /*
             *   If we have changed location, we are no longer being worn by our
             *   original location
             */
            if(newCont != location)
                wornBy = nil;

            /* Set our new location. */
            location = newCont;

            /*
             *   Provided our new location isn't nil, add us to our new location's
             *   list of contents.
             */
            if(location != nil)
                location.addToContents(self);
        }

        /* Move into generated by a user action, which includes notifications */
        actionMoveInto(newCont)
        {
            /*
             *   If we have a location, notify our existing location that we're
             *   about to be removed from it.
             */
            if(location != nil)
                location.notifyRemove(self);

            /*
             *   If the location we're about to be moved into is non-nil, notify our
             *   new location that we're about to be moved into it. Note that both
             *   this and the previous notification can veto the move with an exit
             *   command.
             */
            if(newCont != nil)
                newCont.notifyInsert(self);

            /* Carry out the move. */
            moveInto(newCont);

            /* Note that we have been moved. */
            moved = true;

            /* If the player character can now see us, note that we've been seen */
            if(Q.canSee(gPlayerChar, self))
                noteSeen();
        }

        /*
         *   Receive notification that obj is about to be removed from inside us; by default we do
         *   nothing. Do NOT use this method to prevent the removal of the object from us; use
         *   checkRemove(obj) instead.
         */
        notifyRemove(obj) { }

        /*
         *   checkRemove is called from the check stage of an action (typically TAKE) that might remove
         *   obj from me. If it wants to object to the removal of the object, it should simply display a
         *   message explaining why. By default we call the same method our container to check whether
         *   anything in our containment hierarchy objects to the removal. If this method is overridden
         *   in game code it may only need to call inherited(obj) on any branch that doesn't itself
         *   object to the removal.
         */
        checkRemove(obj)
        {
            if(location)
                location.checkRemove(obj);
        }

        /*
         *   Receive notification that obj is about to be inserted into us; by
         *   default we do nothing.
         */
        notifyInsert(obj) { }

        /*
         *   Move a MultiLoc (ml) into this additional Thing or Room, by adding it
         *   to this thing's contents list and adding the Thing to ml's
         *   locationList.
         */
        moveMLIntoAdd(ml)
        {
            if(contents.indexOf(ml) == nil)
                addToContents(ml);

            if(ml.locationList.indexOf(self) == nil)
                ml.locationList += self;
        }

        /*
         *   Move a MultiLoc (ml) out of this object, by removing it from our
         *   contents list and removing us from its locationList.
         */
        moveMLOutOf(ml)
        {
            removeFromContents(ml);

            ml.locationList -= self;
        }

        /* Is obj visible from us? */
        canSee(obj) { return Q.canSee(self, obj); }

        /* Is obj audible from us? */
        canHear(obj) { return Q.canHear(self, obj); }

        /* Is obj smellable from us? */
        canSmell(obj) { return Q.canSmell(self, obj); }

        /* Is obj reachable (by touch) from us? */
        canReach(obj) { return Q.canReach(self, obj); }

         /*
         *   Are we a containment "child" of the given object with the given
         *   location type?  This returns true if our location is the given
         *   object and our locType is the given type, or our location is a
         *   containment child of the given object with the given type.
         *
         *   'typ' is a LocType giving the relationship to test for, or nil.
         *   If it's nil, we'll return true if we have any containment
         *   relationship to 'obj'.
         */
        isChild(obj, typ)
        {
            /*
             *   If the typ we're testing for is neither nil nor the containment
             *   type of obj, return nil.
             */
            if(typ not in (nil, obj.contType))
                return nil;

            /* Otherwise return whether or not we're in obj. */
            return isIn(obj);
        }

        /*
         *   Are we a direct containment child of the given object with the
         *   given containment type?  'typ' is a LocType giving the
         *   relationship to test for, or nil.  If it's nil, we'll return true
         *   if we have any direct containment relationship with 'obj'.
         */
        isDirectChild(obj, typ)
        {
            /*
             *   If the typ we're testing for is neither nil nor the containment
             *   type of obj, return nil.
             */
            if(typ not in (nil, obj.contType))
                return nil;

            /* Otherwise return whether or not we're directly in obj. */
            return isDirectlyIn(obj);
        }

        /*  Are we directly in cont? */
        isDirectlyIn(cont)
        {
            /* If cont is nil then we're in cont if our location is nil. */
            if(cont == nil)
                return location == nil;

            /*
             *   Otherwise we're directly in cont either if our location is cont or
             *   if we're in cont's contents list (the latter test caters for
             *   MultiLocs).
             */
            return location == cont || valToList(cont.contents).indexOf(self) != nil;
        }

        /* Are we in cont? */
        isIn(cont)
        {
            /* If we're directly in cont, then we're certainly in cont. */
            if(isDirectlyIn(cont))
                return true;

            /* Otherwise if out location is nil, we're not in cont */
            if(location == nil)
                return nil;

            /* Otherwise we're in cont if our location is in cont. */
            return location.isIn(cont);
        }

        /* Are either oont or in cont ? */
        isOrIsIn(cont)
        {
            return self == cont || isIn(cont);
        }

         /*
         *   Get my list of enclosed direct contents.  This is the subset of my
         *   direct contents that have interior location types (In).
         */
        intContents = ( contType == In ? contents : [] )

        /*
         *   Get my list of unenclosed direct contents.  This is the subset of
         *   my direct contents that have exterior location types (On, Outside,
         *   Behind, Under).
         */
        extContents = ( contType == In ? [] : contents)

        /*
         *   Am I the Thing object that starts out as the initial player character?
         *   For just about every Thing this will not the case, but this can be
         *   overridden to true on the one Thing, Player or Actor object that is
         *   meant to represent the initial player character. Note, however, that if
         *   gameMain already defines a non-nil initialPlayerChar property, this
         *   will be used to identify the initial player character object whatever
         *   the value of the isInitialPlayerChar on any other object.
         */
        isInitialPlayerChar = nil

        /* Carry out the preinitialization of a Thing */
        preinitThing()
        {
            /*
             *   If I am meant to be the initial player character and gameMain does
             *   not already define another one, register this object as the initial
             *   player character.
             */
            if(isInitialPlayerChar && gameMain.initialPlayerChar == nil)
            {
                /* Register me as the initial player character on gameMain */
                gameMain.initialPlayerChar = self;

                /* Register me as the current player character on libGlobal */
                gPlayerChar = gameMain.initialPlayerChar;
            }

            /*
             *   If we have both a location and a subLocation (which should be a
             *   property pointer if it's not nil), change our location to the
             *   object defined on the subLocation property of our location; this is
             *   used to place objects in the SubComponents of their parent objects.
             */
            if(subLocation != nil && location != nil)
                location = location.(subLocation);

            /*   If we have a location, add ourselves to its contents list. */
            if(location != nil)
                location.addToContents(self);

            /* if we have a global parameter name, add it to the global table */
            if (globalParamName != nil)
                libGlobal.nameTable_[globalParamName] = self;

            /* If our owner property isn't already a list, convert it to one. */
            owner = valToList(owner);

            /* If we have a keyList, add ourselves to every key in the list */
            if(keyList != nil)
            {
                foreach(local key in valToList(keyList))
                {
                    if(key.ofKind(Key))
                    {
                        key.actualLockList = key.actualLockList.appendUnique([self]);
                        key.plausibleLockList = key.plausibleLockList.appendUnique([self]);
                    }
                }

                foreach(local key in valToList(knownKeyList))
                {
                    if(key.ofKind(Key))
                    {
                        key.knownLockList = key.knownLockList.appendUnique([self]);
                    }
                }

            }

            /*
             *   if we have any remapXXX properties, set those objects to the same
             *   listOrder as our own, since they're effectively representations of
             *   ourself.
             */

            for(local prop in remapProps)
            {
                local obj = self.(prop);
                if(obj)
                    obj.listOrder = listOrder;
            }

            /*
             *   If we're compiling for debug, warn the user if s/he's used the
             *   canSitOn, canLieOn or canStand on properties in an inconsistent
             *   manner.
             */
    #ifdef __DEBUG
            if(isBoardable == nil && contType != On)
            {
                if(canSitOnMe)
                    "WARNING! canSitOnMe is true on <<theName>> when <<theName>>
                    cannot be boarded.\n";
                if(canStandOnMe)
                    "WARNING! canStandOnMe is true on <<theName>> when <<theName>>
                    cannot be boarded.\n";
                if(canLieOnMe)
                    "WARNING! canLieOnMe is true on <<theName>> when <<theName>>
                    cannot be boarded.\n";

                if(canSitOnMe || canStandOnMe || canLieOnMe)
                    "You either need to make <<objToString()>> a Platform or remove
                    your override on its canSit/Stand/LieOnMe properties\b";
            }

    #endif
        }

        /*
         *   Our outermost room, i.e. the top level Room in which we are indirectly
         *   or directly contained.
         */
        getOutermostRoom = (location == nil ? nil : location.getOutermostRoom)

        interiorParent()
        {
            /* if I don't have a location, there's no interior parent */
            if (location == nil)
                return nil;

            /* if my immediate location is interior, it's the interior parent */
            if (location.contType == In || location.ofKind(Room))
                return location;

            /* otherwise, it's my parent's interior parent */
            return location.interiorParent();
        }

        /*
         *   Am I on the inside of the given object?  This returns true if our
         *   relationship to the given object is an interior location type.
         */
        isInterior(obj)
        {
            if(location == nil)
                return nil;

            if(location == obj && obj.contType == In)
                return true;

            if(location.ofKind(SubComponent) && location.contType == In &&
               location.lexicalParent == obj)
                return true;

            return location.isInterior(obj);
        }

        /*
         *   Find the immediate child of 'self' that contains 'other'.  If
         *   'other' is directly in 'self', we return 'other'; otherwise, we
         *   return an object directly within 'self' that contains 'obj',
         *   directly or indirectly.  If 'other' is not within 'self', we
         *   return nil.
         */
        directChildParent(other)
        {
            /* scan other's parent chain until we find a direct child of self */
            for (local o = other ; o != nil ; o = o.location)
            {
                if (o.location == self)
                    return o;
            }

            /* 'other' is not a child */
            return nil;
        }

          /*
         *   Get the containment relationship between 'child' and 'self'.  This
         *   returns the containment type of the immediate child of 'self' that
         *   contains 'child'.
         */
        childLocType(child)
        {
            /* get the direct child of self containing child */
            child = directChildParent(child);

            /*
             *   If we found the direct child container, the containment
             *   relationship is the one between 'self' and the direct child;
             *   otherwise there's no relationship.
             */
            return (child != nil ? child.locType : nil);
        }

        /*
         *   Find the nearest common containing parent of self and other. Unlike
         *   commonInteriorParent this doesn't take account of the type of
         *   containment (it can be In, On, Under, Behind or anything else) just so
         *   long as we find a common parent in the containment hierarchy.
         */
        commonContainingParent(other)
        {
            /* start at each object's nearest direct parent */
            local l1 = location;
            local l2 = other.location;

             /*
              *   if one or the other doesn't have a location, there's no common
              *   parent.
              */

            if(l1 == nil || l2 == nil)
                return nil;

             /* work up the containment tree one parent at a time */
            while (l1 != nil || l2 != nil)
            {
                /* if I'm inside the current other parent, that's the common one */
                if (l2 != nil && isIn(l2))
                    return l2;

                /* if other is in my current parent, that's the nearest one */
                if (l1 != nil && other.isIn(l1))
                    return l1;

                /* move up one level */
                l1 = (l1 != nil ? l1.location : nil);
                l2 = (l2 != nil ? l2.location : nil);
            }

            /* there's no common parent */
            return nil;
        }

       /*
         *   Find the nearest common interior parent of self and other.  This
         *   finds the nearest parent that both self and other are inside of.
         */
        commonInteriorParent(other)
        {
            /* start at each object's nearest interior parent */
            local l1 = interiorParent();
            local l2 = other.interiorParent();

            /*
             *   if one or the other doesn't have an interior parent, there's no
             *   common parent.
             */

            if(l1 == nil || l2 == nil)
                return nil;

            /* work up the containment tree one interior at a time */
            while (l1 != nil || l2 != nil)
            {
                /* if I'm inside the current other parent, that's the common one */
                if (l2 != nil && isInterior(l2))
                    return l2;

                /* if other is in my current parent, that's the nearest one */
                if (l1 != nil && other.isInterior(l1))
                    return l1;

                /* move up one level */
                l1 = (l1 != nil ? l1.interiorParent() : nil);
                l2 = (l2 != nil ? l2.interiorParent() : nil);
            }

            /* there's no common parent */
            return nil;
        }

        /*
         *   Get the interior containment path from 'self' to 'other'.  This
         *   returns a list containing three elements.  The first element is a
         *   sublist giving the interior containers you have to traverse
         *   outwards from self up to the common interior parent.  The second
         *   element is the common container; this will be nil if the two
         *   objects are in separate rooms.  The third element is a sublist
         *   giving the containers you have to traverse inwards from the common
         *   parent to other.
         */
        containerPath(other)
        {
            /* set up vectors for the outward and inward paths */
            local outPath = new Vector(10), inPath = new Vector(10);

            /* set up a variable for the common parent */
            local commonPar = nil;

            /* trace the paths, accumulating the elements in the vectors */
            traceContainerPath(
                other,
                { c: outPath.append(c) },
                { c: commonPar = c },
                { c: inPath.append(c) });

            /* return the lists */
            return [outPath.toList(), commonPar, inPath.toList()];
        }

        /*
         *   Trace the interior containment path from 'self' to 'other'.
         *
         *   We'll start by working up the containment tree from 'self' to the
         *   nearest interior container we have in common with 'other' - that
         *   is, the nearest object that contains both 'self' and 'other' with
         *   an interior location type for each object.  For each container
         *   BELOW the common parent, we call outFunc(container).
         *
         *   Next, we call parentFunc(container) on the common container.  If
         *   there is no common container, we call parentFunc(nil).
         *
         *   Next, we work back down the containment tree from the common
         *   parent to 'other'.  For each container below the common parent, we
         *   call inFunc(container).
         */
        traceContainerPath(other, outFunc, parentFunc, inFunc)
        {
            /* find the nearest common enclosing container */
            local cpar = commonInteriorParent(other);

            /* work up from self to the common parent */
            for (local c = interiorParent() ; c != cpar ; c = c.interiorParent())
                outFunc(c);

            /* call the common parent callback */
            parentFunc(cpar);

            /*
             *   Work back down from the common parent to other.  The easy way
             *   to do this is to build a stack from other up to cpar, then
             *   work back through the stack.
             */
            local stk = new Vector(10);
            for (local c = other.interiorParent() ; c != cpar ;
                 c = c.interiorParent())
                stk.push(c);

            /* work back through the stack */
            while (!stk.isEmpty())
                inFunc(stk.pop());
        }

        /*
         *   Search for a "blockage" along the container path between 'self'
         *   and 'other'.  'outProp' and 'inProp' are "can" properties
         *   (&canSeeOut, &canReachIn, etc) that test a container to see
         *   whether we can see/reach/hear/etc in or out of the container.
         *
         *   We trace the containment path, using traceContainerPath().  For
         *   each outbound container on the path, we evaluate the container's
         *   outProp property: if this is nil, we add that container to the
         *   blockage list.  Next, if there's no common parent, we add the
         *   outermost room containing 'self' to the list.  Next, we trace the
         *   inbound path, evaluating each container's inProp property: if nil,
         *   we add that container to the blockage list.
         *
         *   Finally, we return the blockage list.  This is a vector giving all
         *   of the blockages we found, in the order we encountered them.
         */
        containerPathBlock(other, inProp, outProp)
        {
            /* set up a vector for the blockage list */
            local vec = new Vector(10);

            /* trace the path, noting each blockage */
            traceContainerPath(
                other,
                new function(c) { if (!c.(inProp)) vec.append(c); },
                new function(c) { if (c == nil && outermostParent) vec.append(outermostParent()); },
                new function(c) { if (!c.(outProp)) vec.append(c); });

            /* return the path */
            return vec;
        }

        /*
         *   Get the first blockage in a container path.  This calls
         *   containerPathBlock() and returns the first blockage in the list,
         *   or nil if there's no blockage.
         */
        firstContainerPathBlock(other, inProp, outProp)
        {
            local v = containerPathBlock(other, inProp, outProp);
            return (v.length() != 0 ? v[1] : nil);
        }

        outermostVisibleParent()
        {
            /*
             *   our "eyes" are on our outside, so we can always see our own
             *   parent; find its outermost visible container
             */
            local loc;
            for (loc = location ; loc != nil ; loc = loc.location)
            {
                /* if this is the outermost container, we're done */
                if (loc.location == nil)
                    break;

                /* if we can't see out to our next container, stop here */
                if (loc.contType == In && !loc.canSeeOut)
                    break;
            }

            /* return what we found */
            return loc;

        }

        /*
         *   Our location type with respect to our immediate container; e.g. are we
         *   In, On, Under or Behind it?
         */
        locType()
        {
            /* If we don't have a location we can't have a locType */
            if(location == nil)
                return nil;

            /*
             *   If our location is a Carrier then our locType depends on other
             *   factors.
             */
            if(location.contType == Carrier)
            {
                /* If we're worn by our location then our locType is Worn. */
                if(wornBy == location)
                    return Worn;

                /*
                 *   Otherwise, if we're fixed in place we're a component of our
                 *   location (i.e. a part of the actor that's our location), so our
                 *   locType is Outside (we're attached to but external to our
                 *   location
                 */
                if(isFixed)
                    return Outside;

                /*  Otherwise, if we're a portable object, we're being carried. */
                return Held;
            }

            /* Otherwise our locType is simply our location's contType. */
            else return location.contType;
        }

         /*
         *   Get my outermost parent.  This is simply our ancestor in the
         *   location tree that has no location itself.
         */
        outermostParent()
        {
            return locationWhich({ p: p.location == nil });
        }

        /* are we on the exterior of the given object, directly or indirectly? */
        isOutside(obj)
        {
            return (location == obj ? locType == Outside :
                    location != nil && location.isOutside(obj));
        }

        /* are we held by the given object, directly or indirectly? */
        isHeldBy(obj)
        {
            return (location == obj ? locType == Held :
                    location != nil && location.isHeldBy(obj));
        }

        /*
         *   Flag; is this Thing a vehicle for an actor? If so then issuing a travel
         *   command while in this vehicle will call this vehicle to travel
         */
        isVehicle = nil

        /*
         *   The nested room subhead.  This shows a little addendum to the room
         *   headline when the point-of-view actor is inside an object within
         *   the main room, such as a chair or platform.  This usually shows
         *   something of the form "(in the chair)".  Note that only the
         *   *immediate* container is shown; if the actor is in a chair in a
         *   booth on a stage, we normally only mention the chair.
         *
         *   We leave this to the language library to define, since the exact
         *   syntax varies by language.
         */
        // roomSubhead(pov) { }

        /*
         *   Listing order.  This is an integer giving the relative position of
         *   this item in a miscellaneous item list.  The list is sorted in
         *   ascending order of this value.
         */
        listOrder = 100

        /*
         *   List group.  At the moment this does nothing, but it has been retained
         *   from the Mercury library for possible future use.
         */
        listWith = nil

        /*
         *   Group order.  This gives the relative order of this item within
         *   its list group.
         */
        groupOrder = 100

         /*
          *   CollectiveGroup, or a list of CollectiveGroups, to which this item
          *   belongs.
          */
        collectiveGroups = nil

        /*
         *   The owner or owners of the object.  This is for resolving
         *   possessives in the player's input, such as BOB'S WALLET.  By
         *   default, an object has no explicit owner, so this is an empty
         *   list.
         *
         *   This should only return the *explicit* owner(s), not an implied
         *   locational owner.  For example, if Bob is holding a key, it's
         *   implicitly BOB'S KEY.  However, the key may or may not still be
         *   Bob's after he drops it.  If the key is something that's
         *   understood to belong to Bob, whether it's currently in his
         *   physical possession or not, then this routine would return Bob;
         *   otherwise it would return nil.
         *
         *   An object can have multiple explicit owners, in which case it'll
         *   be recognized with a possessive qualifier for any of the owners.
         *   The first owner in the list is the nominal owner, meaning its the
         *   one we'll use if we're called upon to display the object's name
         *   with a possessive phrase.
         */
        owner = []

        /*
         *   Are we the nominal owner of the objects we contain?  This controls
         *   whether or not we can be chosen as the nominal owner of a contained
         *   object for display purposes.  If a contained object has no explicit
         *   owner, it can still be implicitly owned by an actor carrying it, or by
         *   another suitable container.  (Note that this only applies as a default.
         *   When an item in our contents has an explicit owner, that will override
         *   the implied container ownership for that item.  So, for example, Bob
         *   can be carrying Bill's wallet wallet, and as long as the wallet has its
         *   explicit owner set, we'll still describe it as Bill's despite its
         *   location.)
         *
         *   By default, most objects are not nominal owners.  Actors generally
         *   should set this to true, so that (for example) anything Bob is carrying
         *   can be described as Bob's. Something with contType = Carrier is likely
         *   to be an actor and hence something that can own its contents.
         */
        ownsContents = (contType == Carrier)

        /*
         *   Get my nominal owner.  If we have an explicit owner, we'll return
         *   the first explicit owner.  Otherwise, we'll look for a container
         *   that has ownsContents = true, and return the first such container.
         */
        nominalOwner()
        {
            /* if I have an explicit owner, return the first one */
            if (owner.length() > 0)
                return owner[1];

            /* look for a container with ownsContents = true */
            return locationWhich({loc: loc.ownsContents});
        }

        /*
         *   Does the given object own me, explicitly or implicitly?  This
         *   returns true if 'obj' is in my 'owner' list, but it can also
         *   return true if there's merely an implied ownership relationship.
         *   Location can imply ownership: BOB'S KEY could refer to the key
         *   that Bob is holding, whether or not it would continue to be
         *   considered his key if he were to drop it.
         *
         *   We return true if 'obj' is an explicit owner, OR self is contained
         *   within 'obj', OR self is contained within an object owned by
         *   'obj'.  (The latter case is for things like BOB'S TWENTY DOLLAR
         *   BILL, which is Bob's by virtue of being inside a wallet explicitly
         *   owned by Bob.)
         */
        ownedBy(obj)
        {
            /* if obj is in my explicit owner, we're owned by obj */
            if (owner.indexOf(obj))
                return true;

            /* if we're a child of obj, we're implicitly owned by obj */
            if (isChild(obj, nil))
                return true;

            /* are we inside something owned by obj? */
            if (location != nil && location.ownedBy(obj))
                return true;

            /* we're not owned by obj */
            return nil;
        }

        /*
         *   Return a list of everything that's directly or indirectly contained
         *   within us.
         */
        allContents()
        {
            local vec = new Vector(20);

            addToAllContents(vec, contents);

            return vec.toList;
        }

        addToAllContents(vec, lst)
        {
            vec.appendUnique(lst);
            foreach(local cur in lst)
                addToAllContents(vec, cur.contents);
        }

        /* get everything that's directly in me */
        directlyIn = (contents.subset({ obj: obj.locType == In }))

        /*
         *   Run a check method passed as a property pointer in the prop parameter
         *   and return any string it tried to display
         */
        tryCheck(prop)
        {
            local ret;
            try
            {
                ret = gOutStream.captureOutput({: self.(prop) });
            }
            catch (ExitSignal ex)
            {
                if(ret is in ('', nil))
                    ret = gAction.failCheckMsg;
            }
            finally
            {
                return ret;
            }
        }

        locationWhich(func)
        {
            /*
             *   scan up the location tree until we reach the top, or a
             *   location for which func(loc) returns true
             */
            local loc;
            for (loc = location ; loc != nil && !func(loc) ; loc = loc.location) ;

            /* return what we found */
            return loc;
        }

        /*
         *   Are we transparent to light?  If this is true, then an observer
         *   outside this object can see through it to objects on its interior,
         *   and an observer inside can see through to objects on its exterior.
         *
         *   This property controls transparency symmetrically (looking in from
         *   outside and looking out from within).  The library also lets you
         *   control transparency asymmetrically, using canSeeIn and canSeeOut.
         *   Those values are by default derived from this one, but you can
         *   override them separately to create something like a one-way
         *   mirror.
         */
        isTransparent = nil

        /*
         *   Do we fully enclose our interior contents (true), or only
         *   partially (nil)?  By default, we assume that our contents are
         *   fully enclosed.  This can be set to nil for objects that represent
         *   spaces that are open on one side, such as a nook in a rock or a
         *   create without a lid.
         *
         *   For an object that's sometimes fully enclosing and sometimes not,
         *   such as a cabinet with a door that can be opened and closed, this
         *   should be overridden with a method that figures the current value
         *   based on the open/closed state.
         *
         *   Note that this only applies to our *interior* contents, such as
         *   contents of location type In.  Contents that are atop the object
         *   or otherwise arranged around the exterior aren't affected by this.
         */
        enclosing = (contType == In && isOpen == nil)

        /*
         *   Can we see in from my exterior to my interior?  That is, can an
         *   observer outside of this object see things located within it?  By
         *   default, we can see in from outside if we're transparent or we're
         *   non-enclosing.
         */
        canSeeIn = (isTransparent || !enclosing)

        /*
         *   Can we see out from my interior to my exterior?  That is, can an
         *   observer inside this object see things located outside of it?  By
         *   default, we can see out from inside if we're transparent or we're
         *   non-enclosing.
         */
        canSeeOut = (isTransparent || !enclosing)

         /*
         *   Can we hear in from my exterior to my interior?  That is, can an
         *   observer on the outside of this container hear a sound source on
         *   the inside?
         *
         *   By default, we can hear in for all containers, since most
         *   materials transmit at least some sound even if they're opaque to
         *   light.  For a soundproof material (a glass booth, say), you could
         *   override this to make it (!enclosing) instead.
         */
        canHearIn = true

        /*
         *   Can we hear out from my interior to my exterior?  That is, can an
         *   observer on the inside of this container hear a sound source on
         *   the outside?
         *
         *   By default, we can hear out for all containers, since most
         *   materials transmit at least some sound even if they're opaque to
         *   light.  For a soundproof material (a glass both, say), you could
         *   override this to make it (!enclosing) instead.
         */
        canHearOut = true

        /*
         *   Can we smell in (from an observer on my exterior to an odor source
         *   on my interior)?  By default, we can smell in if we're
         *   non-enclosing, since most solid materials aren't very permeable to
         *   scents (at human sensitivities, at least).
         */
        canSmellIn = (!enclosing)

        /*
         *   Can we smell out (from an observer on my interior to an odor
         *   source on my exterior)?  By default, we can smell out if we're
         *   non-enclosing, since most solid materials aren't very permeable to
         *   scents (at human sensitivities, at least).
         */
        canSmellOut = (!enclosing)

        /*
         *   Can we reach out from my interior to my exterior?  That is, can an
         *   observer inside this object reach something outside of it?  By
         *   default, we can reach out if we're non-enclosing.
         */
        canReachOut = (!enclosing)

        /*
         *   Can we reach in from my exterior to my interior?  That is, can an
         *   observer outside this object reach something inside of it?  By
         *   default, we can reach in if we're non-enclosing.
         */
        canReachIn = (!enclosing)

        /*
         *   Allow this object to add additional checks at the check and verify
         *   stages to stipulate whether it can reach obj (or obj can reach it). We
         *   might use this, for example, to put an object conditionally out of
         *   reach if it's on top of a high cupboard or it's too hot to touch.
         */

        /*
         *   If the verifyReach() method is defined, it should use the
         *   illogical/inaccessible/implausible/logical/logicalRank verify macros
         *   like a verify method. Don't define this method if you don't want it to
         *   block reaching.
         */
    //    verifyReach(obj) { }

        /*
         *   Check whether the actor can reach (touch) this object. If this method
         *   displays anything (which should be the reason this object can't be
         *   touched) then the object can't be reached. Note that this only has any
         *   effect when the touchObj preCondition is defined.
         */
        checkReach(actor) {  }

        /*
         *   Check whether an actor can reach inside this object (for reasons other
         *   that it enclosing its contents; e.g. because it's out of reach). If
         *   this method displays anything (which should be the reason the interior
         *   of this object can't be reached) then disallow reaching inside. Note
         *   that this only has any effect when the touchObj preCondition is defined
         *   on this object. By default we can reach inside if we can reach this
         *   object and not otherwise. If the optional target parameter is supplied,
         *   it's the object that actor is trying to reach.
         */
        checkReachIn(actor, target?)
        {
            checkReach(actor);
        }

        /*
         *   Check whether the actor can reach out of this object to touch obj, if
         *   obj is not in this object.
         */
        allowReachOut(obj) { return true; }

        /*
         *   If an actor within me cannot reach an object from me, should the actor
         *   automatically try to get out of me?
         */
        autoGetOutToReach = true

        /*
         *   Return a message explaining why an object outside me can't reach one
         *   inside (or vice versa); this will normally be triggered by an attempt
         *   to reach an object inside a closed transparent container. The method is
         *   defined here to make it easier to customize the message on the
         *   container that's doing the blocking.
         */
        reachBlockedMsg(target)
        {
            local obj = self;
            gMessageParams(obj, target);
            return  BMsg(cannot reach, '{I} {can\'t} reach {the target} through
                {the obj}. ');
        }

        /*
         *   Return a message (single-quoted string) explaining why we can't be
         *   reached by the actor (typically because we're in a different room).
         */
        tooFarAwayMsg
        {
            local obj = self;
            gMessageParams(obj);
            return BMsg(too far away, '{The subj obj} {is} too far away. ');
        }

        /*
         *   Return a message (single-quoted string) explaining why target can't be
         *   reached from inside this Thing (when this Thing is typically some kind
         *   of nested room such as a Booth or Platform).
         */
        cannotReachOutMsg(target)
        {
            local loc = self;
            gMessageParams(loc, target);
            return BMsg(cannot reach out, '{I} {can\'t} reach {the target} from
                        {the loc}. ');
        }

        /*
         *   Does this object shine light outwards?  This determines if the
         *   object is a light source to objects outside of it.  Light shines
         *   out from an object if the object itself is a light source, or one
         *   of its direct exterior contents shines out, or its contents are
         *   visible from the outside and one of its direct interior contents
         *   shines out.
         */
        shinesOut()
        {
            /* if I'm a light source directly, we shine light outwards */
            if (isLit)
                return true;

            /* check my exterior contents */
            if (contents.indexWhich(
                { c: c.locType.ofKind(ExtLocType) && c.shinesOut() }) != nil)
                return true;

            /* if we can see into this object from outside, check the contents */
            if (canSeeIn
                && contents.indexWhich(
                    { c: c.locType.ofKind(IntLocType) && c.shinesOut() }) != nil)
                return true;

            /* I don't provide light outwards */
            return nil;
        }

        /*
         *   Is this object's interior lit?
         *   an object if the object itself is a light source, or anything
         *   directly inside shines outwards, or we can see out from within and
         *   our location shines inwards.
         */
        litWithin()
        {
            /* if I'm a light source directly, we shine inwards */
            if (isLit)
                return true;

            /* if any interior contents shine outwards, we're lit within */
            if (contents.indexWhich(
                { c: c.locType.ofKind(IntLocType) && c.shinesOut() }) != nil)
                return true;

            /*
             *   if we can see out from within, and my enclosing parent is lit
             *   within, I'm lit within
             */
            if (canSeeOut)
            {
                local p = interiorParent();
                if (p != nil && p.litWithin())
                    return true;
            }

            /* I don't provide light inwards */
            return nil;
        }

        /*
         *   Has this object ever been moved by the player character?  This is
         *   set to true when the PC takes the object or puts it somewhere.
         */
        moved = nil

        /*
         *   Have we been examined?  This is set to true when the player
         *   character examines the object.  For a room, LOOK AROUND counts as
         *   examination, as does triggering a room description by traveling
         *   into the room.
         */
        examined = nil

        /*
         *   Have we been seen?  This is set to true the first time the object
         *   is described or listed in a room description or the description of
         *   another object (such as LOOK IN this object's container).
         */
        seen = nil

        /*
         *   The last location where the player character saw this object.
         *   Whenever the object is described or listed in the description of a
         *   room or another object, we set this to the object's location at
         *   that time.
         */
        lastSeenAt = nil

        /* Note that we've been seen and where we were last seen */
        noteSeen()
        {
            gPlayerChar.setHasSeen(self);
            lastSeenAt = location;
        }

        /*
         *   Whether the player character knows of the existence of this object, if
         *   if it hasn't been seen. Set to true for objects that the player
         *   character should be familiar with at the start of play, or make true
         *   when the PC learns of them.
         */
        familiar = nil

        /*
         *   Properties to set and test whether an object is known about or has been
         *   seen; we define these on Thing to allow the player char to be a Thing.
         *   In what follows 'this Thing' is the object on which the method is
         *   called (which would typically be an Actor, the player character, or
         *   some other object representing an NPC) and obj is the object that is
         *   potentially known about or seen.
         */

        /*  Mark this Thing as knowing about obj. */
        setKnowsAbout(obj) { obj.(knownProp) = true; }

        /*  Mark the player character as knowing about us (i.e. this Thing) */
        setKnown() { gPlayerChar.setKnowsAbout(self); }

        /*  Mark this Thing as having seen obj. */
        setHasSeen(obj) { obj.(seenProp) = true; }

        /*  Mark the player character as having seen this Thing. */
        setSeen() { gPlayerChar.setHasSeen(self); }

        /*  Test whether this Thing has seen obbj. */
        hasSeen(obj) { return obj.(seenProp); }

        /*
         *   Test whether this Thing knows about obj, which it does either if it has
         *   seen this obj or its knownProp (by default, familiar) is true.
         */
        knowsAbout(obj) {return hasSeen(obj) || obj.(knownProp); }

        /*
         *   Test whether this Thing is known to the player character.
         */
        known = (gPlayerChar.knowsAbout(self))

         /*
          *   If we want to track whether characters other than than the player char
          *   know about or have seen this object, we can define knownProp and
          *   seenProp as the properties used by this Thing to track what it knows
          *   about and has seen.
          */
        knownProp = &familiar
        seenProp = &seen

        /*
         *   The currentInterlocutor is the Actor this object is currently in
         *   conversation with. This property is only relevant on gPlayerChar, but
         *   it is defined here rather than on Actor since the player char can be of
         *   kind Thing.
         */
        currentInterlocutor = nil

         /*
          *   Can this Thing (which might be the Player Char for instance) talk to
          *   other?
          */
        canTalkTo(other)
        {
            return Q.canTalkTo(self, other);
        }

          /*
           *   The lister to use when listing this object's inventory. By default we use the standard
           *   inventory lister for the default WIDE inventory listing and the inventoryTallLister for
           *   the TALL inventory listing.
           */
        myInventoryLister = libGlobal.inventoryTall ? inventoryTallLister : inventoryLister

        /* The lister to use when listing what this object is wearing. */
        myWornLister = wornLister

        /*
         *   Score this object for disambiguation.  When a noun phrase is
         *   ambiguous (for example, the phrase matches multiple in-scope
         *   objects, and we have to choose just one), the parser calls this
         *   routine on each object it's considering as a match.
         *
         *   Our job here is to read the player's mind.  The question before us
         *   is: did the player mean *this* object when typing this noun
         *   phrase?  Obviously we can't really know what's in the player's
         *   mind, but in many cases we can make an educated guess based on
         *   what ought to make the most sense in context.  The context in this
         *   case is the state of the simulated game world, as it's portrayed
         *   to the player.  That last bit is important: be cognizant of what
         *   the player is *meant* to know at this point.  DON'T base the score
         *   on information that the player isn't supposed to know, though:
         *   that could give away secrets that the player is meant to discover
         *   on her own.
         *
         *   Before this routine is called, the Action has already assigned an
         *   initial score to each object, but this routine can override the
         *   initial score by assigning its own score value.  This routine is
         *   most useful in cases where a particular object has a special
         *   affinity for a verb, or for the verb in combination with
         *   particular other objects involved in the command.
         *
         *   'cmd' is the Command object.  'role' is the noun phrase's role in
         *   the command (DirectObject, IndirectObject, etc).  'lst' is a list
         *   of NPMatch objects identifying the objects that matched the noun
         *   phrase.  'm' is the NPMatch object for self.
         *
         *   To override or adjust the score, simply set m.score to the new
         *   value.  This routine is also free to override the scores of any
         *   other objects in the list, if needed.
         *
         *   By default, we don't make any adjustment - we simply accept the
         *   initial score calculated by the Action, by leaving m.score
         *   unchanged.
         *
         *   See Action.scoreObjects() for full details.
         */
        scoreObject(cmd, role, lst, m)
        {
            m.score += vocabLikelihood;

            /*
             *   If we're the last object written on, boost our score if the player
             *   wants to write something again.
             */
            if(libGlobal.lastWrittenOnObj == self && cmd.action == WriteOn && role
               == IndirectObject)
                m.score += 20;

            /*
             *   If we're the last object typed on, boost our score if the player
             *   wants to type something again.
             */
            if(libGlobal.lastTypedOnObj == self && cmd.action == TypeOn && role
               == IndirectObject)
                m.score += 20;

        }

        /*
         *   A property that can be used to boost this object being chosen by the
         *   parser, other things being equal; it can be used as a tie-breaker
         *   between two objects that otherwise have the same verification scores.
         *   Game code should normally use fairly small values for this property,
         *   say between -20 and +20, to prevent overriding the verification score.
         */
        vocabLikelihood = 0

        /*
         *   A list of objects that are facets of this object, and so can be
         *   referred to with the same pronoun.
         */
        getFacets = []

        /*
         *   Before travel notification. This is called just before traveler
         *   attempts to travel via connector. By default we do nothing
         */
        beforeTravel(traveler, connector) {}

        /*
         *   After travel notification. This is called just after traveler has
         *   traveled via connector.
         */
        afterTravel(traveler, connector) {}

        /*
         *   Cause this Thing to travel via the connector conn. This method is
         *   supplied in case travelVia is called on a Thing which is not an Actor,
         *   although it's Actor that has the full implementation.
         */
        travelVia(conn, announceArrival = true)
        {
            /*
             *   If we've been mixed in with a TravelConnector class, it's almost
             *   certainly the TravelConnector's version of travelVia() that we need
             *   to execute here.
             */
            if(ofKind(TravelConnector))
                inherited TravelConnector(conn);

            else
                /* Move this actor via conn. */
                conn.travelVia(self);
        }

        /*
         *   Handle a command directed to this open (e.g. BALL, GET IN BOX). Since
         *   inanimate objects generally can't respond to commands we simply display
         *   a message announcing the futility of issuing one. This method is
         *   overridden on Actor to allow Actors to respond to commands via
         *   CommandTopics.
         */
        handleCommand(action)
        {
            DMsg(cannot command thing, 'There{dummy}\'s no point trying to give
                orders to {1}. ', aName);
        }

        /*
         *   The before action handling on this Thing if it's the current actor. We
         *   define it here rather than on Actor since the player character can be a
         *   Thing. By default we do nothing.
         */
        actorAction() { }

        /*
         *   Before action notification on this Thing; this is triggered whenever an
         *   action is about to be performed when we're in scope (and could be used
         *   to veto the action with an exit macro). The action we'd test for here
         *   would normally be one that *doesn't* involve this Thing.
         */
        beforeAction() { }

        /*
         *   After action notification on this Thing; this is triggered whenever an
         *   action has just been performed when we're in scope. The action we'd
         *   test for here would normally be one that *doesn't* involve this Thing.
         */
        afterAction() { }

        /* Is this object the player character? */
        isPlayerChar = (gPlayerChar == self)

        /*
         *   To exclude this item from the list of objects to be acted upon when the
         *   player types a command with ALL for action, override this method to
         *   return true for the action or actions concerned. Note that this
         *   exclusion is applied after the action has constructed its own list of
         *   objects that ALL should apply to, and can only be used to make further
         *   exclusions.
         *
         *   It shouldn't be necessary to use this method very often, since the
         *   normal approach will be to override the getAll() method on the
         *   appropriate action. It may be useful to use this method to handle
         *   exceptional cases, however.
         */
        hideFromAll(action) { return nil; }

        /*
         *   This method is primarily intended for use with the symconn extension, where it is
         *   redefined, but other code may find a use for it.
         */
        byRoom(arg) { return ''; }

        /*
         *   ******************************************************************
         *   ACTION HANDLING
         *
         *   Here follows code relating to the handling of specific actions
         */

         /*
          *   If I declare this object to be a decoration (i.e. isDecoration = true)
          *   then its default behaviour will be to display its notImportantMsg for
          *   every action except Examine or GoTo. We can extend the actions it will
          *   respond to by adding them to the list in the decorationActions
          *   property.
          */
        isDecoration = nil

        /*
         *   The list of actions this object will respond to specifically if
         *   isDecoration is true. All other actions will be handled by
         *   dobjFor(Default) and/or iobjFor(Default). Game code can override this
         *   list (usually to expand it) for decorations that are required to handle
         *   additional actions.
         *
         *   If we're compiling for debugging, it will be useful to allow the GONEAR
         *   command with Decorations for testing purposes, but this can't be
         *   included in a release build without causing a compilation error, so we
         *   define the decorationActions property with different lists of actions
         *   depending on whether we're compiling for debugging or release.
         */
    #ifdef __DEBUG
        decorationActions = [Examine, GoTo, GoNear]
    #else
        decorationActions = [Examine, GoTo]
    #endif
        /*
         *   Our handling of any action of which we're the direct or indirect action
         *   that's not in our list of decorationActions when our isDecoration
         *   property is true. By default we just stop the action at the verify
         *   stage and display our notImportantMsg.
         */
        dobjFor(Default)
        {
            verify
            {
                illogical(notImportantMsg);
            }
        }

        iobjFor(Default)
        {
            verify()
            {
                illogical(notImportantMsg);
            }
        }

        notImportantMsg = BMsg(not important, '{The subj cobj} {is} not important.
             ')

        /*
         *   Next deal with what happens if this object is being tested as a
         *   potential actor
         */
        verifyActor()
        {
           /*
            *   If our contType isn't Carrier we're unlikely to be an actor, so
            *   we're a poor choice of object if the parser has to select an actor,
            *   typically when the player has entered a command targeted at an NPC.
            */
            if(contType != Carrier)
                logicalRank(70);
        }

        remapActor = nil

        preCondActor = [objAudible]

        /* Now the handling for specific actions */

        dobjFor(Examine)
        {
            preCond = [objVisible]

            verify()
            {
                if(isDecoration)
                    logicalRank(70);
                else
                    logical;
            }

            check() { }

            action()
            {
                local descDisplayed = nil;

                /*
                 *   If we have a non-nil darkDesc property and we're in a dark
                 *   room, display our darkDesc and stop there. Note this will only
                 *   ever happen if we're visibleInDark.
                 */
                if(propType(&inDarkDesc) != TypeNil
                   && !getOutermostRoom.isIlluminated())
                {
                    /* Display our darkDesc */
                    display(&inDarkDesc);

                    /*
                     *   Stop there, because in a dark room we can only be seen
                     *   partially, and so we don't add any status information or
                     *   record that we've been (properly) examined.
                     */
                    return;
                }

                /*
                 *   Display our description. Normally the desc property will be
                 *   specified as a double-quoted string or a routine that displays
                 *   a string, but by using the display() message we ensure that it
                 *   will still be shown even if desc has been defined a
                 *   single-quoted string.
                 */
                if(gOutStream.watchForOutput({:display(&desc) }))
                   descDisplayed = true;

                /*
                 *   Display any additional information, such as our stateDesc (if
                 *   we have one) and our contents (if we have any).
                 */
                if(gOutStream.watchForOutput({:examineStatus()} ))
                    descDisplayed = true;

                /*
                 *   If nothing has been displayed yet, show the default message
                 *   saying we nothing special about this object.
                 */
                if(!descDisplayed)
                    DMsg(nothing special,  '{I} {see} nothing special about
                    {the 1}. ', self);

                /*   Note that we've now been examined. */
                examined = true;

                /*
                 *   Note that the player character has seen us. 99 times out a
                 *   hundred this probably won't be necessary, but it may catch the
                 *   odd case where something is examined that hasn't yet been set
                 *   as seen.
                 */
                if(gActor == gPlayerChar)
                    noteSeen();

                "\n";
            }
        }

        /* The message to display when it's too dark to see anything */
        tooDarkToSeeMsg = BMsg(too dark to see, 'It{dummy}{\'s} too dark to see
            anything. ')

        /*
         *   By default everything is smellable, but you can override this to nil if
         *   something isn't
         */
        isSmellable = true

        cannotSmellMsg = BMsg(cannot smell, '{I} {can\'t} smell {the dobj}. ')

        dobjFor(SmellSomething)
        {
            preCond = [objSmellable]

            verify()
            {
                if(!isSmellable)
                    illogical(cannotSmellMsg);
            }

            action()
            {
                displayAlt(&smellDesc, &smellNothingMsg);
            }
        }

        smellNothingMsg = BMsg(smell nothing, '{I} {smell} nothing out of the
                        ordinary.<.p>')

        dobjFor(ListenTo)
        {

            preCond = [objAudible]

            action()
            {
                displayAlt(&listenDesc, &hearNothingMsg);
            }
        }

        hearNothingMsg = BMsg(hear nothing listen to, '{I} hear{s/d} nothing out of
            the ordinary.<.p>')

        /*
         *   By default everything is tasteable, but there might well be things the
         *   that it would not be appropriate to taste.
         */
        isTasteable = true

        cannotTasteMsg = BMsg(cannot taste, '{The subj dobj} {is} not suitable for
            tasting. ')

        dobjFor(Taste)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isTasteable)
                    illogical(cannotTasteMsg);
            }

            action()
            {
                if(propType(&tasteDesc) == TypeNil)
                    DMsg(taste nothing, '{I} taste{s/d} nothing unexpected.<.p>');
                else
                    display(&tasteDesc);
            }
        }

        /*
         *   By default we can try feeling most things, but there may be some things
         *   it would be inappropriate to try feeling (like a blazing fire or Aunt
         *   Mable) or somethings that cannot be felt (like a ray of light).
         */
        isFeelable = true

        cannotFeelMsg = BMsg(cannot feel, 'It{\'s} hardly a good idea to try feeling
            {the dobj}. ')

        /*
         *   This property can be defined to display a message at the check stage
         *   (and so stop the FEEL action there). Normally checkFeelMsg would be
         *   defined as a double-quoted string, but it can also be defined as a
         *   double-quoted string or a method that displays some text.
         */
        checkFeelMsg = nil

        dobjFor(Feel)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isFeelable)
                    illogical(cannotFeelMsg);
            }

            check()
            {
                if(dataType(&checkFeelMsg) != TypeNil)
                    display(&checkFeelMsg);

            }

            action()
            {
                if(propType(&feelDesc) == TypeNil)
                    DMsg(feel nothing, '{I} {feel} nothing unexpected.<.p>');
                else
                    display(&feelDesc);
            }
        }

        /* By default a Thing is takeable if it's not fixed in place */
        isTakeable = (!isFixed)

        dobjFor(Take)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isTakeable)
                    illogical(cannotTakeMsg);

                if(isDirectlyIn(gActor))
                    illogicalNow(alreadyHeldMsg);

                if(gActor.isIn(self))
                    illogicalNow(cannotTakeMyContainerMsg);

                if(gActor == self)
                    illogicalSelf(cannotTakeSelfMsg);

                logical;
            }

            check()
            {

                /* First check that my container doesn't object to my being removed from it. */
                if(location)
                    location.checkRemove(self);

                /*
                 *   Check that the actor has room to hold the item s/he's about to
                 *   pick up.
                 */
                checkRoomToHold();
            }

            action()
            {
                /*
                 *   If we have any contents hidden behind us or under us, reveal it
                 *   now
                 */
                revealOnMove();

                /*
                 *   move us into the actor who is taking us, triggering the
                 *   appropriate notifications.
                 */
                actionMoveInto(gActor);
            }

            /*
             *   Report that we've been taken. Note that if the action causes
             *   several items to be taken, this method will only be called on the
             *   final item, and will need to report on all the items taken.
             */
            report()
            {
                DMsg(report take, 'Taken. | {I} {take} {1}. ', gActionListStr);
            }
        }

        cannotTakeMsg = BMsg(cannot take, '{The subj cobj} {is} fixed in place.
            ')

        alreadyHeldMsg = BMsg(already holding, '{I}{\'m} already holding {the dobj}.
            ')

        cannotTakeMyContainerMsg = BMsg(cannot take my container, '{I} {can\'t}
            take {the dobj} while {i}{\'m} {1} {him dobj}. ', objInPrep)

        cannotTakeSelfMsg = BMsg(cannot take self, '{I} {can} hardly take {myself}. ')

        /*
         *   Flag, should any items behind me be left behind when I'm moved; by
         *   default, they should.
         */
        dropItemsBehind = true

        /*
         *   Flag, should any items behind me be left behind when I'm moved; by
         *   default, they should.
         */
        dropItemsUnder = true

        /*
         *   List and move into an appropriate location any item that was hidden
         *   behind or under us. We place this in a separate method so it can be
         *   conveniently called by other actions that move an object, or overridden
         *   by particular objects that want a different handling.
         *
         *   Note that we don't provide any handling for the hiddenIn property here,
         *   on the assumption that items hidden in something may well stay there
         *   when it's moved; but this method can always be overridden to provide
         *   custom behaviour.
         */
        revealOnMove()
        {
            local moveReport = '';
            local underLoc = location;
            local behindLoc = location;

            /*
             *   If I don't want to leave items under me behind when I'm moved, and
             *   I am or have an underside, change the location to move items hidden
             *   under me to accordingly.
             */
            if(contType == Under && dropItemsUnder == nil)
                underLoc = self;
            else if(remapUnder != nil && dropItemsUnder == nil)
                underLoc = remapUnder;

             /*
              *   If I don't want to leave items behind me behind when I'm moved,
              *   and I am or have a RearContainer, change the location to move
              *   items hidden under me to accordingly.
              */
            if(contType == Behind && dropItemsBehind == nil)
                behindLoc = self;
            else if(remapBehind != nil && dropItemsBehind == nil)
                behindLoc = remapBehind;

            /*
             *   If anything is hidden under us, add a report saying that it's just
             *   been revealed moved and then move the previously hidden items to
             *   our location.
             */
            if(hiddenUnder.length > 0)
            {
                moveReport +=
                    BMsg(reveal move under,'Moving {1} {dummy} reveal{s/ed} {2}
                        previously hidden under {3}. ',
                         theName, makeListStr(hiddenUnder), himName);

                moveHidden(&hiddenUnder, underLoc);

            }

            /*
             *   If anything is hidden behind us, add a report saying that's just
             *   been revealed and then move the previously hidden items to our
             *   location.
             */
            if(hiddenBehind.length > 0)
            {
                moveReport +=
                    BMsg(reveal move behind,'Moving {1} {dummy} reveal{s/ed} {2}
                        previously hidden behind {3}. ',
                         theName, makeListStr(hiddenBehind), himName);

                moveHidden(&hiddenBehind, behindLoc);
            }

            /*
             *   Construct a list of anything left behind from under or behind us
             *   when we're moved.
             */
            local lst = [];

            if(dropItemsUnder)
            {
                if(contType == Under)
                    lst = contents;
                else if(remapUnder)
                    lst = remapUnder.contents;
            }

            if(dropItemsBehind)
            {
                if(contType == Behind)
                    lst += contents;
                else if(remapBehind)
                    lst += remapBehind.contents;
            }

            lst = lst.subset({o: !o.isFixed});

            if(lst.length > 0)
            {
                foreach(local cur in lst)
                    cur.moveInto(location);

                moveReport +=
                    BMsg(report left behind, '<<if moveReport == ''>>Moving {1}
                        <<else>>It also <<end>> {dummy} {leaves} {2} behind. ',
                         theName, makeListStr(lst));
            }

            /*
             *   If anything has been reported as being revealed, report the
             *   discovery after reporting the action that caused it.
             */
            if(moveReport != '' )
                reportAfter(moveReport);
        }

        /*
         *   Service method: move everything in the prop property to loc and mark it
         *   as seen.
         */
        moveHidden(prop, loc)
        {
            foreach(local cur in self.(prop))
            {
                cur.moveInto(loc);
                cur.noteSeen();
            }
            self.(prop) = [];

        }

        /*
         *   Check that the actor has enough spare bulkCapacity to add this item to
         *   his/her inventory. Since by default everything has a bulk of zero and a
         *   very large bulkCapacity, by default there will be no effective
         *   restriction on what an actor (and in particular the player char) can
         *   carry, but game authors may often wish to give portable items bulk in
         *   the interests of realism and may wish to impose an inventory limit by
         *   bulk by reducing the bulkCapacity of the player char.
         */
        checkRoomToHold()
        {
            /*
             *   First check whether this item is individually too big for the actor
             *   to carry.
             */
            if(bulk > gActor.maxSingleBulk)
                DMsg(too big to carry, '{The subj dobj} {is} too big for {me} to
                    carry. ');

            /*
             *   If the BagOfHolding class is defined and the actor doesn't have
             *   enough spare bulk capacity, see if the BagOfHolding class can deal
             *   with it by moving something to a BagOfHolding.
             */
            if(defined(BagOfHolding)
               && bulk > gActor.bulkCapacity - gActor.getCarriedBulk
               && BagOfHolding.tryHolding(self));

            /*
             *   otherwise check that the actor has sufficient spare carrying
             *   capacity.
             */
            else if(bulk > gActor.bulkCapacity - gActor.getCarriedBulk())
                DMsg(cannot carry any more, '{I} {can\'t} carry any more than
                    {i}{\'m} already carrying. ');
        }

        /* By default we can drop anything that's held */
        isDroppable = true

        /* The message to display if something can't be dropped. */
        cannotDropMsg = BMsg(cannot drop, '{The subj dobj} {can\'t} be dropped. ')

        /* The location in which something dropped in me should land. */
        dropLocation = self

        /*
         *   Flag: can our contents be dropped when we're in the actor's inventory (if they can, an
         *   implicit TaksFrom us will be performed to enable the Drop). By default they can't.
         */
        canDropContents = nil

        dobjFor(Drop)
        {
            preCond = [touchObj, objNotWorn, objCarried]

            verify()
            {

                /*
                 *   This object cannot be dropped if game code deems it to be undroppable for reasons
                 *   beyond throse enforced in the objCarried PreCondition.
                 */
                if(!isDroppable)
                    illogical(cannotDropMsg);
            }

            action()
            {
                actionMoveInto(gActor.location.dropLocation);
            }

            report()
            {
                DMsg(report drop, 'Dropped. |{I} drop{s/?ed} {1}. ', gActionListStr);
            }
        }

        notHoldingMsg = BMsg(not holding, '{I} {amn\'t} holding {the dobj}. ')
        partOfYouMsg = BMsg(part of me, '{The subj dobj} {is} part of {me}. ')

        /* By default an object is readable if it defines a non-nil readDesc */
        isReadable = (propType(&readDesc) != TypeNil)

        dobjFor(Read)
        {
            preCond = [objVisible]

            verify()
            {
                if(!isReadable)
                    illogical(cannotReadMsg);
            }

            action()
            {
                if(propType(&readDesc) == TypeNil)
                    say(cannotReadMsg);
                else
                    display(&readDesc);
            }
        }

        cannotReadMsg = BMsg(cannot read, 'There {dummy} {is} nothing to read on
            {the dobj}. ')

        /*
         *   Flag: can this object be followed? Most inanimate objects cannot, so
         *   the default value is nil.
         */
        isFollowable = nil

        dobjFor(Follow)
        {
            preCond = [objVisible]

            verify()
            {
                if(!isFollowable)
                    illogical(cannotFollowMsg);

                if(self == gActor)
                    illogicalSelf(cannotFollowSelfMsg);
            }
        }

        cannotFollowMsg = BMsg(cannot follow, '{The subj dobj} {isn\'t} going
            anywhere. ')

        cannotFollowSelfMsg = BMsg(cannot follow self, '{I} {can\'t} follow
            {myself}. ')

        /*
         *   Although in theory we can attack almost anything, in practice there's
         *   seldom reason to do so.
         */
        isAttackable = nil

        dobjFor(Attack)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isAttackable)
                    illogical(cannotAttackMsg);
            }

            check()
            {
                if(dataType(&checkAttackMsg) != TypeNil)
                    display(&checkAttackMsg);
            }

            /*
             *   In case isAttackable is changed to true but no other handling is
             *   added, we need to provide some kind of default report.
             */
            report()
            {
                say(futileToAttackMsg);
            }
        }

        /*
         *   If we want Attack to fail at the check stage we can supply a message
         *   explaining why.
         */
        checkAttackMsg = nil

        cannotAttackMsg = BMsg(cannot attack, 'It{dummy}{\'s} best to avoid
            pointless violence. ')

        dobjFor(AttackWith)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isAttackable)
                    illogical(cannotAttackMsg);
            }

            /*
             *   In case isAttackable is changed to true but no other handling is
             *   added, we need to provide some kind of default report.
             */
            report()
            {
                say(futileToAttackMsg);
            }
        }

        futileToAttackMsg = BMsg(futile attack, 'Attacking {1} prove{s/d} futile. ',
                                 gActionListStr)

        iobjFor(AttackWith)
        {
            preCond = [objHeld]
            verify()
            {
                if(!canAttackWithMe)
                   illogical(cannotAttackWithMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotAttackWithSelfMsg);
            }
        }

        /* By default we can't use most things as weapons */
        canAttackWithMe = nil

        cannotAttackWithSelfMsg = BMsg(cannot attack with self, '{I} {can\'t}
            attack anything with itself. ')

        cannotAttackWithMsg = BMsg(cannot attack with, '{I} {can\'t} attack
            anything with {that iobj}. ')

        dobjFor(Strike) asDobjFor(Attack)

        /*
         *   By default treat everything as breakable, but there are somethings that
         *   clearly aren't like sunbeams, sounds and mountains.
         */
        isBreakable = true

        /*   Probably most things shouldn't be broken though. */
        shouldBeBroken = nil

        dobjFor(Break)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isBreakable)
                    illogical(cannotBreakMsg);
                else if(!shouldBeBroken)
                    implausible(shouldNotBreakMsg);
            }
        }

        cannotBreakMsg = BMsg(cannot break, '{The subj dobj} {is} not the sort of
            thing (i) {can} break. ')

        shouldNotBreakMsg = BMsg(dont break, '{I} {see} no point in breaking {that
            dobj}. ')

        /* By default something is throwable unless it's fixed in place. */
        isThrowable = (!isFixed)

        dobjFor(ThrowDir)
        {
            preCond = [objHeld ,objNotWorn]

            verify()
            {
                if(!isThrowable)
                    illogical(cannotThrowMsg);

            }

            /*
             *   The default result of throwing something in a compass direction is
             *   that it lands in the dropLocation of its outermost room.
             */
            action() { actionMoveInto(getOutermostRoom.dropLocation); }

            report()
            {
                local obj = gActionListObj;

                gMessageParams(obj);

                DMsg(throw dir, '{I} {throw} {the obj} {1} and {he obj}
                    land{s/ed} on the ground. ', gAction.direction.name );
            }
        }

        cannotThrowMsg = BMsg(cannot throw, '{I} {can\'t} throw {the dobj} anywhere.
            ')

        /*
         *   Is this object openable. If this property is set to true then this
         *   object can be open and closed via the OPEN and CLOSE commands. Note
         *   that setting this property to true also automatically makes the
         *   OpenClosed State apply to this object, so that it can be referred to as
         *   'open' or 'closed' accordingly.
         */
        isOpenable = nil

        /*
         *   Is this object open. By default we'll make Things open so that their
         *   interiors (if they have any) are accessible, unless they're openable,
         *   in which case we'll assume they start out closed.
         */
        isOpen = (!isOpenable)

        /*
         *   Make us open or closed. We define this as a method so that subclasses
         *   such as Door can override to produce side effects (such as opening or
         *   closing the other side).
         */
        makeOpen(stat)
        {
            isOpen = stat;
            if(stat)
                opened = true;
        }

        /*
         *   Flag, has this object ever been opened. Note that this is nil for an
         *   object that starts out open but has never been closed and opened again.
         */
        opened = nil

        /*
         *   Flag, do we want to attempt to unlock this item it it's locked when we
         *   try to open it?
         */
        autoUnlock = nil

        dobjFor(Open)
        {

            preCond = autoUnlock ? [touchObj, objUnlocked] : [touchObj]

            /*
             *   If this object is not itself openable, but its remapIn property
             *   points to an associated object that is, remap this action to use
             *   the remapIn object instead of us.
             */
            remap()
            {
                if(!isOpenable && remapIn != nil && remapIn.isOpenable)
                    return remapIn;
                else
                    return self;
            }

            verify()
            {
                if(isOpenable == nil)
                    illogical(cannotOpenMsg);

                if(isOpen)
                    illogicalNow(alreadyOpenMsg);

                logical;
            }

            /*
             *   An object can't be open if it's locked. We test this at check
             *   rather than verify since it may not be obvious that an object's
             *   locked until someone tries to open it.
             */
            check()
            {
                if(isLocked)
                    say(lockedMsg);
            }

            action()
            {
                makeOpen(true);

                /*
                 *   If opening us is not being performed as an implicit action,
                 *   list the contents that are revealed as a result of our being
                 *   opened.
                 */
                if(!gAction.isImplicit)
                {
                    unmention(contents);
                    listSubcontentsOf(self, &myOpeningContentsLister);
                }
            }

            report()
            {
                DMsg(okay open, okayOpenMsg, gActionListStr);
            }
        }

        /*
         *   The lister to use when listing my contents when I'm opened. By default
         *   we use the openingContentsLister.
         */
        myOpeningContentsLister = openingContentsLister

        okayOpenMsg = 'Opened.|{I} open{s/ed} {1}. '

        cannotOpenMsg = BMsg(cannot open, '{The subj dobj} {is} not something {i}
            {can} open. ')
        alreadyOpenMsg = BMsg(already open, '{The subj dobj} {is} already open. ')
        lockedMsg = BMsg(locked, '{The subj dobj} {is} locked. ')

        /* By default something is closeable if it's openable */
        isCloseable = (isOpenable)

        dobjFor(Close)
        {
            preCond = [touchObj]

            remap()
            {
                if(!isCloseable && remapIn != nil && remapIn.isCloseable)
                    return remapIn;
                else
                    return self;
            }

            verify()
            {
                if(!isCloseable)
                    illogical(cannotCloseMsg);
                if(!isOpen)
                    illogicalNow(alreadyClosedMsg);
                logical;
            }

            action()
            {
                makeOpen(nil);
            }

            report()
            {
                DMsg(report close, 'Done. |{I} close{s/d} {1}. ',  gActionListStr);
            }
        }

        cannotCloseMsg = BMsg(not closeable, '{The subj dobj} {is} not something
            that {can} be closed. ')
        alreadyClosedMsg = BMsg(already closed,'{The subj dobj} {isn\'t} open. ')

        /*
         *   By default we make everything turnable, but lots of things clearly
         *   won't be.
         */
        isTurnable = true

        dobjFor(Turn)
        {

            preCond = [touchObj]

            verify()
            {
                if(!isTurnable)
                    illogical(cannotTurnMsg);
                else if(isDirectlyIn(gActor))
                    logical;
                else
                    logicalRank(80);
            }

            report()
            {
                say(turnNoEffectMsg);
            }

        }

        cannotTurnMsg = BMsg(cannot turn, '{The subj dobj} {can\'t} be turned. ')

        turnNoEffectMsg = BMsg(turn useless, 'Turning {1} {dummy} achieve{s/d}
            nothing. ', gActionListStr)

        dobjFor(TurnWith)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isTurnable)
                    illogical(cannotTurnMsg);
                else if(isDirectlyIn(gActor))
                    logical;
                else
                    logicalRank(80);
            }

            report()
            {
                say(turnNoEffectMsg);
            }

        }

        /* By default things can't be used to turn other things with */
        canTurnWithMe = nil

        iobjFor(TurnWith)
        {
            preCond = [objHeld]
            verify()
            {
                if(!canTurnWithMe)
                    illogical(cannotTurnWithMsg);

                if(gVerifyDobj == self)
                    illogical(cannotTurnWithSelfMsg);
            }
        }

        cannotTurnWithMsg = BMsg(cannot turn with, '{I} {can\'t} turn anything with
            {that iobj}. ')

        cannotTurnWithSelfMsg = BMsg(turn self, '{I} {cannot} turn anything with
            itself. ')

        /* By default things can't be cut */
        isCuttable = nil

        dobjFor(Cut)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isCuttable)
                   illogical(cannotCutMsg);
            }

            action() { askForIobj(CutWith); }
        }

        dobjFor(CutWith)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isCuttable)
                   illogical(cannotCutMsg);
            }
        }

        /* Most things can't be used to cut other things with */
        canCutWithMe = nil

        iobjFor(CutWith)
        {
            preCond = [objHeld]

            verify()
            {
                if(!canCutWithMe)
                    illogical(cannotCutWithMsg);

                if(self == gVerifyDobj)
                    illogicalSelf(cannotCutWithSelfMsg);
            }
        }

        cannotCutMsg = BMsg(cannot cut, '{I} {can\'t} cut {the dobj}. ')
        cannotCutWithMsg = BMsg(cannot cut with, '{I} {can\'t} cut anything with
            {that iobj}. ')
        cannotCutWithSelfMsg = BMsg(cannot cut with self, '{I} {cannot} cut anything
            with itself. ')

        /*
         *   If the actor finds something in a hiddenPrep list and there's nowhere
         *   obvious for it go, should he take it? By default the actor should take
         *   it if the object he's found it in/under/behind is fixed in place.
         */
        autoTakeOnFindHidden = (isFixed)

        /*
         *   Where should an item that's been hidden in/under/behind something be
         *   moved to when its found? If it's taken, move into the actor; otherwise
         *   move it to the location of the object it's just been found
         *   in/under/behind.
         */
        findHiddenDest = (autoTakeOnFindHidden ? gActor : location)

        dobjFor(LookIn)
        {
            preCond = [objVisible, containerOpen]

            remap = remapIn

            verify()
            {
                if(contType == In || remapIn != nil)
                    logicalRank(120);

                logical;
            }

            action()
            {
               /*
                *   If we're actually a container-type object, i.e. if our contType
                *   is In, try to determine what's inside us and display a list of
                *   it; if there's nothing inside us just display a message to that
                *   effect.
                */
                if(contType == In)
                {
                    /*
                     *   If there's anything hidden inside us move it into us before
                     *   doing anything else
                     */
                    if(hiddenIn.length > 0)
                        moveHidden(&hiddenIn, self);

                    /* If there's nothing inside us, simply display our lookInMsg */
                    if(contents.length == 0)
                        display(&lookInMsg);

                    /* Otherwise display a list of our contents */
                    else
                    {
                        /* Start by marking our contents as not mentioned. */
                        unmention(contents);

                        /*
                         *   It's possible that we have contents but nothing in our
                         *   contents is listable, so instead of just displaying a
                         *   list of contents we also watch to see if anything is
                         *   displayed; if nothing was we display our lookInMsg
                         *   instead.
                         */
                        if(gOutStream.watchForOutput(
                            {: listSubcontentsOf(self, &myLookInLister) }) == nil)
                          display(&lookInMsg);

                    }
                }

                /*
                 *   Otherwise, if we're not a container-type object (our contType
                 *   is not In), if there's anything in our hiddenIn list move it
                 *   into scope and display a list of it.
                 */
                else if(hiddenIn.length > 0)
                    findHidden(&hiddenIn, In);

                /*  Otherwise just display our lookInMsg */
                else
                    display(&lookInMsg);
            }

        }

        /*
         *   The lister to use when listing the objects inside me in response to a
         *   LOOK IN command. By default we use the lookInLister.
         */
        myLookInLister = lookInLister

        /*
         *   By default our lookInMsg just says the actor finds nothing of interest
         *   in us; this could be overridden for an objecy with a more interesting
         *   interior.
         */
        lookInMsg = BMsg(look in, '{I} {see} nothing interesting in {the
            dobj}. ')

        /*
         *   If there's something hidden in the dobj but nowhere obvious to move it
         *   to then by default we move everything from the hiddenIn list to the
         *   actor's inventory and announce that the actor has taken it. We call
         *   this out as a separate method to make it easy to override if desired.
         */
        findHidden(prop, prep)
        {
            /* Report what we find */
            sayFindHidden(prop, prep);

            /* Move the hidden items to the appropriate location. */
            moveHidden(prop, findHiddenDest);
        }

        /*
         *   Report what was found hidded in/under/behind us. We make this a
         *   separate method so that it can be easily customized on individual
         *   objects.
         */
        sayFindHidden(prop, prep)
        {
             DMsg(find hidden, '\^{1} {the dobj} {i} {find} {2}<<if findHiddenDest ==
                  gActor>>, which {i} {take}<<end>>. ',
                 prep.prep, makeListStr(self.(prop)));
        }

        /*
         *   We can look under most things, but there are some things (houses, the
         *   ground, sunlight) it might not make much sense to try looking under.
         */
        canLookUnderMe = true

        dobjFor(LookUnder)
        {
            preCond = [objVisible, touchObj]

            remap = remapUnder

            verify()
            {
                if(!canLookUnderMe)
                    illogical(cannotLookUnderMsg);
            }

            action()
            {
                /*
                 *   If we're actually an underside-type object, i.e. if our
                 *   contType is Under, try to determine what's under us and display
                 *   a list of it; if there's nothing under us just display a
                 *   message to that effect.
                 */
                if(contType == Under)
                {

                    /*
                     *   If there's anything hidden under us move it into us before
                     *   doing anything else
                     */
                    if(hiddenUnder.length > 0)
                        moveHidden(&hiddenUnder, self);

                    /* If there's nothing under us, simply display our lookUnerMsg */
                    if(contents.length == 0)
                        display(&lookUnderMsg);

                    /* Otherwise display a list of our contents */
                    else
                    {
                        /* Start by marking our contents as not mentioned. */
                        unmention(contents);

                        /*
                         *   It's possible that we have contents but nothing in our
                         *   contents is listable, so instead of just displaying a
                         *   list of contents we also watch to see if anything is
                         *   displayed; if nothing was we display our lookUnderMsg
                         *   instead.
                         */
                        if(gOutStream.watchForOutput(
                            {: listSubcontentsOf(self, &myLookUnderLister) }) == nil)
                            display(&lookUnderMsg);

                    }
                }

                /*
                 *   Otherwise, if we're not an underside-type object (our contType
                 *   is not Under), if there's anything in our hiddenUnder list move
                 *   it into scope and display a list of it.
                 */
                else if(hiddenUnder.length > 0)
                    findHidden(&hiddenUnder, Under);

                /*  Otherwise just display our lookUnderMsg */
                else
                    display(&lookUnderMsg);

            }
        }

        /*
         *   The lister to use when listing the objects under me in response to a
         *   LOOK UNDER command. By default we use the lookInLister.
         */
        myLookUnderLister = lookInLister

        cannotLookUnderMsg = BMsg(cannot look under, '{I} {can\'t} look under {that
            dobj}. ')

        lookUnderMsg = BMsg(look under, '{I} {find} nothing of interest under
            {the dobj}. ')

        /*
         *   By default we make it possible to look behind things, but there could
         *   be many things it makes no sense to try to look behind.
         */
        canLookBehindMe = true

        dobjFor(LookBehind)
        {
            preCond = [objVisible, touchObj]

            remap = remapBehind

            verify()
            {
                if(!canLookBehindMe)
                    illogical(cannotLookBehindMsg);
            }

            action()
            {
                /*
                 *   If we're actually a rear-type object, i.e. if our contType is
                 *   Behind, try to determine what's behind us and display a list of
                 *   it; if there's nothing behind us just display a message to that
                 *   effect.
                 */
                if(contType == Behind)
                {

                    /*
                     *   If there's anything hidden behind us move it into us before
                     *   doing anything else
                     */
                    if(hiddenBehind.length > 0)
                        moveHidden(&hiddenBehind, self);

                    /*
                     *   If there's nothing behind us, simply display our
                     *   lookBehindMsg
                     */
                    if(contents.length == 0)
                        display(&lookBehindMsg);

                    /* Otherwise display a list of our contents */
                    else
                    {
                        /* Start by marking our contents as not mentioned. */
                        unmention(contents);

                        /*
                         *   It's possible that we have contents but nothing in our
                         *   contents is listable, so instead of just displaying a
                         *   list of contents we also watch to see if anything is
                         *   displayed; if nothing was we display our lookBehindMsg
                         *   instead.
                         */
                        if(gOutStream.watchForOutput(
                            {: listSubcontentsOf(self, &myLookBehindLister) }) == nil)
                            display(&lookBehindMsg);

                    }
                }

                /*
                 *   Otherwise, if we're not a rear-type object (our contType is not
                 *   Behind), if there's anything in our hiddenBehind list move it
                 *   into scope and display a list of it.
                 */
                else if(hiddenBehind.length > 0)
                    findHidden(&hiddenBehind, Behind);

                /*  Otherwise just display our lookBehindMsg */
                else
                    display(&lookBehindMsg);

            }
        }

        /*
         *   The lister to use when listing the objects behind me in response to a
         *   LOOK BEHIND command. By default we use the lookInLister.
         */
        myLookBehindLister = lookInLister

        cannotLookBehindMsg = BMsg(cannot look behind, '{I} {can\'t} look behind
            {that dobj}. ')

        lookBehindMsg = BMsg(look behind, '{I} {find} nothing behind {the
            dobj}. ')

        /*
         *   By default we make it possible to look through things, but there may
         *   well be things you obviously couldn't look through.
         */
        canLookThroughMe = true

        dobjFor(LookThrough)
        {
            preCond = [objVisible]

            verify()
            {
                if(!canLookThroughMe)
                    illogical(cannotLookThroughMsg);
            }

            action() { display(&lookThroughMsg); }
        }

        cannotLookThroughMsg = BMsg(cannot look through, '{I} {can\'t} look through
            {that dobj}. ')

        lookThroughMsg = BMsg(look through, '{I} {see} nothing through {the
            dobj}. ')

        /* Most things cannot be gone through */
        canGoThroughMe = nil

        dobjFor(GoThrough)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canGoThroughMe)
                    illogical(cannotGoThroughMsg);
            }
        }

        cannotGoThroughMsg = BMsg(cannot go through,'{I} {can\'t} go through {that
            dobj}. ')

        /* Most things cannot be gone along */
        canGoAlongMe = nil

        dobjFor(GoAlong)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canGoAlongMe)
                    illogical(cannotGoAlongMsg);
            }
        }

        cannotGoAlongMsg = BMsg(cannot go through,'{I} {can\'t} go along {that
            dobj}. ')

        /* We can at least try to push most things. */
        isPushable = true

        dobjFor(Push)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isPushable)
                    illogical(cannotPushMsg);
            }

            report() { say(pushNoEffectMsg); }
        }

        cannotPushMsg = BMsg(cannot push, 'There{\'s} no point trying to push
            {that dobj}. ')

        pushNoEffectMsg = BMsg(push no effect, 'Pushing {1} {dummy} {has} no
            effect. ', gActionListStr)

        /* We can at least try to pull most things. */
        isPullable = true

        dobjFor(Pull)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isPullable)
                    illogical(cannotPullMsg);
            }

            report() { say(pullNoEffectMsg); }
        }

        cannotPullMsg = BMsg(cannot pull, 'There{\'s} no point trying to pull
            {that dobj}. ')

        pullNoEffectMsg = BMsg(pull no effect, 'Pulling {1} {dummy} {has} no
            effect. ', gActionListStr)

        /*
         *   The most usual reason why we can't put something somewhere is that we
         *   can't pick it up in the first place, so by default we'll just copy
         *   cannotPutMsg from cannotTakeMsg.
         */
        cannotPutMsg = cannotTakeMsg

        dobjFor(PutOn)
        {
            preCond = [objHeld, objNotWorn]

            verify()
            {
                if(gVerifyIobj == self)
                    illogicalSelf(cannotPutInSelfMsg);

                if(isFixed)
                    illogical(cannotPutMsg);

                if(isDirectlyIn(gVerifyIobj))
                    illogicalNow(alreadyInMsg);

                if(gVerifyIobj.isIn(self))
                    illogicalNow(circularlyInMsg);

                logical;
            }

            action()
            {
                /* Handled on iobj */
            }

            report()
            {
                DMsg(report put on, '{I} {put} {1} on {the iobj}. ', gActionListStr);
            }
        }

        alreadyInMsg = BMsg(already in, '{The subj dobj} {is} already {1}. ', gVerifyIobj.objInName)

        circularlyInMsg = BMsg(circularly in, '{I} {can\'t} put {the dobj} {1}
            while {the subj iobj} {is} {in dobj}. ', gVerifyIobj.objInName)

        cannotPutInSelfMsg = BMsg(cannot put in self, '{I} {can\'t} put {the dobj}
            {1} {itself dobj}. ', gIobj.objInPrep)

        iobjFor(PutOn)
        {

            preCond = [touchObj]

            remap = remapOn

            verify()
            {
                if(contType != On)
                    illogical(cannotPutOnMsg);

                logical;
            }

            check()
            {
                checkInsert(gDobj);
            }

            action()
            {
                gDobj.actionMoveInto(self);
            }

        }

        cannotPutOnMsg = BMsg(cannot put on,'{I} {can\'t} put anything on {the
            iobj}. '   )

        dobjFor(PutIn)
        {
            preCond = [objHeld, objNotWorn]

            verify()
            {
                if(gVerifyIobj == self)
                    illogicalSelf(cannotPutInSelfMsg);

                if(isDirectlyIn(gVerifyIobj))
                    illogicalNow(alreadyInMsg);

                if(isFixed)
                    illogical(cannotPutMsg);

                if(gVerifyIobj.isIn(self))
                    illogicalNow(circularlyInMsg);

                logical;
            }

            action()
            {
                /* handled on iobj */
            }

            report()
            {
                DMsg(report put in, '{I} {put} {1} in {the iobj}. ', gActionListStr);
            }
        }

        iobjFor(PutIn)
        {
            preCond = [containerOpen, touchObj]

            remap = remapIn

            verify()
            {
                if(!canPutInMe)
                    illogical(cannotPutInMsg);

                logical;
            }

            check()
            {
                /*
                 *   If we're actually a container-like object (our contType is In),
                 *   check whether there's enough room inside us to contain the
                 *   direct object.
                 */
                if(contType == In)
                   checkInsert(gDobj);

                /*
                 *   Otherwise check whether adding the direct object to our
                 *   hiddenIn list would exceed the amount of bulk allowed there.
                 */
                else if(gDobj.bulk > maxBulkHiddenIn - getBulkHiddenIn)
                    DMsg(no room in, 'There {dummy}{isn\'t} enough room for {the
                        dobj} in {the iobj}. ');
            }

            action()
            {
                /*
                 *   If we're actually a container-like object (i.e. if our contType
                 *   is In) then something put in us can be moved inside us.
                 *   Otherwise, all we can do with something put in us is to add it
                 *   to our hiddenIn list and move it off-stage.
                 */
                if(contType == In)
                    gDobj.actionMoveInto(self);
                else
                {
                    hiddenIn += gDobj;
                    gDobj.actionMoveInto(nil);
                }
            }

        }

        cannotPutInMsg = BMsg(cannot put in, '{I} {can\'t} put anything in {the
            iobj}. ')

        dobjFor(PutUnder)
        {
            preCond = [objHeld, objNotWorn]

            verify()
            {
                if(gVerifyIobj == gIobj)
                    illogicalSelf(cannotPutInSelfMsg);

                if(isFixed)
                    illogical(cannotPutMsg);

                if(isDirectlyIn(gVerifyIobj))
                    illogicalNow(alreadyInMsg);

                if(gVerifyIobj.isIn(self))
                    illogicalNow(circularlyInMsg);

                logical;
            }

            action()
            {
                /* Handled by iobj */
            }

            report()
            {
                DMsg(report put under, '{I} {put} {1} under {the iobj}. ',
                     gActionListStr);
            }

        }

        iobjFor(PutUnder)
        {
            preCond = [touchObj]

            remap = remapUnder

            verify()
            {
                if(!canPutUnderMe)
                    illogical(cannotPutUnderMsg);
                else
                    logical;
            }

            check()
            {
                /*
                 *   If we're actually an underside-like object (our contType is
                 *   Under), check whether there's enough room under us to contain
                 *   the direct object.
                 */
                if(contType == Under)
                   checkInsert(gDobj);

                /*
                 *   Otherwise check whether adding the direct object to our
                 *   hiddenUnder list would exceed the amount of bulk allowed there.
                 */
                else if(gDobj.bulk > maxBulkHiddenUnder - getBulkHiddenUnder)
                    DMsg(no room under, 'There {dummy}{isn\'t} enough room for {the
                        dobj} under {the iobj}. ');
            }

            action()
            {
                /*
                 *   If we're actually an underside-like object (i.e. if our
                 *   contType is Under) then something put under us can be moved
                 *   inside us. Otherwise, all we can do with something put under us
                 *   is to add it to our hiddenUnder list and move it off-stage.
                 */
                if(contType == Under)
                    gDobj.actionMoveInto(self);
                else
                {
                    hiddenUnder += gDobj;
                    gDobj.actionMoveInto(nil);
                }
            }

        }

        cannotPutUnderMsg = BMsg(cannot put under, '{I} {cannot} put anything under
            {the iobj}. ' )

        dobjFor(PutBehind)
        {
            preCond = [objHeld, objNotWorn]

            verify()
            {
                if(gVerifyIobj == self)
                    illogicalSelf(cannotPutInSelfMsg);

                if(isFixed)
                    illogical(cannotPutMsg);

                if(isDirectlyIn(gVerifyIobj))
                    illogicalNow(alreadyInMsg);

                if(gVerifyIobj.isIn(self))
                    illogicalNow(circularlyInMsg);

                logical;
            }

            action()
            {
                /* Handled by iobj */
            }

            report()
            {
                DMsg(report put behind, '{I} {put} {1} behind {the iobj}. ',
                     gActionListStr);
            }

        }

        iobjFor(PutBehind)
        {
            preCond = [touchObj]

            remap = remapBehind

            verify()
            {
                if(!canPutBehindMe)
                    illogical(cannotPutBehindMsg);
                else
                    logical;
            }

            check()
            {
                /*
                 *   If we're actually a rear-like object (our contType is Behind),
                 *   check whether there's enough room behind us to contain the
                 *   direct object.
                 */
                if(contType == Behind)
                    checkInsert(gDobj);

                /*
                 *   Otherwise check whether adding the direct object to our
                 *   hiddenBehind list would exceed the amount of bulk allowed
                 *   there.
                 */
                 else if(gDobj.bulk > maxBulkHiddenBehind - getBulkHiddenBehind)
                    DMsg(no room behind, 'There {dummy}{isn\'t} enough room for {the
                        dobj} behind {the iobj}. ');
            }

            action()
            {
                /*
                 *   If we're actually a rear-like object (i.e. if our contType is
                 *   Behind) then something put behind us can be moved inside us.
                 *   Otherwise, all we can do with something put behind us is to add
                 *   it to our hiddenBehind list and move it off-stage.
                 */
                if(contType == Behind)
                    gDobj.actionMoveInto(self);
                else
                {
                    hiddenBehind += gDobj;
                    gDobj.actionMoveInto(nil);
                }
            }

        }

        cannotPutBehindMsg = BMsg(cannot put behind, '{I} {cannot} put anything
            behind {the iobj}. ')

        /*
         *   A list of Keys that can be used to lock or unlock this Thing. Any Keys
         *   in this list will cause this Thing to be added to the plausible and
         *   actual lock lists of that Key at PreInit. This provides an alternative
         *   way of specifying the relation between locks and keys.
         */
        keyList = nil

        /*
         *   A list of Keys that the player character starts out knowing at the
         *   start of the game can lock our unlock this Thing.
         */
        knownKeyList = nil

        /*
         *   Note: we don't use isLockable, because this is not a binary property;
         *   there are different kings of lockability and defining an isLockable
         *   property in addition would only confuse things and might break the
         *   logic.
         */
        dobjFor(UnlockWith)
        {

            preCond = [touchObj]

            /*
             *   Remap the unlock action to our remapIn object if we're not lockable
             *   but we have a lockable remapIn object (i.e. an associated
             *   container).
             */
            remap()
            {
                if(lockability == notLockable && remapIn != nil &&
                   remapIn.lockability != notLockable)
                    return remapIn;
                else
                    return self;
            }

            verify()
            {
                /*
                 *   If we're not lockable at all, we're a very poor choice of
                 *   direct object for an UnlockWith action.
                 */
                if(lockability == notLockable || lockability == nil)
                    illogical(notLockableMsg);

                /*
                 *   If we're lockable, but not with a key (either because we don't
                 *   need one at all or because we use some other form of locking
                 *   mechanism) then we're still a bad choice of object for an
                 *   UnlockWith action, but not so bad as if we weren't lockable at
                 *   all.
                 */
                if(lockability == lockableWithoutKey)
                    implausible(keyNotNeededMsg);

                if(lockability == indirectLockable)
                    implausible(indirectLockableMsg);

                /*
                 *   If we are lockable with key, then were a good choice of object
                 *   for an UnlockWith action provided we're currently locked.
                 */
                if(lockability == lockableWithKey)
                {
                    if(isLocked)
                        logical;
                    else
                        illogicalNow(notLockedMsg);
                }
            }
        }

        notLockableMsg = BMsg(not lockable, '{The subj dobj} {isn\'t} lockable. ')
        keyNotNeededMsg = BMsg(key not needed,'{I} {don\'t need[ed]} a key to lock
            and unlock {the dobj}. ')
        indirectLockableMsg = BMsg(indirect lockable,'{The dobj} appear{s/ed} to use
            some other kind of locking mechanism. ')
        notLockedMsg = BMsg(not locked, '{The subj dobj} {isn\'t} locked. ')

        /*
         *   Most things can't be used to unlock with. In practice there's probably
         *   little point in overriding this property since if you do want to use
         *   something to unlock other things with, you'd use the Key class.
         */
        canUnlockWithMe = nil

        iobjFor(UnlockWith)
        {
            preCond = [touchObj]

            verify()
            {
                if(!canUnlockWithMe)
                   illogical(cannotUnlockWithMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotUnlockWithSelfMsg);
            }
        }

        cannotUnlockWithMsg = BMsg(cannot unlock with, '{I} {can\'t} unlock
            anything with {that iobj}. ' )

        cannotUnlockWithSelfMsg = BMsg(cannot unlock with self, '{I} {can\'t} unlock
            anything with itself. ' )

        dobjFor(LockWith)
        {
            preCond  = [objClosed, touchObj]

             /*
              *   Remap the lock action to our remapIn object if we're not lockable
              *   but we have a lockable remapIn object (i.e. an associated
              *   container).
              */
            remap()
            {
                if(lockability == notLockable && remapIn != nil &&
                   remapIn.lockability != notLockable)
                    return remapIn;
                else
                    return self;
            }

            verify()
            {
                if(lockability == notLockable || lockability == nil)
                    illogical(notLockableMsg);

                if(lockability == lockableWithoutKey)
                    implausible(keyNotNeededMsg);

                if(lockability == indirectLockable)
                    implausible(indirectLockableMsg);

                if(lockability == lockableWithKey)
                {
                    if(isLocked)
                       illogicalNow(alreadyLockedMsg);
                    else
                        logical;
                }
            }

        }

        alreadyLockedMsg = BMsg(already locked, '{The subj dobj} {is} already
            locked. ')

        /*
         *   Usually, if something can be used to unlock things it can also be used
         *   to lock them
         */
        canLockWithMe = (canUnlockWithMe)

        iobjFor(LockWith)
        {
            preCond = [touchObj]

            verify()
            {
                if(!canLockWithMe)
                   illogical(cannotLockWithMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotLockWithSelfMsg);
            }
        }

        cannotLockWithMsg = BMsg(cannot lock with, '{I} {can\'t} lock anything with
            {that iobj}. ' )

        cannotLockWithSelfMsg = BMsg(cannot lock with self, '{I} {can\'t} lock
            anything with itself. ' )

        dobjFor(Unlock)
        {
            preCond = [touchObj]

            /*
             *   Remap the unlock action to our remapIn object if we're not lockable
             *   but we have a lockable remapIn object (i.e. an associated
             *   container).
             */
            remap()
            {
                if(lockability == notLockable && remapIn != nil &&
                   remapIn.lockability != notLockable)
                    return remapIn;
                else
                    return self;
            }

            verify()
            {
                if(lockability == notLockable || lockability == nil)
                    illogical(notLockableMsg);

                if(lockability == indirectLockable)
                    implausible(indirectLockableMsg);

                if(!isLocked)
                    illogicalNow(notLockedMsg);
            }

            check()
            {
                /*
                 *   if we need a key to be unlocked with, check whether the player
                 *   is holding a suitable one.
                 */
                if(lockability == lockableWithKey)
                    findPlausibleKey();

            }

            action()
            {
                /*
                 *   The useKey_ property will have been set by the
                 *   findPlausibleKey() method at the check stage. If it's non-nil
                 *   it's the key we're going to use to try to unlock this object
                 *   with, so we display a parenthetical note to the player that
                 *   we're using this key. (Note: the action would have failed at
                 *   the check stage if useKey_ wasn't the right key for the job).
                 */
                if(useKey_ != nil)
                    extraReport(withKeyMsg);

                /*
                 *   Otherwise, if we need a key to unlock this object with, ask the
                 *   player to specify it and then execute an UnlockWith action
                 *   using that key.
                 */
                else if(lockability == lockableWithKey)
                    askForIobj(UnlockWith);

                /*  Make us unlocked. */
                makeLocked(nil);
            }

            report()
            {
                DMsg(report unlock, okayUnlockMsg, gActionListStr);
            }

        }

        okayUnlockMsg = 'Unlocked.|{I} unlock{s/ed} {1}. '

        dobjFor(Lock)
        {
            preCond = [objClosed, touchObj]

             /*
              *   Remap the lock action to our remapIn object if we're not lockable
              *   but we have a lockable remapIn object (i.e. an associated
              *   container).
              */
            remap()
            {
                if(lockability == notLockable && remapIn != nil &&
                   remapIn.lockability != notLockable)
                    return remapIn;
                else
                    return self;
            }

            verify()
            {
                if(lockability == notLockable || lockability == nil)
                    illogical(notLockableMsg);

                if(lockability == indirectLockable)
                    implausible(indirectLockableMsg);

                if(isLocked)
                    illogicalNow(alreadyLockedMsg);

            }

            check()
            {
                /*
                 *   if we need a key to be locked with, check whether the player
                 *   is holding a suitable one.
                 */
                if(lockability == lockableWithKey)
                    findPlausibleKey();

            }

            action()
            {
                /*
                 *   The useKey_ property will have been set by the
                 *   findPlausibleKey() method at the check stage. If it's non-nil
                 *   it's the key we're going to use to try to lock this object
                 *   with, so we display a parenthetical note to the player that
                 *   we're using this key. (Note: the action would have failed at
                 *   the check stage if useKey_ wasn't the right key for the job).
                 */
                if(useKey_ != nil)
                    extraReport(withKeyMsg);

                /*
                 *   Otherwise, if we need a key to unlock this object with, ask the
                 *   player to specify it and then execute a LockWith action
                 *   using that key.
                 */
                else if(lockability == lockableWithKey)
                    askForIobj(LockWith);

                /*  Make us locked. */
                makeLocked(true);
            }

            report()
            {
                DMsg(report lock, okayLockMsg, gActionListStr);
            }
        }

        okayLockMsg = 'Locked.|{I} {lock} {1}. '

        withKeyMsg = BMsg(with key, '(with {1})\n', useKey_.theName)

        /*
         *   Find a key among the actor's possessions that might plausibly lock or
         *   unlock us. If the silent parameter is true, then don't report a failed
         *   attempt.
         */
        findPlausibleKey(silent = nil)
        {

            useKey_ = nil;
            local lockObj = self;

            /*
             *   First see if the actor is holding a key that is known to work on
             *   this object. If so, use it.
             */
            foreach(local obj in gActor.contents)
            {
                if(obj.ofKind(Key)
                   && obj.knownLockList.indexOf(self) !=  nil)
                {
                    useKey_ = obj;
                    return;
                }
            }

            /*
             *   Then see if the actor is holding a key that might plausibly work on
             *   this object; if so, try that.
             */
            foreach(local obj in gActor.contents)
            {
                if(obj.ofKind(Key)
                   && obj.plausibleLockList.indexOf(self) !=  nil)
                {
                    useKey_ = obj;
                    break;
                }
            }

            /*
             *   If we haven't found a suitable key yet, check to see if the actor
             *   is holding one that might fit our lexicalParent, if we have a
             *   lexicalParent whose interior we're representing.
             */
            if(useKey_ == nil)
            {
                if(lexicalParent != nil && lexicalParent.remapIn == self)
                {
                    lexicalParent.findPlausibleKey();
                    useKey_ = lexicalParent.useKey_;
                    lockObj = lexicalParent;
                }
            }

            /*
             *   If we've found a possible key but it doesn't actually work on this
             *   object, report that we're trying this key but it doesn't work.
             */
            if(useKey_ && useKey_.actualLockList.indexOf(lockObj) == nil && !silent)
            {
                say(withKeyMsg);
                say(keyDoesntWorkMsg);
            }

        }

        keyDoesntWorkMsg = BMsg(key doesnt work, 'Unfortunately {1} {dummy}
            {doesn\'t work[ed]} on {the dobj}. ', useKey_.theName)

        useKey_ = nil

        dobjFor(SwitchOn)
        {

            preCond = [touchObj]

            verify()
            {
                if(!isSwitchable)
                    illogical(notSwitchableMsg);
                else if(isOn)
                    illogicalNow(alreadyOnMsg);
            }

            action()
            {
                makeOn(true);
            }

            report()
            {
                DMsg(report turn on, 'Done.|{I} turn{s/ed} on {the dobj}. ');
            }
        }

        notSwitchableMsg = BMsg(not switchable, '{The subj dobj} {can\'t} be
            switched on and off. ')
        alreadyOnMsg = BMsg(already switched on, '{The subj dobj} {is} already
            switched on. ')

        dobjFor(SwitchOff)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isSwitchable)
                    illogical(notSwitchableMsg);
                else if(!isOn)
                    illogicalNow(alreadyOffMsg);
            }

            action()
            {
                makeOn(nil);
            }

            report()
            {
                DMsg(report turn off, 'Done.|{I} turn{s/ed} off {the dobj}. ');
            }
        }

       alreadyOffMsg = BMsg(not switched on, '{The subj dobj} {isn\'t} switched on.
           ')

        dobjFor(SwitchVague)
        {
            verify()
            {
                if(!isSwitchable)
                    illogical(notSwitchableMsg);
            }

            action()
            {
                makeOn(!isOn);
            }

            report()
            {
                DMsg(report switch, 'Okay, {i} turn{s/ed} {1} {2}. ', isOn ?
                     'on' : 'off', gActionListStr);
            }
        }

        /*
         *   Since FLIP X is often synonymous with SWITCH X , by default we make
         *   something flippable if it's switchable.
         */
        isFlippable = (isSwitchable)

        dobjFor(Flip)
        {
            verify()
            {
                if(!isFlippable)
                   illogical(cannotFlipMsg);
            }
        }

        cannotFlipMsg = BMsg(cannot flip, '{I} {can\'t} usefully flip {the dobj}. ')

        /* By default we assume most things aren't burnable */
        isBurnable = nil

        dobjFor(Burn)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isBurnable)
                   illogical(cannotBurnMsg);
            }
        }

        dobjFor(BurnWith)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isBurnable)
                   illogical(cannotBurnMsg);
            }
        }

        /*
         *   By default we assume most things can't be used to burn other things
         *   with.
         */
        canBurnWithMe = nil

        iobjFor(BurnWith)
        {
            preCond = [objHeld]
            verify()
            {
                if(!canBurnWithMe)
                    illogical(cannotBurnWithMsg);
            }
        }

        cannotBurnMsg = BMsg(cannot burn, '{I} {cannot} burn {the dobj}. ')
        cannotBurnWithMsg = BMsg(cannot burn with, '{I} {can\'t} burn {the dobj}
            with {that iobj}. ')

        dobjFor(Wear)
        {
            preCond = [objHeld]

            verify()
            {
                if(!isWearable)
                    illogical(cannotWearMsg);

                if(wornBy == gActor)
                    illogicalNow(alreadyWornMsg);
            }

            action()  {  makeWorn(gActor);  }

            report()
            {
                DMsg(okay wear, 'Okay, {i}{\'m} now wearing {1}. ',
                     gActionListStr);
            }
        }

        cannotWearMsg = BMsg(cannot wear, '{The subj dobj} {can\'t} be worn. ')
        alreadyWornMsg = BMsg(already worn, '{I}{\'m} already wearing {the dobj}. ')

        /* By default we assume that something's doffable if it's wearable */
        isDoffable = (isWearable)

        dobjFor(Doff)
        {

            verify()
            {
                if(wornBy != gActor)
                    illogicalNow(notWornMsg);

                if(!isDoffable)
                    illogical(cannotDoffMsg);
            }

            check()
            {
                checkRoomToHold();
            }

            action()  {   makeWorn(nil);  }

            report()
            {
                DMsg(okay doff, 'Okay, {I}{\'m} no longer wearing {1}. ',
                     gActionListStr);

            }
        }

        cannotDoffMsg = (cannotWearMsg)

        notWornMsg = BMsg(not worn, '{I}{\'m} not wearing {the dobj}. ')

        /* Most things can't be climbed */
        isClimbable = nil

        dobjFor(Climb)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isClimbable)
                   illogical(cannotClimbMsg);
            }
        }

        canClimbUpMe = (isClimbable)

        dobjFor(ClimbUp)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canClimbUpMe)
                   illogical(cannotClimbMsg);
            }
        }

        cannotClimbMsg = BMsg(cannot climb,'{The subj dobj} {is} not something {i}
            {can} climb. ')

        canClimbDownMe = (isClimbable)

        dobjFor(ClimbDown)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canClimbDownMe)
                   illogical(cannotClimbDownMsg);
            }
        }

        cannotClimbDownMsg = BMsg(cannot climb down, '{The subj dobj} {is} not
            something {i} {can} climb down. ')

        dobjFor(Throw)
        {
            preCond = [objHeld, objNotWorn]

            verify()
            {
                if(!isThrowable)
                    illogical(cannotThrowMsg);
            }

            action()
            {
                actionMoveInto(getOutermostRoom.dropLocation);
            }

            report()
            {
                local obj = gActionListObj;
                gMessageParams(obj);
                DMsg(throw, '{The subj obj} sail{s/ed} through the air and land{s/ed}
                    on the ground. ' );
            }

        }

        dobjFor(Board)
        {
            preCond = [touchObj, actorInStagingLocation]

            remap = remapOn

            verify()
            {
                if(!isBoardable || contType != On)
                    illogical(cannotBoardMsg);

                if(gActor.isIn(self))
                    illogicalNow(actorAlreadyOnMsg);

                if(isIn(gActor))
                    illogicalNow(cannotGetOnCarriedMsg);
            }

            check() { checkInsert(gActor); }

            action()
            {
                gActor.actionMoveInto(self);
            }

            report()
            {
                DMsg(okay get on, '{I} {get} on {1}. ', gActionListStr);
            }
        }

        cannotBoardMsg = BMsg(cannot board, '{The subj dobj} {is} not something {i}
            {can} get on. ')
        actorAlreadyOnMsg = BMsg(already on, '{I}{\'m} already {in dobj}. ')

        cannotGetOnCarriedMsg = BMsg(cannot board carried, '{I} {can\'t} get on {the
            dobj} while {i}{\'m} carrying {him dobj}. ')

        /*
         *   Since we don't track postures in this library we'll treat STAND ON as
         *   equivalent to BOARD
         */
        dobjFor(StandOn) asDobjWithoutVerifyFor(Board)
        dobjFor(SitOn) asDobjWithoutVerifyFor(Board)
        dobjFor(LieOn) asDobjWithoutVerifyFor(Board)

        /*
         *   Although we don't track postures as such, some objects may be better
         *   choices than other for sitting on (e.g. chairs), lying on (e.g. beds)
         *   and standing on (e.g. rugs), so we allow these to be tested for
         *   individually at the verify stage.
         *
         *   Note that none of these three properties (canSitOnMe, canLieOnMe,
         *   canStandOnMe) should normally be overridden to simply true, since they
         *   cannot make it possible to sit, lie or stand on something for which
         *   isBoardable is not true (or which contType is not On).
         */
        canSitOnMe = isBoardable
        canLieOnMe = isBoardable
        canStandOnMe = isBoardable

        /*
         *   As well as ruling out certain objects for sitting, lying or standing
         *   on, we can also give them a score for each of these postures; e.g. a
         *   bed may be particularly suitable for lying on (although you could lie
         *   on the sofa) while a chair may be particularly suitable for sitting on
         *   (though you could sit on the bed.
         *
         *   By default we'll give each posture a score of 100, the normal logical
         *   verify score. Note that these scores have no effect if the
         *   corresponding can xxxOnMe property is nil.
         */
        sitOnScore = 100
        lieOnScore = 100
        standOnScore = 100

        dobjFor(StandOn)
        {
            verify()
            {
                if(!canStandOnMe)
                    illogical(cannotStandOnMsg);
                else
                    verifyDobjBoard();

                logicalRank(standOnScore);
            }
        }

        dobjFor(SitOn)
        {
            verify()
            {
                if(!canSitOnMe)
                    illogical(cannotSitOnMsg);
                else
                    verifyDobjBoard();

                logicalRank(sitOnScore);
            }
        }

        dobjFor(LieOn)
        {
            verify()
            {
                if(!canLieOnMe)
                    illogical(cannotLieOnMsg);
                else
                    verifyDobjBoard();

                logicalRank(lieOnScore);
            }
        }

        cannotStandOnMsg = BMsg(cannot stand on, '{The subj dobj} {isn\'t}
            something {i} {can} stand on. ')
        cannotSitOnMsg = BMsg(cannot sit on, '{The subj dobj} {isn\'t}
            something {i} {can} sit on. ')
        cannotLieOnMsg = BMsg(cannot lie on, '{The subj dobj} {isn\'t}
            something {i} {can} lie on. ')

        /*
         *   Flag, can we enter (i.e. get inside) this thing? For most objects, we
         *   can't
         */
        isEnterable = nil

        /*   Treat Enter X as equivalent to Get In X */

        dobjFor(Enter)
        {
            preCond = [touchObj, containerOpen, actorInStagingLocation]

            remap = remapIn

            verify()
            {
                if(!isEnterable || contType != In)
                    illogical(cannotEnterMsg);

                if(gActor.isIn(self))
                    illogicalNow(actorAlreadyInMsg);

                if(isIn(gActor))
                    illogicalNow(cannotGetInCarriedMsg);
            }

            check() { checkInsert(gActor); }

            action()
            {
                gActor.actionMoveInto(self);
            }

            report()
            {
                DMsg(okay get in, '{I} {get} in {1}. ', gActionListStr);
            }

        }

        cannotEnterMsg = BMsg(cannot enter, '{The subj dobj} {is} not something {i}
            {can} enter. ')
        actorAlreadyInMsg = BMsg(actor already in, '{I}{\'m} already {in dobj}. ')

        cannotGetInCarriedMsg = BMsg(cannot enter carried, '{I} {can\'t} get in {the
            dobj} while {i}{\'m} carrying {him dobj}. ')

        /*
         *   By default we'll treat standing, sitting or lying IN something as
         *   simply equivalent to entering it.
         */
        dobjFor(StandIn) asDobjFor(Enter)
        dobjFor(SitIn) asDobjFor(Enter)
        dobjFor(LieIn) asDobjFor(Enter)

        /*
         *   Our exitLocation is the location an actor should be moved to when s/he
         *   gets off/out of us.
         */
        exitLocation = (lexicalParent == nil ? location : lexicalParent.location)

        /*   Our staging location is where we need to be to get on/in us */
        stagingLocation = (exitLocation)

        dobjFor(GetOff)
        {

            remap = remapOn

            verify()
            {
                if(!gActor.isIn(self) || contType != On)
                    illogicalNow(actorNotOnMsg);

            }

            action()
            {
                gActor.actionMoveInto(exitLocation);
            }

            report() { say(okayGetOutOfMsg); }
        }

        dobjFor(GetOutOf)
        {
            preCond = [containerOpen]

            remap = remapIn

            verify()
            {
                if(!gActor.isIn(self) || contType != In)
                    illogicalNow(actorNotInMsg);

            }

            action()
            {
                gActor.actionMoveInto(exitLocation);
            }

            report() { say(okayGetOutOfMsg); }
        }

        okayGetOutOfMsg = BMsg(okay get outof, 'Okay, {i} {get} {outof dobj}. ')

        actorNotInMsg = BMsg(actor not in,'{I}{\'m} not in {the dobj}. ')
        actorNotOnMsg = BMsg(actor not on,'{I}{\'m} not on {the dobj}. ')

        /*
         *   We'll take REMOVE to mean DOFF when it's dobj is worn and TAKE
         *   otherwise. This handling will be dealt with by removeDoer insteadof
         *   remap, since this form of remap has now been discontinued. See
         *   english.t for removeDoer (which seems to be a language-specific
         *   construct)
         */
        dobjFor(Remove)
        {
            /*
             *   We still need a verify() routine to help the parser find a suitable
             *   target for the command.
             */

            verify()
            {
                if(!isRemoveable)
                    illogical(cannotRemoveMsg);

                /*
                 *   If we're already holding the object (and not wearing it),
                 *   there's nothing for remove to do.
                 */
                if(isDirectlyIn(gActor) && wornBy != gActor)
                    illogicalNow(alreadyHeldMsg);
            }

        }

        /* By default an object is removeable if it's takeable */

        isRemoveable = (isTakeable)

        /*
         *   Note that this message should never display in an English-language game
         *   since removeDoer will intercept the action before it gets to this
         *   point.
         */
        cannotRemoveMsg = BMsg(cannot remove, '{The subj dobj} {cannot} be removed.
            ')

        /* Treat SEARCH as equivalent to LOOK IN */
        dobjFor(Search) asDobjFor(LookIn)

        /*
         *   By default we assume anything fixed isn't moveable. That's not
         *   necessarily the case since we may be able to move something by pushing
         *   it around (say) even if we can't pick it up.
         */
        isMoveable = (!isFixed)

        /*
         *   Moving an object is generally possible if the object is portable, but
         *   there's no obvious effect, so by default this action does nothing
         *   except say it's done nothing.
         */
        dobjFor(Move)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isMoveable)
                    illogical(cannotMoveMsg);
            }

            action()  {  }

            report()
            {
                say(moveNoEffectMsg);
            }
        }

        cannotMoveMsg = (cannotTakeMsg)

        dobjFor(MoveWith)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isMoveable)
                    illogical(cannotMoveMsg);

            }

            action() {  }

            report()
            {
                say(moveNoEffectMsg);
            }
        }

        moveNoEffectMsg = BMsg(move no effect, 'Moving {1} {dummy} {has} no effect. ',
                     gActionListStr)

        /*
         *   Most things can't be used to move other things with. Note that since
         *   the dobj is resolved first, objects or subclasses could override this
         *   with a method that returns true or nil depending on the identity of the
         *   dobj.
         */
        canMoveWithMe = nil

        iobjFor(MoveWith)
        {
            preCond = [objHeld]

            verify()
            {
                if(!canMoveWithMe)
                   illogical(cannotMoveWithMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotMoveWithSelfMsg);
            }
        }

        cannotMoveWithMsg = BMsg(cannot move with, '{I} {can\'t} move {the dobj}
            with {the iobj}. ')

        cannotMoveWithSelfMsg = BMsg(cannot move with self, '{The subj dobj}
            {can\'t} be used to move {itself dobj}. ')

        /*
         *   MoveTo is a more complex case than MOVE or MOVE WITH. In this
         *   implementation we assume that it represents moving the direct object to
         *   the vicinity of the indirect object, and so we track the object it's
         *   been moved to.
         *
         *   This might be useful, say, if you wanted the player to have to MOVE the
         *   chair TO the bookcase before being able to reach the top shelf by
         *   standing on the chair, since you could then check the value of the
         *   chair's movedTo property before deciding whether the top shelf was
         *   accesssible.
         */
        dobjFor(MoveTo)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isMoveable)
                    illogical(cannotMoveMsg);
            }

            action()
            {
                makeMovedTo(gIobj);
            }

            report()
            {
                DMsg(okay move to, '{I} move{s/d} {1} {dummy} to {the iobj}. ',
                     gActionListStr);
            }
        }

        /*
         *   The notional location (other object) this object has been moved to as
         *   the result of a MoveTo command.
         */
        movedTo = nil

        /* Cause this object to be moved to loc */
        makeMovedTo(loc) { movedTo = loc; }

        /* In general there's no reason why most objects can't be moved to. */
        canMoveToMe = true

        iobjFor(MoveTo)
        {
            preCond = [touchObj]

            verify()
            {
                if(!canMoveToMe)
                    illogical(cannotMoveToMsg);

                if(gDobj.movedTo == self)
                    illogicalNow(alreadyMovedToMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotMoveToSelfMsg);

            }

        }

        cannotMoveToMsg = BMsg(cannot move to, '{The subj dobj} {can\'t} be moved to
            {the iobj}. ')

        cannotMoveToSelfMsg = BMsg(cannot move to self, '{The subj dobj} {can\'t}
            be moved to {itself dobj}. ')

        alreadyMovedToMsg = BMsg(already moved to, '{The subj dobj} {has} already
            been moved to {the iobj}. ')

        /*
         *   Lighting an object makes it a light source by making its isLit property
         *   true.
         */
        dobjFor(Light)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isLightable)
                    illogical(cannotLightMsg);
                else if(isLit)
                    illogicalNow(alreadyLitMsg);
            }

            action()
            {
                makeLit(true);
            }

            report()
            {
                DMsg(okay lit,'Done.|{I} {light} {1}. ', gActionListStr);
            }
        }

        cannotLightMsg = BMsg(cannot light, '{The subj dobj} {is} not something
            {i} {can} light. ')

        alreadyLitMsg = BMsg(already lit, '{The subj dobj} {is} already lit. ')

        /*
         *   Most things are extinguishable if they're lit, but some things (like
         *   the sun or a nuclear explosion) might conceivably not be. Note that
         *   this property should only be set to nil for things that couldn't be
         *   extinguished even if they were lit (the flames of Hell, for example,
         *   which might be considered undousable for all eternity, if you're bent
         *   on writing an infernal game).
         */
        isExtinguishable = true

        dobjFor(Extinguish)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isLit)
                    illogicalNow(notLitMsg);

                if(!isExtinguishable)
                    illogical(cannotExtinguishMsg);

            }

            action()
            {
                makeLit(nil);
            }

            report()
            {
                DMsg(extinguish, '{I} {put} out {1}. ', gActionListStr);
            }
        }

        notLitMsg = BMsg(not lit, '{The subj dobj} {isn\'t} lit. ')

        cannotExtinguishMsg = BMsg(cannot extinguish, '{The dobj} {cannot} be
            extinguished. ')

        dobjFor(Eat)
        {
            preCond = [objHeld]

            verify()
            {
                if(!isEdible)
                    illogical(cannotEatMsg);
            }

            action()
            {
                moveInto(nil);
            }

            report()
            {
                DMsg(eat, '{I} {eat} {1}. ', gActionListStr);
            }
        }

        cannotEatMsg = BMsg(cannot eat, '{The subj dobj} {is} plainly inedible. ')

        /* Most things aren't drinkable */
        isDrinkable = nil

        dobjFor(Drink)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isDrinkable)
                   illogical(cannotDrinkMsg);

            }

        }

        cannotDrinkMsg = BMsg(not potable, '{I} {can\'t} drink {1}. ', fluidName)

        /*
         *   Most things probably could be cleaned, even if they're not worth
         *   cleaning in practice. Some things like a mountain or the moon probably
         *   can't be cleaned and could reasonably define isCleanable = nil.
         */
        isCleanable = true

        /* Assume most things start out not as clean as they could be */
        isClean = nil

        /* But that most things don't actually need cleaning in the game */
        needsCleaning = nil

        /*
         *   If this is non-nil then this is an object or a list of objects that
         *   must be/can be used to clean this object.
         */
        mustBeCleanedWith = nil

        /*
         *   The handling of the Clean action is possibly more elaborate than it
         *   needs to be by default, and game code may wish to override it with a
         *   different implementation, but the aim is to provide a framework that
         *   covers some common cases.
         *
         *   An object starts out with isClean = nil. Cleaning the object makes
         *   isClean true (at which point it doesn't need cleaning again).
         *
         *   If an object needs another object to be cleaned with (e.g. if in order
         *   to clean the window you need a wet sponge to clean it with), this can
         *   be defined by setting mustBeCleanedWith to an object or a list of
         *   objects that can be used to clean this direct object.
         */
        dobjFor(Clean)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isCleanable)
                    illogical(cannotCleanMsg);

                else if(isClean)
                    illogicalNow(alreadyCleanMsg);

                else if(!needsCleaning)
                    implausible(noNeedToCleanMsg);

            }

            action()
            {
                if(mustBeCleanedWith != nil)
                    askForIobj(CleanWith);

                makeCleaned(true);
            }

            report()
            {
                say(okayCleanMsg);
            }
        }

        /*
         *   Carry out the effects of cleaning. By default we just set the value of
         *   the isClean property, but game code could override this to carry out
         *   any side effects of cleaning, such as revealing the inscription on a
         *   dirty old gravestone.
         */
        makeCleaned(stat) { isClean = stat; }

        cannotCleanMsg = BMsg(cannot clean, '{The subj dobj} {is} not something {i}
            {can} clean. ')

        alreadyCleanMsg = BMsg(already clean, '{The subj dobj} {is} already quite
            clean enough. ')

        noNeedToCleanMsg = BMsg(no clean, '{The subj dobj} {doesn\'t need[ed]}
            cleaning. ')

        dontNeedCleaningObjMsg = BMsg(dont need cleaning obj, '{I} {don\'t need[ed]}
            anything to clean {the dobj} with. ')

        okayCleanMsg = DMsg(okay clean, 'Cleaned.|{I} clean{s/ed} {1}. ',
                            gActionListStr)

        dobjFor(CleanWith)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isCleanable)
                    illogical(cannotCleanMsg);

                else if(isClean)
                    illogicalNow(alreadyCleanMsg);

                else if(!needsCleaning)
                    implausible(noNeedToCleanMsg);

                else if(mustBeCleanedWith == nil)
                    implausible(dontNeedCleaningObjMsg);

                else if(valToList(mustBeCleanedWith).indexOf(gVerifyIobj) == nil)
                    implausible(cannotCleanWithMsg);
            }

            action() { makeCleaned(true); }

            report()
            {
                say(okayCleanMsg);
            }
        }

        /*
         *   We assume most objects aren't suitable for cleaning other objects with.
         *   Since the dobj is resolved first canCleanWithMe could be a method that
         *   checks whether the proposed iobj is suitable for cleaning gDobj; but a
         *   better way of doing it might be to list suitable objects in the
         *   mustBeCleanedWith property.
         */
        canCleanWithMe = nil

        iobjFor(CleanWith)
        {
            preCond = [objHeld]
            verify()
            {
                if(!canCleanWithMe)
                   illogical(cannotCleanWithMsg);
            }
        }

        cannotCleanWithMsg = BMsg(cannot clean with, '{I} {can\'t} clean {the dobj}
            with {the iobj}. ')

        /* Most things are not suitable for digging in*/
        isDiggable = nil

        dobjFor(Dig)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isDiggable)
                   illogical(cannotDigMsg);
            }

            /*
             *   If digging is allowed, then most likely we need an implement like a
             *   spade to dig with, so our default action is to ask for one. This
             *   can be overridden on objects in which the actors can effectively
             *   dig with their bare hands.
             */
            action() { askForIobj(DigWith); }
        }

        /* Most objects aren't suitable digging instruments */
        canDigWithMe = nil

        /*
         *   For DigWith we merely provide verify handlers that rule out the action
         *   wih the default values of isDiggable and canDigWithMe. The library
         *   cannot model the effect of digging in general, so it's up to game code
         *   to implement this on particular objects as required.
         */
        dobjFor(DigWith)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isDiggable)
                   illogical(cannotDigMsg);
            }
        }

        iobjFor(DigWith)
        {
            preCond = [objHeld]
            verify()
            {
                if(!canDigWithMe)
                   illogical(cannotDigWithMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotDigWithSelfMsg);
            }
        }

        cannotDigMsg = BMsg(cannot dig, '{I} {can\'t} dig there. ')
        cannotDigWithMsg = BMsg(cannot dig with, '{I} {can\'t} dig anything with
            {that iobj}. ')
        cannotDigWithSelfMsg = BMsg(cannot dig with self, '{I} {can\'t} dig {the
            dobj} with {itself dobj}. ')

        /*
         *   We treat TAKE FROM as equivalent to TAKE except at the verify stage,
         *   where we first check that the direct object is actually in the indirect
         *   object.
         */
        dobjFor(TakeFrom) asDobjWithoutVerifyFor(Take)

        dobjFor(TakeFrom)
        {
            verify()
            {
                if(!isTakeable)
                    illogical(cannotTakeMsg);

                /* Test whether we are contained in any possible iobj. */
                local contained = nil;

                /*
                 *   If we already know what the indirect object is, test if we are in its contents
                 *   (which may include one of its remapXXX subcontainers).
                 */
                if(gIobj)
                {
                    if(gIobj.notionalContents.indexOf(self))
                        contained = true;
                }
                /*
                 *   Otherwise, test if we are in any of the possible matches for the iobj of this
                 *   command.
                 */
                else
                {
                    for(local obj in gTentativeIobj)
                    {
                        if(obj.notionalContents.indexOf(self))
                        {
                            contained = true;
                            break;
                        }
                    }
                }

                /*
                 *   If we're not in any possible iobj for this command, we can't be taken from any of
                 *   them.
                 */
                if(!contained)
                    illogicalNow(notInMsg);

                /*
                 *   If we have a resolved iobj and it's the same as us (the dobj) or our tentative iobj
                 *   list contains only us (the dobj) the player is trying to take us from ourselvdes,
                 *   which we rule out as impossible.
                 */
                if((gIobj == self)
                   || (gTentativeIobj.length == 1 && self == gVerifyIobj))
                    illogicalSelf(cannotTakeFromSelfMsg);
            }
        }

        iobjFor(TakeFrom)
        {
            preCond = [touchObj]

            verify()
            {
                /*We're a poor choice of indirect object if there's nothing in us */
                if(notionalContents.countWhich({x: !x.isFixed}) == 0)
                    logicalRank(70);

                /*
                 *   We're also a poor choice if none of the tentative direct
                 *   objects is in our list of notional contents
                 */
                if(gTentativeDobj.overlapsWith(notionalContents) == nil)
                    logicalRank(80);

            }
        }

        notInMsg = BMsg(not inside, '{The dobj} {is}n\'t ' +
                        (gIobj ? '{in iobj}.' : '{1}.'), gVerifyIobj.objInName)

        cannotTakeFromSelfMsg =  BMsg(cannot take from self, '{I} {can\'t} take
            {the subj dobj} from {himself dobj}. ')

        /*
         *   Flag, can we supply more items from us that are currently in scope? By
         *   default we can't; but a DispensingCollective may be able to.
         */
        canSupply = nil

        dobjFor(ThrowAt)
        {
            preCond = [objHeld, objNotWorn]

            verify() { verifyDobjThrow(); }

            action()
            {
                /*
                 *   Normally the action handling for the ThrowAt action is dealt
                 *   with on the indirect object - iobjFor(ThrowAt) - but if for
                 *   particular objects you want to handle it on the direct object
                 *   and you don't want the iobj handling as well, then you need to
                 *   end your dobj action method with exitAction to suppress the
                 *   iobj action method.
                 */
            }
        }

        /*
         *   Most objects can the target of a throw, but it's conceivable that some
         *   might be obviously unsuitable
         */
        canThrowAtMe = true

        iobjFor(ThrowAt)
        {
            preCond = [objVisible]

            verify()
            {
                if(!canThrowAtMe)
                    illogical(cannotThrowAtMsg);

                if(gDobj == self)
                    illogical(cannotThrowAtSelfMsg);
            }

            action()
            {
                gDobj.actionMoveInto(getOutermostRoom.dropLocation);
            }

            report()
            {
                local obj = gActionListObj;
                gMessageParams(obj);
                DMsg(throw at, '{The subj obj} {strikes} {the iobj} and land{s/ed}
                    on the ground. ');
            }

        }

        /*
         *   Particular instances will nearly always need to override with a less
         *   generic and more plausible refusal message.
         */
        cannotThrowAtMsg = BMsg(cannot throw at, '{I} {can\'t} throw anything at
            {the iobj}. ')

        cannotThrowAtSelfMsg = BMsg(cannot throw at self, '{The subj dobj} {can\'t}
            be thrown at {itself dobj}. ')

        dobjFor(ThrowTo)
        {
            preCond = [objHeld, objNotWorn]

            verify() { verifyDobjThrow(); }
        }

        /*
         *   Most objects cannot have things thrown to then, since this would imply
         *   that they might be able to catch them, which only animate objects can
         *   do.
         */
        canThrowToMe = nil

        iobjFor(ThrowTo)
        {
            preCond = [objVisible]

            verify()
            {
                if(!canThrowToMe)
                    illogical(cannotThrowToMsg);

                if(gVerifyDobj == self)
                    illogical(cannotThrowToSelfMsg);
            }

        }

        cannotThrowToMsg = BMsg(cannot throw to, '{The subj iobj} {can\'t} catch
            anything. ')

        cannotThrowToSelfMsg = BMsg(cannot throw to self, '{The subj dobj} {can\'t}
            be thrown to {itself dobj}. ')

        throwFallsShortMsg = BMsg(throw falls short, '{The subj dobj} land{s/ed} far
            short of {the iobj}. ')

        canTurnMeTo = nil

        /*
         *   Turning something To is setting it to a value by rotating it, such as
         *   turning a dial to point to a particular number.
         */
        dobjFor(TurnTo)
        {
            preCond = [touchObj]

            verify()
            {
                if(!canTurnMeTo)
                   illogical(cannotTurnToMsg);
            }

            check()
            {
                checkSetting(gLiteral);
            }

            action()
            {
                makeSetting(gLiteral);
            }

            report()
            {
                DMsg(okay turn to, 'Okay, {I} turn{s/ed} {1} to {2}', gActionListStr,
                     gLiteral);
            }
        }

        /*
         *   If the setting is valid, do nothing. If it's invalid display a message
         *   explaining why. We do nothing here but this is overridden on the
         *   Settable class, which may be easier to use than providing your own
         *   implementation on Thing.
         */
        checkSetting(val) { }

        /* The value we're currently set to. */
        curSetting = ''

        cannotTurnToMsg = BMsg(cannot turn to, '{I} {cannot} turn {that dobj} to
            anything. ')

        canSetMeTo = nil

        dobjFor(SetTo)
        {
            preCond = [touchObj]

            verify()
            {
                if(!canSetMeTo)
                   illogical(cannotSetToMsg);
            }

            check()
            {
                /* This would be a good place to put code to validate the setting */
                checkSetting(gLiteral);
            }

            action()
            {
                makeSetting(gLiteral);
            }

            report()
            {
                say(okaySetMsg);
            }
        }

        makeSetting(val) { curSetting = val; }

        okaySetMsg = BMsg(okay set to, '{I} {set} {1} to {2}. ', gActionListStr,
            curSetting)

        cannotSetToMsg = BMsg(cannot set to, '{I} {cannot} set {that dobj} to
            anything. ')

        /*
         *   The GoTo action allows the player character to navigate the map through
         *   the use of commands such as GO TO LOUNGE.
         */
        dobjFor(GoTo)
        {
            verify()
            {
                /*
                 *   If the actor is already in the direct object, there's no need
                 *   to move any further.
                 */
                if(gActor.isIn(self))
                    illogicalNow(alreadyThereMsg);

                /*
                 *   If the direct object is in the actor's location, there's no
                 *   need for the actor to move to get to it.
                 */
                if(isIn(gActor.getOutermostRoom))
                    illogicalNow(alreadyPresentMsg);

                /*
                 *   It's legal to GO TO a decoration object, but given the choice,
                 *   it's probably best to let the parser choose a non-decoration in
                 *   cases of ambiguity, so we'll decorations a slightly lower
                 *   logical rank.
                 */
                if(isDecoration)
                    logicalRank(90);
            }

            /*
             *   The purpose of the GO TO action is to take the player char along
             *   the shortest route to the destination. The action routine
             *   calculates the route and takes the first step.
             */
            action()
            {
                /* Get our destination. */
                local dest = lastSeenAt ? lastSeenAt.getOutermostRoom : nil;

                /*
                 *   Calculate the route from the actor's current room to the
                 *   location where the target object was last seen, using the
                 *   pcRouteFinder to carry out the calculations if it is present.
                 */
                local route = defined(pcRouteFinder) && lastSeenAt != nil
                    ? pcRouteFinder.findPath(
                    gActor.getOutermostRoom, dest) : nil;

                /*
                 *   If we don't find a route, just display a message saying we
                 *   don't know how to get to our destination.
                 */
                if(route == nil)
                    sayDontKnowHowToGetThere();

                /*
                 *   If the route we find has only one element in its list, that
                 *   means that we're where we last saw the target but it's no
                 *   longer there, so we don't know where it's gone. In which case
                 *   we display a message saying we don't know how to reach our
                 *   target.
                 */
                else if(route.length == 1)
                    sayDontKnowHowToReach();

                /*
                 *   If the route we found has at least two elements, then use the
                 *   first element of the second element as the direction in which
                 *   we need to travel, and use the Continue action to take a step
                 *   in that direction.
                 */
                else
                {
                    local idx = 2;
                    local dir = route[2][1];
                    local oldLoc = gPlayerChar.getOutermostRoom();

                    local commonRegions =
                        gPlayerChar.getOutermostRoom.regionsInCommonWith(dest);

                    local regionFastGoTo =
                        commonRegions.indexWhich({r: r.fastGoTo }) != nil;

                    local fastGo = regionFastGoTo || gameMain.fastGoTo;

                    Continue.takeStep(dir, getOutermostRoom, fastGo);

                    /*
                     *   If the fastGoTo option is active, continue moving towards
                     *   the destination until either we reach it our we're
                     *   prevented from going any further.
                     */
                    while((fastGo)
                          && oldLoc != gPlayerChar.getOutermostRoom
                          && idx < route.length)
                    {
                        local dir = route[++idx][1];
                        Continue.takeStep(dir, getOutermostRoom, true);
                    }
                }
            }
        }

        /*
         *   We make these two sayDontKnowHowTo... methods separate methods so that
         *   they can be reused on the Distant class without having to repeat the
         *   DMsg() definitions.
         */
        sayDontKnowHowToGetThere()
            { DMsg(route unknown, '{I} {don\'t know} how to get there. ');}

        sayDontKnowHowToReach()
            {  DMsg(destination unknown, '{I} {don\'t know} how to reach
                {him dobj}.' );}

        alreadyThereMsg = BMsg(already there, '{I}{\'m} already there. ')
        alreadyPresentMsg = BMsg(already present, '{The subj dobj} {is} right
            {here}. ')

        /*
         *   By default most things can't be attached to any things. The base
         *   handling of ATTACH and DETACH on Thing merely rules them out at the
         *   verify stage. The SimpleAttachable and NearbyAttachable classes define
         *   in the optional attachables.t module provide fuller handling.
         */
        isAttachable = nil

        dobjFor(Attach)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isAttachable)
                   illogical(cannotAttachMsg);
            }
            action() { askForIobj(AttachTo); }
        }

        dobjFor(AttachTo)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isAttachable)
                   illogical(cannotAttachMsg);
            }
        }

        canAttachToMe = nil

        iobjFor(AttachTo)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canAttachToMe)
                   illogical(cannotAttachToMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotAttachToSelfMsg);
            }
        }

        cannotAttachMsg = BMsg(cannot attach, '{I} {cannot} attach {the dobj} to
            anything. ')
        cannotAttachToMsg = BMsg(cannot attach to, '{I} {cannot} attach anything to
            {the iobj}. ')

        cannotAttachToSelfMsg = BMsg(cannot attach to self, '{I} {cannot} attach
            {the iobj} to {itself iobj}. ')

        isDetachable = nil

        dobjFor(Detach)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isDetachable)
                   illogical(cannotDetachMsg);
            }
        }

        cannotDetachMsg = BMsg(cannot detach, 'There{dummy} {is}n\'t anything from
            which {the subj dobj} could be detached. ')

        dobjFor(DetachFrom)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isDetachable)
                   illogical(cannotDetachMsg);

                if(gVerifyIobj == self)
                    illogicalSelf(cannotDetachFromSelfMsg);
            }
        }

        canDetachFromMe = nil

        iobjFor(DetachFrom)
        {
            verify()
            {
                if(!canDetachFromMe)
                    illogical(cannotDetachFromMsg);
            }
        }

        cannotDetachFromMsg = BMsg(cannot detach from, 'There{dummy} {is}n\'t
            anything that could be detached from {the iobj}. ')

        cannotDetachFromSelfMsg = BMsg(cannot detach from self, '{The subj dobj}
            {can\'t} be detached from {itself dobj}. ')

        /*
         *   Fasten by itself presumably refers to objects like seat-belts. There
         *   are not many such fastenable objects so we may things not fastenable by
         *   default.
         */
        isFastenable = nil

        /*   Most things start out unfastened. */
        isFastened = nil

        /*
         *   Make something fastened or unfastened. By default we just change the
         *   value of its isFastened property, but game code could override this to
         *   provide further side-effects on particular objects.
         */
        makeFastened(stat) { isFastened = stat; }

        dobjFor(Fasten)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isFastenable)
                    illogical(cannotFastenMsg);

                if(isFastened)
                    illogicalNow(alreadyFastenedMsg);
            }

            action() { makeFastened(true); }

            report()
            {
                DMsg(okay fasten, 'Done|{I} fasten{s/ed} {1}. ', gActionListStr);
            }
        }

        cannotFastenMsg = BMsg(cannot fasten, '{That subj dobj}{\'s} not something
            {i} {can} fasten. ')

        alreadyFastenedMsg = BMsg(already fastened, '{The subj dobj} {is} already
            fastened. ')

        dobjFor(FastenTo)
        {
            preCond = [objHeld]
            verify()
            {
                if(!isFastenable)
                   illogical(cannotFastenMsg);
            }
        }

        canFastenToMe = nil

        iobjFor(FastenTo)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canFastenToMe)
                    illogical(cannotFastenToMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotFastenToSelfMsg);
            }
        }

        cannotFastenToMsg = BMsg(cannot fasten to, '{I} {can\'t} fasten anything to
            {that iobj}. ')

        cannotFastenToSelfMsg = BMsg(cannot fasten to self, '{The subj iobj}
            {can\'t} be fastened to {itself iobj}. ')

        isUnfastenable = nil

        dobjFor(Unfasten)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isUnfastenable)
                   illogical(cannotUnfastenMsg);

                if(!isFastened)
                    illogicalNow(notFastenedMsg);
            }
        }

        dobjFor(UnfastenFrom)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isUnfastenable)
                   illogical(cannotUnfastenMsg);

                if(gVerifyIobj == self)
                    illogical(cannotUnfastenFromSelfMsg);
            }
        }

        canUnfastenFromMe = nil

        iobjFor(UnfastenFrom)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canUnfastenFromMe)
                   illogical(cannotUnfastenFromMsg);
            }
        }

        cannotUnfastenMsg = BMsg(cannot unfasten, '{The subj dobj} {cannot} be
            unfastened. ')

        cannotUnfastenFromMsg = BMsg(cannot unfasten from, '{I} {can\'t} unfasten
            anything from {that iobj}. ')

        cannotUnfastenFromSelfMsg = BMsg(cannot unfasten from self, '{I} {can\'t}
            unfasten {the dobj} from {itself dobj}. ')

        notFastenedMsg = BMsg(not fastened, '{The subj dobj} {isn\'t} fastened. ')

        /*
         *   Most things can't be plugged into other things or have other things
         *   plugged into them.
         */
        isPlugable = nil
        canPlugIntoMe = nil

        /*
         *   The base handling of PlugInto on Thing merely rules it out at the
         *   verify stage. A fuller implementation is provided by the PlugAttachable
         *   class in the optional attachables module.
         */
        dobjFor(PlugInto)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isPlugable)
                    illogical(cannotPlugMsg);

                if(self == gVerifyIobj)
                    illogicalSelf(cannotPlugIntoSelfMsg);
            }

        }

        iobjFor(PlugInto)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canPlugIntoMe)
                    illogical(cannotPlugIntoMsg);
            }
        }

        cannotPlugMsg = BMsg(cannot plug, '{The subj dobj} {can\'t} be plugged into
            anything. ')
        cannotPlugIntoSelfMsg = BMsg(cannot plug into self, '{I} {can\'t} plug
            {the dobj} into {itself dobj}. ')
        cannotPlugIntoMsg = BMsg(cannot plug into, '{I} {can\'t} plug anything into
            {the iobj}. ')

        isUnplugable = (isPlugable)
        canUnplugFromMe = (canPlugIntoMe)

        dobjFor(UnplugFrom)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isUnplugable)
                    illogical(cannotUnplugMsg);

                if(gVerifyIobj == self)
                    illogicalSelf(cannotUnplugFromSelfMsg);
            }
        }

        iobjFor(UnplugFrom)
        {
            preCond = []

            verify()
            {
                if(!canUnplugFromMe)
                    illogical(cannotUnplugFromMsg);

            }
        }

        cannotUnplugMsg = BMsg(cannot unplug, '{The subj dobj} {can\'t} be
            unplugged. ')

        cannotUnplugFromSelfMsg = BMsg(cannot unplug from self, '{I} {can\'t} unplug
            {the dobj} from {itself dobj}. ')

        cannotUnplugFromMsg = BMsg(cannot unplug from, '{I} {can\'t} unplug anything
            from {the iobj}. ')

        dobjFor(PlugIn)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isPlugable)
                    illogical(cannotPlugMsg);
            }
        }

        dobjFor(Unplug)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isUnplugable)
                    illogical(cannotUnplugMsg);
            }
        }

        /* We can try kissing most things, even if it isn't very rewarding */
        isKissable = true

        /*
         *   The logical rank assigned to kissing this object if kissing is allowed.
         *   Kissing an inanimate object is less likely than kissing an Actor.
         */
        kissRank = 80

        dobjFor(Kiss)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isKissable)
                    illogical(cannotKissMsg);

                /*
                 *   It's more logical to kiss actors, so we give the Kiss action a
                 *   lower logical rank on ordinary things.
                 */
                logicalRank(kissRank);
            }

            check()
            {
                if(dataType(&checkKissMsg) != TypeNil)
                    display(&checkKissMsg);
            }

            action()
            {
                if(dataType(&futileToKissMsg) != TypeNil)
                    display(&futileToKissMsg);
            }

            report()
            {
                DMsg(report kiss, 'Kissing {1} {dummy}prove{s/d} remarkably
                    unrewarding. ',  gActionListStr);
            }
        }

        futileToKissMsg = nil

        cannotKissMsg = BMsg(cannot kiss, '{I} really {can\'t} kiss {that dobj}. ')

        /*
         *   If we want Kissing to fail at the check stage we can supply a message
         *   here explaining why. This is most simply given as a single-quoted
         *   string, but a double-quoted string or method will also work.
         */
        checkKissMsg = nil

        /*
         *   Flag, if this is a nested room, should an actor get out of it before
         *   executing an intransitive Jump command. By default it should.
         */
        getOutToJump = true

        /*
         *   It should be possible to jump off something if and only if the actor is
         *   on it in the first place.
         */
        canJumpOffMe = (gActor.location == self && contType == On)

        dobjFor(JumpOff)
        {
            preCond = [touchObj]

            verify()
            {
                if(!canJumpOffMe)
                    illogical(cannotJumpOffMsg);
            }

            action()
            {
                /*
                 *   Jumping off something has much the same effect as getting off
                 *   it, i.e. moving the actor to our exitLocation.
                 */
                gActor.actionMoveInto(exitLocation);
            }

            report()
            {
                DMsg(jump off, '{I} jump{s/ed} off {1} and land{s/ed} on the ground',
                     gActionListStr);
            }
        }

        cannotJumpOffMsg = BMsg(cannot jump off, '{I}{\'m} not on {the dobj}. ')

        /* It usually isn't possible (or at least useful) to jump over things. */
        canJumpOverMe = nil

        /*
         *   The base handling of JumpOver is simply to rule it out at the verify
         *   stage.
         */
        dobjFor(JumpOver)
        {
            preCond = [touchObj]

            verify()
            {
                if(!canJumpOverMe)
                   illogical(cannotJumpOverMsg);
            }
        }

        cannotJumpOverMsg = BMsg(pointless to jump over, 'It {dummy}{is}
            pointless to try to jump over {the dobj}. ')

        /* Most things aren't settable. */
        isSettable = nil

        /*
         *   The Set command by itself doesn't do much. By default we just rule it
         *   out at the verify stage.
         */
        dobjFor(Set)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isSettable)
                   illogical(cannotSetMsg);
            }
        }

        cannotSetMsg = BMsg(cannot set, '{The subj dobj} {is} not something {i}
            {can} set. ')

        /* Most things can't be typed on. */
        canTypeOnMe = nil

        /*
         *   The base handling of both the vague (TYPE ON X) and specific (TYPE FOO
         *   ON X) forms of TypeOn is simply to rule them out at the verify stage.
         *   Game code that needs objects that can be typed on is responsible for
         *   handling these actions in custom code.
         */
        dobjFor(TypeOnVague)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canTypeOnMe)
                   illogical(cannotTypeOnMsg);
            }
        }

        dobjFor(TypeOn)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canTypeOnMe)
                   illogical(cannotTypeOnMsg);
            }
        }

        cannotTypeOnMsg = BMsg(cannot type on, '{I} {can\'t} type anything on {the
            dobj}. ')

        /*
         *   Entering something on means ENTER FOO ON BAR where FOO is a string
         *   literal and BAR is an object such as a computer terminal. Most objects
         *   can't be entered on in this sense.
         */
        canEnterOnMe = nil

        /*
         *   The base handling is simply to rule out EnterOn at verify. There's no
         *   further handling the library can provide for a 'general' case so it's
         *   up to game code to define it for specific cases.
         */
        dobjFor(EnterOn)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canEnterOnMe)
                   illogical(cannotEnterOnMsg);
            }
        }

        cannotEnterOnMsg = BMsg(cannot enter on, '{I} {can\'t} enter anything on
            {the dobj}. ')

        /*  Most things can't be written on. */
        canWriteOnMe = nil

        /*  By default we simply rule out writing on things at the verify stage. */
        dobjFor(WriteOn)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canWriteOnMe)
                   illogical(cannotWriteOnMsg);
            }

        }

        cannotWriteOnMsg = BMsg(cannot write on, '{I} {can\'t} write anything on
            {the dobj}. ')

        /* Most things aren't consultable */
        isConsultable = nil

        /*
         *   The base handling on Thing merely rules out the Consult action at the
         *   verify stage. For a fuller implementation that allows consulting use
         *   the Consultable class.
         */
        dobjFor(ConsultAbout)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isConsultable)
                   illogical(cannotConsultMsg);
            }

        }

        cannotConsultMsg = BMsg(cannot consult, '{The subj dobj} {is} not a
            provider of information. ')

        /*
         *   Most things aren't pourable (they can't be poured into or onto other
         *   things.
         */
        isPourable = nil

        /*
         *   Sometimes we may have a container, such as an oilcan, from which we
         *   want to pour a liquid, such as oil, and we're using the same object to
         *   do duty for both. We can then use the fluidName property to say 'the
         *   oil' rather than 'the oilcan' in messages that refer specifically to
         *   pouring the liquid.
         */
        fluidName = theName

        /*
         *   The base handling of Pour, PourOnto and PourInto is simply to rule out
         *   all three actions at the verify stage. Game code that wants to allow
         *   these actions on specific objects will need to provide further handling
         *   for them.
         */
        dobjFor(Pour)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isPourable)
                   illogical(cannotPourMsg);
            }
        }

        dobjFor(PourOnto)
        {
            preCond = [touchObj]

            verify()
            {
                if(!isPourable)
                   illogical(cannotPourMsg);
            }
        }

        /*
         *   Most things can probably have something poured onto them in principle,
         *   though we might want to prevent it in practice. The canPourOntoMe
         *   property controls whether it's possible to pour onto this thing.
         */

        canPourOntoMe = true

        /*
         *   The allowPourOntoMe property controls whether we want allow anything to
         *   be poured onto this thing (even if it's possible). By default we don't.
         */
        allowPourOntoMe = nil

        iobjFor(PourOnto)
        {
            preCond = [touchObj]

            remap = (remapOn)

            verify()
            {
                if(gVerifyDobj == self)
                    illogicalSelf(cannotPourOntoSelfMsg);

                if(!canPourOntoMe)
                    illogical(cannotPourOntoMsg);
                else if(!allowPourOntoMe)
                    implausible(shouldNotPourOntoMsg);

            }
        }

        dobjFor(PourInto)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isPourable)
                   illogical(cannotPourMsg);
            }
        }

        /*
         *   Presumably it's possible by default to pour something into me if I'm a
         *   container; but this could be overridden simply to true for objects like
         *   the sea or a river.
         */
        canPourIntoMe = (contType == In || remapIn != nil)

        /*
         *   While it's possible to pour stuff into any container, we probably don't
         *   want to allow it on most of them
         */
        allowPourIntoMe = nil

        iobjFor(PourInto)
        {
            preCond = [touchObj]

            remap = (remapIn)

            verify()
            {
                if(gVerifyDobj == self)
                    illogicalSelf(cannotPourIntoSelfMsg);

                if(!canPourIntoMe)
                    illogical(cannotPourIntoMsg);
                else if(!allowPourIntoMe)
                    implausible(shouldNotPourIntoMsg);
            }
        }

        cannotPourMsg = BMsg(cannot pour, '{I} {can\'t} pour {1} anywhere. ',
                             fluidName)
        cannotPourOntoSelfMsg = BMsg(cannot pour on self, '{I} {can\'t} pour {the
            dobj} onto {itself dobj}. ')
        cannotPourIntoSelfMsg = BMsg(cannot pour in self, '{I} {can\'t} pour {the
            dobj} into {itself dobj}. ')
        cannotPourIntoMsg = BMsg(cannot pour into, '{I} {can\'t} pour {1}
            into {that dobj}. ', gDobj.fluidName)
        cannotPourOntoMsg = BMsg(cannot pour onto, '{I} {can\'t} pour {1}
            into {that dobj}. ', gDobj.fluidName)
        shouldNotPourIntoMsg = BMsg(should not pour into, 'It{dummy}{\'s} better not
            to pour {1} into {the iobj}. ', gDobj.fluidName)

        shouldNotPourOntoMsg = BMsg(should not pour onto, 'It{dummy}{\'s} better not
            to pour {1} onto {the iobj}. ', gDobj.fluidName)

        /* Most things can't be screwed */
        isScrewable = nil

        /* Most things can't be used to screw other things with. */
        canScrewWithMe = nil

        /*
         *   In the base handling we simply rule out Screw and Unscrew actions at
         *   the verify stage. It's up to game code to provide specific handling for
         *   objects that can be screwed and unscrewed.
         */
        dobjFor(Screw)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isScrewable)
                   illogical(cannotScrewMsg);
            }
        }

        dobjFor(ScrewWith)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isScrewable)
                   illogical(cannotScrewMsg);
            }
        }

        iobjFor(ScrewWith)
        {
            preCond = [objHeld]
            verify()
            {
                if(!canScrewWithMe)
                    illogical(cannotScrewWithMsg);

                if(gVerifyDobj == self)
                    illogical(cannotScrewWithSelfMsg);
            }
        }

        isUnscrewable = (isScrewable)
        canUnscrewWithMe = (canScrewWithMe)

        dobjFor(Unscrew)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isUnscrewable)
                   illogical(cannotUnscrewMsg);
            }
        }

        dobjFor(UnscrewWith)
        {
            preCond = [touchObj]
            verify()
            {
                if(!isUnscrewable)
                   illogical(cannotUnscrewMsg);
            }
        }

        iobjFor(UnscrewWith)
        {
            preCond = [objHeld]
            verify()
            {
                if(!canUnscrewWithMe)
                    illogical(cannotUnscrewWithMsg);

                if(gVerifyDobj == self)
                    illogicalSelf(cannotUnscrewWithSelfMsg);
            }
        }

        cannotScrewMsg = BMsg(cannot screw, '{I} {can\'t} screw {the dobj}. ')
        cannotScrewWithMsg = BMsg(cannot screw with, '{I} {can\'t} screw anything
            with {that iobj}. ')
        cannotScrewWithSelfMsg = BMsg(cannot screw with self, '{I} {can\'t} screw
            {the iobj} with {itself iobj}. ')
        cannotUnscrewMsg = BMsg(cannot unscrew, '{I} {can\'t} unscrew {the dobj}. ')
        cannotUnscrewWithMsg = BMsg(cannot unscrew with, '{I} {can\'t} unscrew
            anything with {that iobj}. ')
        cannotUnscrewWithSelfMsg = BMsg(cannot unscrew with self, '{I} {can\'t}
            unscrew {the iobj} with {itself iobj}. ')

        /*
         *   Common handler for verifying push travel actions. The via parameter may
         *   be a preposition object (such as Through) defining what kind of push
         *   traveling the actor is trying to do (e.g. through a door or up some
         *   stairs).
         */
        verifyPushTravel(via)
        {
            viaMode = via;

            if(!canPushTravel && !canPullTravel)
                illogical(cannotPushTravelMsg);

            if(matchPushOnly && !canPushTravel)
                implausible(cannotPushTravelMsg);

            if(matchPullOnly && !canPullTravel)
                implausible(cannotPushTravelMsg);

            if(gActor.isIn(self))
                illogicalNow(cannotPushOwnContainerMsg);

            if(gVerifyIobj == self)
                illogicalSelf(cannotPushViaSelfMsg);

        }

        /*
         *   Check if the player specifically asked to PUSH this object somewhere.
         *   In the main library we assume not, but language-specific code will need
         *   to override to check what that player's command actually said.
         */
        matchPushOnly = nil

        /*
         *   Check if the player specifically asked to PULL this object somewhere.
         *   In the main library we assume not, but language-specific code will need
         *   to override to check what that player's command actually said.
         */
        matchPullOnly = nil

        viaMode = ''

        cannotPushOwnContainerMsg = BMsg(cannot push own container, '{I} {can\'t}
            {1} {the dobj} anywhere while {he actor}{\'s} {2} {him dobj}. ',
                                         gVerbWord, gDobj.objInPrep)

        cannotPushViaSelfMsg = BMsg(cannot push via self, '{I} {can\'t} {1} {the
            dobj} {2} {itself dobj}. ', gVerbWord, viaMode.prep)

        /*
         *   By default we can't push travel most things. Push Travel means pushing
         *   an object from one place to another and traveling with it.
         */
        canPushTravel = nil

        /*
         *   Normally we don't distinguish PushTravel from PullTravel, but if we
         *   want something to be pushable between rooms but not pullable, or vice
         *   versa, we can set these to different values.
         */
        canPullTravel = canPushTravel

        /*
         *   PushTravelDir handles pushing an object in a particular direction, e.g.
         *   PUSH BOX NORTH
         */
        dobjFor(PushTravelDir)
        {
            preCond = [touchObj, travelPermitted]

            check()
            {
                /* set up a local variable to hold the connector we want to travel through. */
                local conn;

                /* note which room we're in */
                local loc = getOutermostRoom;

                /* Get the direction of travel from the command */
                local dirn = gCommand.verbProd.dirMatch.dir;

                if(loc.propType(dirn.dirProp) == TypeObject)
                {
                    /* Note the connector object in the relevant direction */
                    conn = loc.(dirn.dirProp);

                    /*
                     *   If our connector is an UnlistedProxyConnector we need to replace the direction
                     *   we're heading in with the one the UPC points to and our connector with the one
                     *   our new direction points to.
                     */
                    if(conn.ofKind(UnlistedProxyConnector))
                    {
                        /* get our real direction of travel. */
                        dirn = conn.direction;

                        /* get the connector in that direction, or nil if it's not an object */
                        conn = loc.propType(dirn.dirProp) == TypeObject ? loc.(dirn.dirProp) : nil;

                    }

                    /* If the connector we want to use is an object then check its travel barriers. */
                    if(dataType(conn) == TypeObject)
                    {
                        if(conn.checkTravelBarriers(self))
                            conn.checkTravelBarriers(gActor);
                    }
                }

            }
        }

        /* Display a message saying we pushed the direct object in a particular direction. */
        sayPushTravel(dir)
        {
            DMsg(before push travel dir, '{I} <<gDobj.matchPullOnly ? 'pull(s/ed}' : 'push{es/ed}'>>
                  {the dobj} {1}. ',  dir.departureName);
            "<.p>";
        }

        pushTravelRevealItems()
        {
            /*
             *   Check whether moving this object revealed any items hidden behind
             *   or beneath it (even if we don't succeed in pushing the object to
             *   another room we can presumably move it far enough across its
             *   current one to reveal any items it was concealing.
             */
            revealOnMove();

            /*
             *   If moving this item did reveal any hidden items, we want to see the
             *   report of them now, before moving to another location.
             */
            gCommand.afterReport();

            /*
             *   We don't want to see these reports again at the end of the action,
             *   so clear the list.
             */
            gCommand.afterReports = [];
        }

        /* Display a message explaining that push travel is not possible */
        cannotPushTravelMsg()
        {
            if(isFixed)
                return cannotTakeMsg;
            return BMsg(cannot push travel, 'There{dummy}{\'s} no point trying to
                {1} {that dobj} anywhere. ', gVerbWord);
        }

        /* Check the travel barriers on the indirect object of the action */
        checkPushTravel()
        {
            /*
             *   First check the travel barriers for the actor doing the pushing.
             *   Only go on to check those for the item being pushed if the actor
             *   can travel, so we don't see the same messages twice.
             */
            if(checkTravelBarriers(gActor))
                checkTravelBarriers(gDobj);

        }

        /*  Carry out the push travel on the direct object of the action. */
        doPushTravel(via)
        {
            /*
             *   Check whether moving this object revealed any items hidden behind
             *   or beneath it (even if we don't succeed in pushing the object to
             *   another room we can presumably move it far enough across its
             *   current one to reveal any items it was concealing.
             */
            pushTravelRevealItems();

            if(!gIobj.isLocked)
                describePushTravel(via);

            /*
             *   We temporarily make the push traveler item hidden before moving it
             *   to the new location so that it doesn't show up listed in its former
             *   location when actor moves to the new location and there's a sight
             *   path between the two.
             */
            local wasHidden;

            /*   Note the actor's current location. */
            local oldLoc = gActor.getOutermostRoom;
            try
            {
                wasHidden = propType(&isHidden) is in (TypeCode, TypeFuncPtr) ?
                        getMethod(&isHidden) : isHidden;

                isHidden = true;

                gIobj.travelVia(gActor);
            }
            finally
            {
                if(dataTypeXlat(wasHidden) is in (TypeCode, TypeFuncPtr))
                    setMethod(&isHidden, wasHidden);
                else
                    isHidden = wasHidden;
            }

            /*
             *   Use the travelVia() method of the iobj to move the dobj to its new
             *   location.
             */

            if(gActor.isIn(gIobj.getDestination(oldLoc)))
            {
                gIobj.travelVia(gDobj);
                gDobj.describeMovePushable(self, gActor.location);
            }
        }

        beforeMovePushable(connector, dir)
        {
            /* make a note of our connector */
            local conn = connector;

            /*
             *   If our connector is an UnlistedProxyConnector we need some special handling to identify
             *   the real connector we're going to use.
             */
            if(connector.ofKind(UnlistedProxyConnector))
            {
                /* Note the room we're in. */
                local loc = getOutermostRoom;

                /* Get the direction prop our UnlistedProxyConnector is a proxy for. */
                local prop = connector.direction.dirProp;

                if(loc.propType(prop) == TypeObject)
                {

                    /* Get the connector that direction property points to. */
                    conn = loc.(prop);

                    /* Make that connector the iobj of this action. */
                    gIobj = conn;
                }
                else
                {
                    /*  Otherwise note the direction we're actually going to try to go in. */
                    dir = connector.direction;

                    /* If the connector isn't an object, we don't want to deal with it here. */
                    conn = nil;
                }
            }

            /*
             *   Next check that there's nothing that wants to disallow this travel.. If the
             *   travelPermitted preCond is present for this action on this object this should already
             *   have been done, but if game code has overridden that we need to carry out the
             *   beforeTravelNotifications now.
             */
            if(conn && dataType(conn == TypeObject))
                conn.beforeTravelNotifications(self);

           /*  If we have an indirect object, describe our PushTravel via it */
            if(gIobj)
                describePushTravel(gAction.viaMode);

            /*
             *   Otherwise we have a travel connector to travel through, report which direction we're
             *   pushing to.
             */
            else if(objOfKind(conn, TravelConnector))
                sayPushTravel(dir);

            /*
             *   Otherwise do nothing, because our 'connector' must be a string or method that explains
             *   why travel that way isn't possible.
             */
        }

        describeMovePushable (connector, dest)
        {
            local obj = self;
            gMessageParams(obj, dest);
            DMsg(describe move pushable, '{The subj obj} {comes} to a halt. ' );

        }

        /*
         *   This message, called on the direct object of a PushTravel command (i.e.
         *   the object being pushed) is displayed just before travel takes place.
         *   It is used when the PushTravel command also involves an indirect
         *   object, e.g. a Passage, Door or Stairway the direct object is being
         *   pushed through, up or down. The via parameter is the preposition
         *   relevant to the kind of pushing, e.g. Into, Through or Up.
         */
        describePushTravel(via)
        {
            /* If I have a traversalMsg, use it */
            if(gIobj && gIobj.propType(&traversalMsg) != TypeNil)
                DMsg(push travel traversal, '{I} <<if matchPullOnly>> pull{s/ed}
                    <<else>> push{es/ed}<<end>> {the dobj} {1}. <.p>',
                     gIobj.traversalMsg);
            else
                DMsg(push travel somewhere, '{I} <<if matchPullOnly>> pull{s/ed}
                    <<else>> push{es/ed}<<end>> {the dobj} {1} {the iobj}. <.p>',
                     via.prep);

            "<.p>";
        }

        /*
         *   PushTravelThrough handles pushing something through something, such as a door or archway.
         *   Most of the actual handling is dealt with by the common routines defined above.
         */
        dobjFor(PushTravelThrough)
        {
            preCond = [touchObj]
            verify()   {   verifyPushTravel(Through);   }

            action() { doPushTravel(Through); }
        }

        iobjFor(PushTravelThrough)
        {
            preCond = [travelPermitted, touchObj]
            verify()
            {
                if(!canGoThroughMe || getDestination(gActor.getOutermostRoom) == nil)
                    illogical(cannotPushThroughMsg);
            }

            check() { checkPushTravel(); }
        }

        cannotPushThroughMsg = BMsg(cannot push through, '{I} {can\'t} {1}
            anything through {the iobj}. ', gVerbWord)

        /*
         *   PushTravelEnter handles commands like PUSH BOX INTO COFFIN, where the
         *   indirect object is a Booth-like object. The syntactically identical
         *   command for pushing things into an Enterable (e.g. PUSH BOX INTO HOUSE
         *   where HOUSE represents the outside of a separate location) is handled
         *   on the Enterable class.
         */
        dobjFor(PushTravelEnter)
        {
            preCond = [touchObj]
            verify()  {  verifyPushTravel(Into);  }

        }

        okayPushIntoMsg = BMsg(okay push into, '{I} <<if matchPullOnly>> pull{s/ed}
                    <<else>> push{es/ed}<<end>>} {the dobj} into {the iobj}. ')

        iobjFor(PushTravelEnter)
        {
            preCond = [containerOpen]
            verify()
            {
                if(!isEnterable)
                    illogical(cannotPushIntoMsg);
            }

            check()
            {
                checkInsert(gActor);
                checkInsert(gDobj);
            }

            action()
            {
                gDobj.actionMoveInto(self);
                gActor.actionMoveInto(self);

                if(gDobj.isIn(self))
                    say(okayPushIntoMsg);
            }
        }

        cannotPushIntoMsg = BMsg(cannot push into, '{I} {can\'t} {1}
            anything into {the iobj}. ', gVerbWord)

        dobjFor(PushTravelGetOutOf)
        {
            preCond = [touchObj]
            verify()
            {
                verifyPushTravel(OutOf);
                if(!self.isIn(gIobj))
                    illogicalNow(notInMsg);
            }

        }

        iobjFor(PushTravelGetOutOf)
        {
            preCond = [touchObj]

            verify()
            {
                if(!gActor.isIn(self))
                    illogicalNow(actorNotInMsg);

            }

            action()
            {
                gDobj.actionMoveInto(location);
                if(gDobj.location ==  location)
                {
                    say(okayPushOutOfMsg);
                    gActor.actionMoveInto(location);
                }
            }

        }

        okayPushOutOfMsg = BMsg(okay push out of, '{I} <<if matchPullOnly>> pull{s/ed}
                    <<else>> push{es/ed}<<end>> {the dobj} {outof iobj}. ')

        dobjFor(PushTravelClimbUp)
        {
            preCond = [touchObj]
            verify()  {  verifyPushTravel(Up);  }

            action() { doPushTravel(Up); }
        }

        iobjFor(PushTravelClimbUp)
        {
            preCond = [travelPermitted, touchObj]

            verify()
            {
                if(!isClimbable || getDestination(gActor.getOutermostRoom) == nil)
                    illogical(cannotPushUpMsg);
            }

            check() { checkPushTravel(); }
        }

        cannotPushUpMsg = BMsg(cannot push up, '{I} {can\'t} {1}
            anything up {the iobj}. ', gVerbWord)

        dobjFor(PushTravelClimbDown)
        {
            preCond = [touchObj]
            verify()  { verifyPushTravel(Down);  }

            action() { doPushTravel(Down); }
        }

        iobjFor(PushTravelClimbDown)
        {
            preCond = [travelPermitted, touchObj]

            verify()
            {
                if(!canClimbDownMe || getDestination(gActor.getOutermostRoom) == nil)
                    illogical(cannotPushDownMsg);
            }

            check() { checkPushTravel(); }
        }

        cannotPushDownMsg = BMsg(cannot push down, '{I} {can\'t} {1}
            anything down {the iobj}. ', gVerbWord)

        /*
         *   We don't bother to define isAskable etc. properties since we assume
         *   that no inanimate object can be conversed with, and that game code will
         *   use the Actor class to allow conversation. In any case since there's
         *   never any difficult in talking about oneself, the various illogicalSelf
         *   checks aren't needed.
         *
         *   Indeed, the handling of conversational commands on Thing is minimal;
         *   they are simply ruled out at the verify stage, since most Things can't
         *   converse. The implementation of these actions that allows conversation
         *   to take place is on the Actor class. We do however define a canTalkToMe
         *   property so that Actor can use the verify handling defined on Thing by
         *   just overriding it.
         *
         *   Things can't be talked to, so game code shouldn't normally override
         *   this property; it's there to be overridden on the Actor class.
         */
        canTalkToMe = nil

        dobjFor(AskAbout)
        {
            preCond = [objAudible]
            verify()
            {
                if(gActor == self)
                    illogicalSelf(cannotTalkToSelfMsg);

                else if(!canTalkToMe)
                  illogical(cannotTalkToMsg);
            }
        }

        dobjFor(AskFor)
        {
            preCond = [objAudible]
            verify()
            {
                if(gActor == self)
                    illogicalSelf(cannotTalkToSelfMsg);

                else if(!canTalkToMe)
                  illogical(cannotTalkToMsg);
            }
        }

        dobjFor(TellAbout)
        {
            preCond = [objAudible]
            verify()
            {
                if(gActor == self)
                    illogicalSelf(cannotTalkToSelfMsg);

                else if(!canTalkToMe)
                  illogical(cannotTalkToMsg);
            }
        }

        dobjFor(SayTo)
        {
            preCond = [objAudible]
            verify()
            {
                if(gActor == self)
                    illogicalSelf(cannotTalkToSelfMsg);

                else if(!canTalkToMe)
                  illogical(cannotTalkToMsg);
            }
        }

        /*
         *   Do we allow an implicit say command to be directed to this object? Normally we don't. Thuis
         *   property is only really meaningful on the Actor class but we define it here because it's
         *   needed by parser.t.
         */
        allowImplicitSay = nil

        dobjFor(QueryAbout)
        {
            preCond = [objAudible]
            verify()
            {
                if(gActor == self)
                    illogicalSelf(cannotTalkToSelfMsg);

                else if(!canTalkToMe)
                  illogical(cannotTalkToMsg);
            }
        }

        dobjFor(TalkAbout)
        {
            preCond = [objAudible]
            verify()
            {
                if(gActor == self)
                    illogicalSelf(cannotTalkToSelfMsg);

                else if(!canTalkToMe)
                  illogical(cannotTalkToMsg);
            }
        }

        dobjFor(TalkTo)
        {
            preCond = [objAudible]
            verify()
            {
                if(gActor == self)
                    illogicalSelf(cannotTalkToSelfMsg);

                else if(!canTalkToMe)
                  illogical(cannotTalkToMsg);
            }
        }

        cannotTalkToMsg = BMsg(cannot talk, 'There{dummy}{\'s} no point trying to
            talk to {the cobj}. ')

        cannotTalkToSelfMsg = BMsg(cannot talk to self, 'Talking to oneself {dummy}
            {is} generally pointless. ')

        dobjFor(GiveTo)
        {
            preCond = [objHeld, objNotWorn]
            verify()
            {
                if(isIn(gIobj))
                    illogical(alreadyHasMsg);
            }

            report()
            {
                if(gAction.summaryReport != nil)
                    dmsg(gAction.summaryReport, gActionListStr);

                if(gAction.summaryProp != nil)
                    gIobj.(gAction.summaryProp);
            }
        }

        alreadyHasMsg = BMsg(already has, '{The subj iobj} already {has}
            {the dobj}.')

        iobjFor(GiveTo)
        {
            preCond = [touchObj]
            verify()
            {
                if(!canTalkToMe)
                    illogical(cannotGiveToMsg);
                if(gActor == self)
                    illogicalSelf(cannotGiveToSelfMsg);
            }

        }

        cannotGiveToMsg = BMsg(cannot give to, '{I} {can\'t} give anything to {that
            iobj}. ')

        cannotGiveToSelfMsg = BMsg(cannot give to self, '{I} {can\'t} give anything
            to {himself actor}. ')

        dobjFor(ShowTo)
        {
            preCond = isFixed ? [objVisible] : [objHeld]
            report()
            {
                if(gAction.summaryReport != nil)
                    dmsg(gAction.summaryReport, gActionListStr);

                if(gAction.summaryProp != nil)
                    gIobj.(gAction.summaryProp);
            }
        }

        iobjFor(ShowTo)
        {
            preCond = [touchObj]
            verify()
            {
                if(gActor == self)
                    illogicalSelf(cannotShowToSelfMsg);
                else if(!canTalkToMe)
                    illogical(cannotShowToMsg);
            }
        }

        cannotShowToMsg = BMsg(cannot show to, '{I} {can\'t} show anything to {that
            iobj}. ')

        cannotShowToSelfMsg = BMsg(cannot show to self, '{I} {can\'t} show anything
            to {himself actor}. ')

        dobjFor(ShowToImplicit)
        {
            preCond = isFixed ? [objVisible] : [objHeld]

            verify()
            {
                if(gActor.currentInterlocutor == nil)
                    illogical(notTalkingToAnyoneMsg);
                else if(!Q.canTalkTo(gActor, gActor.currentInterlocutor))
                    illogicalNow(noLongerTalkingToAnyoneMsg);

            }

            action()
            {
                gActor.currentInterlocutor.handleTopic(&showTopics, [self]);
            }

            report()
            {
                if(gAction.summaryReport != nil)
                    dmsg(gAction.summaryReport, gActionListStr);

                if(gAction.summaryProp != nil)
                    gActor.currentInterlocutor.(gAction.summaryProp);
            }
        }

        dobjFor(GiveToImplicit)
        {
            preCond = [objHeld]

            verify()
            {
                if(gActor.currentInterlocutor == nil)
                    illogical(notTalkingToAnyoneMsg);
                else if(!Q.canTalkTo(gActor, gActor.currentInterlocutor))
                    illogicalNow(noLongerTalkingToAnyoneMsg);

            }

            action()
            {
                 gActor.currentInterlocutor.handleTopic(&giveTopics, [self]);
            }

            report()
            {
                if(gAction.summaryReport != nil)
                    dmsg(gAction.summaryReport, gActionListStr);

                if(gAction.summaryProp != nil)
                    gActor.currentInterlocutor.(gAction.summaryProp);
            }
        }

        notTalkingToAnyoneMsg = BMsg(not talking to anyone, '{I}{\'m} not talking to
            anyone. ')

        noLongerTalkingToAnyoneMsg = BMsg(no longer talking to anyone, '{I}{\'m} no
            longer talking to anyone. ')

     #ifdef __DEBUG
        /* Handling of Debugging actions. */

        /*
         *   PURLOIN allows the player to move any portable object in the game
         *   directly into his/her inventory, wherever it is currently. We don't
         *   allow absolutely anything to be purloined, as this could cause chaos.
         */
        dobjFor(Purloin)
        {
            verify()
            {
                if(isDirectlyIn(gActor))
                    illogicalNow(alreadyHeldMsg);

                if(self == gActor)
                    illogicalSelf(cannotPurloinSelfMsg);

                if(isFixed)
                    illogical(cannotTakeMsg);

                if(ofKind(Room))
                    illogical(cannotPurloinRoomMsg);

                if(gActor.isIn(self))
                    illogicalNow(cannotPurloinContainerMsg);

                logical;
            }

            check() {}

            action()
            {
                /*
                 *   We use moveInto() rather than actionMoveInto() to move the item
                 *   into the player's inventory since this isn't a regular take and
                 *   we don't want the side-effects of movement notifications,
                 *   neither to we want a notifyRemove() routine to veto a Purloin.
                 */
                moveInto(gActor);

                /*
                 *   Make this item unhidden even if it was hidden before, otherwise
                 *   it won't show up in inventory and we can't interact with it.
                 */
                isHidden = nil;

                /*
                 *   Note that the player char has seen the purloined item. Not
                 *   doing this can make it appear that the player character doesn't
                 *   know about an object that's in his/her inventory.
                 */
                if(gPlayerChar.canSee(self))
                    gSetSeen(self);
            }

            report()
            {
                DMsg(purloin, '{I} suddenly {find} {myself} holding {1}. ',
                    gActionListStr );
            }
        }

        cannotPurloinSelfMsg = BMsg(cannot purloin self, '{I} {can\'t} purloin
            {myself}. ')
        cannotPurloinRoomMsg = BMsg(cannot purloin room, '{I} {can\'t} purloin a
            room. ')
        cannotPurloinContainerMsg = BMsg(cannot purloin container, '{I} {can\'t}
            purloin something {i}{\'m} contained within. ')

        /*
         *   The GoNear action allows the player character to teleport around the
         *   map.
         */
        dobjFor(GoNear)
        {
            verify()
            {
                if(getOutermostRoom == nil)
                    illogicalNow(cannotGoNearThereMsg);

                if(ofKind(Room))
                    logicalRank(120);
            }

            action()
            {
                DMsg(gonear, '{I} {am} translated in the twinkling of an
                    eye...<.p>');
                getOutermostRoom.travelVia(gActor);
            }
        }

        cannotGoNearThereMsg = BMsg(cannot go there, '{I} {can\'t} go there right
            {now}. ')

     #endif
    ;

    thingPreinit: PreinitObject
        execute()
        {
            forEachInstance(Thing, {obj: obj.preinitThing });

            /*
             *   The player character presumably knows about the objects s/he's
             *   immediately holding even without explicitly examining them or
             *   taking inventory
             */
            foreach(local cur in gPlayerChar.contents)
                gSetKnown(cur);
        }

        execBeforeMe = [pronounPreinit]
    ;

    /*
     *   The Player class can be used to define the player character object. If
     *   there is only one player character in the game (the PC never changes) and
     *   the game is in the second person this can be done very conveniently, and
     *   the Player object will register itself with gameMain and libGlobal.
     */
    class Player: Thing

        /* The player character can't be picked up */
        isFixed = true

        /*
         *   The player character is most normally referred to in the first person,
         *   although this can be overridden to 1 or 3 for first- or third-person
         *   games.
         */
        person = 2

        /*
         *   Objects located in the player character are generally regarded as being
         *   held.
         */
        contType = Carrier

        /*
         *   Am I the Player object that starts out as the initial player character?
         *   Most games will only define one Player object so this will normally be
         *   true, but if a game defines more than one (e.g. to allow the player
         *   character to be switched mid-game) this should be set to nil on all the
         *   others so that the game knows which one to register as the initial
         *   player character. Note, however, that if a game contains more than one
         *   potential player character, it may often be better to define the others
         *   using the Actor class.
         */
        isInitialPlayerChar = true
    ;

    /*
     *   A Key is any object that can be used to lock or lock selected items whose
     *   lockabilty is lockableWithKey. We define all the special handling on the
     *   Key class rather than on the items to be locked and/or unlocked.
     */
    class Key: Thing

        /* The list of things this key can actually be used to lock and unlock. */
        actualLockList = []

        /*
         *   The list of things this key plausibly looks like it might lock and
         *   unlock (e.g. if we're a yale key, we might list all the doors in the
         *   game that have yale locks here).
         */
        plausibleLockList = []

        /*
         *   The list of all the things the player character knows this key can lock
         *   and unlock. Items are automatically added to this list when this key is
         *   successfully used to lock or unlock them, but game code can also use
         *   this property to list items the player character starts out knowing,
         *   such as the door locked by his/her own front door key.
         */
        knownLockList = []

        /*
         *   Determine whether we're a possible key for obj (i.e. whether we might
         *   be able to lock or unlock obj).
         */
        isPossibleKeyFor(obj)
        {
            /*
             *   First test if we've been defined as a plausible or known key for
             *   our lexicalParent in the case that we're the remapIn object for our
             *   lexicalParent. If so return true. We do this because game code
             *   might easily define the plausibleKeyList and/or knownKeyList on our
             *   lexicalParent intending to refer to what keys might unlock is
             *   associated container (i.e. ourselves if we're our lexicalParent's
             *   remapIn object).
             */
            if(obj.lexicalParent != nil && obj.lexicalParent.remapIn == obj
               &&(knownLockList.indexOf(obj.lexicalParent) != nil
                  || plausibleLockList.indexOf(obj.lexicalParent) != nil))
                return true;

            /*
             *   Otherwise return true if obj is in either our knownLockList or our
             *   plausibleLockList or nil otherwise.
             */
            return knownLockList.indexOf(obj) != nil ||
                plausibleLockList.indexOf(obj) != nil;
        }

        /* A key is something we can unlock with. */
        canUnlockWithMe = true

        iobjFor(UnlockWith)
        {
            preCond = [objHeld]

            verify()
            {
                inherited;

                /*
                 *   We're a logical choice of key if we're a possible key for the
                 *   direct object.
                 */
                if(gVerifyDobj && isPossibleKeyFor(gVerifyDobj))
                    logical;

                /* Otherwise we're not a very good choice. */
                else
                    implausible(notAPlausibleKeyMsg);
            }

            check()
            {
                /*
                 *   Check whether this key *actually* fits the direct object, and
                 *   if not display a message to say it doesn't (which halts the
                 *   action).
                 *
                 *   This is complicated by the fact that if the direct object is a
                 *   SubComponent the game author may have listed the dobj's
                 *   lexicalParent in our actualLockList property instead of the
                 *   actual dobj (e.g. the fridge object itself instead of the
                 *   SubComponent representing the interior of the fridge). So in
                 *   addition to seeing if the dobj is included in our
                 *   actuallockList we need to check whether, if the dobj has a
                 *   lexicalParent of which it's the remapIn object, dobj's
                 *   lexicalParent is in our actualLockList.
                 */

                if(actualLockList.indexOf(gDobj) == nil
                   && (gDobj.lexicalParent == nil
                   || gDobj.lexicalParent.remapIn != gDobj
                   || actualLockList.indexOf(gDobj.lexicalParent) == nil))
                    say(keyDoesntFitMsg);
            }

            action()
            {
                /* Make the dobj unlocked. */
                gDobj.makeLocked(nil);

                /* If the dobj is not already in our knownLockList, add it there. */
                if(knownLockList.indexOf(gDobj) == nil)
                    knownLockList += gDobj;
            }

            report()
            {
                DMsg(okay unlock with, okayUnlockWithMsg, gActionListStr);
            }

        }

        okayUnlockWithMsg = '{I} unlock{s/ed} {the dobj} with {the iobj}. '

        iobjFor(LockWith)
        {
            preCond = [objHeld]

            verify()
            {
                inherited;

                if(gVerifyDobj && isPossibleKeyFor(gVerifyDobj))
                    logical;
                else
                    implausible(notAPlausibleKeyMsg);
            }

            check()
            {
                /*
                 *   Check whether this key *actually* fits the direct object, and
                 *   if not display a message to say it doesn't (which halts the
                 *   action).
                 *
                 *   This is complicated by the fact that if the direct object is a
                 *   SubComponent the game author may have listed the dobj's
                 *   lexicalParent in our actualLockList property instead of the
                 *   actual dobj (e.g. the fridge object itself instead of the
                 *   SubComponent representing the interior of the fridge). So in
                 *   addition to seeing if the dobj is included in our
                 *   actuallockList we need to check whether, if the dobj has a
                 *   lexicalParent of which it's the remapIn object, dobj's
                 *   lexicalParent is in our actualLockList.
                 */
                 if(actualLockList.indexOf(gDobj) == nil
                   && (gDobj.lexicalParent == nil
                   || gDobj.lexicalParent.remapIn != gDobj
                   || actualLockList.indexOf(gDobj.lexicalParent) == nil))
                    say(keyDoesntFitMsg);
            }

            action()
            {
                /*Make the dobj locked. */
                gDobj.makeLocked(true);

                /* If the dobj is not already in our knownLockList, add it there. */
                if(knownLockList.indexOf(gDobj) == nil)
                    knownLockList += gDobj;
            }

            report()
            {
                 DMsg(okay lock with, okayLockWithMsg, gActionListStr);
            }
        }

        /* The message to say that the actor has lock the dobj with this key. */
        okayLockWithMsg = '{I} lock{s/ed} {the dobj} with {the iobj}. '

        /*
         *   The message to say that this key clearly won\'t work on the dobj
         *   (because it\'s the wrong sort of key for the lock; e.g. a yale key
         *   clearly won\'t fit the lock on a small jewel box).
         */
        notAPlausibleKeyMsg = '\^<<theName>> clearly won\'t work on <<gVerifyDobj.theName>>. '

        /*  The message to say that this key doesn\'t in fact fit the dobj. */
        keyDoesntFitMsg = '\^<<theName>> won\'t fit <<gVerifyDobj.theName>>. '

        preinitThing()
        {
            inherited;

            /*
             *   Add the actualLockList to the plausibleLockList if it's not already
             *   there to ensure that this key will work on anything in its
             *   actualLockList.
             */
            plausibleLockList = plausibleLockList.appendUnique(actualLockList);

        }

    ;

    /*
     *   A SubComponent is a Thing that is part of something else and represents the
     *   surface, container, underside or rear of the object to which it's attached.
     *   This allows a Thing to model several types of containment at once, by
     *   having (say) one SubComponent that represents its top (on which things can
     *   be placed) and another to represent its interior (in which things can be
     *   placed.
     *
     *   A SubComponent is normally defined as a nested anonymous object on the
     *   remapOn, remapIn, remapUnder or remapBehind property of a Thing. There's no
     *   need to further specify whether a SubComponent is also a Surface,
     *   Container, Underside or RearContainer, since the library can work this out
     *   from the property on which it is defined.
     */
    class SubComponent: Thing

        /*
         *   A SubComponent is always fixed in place since it represents a fixed
         *   part of its lexicalParent.
         */
        isFixed = true

        /* Preinitialize a SubComponent. */
        preinitThing()
        {
            /*
             *   Carry out the particular initialization of a SubComponent (see next
             *   method); this sets up the proper relation between the SubComponent
             *   and its lexicalParent.
             */
            initializeSubComponent(lexicalParent);

            /*  Note any vocab defined on this SubComponent */
            origVocab = vocab;

            /* Carry out the inherited handling. */
            inherited;
        }

        /*
         *   Initialize this SubComponent. This sets up the necessary relations
         *   between the SubCompononent and the object of which it is a part,
         *   usually its lexicalParent (passed as the parent parameter).
         */
        initializeSubComponent(parent)
        {
            /*
             *   If for any reason we don't have a parent (normally, our
             *   lexicalParent), stop here because there's nothing we can do.
             */
            if(parent == nil)
                return;

            /* A SubComponent is located in its parent. */
            location = parent;

            /*
             *   A SubComponent takes its name from its parent, since it's
             *   effectively an aspect of its parent.
             */
            nameAs(parent);

            /*
             *   Determine the contType of this SubComponent from the property to
             *   which it's attached. The contType must match the remapXX type.
             */
            if(parent.remapIn == self)
                contType = In;

            if(parent.remapOn == self)
                contType = On;

            if(parent.remapUnder == self)
                contType = Under;

            if(parent.remapBehind == self)
                contType = Behind;

            /* If we have a contType, set our listOrder to that of our contType. */
            if(contType != nil)
                listOrder = contType.listOrder;
        }

        /*
         *   Take each of our name-related properties from the corresponding
         *   properties of our lexicalParent. Language-specific modules may need to
         *   override this method to cater for additional properties.
         *
         *   Note that if the lexicalParent is renamed at any point during the game,
         *   this nameAs() method should be called on any openable SubComponent at
         *   the same time to ensure the names stay in sync.
         */
        nameAs(parent)
        {
            name = parent.name;
            proper = parent.proper;
            qualified = parent.qualified;
            person = parent.person;
            plural = parent.plural;
            massNoun = parent.massNoun;
            isHim = parent.isHim;
            isHer = parent.isHer;
            isIt = parent.isIt;
        }

        makeOpen(stat)
        {
            inherited(stat);

            /*
             *   If we close this item when the playerChar is inside it, the player
             *   will need to be able to refer to it with the vocab of its
             *   lexicalParent, since the lexicalParent will no longer be in scope
             */
            if(lexicalParent != nil && gPlayerChar.isIn(self))
            {
                if(stat)
                {
                    /*
                     *   If this object is being opened, replace our vocab with the
                     *   vocab we started out with (which will normally be none),
                     *   since we no longer want the player to be able to refer
                     *   directly* to us using our lexicalParent's vocab once we're
                     *   open.
                     */
                    replaceVocab(origVocab);

                    /*   Restore our name to our lexicalParent's name. */
                    nameAs(lexicalParent);
                }
                else
                {
                    /*
                     *   If this object is being closed, replace our vocab with that
                     *   of our lexicalParent (which is no longer in scope once
                     *   we're closed with the playerChar inside us) so that the PC
                     *   can still refer to us (e.g. to open us again).
                     */
                    addVocab(lexicalParent.vocab);

                }
            }
        }

        /*
         *   Our original vocab. We need to store this in case makrOpen() wants to
         *   restore it.
         */
        origVocab = nil

        matchNameDisambig(tokens)
        {
            local match = inherited(tokens);

            /*
             *   If we're being matched at a disambig prompt and we don't have any
             *   vocabWords of our own, try matching us against our lexicalParent's
             *   vocab instead, provided we have a lexicalParent.
             *
             *   This is necessary because SubComponents don't normally have any
             *   vocab of their own, but a SubComponent may end up as one of the
             *   objects that notionally matched the vocab of its lexicalParent
             *   among which the parser now wishes the player to disambiguate. For
             *   example, if the player types LOOK IN BOX and there's more than one
             *   box in scope, and the red box (say) has a SubComponent defined on
             *   its remapIn property, then since the LOOK IN action will have been
             *   redirected to the SubComponent, the parser will list the
             *   SubComponent as the object corresponding to "red box", but the
             *   SubComponent doesn't have any vocab of its own for the player's
             *   response "red" to match; when disambiguating we therefore need to
             *   try to match against our lexicalParent's vocab.
             */
            if(match == 0 && lexicalParent != nil)
                return lexicalParent.matchNameDisambig(tokens);

            /*
             *   Otherwise just return our normal match
             */
            return match;

        }
    ;

    /*
     *   A MultiLoc is an object that can exist in several locations at once.
     *   MultiLoc is a mix-in class that should be used in conjunction with Thing or
     *   a Thing-derived class.
     */
    class MultiLoc: object

        /*
         *   A list of the locations this object is currently present in. If this
         *   property is defined at the start of the game and initialLocationList
         *   isn't, then this list will be copied to initialLocationList, and so can
         *   be specified by users in exactly the same way.
         */
        locationList = []

        /*
         *   A list of the locations this object is to start out in. Locations may
         *   be specified as Things, Rooms or Regions, or as some mix of all three.
         */
        initialLocationList = []

        /*
         *   A list of locations this object is not to be present in. This is
         *   intended mainly to allow certain rooms to be excepted from a specified
         *   region.
         */
        exceptions = []

        /*
         *   If the initialLocationClass property is defined, then this MultiLoc is
         *   initially located in every instance of this class. Note that this would
         *   be in addition to the locations defined in the locationList class and
         *   would likewise be subject to anything defined in the exceptions
         *   property.
         */
        initialLocationClass = nil

        /*
         *   Test an object for inclusion in our initial location list.  By
         *   default, we'll simply return true to include every object.  We
         *   return true by default so that an instance can merely specify a
         *   value for initialLocationClass in order to place this object in
         *   every instance of the given class.
         */
        isInitiallyIn(obj) { return true; }

        /*
         *   In Preinit, add this MultiLoc into the contents list of every item in
         *   its locationList and every object of class initialLocationClass (if
         *   this is not nil) and then remove it from the contents list of every
         *   item in its exceptions list.
         */
        addToLocations()
        {
            /*
             *   If there's nothing in the initialLocationList, we'll assume the
             *   author used the locationList property to specify the initial
             *   locations of this MultiLoc, since this was correct in earlier
             *   versions and should be maintained for backward compatibility. In
             *   That case copy the locationList to the initialLocationList and then
             *   set the locationList to an empty list before attempting to build
             *   it.
             */
            if(initialLocationList.length == 0)
            {
                initialLocationList = locationList;
                locationList = [];
            }

            /* Create a new Vector to keep track of our list of locations. */
            local locationVec = new Vector(10);

            /*
             *   Add ourselves to the content property of all the items listed in
             *   our initialLocationList. At the same time append each item listed
             *   to our locationVec vector.
             */
            foreach(local loc in valToList(initialLocationList))
            {
                loc.addToContents(self, locationVec);
            }

            /*
             *   If we have an initialLocationClass, add ourselves to the location
             *   list of every object of that class for which our isInitiallyIn(obj)
             *   method returns true; at the last time add all such objects to our
             *   locationVec vector.
             */
            if(initialLocationClass != nil)
            {
                for(local obj = firstObj(initialLocationClass); obj != nil; obj =
                    nextObj(obj, initialLocationClass))
                {
                    if(isInitiallyIn(obj))
                        obj.addToContents(self, locationVec);
                }
            }

            /*
             *   Now remove ourselves from the contents list of all objects listed
             *   as exceptions in our exceptions property, and remove those objects
             *   from our locationVec vector.
             */
            foreach(local loc in valToList(exceptions))
            {
                loc.removeFromContents(self, locationVec);
            }

            /*
             *   Store our resulting list of locations in our locationList property.
             */
            locationList = locationVec.toList();
        }

        /*
         *   Move this MultiLoc into an additional location.
         */
        moveIntoAdd(loc)
        {
            /*
             *   Let the new location handle it, so it will work whether the new
             *   location is a Thing, a Room or a Region.
             */
            loc.moveMLIntoAdd(self);

        }

        /*
         *   Remove this MultiLoc from loc.
         */
        moveOutOf(loc)
        {
            /*
             *   Let the new location handle it, so it will work whether the new
             *   location is a Thing, a Room or a Region.
             */
            loc.moveMLOutOf(self);
        }

        /*
         *   To move a MultiLoc into a single location, first remove it from every
         *   location in its location list, then add it to the single location it's
         *   now in.
         */
        moveInto(loc)
        {
            foreach(local cur in locationList)
                cur.removeFromContents(self);

            locationList = [];

            if(loc != nil)
                moveIntoAdd(loc);
        }

        /*
         *   A MultiLoc is directly in another object if it's listed in that other
         *   object's contents list.
         */
        isDirectlyIn(loc)
        {
            if(loc != nil)
                return valToList(loc.contents).indexOf(self) != nil;

            /*
             *   We only reach this point if loc is nil, in which case we're testing
             *   whether this MultiLoc is in nil, i.e. nowhere at all. This will be
             *   the case if and only if its location list is empty.
             */
            return locationList == [];
        }

        /*
         *   A MultiLoc is in another object either if it's directly in that object
         *   or if one of the items in its location list is in that object.
         */
        isIn(loc)
        {
            return isDirectlyIn(loc)
                || locationList.indexWhich({x: x.isIn(loc)}) != nil;
        }

        /*
         *   For certain purposes, such as sense path calculations, a Multiloc needs
         *   a notional location. We assume the enquiry is made from the perspective
         *   of the current actor, or, failing that, the player char, so we return
         *   the current actor's (or the player char's) current location if the
         *   MultiLoc is present there, or the last place where the MultiLoc was
         *   seen otherwise. The intention is to select the most currently
         *   significant location where we're present.
         */
        location()
        {
            /*
             *   If our locationList is empty, then we aren't anywhere, so our
             *   location is nil.
             */
             if(locationList.length == 0)
                return nil;

            /*
             *   Get the room either of the current actor (if there is one) or else
             *   of the player char.
             */
            local rm = gActor == nil ? gPlayerChar.getOutermostRoom :
            gActor.getOutermostRoom;

            /*
             *   First see if we're directly in the actor's enclosing room. If so,
             *   return that room as our location.
             */
            if(isDirectlyIn(rm))
                return rm;

            /*
             *   If that doesn't work, check if anything in our location list is in
             *   the actor's room; if so, use that.
             */
            local loc = locationList.valWhich({x: x.isIn(rm)});

            if(loc != nil)
                return loc;

            /*
             *   If that doesn't work, return the location we were last seen at,
             *   provided we have one and we're still there.
             */
            if(lastSeenAt != nil && locationList.indexOf(lastSeenAt) != nil)
                return lastSeenAt;

             /*
              *   If all else fails, return the first location from our locationList
              *   (if we've reached this point we know for sure there is one, since
              *   if our locationList were empty this method would have already
              *   returned nil).
              */
            return locationList[1];
        }

        /*
         *   If we're a MultiLoc we don't want to carry out any of the normal
         *   preinitialization related to our location.
         */
        preinitThing()
        {
            /* if we have a global parameter name, add it to the global table */
            if (globalParamName != nil)
                libGlobal.nameTable_[globalParamName] = self;
        }
    ;

    /*
     *   The Floor Class is used to provide a floor or ground object to each room
     *   that wants one (by default, every Room). While this serves the secondary
     *   purpose of allowing the player to refer to the ground/floor of the current
     *   location (which is nearly always implicitly present), it's primary purpose
     *   is to allow the parser to refer to the floor/ground when it wants to
     *   distinguish items directly in a Room (and hence notionally on the ground)
     *   from those in or on some other object.
     */
    class Floor: MultiLoc, Thing
        /*
         *   A Floor is a Decoration, but since the extras.t module is optional we
         *   have to define is as isFixed = true and isDecoration = true.
         */
        isFixed = true

        isDecoration = true

        /* By default, every room has a floor. */
        initialLocationClass = Room

        /* A Floor is something we can put things on. */
        contType = On

        /*
         *   We narrow down our list of locations to those Rooms that actually
         *   define this Floor as their floorObj. Some rooms may wish to define a
         *   custom floorObj, and some (e.g. those representing the top of a mast or
         *   tree) may want to have no floorObj at all.
         */
        isInitiallyIn(obj) { return obj.floorObj == self; }

        /*
         *   The Floor object needs to appear to share the contents of the player
         *   character's room (or other enclosing container) for certain purposes
         *   (such as disambiguating by container or the TakeFrom command), but
         *   nothing is really moved into or out of a Floor).
         */
        contents = (gPlayerChar.outermostVisibleParent().contents - self)

        /*
         *   We can examine a Floor or take something from it, but other actions are
         *   ruled out. A Floor should generally be treated as a Decoration object
         *   rather than something with which any extensive interaction is allowed.
         */
        decorationActions = [Examine, TakeFrom]

        /*
         *   By default we probably want to keep the description of a Floor object
         *   as minimalistic as possible to discourage players from trying to
         *   interact with it, so we won't listed the 'contents' of a Floor when
         *   it's examined. This can of course be overridden if desired.
         */
        contentsListed = nil
    ;

    /*
     *   The defaultGround object is the specific Floor object that's present in
     *   every Room by default.
     */
    defaultGround: Floor
    ;

    /* Preinitilizer for MultiLocs */
    multiLocInitiator: PreinitObject
        execute()
        {
            /*
             *   Add every MultiLoc to the contents list of all its locations. This
             *   also builds each MultiLoc's locationList as it goes.
             */
            for(local cur = firstObj(MultiLoc); cur !=nil ; cur = nextObj(cur,
                MultiLoc))

                cur.addToLocations();
        }

        /*
         *   Make sure we've preinitialized Regions first, so that their roomLists
         *   are ready when we want to use them.
         */
        execBeforeMe = [regionPreinit]
    ;

    /*
     *   A Topic is something that the player character can refer to in the course
     *   of conversation or look up in a book, but which is not implemented as a
     *   physical object in the game. Topics can be used for abstract concepts such
     *   as life, liberty and happiness, or for physical objects that are referred
     *   to but not actually implemented as Things in the game, such as Alfred the
     *   Great or the Great Wall of China.
     */
    class Topic: Mentionable
        construct(name_)
        {
            vocab = name_;
            initVocab();
        }

        /*
         *   Whether the player character knows of the existence of this topic. By
         *   default we assume this is true.
         */
        familiar = true

        /*   Make this topic known to the player character */
        setKnown() { gPlayerChar.setKnowsAbout(self); }

        /* Test whether this topic is known to the player character */
        known = (gPlayerChar.knowsAbout(self))

        /*
         *   Return a textual description of this topic, which will normally be just
         *   its name. We use the vocab as fall-back alternative.
         */
        getTopicText()
        {
            return name == nil ? vocab : name;
        }
    ;
    /* ------------------------------------------------------------------------ */
    /*
     *   LocType objects are used for Thing.locType property values to specify
     *   the relationship between an object and its container.
     *
     *   The language module must set appropriate vocabulary properties for
     *   each LocType object during pre-initialization.  The exact vocabulary
     *   needed is up to the language to define.  For the English module, we
     *   set the 'prep' property to a suitable preposition for constructing
     *   locational phrases ("the book *on* the table", etc).
     */
    class LocType: object

        listOrder = 100
    ;

    /*
     *   An IntLocType is an interior location type.  These represent objects
     *   on the inside of an enclosed space.
     */
    IntLocType: LocType

    ;

    /*
     *   An ExtLocType is an exterior location type.  These represent objects
     *   on the outside of an object, such as atop it or attached to it.
     */
    ExtLocType: LocType
    ;

    /*
     *   "In" location type - specifies that an object is contained within its
     *   location; its location encloses it.
     */
    In: IntLocType
        listOrder = 10
    ;

    /*
     *   "Outside" location type - specifies that an object is situated
     *   somewhere on the exterior of the object.  This can be used for
     *   components, attachments, things stuck to an object, things nailed to
     *   it, messages painted on it, etc.
     */
    Outside: ExtLocType
    ;

    /*
     *   "On" location type - specifies that an object is sitting on the top
     *   surface of its container.
     */
    On: ExtLocType
        listOrder = 20
    ;

    /*
     *   "Under" location type - specifies that an object is situated
     *   underneath its container.
     */
    Under: ExtLocType
        listOrder = 30
    ;

    /*
     *   "Behind" location type - specifies that an object is situated behind
     *   its container.
     */
    Behind: ExtLocType
        listOrder = 40
    ;

    /*
     *   "Held" location type - specifies that an object is being held by its
     *   container, in the sense of a person holding an object in her hands.
     *   An object being held is exterior to the holder, not enclosed.
     */
    Held: ExtLocType
    ;

    /*
     *   "Worn" location type - specifies that an object is being worn by its
     *   container, in the sense of a person wearing a coat.
     */
    Worn: ExtLocType
    ;

    /*
     *   "Attached" location type - specifies that an object is attached to its
     *   container.
     */
    Attached: ExtLocType
    ;

    /*
     *   "PartOf" location type - specifies that an object is part of -- a component
     *   of -- its container.
     */
    PartOf: ExtLocType
    ;

    /*
     *   "Carrier" location type - specifies that the object is being carried by its
     *   container (which will then normally be the actor holding this object). Any
     *   actor-type object should define Carrier as its contType.
     */
    Carrier: ExtLocType
    ;

    /*
     *   A ViaType is an object used to define the preposition to use to describe
     *   various kinds of PushTravel. The language-specific part of the libary needs
     *   to override the various ViaType objects to give the names of the
     *   prepositions in the target language.
     */
    class ViaType: object
        prep = ''
    ;

    Into: ViaType;
    OutOf: ViaType;
    Down: ViaType;
    Up: ViaType;
    Through: ViaType;

    /*
     *   The displayProbe object is used to store the result of capturing text in
     *   Thing.checkDisplay() before undoing the trial display of the string. By
     *   making displayProbe transient we preserve the value of its displayed
     *   property across the undo.
     */
    transient displayProbe: object
        displayed = nil
    ;

    /*
     *   The failVerifyObj is intended for internal library use only as a fallback value for gVerifyIobj
     *   or gVerifyDobj when these might otherwise evailuate to nil and potentially cause nil object
     *   reference runtime errors. Since this is never intended to be a valid verify result,
     *   failVerifyObj is designed to fail the verify stage of any action.
     */

    failVerifyObj: Thing
        dobjFor(Default) { verify {inaccessible(inaccessibleMsg);}}
        iobjFor(Default) { verify {inaccessible(inaccessibleMsg);}}
        aobjFor(Default) { verify {inaccessible(inaccessibleMsg);}}

        inaccessibleMsg = BMsg(dummy object inaccessible, 'The dummy failVerifyObj is not a valid object
            of a command. ')
    ;

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