english.t | documentation |
#charset "us-ascii" #include "advlite.h" /* * The main English language module. * * This is the English implementation of the generic language interfaces. * All of the code here is English-specific, so other language modules * will replace the actual implementation. However, somef of the methods * and properties are part of the generic interface - this means that * each language module must define methods and properties with these * names, and with the abstract behavior described. How they actually * implement the behavior is up to them. * * Methods and properties that are part of the generic interface are * identified with [Required]. */ property pluralTokens; /* ------------------------------------------------------------------------ */ /* * English module options. */ englishOptions: object /* decimal point character */ decimalPt = '.' /* group separator in large numbers */ numGroupMark = ',' ; /* ------------------------------------------------------------------------ */ /* * LMentionable is the language-specific base class for Mentionable. * * This is the root class for objects that the player can mention in * commands. The key feature of these objects is that they can match * noun phrases in command input. The library subclasses this base class * with Mentionable. This class provides the part of the class that * varies by language. * * [Required] */ class LMentionable: object /* * Get the indefinite form of the name, nominative case. * * [Required] */ aName = (ifPronoun(&name, aNameFrom(name))) /* * Get the definite form of the name, nominative case. * * [Required] */ theName = (ifPronoun(&name, theNameFrom(name))) /* Definite name, objective case. */ theObjName = (ifPronoun(&objName, theNameFrom(name))) /* * Get the objective form of the name. The regular 'name' property * gives the subjective form - i.e., the form that appears as the * subject of a verb. 'objName' gives the form that appears as a * direct or indirect object. Unlike many languages, English doesn't * further distinguish cases for the different roles of verb objects; * they're all just "objective". English also doesn't inflect * regular nouns at all for these two cases - the objective form of * "book" or "key" or "widget" is identical to the subjective form. * The only place where case makes a difference in English is * pronouns: "I" and "me", "he" and "him", etc. So, this routine * simply returns the subjective name string by default, which will * work for any object with a regular noun as its name. Generally, * this will only need to be overridden for the player character * object, which usually uses a pronoun as its name ("you" for a * second-person game, "I" for a first-person game). */ objName = (name) /* * Get the possessive adjective-like form of the name. This is the form of the name we use as * a qualifier phrase when showing an object we possess. The English rule for ordinary nouns * is just to add apostrophe-s to the name: "Bob" becomes "Bob's", "Orc guard" becomes "Orc * guard's". This works for nearly all nouns in English, but you can override this if the * rule produces the wrong result for a particular name. But if the noun is plural and its * name ends with an 's' we should just add an apostrophe - so we do this via the possEnding * property. * * However, it does vary for pronouns. By default, we check the name to see if it's a * pronoun, and apply the correct pronoun mapping if so. */ possAdj = (ifPronoun(&possAdj, '<<theName>><<possEnding>>')) /* * Get the possessive noun-like form of the name. This is the form * of the possessive we use in a genetive "of" phrase or a "being" * predicate, such as "that's a book of Bob's" or "that book is * Bob's". In English, this is almost always identical to the * possessive adjective form for a regular noun - it's just the same * apostrophe-s word as the adjective form. * * However, it diverges for some of the pronouns: "my" vs "mine", * "her" vs "hers", "their" vs "theirs", "our" vs "ours". We check * the name to see if it's a pronoun, and apply the appropriate * pronoun mapping if so. */ possNoun = (ifPronoun(&possNoun, '<<theName>><<possEnding>>')) /* * The correct ending for our possessive form. This is usually apostrophe-s for an English * noun, except where the noun is plural and ends with an 's', which case we just want an * apostrophe; for example "the clerks' lunch" but "the women's dinner". */ possEnding = (theName.endsWith('s') && plural ? '’' : '’s') /* The subjective-case pronoun for this object. We'll try to infer the pronoun from the * gender and number flags: if plural, 'they'; if isHim, 'he'; if isHer 'she'; otherwise 'it'. */ heName = (pronoun().name) /* * The objective-case pronoun for this object. We'll try to infer * the pronoun from the gender and number flags: if plural, 'them'; * if isHim, 'him'; if isHer 'her'; otherwise 'it'. */ himName = (pronoun().objName) /* * The possessive adjective pronoun for this object. We'll try to * infer the pronoun from the gender and number flags: if plural, * 'their'; if isHim, 'his'; if isHer, 'her'; otherwise * 'its'. */ herName = (pronoun().possAdj) /* * The possessive noun pronoun for this object. We'll try to infer * the pronoun from the gender and number flags: if plural, 'theirs'; * if isHim, 'his'; if isHer, 'hers'; otherwise 'its'. */ hersName = (pronoun().possNoun) /* * The demonstrative pronoun for this object, nominative case. For a * singular gendered object, or a first- or second-person object, * we'll use the regular pronoun (I, you, he, her). For any other * singular object, we'll use 'that', and for plural, 'those'. */ thatName = (pronoun().thatName) /* * The demonstrative pronoun, objective case. For a singular * gendered object, or a first- or second-person object, we'll use * the regular pronoun (me, you, him, her). For any other singular * object, we'll use 'that', and for plural, 'those'. */ thatObjName = (pronoun().thatObjName) /* The reflexive name as a pronoun, e.g. himself, herself, itself */ reflexiveName = (pronoun().reflexive.name) /* * Pronoun-or-name mapper. If our name is a pronoun, return the * given pronoun name property. Otherwise return the given name * string. */ ifPronoun(prop, str) { /* check to see if our name is a pronoun */ local p = LMentionable.pronounMap[name]; if (p != nil) { /* our name is a pronoun - return the pronoun property */ return p.(prop); } else { /* not a pronoun - use the name string */ return str; } } /* * The VocabWords list for empty objects. These are words (usually * adjectives) that can be applied to an object that can be * distinguished from similar objects by its contents ("box of * papers", "bucket of water"), for times when it's empty. This is a * list of VocabWords objects for matching during parsing. * * [Required] */ emptyVocabWords = static [new VocabWord('empty', MatchAdj)] /* * Flag, do we want our theName to be constructed from our owner's name, * e.g. "Bob's wallet" rather than "the wallet". */ ownerNamed = nil /* * Get the definite form of the name, given the name string under * construction. The English default is "the <name>", unless the object * is already qualified, in which case it's just the base name. If, * however, we're ownerNamed and we have a nominalOwner, return our * owner's possessive adjective followed by our name (e.g. "Bob's * wallet"). */ theNameFrom(str) { if(ownerNamed && nominalOwner != nil) return nominalOwner.possAdj + ' ' + str; if (qualified) return str; else return 'the <<str>>'; } /* Determine the gender of this object */ isHim = nil isHer = nil isGenderNeutral = nil /* * By default an object is neuter if it's neither masculine nor feminine, * but that can be overridden in cases where something might be referred * to as either 'him' or 'it' for example. */ isIt = (!(isHim || isHer)) /* * The name with a definite article (or just the proper or qualified name) * followed by the appropriate form of the verb 'to be'. This can be * useful for producing sentences of which this object is the subject. */ theNameIs() { local obj = self; /* * Return theName plus the appropriate conjugation of the verb 'to * be', which we obtain from the conjugateBe() function, which expects * a ctx object as its first parameter; we instead create an inline * object and set its subj property to the current object (which is * all the conjugateBe() function needs). */ return theName + ' ' + conjugateBe(object {subj = obj; }, nil); } /* * Class initialization. The library calls this at preinit time, * before calling construct() on any instances, to set up any * pre-built tables in the class. There's no required implementation * here - this is purely for the language module's convenience to do * any initial set-up work. * * For the English version, we take the opportunity to set up the * main parser Dictionary object, and initialize the plural table. * The plural table is a lookup table we build from the plural list, * for quicker access during execution. * * [Required] */ classInit() { /* initialize the dictionary comparator */ cmdDict.setComparator(Mentionable.dictComp); /* create the lookup table for the plurals and a/an words */ local plTab = irregularPlurals = new LookupTable(128, 256); local anTab = specialAOrAn = new LookupTable(128, 256); /* set up the a/an pattern */ local aAnPat = R'^(a|an)<space>+(.*)$'; /* populate our tables from the CustomVocab objects we find */ forEachInstance(CustomVocab, function(cv) { /* set up the irregular plurals */ for (local lst = cv.irregularPlurals, local i = 1, local len = lst.length() ; i <= len ; i += 2) { /* add the association for singular -> plural */ plTab[lst[i]] = lst[i+1]; /* * also add an association for plural -> plural, in case we * encounter words like 'data' that are already plural */ plTab[lst[i+1][1]] = lst[i+1]; } /* set up the special a/an words */ for (local lst = cv.specialAOrAn, local i = 1, local len = lst.length() ; i <= len ; ++i) { /* parse the entry */ rexMatch(aAnPat, lst[i]); /* * Set up its table entry - 1 for 'a', 2 for 'an'. (We * use an integer rather than storing the full 'a' or * 'an' string to make the table a little smaller * memory-wise. Storing the strings would require an * object per string. Since there are only the two * possibilities, it's easy to store an integer instead, * and it saves a bit of space.) */ anTab[rexGroup(2)[3]] = rexGroup(1)[3] == 'a' ? 1 : 2; } /* we're done with these lists - to save space, forget them */ cv.irregularPlurals = nil; cv.specialAOrAn = nil; }); } /* * Match a pronoun. This returns true if this object is a valid * antecedent for this pronoun grammatically: that is, it matches the * pronoun in gender, number, and any other attributes the pronoun * carries. * * English pronouns have gender and number. (Some other languages * have other attributes, such as animation - whether or not they * refer to living creatures.) * * This routine doesn't tell us if the object is a *current* * antecedent for the pronoun. The current antecedent is a function * of the command history. This routine only tells us whether this * object is a match in terms of grammatical attributes for the * pronoun. * * Note that this routine can and should ignore first-person and * second-person pronouns. Those pronouns are relative to the * speaker, so the parser handles them directly. * * [Required] */ matchPronoun(p) { /* * - If we have plural usage, we'll match Them *. - If we're gendered, we'll match Him or Her as appropriate *. - If we have singular neuter usage, we'll match It */ return (p == Them && (plural || ambiguouslyPlural) || p == Him && isHim || p == Her && isHer || p == It && isIt && (!plural || ambiguouslyPlural)); } /* * Get the pronoun to use for this object. This returns the Pronoun * object suitable for representing this object in a generated * message. * * [Required] */ pronoun() { switch(person) { case 1: return (plural ? Us : Me); case 2: return (plural ? Yall : You); default: return ((plural || isGenderNeutral) ? Them : isHim ? Him : isHer ? Her : It); } } /* The appropriate pronoun for leaving (getting out of) this object */ objOutOfPrep { switch(contType) { case On: return 'off'; case Under: return 'out from under'; case Behind: return 'out from behind'; default: return 'out of'; } } /* * The prepositional phrase for something located inside this object, e.g. * 'in the box' or 'on the table */ objInName = (objInPrep + ' ' + theName) /* * The prepositional phrase for something being moved inside this object, e.g. * 'into the box' or 'onto the table */ objIntoName = (objIntoPrep + ' ' + theName) /* * The pronominal phrase for something leaving this object, e.g. 'out of * the box' */ objOutOfName = (objOutOfPrep + ' ' + theName) /* * initVocab() - Parse the 'vocab' string. This is called during preinit * and on dynamically constructing a new Mentionable, to initialize up the * object's vocabulary for use by the parser. * * The vocab string is designed to make it as quick and easy as possible * to define an object's name and vocabulary. To the extent possible, we * derive the vocabulary from the name, so for many objects the whole * definition will just look like the object name. However, we also make * it possible to define as much extra vocabulary beyond the name as * needed, and to control the way the words making up the name are handled * in terms of their parts of speech. * * The 'vocab' string has this overall syntax: * *. vocab = 'article short name; adjectives; nouns; pronouns' * * You don't have to include all of the parts; you can simply stop when * you're done, so it's valid, for example, to just write the 'short name' * part. It's also fine to include an empty part: if you have extra nouns * to list, but no adjectives, you can say 'short name;;nouns'. * * The 'article' is optional. This can be one of 'a', 'an', 'some', or * '()'. If it's 'a' or 'an', and this differs from what we'd * automatically generate based on the first word of the short name, we * automatically enter the first word into the list of special cases for * a/an words. If it's 'some', we automatically set massNoun=true for the * object. If it's '-', we set qualified=true ('()' means that the name * doesn't take an article at all). * * Note that if you want to use 'a', 'an', 'some', or '()' as the first * word of the actual short name, you simply need to add the desired * article in front of it: 'an a tile from a scrabble set'. * * The short name gives name that we display whenever the parser needs to * show the object in a list, an announcement, etc. * * If the short name consists entirely of capitalized words (that is, if * every word starts with a capital letter), and the 'proper' property * isn't explicitly set for this object, we'll set 'proper' to true to * indicate that this is a proper name. * * We also try to infer the object's vocabulary words from the short name. * We first break off any prepositional phrases, if we see the * prepositions 'to', 'of', 'from', 'with', or 'for'. We then assume that * the FIRST phrase is of the form 'adj adj adj... noun' - that is, zero * or more adjectives followed by a noun; and that the SECOND and * subsequent phrases are entirely adjectives. You can override the * part-of-speech inference by putting the actual part of speech * immediately after a word (with no spaces) in square brackets: 'John[n] * Smith' overrides the assumption that 'John' is an adjective. Use [n] * to make a word a noun, [adj] to make it an adjective, [prep] to make it * a preposition, and [pl] to make it a plural. You can also add [weak] to * make it a weak token (one on which the object won't be matched alone), * or equivalently, enclose the word in parentheses. These annotations * are stripped out of the name when it's displayed. * * We consider ALL of the words in the short name's second and subsequent * phrases (the prepositional phrases) to be adjectives, except for the * preposition words themselves, which we consider to be prepositions. * This is because these phrases all effectively qualify the main phrase, * so we don't consider them as "important" to the object's name. This * helps the parser be smarter about disambiguation, without bothering the * user with clarifying questions all the time. When the player types * "garage", we'll match the "key to the garage" object as well as the * "garage" object, but if both objects are present, we'll know to choose * the garage over the key because the noun usage is a better match to * what the user typed. * * We automatically ignore articles (a, an, the, and some) as vocabulary * words when they immediately follow prepositions in the short name. For * example, in 'key to the garage', we omit 'the' as a vocabulary word for * the object because it immediately follows 'to'. We also omit 'to', * since we don't enter the prepositions as vocabulary. We do the * complementary work on parsing, by ignoring these words when we see them * in the command input in the proper positions. These words are really * structural parts of the grammar rather than parts of the object names, * so the parser can do a better job of recognizing noun phrases by * considering the grammatical functions of these words. * * For many (if not most) objects, the short name won't be enough to state * all of the vocabulary words you want to recognize for the object in * command input. Trying to cram every possible vocabulary word into the * short name would usually make for an unwieldy display name. * Fortunately, it's easy to add input vocabulary words that aren't * displayed in the name. Just add a semicolon, then the adjectives, then * another semicolon, then the nouns. * * Note that there's no section for adding extra prepositions, but you can * still add them. Put the prepositions in the adjective list, and * explicitly annotate each one as a preposition by adding "[prep]" at the * end, as in "to[prep]". * * Next, there's the matter of plurals. For each noun, we'll try to * automatically infer a plural according to the spelling pattern. We also * have a table of common irregular plurals that we'll apply. For * irregular words that aren't in the table, you can override the * spelling-based plural by putting the real plural in braces * immediately after the noun, with no spaces. Start with a hyphen to * specify a suffix; otherwise just write the entire plural word. For * example, you could write 'man{men}' or 'child{-ren}' (although these * particular irregular plurals are already in our special-case list, so * the custom plurals aren't actually needed in these cases). You can use * plural annotations in the short name as well as the extra noun list; * they'll be removed from the short name when it's displayed. We don't * try to generate a plural for a proper noun (a noun that starts with a * capital letter), but you can provide explicit plurals. * * For words longer than the truncation length in the string comparator, * you can set the word to match exactly by adding '=' as the last * character. This also requires exact character matching, rather than * allowing accented character approximations (e.g., matching 'a' in the * input to 'a-umlaut' in the dictionary). * * We automatically assume that plurals should be matched without * truncation. This is because English plurals are usually formed with * suffixes; if the user wants to enter a plural, they'll have to type the * whole word anyway, because that's the only way you make it all the way * to the suffix. You can override this assumption for a given plural by * adding '~' at the end of the plural. This explicitly allows truncated * and character approximation matches. * * Finally, the 'pronouns' section gives a list of the pronouns that this * word can match. You can include 'it', 'him', 'her', and 'them' in this * section. We'll automatically set the isIt, isHim, isHer, and plural * properties to true when we see the corresponding pronouns. * * [Required] */ initVocab() { /* If we're a singular first-person object, our name is 'I' or 'we' */ if(person == 1) name = plural ? 'we' : 'I'; /* If we're a second-person object, our name is 'you' */ if(person == 2) name = 'you'; /* if we don't have a vocab string, there's nothing to do */ if (vocab == nil || vocab == '') return; /* inherit any vocab from our superclasses */ inheritVocab(); /* clear our vocabulary word list */ vocabWords = new Vector(10); /* * get the initial string; we'll break it down as we work * At the same time change any weak tokens of the form * (tok) into tok[weak], so that they're effectively treated * as prepositions (i.e. they won't match alone) */ local str = vocab.findReplace(R'<lparen>.*?<rparen>', {s: s.substr(2, s.length - 2) + (s == '()' ? '()' : '[weak]')}); /* pull out the major parts, delimited by semicolons */ local parts = str.split(';').mapAll({x: x.trim()}); #ifdef __DEBUG if(parts.length > 4) { "<b><FONT COLOR=RED>WARNING!</b></FONT> "; "Too many semicolons in vocab string '<<vocab>>'; there should be a maximum of three separating four different sections.\n"; } #endif /* the first part is the short name */ local shortName = parts[1].trim(); /* * if the short name is all in title case, and 'proper' isn't * explicitly set or we're repacing vocab, mark it as a proper name */ if ((propDefined(&proper, PropDefGetClass) == Mentionable || replacingVocab) && rexMatch(properNamePat, shortName) != nil) proper = true; /* note the tentative name value */ local tentativeName = shortName; /* split the name into individual words */ local wlst = shortName.split(' '), wlen = wlst.length(); /* check for an article at the start of the phrase */ local i = 1; if (wlen > 0 && wlst[1] is in('a', 'an', 'some', 'the', '()')) { /* check which word we have */ switch (wlst[1]) { case 'a': case 'an': /* * if this doesn't match what we'd synthesize by default * from the second word, add the word as a special case */ if (wlen > 1 && aNameFrom(wlst[2]) != '<<wlst[1]>> <<wlst[2]>>') specialAOrAn[wlst[2]] = (wlst[1] == 'a' ? 1 : 2); break; case 'some': /* mark this as a mass noun */ massNoun = true; break; case '()': /* mark this as a qualified name */ qualified = true; break; case 'the': qualified = true; wlst[1] = '!!!&&&'; break; } /* it's a special flag, not a vocabulary word - skip it */ ++i; /* trim this word from the tentative name as well */ tentativeName = tentativeName.findReplace( wlst[1], '', ReplaceOnce).trim(); } /* * If there's no 'name' property already, assign the name from * the short name string. Remove any of the special annotations * for parts of speech or plural forms. */ if (name == nil && tentativeName != '') name = tentativeName.findReplace(deannotatePat, '', ReplaceAll); /* * Process each word in the short name. Assume each is an * adjective except the last word of the first phrase, which we * assume is a noun. We treat everything in a prepositional * phrase (i.e., any phrase beyond the first) as an adjective, * because it effectively modifies the main phrase: a "pile of * paper" is effectively a paper pile; a "key to the front door * of the house" is effectively a front door house key. */ local firstPhrase = true; for ( ; i <= wlen ; ++i) { /* get this word and the next one */ local w = wlst[i].trim(); local wnxt = (i + 1 <= wlen ? wlst[i+1] : nil); /* * If this word is one of our prepositions, enter it without * a part of speech - it doesn't count towards a match when * parsing input, since it's so non-specific, but it's not * rejected either. * * If the *next* word is a preposition, or there's no next * word, this is the last word in a sub-phrase. If this is * the first sub-phrase, enter the word as a noun. * Otherwise, enter it as an adjective. */ local pos; if (rexMatch(prepWordPat, w) != nil) { /* it's a preposition */ pos = MatchPrep; /* * If the next word is an article, skip it. Articles in * the name phrase don't count as vocabulary words, since * the parser strips these out when matching objects to * input. (That doesn't mean the parser ignores * articles, though. It parses them in input and * respects the meaning they convey, but it does that * internally, sparing the object name matcher the * trouble of dealing with them.) */ if (wnxt is in ('a', 'an', 'the', 'some')) ++i; } else if (rexMatch(weakWordPat, w) != nil) { /* It's a weak token */ pos = MatchWeak; } else if (firstPhrase && (wnxt == nil || rexMatch(prepWordPat, wnxt) != nil)) { /* it's the last word in the first phrase - it's a noun */ pos = MatchNoun; /* we've just left the first phrase */ firstPhrase = nil; } else { /* anything else is an adjective */ pos = MatchAdj; } /* enter the word under the part of speech we settled on */ initVocabWord(w, pos); } /* the second section is the list of adjectives */ if (parts.length() >= 2 && parts[2] != '') { parts[2].split(' ').forEach( {x: initVocabWord(x.trim(), MatchAdj)}); #ifdef __DEBUG /* * If we're compiling for debugging, issue a warning if a pronoun * appears in the adjective section. We exclude 'her' from the * list of pronouns we test for here since 'her' in the adjective * section could be intended as the female possessive pronoun. But * only carry out this check for a Thing, since a Topic might * legally have pronouns in any section. */ parts[2].split(' ').forEach(function(x){ if(ofKind(Thing) && x is in ('him', 'it', 'them', 'them!')) { "<b><FONT COLOR=RED>WARNING!</FONT></B> "; "Pronoun '<<x>>' appears in adjective section (after first semicolon) of vocab string '<<vocab>>'. This may mean the vocab string has too few semicolons.\n"; } }); #endif } /* the third section is the list of nouns */ if (parts.length() >= 3 && parts[3] != '') { parts[3].split(' ').forEach( {x: initVocabWord(x.trim(), MatchNoun)}); #ifdef __DEBUG /* * If we're compiling for debugging, issue a warning if a pronoun * appears in the noun section. */ parts[3].split(' ').forEach(function(x){ if(ofKind(Thing) && x is in ('him', 'her', 'it', 'them', 'them!')) { "<b><FONT COLOR=RED>WARNING!</FONT></B> "; "Pronoun '<<x>>' appears in noun section (after second semicolon) of vocab string '<<vocab>>'. This probably mean this vocab string has too few semicolons.\n"; } }); #endif } /* the fourth section is the list of pronouns */ if (parts.length() >= 4 && parts[4] != '') { local map = ['it', &isIt, 'him', &isHim, 'her', &isHer, 'them', &plural, 'them!', &isGenderNeutral ]; local explicitlySingular = nil; parts[4].split(' ').forEach(function(x) { local i = map.indexOf(x.trim()); if (i != nil) self.(map[i+1]) = true; if(x.trim() != 'them') explicitlySingular = true; }); /* * If we're both explicitly singular and plural (i.e. both a * singular pronoun and 'them' have appeared in our pronoun * list) we must be ambiguously plural. */ if(explicitlySingular && plural) { ambiguouslyPlural = true; /* * We're actually plural only if 'them' is the first * pronoun encountered; so if it's not we're actually * singular. */ if(!parts[4].trim().startsWith('them')) plural = nil; } #ifdef __DEBUG /* * If we're compiling for debugging, issue a warning if a * something other than a pronoun appears in the pronoun section. */ parts[4].split(' ').forEach(function(x){ if(x not in ('him', 'her', 'it', 'them', 'them!')) { "<b><FONT COLOR=RED>WARNING!</FONT></B> "; "Non-Pronoun '<<x>>' appears in pronoun section (after third semicolon) of vocab string '<<vocab>>'. Check that this vocab string doesn't have too many semicolons.\n"; } }); #endif } /* turn vocabWords back into a list */ vocabWords = vocabWords.toList(); } /* * pattern for detecting a proper name - each word starts with a * capital letter */ properNamePat = R'(<upper><^space>*)(<space>+<upper><^space>*)*' /* * Inherit vocab from our superclasses according to the following scheme: *. 1. A + sign in the name section will be replaced with the name from our * superclass. *. 2 Unless the adjective and nouns section start with a -, any * adjectives and nouns from our superclasses vocab will be added to the * respective section. *. 3 If our pronouns section is empty or contains a +, inherit pronouns * from our superclass, otherwise leave it unchanged. */ inheritVocab() { /* * If we don't have any vocab, there's no work to do. */ if(vocab == nil || vocab == '') return; foreach(local cls in getSuperclassList) { /* * If the superclass doesn't have any vocab, there's nothing more * we need do with it. Otherwise Ensure that our superclasses have * inherited any vocab they need to before we try to inherit from * them. */ if(cls.vocab not in (nil, '')) cls.inheritVocab(); } /* * If we don't define our own vocab property directly, there's no more * work to do; it's all been done on our parent classes. There's also * no more work to do if none of our parent classes defines any vocab. */ if(!propDefined(&vocab, PropDefDirectly) || getSuperclassList.indexWhich({c: c.vocab not in (nil, '')}) == nil) return; /* Our list of vocab, split into parts. */ local vlist = vocab.split(';').mapAll({x: x.trim()}); /* for convenience, make sure we end up with four parts */ for(local i = vlist.length; i < 4; i++) vlist += ''; foreach(local cls in getSuperclassList) { /* * If this class doesn't specify any vocab, we don't need to * process it. */ if(cls.vocab is in (nil, '')) continue; /* The inherited vocab, split into parts */ local ilist = cls.vocab.split(';').mapAll({x: x.trim()}); /* For convenience, make sure we have four parts. */ for(local i = ilist.length; i < 4; i++) ilist += ''; /* Replace any + sign in the first part with the inherited name */ vlist[1] = vlist[1].findReplace('+', ilist[1]); /* * For the second and third parts, unless they atart with - add in * the inherited adjectives and nouns. */ for(local i in 2..3) { if(!vlist[i].startsWith('-')) vlist[i] = vlist[i] + ' ' + ilist[i]; } /* * For the 4th (pronoun) part, add any inherited pronouns only if * we don't have any of our own or there's a + in the pronoun part */ if((vlist[4] == '' || vlist[4].find('+') != nil) && ilist[4] != '') vlist[4] = vlist[4] + ' ' + ilist[4]; } /* Strip out any leading - in parts 2 and 3 */ for(local i in 2..3) { if(vlist[i].startsWith('-')) vlist[i] = vlist[i].substr(2); } /* Strip out any + in part 4 */ vlist[4] = vlist[4].findReplace('+', ''); /* Join the list back into a vocab string */ vocab = vlist.join(';'); } /* * Initialize vocabulary for one word from the 'vocab' list. 'w' is * the word text, with optional part-of-speech and plural-form * annotations ([n], [pn], [adj], [prep], [pl], (-s)). It can also have a * special flag character as its final character: '=' for an exact * match (no truncation and no character approximations), or '~' for * fuzzy matches (truncation and approximation allowed). * * 'matchFlags' is a combination of MatchXxx values. This should * minimally provide the part of speech as one of MatchAdj, * MatchNoun, or MatchPlural. You can also include MatchNoTrunc to * specify that user input can only match this word without any * truncation, and MatchNoApprox to specify that input can only match * without character approximations (e.g., 'a' matching 'a-umlaut'). */ initVocabWord(w, matchFlags) { /* presume this will be entered in the dictionary as a noun */ local partOfSpeech = &noun; /* * if there's an explicit part-of-speech annotation, it overrides * the assumed part of speech */ if (w.find(posPat) != nil) { /* clear the old part-of-speech flags */ matchFlags &= ~(MatchPrep | MatchAdj | MatchNoun | MatchPlural | MatchWeak); local ann = rexGroup(1)[3]; /* note the new part of speech */ switch (ann) { case 'n': case 'pn': matchFlags |= MatchNoun; break; case 'adj': matchFlags |= MatchAdj; break; case 'weak': matchFlags |= MatchWeak; break; case 'prep': matchFlags |= MatchPrep; break; case 'pl': matchFlags |= MatchPlural; break; } /* strip the annotation from the word string */ w = w.findReplace(posPat, '', ReplaceOnce); /* If w has been marked as a plural noun, add it to our plural tokens. */ if(ann == 'pn') pluralTokens = valToList(pluralTokens).appendUnique([w]); } /* * If we're compiling for debug, try to warn the author of any illegal * part-of-speech tags that may have inadvertently crept into a vocab * string. */ #ifdef __DEBUG else if(w.find(R'<lsquare>.*<rsquare>') != nil && !w.endsWith('s')) { "<B><FONT COLOR=RED>WARNING!</FONT></B> "; "Illegal part of speech tag for '<<w>>' in vocab string '<<vocab>>'\n"; } #endif /* * if this is an adjective ending in apostrophe-S, use that form * of the word */ // if (rexMatch(apostSPat, w)) // { // /* note the existing value of w*/ // local wOld = w; // // /* // * strip off the apostrophe-S, since the tokenizer will do // * this when parsing the input // */ // w = rexGroup(1)[3]; // // /* mark it as an apostrophe-S word in the dictionary */ // partOfSpeech = &nounApostS; // // w += '^s'; // // apostropheSPreParser.addEntry(wOld, w); // } if (rexMatch(apostSPat, w)) { /* mark it as an apostrophe-S word in the dictionary */ partOfSpeech = &nounApostS; /* * Add a dictionary word without the apostrophe-S, since the * tokenizer will separate the apostrophe-S as a separate token, * hence we need to be able to look up the base word in the * dictionary. But then proceed with our regular handling with * the full word as well, since the parser will reassemble the * full token with the apostrophe-S for the actual noun phrase * matching. */ cmdDict.addWord(dictionaryPlaceholder, rexGroup(1)[3], partOfSpeech); } /* if there's a plural annotation, note it */ if ((matchFlags & MatchPlural) != 0 || partOfSpeech == &nounApostS) { /* * Either this is already marked as a plural, or it has an * apostrophe-S. In either case, it's already as inflected * as an English word can get, so we don't want to try * inflecting it further with a plural formation. */ } else if (w.find(pluralPat) != nil) { /* there's an explicit plural - retrieve it */ local pl = rexGroup(1)[3]; /* strip it out of the string */ w = w.findReplace(pluralPat, '', ReplaceOnce); /* split out the list elements, if there are multiple entries */ pl = pl.split(',').mapAll({x: x.trim()}); /* for each plural given as a suffix, append it to the word */ pl = pl.mapAll({x: x.startsWith('-') ? w + x.substr(2) : x}); /* add each plural; assume truncation isn't allowed */ pl.forEach({x: initVocabWord(x, MatchPlural | MatchNoTrunc)}); } else if (matchFlags == MatchNoun && rexMatch(properPat, w) == nil) { /* * it's a noun, it's not a proper noun, and no plural form * was explicitly provided, so infer one */ local pl = [pluralWordFrom(w, '\'')]; /* * if it's an acronym or number, add the apostrophe-s plural * as an alternative (1990's, LCD's) */ if (rexMatch(acronymPluralPat, w) != nil || w.length() == 1) pl += ['<<w>>\'s']; /* * If it's an irregular plural form, add any variations. * (The first irregular will have already been picked up, so * we only need to add the second and other variations here.) */ local irr; if ((irr = irregularPlurals[w]) != nil && irr.length() > 1) pl += irr.sublist(2); /* * Remove the original word from the plural list, if it's * there. Some words are their own plurals, such as "fish" * or "sheep". Practically speaking, it's better to treat * these words as singular in the parser. It's easy to * explicitly make a phrase plural: you just put ALL in front * of it (PUT ALL FISH IN BOWL). But there's no converse; if * we treated FISH as plural, there'd be no good way to * singularize it when you just wanted to talk about one * fish. * * (Note that we have to remove these words here. We don't * want to strip them out of the irregular plural list, * because we also use that list to synthesize plural names, * and we certainly want the correct word for that purpose. * We also don't want to strip them out in initVocabWord(), * because the game might call that to explicitly add a * plural that matches a noun. The correct place to remove * them is right here, because we only want to remove them * from implicitly generated lists of vocabulary plurals.) */ pl -= w; /* add each plural; assume truncation isn't allowed */ pl.forEach({x: initVocabWord(x, MatchPlural | MatchNoTrunc)}); } /* check for exact and inexact flags */ if (w.endsWith('=')) { matchFlags |= MatchNoTrunc | MatchNoApprox; w = w.left(-1); } else if (w.endsWith('~')) { matchFlags &= ~(MatchNoTrunc | MatchNoApprox); w = w.left(-1); } /* add this word to the dictionary and to our part-of-speech list */ addDictWord(w, partOfSpeech, matchFlags); } /* pattern for apostrophe-s words */ apostSPat = R'^(.*)(\'|’|\u2019)s$' /* * Add a word to the dictionary and to our vocabulary list for the * given match flags. */ addDictWord(w, partOfSpeech, matchFlags) { /* for dictionary purposes, we want everything in lower case */ w = w.toLower(); /* * Add it to the dictionary. Note that our parser doesn't use * the dictionary's object association feature; but we do need * *some* object for the entry, so use the dictionary placeholder * object as the associated object. Using the placeholder * minimizes the dictionary's memory needs by creating only one * entry for each word. */ cmdDict.addWord(dictionaryPlaceholder, w, partOfSpeech); /* add it to our internal vocabulary list */ vocabWords = vocabWords.append(new VocabWord(w, matchFlags)); } /* * reinitialize the vocab of this object from scratch, using the string * voc in place of the original vocab property. */ replaceVocab(voc) { /* clear out the existing name */ name = nil; /* Reset to nil the various properties that may be set by initVocab() */ proper = nil; /* * Note that we're replacing vocab so that initVocab can set proper to true if appropriate */ replacingVocab = true; /* Reset other vocab properties to nil. */ plural = nil; ambiguouslyPlural = nil; isHim = nil; isHer = nil; isGenderNeutral = nil; massNoun = nil; /* Restore the default expression for qualified. */ setMethod(&qualified, {: proper }); /* Restore the default expression for isIt. */ setMethod(&isIt, { : !(isHim || isHer || isGenderNeutral)} ); /* Set our vocab property to the vocab we're replacing it with */ vocab = voc; /* Clear out any existing vocabWords */ vocabWords = []; /* Initialize the vocab again */ initVocab(); } /* * Flag for internal use only; are we replacingVocab? The replaceVocab() method sets this to * true for use by initVocab(). */ replacingVocab = nil /* * Add additional vocab words to those already in use for this object. If * we specify the name part this will replace the existing name for the * object. */ addVocab(voc) { /* * First check if we have anything in the name part. If so, assume * that it's meant to replace the existing name. */ local parts = voc.split(';'); if(parts[1] > '') name = nil; /* * Make a note of the existing vocabWords, since they'll be overridden * when we add the new ones. */ local vocWords = vocabWords; /* Copy the new vocab string to our vocab property. */ vocab = voc; /* Initialize our vocabWords using the new string. */ initVocab(); /* Add back our old vocabWords, without any duplicates */ vocabWords = vocabWords.appendUnique(vocWords); } /* * Remove a word from this object's vocabulary. If the matchFlags * parameter is supplied it should be one of MatchNoun, MatchAdj, * MatchPrep or MatchPlural, in which case only VocabWords matching the * corresponding part of speech (as well as word) will be removed. */ removeVocabWord(word, matchFlags?) { if(matchFlags) vocabWords = vocabWords.subset({v: v.wordStr != word || v.posFlags != matchFlags}); else vocabWords = vocabWords.subset({v: v.wordStr != word}); } addVocabWord(word, matchFlags) { initVocabWord(word, matchFlags); } /* * Regular expression pattern for matching a single preposition word. * A word is a preposition if it's in our preposition list, OR it's * annotated explicitly with "[prep]" at the end. */ prepWordPat = static new RexPattern( '^(<<prepList>>)$|.*<lsquare>prep<rsquare>$') weakWordPat = static new RexPattern( '.*<lsquare>weak<rsquare>$') /* preposition list, as a regular expression OR pattern */ prepList = 'to|of|from|with|for' /* regular expression for removing annotations from a short name */ deannotatePat = R"<lsquare><alpha>+<rsquare>|<lbrace><alphanum|-|'|,|~|=>+<rbrace>" /* pattern for part-of-speech annotations */ posPat = R'<lsquare>(n|pn|adj|pl|prep|weak)<rsquare>' /* pattern for plural annotations */ pluralPat = R"<lbrace>(<alphanum|-|'|space|,|~|=>+)<rbrace>" /* * pattern for proper nouns: starts with a capital, and at least one * lower-case letter within */ properPat = R'^<upper>(.*<lower>.*)' /* * Generate the "distinguished name" for this object, given a list of * Distinguisher objects that we're using to tell it apart from * others in a list. * * 'article' indicates which kind of article to use: Definite * ("the"), Indefinite ("a", "an", "some"), or nil (no article). * 'distinguishers' is a list of Distinguisher object that are being * used to identify this object uniquely. Our job is to elaborate * the object's name with all of the qualifying phrases implied by * the distinguishers. * * [Required] */ distinguishedName(article, distinguishers) { local ret; /* note which distinguishers are present */ local dis = distinguishers.indexOf(disambigNameDistinguisher) != nil; local poss = distinguishers.indexOf(ownerDistinguisher) != nil; local loc = distinguishers.indexOf(locationDistinguisher) != nil; local cont = distinguishers.indexOf(contentsDistinguisher) != nil; /* * start with the core name: this is either the basic name or the * disambiguation name, depending on whether there's a * disambigNameDistinguisher in the list */ ret = (dis ? disambigName : name); /* add any state adjectives */ foreach (local d in distinguishers) { if (d.ofKind(StateDistinguisher)) ret = d.state.addToName(self, ret); } /* add the possessive, if it's not a qualified name */ if (poss && !qualified) { local o = nominalOwner(); if (o != nil) ret = nominalOwner().possessify(article, self, ret); // If this object is unowned and no locational distinguisher is present // and the object is lying on the ground, we still need to describe it. else if (!loc && self.location == gActor.getOutermostRoom()) { ret = self.location.locify(self, ret); } } /* add the contents qualifier */ if (cont) { local c = nominalContents(); if (c != nil) ret = c.contify(self, ret); else ret = 'empty <<ret>>'; } /* add the locational qualifier */ if (loc) { if (location != nil) ret = location.locify(self, ret); } /* * If an article is desired, add it. Exception: don't add a * definite article if there's a possessive, since the possessive * takes the place of the definite article. */ if (article == Indefinite) ret = (poss ? aNameFromPoss(ret) : aNameFrom(ret)); else if (article == Definite && !poss) ret = theNameFrom(ret); /* return the result */ return ret; } /* * Generate a possessive name for an object that we own, given a * string under construction for the object's name. 'obj' is the * object we're possessing ('self' is the owner), and 'str' is the * name string under construction, without any possessive or article * qualifiers yet. * * Note that we must add to 'str', not the base name of the object. * We might be using a variation on the name (such as the * disambiguation name), or we might have already adorned the name * with other qualifiers. * * 'article' specifies the usage: Definite, Indefinite, or nil for no * article. We DON'T actually add the article here; rather, this * tells us the form that the name will take when the caller is done * with it, so we should use a suitable form of the possessive * phrasing to the extent it varies by article. In English, it does * vary. In the Definite case, the possessive effectively replaces * the article: "the book" becomes "Bob's book". In the Indefinite * case, the possessive has to be rephrased prepositionally so that * the article can still be included: "a book" becomes "a book of * Bob's". Mass nouns are a further special case: "some water" * becomes "some of Bob's water". * * The default behavior is as follows. In Definite mode, we return * "<name>'s <string>". In Indefinite mode, we return "<string> of * <name>" (for a final result like "a book of Bob's"). */ possessify(article, obj, str) { /* * Definite: book -> Bob's book -> Bob's book *. No article: book -> Bob's book -> Bob's book *. Indefinite mass noun: water -> Bob's water -> some of Bob's water *. Indefinite count noun: book -> book of Bob's -> a book of Bob's */ if (article is in (Definite, nil) || (article == Indefinite && obj.massNoun)) return '<<possAdj>> <<str>>'; else return '<<str>> of <<possNoun>>'; } /* * Apply a locational qualifier to the name for an object contained * within me. 'obj' is the object (something among my contents), and * 'str' is the name under construction. We'll add the appropriate * prepositional phrase: "the box UNDER THE TABLE". */ locify(obj, str) { if (obj.location == gActor.getOutermostRoom() && obj.location.floorObj != nil) return '<<str>> <<obj.location.floorObj.objInName>> '; else return '<<str>> <<obj.locType.prep>> <<theName>>'; } /* * Apply a contents qualifier to the name for my container. 'obj' is * the object (my container), and 'str' is the name under * construction. We'll add the appropriate prepositional phrase: * "the bucket OF WATER". */ contify(obj, str) { return '<<str>> of <<name>>'; } /* * Apply an indefinite article ("a box", "an orange", "some lint") to * the given name string 'str' for this object. We'll try to figure * out which indefinite article to use based on what kind of noun * phrase we use for our name (singular, plural, or a "mass noun" * like "lint"), and our spelling. * * By default, we'll use the article "a" if the name starts with a * consonant, or "an" if it starts with a vowel. * * If the name starts with a "y", we'll look at the second letter; if * it's a consonant, we'll use "an", otherwise "a" (hence "an yttrium * block" but "a yellow brick"). * * If the object is marked as a mass noun or having plural usage, we * will use "some" as the article ("some water", "some shrubs"). If * the string has a possessive qualifier, we'll make that "some of" * instead ("some of Bob's water"). * * Some objects will want to override the default behavior, because * the lexical rules about when to use "a" and "an" are not without * exception. For example, silent-"h" words ("honor") are written * with "an", and "h" words with a pronounced but weakly stressed * initial "h" are sometimes used with "an" ("an historian"). Also, * some 'y' words might not follow the generic 'y' rule. * * 'U' words are especially likely not to follow any lexical rule - * any 'u' word that sounds like it starts with 'y' should use 'a' * rather than 'an', but there's no good way to figure that out just * looking at the spelling (consider "unassuming", " unimportant * word", or "a unanimous decision" and "an unassuming man"). We * simply always use 'an' for a word starting with 'u', but this will * have to be overridden when the 'u' sounds like 'y'. */ aNameFrom(str) { /* remember the original source string */ local inStr = str; /* * The complete list of unaccented, accented, and ligaturized * Latin vowels from the Unicode character set. (The Unicode * database doesn't classify characters as vowels or the like, * so it seems the only way we can come up with this list is * simply to enumerate the vowels.) * * These are all lower-case letters; all of these are either * exclusively lower-case or have upper-case equivalents that * map to these lower-case letters. * * (Note an implementation detail: the compiler will append all * of these strings together at compile time, so we don't have * to perform all of this concatenation work each time we * execute this method.) * * Note that we consider any word starting with an '8' to start * with a vowel, since 'eight' and 'eighty' both take 'an'. */ local vowels = '8aeiou\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6' + '\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF' + '\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8\u00F9\u00FA' + '\u00FB\u00FC\u0101\u0103\u0105\u0113\u0115\u0117' + '\u0119\u011B\u0129\u012B\u012D\u012F\u014D\u014F' + '\u0151\u0169\u016B\u016D\u016F\u0171\u0173\u01A1' + '\u01A3\u01B0\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8' + '\u01DA\u01DC\u01DF\u01E1\u01E3\u01EB\u01ED\u01FB' + '\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B' + '\u020D\u020F\u0215\u0217\u0254\u025B\u0268\u0289' + '\u1E01\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E2D\u1E2F' + '\u1E4D\u1E4F\u1E51\u1E53\u1E73\u1E75\u1E77\u1E79' + '\u1E7B\u1E9A\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB' + '\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB' + '\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB' + '\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB' + '\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB' + '\u1EED\u1EEF\u1EF1\uFF41\uFF4F\uFF55'; /* * A few upper-case vowels in unicode don't have lower-case * mappings - consider them separately. */ local vowelsUpperOnly = '\u0130\u019f'; /* * the various accented forms of the letter 'y' - these are all * lower-case versions; the upper-case versions all map to these */ local ys = 'y\u00FD\u00FF\u0177\u01B4\u1E8F\u1E99\u1EF3' + '\u1EF5\u1EF7\u1EF9\u24B4\uFF59'; /* if the name is already qualified, don't add an article at all */ if (qualified) return str; /* if it's plural or a mass noun, use "some" as the article */ if (plural || massNoun) { /* use "some" as the article */ return 'some <<str>>'; } else { local firstChar; local firstCharLower; /* if it's empty, just use "a" */ if (inStr == '') return 'a'; /* * if the first word is in our special-case list, use the special * case handling */ local sc; if (rexMatch(firstWordPat, str) && (sc = specialAOrAn[rexGroup(1)[3]]) != nil) return (sc == 1 ? 'a ' : 'an ') + str; /* get the first character of the name */ firstChar = inStr.substr(1, 1); /* skip any leading HTML tags */ if (rexMatch(tagOrQuotePat, firstChar) != nil) { /* * Scan for tags. Note that this pattern isn't quite * perfect, as it won't properly ignore close-brackets * that are inside quoted material, but it should be good * enough for nearly all cases in practice. In cases too * complex for this pattern, the object will simply have * to override aDesc. */ local len = rexMatch(leadingTagOrQuotePat, inStr); /* if we got a match, strip out the leading tags */ if (len != nil) { /* strip off the leading tags */ inStr = inStr.substr(len + 1); /* re-fetch the first character */ firstChar = inStr.substr(1, 1); } } /* get the lower-case version of the first character */ firstCharLower = firstChar.toLower(); /* * if the first word of the name is only one letter long, * treat it specially */ if (rexMatch(oneLetterWordPat, inStr) != nil) { /* * We have a one-letter first word, such as "I-beam" or * "M-ray sensor", or just "A". Choose the article based * on the pronunciation of the letter as a letter. */ return (rexMatch(oneLetterAnWordPat, inStr) != nil ? 'an ' : 'a ') + str; } /* * look for the first character in the lower-case and * upper-case-only vowel lists - if we find it, it takes * 'an' */ if (vowels.find(firstCharLower) != nil || vowelsUpperOnly.find(firstChar) != nil) { /* it starts with a vowel */ return 'an <<str>>'; } else if (ys.find(firstCharLower) != nil) { local secondChar; /* get the second character, if there is one */ secondChar = inStr.substr(2, 1); /* * It starts with 'y' - if the second letter is a * consonant, assume the 'y' is a vowel sound, hence we * should use 'an'; otherwise assume the 'y' is a * diphthong 'ei' sound, which means we should use 'a'. * If there's no second character at all, or the second * character isn't alphabetic, use 'a' - "a Y" or "a * Y-connector". */ if (secondChar == '' || rexMatch(alphaCharPat, secondChar) == nil || vowels.find(secondChar.toLower()) != nil || vowelsUpperOnly.find(secondChar) != nil) { /* * it's just one character, or the second character * is non-alphabetic, or the second character is a * vowel - in any of these cases, use 'a' */ return 'a <<str>>'; } else { /* the second character is a consonant - use 'an' */ return 'an <<str>>'; } } else if (rexMatch(elevenEighteenPat, inStr) != nil) { /* * it starts with '11' or '18', so it takes 'an' ('an * 11th-hour appeal', 'an 18-hole golf course') */ return 'an <<str>>'; } else { /* it starts with a consonant */ return 'a <<str>>'; } } } /* * Get the indefinite name for a version of our name that has a * possessive qualifier. The caller is responsible for ensuring that * the possessive is already in a suitable format for adding an * indefinite article - usually something like "book of Bob's", so * that we can turn this into "a book of Bob's". * * In English, there's a special case where the regular indefinite * name format differs from the possessive format, which is why we * need this separate method in the English module. Specifically, if * the basic name is a plural or mass noun, we have to use "some of" * in the possessive case, rather than the usual "some": "some water" * in the normal case, but "some of Bob's water" in the possessive * case. */ aNameFromPoss(str) { /* * for mass nouns and plurals, use "some of"; otherwise use the * normal a-name */ return (massNoun || plural ? 'some of <<str>>' : aNameFrom(str)); } /* * lookup table of special-case a/an words (we build this * automatically during classInit from CustomVocab objects) */ specialAOrAn = nil /* pre-compile some regular expressions for aName */ tagOrQuotePat = R'[<"\']' leadingTagOrQuotePat = R'(<langle><^rangle>+<rangle>|"|\')+' firstWordPat = R'(?:<langle><^rangle>+<rangle>|"|\'|<space>)*(<alphanum>+)%>' oneLetterWordPat = R'<alpha>(<^alpha>|$)' oneLetterAnWordPat = R'<nocase>[aefhilmnorsx]' alphaCharPat = R'<alpha>' elevenEighteenPat = R'1[18](<^digit>|$)' /* * Get the plural form of the given name. If the string ends in * vowel-plus-'y' or anything other than 'y', we'll add an 's'; * otherwise we'll replace the 'y' with 'ies'. We also handle * abbreviations and individual letters specially. * * This can only deal with simple adjective-noun forms. For more * complicated forms, particularly for compound words, it must be * overridden (e.g., "Attorney General" -> "Attorneys General", * "man-of-war" -> "men-of-war"). We recognize a fairly extensive * set of special cases (child -> children, man -> men), as listed in * the irregularPlural lists in any CustomVocab objects. Add new * items to the irregular plural list by creating one or more * CustomVocab objects with their own irregularPlural lists. */ pluralNameFrom(str) { local str2; /* check for a 'phrase prep phrase' format */ if (str.find(prepPhrasePat) != nil) { /* * Pull out the two parts - the part up to the 'of' is the * part we'll actually pluralize, and the rest is a suffix * we'll stick on the end of the pluralized part. */ str = rexGroup(1)[3]; str2 = rexGroup(2)[3]; /* * now pluralize the part up to the 'of' using the normal * rules, then add the rest back in at the end */ return pluralNameFrom(str) + str2; } /* parse out the last word */ rexMatch(lastWordPat, str); /* pluralize the last word */ str = rexGroup(1)[3]; str2 = rexGroup(2)[3]; return str + pluralWordFrom(str2, '’'); } /* * if we need to add 's as the plural ending - this should be either * a simple straight quote ('\''), or HTML markup for a curly quote * regular expression for separating the main phrase and * prepositional phrase from a "noun prep noun" phrase */ prepPhrasePat = static new RexPattern( '^(.+)(<space>+(<<prepList>>)<space>+.+)$') /* pattern for pulling the last word out of a phrase */ lastWordPat = R'^(.*?)(<^space>*)<space>*$' /* * Get the plural of the given word. If there's an irregular plural * entry for the word, we return that; otherwise we infer the plural * from the spelling. 'apost' is the string to use for an apostrophe * ('’'). */ pluralWordFrom(str, apost) { local irr; local len; /* * Check for irregulars from our table. If we find an entry, use * the first plural in the list, since it's the preferred form in * cases where there are multiple variations (e.g., indices vs. * indexes). */ if ((irr = irregularPlurals[str]) != nil) return irr[1]; /* check for words ending in 'man' */ if (rexMatch(menPluralPat, str)) return '<<rexGroup(1)[3]>>men'; /* if the string is empty, return empty */ len = str.length(); if (len == 0) return ''; /* * Certain plurals are formed with apostrophe-s. This applies to * single lower-case letters; certain upper-case letters that * would be ambiguous without the apostrophe, because they'd * otherwise look like words or common abbreviations (As, Es, Is, * Ms, Us, Vs); and words made up of initials with periods * between each letter. */ if (rexMatch(apostPluralPat, str) != nil) return '<<str>><<apost>>s'; /* for any other single letter, return the letter + 's' */ if (len == 1) return '<<str>>s'; /* for all-capital words (CPA, PC) or numbers, just add -s */ if (rexMatch(acronymPluralPat, str) != nil) return '<<str>>s'; /* check for -es plurals */ if (rexMatch(esPluralPat, str) != nil) return '<<str>>es'; /* check for 'y' -> 'ies' plurals */ if (rexMatch(iesPluralPat, str) != nil) return '<<rexGroup(1)[3]>>ies'; /* for anything else, just add -s */ return '<<str>>s'; } /* lookup table for irregular plurals - we build this at preinit time */ irregularPlurals = nil /* regular expression for trimming leading and trailing spaces */ trimPat = R'^<space>+|<space>+$' /* pattern for nouns with -es plurals */ esPluralPat = R'^.*((?!o)o|u|sh|ch|ss|z|x|us)$' /* pattern for nouns y -> -ies plurals) */ iesPluralPat = R'<case>^((?![A-Z]).*[^aeoAEO])y$' /* pattern for words ending in 'men' (chairman, fireman, etc) */ menPluralPat = R'^(.*)man$' /* pattern for plurals that add apostrophe-s */ apostPluralPat = R'<case>^(<lower|A|E|I|M|U|V>|(<alpha><dot>)+)$' /* pattern for acronyms and numbers */ acronymPluralPat = R'<case>^<upper|digit>+$' /* class property: the main dictionary StringComparator */ dictComp = static new StringComparator(truncationLength, nil, nil) /* * class property: the truncation length to use for the main dictionary * StringComparator. */ truncationLength = 8 /* class property: pronoun lookup table (built during preinit) */ pronounMap = nil /* * The dummyName is a property that displays nothing, for use when we want * to use an object in a sentence without actually displaying any text for * it (e.g. to provide a subject for a verb to agree with). */ dummyName = '' /* * If we're ambiguously plural (meaning we can be referred to by either singular or plural * noun(s)) we can list the plural nouns in pluralToken so we can see whether one of them was * used in the player's input referring to us. Alternatively we can annotate any such plural * nouns in our vocab property by annotating it with [pn] and the library will add to our * pluralTokens list for us, e.g.: * * Decoration 'leaves[pn]; abundant; foliage; them it' ; */ // pluralTokens = [] /* * Did the player use one of our plural tokens when they referred to us on this turn? Game * code can call this method to check whether we were referred to by a plural or singular name * and adjust our response accordingly. */ usedPluralToken() { return propDefined(&pluralTokens) && valToList(pluralTokens).overlapsWith(gCommand.verbProd.tokenList.mapAll({x: getTokVal(x)})); } ; modify SubComponent /* * In addition to the properties copied on the original definition of this * class in his method, we need to copy all the other name related * properties of the parent object that game authors might conceivably * customize. */ nameAs(parent) { /* Carry out the inherited handling. */ inherited(parent); /* Then copy all the other name-related properties. */ aName = parent.aName; theName = parent.theName; theObjName = parent.theObjName; objName = parent.objName; possAdj = parent.possAdj; possNoun = parent.possNoun; // objInName = parent.objInName; // objIntoName = parent.objIntoName; // objOutOfName = parent.objOutOfName; } ; /* ------------------------------------------------------------------------ */ /* * LState is the language-specific base class for State objects. * * [Required] */ class LState: object /* * Add the state name to an object name under construction. 'obj' is * the object, and 'str' the object name being built. This adds the * appropriate adjective for the state to the name. * * [Required] */ addToName(obj, str) { /* get the adjective list entry for the object's current state */ local st = obj.(stateProp); local adj = adjectives.valWhich({ ele: ele[1] == st }); /* add it to the name */ return '<<adj[2][1]>> <<str>>'; } /* * Initialize a state adjective. The base library calls this during * preinit for each word, given as a string. The language module * must define this routine, but it doesn't have to do anything. The * English version adds the word to the dictionary, so that the * spelling corrector can recognize it. * * [Required] */ initWord(w) { /* add it to the dictionary */ cmdDict.addWord(dictionaryPlaceholder, w, &noun); } /* * Additional info to be added to the item name when it appears in a * listing and is in the corresponding state */ additionalInfo = [] /* * Get the string providing additional info about this object when it's in * a particular state (such as '(providing light)', the only additional * state info actually defined in the English library) */ getAdditionalInfo(obj) { /* get the info list entry for the object's current state */ local st = obj.(stateProp); local info = additionalInfo.valWhich({ ele: ele[1] == st }); return info != nil ? info[2] : ''; } ; /* ------------------------------------------------------------------------ */ /* * Common object states. The objects themselves are cross-language and * thus are required, but the base library leaves it up to the language * modules to provide the actual definitions, since the body of each * definition is mostly vocabulary words. */ /* * Lit/Unlit state. This is useful for light sources and flammable * objects. * * [Required] */ LitUnlit: State stateProp = &isLit adjectives = [[nil, ['unlit']], [true, ['lit']]] appliesTo(obj) { return obj.isLightable || obj.isLit; } additionalInfo = [[true, ' (providing light)']] ; /* * Open/Closed state. This is useful for things like doors and * containers. */ OpenClosed: State stateProp = &isOpen adjectives = [[nil, ['closed']], [true, ['open']]] appliesTo(obj) { return obj.isOpenable; } ; /* * DirState. This is useful for SymConnectors and the like, whose directional * vocab may change according to which direction they're approached from. */ DirState: State stateProp = &attachedDir /* * We make all these vocab words weak tokens because we don't want to match on the direction * name alone, e.g., X NORTH. */ vocabWords = [ [&north, 'north', MatchWeak], [&north, 'n', MatchWeak], [&south, 'south', MatchWeak], [&south, 's', MatchWeak], [&east, 'east', MatchWeak], [&east, 'e', MatchWeak], [&west, 'west', MatchWeak], [&west, 'w', MatchWeak], [&southeast, 'southeast', MatchWeak], [&southeast, 'se', MatchWeak], [&southwest, 'southwest', MatchWeak], [&southwest, 'sw', MatchWeak], [&northwest, 'northwest', MatchWeak], [&northwest, 'nw', MatchWeak], [&southwest, 'southwest', MatchWeak], [&southwest, 'sw', MatchWeak], [&port, 'port', MatchWeak], [&port, 'p', MatchWeak], [&starboard, 'starboard', MatchWeak], [&starboard, 'sb', MatchWeak], [&fore, 'fore', MatchWeak], [&fore, 'forward', MatchWeak], [&fore, 'f', MatchWeak], [&aft, 'aft', MatchWeak], [&up, 'up', MatchWeak], [&up, 'upward', MatchWeak], [&down, 'down', MatchWeak], [&down, 'd', MatchWeak], [&down, 'downward', MatchWeak], [&in, 'in', MatchWeak], [&in, 'inner', MatchWeak], [&in, 'inward', MatchWeak], [&out, 'out', MatchWeak], [&out, 'outer', MatchWeak], [&out, 'outward', MatchWeak] ] appliesTo(obj) { /* * We exclude DStairway because including 'up' or 'down' in its vocab confuses the * parser's interpretation of CLIMB UP and CLIMB DOWN. */ if(defined(DSStairway) && obj.ofKind(DSStairway)) return nil; else return inherited(obj); } ; /* * Modifications to TopicPhrase to make it work better with the * English-specific part of the library. */ modify TopicPhrase matchNameScope(cmd, scope) { local toks = tokens; local ret; /* * Strip any apostrophe-S from our tokens since the vocab words * initialization will have done the same */ tokens = tokens.subset({x: x != '\'s'}); /* * Strip any articles out of the tokens. We need to do this in the * English library to ensure that we get a sensible match to a Topic * when the player's input includes articles (e.g. ASK ABOUT THE * DARK), since the parser will first try to match items that include * the tokens 'the' and 'dark' in their vocabWords, with results that * may not be what we want. */ tokens = tokens.subset({x: x not in ('a', 'the', 'an')}); try { /* Carry out the inherited handling and store the result */ ret = inherited(cmd, scope); } finally { /* Restore the original tokens on the way out. */ tokens = toks; } /* Return the result of the inherited handling. */ return ret; } ; /* * Modification to the ResolvedTopic for use with the English-language * specific part of the library. */ modify ResolvedTopic /* * The English Tokenizer separates apostrophe-S from the word it's part * of, so in restoring the original text we need to join any apostrophe-S * back to the word it was separated from. */ getTopicText() { local str = tokens.join(' ').trim(); str = str.findReplace(' \'s', '\'s', ReplaceAll); return str; } ; /* * Modification to the Topic class so that when constructing a new Topic a * separate apostrophe-S token is concatenated with the previous word when * storing the name (which undoes the effect on building the name of what the * English-language tokenizer does with apostrophe-S). */ modify Topic construct(name_) { name_ = name_.findReplace(' \'s', '\'s', ReplaceAll); inherited(name_); } ; /* * Modification to the Command class so that when reconstructing a command * string from its tokens a separate apostrophe-S token is concatenated with * the previous word when storing the name (which undoes the effect on * building the name of what the English-language tokenizer does with * apostrophe-S). */ modify Command buildCommandString() { /* Get a list of the tokens making up the command we want to reparse. */ local toks = valToList(verbProd.tokenList).mapAll({x: getTokVal(x)}); /* * If there's a full stop or semicolon in the command line (indicating more than one * command on the line), remove any again commaands from the tok list that follow the * punctuation so we don't end up repeating an again command until there's a stack * overflow. We don't want to remove G or AGAIN if it occurs before the punctuation since * it might be a legitimate part of the original command, e.g., PUSH BUTTON G. */ local str = toks.join(' '); /* Split the string into a list at full stops and/or semi-colons */ local lst = str.split(R'<period>|;'); /* Remove any instances of again commmands from the list */ lst = lst.subset({x: x.toLower().trim() not in ('g', 'again')}); /* Put the command string back together from the adjusted list. */ str = lst.join('.'); /* * The tokenizer separates 's from the noun it qualifies, so we remove any unwanted spaces * before 's. */ str = str.findReplace(' \'s', '\'s', ReplaceAll); /* Return the adjusted command string. */ return str; } ; /* ------------------------------------------------------------------------ */ /* * English modifications for Thing. This adds some methods that vary by * language, so they can't be defined in the generic Thing class. */ modify Thing /* * Show the nested room subhead. This shows the actor's immediate * container as an addendum to the room name in the room description * headline. * * [Required] */ roomSubhead(pov) { " (<<childLocType(pov).prep>> <<theName>>)"; } /* Did the player's command ask to PUSH this object ? */ matchPushOnly = (gVerbWord == 'push') /* Did the player's command ask to PULL this object? */ matchPullOnly = (gVerbWord is in ('pull', 'drag')) /* * Check whether we need to add or remove the LitUnlit State from our list * of states. */ makeLit(stat) { inherited(stat); if(LitUnlit.appliesTo(self)) states = states.appendUnique([LitUnlit]); else states -= LitUnlit; } /* * Announce Best Choice name. This can be used in those rare cases where * you want to override the name the parser uses to describe an object * when announcing its best choice of object. For example, if you have a * bottle of wine from which you can fill a glass, you might prefer '(with * wine from the bottle)' to '(with the bottle of wine)' following FILL * GLASS; action is the action being carried out for which the object has * been chosen and role(DirectObject or IndirectObject) is the role the * chosen object is playing in the action. By default this method just * returns theName. */ abcName(action, role) { return theName; } /* * Get the list suffix for miscellaneous items within our Room when the actor (usually the * player character) is inside this Thing. If the misxListSuffix is defined (non-nil) on this * Thing, or if our location is nil (presumaby because we're a Room rather than some kind of * nested room). then return our miscListSuffix. * * We do it this way to allow game code to either customize our misc list suffix (which, by * default, is just 'here' on the actor's location (if it's a nested room) or on the actor's * Room, in which caas the pov parameter can be used to vary the suffix according to the * actor's lcoation (which may save work if, for example, the room contains several booths and * we want the same prefix for eacn one). */ getMiscListSuffix(pov) { return miscListSuffix || location == nil ? miscListSuffix : location.getMiscListSuffix(pov); } /* * The suffix to display at the end of a list of the miscellaneous items directly in our room * , e.g. 'lying on the floor' would customize llst to "You see X, Y, and Z lying on the * floor" instead of the defauult "!You see X, Y, and Z here." */ miscListSuffix = nil ; /*-------------------------------------------------------------------------- */ /* * English language modifications to Room. Here we simply allow a Room to take * its vocab from its roomTitle property if vocab is not already defined; this * reduces the need to type the same text twice when the two are effectively * the same. */ modify Room initVocab() { /* * If our vocab property isn't already defined, take it from our * roomTitle, converting it to lower case, provided proper is false. */ if(vocab == nil && autoName && roomTitle) vocab = proper ? roomTitle : roomTitle.toLower() ; /* Carry out the inherited handling */ inherited(); } /* * Flag: do we want this room to take its vocab (and hence its name) from * its roomTitle property if its vocab property isn't explicitly defined? * By default we do. */ autoName = true /* * By default the list suffix for items directly in a Room is simply 'here'. This can be * changed for individual Rooms or overrideen on the Room class. or by overriding the Room's * getMiscListSuffix(pove) method, or by using a CustomMessages object. */ miscListSuffix = BMsg(misc list suffix, '{here}'); ; /* * Modifications to Pronoun to ensure that aName, theName and theObjName * return the appropriate results. */ modify Pronoun aName = (name) theName = (name) theObjName = (objName) ; /* ------------------------------------------------------------------------ */ /* * Base library vocabulary initialization. For the English module's own * convenience, we add vocabulary words to a number of abstract * grammar-related objects defined in the base library parser. The base * library can't define vocabulary, for obvious reasons, so we have to * add the vocabulary words ourselves. */ property prep; pronounPreinit: PreinitObject execute() { /* * Initialize the pronoun names. These are used only within the * English library. Other language modules will probably need to * define vocabulary for pronouns as well, but the specific * properties are up to the translator. Languages that have more * noun cases than English can add properties for the extra noun * cases as needed. */ It.name = It.objName = 'it'; It.possAdj = It.possNoun = 'its'; It.thatName = It.thatObjName = 'that'; It.reflexive = Itself; Her.name = 'she'; Her.objName = Her.possAdj = 'her'; Her.possNoun = 'hers'; Her.reflexive = Herself; Him.name = 'he'; Him.objName = 'him'; Him.possAdj = Him.possNoun = 'his'; Him.reflexive = Himself; Them.name = 'they'; Them.objName = 'them'; Them.possAdj = 'their'; Them.possNoun = 'theirs'; Them.thatName = Them.thatObjName = 'those'; Them.reflexive = Themselves; Them.plural = true; You.name = You.objName = 'you'; You.possAdj = 'your'; You.possNoun = 'yours'; You.reflexive = Yourself; Yall.name = Yall.objName = 'you'; Yall.possAdj = 'your'; Yall.possNoun = 'yours'; Yall.reflexive = Yourselves; Yall.plural = true; Me.name = 'I'; Me.objName = 'me'; Me.possAdj = 'my'; Me.possNoun = 'mine'; Me.reflexive = Myself; Us.name = 'we'; Us.objName = 'us'; Us.possAdj = 'our'; Us.possNoun = 'ours'; Us.reflexive = Ourselves; Us.plural = true; Myself.name = Myself.objName = 'myself'; Yourself.name = Yourself.objName = 'yourself'; Itself.name = Itself.objName = 'itself'; Herself.name = Herself.objName = 'herself'; Himself.name = Himself.objName = 'himself'; Ourselves.name = Ourselves.objName = 'ourselves'; Yourselves.name = Yourselves.objName = 'yourselves'; Themselves.name = Themselves.objName = 'themselves'; /* * Set the default 'that' name for each pronoun to its regular * name. We use the regular pronoun instead of 'that' for any * gendered noun or first- or second-person object. */ foreach (local pro in Pronoun.all) { if (pro.thatName == nil) pro.thatName = pro.name; if (pro.thatObjName == nil) pro.thatObjName = pro.objName; } /* create the pronoun map for LMentionable */ LMentionable.pronounMap = new LookupTable(16, 32); forEachInstance(Pronoun, function(p) { LMentionable.pronounMap[p.name] = p; if (p.objName != nil) LMentionable.pronounMap[p.objName] = p; }); /* * Initialize the LocType prepositions. The 'prep' property is * English-specific and is NOT used by the base library, but * other language modules will probably need some type of * corresponding vocabulary for each LocType. */ In.prep = 'in'; Outside.prep = 'on'; On.prep = 'on'; Under.prep = 'under'; Behind.prep = 'behind'; Held.prep = 'held by'; Worn.prep = 'worn by'; Attached.prep = 'attached to'; PartOf.prep = 'part of'; } ; /* ------------------------------------------------------------------------ */ /* * CustomVocab objects define special-case vocabulary for the parser and * name generation routines. * * The library provides a CustomVocab object with many common * special-case words, but games and extensions can augment the built-in * lists by defining their own CustomVocab objects that follow the same * patterns. The library automatically includes all of the special word * lists in all of the CustomVocab objects defined throughout the game. */ class CustomVocab: object /* * The list of special-case a/an words. Choosing 'a' or 'an' is * purely phonetic, and English orthography is notoriously * inconsistent phonetically. What's more, the choice for many words * varies by dialect, accent, and personal style. We try to cover as * much as we can in our spelling-based rules, but it's hopeless to * cover all the bases purely with spelling. At some point we just * have to turn to a table of special cases. * * We apply the special rules based on the first word in a name. The * first word is simply the first contiguous group of alphanumeric * characters. If the first word in a name is found in this list, * the setting here will override any spelling rules. * * The entries here are simply strings of the form 'a word' or 'an * word'. Start with the appropriate form of a/an, then add a space, * then the special word to match. */ specialAOrAn = [] /* * Irregular plural list. This is a list of words with plurals that * can't be inferred from any of the usual spelling rules. The * entries are in pairs: singular, [plurals]. The plurals are given * in a list, since some words have more than one valid plural. The * first plural is the preferred one; the remaining entries are * alternates. */ irregularPlurals = [] /* * Verbs for substitution parameter strings. This is a list of * strings, using the following template: * *. 'infinitive/present3/past/past-participle' * * The 'infinitive' is the 'to' form of the verb (to go, to look, to * see), but *without* the word 'to'. 'present3' is the third-person * present form (is, goes, sees). 'past' is the past tense form * (went, looked, saw). 'past-participle' is the past participle * form; this is optional, and is needed only for verbs with distinct * past and past participle forms (e.g., saw/seen, went/gone). Most * regular verbs - those with the past formed by adding -ed to the * infinitive - have identical past and participle forms. * * For every English verb except "to be", the entire present and past * conjugations can be derived from these three bits of information. * The past perfect, future, and future perfect conjugations can also * be derived from this information, for any verb except "to be" and * the auxiliary verbs (could, should, etc). The English library * pre-defines "to be" and all of the auxiliary verbs, so there's no * need to define those with this mechanism. */ verbParams = [] ; /* * Custom English vocabulary. Here we define a basic dictionary of * irregular plurals, a/an words, and verb parameters. Games that want * to save a little compiled file space might want to replace this with a * set that only defines the words actually needed in the game. Games * are free to define additional custom vocabulary words by adding their * own CustomVocab objects; the library will automatically find and merge * them into the dictionary during preinit. */ englishCustomVocab: CustomVocab /* irregular plurals */ irregularPlurals = [ 'calf', ['calves', 'calfs'], 'elf', ['elves', 'elfs'], 'half', ['halves', 'halfs'], 'hoof', ['hooves', 'hoofs'], 'knife', ['knives'], 'leaf', ['leaves'], 'life', ['lives'], 'loaf', ['loaves'], 'scarf', ['scarves', 'scarfs'], 'self', ['selves', 'selfs'], 'sheaf', ['sheaves', 'sheafs'], 'shelf', ['shelves'], 'bookshelf', ['bookshelves'], 'thief', ['thieves'], 'wife', ['wives'], 'wolf', ['wolves'], 'foot', ['feet'], 'goose', ['geese'], 'louse', ['lice'], 'mouse', ['mice'], 'tooth', ['teeth'], 'auto', ['autos'], 'kilo', ['kilos'], 'memo', ['memos'], 'motto', ['mottos'], 'photo', ['photos'], 'piano', ['pianos'], 'pimento', ['pimentos'], 'pro', ['pros'], 'solo', ['solos'], 'soprano', ['sopranos'], 'studio', ['studios'], 'video', ['videos'], 'die', ['dice', 'dies'], 'alga', ['algae'], 'larva', ['larvae', 'larvas'], 'vertebra', ['vertebrae'], 'alumnus', ['alumni'], 'alumna', ['alumnae'], 'bacillus', ['bacilli'], 'cactus', ['cacti', 'catuses'], 'focus', ['foci', 'focuses'], 'fungus', ['fungi', 'funguses'], 'nucleus', ['nuclei'], 'octopus', ['octopi', 'octopuses'], 'radius', ['radii'], 'stimulus', ['stimuli'], 'terminus', ['termini'], 'addendum', ['addenda'], 'bacterium', ['bacteria'], 'cirriculum', ['cirricula'], 'datum', ['data'], 'erratum', ['errata'], 'medium', ['media'], 'memorandum', ['memoranda'], 'ovum', ['ova'], 'stratum', ['strata'], 'symposium', ['symposia'], 'appendix', ['appendices', 'appendixes'], 'index', ['indices', 'indexes'], 'matrix', ['matrices', 'matrixes'], 'vortex', ['vortices', 'vortexes'], 'analysis', ['analyses'], 'axis', ['axes'], 'basis', ['bases'], 'crisis', ['crises'], 'diagnosis', ['diagnoses'], 'emphasis', ['emphases'], 'hypothesis', ['hypotheses'], 'neurosis', ['neuroses'], 'parenthesis', ['parentheses'], 'synopsis', ['synopses'], 'thesis', ['theses'], 'criterion', ['criteria'], 'phenomenon', ['phenomena'], 'automaton', ['automata'], 'libretto', ['libretti'], 'tempo', ['tempi'], 'virtuoso', ['virtuosi'], 'cherub', ['cherubim'], 'seraph', ['seraphim'], 'schema', ['schemata'], 'barracks', ['barracks'], 'crossroads', ['crossroads'], 'dice', ['dice'], 'gallows', ['gallows'], 'headquarters', ['headquarters'], 'means', ['means'], 'offspring', ['offspring'], 'series', ['series'], 'species', ['species'], 'cattle', ['cattle'], 'billiards', ['billiards'], 'clothes', ['clothes'], 'pants', ['pants'], 'measles', ['measles'], 'thanks', ['thanks'], 'pliers', ['pliers'], 'scissors', ['scissors'], 'shorts', ['shorts'], 'trousers', ['trousers'], 'tweezers', ['tweezers'], 'glasses', ['glasses'], 'eyeglasses', ['eyeglasses'], 'spectacles', ['spectacles'], 'information', ['information'], 'honesty', ['honesty'], 'wisdom', ['wisdom'], 'beauty', ['beauty'], 'intelligence', ['intelligence'], 'stupidity', ['stupidity'], 'curiosity', ['curiosity'], 'chemistry', ['chemistry'], 'geometry', ['geometry'], 'physics', ['physics'], 'mechanics', ['mechanics'], 'optics', ['optics'], 'dynamics', ['dynamics'], 'thermodynamics', ['thermodynamics'], 'linguistics', ['linguistics'], 'acoustics', ['acoustics'], 'mathematics', ['mathematics'], 'jazz', ['jazz'], 'traffic', ['traffic'], 'sand', ['sand'], 'air', ['air'], 'water', ['water'], 'furniture', ['furniture'], 'equipment', ['equipment'], 'cod', ['cod', 'cods'], 'deer', ['deer', 'deers'], 'fish', ['fish', 'fishes'], 'perch', ['perch', 'perches'], 'sheep', ['sheep'], 'trout', ['trout', 'trouts'] ] /* special-case 'a' vs 'an' words */ specialAOrAn = [ 'an heir', 'an honest', 'an honor', 'an honour', 'an hors', 'an hour', 'a one', 'a ouija', 'a unified', 'a union', 'a unit', 'a united', 'a unity', 'a universal', 'a university', 'a universe', 'a unicycle', 'a unicorn', 'a usage', 'a user' ] /* verb parameters, for {xxx} tokens in message strings */ verbParams = [ 'arise/arises/arose/arisen', 'awake/awakes/awoke/awoken', 'bear/bears/bore/born', 'beat/beats/beat/beaten', 'become/becomes/became/become', 'begin/begins/began/begun', 'behold/beholds/beheld', 'bend/bends/bent', 'bet/bets/bet', 'bid/bids/bade/bidden', 'bind/binds/bound', 'bite/bites/bit/bitten', 'bleed/bleeds/bled', 'blow/blows/blew/blown', 'break/breaks/broke/broken', 'breed/breeds/bred', 'bring/brings/brought', 'build/builds/built', 'burn/burns/burnt', 'bust/busts/bust', 'buy/buys/bought', 'cast/casts/cast', 'catch/catches/caught', 'choose/chooses/chose/chosen', 'clap/claps/clapt', 'cling/clings/clung', 'come/comes/came', 'creep/creeps/crept', 'cut/cuts/cut', 'deal/deals/dealt', 'dig/digs/dug', 'dive/dives/dove/dived', 'do/does/did', 'draw/draws/drew/drawn', 'dream/dreams/dreamt', 'drink/drinks/drank/drunk', 'drive/drives/drove/driven', 'dwell/dwells/dwelt', 'eat/eats/ate/eaten', 'fall/falls/fell/fallen', 'feed/feeds/fed', 'feel/feels/felt', 'fight/fights/fought', 'find/finds/found', 'fling/flings/flung', 'fly/flies/flew/flown', 'forbid/forbids/forbade/forbidden', 'freeze/freezes/froze/frozen', 'get/gets/got/gotten', 'give/gives/gave/given', 'go/goes/went/gone', 'grind/grinds/ground', 'grow/grows/grew/grown', 'handwrite/handwrites/handwrote/handwritten', 'hang/hangs/hung', 'have/has/had', 'hide/hides/hid/hidden', 'hit/hits/hit', 'hold/holds/held', 'hurt/hurts/hurt', 'inlay/inlays/inlaid', 'input/inputs/input', 'interlay/interlays/interlaid', 'keep/keeps/kept', 'kneel/kneels/knelt', 'knit/knits/knit', 'know/knows/knew/known', 'lay/lays/laid', 'lead/leads/led', 'leans/leans/leant', 'leap/leaps/leapt', 'learn/learns/learnt', 'leave/leaves/left', 'lend/lends/lent', 'let/lets/let', 'lie/lies/lay/lain', 'light/lights/lit', 'lose/loses/lost', 'make/makes/made', 'mean/means/meant', 'meet/meets/met', 'melt/melts/melted/molten', 'mislead/misleads/misled', 'mistake/mistakes/mistook/mistaken', 'misunderstand/misunderstands/misunderstood', 'miswed/misweds/miswed', 'mow/mows/mowed/mown', 'overdraw/overdraws/overdrew/overdrawn', 'overhear/overhears/overheard', 'overtake/overtakes/overtook/overtaken', 'pay/pays/paid', 'preset/presets/preset', 'prove/proves/proved/proven', 'put/puts/put', 'quit/quits/quit', 'read/reads/read', 'rid/rids/rid', 'ride/rides/rode/ridden', 'ring/rings/rang/rung', 'rise/rises/rose/risen', 'rive/rives/rived/riven', 'run/runs/ran/run', 'saw/saws/sawed/sawn', 'sew/sews/sewed/sewn', 'say/says/said', 'see/sees/saw/seen', 'set/sets/set', 'shake/shakes/shook/shaken', 'shave/shaves/shaved/shaven', 'shear/shears/shore', 'shed/sheds/shed', 'shine/shines/shone', 'shoe/shoes/shod', 'shoot/shoots/shot', 'show/shows/showed/shown', 'shrink/shrinks/shrank/shrunk', 'shut/shuts/shut', 'sing/sings/sang/sung', 'sit/sits/sat', 'slay/slays/slew/slain', 'sleep/sleeps/slept', 'slide/slides/slid', 'sling/slings/slung', 'slink/slinks/slunk', 'slit/slits/slit', 'smell/smells/smelt', 'sneak/sneaks/snuck', 'soothsay/soothsays/soothsaid', 'sow/sows/sowed/sown', 'speak/speaks/spoke/spoken', 'speed/speeds/sped', 'spell/spells/spelt', 'spend/spends/spent', 'spill/spills/spilt', 'spin/spins/span/spun', 'spit/spits/spat', 'split/splits/split', 'spoil/spoils/spoilt', 'spread/spreads/spread', 'spring/springs/sprang/sprung', 'stand/stands/stood', 'steal/steals/stole/stolen', 'stick/sticks/stuck', 'sting/stings/stung', 'stink/stinks/stank/stunk', 'stride/strides/strode/stridden', 'strike/strikes/struck/stricken', 'strive/strives/strove/striven', 'sublet/sublets/sublet', 'sunburn/sunburns/sunburnt', 'swear/swears/swore/sworn', 'sweat/sweats/sweat', 'sweep/sweeps/swept', 'swell/swells/swelled/swollen', 'swim/swims/swam/swum', 'swing/swings/swung', 'take/takes/took/taken', 'teach/teaches/taught', 'tear/tears/tore/torn', 'tell/tells/told', 'think/thinks/thought', 'thrive/thrives/throve/thrived', 'throw/throws/threw/thrown', 'thrust/thrusts/thrust', 'tread/treads/trod/trodden', 'undergo/undergoes/underwent/undergone', 'understand/understands/understood', 'undertake/undertakes/undertook/undertaken', 'upset/upsets/upset', 'vex/vexes/vext', 'wake/wakes/woke/woken', 'wear/wears/wore/worn', 'weave/weaves/wove/woven', 'wed/weds/wed', 'weep/weeps/wept', 'wend/wends/went', 'wet/wets/wet', 'will/will/would', 'win/wins/won', 'wind/wind/wound', 'withdraw/withdraws/withdrew/withdrawn', 'withhold/withholds/withheld', 'withstand/withstands/withstood', 'won\'t/won\'t/would not', 'wring/wrings/wrung', 'write/writes/wrote', 'zinc/zincks/zincked' ] ; /* ------------------------------------------------------------------------ */ /* * Generate a spelled-out version of the given number value, or simply a * string representation of the number. We follow fairly standard * English style rules: * *. - we spell out numbers below 100 *. - we also spell out round figures above 100 that can be expressed *. in two words (e.g., "fifteen thousand" or "thirty million") *. - for millions and billions, we write, e.g., "1.7 million", if possible *. - for anything else, we return the decimal digits, with commas to *. separate groups of thousands (e.g., 120,400) * * Other languages might have different style rules, so the choice using * a spelled-out number or digits might vary by language. * * [Required] */ spellNumber(n) { /* get the number formatting options */ local dot = englishOptions.decimalPt; local comma = englishOptions.numGroupMark; /* if it's a BigNumber with a fractional part, write as digits */ if (dataType(n) == TypeObject && n.ofKind(BigNumber) && n.getFraction() != 0) { /* * format it, and convert decimals and group separators per the * options */ return n.formatString(n.getPrecision(), BignumCommas).findReplace( ['.', ','], [dot, comma]); } /* if it's less than zero, use "minus seven" or "-123" */ if (n < 0) { /* get the spelled version of the absolute value */ local s = spellNumber(-n); /* if it has any letters, use "minus", otherwise "-" */ return (s.find(R'<alpha>') != nil ? 'minus ' : '-') + s; } /* spell anything less than 100 */ if (n < 100) { if (n < 20) return ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'][n+1]; else return ['twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'][n/10-1] + ['', '-one', '-two', '-three', '-four', '-five', '-six', '-seven', '-eight', '-nine'][n%10 + 1]; } /* spell out single-digit multiples of 100 */ if (n % 100 == 0 && n/100 < 10) return '<<spellNumber(n/100)>> hundred'; /* * Spell out single-digit multiples of each power of 10 from a thousand to * a billion. */ if (n % 1000000000 == 0 && n/1000000000 < 10) return '<<spellNumber(n/1000000000)>> billion'; if ((n % 1000000 == 0 && n/1000000 < 10) || (n % 10000000 == 0 && n/10000000 < 10) || (n % 100000000 == 0 && n/100000000 < 10)) return '<<spellNumber(n/1000000)>> million'; if ((n % 1000 == 0 && n/1000 < 10) || (n % 10000 == 0 && n/10000 < 10) || (n % 100000 == 0 && n/100000 < 10)) return '<<spellNumber(n/1000)>> thousand'; /* * check to see if it can be expressed as a whole number of millions or * billions, or as millions or billions using up to three significant * figures ("1.75 million", "17.5 billion") */ if (n % 1000000 == 0 && n/1000000 < 1000) return '<<n/1000000>> million'; if (n % 100000 == 0 && n/1000000 < 100) return '<<n/1000000>><<dot>><<n%1000000 / 100000>> million'; if (n % 10000 == 0 && n/1000000 < 10) return '<<n/1000000>><<dot>><<n%1000000 / 10000>> million'; if (n % 1000000000 == 0 && n/1000000000 < 1000) return '<<n/1000000000>> billion'; if (n % 100000000 == 0 && n/1000000000 < 100) return '<<n/1000000000>><<dot>><<n%1000000000 / 100000000>> billion'; if (n % 10000000 == 0 && n/1000000000 < 10) return '<<n/1000000000>><<dot>><<n%1000000000 / 10000000>> billion'; /* convert to digits */ local s = toString(n); /* insert commas at the thousands */ for (local i = s.length() - 2 ; i > 1 ; i -= 3) s = s.splice(i, 0, comma); /* return the result */ return s; } /* * Try to convert a spelled out number (e.g. 'ninety-six') to its integer * representation. If this fails, return nil. */ spelledToInt(str) { /* Tokenize the input string */ local toks = cmdTokenizer.tokenize(str); /* Try parsing the tokens against the spelledNumber production */ local lst = spelledNumber.parseTokens(toks, cmdDict); /* If there's a vlid result, convert it to an integer. */ if(lst.length > 0) return lst[1].numval(); /* Otherwise return nil */ return nil; } /* ------------------------------------------------------------------------ */ /* * List display routines */ modify Lister /* * Show the list as an 'and' list, that is a list of the aNames of each * item in lst formatted with commas between list elements and 'and' * between the last two items in the list. */ showList(lst, pl, paraCnt) { "<<andList(lst.mapAll({ o: o.aName }))>>"; } ; /* * Modifications to the ItemLister (the base class for listers that list * physical objects) for the English-language part of the library. */ modify ItemLister /* * For an item lister we use the listName method of the lister rather than * the aName property of the object to provide a name for the object; this * allows the lister to add status-specific information like '(providing * light)' or '(being worn)' to the name as it appears in the list. */ showList(lst, pl, parent) { "<<andList(lst.mapAll({ o: listName(o) }))>>"; } /* * The listName is the aName of o plus any status-specific information we * might want to appear in the listing, such as '(providing light)' */ listName(o) { /* Store the object name in a local variable */ local lName = o.aName; /* * Add any additional state-specific info, such as ' (providing * light)', to the name of this object. */ if(showAdditionalInfo) { foreach(local state in o.states) lName += state.getAdditionalInfo(o); } /* * If this object is being worn and we want to show information about * objects being worn, add ' (being worn)' to the name */ if(o.wornBy != nil && showWornInfo) lName += BMsg(being worn, ' (being worn)'); /* * If this object hyas been moved to another object, and we want to show information about * this notional location, add ' {by the whatever)' to the name. */ if(o.movedTo != nil && showMovedToInfo) { local obj = o.movedTo; gMessageParams(obj); lName += BMsg(moved to, ' (by {the obj})'); } /* * If the object being listed has visible contents, list its visible contents recursively. * Then do the same for any remapIn or remapOn subcomponents. */ if(showSubListing) { local lists = [o]; if(o.remapIn) lists += o.remapIn; if(o.remapOn) lists += o.remapOn; foreach(local s in lists) { if(s.contents != nil && s.contents.length > 0 && s.canSeeIn) { lName += subLister.buildList(s.contents); s.contents.subset({ o: listed(o) }).forEach({x: x.mentioned = true}); } } } /* * Return the result, i.e. the aName of the object plus any additional * information about its state and/or contents. */ return lName; } /* * Flag: do we want to show additional information such as '(providing * light)' after the names of items listed in inventory. * By default we do. */ showAdditionalInfo = true /* * Flag: do we want to show (being worn) after items in an inventory list * that the player character is wearing. By default we do if we're showing * additional info. */ showWornInfo = (showAdditionalInfo) /* * Flag: do we want to show {by the whatever) if after items that have notionally being moved * to the vicinity of whatever. */ showMovedToInfo = true /* * Flag: do we want to show the contents of items listed in inventory (in * parentheses after the name, e.g. a bag (in which is a blue ball)). By * default we do if the actor is the player character. */ showSubListing = (gActor == gPlayerChar) ; /* * English-language modifications to the lister used for miscellaneous items * when looking around in a room. */ modify lookLister showListPrefix(lst, pl, paraCnt) { "{I} {can} see "; } /* * We obtain the list suffix from the suffix the actor's (usually the player character's) * location wants us to use to allow customization to something that might be appropriate than * the default 'here'. */ showListSuffix(lst, pl, paraCnt) { " <<gActor.location.getMiscListSuffix(gActor)>>."; } showSubListing = (gameMain.useParentheticalListing) ; /* * English-language modifications to the lister used for listing items carried * by the player character. */ modify inventoryLister showListPrefix(lst, pl, paraCnt) { "<<if paraCnt == 0>>{I} {am}<<else>>, and<<end>> carrying "; } showListSuffix(lst, pl, paraCnt) { "."; } showListEmpty(paraCnt) { "{I} {am} empty-handed. "; } ; /* * English-language modifications to the lister used for listing items worn * by the player character. */ modify wornLister showListPrefix(lst, pl, paraCnt) { "{I} {am} wearing "; } showListSuffix(lst, pl, paraCnt) { } showListEmpty(paraCnt) { } /* * We don't want to show "(being worn)" after items listed after "You are * wearing" since this would clearly be redundant. */ showWornInfo = nil ; /* * The subLister is used by other listers such as inventoryLister and * wornLister to show the contents of listed items in parentheses (e.g. '(in * which is a pen, a pencil and a piece of paper). The depth of nesting is * limited by the maxNestingDepth property. */ subLister: ItemLister showListPrefix(lst, pl, paraCnt) { " (<<lst[1].location.objInPrep>> which <<pl ? '{plural} {is}' : '{dummy} {is}'>> "; } showListSuffix(lst, pl, paraCnt) { ")"; } showListEmpty(paraCnt) { } /* Construct the list contents from lst to a maximum nesting depth */ buildList(lst) { /* increase the nesting depth by 1 */ nestingDepth++; /* * if we've gone beyond the maximum nesting depth, simply return an * empty string to stop going any deeper. */ if(nestingDepth > maxNestingDepth) { /* reduce the nesting depth by 1 */ nestingDepth--; /* return an empty string. */ return ''; } /* * Carry out the inherited handling and store the result in a local * variable. */ local str = inherited(lst); /* Reduce the nesting depth by 1 */ nestingDepth--; /* Return the result */ return str; } showList(lst, pl, paraCnt) { "<<andList(lst.mapAll({ o: listName(o) }))>>"; } /* The maximum nesting depth this subLister is allowed to reach */ maxNestingDepth = 1 /* The current nesting depth of this subLister */ nestingDepth = 0 showSubListing = true listed(o) { return o.lookListed; } ; /* * English language modifications to the descContentsLister, which is used to * list the contents of a container when it's examined. */ modify descContentsLister showListPrefix(lst, pl, parent) { gMessageParams(parent); /* * If the item whose contents we're listing want to report its * open-or-closed status, start the listing by reporting that it's * open and then say 'and contains' */ if(parent.openStatusReportable == UsePronoun) "{He parent}{\'s} open and contain{s/ed} "; else if(parent.openStatusReportable) "{The subj parent} {is} open and contain{s/ed} "; /* * Otherwise start the listing without explicitly mentioning that the * container is open. */ else "{In parent} {i} {see} "; } showListSuffix(lst, pl, paraCnt) { "."; } /* * If there's no contents to list, but the item whose contents we were * trying to list wants to report its open-or-closed status, simply state * that it's open or closed. */ showListEmpty(parent) { gMessageParams(parent); if(parent.openStatusReportable == UsePronoun) "{He parent}{\'s} <<if parent.isOpen>>open<<end>>. "; else if(parent.openStatusReportable) "\^<<parent.theNameIs>> <<if parent.isOpen>>open<<end>>. "; } /* * Flag: Show a sublisting (i.e. the contents of the items in our * immediate contents, in parentheses after the name of the item, if the * gameMain option useParentheticalListing is true. */ showSubListing = (gameMain.useParentheticalListing) ; /* * English-language modifications to the lister used to list the contents of * containers when looking around in a room. */ modify lookContentsLister showListPrefix(lst, pl, parent) { "\^<<parent.remoteObjInName(gActor)>> {i} {see} "; } showListSuffix(lst, pl, paraCnt) { "."; } /* * Flag: Show a sublisting (i.e. the contents of the items in our * immediate contents, in parentheses after the name of the item, if the * gameMain option useParentheticalListing is true. */ showSubListing = (gameMain.useParentheticalListing) ; /* * English-language modifications to the lister used to describe the contents * of an openable container when it has just been opened. */ modify openingContentsLister showListPrefix(lst, pl, parent) { gMessageParams(parent); "Opening {the parent} {dummy} reveal{s/ed} "; } showListSuffix(lst, pl, paraCnt) { ".\n"; } showListEmpty(parent) { "{I} open{s/ed} {the dobj}. "; } showSubListing = (gameMain.useParentheticalListing) ; /* * English-language modifications to the lister used to list the contents of * something that's being looked in (or searched). */ modify lookInLister showListPrefix(lst, pl, parent) { gMessageParams(parent); "{In parent} {i} {see} "; } showListSuffix(lst, pl, paraCnt) { ".\n"; } showListEmpty(parent) { } showSubListing = (gameMain.useParentheticalListing) ; /* * English-language modifications to the lister used to list the items * attached to a SimpleAttachable. */ modify simpleAttachmentLister showListPrefix(lst, pl, parent) { "{I} {see} "; } showListSuffix(lst, pl, parent) { " attached to <<parent.theName>>. "; } showSubListing = (gameMain.useParentheticalListing) ; /* * English-language modifications to the lister used to list the items plugged * into a PlugAttachable. */ modify plugAttachableLister showListSuffix(lst, pl, parent) { " plugged into <<parent.theName>>. "; } ; /* * The lister used to list the options from which the player can choose when * the game comes to an end. */ finishOptionsLister: Lister showList(lst, pl, paraCnt) { /* * List the options as alternatives (with an 'or' between the last two * items). */ "<<orList(lst.mapAll({ o: o.desc }))>>"; } showListPrefix(lst, pl, parent) { cquoteOutputFilter.deactivate(); "<.p>Would you like to "; } showListSuffix(lst, pl, paraCnt) { /* end the question, add a blank line */ "?\b"; cquoteOutputFilter.activate(); } showSubListing = nil ; /* * Take a list of objects supplied in objList and return a formatted list in a * single quoted string, having first sorted the items in objList in the order * of their listOrder property. * * If the nameProp parameter is supplied, we'll use that property for the name * of every item in the list; otherwise we use the aName property by default. * * By default the last two items in the list are separated by 'and', but we * can choose a different conjunction by supplying the conjunction parameter. */ makeListStr(objList, nameProp = &aName, conjunction = 'and') { local lst = []; local i = 0; local obj; objList = valToList(objList); /* * Sort the list by listOrder, but only if the items it contains provide * the property, and only if they use it to define an order. If all the * items in the list have the same sortOrder, we don't want to shuffle * them out of their original order by performing an unnecessary sort. */ if(objList.length > 0 && objList[1].propDefined(&listOrder) && objList.indexWhich({x: x.listOrder != objList[1].listOrder})) objList = objList.sort(SortAsc, {a, b: a.listOrder - b.listOrder}); /* Go through every item in our sorted list */ for(i = 1, obj in objList ; ; ++i) { /* Mark it as having been mentioned in a list */ obj.mentioned = true; /* Store the value of the nameProp property in a local variable */ local desc = obj.(nameProp); /* Add any state-specific information */ foreach(local state in obj.states) desc += state.getAdditionalInfo(obj); /* Add the expanded name to our list of strings. */ lst += desc; } /* * Note whether the list would make a singular or plural grammatical * subject if referred to with the {prev} tag. */ if(objList.length > 1 || (objList.length > 0 && objList[1].plural)) prevDummy_.plural = true; else prevDummy_.plural = nil; /* * If the list is empty return 'nothing', otherwise use the genList() * function to construct the string representation of the list and return * that. */ return lst == [] ? 'nothing' : genList(lst, conjunction); } /* * Function to use with the <<mention a *>> string template. This marks the * object as mentioned in a room description and allows it to be used as the * antecedent of a {prev} tag, to ensure verb agreement. */ mentionA(obj) { /* Note that the object has been mentioned */ obj.mentioned = true; /* Note that the object has been seen */ obj.noteSeen(); /* * Set the plurality of the prevDummy_ object to the plurality of the * object we're mentioning (so that prevDummy_ can be used to secure * grammatical agreement with a subsequent verb). */ prevDummy_.plural = obj.plural; /* Return the aName of our obj. */ return obj.aName; } /* * Function to use with the <<mention the *>> string template. This marks the * object as mentioned in a room description and allows it to be used as the * antecedent of a {prev} tag, to ensure verb agreement. */ mentionThe(obj) { /* Note that the object has been mentioned */ obj.mentioned = true; /* Note that the object has been seen */ obj.noteSeen(); /* * Set the plurality of the prevDummy_ object to the plurality of the * object we're mentioning (so that prevDummy_ can be used to secure * grammatical agreement with a subsequent verb). */ prevDummy_.plural = obj.plural; /* Return the theName of our obj. */ return obj.theName; } /* * A version of makeListStr that uses only one parameter, for use by the * <<list of *>>string template */ makeListInStr(objList) { return makeListStr(objList); } /* * A version of makeListStr that uses the theName property, for use by the * <<list of *>>string template */ makeTheListStr(objList) { return makeListStr(objList, &theName); } /* * Function for use with the <<is list of *>> string template, prefixing a * list with the correct form of the verb to be to match the grammatical * number of the list (e.g. "There are a box and a glove here" or "There is * box here"). */ isListStr(objList) { if(objList.length > 1 || objList[1].plural) prevDummy_.plural = true; else prevDummy_.plural = nil; return '{prev} {is} ' + makeListStr(objList); } /* * Function for use by the <<list of * is>> string template, which returns a * formatted list followed by the appropriate form of the verb 'to be' in * grammatical agreement with that list. */ listStrIs(objList) { return makeListStr(objList) + ' {prev} {is}'; } /* * Construct a printable list of strings separated by "or" conjunctions. */ orList(lst) { return genList(lst, 'or'); } /* * Construct a printable list of strings separated by "and" conjunctions. */ andList(lst) { return genList(lst, 'and'); } /* * General list constructor */ genList(lst, conj) { /* start with an empty string */ local ret = new StringBuffer(); /* combine any duplicate items in the list */ lst = mergeDuplicates(lst); /* add each element */ local i = 1, len = lst.length(); foreach (local str in lst) { /* add a separator if this isn't the first item */ if (i > 1) { if (len == 2) ret.append(' <<conj>> '); else if (i == len) ret.append(', <<conj>> '); else ret.append(', '); } /* add this item */ ret.append(str); /* count the item */ ++i; } /* return the string */ return toString(ret); } /* * Take a list of strings of the form ['a book', 'a cat', 'a book'] and merge * the duplicate items to return a list of the form ['two books', 'a cat'] */ mergeDuplicates(lst) { /* A vector to store items that have duplicates */ local dupVec = new Vector(10); /* The Vector we build to return the processed list */ local processedVec = new Vector(10); /* Go through every item in our list */ foreach(local cur in lst) { /* * If we've already dealt with this item (or one identical to it), * skip over it. */ if(dupVec.indexOf(cur)) continue; /* Count how many times the current item occurs in the list */ local num = lst.countWhich({x: x == cur}); /* * If it doesn't occur more than once, simply add it to the processed * list and continue to the next item. */ if(num < 2) { processedVec.append(cur); continue; } /* * Othewise get the appropriate plural according to the number of * times the item appears in the list; e.g. if 'a gold coin' appears * three times in the list, pl will be 'three gold coins' */ local pl = makeCountedPlural(cur, num); { /* * If the makeCountedPlural() function returned the current value * unchanged simply add it to the processed list, otherwise add * the plural form to the processed list and the current form to * the list of duplicated items. */ if(pl == cur) processedVec.append(cur); else { processedVec.append(pl); dupVec.append(cur); } } } /* Convert the processed vector to a list and return it */ return processedVec.toList(); } /* * Take the string representation of a name (str) and a number (num) and * return a string with the number spelled out and the name pluralised, e.g. * makeCountPlural('a cat', 3) -> 'three cats' Amended to deal with the more * complex case ('taking the coin'), 3) -> 'taking three coins'); i.e. the * method now substitutes the number for the first occurrence of an article, * if there is one. */ makeCountedPlural(str, num) { /* Split the string into a list of words to make it easier to manipulate */ local strList = str.split(' '); /* * Don't attempt to pluralize the name unless it contains the singular * indefinite article or the definite article */ local idx = strList.indexWhich({s: s is in ('a', 'an', 'the')}); if(idx == nil) return str; /* Substitute the number for the article */ strList[idx] = spellNumber(num); /* Look for any part of the name in parentheses */ local idx1 = strList.indexWhich({x: x.startsWith('(')}); local idx2 = strList.indexWhich({x: x.endsWith(')')}); /* * If the name ends with a section in parentheses, pluralize the part of * the name before the parentheses and then append the parenthetical * section. */ if(idx1 != nil && idx2 != nil && idx2 >= idx1 && idx2 == strList.length) { local plStr = strList.sublist(1, idx1 - 1).join(' '); local parStr = strList.sublist(idx1).join(' '); return LMentionable.pluralNameFrom(plStr) + ' ' + parStr; } /* Otherwise return the entire string pluralized */ return LMentionable.pluralNameFrom(strList.join(' ')); } /* * Remove any definite or indefinite article that occurs at the beginning of * txt, and return the resultant string in lower case. */ stripArticle(txt) { txt = txt.toLower(); txt = txt.findReplace(R'^(the|a|an|some) ',''); return txt; } /* ------------------------------------------------------------------------ */ /* * finishGame options. We provide descriptions and keywords for the * option objects here, because these are inherently language-specific. * * Note that we provide hyperlinks for our descriptions when possible. * When we're in plain text mode, we can't show links, so we'll instead * show an alternate form with the single-letter response highlighted in * the text. We don't highlight the single-letter response in the * hyperlinked version because (a) if the user wants a shortcut, they can * simply click the hyperlink, and (b) most UI's that show hyperlinks * show a distinctive appearance for the hyperlink itself, so adding even * more highlighting within the hyperlink starts to look awfully busy. */ modify finishOptionQuit desc = '<<aHrefAlt('quit', 'QUIT', '<b>Q</b>UIT', 'Leave the story')>>' responseKeyword = 'quit' responseChar = 'q' ; modify finishOptionRestore desc = '''<<aHrefAlt('restore', 'RESTORE', '<b>R</b>ESTORE', 'Restore a saved position')>> a saved position''' responseKeyword = 'restore' responseChar = 'r' ; modify finishOptionRestart desc = '''<<aHrefAlt('restart', 'RESTART', 'RE<b>S</b>TART', 'Start the story over from the beginning')>> the story''' responseKeyword = 'restart' responseChar = 's' ; modify finishOptionUndo desc = '''<<aHrefAlt('undo', 'UNDO', '<b>U</b>NDO', 'Undo the last move')>> the last move''' responseKeyword = 'undo' responseChar = 'u' ; modify finishOptionCredits desc = '''see the <<aHrefAlt('credits', 'CREDITS', '<b>C</b>REDITS', 'Show credits')>>''' responseKeyword = 'credits' responseChar = 'c' ; modify finishOptionFullScore desc = '''see your <<aHrefAlt('full score', 'FULL SCORE', '<b>F</b>ULL SCORE', 'Show full score')>>''' responseKeyword = 'full score' responseChar = 'f' ; modify finishOptionAmusing desc = '''see some <<aHrefAlt('amusing', 'AMUSING', '<b>A</b>MUSING', 'Show some amusing things to try')>> things to try''' responseKeyword = 'amusing' responseChar = 'a' ; modify restoreOptionStartOver desc = '''<<aHrefAlt('start', 'START', '<b>S</b>TART', 'Start from the beginning')>> the game from the beginning''' responseKeyword = 'start' responseChar = 's' ; modify restoreOptionRestoreAnother desc = '''<<aHrefAlt('restore', 'RESTORE', '<b>R</b>ESTORE', 'Restore a saved position')>> a different saved position''' ; /* ------------------------------------------------------------------------ */ /* English-language addition to defaultGround to give it appropriate vocab */ modify defaultGround vocab = 'ground;;floor' ; /* ------------------------------------------------------------------------ */ /* * Ask for a missing noun phrase. The parser calls this when the player * enters a command that omits a required noun phrase, such as PUT KEY or * just TAKE. * * 'cmd' is the Command object. The other objects in the command, if * any, have been resolved as much as possible when this is called. * 'role' is the NounRole object telling us which predicate role is * missing (DirectObject, IndirectObject, etc). * * [Required] */ askMissingNoun(cmd, role) { "\^<<nounRoleQuestion(cmd, role)>>?\n"; } /* * Ask for help with an ambiguous noun. The parser calls this when the * player enters a noun phrase that's ambiguous, and we need to ask for * clarification. * * 'cmd' is the command, 'role' is the noun phrase's role in the * predicate (DirectObject, etc), and 'nameList' is a list of strings * determined by the Distinguisher process. * * [Required] */ askAmbiguous(cmd, role, names) { /* * For the direct object or actor role, keep it simple and just ask "which do * you mean". * * For other roles, be more specific: use the basic predicate * question for the role, so it's clear which object we're asking * about. Replace 'what' with 'which' in these questions. */ local q; if (role is in (DirectObject, ActorRole)) q = BMsg(which do you mean, 'Which do you mean'); else q = nounRoleQuestion(cmd, role) .findReplace('what', 'which', ReplaceOnce); /* * If the option to enumerate the dimabigiation possibilites is set, then prefix every item in * the list with a number, provided we have no more than 20 options (the parser seems unable * to cope with more than this). */ if(libGlobal.enumerateDisambigOptions && names.length < 20) { /* Set up a new list to hold the numbered names */ local numbered_names = []; /* The current item in the list of names */ local item; /* For each item in the list of names, prepend its number in the list. */ for(local i in 1 .. names.length) { /* * Prepend the number represented the place in the list to the name of the item in the * list. */ item = '<b>(' + toString(i) + ')</b> ' + names[i]; /* Add the numbered item to the list of numbered names; */ numbered_names += item; } /* Copy the numbered list to the original list of names. */ names = numbered_names; /* Note the number of available choices. */ libGlobal.disambigLen = names.length; } /* ask the question */ "\^<<q>>, <<orList(names)>>?\n"; } /* * Get the basic question for a noun role. This turns the verb around * into a question about one of its roles. For example, for (Open, * DirectObject), we'd return "what do you want to open". For (AttachTo * IndirectObject), "what do you want to connect it to". */ nounRoleQuestion(cmd, role) { /* get the missing query from the verb, and split into its parts */ local q = cmd.verbProd.missingQ.split(';'); /* pull out the appropriate question */ q = q[role == DirectObject ? 1 : role == IndirectObject ? 2 : 3]; /* the implied order of the object references is dobj-iobj-acc */ local others = [DirectObject, IndirectObject, AccessoryObject]; local otheridx = 1; /* set up the replacement function */ local f = function(match, idx, str) { /* get the explicit or implied other-object role */ local r; if (rexGroup(3) != nil) { r = rexGroup(3)[3]; r = (r == 'dobj' ? DirectObject : r == 'iobj' ? IndirectObject : AccessoryObject); } else { /* * no -role suffix, so it's implied: get the next role, but * skip the role we're asking about */ while ((r = others[otheridx++]) == role) ; } /* get the preposition, if supplied */ local prep = (rexGroup(1) != nil ? rexGroup(1)[3].substr(2) : ''); /* return the noun phrase */ return npListPronoun(rexGroup(2)[3], cmd.(r.npListProp), prep); }; /* substitute each other-object phrase and return the result */ return q.findReplace( R'(<lparen><alpha|space>+)?%<(it|that)(-<alpha>+)?%><rparen>?', f).trim(); } /* As for a missing literal for a LiteralAction or LiteralTAction */ askMissingLiteralQ(action, role) { "\^<<literalRoleQuestion(action, role)>>?\n"; } /* * Get the question to ask for a missing literal. We do this more straightforwardly than for a * missing noun, since the context usually provides less information. The action parameter can be * either the action we want to perform (as it is whenever called by the library) or a * correspondind Command object) allowed for consistancy with askMissingNoun). */ literalRoleQuestion(action, role) { /* If the action argument was provided as a Command, exttract the associated action. */ if(action.ofKind(Command)) action = action.action; /* get the missing query from the verb, and split into its parts */ local q = action.verbRule.missingQ.split(';'); /* pull out the appropriate question */ q = q[role == DirectObject ? 1 : role == IndirectObject ? 2 : 3]; /* Return the result */ return q; } /* * Announce our choice of object when askForIobj() or askForDobj() chooses the * best match on the player's behalf. We find the section of the verbPhrase * appropriate to the direct or indirect object (e.g. '(what)' or '(on what)') * and replace 'what' with the object name. */ announceBestChoice(action, obj, role) { /* The string to display the object announcement */ local ann; /* Get the verb phrase for the action */ local vp = action.verbRule.verbPhrase; /* * Set up a rex pattern to search for the shortest match to something in * parentheses. */ local pat = R'(<lparen>.*?<rparen>)'; /* Pull out the first parenthesised section */ local rm = rexSearch(pat, vp); /* * If we're looking for the indirect object pull out the next * parenthesized section */ if(role == IndirectObject) rm = rexSearch(pat, vp, rm[1] + rm[2]); /* Replace 'what' with the object name */ ann = rm[3].findReplace('what', obj.abcName(action, role)); /* * Although we probably want the announcement to be enclosed in parentheses, this should be * defined by the <.aasume> style tag, so we replace the surrounding parentheses with the * appopriate style tags. */ ann = ann.findReplace('(', '<.assume>'); ann = ann.findReplace(')', '<./assume>'); /* Display the annoucement */ "<<ann>>\n"; } /* * Get the pronoun for a resolved (or partially resolved) NounPhrase list * from a command. */ npListPronoun(pro, nplst, prep) { /* * the prep starts with '(', it means that we should omit this role * from queries about other roles */ if (prep.startsWith('(')) return ''; /* if there's no noun phrase, return nothing */ if (nplst.length() == 0) return ''; /* if we have more than one noun phrase, it's obviously 'them' */ if (nplst.length() > 1) return '<<prep>> them'; /* we have a single noun phrase - retrieve it */ local np = nplst[1]; /* if it explicitly refers to multiple objects, use 'them' */ if (np.matches.length() > 1 && np.isMultiple()) return '<<prep>> them'; /* run through the matches and check for genders */ local him = true, her = true, them = true; foreach (local m in np.matches) { if (!m.obj.isHim) him = nil; if (!m.obj.isHer) her = nil; if (!m.obj.plural) them = nil; } /* if all matches agree on a pronoun, use it, otherwise use 'it' */ if (them) return '<<prep>> them'; if (him) return '<<prep>> him'; if (her) return '<<prep>> her'; else return '<<prep>> <<pro>>'; } /* * The libMessages object contains a number of messages/string values needed * by the menu system and the WebUI. It is not used for any other library * messages. */ libMessages: object /* * Command key list for the menu system. This uses the format * defined for MenuItem.keyList in the menu system. Keys must be * given as lower-case in order to match input, since the menu * system converts input keys to lower case before matching keys to * this list. * * Note that the first item in each list is what will be given in * the navigation menu, which is why the fifth list contains 'ENTER' * as its first item, even though this will never match a key press. */ menuKeyList = [ ['q'], ['p', '[left]', '[bksp]', '[esc]'], ['u', '[up]'], ['d', '[down]'], ['ENTER', '\n', '[right]', ' '] ] /* link title for 'previous menu' navigation link */ prevMenuLink = '<font size=-1>Previous</font>' /* link title for 'next topic' navigation link in topic lists */ nextMenuTopicLink = '<font size=-1>Next</font>' /* * main prompt text for text-mode menus - this is displayed each * time we ask for a keystroke to navigate a menu in text-only mode */ textMenuMainPrompt(keylist) { "\bSelect a topic number, or press ‘<< keylist[M_PREV][1]>>’ for the previous menu or ‘<<keylist[M_QUIT][1]>>’ to quit:\ "; } /* prompt text for topic lists in text-mode menus */ textMenuTopicPrompt() { "\bPress the space bar to display the next line, ‘<b>P</b>’ to go to the previous menu, or ‘<b>Q</b>’ to quit.\b"; } /* * Position indicator for topic list items - this is displayed after * a topic list item to show the current item number and the total * number of items in the list, to give the user an idea of where * they are in the overall list. */ menuTopicProgress(cur, tot) { " [<<cur>>/<<tot>>]"; } /* * Message to display at the end of a topic list. We'll display * this after we've displayed all available items from a * MenuTopicItem's list of items, to let the user know that there * are no more items available. */ menuTopicListEnd = '[The End]' /* * Message to display at the end of a "long topic" in the menu * system. We'll display this at the end of the long topic's * contents. */ menuLongTopicEnd = '[The End]' /* * instructions text for banner-mode menus - this is displayed in * the instructions bar at the top of the screen, above the menu * banner area */ menuInstructions(keylist, prevLink) { "<tab align=right ><b>\^<<keylist[M_QUIT][1]>></b>=Quit <b>\^<< keylist[M_PREV][1]>></b>=Previous Menu<br> <<prevLink != nil ? aHrefAlt('previous', prevLink, '') : ''>> <tab align=right ><b>\^<<keylist[M_UP][1]>></b>=Up <b>\^<< keylist[M_DOWN][1]>></b>=Down <b>\^<< keylist[M_SEL][1]>></b>=Select<br>"; } /* show a 'next chapter' link */ menuNextChapter(keylist, title, hrefNext, hrefUp) { "Next: <<aHref(hrefNext, title)>>; <b>\^<<keylist[M_PREV][1]>></b>=<<aHref(hrefUp, 'Menu')>>"; } /* * Standard dialog titles, for the Web UI. These are shown in the * title bar area of the Web UI dialog used for inputDialog() calls. * These correspond to the InDlgIconXxx icons. The conventional * interpreters use built-in titles when titles are needed at all, * but in the Web UI we have to generate these ourselves. */ dlgTitleNone = 'Note' dlgTitleWarning = 'Warning' dlgTitleInfo = 'Note' dlgTitleQuestion = 'Question' dlgTitleError = 'Error' /* * Standard dialog button labels, for the Web UI. These are built in * to the conventional interpreters, but in the Web UI we have to * generate these ourselves. */ dlgButtonOk = 'OK' dlgButtonCancel = 'Cancel' dlgButtonYes = 'Yes' dlgButtonNo = 'No' /* web UI alert when a new user has joined a multi-user session */ webNewUser(name) { "\b[<<name>> has joined the session.]\n"; } /* * Warning prompt for inputFile() warnings generated when reading a * script file, for the Web UI. The interpreter normally displays * these warnings directly, but in Web UI mode, the program is * responsible, so we need localized messages. */ inputFileScriptWarning(warning, filename) { /* remove the two-letter error code at the start of the string */ warning = warning.substr(3); /* build the message */ return warning + ' Do you wish to proceed?'; } inputFileScriptWarningButtons = [ '&Yes, use this file', '&Choose another file', '&Stop the script'] /* Web UI inputFile error: uploaded file is too large */ webUploadTooBig = 'The file you selected is too large to upload.' ; /* * make an error message into a sentence, by capitalizing the first letter and * adding a period at the end if it doesn't already have one */ makeSentence(msg) { return rexReplace( ['^<space>*[a-z]', '(?<=[^.?! ])<space>*$'], msg, [{m: m.toUpper()}, '.']); } /* * The dummy object is used to provide a {dummy} parameter in a message * parameter substitution to provide a grammatical subject in a sentence for * which no actual in-game subject is available; e.g. "There {dummy}{is} * nothing here" provides a subject for {is} without displaying any text. */ modify dummy_ dummyName = '' name = '' noteName(src) { name = src; } ; /* * pluralDummy_ provides the same function as dummy_ when a plural subject is * required in a sentence. */ modify pluralDummy_ dummyName = '' name = '' noteName(src) { name = src; } plural = true ; /* * prevDummy_ is used by the {prev} message parameter substitution to enable a * subsequent verb to agree with a previous list defined through one of the * string templates. */ prevDummy_: Mentionable dummyName = '' name = '' noteName(src) { name = src; } plural = true ; /* * When the actor has multiple verbs per sentence, we can use this to keep the expansion on * track. */ actorActionContinuer_: dummy_ { person = (gActor == nil ? 3 : gActor.person) plural = (gActor == nil ? nil : gActor.plural) } /* ------------------------------------------------------------------------ */ /* * The message parameters object. The language module must provide one * instance of MessageParams, to fill in the language-specific list of * parameter names and expansion functions. * * [Required] */ englishMessageParams: MessageParams /* * The language's general sentence order. This should be a string * containing the letters S, V, and O in the appropriate order for * the language. S is for Subject, V is for Verb, and O is for * Object. For example, English is an SVO language, since the * general order of an English sentence is Subject Verb Object. * * This can be left nil for languages with no prevailing sentence * order. */ sentenceOrder = 'SVO' /* * The English parameter mappings. The base library doesn't use any * of these directly, so parameter names and mappings are entirely up * to the language module. The only part of the library that uses * the parameters is the library message strings, which are all * defined by the language module itself. Other translations are * free to use different parameter names, and don't have to replicate * 1-for-1 the parameters defined for English. Translations only * have to define the parameters needed for their own library * messages (plus any others they want to provide for use by game * authors, of course). * * [Required] */ params = [ /* {lb} is a literal left brace */ [ 'lb', { ctx, params: '{' } ], /* {rb} is a literal right brace */ [ 'rb', { ctx, params: '}' } ], /* {bar} is a literal vertical bar */ [ 'bar', { ctx, params: '|' } ], /* * {s}, {es}, and {ies} are context-sensitive plural suffixes: * these expand to nothing if the previous parameter was * singular, or 's', 'es', and 'ies' if plural. If the previous * parameter was a numeric value, 1 is singular and anything else * is plural; if it was a Mentionable, the 'plural' property * determines it. */ [ 's', { ctx, params: ctx.lastParamPlural() ? 's' : '' } ], [ 'es', { ctx, params: ctx.lastParamPlural() ? 'es' : '' } ], [ 'ies', { ctx, params: ctx.lastParamPlural() ? 'ies' : 'y' } ], /* {1} through {9} substitute literal text string arguments 1-9 */ [ '1', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[1])) } ], [ '2', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[2])) } ], [ '3', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[3])) } ], [ '4', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[4])) } ], [ '5', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[5])) } ], [ '6', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[6])) } ], [ '7', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[7])) } ], [ '8', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[8])) } ], [ '9', { ctx, params: ctx.paramToString(ctx.noteParam(ctx.args[9])) } ], /* {# n} - spells the number given by integer argument n (1-9) */ [ '#', { ctx, params: spellNumber(ctx.paramToNum(ctx.noteParam( ctx.args[toInteger(params[2])]))) } ], /* * {and n} - shows the list given by integer argument n (1-9) as * a basic "and" list ("x, y, and z") */ [ 'and', { ctx, params: andList(ctx.noteParam(ctx.args[toInteger(params[2])]) .mapAll({ x: ctx.paramToString(x) })) } ], /* * {or n} - shows the list given by integer argument n (1-9) as a * basic "or" list ("x, y, or z") */ [ 'or', { ctx, params: orList(ctx.noteParam(ctx.args[toInteger(params[2])]) .mapAll({ x: ctx.paramToString(x) })) } ], /* {I} is the addressee's name in subjective case */ [ 'i', { ctx, params: cmdInfo(ctx, &actor, &theName, vSubject) } ], /* {me} is the addressee's name in objective case */ [ 'me', { ctx, params: cmdInfo(ctx, &actor, &theObjName, vObject) } ], /* {my} is a possessive adjective for the addressee */ [ 'my', { ctx, params: cmdInfo(ctx, &actor, &possAdj, vPossessive) } ], /* {mine} is a possessive noun for the addressee */ [ 'mine', { ctx, params: cmdInfo(ctx, &actor, &possNoun, vPossessive) } ], /* {myself} is the reflexive pronoun for the addressee */ [ 'myself', { ctx, params: cmdInfo(ctx, &actor, &reflexiveName, vObject) } ], /* {we} is the addressee's name in subjective case */ [ 'we', { ctx, params: cmdInfo(ctx, &actor, &theName, vSubject) } ], /* {us} is the addressee's name in objective case */ [ 'us', { ctx, params: cmdInfo(ctx, &actor, &theObjName, vObject) } ], /* {our} is a possessive adjective for the addressee */ [ 'our', { ctx, params: cmdInfo(ctx, &actor, &possAdj, vPossessive) } ], /* {ours} is a possessive noun for the addressee */ [ 'ours', { ctx, params: cmdInfo(ctx, &actor, &possNoun, vPossessive) } ], /* {ourselves} is the reflexive pronoun for the addressee */ [ 'ourselves', { ctx, params: cmdInfo(ctx, &actor, &reflexiveName, vObject) } ], /* * {dummy} provides a singular subject for an ensuing verb without * displaying any text */ [ 'dummy', { ctx, params: cmdInfo(ctx, dummy_, &dummyName, vSubject) } ], /* * {sing} provides a singular subject for an ensuing verb without * displaying any text */ [ 'sing', { ctx, params: cmdInfo(ctx, dummy_, &dummyName, vSubject) } ], /* * {plural} provides a plural subject for an ensuing verb without * displaying any text */ [ 'plural', { ctx, params: cmdInfo(ctx, pluralDummy_, &dummyName, vSubject) } ], /* * {prev} provides a singular or plural subject for an ensuing verb, * matching the plurarity of a previous list, without displaying any * text */ [ 'prev', { ctx, params: cmdInfo(ctx, prevDummy_, &dummyName, vSubject) } ], /* * {aac} can be used to enure multiple verbs following the same actor continue to agree in * person and number with the actor, e.g. "{I} yell{s/ed} and {aac} drop{s/?ed} my guard * and {aac} scream{s/ed}. */ [ 'aac', { ctx, params: cmdInfo(ctx, actorActionContinuer_, &dummyName, vSubject) } ], /* * {here} is the addressee's location, relative to the PC's. * * If the actor is the PC, this translates to "here" for present * tense, and "there" for other tenses. We use "there" for * tenses other than the present because other tenses impose a * distance between the narration and the events. Once the * narration is separated from the events in time, it's * implicitly separated in space as well. Saying "here" in the * past tense seems to imply that the narrator is standing at a * later time in the same spot where the events took place. * Switching to "there" fixes this, by making the spatial locale * of the narration indeterminate the same way the tense makes * the temporal locale indeterminate. * * If the actor is an NPC, this translates to nothing at all (we * use the special "backspace" notation to delete any preceding * space). We could say something like "there" or "in the * kitchen" or "where Bob is", but it usually seems more pleasing * to say nothing at all in these cases. When another actor is * specifically mentioned, the implication that we're talking * about that actor's location is usually strong enough that it * seems redundant to state it explicitly. */ [ 'here', { ctx, params: ctx.actorIsPC() ? (Narrator.tense == Present ? 'here' : 'there') : '\010' } ], /* * {then} translates to "now" if we're in the present tense, * "then" otherwise. */ [ 'then', { ctx, params: Narrator.tense == Present ? 'now' : 'then' } ], /* * {now} translates to "now" if we're in the present tense, * nothing otherwise. There are times when we want to add "now" * to a thought, but more as a flavor particle than as a precise * specification of time: "You can't do that now". The flavor * particle doesn't seem to have an equivalent in other tenses, * so it's better to just leave it out entirely: "You couldn't do * that". */ [ 'now', { ctx, params: Narrator.tense == Present ? 'now' : '\010' } ], /* * {the subj obj} - name, subjective case *. {the obj} - name, objective case *. {the obj's} - name, possessive case */ [ 'the', function(ctx, params) { if (params[2] == 'subj') return cmdInfo(ctx, params[3], &theName, vSubject); else if (params[2].endsWith('\'s')) return cmdInfo(ctx, params[2].left(-2), &possAdj, vObject); else return cmdInfo(ctx, params[2], &theObjName, vAmbig); } ], /* * {a subj obj} - aName, subjective case *. {a obj} - name, objective case */ [ 'a', function(ctx, params) { if (params[2] == 'subj') return cmdInfo(ctx, params[3], &aName, vSubject); else return cmdInfo(ctx, params[2], &aName, vAmbig); } ], /* * {an subj obj} - aName, subjective case *. {an obj} - name, objective case */ [ 'an', function(ctx, params) { if (params[2] == 'subj') return cmdInfo(ctx, params[3], &aName, vSubject); else return cmdInfo(ctx, params[2], &aName, vAmbig); } ], /* * {in obj} - the appropriate containment preposition (in, on, under * or behind) followed by the theName of the object (e.g. 'in the * bath') */ [ 'in', { ctx, params: cmdInfo(ctx, params[2], &objInName, vObject) } ], /* {inprep obj} the objInPrep (in, on, under or behind) of obj */ ['inprep' , { ctx, params: cmdInfo(ctx, params[2], &objInPrep, vObject) } ], /* * {into obj} - the appropriate movement preposition (inro, onto, under or * behind) followed by the theName of the object (e.g. 'into the bath') */ [ 'into', { ctx, params: cmdInfo(ctx, params[2], &objIntoName, vObject) } ], /* * {outof obj} the appropriate exit preposition followed the theName of the obj, e.g. 'out of the * bath' */ ['outof', { ctx, params: cmdInfo(ctx, params[2], &objOutOfName, vObject) } ], /* {he obj} - pronoun, subjective case */ [ 'he', { ctx, params: cmdInfo(ctx, params[2], &heName, vSubject) } ], /* {she obj} - pronoun, subjective case */ [ 'she', { ctx, params: cmdInfo(ctx, params[2], &heName, vSubject) } ], /* {they obj} - pronoun, subjective case */ [ 'they', { ctx, params: cmdInfo(ctx, params[2], &heName, vSubject) } ], /* {him obj} - pronoun, objective case */ [ 'him', { ctx, params: cmdInfo(ctx, params[2], &himName, vObject) } ], /* {them obj} - pronoun, objective case */ [ 'them', { ctx, params: cmdInfo(ctx, params[2], &himName, vObject) } ], /* {her obj} - possessive adjective pronoun (my, his, her) */ [ 'her', { ctx, params: cmdInfo(ctx, params[2], &herName, vPossessive) } ], /* {his obj} - possessive adjective pronoun (my, his, her) */ [ 'his', { ctx, params: cmdInfo(ctx, params[2], &herName, vPossessive) } ], /* {its obj} - possessive adjective pronoun (my, his, her) */ [ 'its', { ctx, params: cmdInfo(ctx, params[2], &herName, vPossessive) } ], /* {their obj} - possessive adjective pronoun (my, his, her) */ [ 'their', { ctx, params: cmdInfo(ctx, params[2], &herName, vPossessive) } ], /* {hers obj} - possessive noun pronoun (mine, his, hers) */ [ 'hers', { ctx, params: cmdInfo(ctx, params[2], &hersName, vPossessive) } ], /* {theirs obj} - possessive noun pronoun (mine, his, hers) */ [ 'theirs', { ctx, params: cmdInfo(ctx, params[2], &hersName, vPossessive) } ], /* {herself obj} - reflexive pronouns (itself, herself, himself) */ [ 'herself', { ctx, params: cmdInfo(ctx, params[2], &reflexiveName, vObject) } ], [ 'himself', { ctx, params: cmdInfo(ctx, params[2], &reflexiveName, vObject) } ], [ 'itself', { ctx, params: cmdInfo(ctx, params[2], &reflexiveName, vObject) } ], [ 'themselves', { ctx, params: cmdInfo(ctx, params[2], &reflexiveName, vObject) } ], /* *. {that subj obj} - demonstrative pronoun, subjective (that, those) *. {that obj} - demonstrative pronoun, objective */ [ 'that', function(ctx, params) { if (params[2] == 'subj') return cmdInfo(ctx, params[3], &thatName, vSubject); else return cmdInfo(ctx, params[2], &thatObjName, vObject); } ], /* To Be verbs */ [ 'am', conjugateBe ], [ 'are', conjugateBe ], [ 'is', conjugateBe ], [ 'isn\'t', conjugateIsnt ], [ 'aren\'t', conjugateIsnt ], [ 'amn\'t', conjugateIsnt ], [ '\'m', conjugateIm ], [ '\'re', conjugateIm ], [ '\'s', conjugateIm ], [ 'was', conjugateWas], [ 'were', conjugateWas], [ 'wasn\'t', conjugateWasnt], [ 'weren\'t', conjugateWasnt], [ 'wasnot', conjugateWasnot], [ 'werenot', conjugateWasnot], /* {don't <verb>} - the second token is the verb infinitive */ [ 'don\'t', conjugateDont ], [ 'doesn\'t', conjugateDont ], [ 'doesnot', conjugateDoNot], [ 'donot', conjugateDoNot], /* Have verbs */ ['\'ve', conjugateIve ], ['haven\'t', conjugateHavnt ], ['hasn\'t', conjugateHavnt ], ['havenot', conjugateHavenot ], ['hasnot', conjugateHavenot ], /* {can}, {cannot}, {can't} */ [ 'can', { ctx, params: conjugateCan( ctx, params, conjugateBe, 'can', 'could') } ], [ 'cannot', { ctx, params: conjugateCan( ctx, params, conjugateBeNot, 'cannot', 'could not') } ], [ 'can\'t', { ctx, params: conjugateCan( ctx, params, conjugateIsnt, 'can\'t', 'couldn\'t') } ], /* {must <verb>} - the second token is a verb infinitive */ [ 'must', { ctx, params: conjugateMust(ctx, params) } ], [ 'actionliststr', {ctx, params: gActionListStr } ], /* conj <verb> <type> congugate a regular verb */ ['conj', {ctx, params: conjugateRegular(ctx, params) } ], ['posture', {ctx, params: ctx.subj.postureDesc } ] ] /* * Check for reflexives in cmdInfo. This is called when we see a * noun phrase being used as an object of the verb (i.e., in a role * other than as the subject of the verb). If appropriate, we can * return a reflexive pronoun instead of the noun we'd normally * generate. If no reflexive is required, we return nil, and the * caller will use the normal noun or pronoun instead. */ cmdInfoReflexive(ctx, srcObj, objProp) { /* * if this object has already been used in the sentence in an * objective role, use a reflexive pronoun instead */ /* * Note, this seems to produce rather odd results, so I'll try * commenting it out. */ if (ctx.reflexiveAnte.indexOf(srcObj) != nil) return srcObj.pronoun().reflexive.name; /* it's not reflexive - use the normal noun or pronoun */ return nil; } /* * On construction, fill in the verb parameters from CustomVocab * objects. */ construct() { /* create the verb form table */ verbTab = new LookupTable(128, 256); /* add the verb parameters for all CustomVocab objects */ forEachInstance(CustomVocab, function(cv) { /* set up a vector for the mapping */ local vec = new Vector(cv.verbParams.length() * 2); /* set up a mapping for each verb */ foreach (local p in cv.verbParams) { /* tokenize the verb conjugation string */ local toks = p.split('/').mapAll({ s: s.trim() }); /* * if there are only three elements (infinite, present3, * and past), the participle is identical to the past */ if (toks.length() == 3) toks += toks[3]; /* add the verb forms to the verb table */ verbTab[toks[1]] = toks; verbTab[toks[2]] = toks; /* * in the main parameters table, point the verb to the * generic regular verb conjugator */ vec.append([toks[1], conjugate]); vec.append([toks[2], conjugate]); } /* add all of the verb mappings to our parameter list */ params += vec; }); /* * Do the base class construction. Note that we had to wait to * do this *after* we scan the CustomVocab objects, because the * inherited constructor will populate the param table from the * param list. */ inherited(); } /* verb table - we build this at preinit from the verb parameters */ verbTab = nil /* * Word-ending letter combinations that are awkward to follow with a * contracted verb such a 've. */ sLetters = ['s', 'x', 'z', 'sh', 'ch'] /* * Does nam end with one of the letter combinations in sLetters, in which * case it's awkward to follow it with a contraction. */ awkwardEnding(nam) { return sLetters.indexWhich({lts: nam.endsWith(lts)})!= nil; } ; /* * Regular verb conjugator. This takes the list of CustomVocab * verbParmas tokens built during preinit, and returns the * appropriate conjugation for the subject and tense. */ conjugate(ctx, params) { /* get the list of forms for the verb */ local toks = englishMessageParams.verbTab[params[1]]; if (toks == nil) return nil; /* * get the present tense index: third-person singular has the second * slot, all other forms have the first slot */ local idx = ctx.subjEffectivelyPlural || ctx.subj.person != 3 ? 1 : 2; switch (Narrator.tense) { case Present: /* "I go"/"he goes" - return the appropriate present token */ return toks[idx]; case Past: /* "I went" - all persons and numbers use the same past form */ return toks[3]; case Perfect: /* "I have gone" - "have" plus the participle */ return ['have ', 'has '][idx] + toks[4]; case PastPerfect: /* "I had gone" - "had" plus the participle */ return 'had <<toks[4]>>'; case Future: /* "I will go" - "will" plus the infinitive */ return 'will <<toks[1]>>'; case FuturePerfect: /* "I will have gone" - "will have" plus the participle */ return 'will have <<toks[4]>>'; } return nil; } /* * Language specific adjustments to a string applied before the message * processor looks for parameter substitutions. Here we scan txt for sequences * like 'verb{s/d}' and convert them to '{conj verb s/d}', which the parameter * substitution mechanism can then deal with. */ langAdjust(txt) { if(txt == nil) return ''; for(;;) { local rf = rexSearch( R'<lbrace>(s/d|s/ed|es/ed|ies/ied|s/<question>ed)<rbrace>', txt); if(rf == nil) break; local idx = rf[1]; local str = rf[3]; local rootStart = idx; while(rootStart > 1) { if(txt.substr(--rootStart, 1) is in (' ', '}')) break; } local verbRoot = txt.substr(rootStart + 1, idx - rootStart - 1); txt = txt.findReplace(verbRoot+str, '{conj ' + verbRoot + ' ' + str.substr(2)); } return txt; } /* Conjugate a regular verb */ conjugateRegular(ctx, params) { local thirdPresentEnding = ''; local participleEnding = ''; local root = params[2]; switch(params[3]) { case 's/d': thirdPresentEnding = 's'; participleEnding = 'd'; break; case 's/ed': thirdPresentEnding = 's'; participleEnding = 'ed'; break; case 'es/ed': thirdPresentEnding = 'es'; participleEnding = 'ed'; break; case 'ies/ied': thirdPresentEnding = 'ies'; participleEnding = 'ied'; if(root.endsWith('y')) root = root.substr(1, root.length - 1); else params[2] = root + 'y'; break; case 's/?ed': thirdPresentEnding = 's'; participleEnding = root.substr(root.length, 1) + 'ed'; break; } switch (Narrator.tense) { case Present: return !ctx.subjEffectivelyPlural && ctx.subj.person == 3 ? root + thirdPresentEnding : params[2]; case Past: return root + participleEnding; case Perfect: return (!ctx.subjEffectivelyPlural && ctx.subj.person == 3 ? 'has ' : 'have ') + root + participleEnding; case PastPerfect: return 'had ' + root + participleEnding; case Future: return (ctx.subj.person == 1 ? 'shall ' : 'will ') + params[2]; case FuturePerfect: return (ctx.subj.person == 1 ? 'shall have ' : 'will have ') + params[2]; } return nil; } /* * Form the past participle of a verb, which may either be given in the form * xxx, in which case we assume it's an irregular verb to be looked up in the * table of irregular verbs, or in the form xxx[y/z], in which case we assume * it's a regular verb to be conjugated according to the [y/z] ending. */ pastParticiple(verb) { local b1 = verb.find('['); local b2 = verb.find(']'); if(b1 != nil && b2 != nil && b2 > b1) { local stem = verb.substr(1, b1 - 1); local suffix = verb.substr(b1 + 1, b2 - b1 - 1); local dash = suffix.find('/'); local ending = suffix; if(dash) { ending = suffix.substr(dash + 1, suffix.length - 2); } if(ending.startsWith('?')) { stem = stem + stem.substr(-1, 1); ending = ending.substr(2); } return stem + ending; } else return englishMessageParams.verbTab[verb][4]; } /* * Conjugate "to be". This is a handler function for message parameter * expansion, for the "be" verb parameters ({am}, {is}, {are}). */ conjugateBe(ctx, params) { /* * get the present/past conjugation index for the grammatical person * and number: [I am, you are, he/she/it is, we are, you are, they * are] */ local idx = ctx.subjEffectivelyPlural ? 4 : ctx.subj.person; /* * for other tenses, the conjugation boils down to at most two * options: third person singular and everything else */ local idx2 = ctx.subj.person == 3 && !ctx.subj.plural ? 2 : 1; /* look up the conjugation in the current tense */ switch (Narrator.tense) { case Present: return ['am', 'are', 'is', 'are'][idx]; case Past: return ['was', 'were', 'was', 'were'][idx]; case Perfect: return ['have been', 'has been'][idx2]; case PastPerfect: return 'had been'; case Future: return 'will be'; case FuturePerfect: return 'will have been'; } return nil; } /* * Conjugate "to be" in negative senses. This is a handler function for * message parameter expansion, for auxiliaries with negative usage * (cannot, will not, etc). */ conjugateBeNot(ctx, params) { /* * get the present/past conjugation index for the grammatical person * and number: [I am, you are, he/she/it is, we are, you are, they * are] */ local idx = ctx.subjEffectivelyPlural ? 4 : ctx.subj.person; /* * for other tenses, the conjugation boils down to at most two * options: third person singular and everything else */ local idx2 = ctx.subj.person == 3 && !ctx.subj.plural ? 2 : 1; /* look up the conjugation in the current tense */ switch (Narrator.tense) { case Present: return '<<['am', 'are', 'is', 'are'][idx]>> not'; case Past: return '<<['was', 'were', 'was', 'were'][idx]>> not'; case Perfect: return ['have not been', 'has not been'][idx2]; case PastPerfect: return 'had not been'; case Future: return 'will not be'; case FuturePerfect: return 'will not have been'; } return nil; } /* * Conjugate "isn't". This is a handler function for message parameter * expansion, for the "be" verb parameters with "not" contractions ({am * not}, {isn't}, {aren't}). */ conjugateIsnt(ctx, params) { /* * get the present/past conjugation index for the grammatical person * and number: [I am, you are, he/she/it is, we are, you are, they * are] */ local idx = ctx.subjEffectivelyPlural ? 4 : ctx.subj.person; /* * for other tenses, the conjugation boils down to at most two * options: third person singular and everything else */ local idx2 = ctx.subj.person == 3 && !ctx.subjEffectivelyPlural ? 2 : 1; /* look up the conjugation in the current tense */ switch (Narrator.tense) { case Present: return ['am not', 'aren\'t', 'isn\'t', 'aren\'t'][idx]; case Past: return ['wasn\'t', 'weren\'t', 'wasn\'t', 'weren\'t'][idx]; case Perfect: return ['haven\'t been', 'hasn\'t been'][idx2]; case PastPerfect: return 'hadn\'t been'; case Future: return 'won\'t be'; case FuturePerfect: return 'won\'t have been'; } return nil; } /* * Conjugate "to be" contractions - I'm, you're, he's, she's, etc. This * is a handler function for message parameter expansion, for the "be" * verb parameters with contraction ({I'm}, {he's}, {you're}). */ conjugateIm(ctx, params) { /* * get the present/past conjugation index for the grammatical person * and number: [I am, you are, he/she/it is, we are, you are, they * are] */ local idx = ctx.subjEffectivelyPlural ? 4 : ctx.subj.person; /* * for other tenses, the conjugation boils down to at most two * options: third person singular and everything else */ local idx2 = ctx.subj.person == 3 && !ctx.subjEffectivelyPlural ? 2 : 1; /* look up the conjugation in the current tense */ switch (Narrator.tense) { case Present: return ['\'m', '\'re', '\'s', '\'re'][idx]; case Past: return [' was', ' were', ' was', ' were'][idx]; case Perfect: return ['\'ve been', '\'s been'][idx2]; case PastPerfect: return '\'d been'; case Future: return '\'ll be'; case FuturePerfect: return '\'ll have been'; } return nil; } /* * Conjugate the past tense of "to be" where we want to use the past tense in * a present context (e.g. "You can see that Kilroy was here. "). This is a * handler function for {was} or {were}. */ conjugateWas(ctx, params) { switch (Narrator.tense) { case Present: return ctx.subjEffectivelyPlural || ctx.subj.person == 2 ? 'were' : 'was'; case Past: case Perfect: case PastPerfect: return 'had been'; case Future: case FuturePerfect: return 'will have been'; } return nil; } conjugateWasnt(ctx, params) { switch (Narrator.tense) { case Present: return ctx.subjEffectivelyPlural || ctx.subj.person == 2 ? 'weren\'t' : 'wasn\'t'; case Past: case Perfect: case PastPerfect: return 'hadn\'t been'; case Future: case FuturePerfect: return 'won\'t have been'; } return nil; } conjugateWasnot(ctx, params) { switch (Narrator.tense) { case Present: return ctx.subj.plural || ctx.subj.person == 2 ? 'were not' : 'was not'; case Past: case Perfect: case PastPerfect: return 'had not been'; case Future: case FuturePerfect: return 'will not have been'; } return nil; } /* * Conjugate 've (contraction of have). After some words it's better not to * contract (e.g. Liz's or Jesus'd is a a bit awkward) so we use the full * 'have' or 'had' form for such subjects and the contracted form otherwise. */ conjugateIve(ctx, params) { local fullForm = englishMessageParams.awkwardEnding(ctx.subj.name); switch (Narrator.tense) { case Present: if(fullForm) return !ctx.subjEffectivelyPlural && ctx.subj.person == 3 ? ' has' : ' have'; else return !ctx.subjEffectivelyPlural && ctx.subj.person == 3 ? '\'s' : '\'ve'; case Past: case Perfect: case PastPerfect: return fullForm ? ' had' : '\'d'; case Future: case FuturePerfect: return fullForm ? ' will have' : '\'ll have'; } return nil; } /* Conjugate haven\'t (contracted have not) */ conjugateHavnt(ctx, params) { switch (Narrator.tense) { case Present: return !ctx.subjEffectivelyPlural && ctx.subj.person == 3 ? 'hasn\'t' : 'haven\'t'; case Past: case Perfect: case PastPerfect: return 'hadn\'t'; case Future: case FuturePerfect: return 'won\'t have' ; } return nil; } conjugateHavenot(ctx, params) { switch (Narrator.tense) { case Present: return !ctx.subjEffectivelyPlural && ctx.subj.person == 3 ? 'has not' : 'have not'; case Past: case Perfect: case PastPerfect: return 'had not'; case Future: case FuturePerfect: return 'will not have' ; } return nil; } /* * Conjugate "don't" plus a verb. "Don't" is always an auxiliary in * English, and has an irregular structure in different tenses. The * second token in the {don't x} phrase is the main verb we're * auxiliarizing. */ conjugateDont(ctx, params) { /* get the present index - don't vs doesn't */ local idx = ctx.subj.person == 3 && !ctx.subjEffectivelyPlural ? 2 : 1; /* get the infinitive form by splitting off any [x/y] ending */ local inf = params[2].split('[')[1]; /* look up the conjugation in the current tense */ switch (Narrator.tense) { case Present: /* I don't see that here */ return ['don\'t ', 'doesn\'t '][idx] + inf; case Past: /* I didn't see that here */ return 'didn\'t <<inf>>'; case Perfect: /* I don't see -> I haven't seen */ return ['haven\'t ', 'hasn\'t '][idx] + pastParticiple(params[2]); // case PastPerfect: /* I don't see -> I hadn't seen */ return 'hadn\'t <<pastParticiple(params[2])>>'; case Future: /* I don't see -> I won't see */ return 'won\'t <<inf>>'; case FuturePerfect: /* I don't see -> I won't have seen */ return 'won\'t have <<pastParticiple(params[2])>>'; } return nil; } /* * Conjugate "do not" plus a verb. "Do not" is always an auxiliary in * English, and has an irregular structure in different tenses. The * second token in the {donot x} phrase is the main verb we're * auxiliarizing. */ conjugateDoNot(ctx, params) { /* get the present index - don't vs doesn't */ local idx = ctx.subj.person == 3 && !ctx.subjEffectivelyPlural ? 2 : 1; /* get the infinitive form by splitting off any [x/y] ending */ local inf = params[2].split('[')[1]; /* look up the conjugation in the current tense */ switch (Narrator.tense) { case Present: /* I do not see that here */ return ['do not ', 'does not '][idx] + inf; case Past: /* I did not see that here */ return 'did not <<inf>>'; case Perfect: /* I do not see -> I have not seen */ return ['have not ', 'has not '][idx] + pastParticiple(params[2]); // case PastPerfect: /* I do not see -> I had not seen */ return 'had not <<pastParticiple(params[2])>>'; case Future: /* I do not see -> I will not see */ return 'will not <<inf>>'; case FuturePerfect: /* I do not see -> I will not have seen */ return 'will not have <<pastParticiple(params[2])>>'; } return nil; } /* * Conjugate 'can', 'can\'t', or 'cannot'. In the present, these are * variations on "I can"; in the past, "I could"; in all other tenses, * these change to conjugations of "to be able to": I have been able to, * I had been able to, I will be able to, I will have been able to. */ conjugateCan(ctx, params, beFunc, present, past) { switch (Narrator.tense) { case Present: return present; case Past: return past; case Perfect: case PastPerfect: case Future: case FuturePerfect: return '<<beFunc(ctx, params)>> able to '; } return nil; } /* * Conjugate "must" plus a verb. In the present, this is "I must <x>"; * in other tenses, this is a conjugation of "to have to <x>": I had to * <x>, I have to have <xed>, I had to have <xed>, I will have to <x>, I * will have to have <xed>. */ conjugateMust(ctx, params) { local inf = params[2].split('[')[1]; local part = pastParticiple(params[2]); local idx = ctx.subj.person == 3 && !ctx.subjEffectivelyPlural ? 2 : 1; switch (Narrator.tense) { case Present: return 'must <<inf>>'; case Past: return 'had to <<inf>>'; case Perfect: return ['have to have ', 'has to have '][idx] + part; case PastPerfect: return 'had to have <<part>>'; case Future: return 'will have to <<inf>>'; case FuturePerfect: return 'will have to have <<part>>'; } return nil; } /* * Language-specific modifications to Action classes principally to enable the * construction of implicit action announcements. */ modify Action getVerbPhrase(inf, ctx) { /* * parse the verbPhrase into the parts before and after the * slash, and any additional text following the slash part */ rexMatch('(.*)/(<alphanum|-|squote>+)(.*)', verbRule.verbPhrase); /* return the appropriate parts */ if (inf) { /* * infinitive - we want the part before the slash, plus the * extra prepositions (or whatever) after the switched part */ return rexGroup(1)[3] + rexGroup(3)[3]; } else { /* participle - it's the part after the slash */ return rexGroup(2)[3] + rexGroup(3)[3]; } } /* * Flag - do we want to show implicit action reports for this action? By * default we do. */ reportImplicitActions = true /* * Construct the announcement of an implicit action according to whether * the implict action succeeds (success = true) or fails (success = nil) * * [Required] */ buildImplicitActionAnnouncement(success, clearReports = true) { /* * If we don't want to show implicit action reports for this action, * then don't do anything at all here; simply return an empty string * at once. */ if(!reportImplicitActions) return ''; local rep = ''; local cur; /* * If the current action is an implicit action, add it to the list of * implicit action reports, either in the participle form if it's a * success (e.g. 'opening the door') or in the infinitive form * preceded by 'trying' if it's a failure (e.g. 'trying to open the * door') */ if(isImplicit) { cur = implicitAnnouncement(success); if(cur != nil) gCommand.implicitActionReports += cur; } /* * If this implicit action failed we need to report this implicit * action. If we're not an implicit action we need to report the * previous set of implicit actions, if there are any. */ if((success == nil || !isImplicit) && gCommand.implicitActionReports.length > 0) { local lst = mergeDuplicates(gCommand.implicitActionReports); /* * Begin our report with an opening parenthesis and the word * 'first' */ rep = BMsg(implicit action report start, '<.assume>first '); /* * Then go through all the implicit action reports on the current * Command object, adding them to our report string. */ for (cur in lst, local i = 1 ;; ++i) { rep += cur; /* * if we haven't reached the last report, add ', then ' to * separate one report from the next */ if(i < lst.length) rep += BMsg(implicit action report separator, ' then '); } local prp = getPostImplicitReports(); if(clearReports) { /* We're done with this list of reports, so clear them out */ gCommand.implicitActionReports = []; gCommand.postImplicitReports = []; } /* Return the completed implicit action report */ return rep + BMsg(implicit action report terminator, '<./assume>\n') + prp; } /* * if we don't need to report anything, return an empty string, since * this routine may have been called speculatively to see if there was * anything to report. */ return ''; } /* * Return a string giving the implicit action announcement for the current * action according to whether it's a success (e.g. "taking the spoon") or * a failure (e.g. "trying to take the spoon"). We make this a separate * method to make it a little easier for game code to customize implicit * action announcements. * * A return value of nil will suppress the implicit action report for this * action altogeher. */ implicitAnnouncement(success) { return success ? getVerbPhrase(nil, nil) : BMsg(implicit action report failure, 'trying to ') + getVerbPhrase(true, nil); } /* add a space prefix/suffix to a string if the string is non-empty */ spPrefix(str) { return (str == '' ? str : ' ' + str); } spSuffix(str) { return (str == '' ? str : str + ' '); } /* * Announce an object (for use to introduce a report on what an action * does to particular object when it's one of a number of objects the * actions is acting upon) */ announceObject(obj) { "<.announceObj><<obj.name>>:<./announceObj> "; } ; modify TAction /* * The verbPhrase to use for this object when constructing implicit action reports. By default * we just return the verbPhrase defined on our verbRule but game code can override this to * return a custom verbPhrase for a particular object (or, on a TIAction, combination of * objects). */ vPhrase(dobj, iobj?) { return verbRule.verbPhrase; } /* get the verb phrase in infinitive or participle form */ getVerbPhrase(inf, ctx) { local dobj; local dobjText; local dobjIsPronoun; local ret; /* get the direct object */ dobj = curDobj; /* Assume the direct object is not a pronoun */ dobjIsPronoun = nil; /* get the direct object name */ dobjText = dobj.theName; /* get the phrasing */ // ret = getVerbPhrase1(inf, verbRule.verbPhrase, dobjText, dobjIsPronoun); ret = getVerbPhrase1(inf, vPhrase(dobj), dobjText, dobjIsPronoun); /* return the result */ return ret; } /* * Given the text of the direct object phrase, build the verb phrase * for a one-object verb. This is a class method that can be used by * other kinds of verbs (i.e., non-TActions) that use phrasing like a * single object. * * 'inf' is a flag indicating whether to use the infinitive form * (true) or the present participle form (nil); 'vp' is the * verbPhrase string; 'dobjText' is the direct object phrase's text; * and 'dobjIsPronoun' is true if the dobj text is rendered as a * pronoun. */ getVerbPhrase1(inf, vp, dobjText, dobjIsPronoun) { local ret; local dprep; local vcomp; /* * parse the verbPhrase: pick out the 'infinitive/participle' * part, the complementizer part up to the '(what)' direct * object placeholder, and any preposition within the '(what)' * specifier */ rexMatch('(.*)/(<alphanum|-|squote>+)(.*) ' + '<lparen>(.*?)<space>*?<alpha>+<rparen>(.*)', vp); /* start off with the infinitive or participle, as desired */ if (inf) ret = rexGroup(1)[3]; else ret = rexGroup(2)[3]; /* get the prepositional complementizer */ vcomp = rexGroup(3)[3]; /* get the direct object preposition */ dprep = rexGroup(4)[3]; /* * if the direct object is not a pronoun, put the complementizer * BEFORE the direct object (the 'up' in "PICKING UP THE BOX") */ if (!dobjIsPronoun) ret += spPrefix(vcomp); /* add the direct object preposition */ ret += spPrefix(dprep); /* add the direct object, using the pronoun form if applicable */ ret += ' ' + dobjText; /* * if the direct object is a pronoun, put the complementizer * AFTER the direct object (the 'up' in "PICKING IT UP") */ if (dobjIsPronoun) ret += spPrefix(vcomp); /* * if there's any suffix following the direct object * placeholder, add it at the end of the phrase */ ret += rexGroup(5)[3]; /* return the complete phrase string */ return ret; } ; modify TIAction /* get the verb phrase in infinitive or participle form */ getVerbPhrase(inf, ctx) { local dobj, dobjText, dobjIsPronoun; local iobj, iobjText; local ret; /* get the direct object information */ dobj = curDobj; dobjText = dobj.theName; dobjIsPronoun = nil; /* get the indirect object information */ iobj = curIobj; iobjText = (iobj != nil ? iobj.theName : nil); /* get the phrasing */ ret = getVerbPhrase2(inf, vPhrase(dobj, iobj), dobjText, dobjIsPronoun, iobjText); // ret = getVerbPhrase2(inf, verbRule.verbPhrase, // dobjText, dobjIsPronoun, iobjText); /* return the result */ return ret; } /* * Get the verb phrase for a two-object (dobj + iobj) phrasing. This * is a class method, so that it can be reused by unrelated (i.e., * non-TIAction) classes that also use two-object syntax but with * other internal structures. This is the two-object equivalent of * TAction.getVerbPhrase1(). */ getVerbPhrase2(inf, vp, dobjText, dobjIsPronoun, iobjText) { local ret; local vcomp; local dprep, iprep; /* parse the verbPhrase into its component parts */ rexMatch('(.*)/(<alphanum|-|squote>+)(?:<space>+(<^lparen>*))?' + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>' + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>', vp); /* start off with the infinitive or participle, as desired */ if (inf) ret = rexGroup(1)[3]; else ret = rexGroup(2)[3]; /* get the complementizer */ vcomp = (rexGroup(3) == nil ? '' : rexGroup(3)[3]); /* get the direct and indirect object prepositions */ dprep = rexGroup(4)[3]; iprep = rexGroup(5)[3]; /* * add the complementizer BEFORE the direct object, if the * direct object is being shown as a full name ("PICK UP BOX") */ if (!dobjIsPronoun) ret += spPrefix(vcomp); /* * add the direct object and its preposition, using a pronoun if * applicable */ ret += spPrefix(dprep) + ' ' + dobjText; /* * add the complementizer AFTER the direct object, if the direct * object is shown as a pronoun ("PICK IT UP") */ if (dobjIsPronoun) ret += spPrefix(vcomp); /* if we have an indirect object, add it with its preposition */ if (iobjText != nil) ret += spPrefix(iprep) + ' ' + iobjText; /* return the result phrase */ return ret; } ; /* -------------------------------------------------------------------------- */ /* * English-language modifications to the various pronoun objects to provide * them with an appropriate English name in their prep properties. */ modify LocType prep = '' intoPrep = prep ; modify In prep = 'in' intoPrep = 'into' ; modify Outside prep = 'part of' ; modify On prep = 'on' intoPrep = 'into' ; modify Under prep = 'under' ; modify Behind prep = 'behind' ; modify Held prep = 'held by' ; modify Worn prep = 'worn by' ; modify Into prep = 'into' ; modify OutOf prep = 'out of' ; modify Down prep = 'down' ; modify Up prep = 'up'; ; modify Through prep = 'through'; ; modify Carrier prep = 'borne by' ; /* * [must define] The language-specific part of CommandTopicHelper. */ property myAction; class LCommandTopicHelper: object /* * builds the action phrase of the command, e.g. 'jump', 'take the red * ball', 'put the blue ball in the basket'. This can be used to help * construct the player char's command to the actor in the topic response, * e.g. "<q>\^<<actionPhrase>>!</q> you cry. " */ actionPhrase() { if(myAction == nil || myAction.verbRule == nil) return 'do something'; /* * Find the longest grammar template that starts with the same word at * the verb phrase. This is likely to be the most complete version in * a reasonably canonical form. * */ local txt = ''; /* * Obtain the verb from the first part of the verbPhrase of the * verbRule of the action matched by this CommandTopic. */ local verb = myAction.verbRule.verbPhrase.split('/')[1]; /* * Go through all the grammar templates associated with the action * matched by this CommandTopic and pick out the longest one that * starts with the verb we've just identified; we'll take this to be * the 'canonical' form (as a rough rule of thumb this works * reasonably well) */ foreach(local cur in myAction.grammarTemplates) { if(cur.length > txt.length && cur.startsWith(verb)) txt = cur; } /* * If the matched action has a direct object, replace the string * 'dobj' in our txt string with the name of the direct object */ if(myAction.curDobj != nil) txt = txt.findReplace('(dobj)', getName(myAction.curDobj)); /* * If the matched action has an indirect object, replacc the string * 'iobj' in our txt string with the name of the indirect object */ if(myAction.curIobj != nil) txt = txt.findReplace('(iobj)', getName(myAction.curIobj)); if(gCommand.verbProd.dirMatch) txt = txt.findReplace('(direction)', gCommand.verbProd.dirMatch.dir.name); return txt; } /* * Get the appropriate name for an object to use the action phrase of a * CommandTopic. */ getName(obj) { /* If the object is the player character, it should appear as 'me' */ if(obj == gPlayerChar) return 'me'; /* * If the object is the actor to whom the command is being addressed, * it should appear as the singular or plural form of the second * person reflexive pronoun */ if(obj == gActor) return gActor.plural ? 'yourselves' : 'yourself'; /* Otherwise just use the theName property of the object */ return obj.theName; } ; /* ------------------------------------------------------------------------ */ /* * Simple yes/no confirmation. The caller must display a prompt; we'll * read a command line response, then return true if it's an affirmative * response, nil if not. */ yesOrNo() { /* switch to no-command mode for the interactive input */ "<.commandnone>"; /* * Read a line of input. Do not allow real-time event processing; * this type of prompt is used in the middle of a command, so we * don't want any interruptions. Note that the caller must display * any desired prompt, and since we don't allow interruptions, we * won't need to redisplay the prompt, so we pass nil for the prompt * callback. */ local str = inputManager.getInputLine(nil); /* switch back to mid-command mode */ "<.commandmid>"; /* * If they answered with something starting with 'Y', it's * affirmative, otherwise it's negative. In reading the response, * ignore any leading whitespace. */ return rexMatch('<space>*[yY]', str) != nil; } /* * In English, Remove X might mean take it off (if we're wearing it) or take * it (if it's simply a free-standing object). We handle this with a Doer in * the English-specific library (a) because this ambiguity may be * language-specific and (b) because remap is now deprecated. */ removeDoer: Doer 'remove Thing' execAction(c) { if(c.dobj.wornBy == c.actor) redirect(c, Doff, dobj: c.dobj); else redirect(c, Take, dobj: c.dobj); } ; /* * Make putting something on a Floor object or throwing something at a Floor * object equivalent to dropping it; since this depends on the * English-language grammar of the actions concerned it needs to appear in the * English-specific part of the library. */ putOnGroundDoer: Doer 'put Thing on Floor; throw Thing at Floor' execAction(c) { /* * The player has asked to put something on the ground, so we should * override the actor's location's dropLocation on this occasion to * ensure that that's where the dropped object indeed ends up */ local oldDropLocation; local oldLocation; try { /* Note the original dropLocation */ oldLocation = gActor.location; oldDropLocation = oldLocation.dropLocation; /* Change the dropLocation to the Room */ oldLocation.dropLocation = gActor.getOutermostRoom; /* redirect the action to Drop */ redirect(c, Drop, dobj: c.dobj); } finally { /* Restore the original dropLocation */ oldLocation.dropLocation = oldDropLocation; } } ; /* * 'Get on ground' or 'stand on ground' does nothing if the player is already * directly in a room, so in that case we just display an appropriate message; * otherwise we take the command as equivalent to getting out of the actor's * immediate container. */ getOnGroundDoer: Doer 'stand on Floor; get on Floor' execAction(c) { if(gPlayerChar.location.ofKind(Room)) "{I} {am} standing on {the dobj}. "; else redirect(c, GetOut); } ; /* In English taking a path means going along it */ takePathDoer: Doer 'take PathPassage' execAction(c) { redirect(c, GoThrough); } /* * But this only applies if the command is actually 'take' and not a * synonymn like 'get' or 'pick up' */ strict = true /* * Ignore an error report for this Doer in PreInit, since it only means * that extras.t hasn't been included. */ ignoreError = true ; /* * Preparser to catch numbers entered with a decimal point, so that the * decimal point is not treated as terminating a sentence in the command. This * works by wrapping the number in double quote marks, so any code that wants * to do anything with the decimal number will first have to strip off the * quotes. */ decimalPreParser: StringPreParser doParsing(str, which) { str = str.findReplace( R'<digit>+<period><digit>+', { match, index, orig: '"'+match+'"' } ); return str; } ; /* * PreParser to convert a number input by the player (e.g. 1 or 2) in response to a diambiguation * prompt into the corresponding ordinal (e.g., first or second) so that the parser will recognize * it as choosing one of the options. */ disambigPreParser: StringPreParser doParsing(str, which) { /* * If we are disammguating and the option to enumerate the list of possible responsses is * set, then see if the player has entered a number and convert it to its corresponding * ordinal (e.g. '1' to 'first') */ if(which == rmcDisambig && libGlobal.enumerateDisambigOptions) { /* First strip our any extraneous bracekts the player may have typed. */ local str2 = str.findReplace(['(', ')'], ''); /* Then try to convert the player's input into an integer. */ local num = tryInt(str2); /* * If we have an integer and it's less than the number of ordinals that we define, and * it's within the allowable range of disambig options then return the ccrresponding * ordinal number. */ if(num && num <= ordinals.length && num > 0) { if(num <= libGlobal.disambigLen) return ordinals[num]; "There were only <<libGlobal.disambigLen>> options.<.p>"; return nil; } } /* return the original string unchanged */ return str; } /* * The list of ordinal numbers. We only list the first 20 here since it seems highly unlikely * that a player would be presented with a list of more than twelve diambiguation options. In * any case the parser seems unable to cope with a response of more than 'twentieth'. */ ordinals = ['first', 'second', 'third', 'fourth' ,'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'tweltfh', 'thirteenth', 'fourteenth', 'fifteenth', 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth', 'twentieth' ]; ; modify SpecialVerb /* * A list of prepositions we want SpecialVerb to check for when detecting the possible end of * a noun phrase. */ prepositions = ['in', 'on', 'with', 'to', 'under', 'behind', 'from'] /* a list of articles we want the SpecialVerb to ignore when matching vocab. */ articles = ['the', 'a', 'an', 'some'] ; /* * Define a LookupTable that translates the names of various enums into their string equivalents. * This is used by the say() function to display the names of these enums. This is defined in the * lnaguage-dependent part of the library to allow customization to other languages. */ enumTabObj: object enumTab = [ dubious -> 'dubious', likely -> 'likely', unlikely -> 'unlikely', untrue -> 'untrue', small -> 'small', medium -> 'medium', large -> 'large', notLockable -> 'notLockable', lockableWithoutKey -> 'lockableWithoutKey', lockableWithKey -> 'lockableWithKey', indirectLockable -> 'indirectLockable', masculine -> 'masculine', feminine -> 'feminine', neuter -> 'neuter', OpenGoal -> 'OpenGoal', ClosedGoal -> 'ClosedGoal', UndiscoveredGoal -> 'UndiscoveredGoal', null -> 'null', oneToOne -> 'oneToOne', oneToMany -> 'oneToMany', manyToOne -> 'manyToOne', manyToMany -> 'manyToMany', normalRelation -> 'normalRelation', reverseRelation -> 'reverseRelation' ] reverseEnumTab = [ 'dubious' -> dubious, 'likely' -> likely, 'unlikely' -> unlikely, 'untrue' -> untrue ] /* Convert enum to corresponding string value or vice versa. */ getEnum(arg) { switch(dataType(arg)) { case TypeSString: return reverseEnumTab[arg]; case TypeEnum: return enumTab[arg]; default: return nil; } } ; enum normalRelation, reverseRelation; /* * Possibly a temporary measure to replace the apostrophe in possessives in * certain words in the player's input with a carat in order to enable * matching vocab. */ /* * Definitions for moods. We define them in the language-specific part of the library * so that translators can use more appropriate names for their languages. */ DefStance(amorous, 50); DefStance(loving, 40); DefStance(warm, 30); DefStance(friendly, 20); DefStance(lukewarm, 10); DefStance(neutral, 0); DefStance(cool, -10); DefStance(unfriendly, -20); DefStance(hostile, -30); DefStance(rancorous, -40); DefStance(loathing, -50); /* * Definitions for moods. We define them in the language-specific part of the library so that * translators can use more appropriate names for their languages. Game code can readily define * more moods if needs them. */ DefMood(neutral); DefMood(calm); DefMood(happy); DefMood(euphoric); DefMood(contented); DefMood(sad); DefMood(depressed); DefMood(angry); DefMood(furious); DefMood(afraid); DefMood(terrified); DefMood(confident); DefMood(bold); DefMood(lonely); DefMood(bored); DefMood(excited); /* By default Actors start our with neutral mood and stance. */ modify libGlobal defaultStance = neutralStance defaultMood = neutralMood ; modify MessageCtx /* A list of object properties that refer to pronouns. */ pronounProps = [&heName, &himName, &herName, &hersName, &reflexiveName ] /* * Has a pronoun just been used? It has it we've just used a pronoun property to generate the * messaage text relating to the subject. */ pronounUsed = pronounProps.indexOf(sourceProp) /* * The subject is effectively plural for the purponses of conjugation if it as actually plural * or we've just used a plural pronoun ('they') to refer to it. */ subjEffectivelyPlural = subj.plural || (pronounUsed && subj.pronoun.plural) ;
Adv3Lite Library Reference Manual
Generated on 26/02/2025 from adv3Lite version 2.2
Generated on 26/02/2025 from adv3Lite version 2.2