#charset "us-ascii"
#include "advlite.h"
property subLocation;
property lookAroundShowExits;
property stanceToward;
property setStanceToward;
/*
* 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) { }
/*
* Our original vocab string, if we've defined an altVocab that might replace our original
* vocab. Tbis is should normally be left to the library to set at preinit.
*/
originalVocab = nil
/*
* An alternative vocab string to be used when useAltVocabWhen is true, or list of altenative
* strings to be used under various conditions.
*/
altVocab = nil
/*
* A condition that must be true for us to change (or maintain) our vocab to our altVocab. If
* it returns nil we revert back to our original vocab. If we return -1 the change to altVocab
* becomes permanent and our updateVocab methdd won't be executed any more.
*
* But if altViocab is defined as a list, we return nil to return to our originalVocab, 0 to
* return to our original vocab and keep it for the rest of the game, n (where n > 0) to
* change our vocab to the nth item in our altVocab list or -n to change our vocab to the nth
* item in our altVocab list and then keep it there for the remainder of the game (i.e. stop
* checking or vocab updates).
*/
useAltVocabWhen = nil
/*
* A condition that when true means that the library will stop checking for switching vocab to
* and from the altVocab (or between different vocabs). This could, for example, be set to
* useAltVocabWhen when we only want to change vocab once, say when the player gets to learn
* the name of an NPC or the true nature of an object is first revealed.
*/
finalizeVocabWhen = nil
/* Initialize our alternative vocab */
initAltVocab()
{
/*
* Add ourselves to the list of Things whose vocab might change so we can be checked each
* turn.
*/
libGlobal.altVocabLst += self;
/* Store a copy of our original vocab string so we can revert to it. */
originalVocab = vocab;
}
/*
* This is called every turn on every Thing listed in libGlobal.altVocabLst. By default it
* carries out alternation between our original vocab and our altVocab according to the value
* of useAltVocabWhen. Game code can override this methed to do something different, but must
* give altVocab a non-nil value for this method to be invoked each turn, or each turn when
* this Thing is in scope.
*
*/
updateVocab()
{
if(altVocab)
{
/* Stash the current value of useAltVocabWhen so we don't have to recalculate it. */
local uavw = useAltVocabWhen;
/*
* If the condition for using our altVocab is false and we're not already using our
* original vocab, replace our current vocab with our original vocab.
*/
if(uavw == nil && vocab != originalVocab)
replaceVocab(originalVocab);
/*
* if altVocab is defined as a list we change vocab to the appropriate item in the
* list.
*/
if(dataType(altVocab) == TypeList)
{
/*
* A return value of less that 1 means we want to change the vocab to the -uavw
* item in the list and keep it there for the rest of the game.
*/
if((uavw != nil && uavw < 1) || finalizeVocabWhen)
{
libGlobal.altVocabLst -= self;
uavw = -uavw;
}
/*
* If uavw is in range and is different from the previous value, then we need to
* change the vocab to entry uavw in our altVocab list. A value of 0 means that we
* want to change the vocab to its original value and leave it there for the rest
* of the game.
*/
if(uavw != uavwNum && (uavw == nil || uavw <= altVocab.length))
{
uavwNum = uavw;
local newVocab = (uavw && uavw > 0) ? altVocab[uavw] : originalVocab;
replaceVocab(newVocab);
}
}
else
{
/*
* If the condition for using our altVocab is true and we're not already using it,
* then replace our vocab with our altVocab.
*/
if(uavw && vocab != altVocab)
replaceVocab(altVocab);
/*
* If our useAltVocabWhen property evaluates to the special value of -1, then we
* want the change to our altVocab to be permanent, so remove us from the list of
* Things whose updateVocab() property is regularly called.
*/
if(uavw == -1 || finalizeVocabWhen)
libGlobal.altVocabLst -= self;
}
}
}
/* The previous return value from useAltVocabWhen - for internal library use only. */
uavwNum = nil
/*
* Method designed to be called from the action() method of a dobjFor(XXX) block to display a
* message safely for an action that maight be executed implicitly. If the action is implicit,
* the message won't be displayed until immediately after the implicit action reports. If the
* action isn't an implicit one, the message will be displayed straight away. The optional
* second msg2 parameter is a variant message for display immediately after the implicit
* action reports; otherwise msg will be used.
*/
actionReport(msg, msg2?)
{
if(gAction.isImplicit)
reportPostImplicit(msg2 ?? msg);
else
say(msg);
}
;
/* ------------------------------------------------------------------------ */
/*
* 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);
/*
* We start by describing the PC's immediate environment, provided the flag
* pclistedInLook is true.
*/
if(loc.pcListedInLook)
{
/* 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, provided it's not hidden and its
* contentsListedInLook property is true
*/
if(!loc.isHidden && loc.contentsListedInLook)
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)
/*
* Fllag: should the Player Character be listed as being in this object as part of a room
* description (this is often useful but may sometimes look redundant)? By default we take out
* value from contentsListedInLook.
*/
pcListedInLook = (contentsListedInLook)
/*
* 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)
/*
* The maximum number of items we can hold, irrespective of their bulk (or weight). By default
* we make this a very large number so there's no effective limit; game code can set a lower
* limit on the player character and/or other actors.
*/
maxItemsCarried = 100000
/* 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 and we are no longer in our notional moveTo location.
*/
if(newCont != location)
{
wornBy = nil;
movedTo = 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)
/*
* The isInitialPlayerChar property was formerly used as an alternative method of identifying
* the player character. This method of doing so is now deprecated, except for its use on the
* Player class. Instead you should now define the player character on
* gameMain.initialPlayerChar
*/
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.propType(&initialPlayerChar) != TypeObject)
{
/* 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;
}
/* Set us as knowning about everything in our initiallyKnown lisr. */
foreach(local item in valToList(initiallyKnowsAbout))
setKnowsAbout(item);
/* if we have an altVocab string, initialize our altVocab handling. */
if(altVocab)
initAltVocab();
/*
* 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, val?)
{
switch(dataType(obj))
{
case TypeObject:
obj.(knownProp) = true;
break;
case TypeSString:
setInformed(obj, val);
break;
default:
;
}
}
/* 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, or, if obj is passsed as a string tag, if we
* have been informed about it.
*/
knowsAbout(obj)
{
switch(dataType(obj))
{
/*
* If obj is an object, then return true either if we've seen this objector if we know
* about it as defined by our knownProp.
*/
case TypeObject:
return hasSeen(obj) || obj.(knownProp);
/*
* If obj is a single-quoted string, assume it's a knowledge tag and test for our
* being informed abnout it.
*/
case TypeSString:
return informedAbout(obj);
default:
return nil;
}
}
/*
* 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
/* Our look up table for things we've been informed about */
informedNameTab = nil
/*
* Note that we've been informed of something, by adding it to our
* informedNameTab. Tag is an arbitrary single-quoted string value used to
* represent the information in question.
*/
setInformed(tag, val?)
{
if(informedNameTab == nil)
informedNameTab = new LookupTable(32, 32);
if(val == nil && informedNameTab[tag] == nil)
informedNameTab[tag] = true;
else
informedNameTab[tag] = val ?? true;
}
/* Make this Actor or Consultable forget about tag altogether. */
forget(tag)
{
if(informedNameTab)
informedNameTab.removeElement(tag);
}
/*
* Determine whether this Thing has been informed about tag. We return true if there is a
* corresponding non-nil entry in our informedNameTab, or else the value of the corresponding
* entry if ibGlobal.informedTrueOrFalseOnly is nil (the default).
*/
informedAbout(tag)
{
return informedNameTab == nil ? nil
: (libGlobal.informedTrueOrFalseOnly ? (informedNameTab[tag] != nil)
: informedNameTab[tag]);
}
/*
* The list of things we start the game knowing about. This list can contain a mix of game
* objects (Things and/or Topics) and fact tags. Note that it would be redundant to include
* Things and Topics already defined as familiar if this property is overridden on the initial
* player character object.
*/
initiallyKnowsAbout = nil
/*
* 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 preAction handling on this Thing if it's the current actor. This is called just before
* the relevant Doer is executed to provide a convenient entrypoint to intervene in an action
* before it can do anything at all (typically when the actor is tied up, paralysed, or
* otherwise temporarily incapacitated, which might require the intervention of three or four
* simlar Doers to trap). the object combination to execute: this is an [action, dobj, iobj,
*...] list.
*/
preAction(lst)
{
}
/*
* 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 ''; }
/*
* The ThoughtManager object associated with this Thing (if this Thing does any thinking);
* this is only relevant if thoughts.t is present and this Thing becomea a player character.
*/
myThoughtManager = nil
/*
* ******************************************************************
* 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 and enough items carried capacity 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 or maxItemCarried capacity, see if the BagOfHolding class can deal with it by
* moving something to a BagOfHolding.
*/
if(defined(BagOfHolding)
&& (bulk > gActor.bulkCapacity - gActor.getCarriedBulk ||
gActor.directlyHeld.length > gActor.maxItemsCarried - 1)
&& BagOfHolding.tryHolding(self));
/*
* otherwise check that the actor has sufficient spare carrying capacity.
*/
else if(bulk > gActor.bulkCapacity - gActor.getCarriedBulk ||
gActor.directlyHeld.length > gActor.maxItemsCarried - 1)
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} {find} nothing of interest 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 of interest 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 of interest 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 unless it explicitly can be moved by
* PushTravel or PullTravel)
*/
isMoveable = (!isFixed || canPushTravel || canPullTravel)
/*
* 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 = BMsg(cannot move, '{The subj dobj} {won\'t} budge. ')
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 = location.ofKind(Room) ? [touchObj] : [objHeld]
verify()
{
if(!isMoveable)
illogical(cannotMoveMsg);
}
action()
{
/*
* If the iobj is a container-like object, assume MOVE TO it means putting us inside
* it/
*/
if(gIobj.contType == In)
replaceAction(PutIn, self, gIobj);
/* If the obj is a surface-like object, assume MOVE TO it means putting us on it. */
if(gIobj.contType == On)
replaceAction(PutOn, self, gIobj);
/* Otherwise we mean moving us near the iobj. */
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)
{
actionMoveInto(loc.location);
if(location == loc.location)
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}. ')
dobjFor(MoveAwayFrom)
{
preCond = [touchObj]
verify()
{
if(!isMoveable)
illogical(cannotMoveMsg);
}
action()
{
if(movedTo)
movedTo = nil;
else if(gIobj.contType is in (In, On, Under, Behind))
doInstead(TakeFrom, gDobj, gIobj);
}
report()
{
DMsg(okay move from, '{I} move{s/d} {1} {dummy} away from {the iobj}. ',
gActionListStr);
}
}
iobjFor(MoveAwayFrom)
{
verify()
{
if(gDobj == self)
illogicalSelf(cantMoveAwayFromSelfMsg);
if(gDobj.movedTo != self && contType not in (In, On, Under, Behind))
illogicalNow(notMovedToMsg);
}
}
cantMoveAwayFromSelfMsg = BMsg(cant move away from self, '{I} {can\'t} move {the dobj} away from
{itself dobj}. ')
notMovedToMsg = BMsg(not by obj, '{The subj dobj} {is}n\'t by {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(TypeOn)
{
preCond = [touchObj]
verify()
{
if(!canTypeOnMe)
illogical(cannotTypeOnMsg);
}
}
dobjFor(TypeOnVague)
{
preCond = [touchObj]
verify()
{
if(!canTypeOnMe)
illogical(cannotTypeOnMsg);
}
action() { askMissingLiteral(TypeOn, DirectObject); }
}
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 = [canTalkToObj]
verify()
{
if(gActor == self)
illogicalSelf(cannotTalkToSelfMsg);
else if(!canTalkToMe)
illogical(cannotTalkToMsg);
}
}
dobjFor(AskFor)
{
preCond = [canTalkToObj]
verify()
{
if(gActor == self)
illogicalSelf(cannotTalkToSelfMsg);
else if(!canTalkToMe)
illogical(cannotTalkToMsg);
}
}
dobjFor(TellAbout)
{
preCond = [canTalkToObj]
verify()
{
if(gActor == self)
illogicalSelf(cannotTalkToSelfMsg);
else if(!canTalkToMe)
illogical(cannotTalkToMsg);
}
}
dobjFor(SayTo)
{
preCond = [canTalkToObj]
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 = [canTalkToObj]
verify()
{
if(gActor == self)
illogicalSelf(cannotTalkToSelfMsg);
else if(!canTalkToMe)
illogical(cannotTalkToMsg);
}
}
dobjFor(TalkAbout)
{
preCond = [canTalkToObj]
verify()
{
if(gActor == self)
illogicalSelf(cannotTalkToSelfMsg);
else if(!canTalkToMe)
illogical(cannotTalkToMsg);
}
}
dobjFor(TalkTo)
{
preCond = [canTalkToObj]
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. ')
dobjFor(SpecialAction)
{
verify()
{
illogical(cantSpecialActionMsg);
}
}
cantSpecialActionMsg = BMsg(cant do special, '{I} {can\'t} {1} {the dobj}. ',
gAction.specialPhrase )
#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 getPlayerChar().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: Actor
/* 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
/* The Player object is the initial player character. */
isInitialPlayerChar = true
isProper = 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;
}
/* For internal use by the parser; has the parser newly created us for its own purposes? */
newlyCreated = nil
;
/* Stub definitions to allow Actor to be modfied in actor.t */
class EndConvBlocker: object;
class AgendaManager: object;
class ActorTopicDatabase: TopicDatabase;
class TopicDatabase: object;
/*
* Very basic Actor class with a few basic properties defined. This allows code that need to
* references the Actor class to compile in adv3Liter and adv3Litest. It also allows the Player
* class to descend from Actor. The implementation here is replaced by the much more sophisticated
* one in actor.t when actor.t is present.
*/
class Actor: EndConvBlocker, AgendaManager, ActorTopicDatabase, Thing
isFixed = true
contType = Carrier
ownsContents = true
mood = nil
stance = nil
cannotTalkToMsg = BMsg(cannot talk basicactor, '{The subj cobj} {doesn\'t seem} interested. ')
cannotGiveToMsg = cannotTalkToMsg
cannotShowToMsg = cannotTalkToMsg
isAttackable = true
checkAttackMsg = cannotAttackMsg
;
/* ------------------------------------------------------------------------ */
/*
* 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. ')
;
/* We define Mood and Stance in thing.t so english.t can define the built-in moods and stances */
/* A Mood object can be used to represent the mood of an actor (happy, sad, bored, etc.) */
class Mood: object
/*
* A single-quoted string giving the name of this Mood, which will normally correspond to the
* name of the Mood object; e.g. happyMood.name = 'happy'
*/
name = nil
objToString() { return name; }
;
/*
* A Stance object can be used to represent the stance of an actor towards the player character
* (neutral, friendly, hostile, etc.).
*/
class Stance: object
/*
* A single-quoted string giving the name of this Stance which will normally correspond to the
* name of the Mood object; e.g. friendlyStance.name = 'friendly'
*/
name = nil
/*
* The score is a measure of how positive or negative an actor with this stance is towards the
* player character. Each Stance object defines its own score.
*/
score = 0
operator >> (x) { return self.score > x.score; }
operator << (x) { return self.score < x.score; }
operator >>> (x) { return self.score >= x.score; }
operator []= (x, y) { x.setStanceToward(y, self); }
operator - (x) {return self.score - x.score; }
/* Get a list of actors who have this stance towards x */
operator * (x)
{
local vec = new Vector;
for(local a = firstObj(Actor); a != nil; a = nextObj(a, Actor))
{
if(a.stanceToward(x) == self)
vec.append(a);
}
return vec.toList();
}
/* Get a list of actors x holds this stance towards */
operator [](x)
{
local vec = new Vector;
for(local a = firstObj(Actor); a != nil; a = nextObj(a, Actor))
{
if(x.stanceToward(a) == self)
vec.append(a);
}
return vec.toList();
}
objToString() { return name; }
;
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1