action.t | documentation |
#charset "us-ascii" /* * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. * * TADS 3 Library: Actions. * * This module defines the Action classes. An Action is an abstract * object representing a command to be performed. */ #include "adv3.h" #include "tok.h" /* ------------------------------------------------------------------------ */ /* * Object associations lists. We use this object to store some lookup * tables that we build during preinitialization to relate object usages * (DirectObject, IndirectObject) to certain properties. */ objectRelations: PreinitObject /* preinitialization - build the lookup tables */ execute() { /* build the default pre-condition properties table */ preCondDefaultProps[DirectObject] = &preCondDobjDefault; preCondDefaultProps[IndirectObject] = &preCondIobjDefault; /* build the catch-all pre-conditions properties table */ preCondAllProps[DirectObject] = &preCondDobjAll; preCondAllProps[IndirectObject] = &preCondIobjAll; /* build the default verification properties table */ verifyDefaultProps[DirectObject] = &verifyDobjDefault; verifyDefaultProps[IndirectObject] = &verifyIobjDefault; /* build the catch-all verification properties table */ verifyAllProps[DirectObject] = &verifyDobjAll; verifyAllProps[IndirectObject] = &verifyIobjAll; /* build the default check properties table */ checkDefaultProps[DirectObject] = &checkDobjDefault; checkDefaultProps[IndirectObject] = &checkIobjDefault; /* build the catch-all check properties table */ checkAllProps[DirectObject] = &checkDobjAll; checkAllProps[IndirectObject] = &checkIobjAll; /* build the default action properties table */ actionDefaultProps[DirectObject] = &actionDobjDefault; actionDefaultProps[IndirectObject] = &actionIobjDefault; /* build the catch-all check properties table */ actionAllProps[DirectObject] = &actionDobjAll; actionAllProps[IndirectObject] = &actionIobjAll; } /* lookup table for default precondition properties */ preCondDefaultProps = static new LookupTable() /* lookup table for catch-all precondition properties */ preCondAllProps = static new LookupTable() /* lookup table for default verification properties */ verifyDefaultProps = static new LookupTable() /* lookup table for catch-all verification properties */ verifyAllProps = static new LookupTable() /* lookup table for default check properties */ checkDefaultProps = static new LookupTable() /* lookup table for catch-all check properties */ checkAllProps = static new LookupTable() /* lookup table for default action properties */ actionDefaultProps = static new LookupTable() /* lookup table for catch-all action properties */ actionAllProps = static new LookupTable() ; /* ------------------------------------------------------------------------ */ /* * Invoke the given function with the given values for the parser global * variables gActor and gAction. */ withParserGlobals(issuer, actor, action, func) { local oldIssuer; local oldActor; local oldAction; /* remember the old action and actor, so we can restore them later */ oldIssuer = gIssuingActor; oldActor = gActor; oldAction = gAction; /* establish our actor and action as the global settings */ gIssuingActor = issuer; gActor = actor; gAction = action; /* make sure we restore globals on the way out */ try { /* invoke the callback and return the result */ return (func)(); } finally { /* restore the globals we changed */ gActor = oldActor; gIssuingActor = oldIssuer; gAction = oldAction; } } /* ------------------------------------------------------------------------ */ /* * Pre-condition Descriptor. This object encapsulates a precondition * object and the argument we want to pass to its condition check method. */ class PreCondDesc: object construct(cond, arg) { /* remember the condition and the check argument */ cond_ = cond; arg_ = arg; } /* check the precondition */ checkPreCondition(allowImplicit) { /* call the precondition's check method with the argument we stored */ return cond_.checkPreCondition(arg_, allowImplicit); } /* the precondition object */ cond_ = nil /* the check argument */ arg_ = nil /* our list sorting index */ index_ = 0 ; /* ------------------------------------------------------------------------ */ /* * Basic Action class. An Action is the language-independent definition * of the abstract action of a command. */ class Action: BasicProd /* * Are we the given kind of action? By default, this simply returns * true if we're of the given action class. */ actionOfKind(cls) { return ofKind(cls); } /* * Reset the action in preparation for re-execution. This should * discard any scoped context from any past execution of the * command, such as cached scope information. */ resetAction() { /* forget any past successful verification passes */ verifiedOkay = []; } /* * Repeat the action, for an AGAIN command. */ repeatAction(lastTargetActor, lastTargetActorPhrase, lastIssuingActor, countsAsIssuerTurn) { /* execute the command */ executeAction(lastTargetActor, lastTargetActorPhrase, lastIssuingActor, countsAsIssuerTurn, self); } /* * Cancel iteration of the action. This can be called during the * 'check' or 'action' phases of executing this action. It tells * the action that we want to stop executing the action when we're * finished with the current object. * * Note that this doesn't cause a jump out of the current code, so * it's not like 'exit' or the other termination signals. Instead, * this simply tells the action to proceed normally for the * remainder of the processing for the current object, and then act * as though there were no more objects to iterate over, ending the * command normally. If you want to cut off the remainder of the * execution cycle for the current object, you can use 'exit' (for * example) immediately after calling this method. */ cancelIteration() { iterationCanceled = true; } /* internal flag: object iteration has been canceled */ iterationCanceled = nil /* * Create an instance of this action, for use by a recursive or * programmatically-generated command. * * The generic actions defined in the library are always subclassed * by language-specific library modules, because the language * modules have to define the grammar rules for the verbs - we can't * define the grammar rules generically because the verbs wouldn't * be reusable for non-English translations if we did. As a result, * library code referring to one of the library verbs by name, say * TakeAction, doesn't get a language-specific subclass of the verb, * but just gets the language-independent base class. * * However, to make full use of an Action object in a recursive * command, we do need a final language-specific subclass - without * this, we won't be able to generate text describing the command, * for example. This method bridges this gap by finding a suitable * language-specific subclass of the given action, then creating an * instance of that subclass rather than an instance of the base * class. * * By default, we'll take any subclass of this action that is itself * a class. However, if any subclass has the property * defaultForRecursion set to true, we'll use that class * specifically - this lets the language module designate a * particular subclass to use as the default for recursive commands, * which might be desirable in cases where the language module * defines more than one subclass of an action. */ createActionInstance() { local found; /* * Iterate over our subclasses. Initialize 'found' to this base * class, so that if we fail to find any subclasses, we'll at * least be able to create an instance of the generic base * class. */ for (local cur = firstObj(self, ObjClasses), found = self ; cur != nil ; cur = nextObj(cur, self, ObjClasses)) { /* * if this one is marked as a default for recursion, and the * last one we found isn't, choose this one over the last * one we found */ if (cur.defaultForRecursion && !found.defaultForRecursion) found = cur; /* * If this one is a subclass of the last one we found, pick * it instead of the last one. We always want a final * subclass here, never an intermediate class, so if we find * a subclass of the one we've tentatively picked, we know * that the tentative selection isn't final after all. */ if (cur.ofKind(found)) found = cur; } /* return a new instance of what we found */ return found.createInstance(); } /* * Create an instance of this action based on another action. We'll * copy the basic properties of the original action. */ createActionFrom(orig) { local action; /* create a new instance of this action */ action = createActionInstance(); /* copy the token list information to the new action */ action.tokenList = orig.tokenList; action.firstTokenIndex = orig.firstTokenIndex; action.lastTokenIndex = orig.lastTokenIndex; /* the new action is implicit if the original was */ if (orig.isImplicit) action.setImplicit(orig.implicitMsg); /* return the new action */ return action; } /* * Mark the command as implicit. 'msgProp' is the property (of * gLibMessages) to use to announce the implicit command. */ setImplicit(msgProp) { /* mark ourselves as implicit */ isImplicit = true; /* do not show default reports for implicit commands */ showDefaultReports = nil; /* all implicit commands are nested */ setNested(); /* * do not include implicit commands in undo - since an implicit * command is only a subpart of an explicit command, an implicit * command is not undone individually but only as part of the * enclosing explicit command */ includeInUndo = nil; /* remember the implicit command announcement message property */ implicitMsg = msgProp; } /* * Mark the command as nested, noting the parent action (which we * take as the global current action). */ setNested() { /* remember the parent action */ parentAction = gAction; } /* * Determine if I'm nested within the given action. Returns true if * the given action is my parent action, or if my parent action is * nested within the given action. */ isNestedIn(action) { /* if my parent action is the given action, I'm nested in it */ if (parentAction == action) return true; /* * if I have a parent action, and it's nested in the given * action, then I'm nested in the given action because I'm * nested in anything my parent is nested in */ if (parentAction != nil && parentAction.isNestedIn(action)) return true; /* we're not nested in the given action */ return nil; } /* * Set the "original" action. An action with an original action is * effectively part of the original action for the purposes of its * reported results. * * An action has an original action if it's a nested or replacement * action for an action. */ setOriginalAction(action) { /* remember my original action */ originalAction = action; } /* * Get the "original" action. If I'm a replacement or nested action, * this returns the original main action of which I'm a part, for * reporting pruposes. * * It's important to note that an implied action does NOT count as a * nested or replacement action for the purposes of this method. * That is, if a command A triggers an implied action B, which * triggers a nested action C, and then after that command A itself * triggers a nested action D, then * * A.getOriginalAction -> A *. B.getOriginalAction -> B (B is implied, not nested) *. C.getOriginalAction -> C (C is nested within B) *. D.getOriginalAction -> A (D is nested within A) * * The purpose of the original action is to tell us, mainly for * reporting purposes, what we're really trying to do with the * action. This allows reports to hide the internal details of how * the action is carried out, and instead say what the action was * meant to do from the player's perspective. */ getOriginalAction() { /* if I have no other original action, I'm the original action */ if (originalAction == nil) return self; /* return my original action's original action */ return originalAction.getOriginalAction(); } /* * Determine if this action is "part of" the given action. I'm part * of the given action if I am the given action, or the given action * is my "original" action, or my original action is part of the * given action. */ isPartOf(action) { /* if I'm the same as the given action, I'm obviously part of it */ if (action == self) return true; /* if my original action is part of the action, I'm part of it */ if (originalAction != nil && originalAction.isPartOf(action)) return true; /* I'm not part of the given action */ return nil; } /* * Mark the action as "remapped." This indicates that the action * was explicitly remapped to a different action during the remap() * phase. */ setRemapped(orig) { remappedFrom = gAction; } /* determine if I'm remapped, and get the original action if so */ isRemapped() { return remappedFrom != nil; } getRemappedFrom() { return remappedFrom; } /* * Get the "simple synonym" remapping for one of our objects, if * any. 'obj' is the resolved object to remap, and 'role' is the * object role identifier (DirectObject, IndirectObject, etc). * 'remapProp' is the remapping property for the role; this is * simply the result of our getRemapPropForRole(role), but we ask * the caller to pass this in so that it can be pre-computed in * cases where we'll called in a loop. * * A simple synonym remapping is a remapTo that applies the same * verb to a new object in the same role. For example, if we remap * OPEN DESK to OPEN DRAWER, then the drawer is the simple synonym * remapping for the desk in an OPEN command. A remapping is * considered a simple synonym remapping only if we're remapping to * the same action, AND the new object is in the same action role as * the original object was. * * If there's no simple synonym remapping, we'll return nil. */ getSimpleSynonymRemap(obj, role, remapProp) { local mapping; local remapIdx; /* if the object isn't remapped at all, there's no remapping */ if ((mapping = obj.(remapProp)) == nil) return nil; /* if the mapping isn't to the same action, it's not a synonym */ if (!ofKind(mapping[1])) return nil; /* * Find the specific (non-role) object in the remap vector - * this is the entry that's actually an object rather than a * role identifier. (The remapping vector is required to have * exactly one such entry. Look from the right end of the list, * since the first entry is always the new action, which is * itself an object, but not the object we're looking for.) */ remapIdx = mapping.lastIndexWhich({x: dataType(x) == TypeObject}); if (remapIdx == nil) return nil; /* * Determine if the object plays the same role in the new action * as the original object did in the original action. It does * if it's at the index in the mapping vector of our object role * in the action. Note that the mapping vector has slot 1 * filled with the action, so its objects are actually one slot * higher than they are in the action itself. * * If the new object isn't in the same role, then this isn't a * simple synonym remapping. */ if (getRoleFromIndex(remapIdx - 1) != role) return nil; /* * We have the same action applied to a new object in the same * role as the original object, so this is a simple synonym * remapping. Return the new object we're mapping to. */ return mapping[remapIdx]; } /* * the defaultForRecursion flag must be explicitly set in subclasses * when desired - by default we'll use any language-specific * subclass of an Action for recursive commands */ defaultForRecursion = nil /* * Flag: the command is implicit. An implicit command is one that * is performed as an implied enabling step of another command - for * example, if an actor wants to throw something, the actor must be * holding the object, so will implicitly try to take the object. */ isImplicit = nil /* * The parent action. If the command is performed programmatically * in the course of executing another command, this is set to the * enclosing action. * * Note that while all implicit commands are nested, not all nested * commands are implicit. A nested command may simply be a * component of another command, or another command may be handled * entirely by running a different command as a nested command. In * any case, a nested but non-implicit command does not appear to * the player as a separate command; it is simply part of the * original command. */ parentAction = nil /* * The original action we were remapped from. This is valid when * the action was explicitly remapped during the remap() phase to a * different action. */ remappedFrom = nil /* * the original action - we are effectively part of the original * action for reporting purposes */ originalAction = nil /* the libMessage property, if any, to announce the implicit command */ implicitMsg = nil /* * Flag: we are to show default reports for this action. In most * cases we will do so, but for some types of commands (such as * implicit commands), we suppress default reports. */ showDefaultReports = true /* * Get a message parameter object for the action. Each action * subclass defines this to return its objects according to its own * classifications. The default action has no objects, but * recognizes 'actor' as the current command's actor. */ getMessageParam(objName) { switch(objName) { case 'pc': /* return the player character */ return gPlayerChar; case 'actor': /* return the current actor */ return gActor; default: /* * if we have an extra message parameters table, look up the * parameter name in the table */ if (extraMessageParams != nil) return extraMessageParams[objName]; /* we don't recognize other names */ return nil; } } /* * Define an extra message-specific parameter. Message processors * can use this to add their own special parameters, so that they * can refer to parameters that aren't involved directly in the * command. For example, a message for "take <dobj>" might want to * refer to the object containing the direct object. */ setMessageParam(objName, obj) { /* * if we don't yet have an extra message parameters table, * create a small lookup table for it */ if (extraMessageParams == nil) extraMessageParams = new LookupTable(8, 8); /* add the parameter to the table, indexing by the parameter name */ extraMessageParams[objName.toLower()] = obj; } /* * For convenience, this method allows setting any number of * name/value pairs for message parameters. */ setMessageParams([lst]) { /* set each pair from the argument list */ for (local i = 1, local len = lst.length() ; i+1 <= len ; i += 2) setMessageParam(lst[i], lst[i+1]); } /* * Synthesize a global message parameter name for the given object. * We'll store the association and return the synthesized name. */ synthMessageParam(obj) { local nm; /* synthesize a name */ nm = 'synth' + toString(synthParamID++); /* store the association */ setMessageParam(nm, obj); /* return the synthesized name */ return nm; } /* synthesized message object parameter serial number */ synthParamID = 1 /* * Extra message parameters. If a message processor wants to add * special message parameters of its own, we'll create a lookup * table for the extra parameters. Message processors might want to * add their own special parameters to allow referring to objects * other than the main objects of the command. */ extraMessageParams = nil /* * Flag: this command is repeatable with 'again'. Before executing * a command, we'll save it for use by the 'again' command if this * flag is true. */ isRepeatable = true /* * Flag: this command should be included in the undo records. This * is true for almost every command, but a few special commands * (undo, save) are not subject to undo. */ includeInUndo = true /* * Flag: this is a "conversational" command, as opposed to an order. * When this type of command is addressed to another character, it * doesn't ask the other character to do anything, but rather engages * the other character in conversation. Examples: * *. Bob, hello *. Bob, goodbye *. Bob, tell me about the planet *. Bob, yes *. Bob, no * * ("Tell me about..." is a little different from the others. We * treat it as conversational because it means the same thing as "ask * Bob about...", which we consider conversational because it would * be rendered in real life as a question. In other words, the * action involves the issuing actor stating the question, which * means that issuing actor is the one doing the physical work of the * action. "Tell me about..." could be seen as an order, but it * seems more appropriate to view it as simply an alternative way of * phrasing a question.) * * The issuing actor is passed as a parameter because some actions * are conversational only in some cases; "tell me about the planet" * is conversational, but "tell Bill about the planet" isn't, since * the latter doesn't ask Bob a question but orders Bob to talk to * Bill. * * When the issuing actor and target actor are the same, this is * irrelevant. The purpose of this is to distinguish orders given to * another character from conversational overtures directed to the * other character, so if the command is coming from the player and * bound for the player character, there's obviously no conversation * going on. * * Note also that, contrary to what one might think at first glance, * a command like ASK ABOUT is NOT conversational; it's a command to * ask someone about something, and isn't itself a conversational * overture. The act of asking is itself a conversational overture, * but the asking is the *result* of the command, not the command * itself. An action is only conversational if the action itself is * a conversational overture. So, "BOB, HELLO" is conversational; * "BOB, ASK BILL ABOUT COMPUTER" is not, because it orders Bob to do * something. */ isConversational(issuingActor) { return nil; } /* * Get the actual verb phrase the player typed in to generate this * Action, expressed in a canonical format. The canonical format * consists of the lower-case version of all of the actual text the * player typed, but with each noun phrase replaced by a standard * placeholder token describing the slot. The placeholder tokens are * '(dobj)' for the direct object, '(iobj)' for the indirect object, * '(literal)' for a literal text phrase, and '(topic)' for a topic * phrase. * * For example, if the player typed PUT BOOK AND PENCIL IN BOX, the * canonical phrasing we return would be "put (dobj) in (iobj)". */ getEnteredVerbPhrase() { local orig; local txt; /* * If there's an original action, let the original action do the * work. If we've been remapped, or if this is an implied * action, we won't necessarily have been constructed from the * actual player input, so we need to go back to the original * action for this information. */ if (getOriginalAction() != self) return getOriginalAction.getEnteredVerbPhrase(); /* start with the original token list for the predicate */ orig = getPredicate().getOrigTokenList(); /* add each token to the result text */ for (txt = '', local i = 1, local len = orig.length() ; i <= len ; ++i) { local foundNp; /* add a space if this isn't the first element */ if (i > 1) txt += ' '; /* * Check to see if we're entering one of the noun-phrase * slots. We are if we've reached the first token of one of * the predicate noun phrases. */ foundNp = nil; foreach (local npProp in predicateNounPhrases) { local match; /* check to see if we're at this noun phrase */ if ((match = self.(npProp)) != nil && i == match.firstTokenIndex) { /* * we're entering this noun phrase - add the generic * placeholder token for the noun phrase */ txt += (npProp == &dobjMatch ? '(dobj)' : npProp == &iobjMatch ? '(iobj)' : npProp == &topicMatch ? '(topic)' : npProp == &literalMatch ? '(literal)' : '(other)'); /* skip the entire run of tokens for the noun phrase */ i = match.lastTokenIndex; /* note that we found a noun phrase */ foundNp = true; /* stop looking for a noun phrase */ break; } } /* * if we didn't find a noun phrase, this token is a literal * part of the predicate grammar, so add it as-is */ if (!foundNp) txt += getTokVal(orig[i]); } /* return the phrase, with everything converted to lower-case */ return txt.toLower(); } /* * Get the grammar match tree object for the predicate that was used * to enter this command. By default, if we have an original action, * we return the original action; otherwise we just return 'self'. * * Language libraries must override this to return the original match * tree object if Actions are separate from predicate match trees. * * (The 'predicate' is simply the grammar match tree object for the * entire verb phrase from the player's actual command entry text * that matched this Action. For example, in "BOB, TAKE BOX", the * predicate is the match tree for the "TAKE BOX" part. In "BOB, * TAKE BOX AND GO NORTH", the predicate for the Take action is still * the "TAKE BOX" part. For "BOB, TAKE BOX AND BOOK AND GO NORTH", * the predicate for the Take action is "TAKE BOX AND BOOK" - the * full verb phrase includes any multiple-object lists in the * original command.) */ getPredicate() { /* * By default, we just return the original Action - we assume * that the language library defines Action subclasses as the * actual match tree objects for predicate grammars. Language * modules must override this if they use separate object types * for the predicate match tree objects. */ return getOriginalAction(); } /* * Get the noun-phrase information for my predicate grammar. This * returns a list of the match-tree properties for the noun-phrase * sub-productions in our predicate grammar. The properties * generally include &dobjMatch, &iobjMatch, &literalMatch, and * &topicMatch. The order of the slots isn't important; they simply * tell us which ones we should find in our predicate grammar match. * * The base Action is intransitive, so it doesn't have any * noun-phrase slots, hence this is an empty list. */ predicateNounPhrases = [] /* * Get the object "role" identifier for the given index. This * returns the identifier (DirectObject, IndirectObject, etc.) for * the object at the given slot index, as used in * setResolvedObjects(). The first object is at index 1. */ getRoleFromIndex(idx) { return nil; } /* * Get the "other object" role - this is the complement of the given * object role for a two-object action, and is used to determine the * real role of the special OtherObject placeholder in a remapTo(). * This is only meaningful with actions of exactly two objects. */ getOtherObjectRole(role) { return nil; } /* get the resolved object in a given role */ getObjectForRole(role) { return nil; } /* get the match tree for the noun phrase in the given role */ getMatchForRole(role) { return nil; } /* get the 'verify' property for a given object role */ getVerifyPropForRole(role) { return nil; } /* get the 'preCond' property for a given object role */ getPreCondPropForRole(role) { return nil; } /* get the 'remap' property for a given object role */ getRemapPropForRole(role) { return nil; } /* * Get the ResolveInfo for the given object in the current command. * Since we don't have any objects at all, we'll simply return a new * ResolveInfo wrapping the given object. 'cur' is the object we're * looking for, and 'oldRole' is the role the object previously * played in the action. */ getResolveInfo(obj, oldRole) { return new ResolveInfo(obj, 0, nil); } /* * Explicitly set the resolved objects. This should be overridden * in each subclass for the number of objects specific to the action * (a simple transitive action takes one argument, an action with * both a direct and indirect object takes two arguments, and so * on). The base action doesn't have any objects at all, so this * takes no arguments. * * This method is used to set up an action to be performed * programmatically, rather than based on parsed input. Since * there's no parsed input in such cases, the objects are specified * directly by the programmatic caller. */ setResolvedObjects() { } /* * Explicitly set the object match trees. This sets the pre-resolved * production match trees. The arguments are given in the order of * their roles in the action, using the same order that * setResolvedObjects() uses. * * The arguments to this routine can either be match tree objects, * which we'll plug into our grammar tree in the respective roles * exactly as given; or they can be ResolveInfo objects giving the * desired resolutions, in which case we'll build the appropriate * kind of PreResolvedProd for each one. The types can be freely * mixed. */ setObjectMatches() { } /* * Check that the resolved objects are all in scope. Returns true if * so, nil if not. * * This routine is meant for use only for actions built * programmatically using setResolvedObjects(). In particular, we * assume that we have only one object in each slot. For normal * parser-built actions, this check isn't necessary, because the * parser only resolves objects that are in scope in the first place. */ resolvedObjectsInScope() { /* we have no objects at all, so there is nothing out of scope */ return true; } /* * Get the list of bindings for an anaphoric pronoun - this is a * pronoun that refers back to an earlier noun phrase in the same * sentence, such as HIMSELF in ASK BOB ABOUT HIMSELF. These * obviously make no sense for verbs that take one (or zero) * objects, since there's no earlier phrase to refer back to; these * should return nil to indicate that an anaphoric pronoun is simply * nonsensical in such a context. Actions of two or more objects * should return a list of objects for the preceding noun phrase. * * Actions of two or more objects should set this if possible to the * resolved object list for the previous noun phrase, as a * ResolveInfo list. * * The tricky part is that some actions will resolve noun phrases in * a different order than they appear in the actual command grammar; * similarly, it's also possible that some non-English languages use * cataphoric pronouns (i.e., pronouns that refer to noun phrases * that appear later in the sentence). To allow for these cases, * return an empty list here if a binding is grammatically possible * but not yet available because of the resolution order, and note * internally that the parser asked us for an anaphoric binding. * Afterwards, the action's resolver method must go back and perform * *another* resolve pass on the noun phrase production that * requested the anaphor binding. * * 'typ' is the PronounType specifier for the corresponding ordinary * pronoun. For 'himself', for example, typ will be PronounHim. */ getAnaphoricBinding(typ) { return nil; } /* * Set a special pronoun override. This creates a temporary pronoun * definition, which lasts as long as this action (and any nested * actions). The temporary definition overrides the normal meaning * of the pronoun. * * One example of where this is useful is in global action remapping * cases where the target actor changes in the course of the * remapping. For example, if we remap BOB, GIVE ME YOUR BOOK to ASK * BOB FOR YOUR BOOK, the YOUR qualifier should still refer to Bob * even though the command is no longer addressing Bob directly. * This routine can be used in this case to override the meaning of * 'you' so that it refers to Bob. */ setPronounOverride(typ, val) { /* if we don't have an override table yet, create one */ if (pronounOverride == nil) pronounOverride = new LookupTable(5, 10); /* add it to the table */ pronounOverride[typ] = val; } /* * Get any special pronoun override in effect for the action, as set * via setPronounOverride(). This looks in our own override table * for a definition; then, if we have no override of our own, we ask * our parent action if we have one, then our original action. */ getPronounOverride(typ) { local pro; /* check our own table */ if (pronounOverride != nil && (pro = pronounOverride[typ]) != nil) return pro; /* we don't have anything in our own table; check our parent */ if (parentAction != nil && (pro = parentAction.getPronounOverride(typ)) != nil) return pro; /* if still nothing, check with the original action */ if (originalAction != nil && originalAction != parentAction && (pro = originalAction.getPronounOverride(typ)) != nil) return pro; /* we didn't find an override */ return nil; } /* * the pronoun override table - this is nil by default, which means * that no overrides have been defined yet; we create a LookupTable * upon adding the first entry to the table */ pronounOverride = nil /* wrap an object with a ResolveInfo */ makeResolveInfo(val) { /* * if it's already a ResolveInfo object, return it as-is; * otherwise, create a new ResolveInfo wrapper for it */ if (dataType(val) == TypeObject && val.ofKind(ResolveInfo)) return val; else return new ResolveInfo(val, 0, nil); } /* * Convert an object or list of objects to a ResolveInfo list */ makeResolveInfoList(val) { /* if we have a non-list collection, make it a list */ if (dataType(val) == TypeObject && val.ofKind(Collection)) val = val.toList(); /* if it's nil or an empty list, return an empty list */ if (val == nil || val == []) return []; /* see what we have */ if (dataType(val) == TypeList) { /* it's a list - make a ResolveInfo for each item */ return val.mapAll({x: makeResolveInfo(x)}); } else { /* it's not a list - return a one-element ResolveInfo list */ return [makeResolveInfo(val)]; } } /* * If the command is repeatable, save it for use by 'again'. */ saveActionForAgain(issuingActor, countsAsIssuerTurn, targetActor, targetActorPhrase) { /* * Check to see if the command is repeatable. It's repeatable if * the base action is marked as repeatable, AND it's not nested, * AND it's issued by the player character, AND it's either a PC * command or it counts as an issuer turn. * * Nested commands are never repeatable with 'again', since no * one ever typed them in. * * "Again" is strictly for the player's use, so it's repeatable * only if this is the player's turn, as opposed to a scripted * action by an NPC. This is the player's turn only if the * command was issued by the player character (which means it * came from the player), and either it's directed to the player * character OR it counts as a turn for the player character. */ if (isRepeatable && parentAction == nil && (issuingActor.isPlayerChar() && (targetActor.isPlayerChar() || countsAsIssuerTurn))) AgainAction.saveForAgain(issuingActor, targetActor, targetActorPhrase, self); } /* * Perform this action. Throughout execution of the action, the * global parser variables that specify the current actor and action * are valid. */ doAction(issuingActor, targetActor, targetActorPhrase, countsAsIssuerTurn) { local oldActor; local oldIssuer; local oldAction; local oldResults; /* * save the current parser globals, for restoration when we * finish this command - if this command is nested within * another, this will let us ensure that everything is restored * properly when we finish with this command */ oldActor = gActor; oldIssuer = gIssuingActor; oldAction = gAction; oldResults = gVerifyResults; /* * set the new globals (note that there are no verification * results or command reports objects yet - these are valid only * while we're running the corresponding command phases) */ gActor = targetActor; gIssuingActor = issuingActor; gAction = self; gVerifyResults = nil; /* make sure we restore globals on our way out */ try { local pc; /* if applicable, save the command for AGAIN */ saveActionForAgain(issuingActor, countsAsIssuerTurn, targetActor, targetActorPhrase); /* start a new command visually if this isn't a nested action */ if (parentAction == nil) gTranscript.addCommandSep(); /* have the player character note initial conditions */ pc = gPlayerChar; pc.noteConditionsBefore(); /* run the before routine for the entire action */ beforeActionMain(); /* run the subclass-specific processing */ doActionMain(); /* run the after routine for the entire action */ afterActionMain(); /* * If this is a top-level action, show the command results. * Don't show results for a nested action, since we want to * wait and let the top-level action show the results after * it has the full set of results. */ if (parentAction == nil) { /* * If the player character didn't change, ask the player * character to note any condition changes. If the * player character did change to a new actor, * presumably the command will have displayed a specific * message, since this would be an unusual development * for which we can generate no generic message. */ if (gPlayerChar == pc) pc.noteConditionsAfter(); } } finally { /* restore the parser globals to how we found them */ gActor = oldActor; gIssuingActor = oldIssuer; gAction = oldAction; gVerifyResults = oldResults; } } /* * Perform processing before running the action. This is called * just once per action, even if the action will be iterated for a * list of objects. */ beforeActionMain() { } /* * Perform processing after running the entire action. This is * called just once per action, even if the action was iterated for * a list of objects. */ afterActionMain() { /* call each registered after-action handler */ if (afterActionMainList != nil) { foreach (local cur in afterActionMainList) cur.afterActionMain(); } /* * Mark ourselves as busy for the amount of time this action * takes. Don't count the time taken for implied actions, * though, since these are meant to be zero-time sub-actions * performed as part of the main action and thus don't have a * separate time cost. * * Note that we add our busy time in the main after-action * processing because we only want to count our time cost once * for the whole command, even if we're performing the command on * multiple objects. */ if (!isImplicit) { local actor; /* * If the command is conversational, the turn counts as an * issuer turn; otherwise, it counts as a turn for the target * actor. Conversational commands are effectively carried * out by the issuer, even though in form they're directed to * another actor (as in "BOB, HELLO"), so we need to count * the time they take as the issuer's time. */ actor = (isConversational(gIssuingActor) ? gIssuingActor : gActor); /* add the busy time to the actor */ actor.addBusyTime(self, actionTime); } /* * If the command failed, and this is a top-level (not nested) * action, check to see if the game wants to cancel remaining * commands on the line. */ if (gTranscript.isFailure && parentAction == nil && gameMain.cancelCmdLineOnFailure) { /* * the command failed, and they want to cancel remaining * commands on failure - throw a 'cancel command line' * exception to cancel any remaining tokens */ throw new CancelCommandLineException(); } } /* * Register an object for afterActionMain invocation. After we've * finished with the entire action - including all iterated objects * involved in the action - we'll invoke each registered object's * afterActionMain() method. This registration is only meaningful * for the current action instance, and can only be set up before * the action has been finished (i.e., before the current gAction * invokes its own afterActionMain() method). * * Each object is only registered once. If a caller attempts to * register the same object repeatedly, we'll simply ignore the * repeated requests. * * This is a convenient way to implement a collective follow-up to * the parts of an iterated action. Since repeated registrations * are ignored, each handler for an iterated object (such as a * "dobjFor" action() handler) can register its follow-up handler * without worrying about redundant registration. Then, when the * overall action is completed for each iterated object involved, * the follow-up handler will be invoked, and it can do any final * work for the overall action. For example, the follow-up handler * could display a message summarizing the iterated parts of the * action; or, it could even scan the transcript for particular * messages and replace them with a summary. */ callAfterActionMain(obj) { /* if we don't have an after-action list yet, create one */ if (afterActionMainList == nil) afterActionMainList = new Vector(8); /* if this item isn't already in the list, add it */ if (afterActionMainList.indexOf(obj) == nil) afterActionMainList.append(obj); } /* list of methods to invoke after we've completed the action */ afterActionMainList = nil /* * the amount of time on the game clock that the action consumes - * by default, each action consumes one unit, but actions can * override this to consume more or less game time */ actionTime = 1 /* * Zero the action time in this action and any parent actions. This * should be used when a nested replacement action is to completely * take over the time-keeping responsibility for the entire turn; all * containing actions in this case are to take zero time, since the * nested action is the only part of the turn that will count for * timing. */ zeroActionTime() { /* clear my action time */ actionTime = 0; /* if we have a parent action, zero it and its parents */ if (parentAction != nil) parentAction.zeroActionTime(); } /* * Execute the action for a single set of objects. This runs the * full execution sequence for the current set of objects. * * Subclasses generally won't override this method, but will instead * override the methods that implement the individual steps in the * execution sequence. */ doActionOnce() { /* * Perform the sequence of operations to execute the action. If * an ExitSignal is thrown during the sequence, skip to the * end-of-turn processing. */ try { local result; local impReport; /* * Before doing any actual execution, check the command for * remapping. If we end up doing any remapping, the * remapping routine will simply replace the current command, * so we the remapping call will terminate the current action * with 'exit' and thus never return here. */ checkRemapping(); /* * If this is an implicit action, check for danger: we never * try a command implicitly when the command is obviously * dangerous. */ if (isImplicit) { /* * verify the action for an implicit command, checking * for actions that aren't allowe implicitly - we never * try a command implicitly when the command is (or * should be) obviously dangerous or is simply * non-obvious */ result = verifyAction(); /* * If the action can be performed, but can't be performed * implicitly, abort. Note that we only silently ignore * the action if it is allowed normally but not * implicitly: if it's not even allowed normally, we'll * simply fail later with the normal failure message, * since there's no harm in trying. */ if (result != nil && result.allowAction && !result.allowImplicit) abortImplicit; } /* * If this is an implicit command, display a message * indicating that we're performing the command. */ impReport = maybeAnnounceImplicit(); /* * Make one or two passes through verifications and * preconditions. If any precondition performs an implicit * command, we must run everything a second time to ensure * that the implicit command or commands did not invalidate * any earlier precondition or a verification. * * Run verifications before preconditions, because there * would be no point in applying implicit commands from * preconditions if the command verifies as illogical in the * first place. */ for (local iter = 1 ; iter <= 2 ; ++iter) { /* verify the action */ result = verifyAction(); /* * if verification doesn't allow the command to proceed, * show the reason and end the command */ if (result != nil && !result.allowAction) { /* show the result message */ result.showMessage(); /* mark the command as a failure */ gTranscript.noteFailure(); /* * if we have an implicit report, mark it as a mere * attempt, since the action can't be completed */ if (impReport != nil) impReport.noteJustTrying(); /* terminate the command */ exit; } /* * Check preconditions of the action. If we don't invoke * any implicit commands, we can stop here: nothing in * the game state will have changed, so there is no need * to re-verify or re-check preconditions. * * Only allow implicit actions on the first pass. Do not * perform implicit actions on the second pass, because * if we did so we could get into an infinite loop of * conflicting preconditions, where each pass would * reverse the state from the last pass. */ if (!checkPreConditions(iter == 1)) break; } /* * Disable sense caching once we start the action phase - * once we start making changes to game state, it's too much * work to figure out when to invalidate the cache, so simply * turn off caching entirely. * * Note that the sense cache will already be disabled if we * executed any implied commands, because the first implied * command will have disabled the cache as soon as it reached * its execution phase, and no one will have turned caching * back on. It does no harm to disable it again here. */ libGlobal.disableSenseCache(); /* if desired, run the "before" notifications before "check" */ if (gameMain.beforeRunsBeforeCheck) runBeforeNotifiers(); /* * Invoke the action's execution method. Catch any "exit * action" exceptions - these indicate that the action is * finished but that the rest of the command processing is to * proceed as normal. */ try { /* notify the actor of what we're about to do */ gActor.actorAction(); /* check the action */ checkAction(); /* if desired, run the "before" notifications after "check" */ if (!gameMain.beforeRunsBeforeCheck) runBeforeNotifiers(); /* execute the action */ execAction(); } catch (ExitActionSignal eaSig) { /* * an exit action signal was thrown - since we've now * skipped past any remaining action processing, simply * continue with the rest of the command processing as * normal */ } /* call afterAction for each object in the notify list */ notifyBeforeAfter(&afterAction); /* notify the actor's containers of the completed action */ gActor.forEachContainer(callRoomAfterAction); /* run the after-action processing */ afterAction(); } catch (ExitSignal exc) { /* the execution sequence is finished - simply stop here */ } } /* * Run the "before" notifiers: this calls beforeAction on everything * in scope, and calls roomBeforeAction on the actor's containers. */ runBeforeNotifiers() { /* run the before-action processing */ beforeAction(); /* * notify the actor's containers that an action is about * to take place within them */ gActor.forEachContainer(callRoomBeforeAction); /* call beforeAction for each object in the notify list */ notifyBeforeAfter(&beforeAction); } /* * Reset the message generation context for a sense change. This * can be called when something substantial happens in the midst of * a command, and we might need different message generation rules * before and after the change. For example, this is used when a * non-player character moves from one location to another, because * the NPC might want to generate leaving and arriving messages * differently in the two locations. */ recalcSenseContext() { /* tell the sense context capturer to recalculate the context */ senseContext.recalcSenseContext(); } /* * Maybe announce the action as an implied action. */ maybeAnnounceImplicit() { /* * if we're a remapped action, we'll actually want to announce * the original, not the remapping */ if (remappedFrom != nil) return remappedFrom.maybeAnnounceImplicit(); /* * if we're implicit, and we have an implicit announcement * message, announce the implicit action */ if (isImplicit && implicitMsg != nil) return gTranscript.announceImplicit(self, implicitMsg); /* we don't need to announce the implied action */ return nil; } /* * Announce the object of an action. This should be used for each * iteration of a command that takes objects to announce the objects * on this iteration. * * We announce an object under several circumstances: * * - If we are iterating through multiple objects, we'll show the * current object to show the player the individual step in the * command being performed. * * - If 'all' was used to specify the object, we'll announce it even * if only one object is involved, to make it clear to the player * exactly what we chose as a match. * * - If we are executing the command on a single object, and the * object was chosen through disambiguation of a set of ambiguous * choices, and some of the discarded possibilities were logical but * less so than the chosen object, we'll show the assumption we * made. In such cases, our assumption is not necessarily correct, * so we'll tell the user about our choice explicitly by way of * confirmation - this gives the user a better chance of noticing * quickly if our assumption was incorrect. * * - If we supplied a default for a missing noun phrase in the * player's command, we'll show what we chose. Since the player * didn't say what they meant, we'll make it plain that we're * providing an assumption about what we thought they must have * meant. * * 'info' is the ResolveInfo object describing this resolved object, * and 'numberInList' is the total number of objects we're iterating * over for this object function (direct object, indirect object, * etc). 'whichObj' is one of the object function constants * (DirectObject, IndirectObject, etc) describing which object we're * mentioning; the language-specific message generator might use * this in conjunction with the action to include a preposition with * the displayed phrase, for example, or choose an appropriate * inflection. */ announceActionObject(info, numberInList, whichObj) { /* * Show prefix announcements only if the player character is * performing the action. For NPC's, we don't use the prefix * format, because it doesn't work as well for NPC result * reports; instead, the NPC versions of the library messages * tend to use sufficiently detailed reports that the prefix * isn't required (for example, "Bob takes the iron key" rather * than just "Taken"). */ if (gActor.isPlayerChar) { /* check for the various announcements */ if (maybeAnnounceMultiObject(info, numberInList, whichObj)) { /* we've done a multi announcement, so we're now done */ } else if ((info.flags_ & UnclearDisambig) != 0 || ((info.flags_ & ClearDisambig) != 0 && gameMain.ambigAnnounceMode == AnnounceClear)) { /* show the object, since we're not certain it's right */ gTranscript.announceAmbigActionObject(info.obj_, whichObj); } else if ((info.flags_ & DefaultObject) != 0 && !(info.flags_ & AnnouncedDefaultObject)) { /* * Show the object, since we supplied it as a default. * At this stage in the command, we have resolved * everything. */ gTranscript.announceDefaultObject( info.obj_, whichObj, self, true); /* note that we've announced this object */ info.flags_ |= AnnouncedDefaultObject; } } } /* announce a multi-action object, if appropriate */ maybeAnnounceMultiObject(info, numberInList, whichObj) { /* * announce if we have more than one object, or we're set to * announce this object in any case */ if (numberInList > 1 || (info.flags_ & AlwaysAnnounce) != 0) { /* show the current object of a multi-object action */ gTranscript.announceMultiActionObject( info.multiAnnounce, info.obj_, whichObj); /* tell the caller we made an announcement */ return true; } /* tell the caller we didn't make an announcement */ return nil; } /* * Pre-calculate the multi-object announcement text for each object. * This is important because these announcements might choose a form * for the name that distinguishes it from the other objects in the * iteration, and the basis for distinction might be state-dependent * (such as the object's current owner or location), and the relevant * state might change as we iterate over the objects. From the * user's perspective, they're referring to the objects based on the * state at the start of the command, so the user will expect to see * names based on the that state. */ cacheMultiObjectAnnouncements(lst, whichObj) { /* run through the list and cache each object's announcement */ foreach (local cur in lst) { /* calculate and cache this object's multi-object announcement */ cur.multiAnnounce = libMessages.announceMultiActionObject( cur.obj_, whichObj, self); } } /* get the list of resolved objects in the given role */ getResolvedObjList(which) { /* * the base action doesn't have any objects in any roles, so just * return nil; subclasses need to override this */ return nil; } /* * Announce a defaulted object list, if appropriate. We'll announce * the object if we have a single object in the given resolution * list, it was defaulted, and it hasn't yet been announced. */ maybeAnnounceDefaultObject(lst, which, allResolved) { /* * if the list has exactly one element, and it's marked as a * defaulted object, and it hasn't yet been announced, announce * it */ if (lst != nil && lst.length() == 1 && (lst[1].flags_ & DefaultObject) != 0 && !(lst[1].flags_ & AnnouncedDefaultObject)) { /* announce the object */ gTranscript.announceDefaultObject( lst[1].obj_, which, self, allResolved); /* * we've now announced the object; mark it as announced so * we don't show the same announcement again */ lst[1].flags_ |= AnnouncedDefaultObject; } } /* * "Pre-announce" a common object for a command that might involve * iteration over other objects. For example, in "put all in box", * the box is common to all iterations of the command, so we would * want to preannounce it, if it needs to be announced at all, * before the iterations of the command. * * We'll announce the object only if it's marked as defaulted or * unclearly disambiguated, and then only if the other list will be * announcing its objects as multi-action objects. However, we do * not pre-announce anything for a remapped action, because we'll * show the full action description for each individually announced * object, so we don't need or want a separate announcement for the * group. * * Returns true if we did any pre-announcing, nil otherwise. If we * return true, the caller should not re-announce this object during * the iteration, since our pre-announcement is common to all * iterations. */ preAnnounceActionObject(info, mainList, whichObj) { /* do not pre-announce anything for a remapped action */ if (isRemapped()) return nil; /* * determine if the main list will be announcing its objects - * it will if it has more than one object, or if its one object * is marked as "always announce" */ if (mainList.length() > 1 || (mainList[1].flags_ & AlwaysAnnounce) != 0) { /* * we will be announcing the main list object or objects, so * definitely pre-announce this object if appropriate */ announceActionObject(info, 1, whichObj); /* tell the caller we pre-announced the object */ return true; } /* we didn't pre-announce the object */ return nil; } /* * Run our action-specific pre-processing. By default, we do * nothing here. */ beforeAction() { } /* * Check the action. This runs the 'check' phase, and must be * overridden for each subclass. * * Intransitive actions don't generally have to do anything in the * 'check' phase, since they can simply do any necessary checks in * the 'execute' phase that runs immediately after 'check'. This * phase is separated out from 'execute' mostly for the benefit of * transitive actions, where the 'check' phase gives the involved * objects a chance to object to the action. */ checkAction() { } /* * Execute the action. This must be overridden by each subclass. * * Intransitive actions must do all of their work in this routine. * In most cases, transitive actions will delegate processing to one * or more of the objects involved in the command - for example, * most single-object commands will call a method in the direct * object to carry out the command. */ execAction() { /* by default, just show the 'no can do' message */ mainReport(&cannotDoThatMsg); } /* * Run our action-specific post-processing. By default, we do * nothing here. */ afterAction() { } /* * Verify the action. Action subclasses with one or more objects * should call object verification routines here. Returns a * VerifyResultList with the results, or nil if there are no * verification results at all. A nil return should be taken as * success, not failure, because it means that we found no objection * to the command. */ verifyAction() { /* * there are no objects in the default action, but we might have * pre-condition verifiers */ return callVerifyPreCond(nil); } /* * Initialize tentative resolutions for other noun phrases besides * the one indicated. */ initTentative(issuingActor, targetActor, whichObj) { /* by default, we have no noun phrases to tentatively resolve */ } /* * Check for remapping the action. This should check with each * resolved object involved in the command to see if the object wants * to remap the action to a new action; if it does, the object must * replace the current action (using replaceAction or equivalent). * Note that replacing the action must use 'exit' to terminate the * original action, so this will never return if remapping actually * does occur. */ checkRemapping() { /* by default, we have no objects, so we do nothing here */ } /* * Invoke a callback with a verify results list in gVerifyResults, * using the existing results list or creating a new one if there is * no existing one. Returns the results list used. */ withVerifyResults(resultsSoFar, obj, func) { local oldResults; /* if we don't already have a result list, create one */ if (resultsSoFar == nil) resultsSoFar = new VerifyResultList(); /* remember the old global results list */ oldResults = gVerifyResults; /* install the new one */ gVerifyResults = resultsSoFar; /* make sure we restore the old result list on the way out */ try { /* invoke the callback */ (func)(); } finally { /* restore the old result list */ gVerifyResults = oldResults; } /* return the result list */ return resultsSoFar; } /* * Verify the non-object-related pre-conditions. This runs * verification on each of the pre-condition objects defined for the * action. */ callVerifyPreCond(resultSoFar) { /* look at each of our preconditions */ foreach (local cond in preCond) { /* * If this precondition defines a verifier, call it. Check * to see if we have a verifier defined first so that we can * avoid creating a result list if we won't have any use for * it. */ if (cond.propDefined(&verifyPreCondition)) { /* * invoke the pre-condition verifier - this is an * action-level verifier, so there's no object involved */ resultSoFar = withVerifyResults(resultSoFar, nil, {: cond.verifyPreCondition(nil) }); } } /* return the latest result list */ return resultSoFar; } /* * Call a catch-all property on the given object. * * actionProp is the custom per-object/per-action property that we * normally invoke to process the action. For example, if we're * processing verification for the direct object of Take, this would * be &verifyDobjTake. * * defProp is the default property that corresponds to actionProp. * This is the per-object/default-action property that we invoke * when the object doesn't provide a "more specialized" version of * actionProp - that is, if the object doesn't define or inherit * actionProp at a point in its class hierarchy that is more * specialized than the point at which it defines defProp, we'll * call defProp. If there is a more specialized definition of * actionProp for the object, it effectively overrides the default * handler, so we do not invoke the default handler. * * allProp is the catch-all property corresponding to actionProp. * We invoke this property in all cases. * * Returns true if there is indeed a Default property that overrides * the action, nil if not. */ callCatchAllProp(obj, actionProp, defProp, allProp) { /* always invoke the catch-all property first */ obj.(allProp)(); /* * invoke the default property only if the object doesn't have a * "more specialized" version of actionProp */ if (!obj.propHidesProp(actionProp, defProp)) { /* the Default overrides the action-specific method */ obj.(defProp)(); /* tell the caller the Default routine handled it */ return true; } else { /* tell the caller to call the action-specific property */ return nil; } } /* * Call a verification routine. This creates a results object and * makes it active, then invokes the given verification routine on * the given object. * * We call verification directly on the object, and we also call * verification on the object's preconditions. * * If resultSoFar is non-nil, it is a VerifyResultList that has the * results so far - this can be used for multi-object verifications * to gather all of the verification results for all of the objects * into a single result list. If resultSoFar is nil, we'll create a * new result list. */ callVerifyProp(obj, verProp, preCondProp, remapProp, resultSoFar, whichObj) { local remapInfo; /* check for remapping */ if ((remapInfo = obj.(remapProp)()) != nil) { /* call the remapped verify */ resultSoFar = remapVerify(whichObj, resultSoFar, remapInfo); /* * If the original object has a verify that effectively * "overrides" the remap - i.e., defined by an object that * inherits from the object where the remap is defined - then * run it by the overriding verify as well. */ local remapSrc = obj.propDefined(remapProp, PropDefGetClass); if (obj.propDefined(verProp) && overrides(obj, remapSrc, verProp)) { resultSoFar = withVerifyResults( resultSoFar, obj, function() { obj.(verProp)(); }); } /* return the results */ return resultSoFar; } /* initialize tentative resolutions for other noun phrases */ initTentative(gIssuingActor, gActor, whichObj); /* * run the verifiers in the presence of a results list, and * return the result list */ return withVerifyResults(resultSoFar, obj, function() { local lst; /* * check the object for a default or catch-all verifier, and * call it if present; if there isn't a default that * overrides the action-specific verifier, continue to the * action-specific verifer */ if (!callCatchAllProp( obj, verProp, objectRelations.verifyDefaultProps[whichObj], objectRelations.verifyAllProps[whichObj])) { /* * invoke the action-specific verifier method - this * will update the global verification results object * with the appropriate status for this action being * performed on this object */ obj.(verProp)(); } /* * Check the pre-conditions defined for this action on this * object. For each one that has a verifier, invoke the * verifier. */ lst = getObjPreConditions(obj, preCondProp, whichObj); if (lst != nil) foreach (local cur in lst) cur.verifyPreCondition(obj); }); } /* * Get the precondition list for an object. whichObj is the object * role of the object whose preconditions we're retrieving; this is * nil if we're looking for action-level preconditions. */ getObjPreConditions(obj, preCondProp, whichObj) { local allPreProp; local defPreProp; local pre; /* * if we're looking for action preconditions, there are no * default or catch-all properties, so simply get the * preconditions from the action */ if (whichObj == nil) return obj.(preCondProp); /* get the default-action and catch-all precondition properties */ defPreProp = objectRelations.preCondDefaultProps[whichObj]; allPreProp = objectRelations.preCondAllProps[whichObj]; /* * Check for an "overriding" default-action handler. If we have * a default-action handler that hides the specific handler for * this action, use the default handler's precondition list * instead. Otherwise, use the specific action preconditions. */ if (obj.propHidesProp(defPreProp, preCondProp)) pre = obj.(defPreProp); else pre = obj.(preCondProp); /* if we have catch-all preconditions, add them to the list */ if (obj.propDefined(allPreProp)) { /* get the catch-all preconditions */ local allPre = obj.(allPreProp); /* add them to the list so far (if any) */ pre = (pre == nil ? allPre : pre + allPre); } /* return the precondition list */ return pre; } /* * Verify that some sort of handling for this action is defined on * at least one of the given objects. If we have no handlers at all * definfed, we'll add an "illogical" status to the verification * results to indicate that the action is not defined on this * object. This check provides a last resort for verbs with no * handling at all defined on the involved objects, to ensure that * the verb won't go completely unprocessed. * * Each entry in objList is an object involved in the action. For * each entry in objList, there must be *THREE* entries in propList: * a verify property, a check property, and an action property. If * any of these three properties is defined on the corresponding * object, we'll allow the command to proceed. If we can find none * of the given handler properties on any of our objects, we'll add * an "illogical" verify result. */ verifyHandlersExist(objList, propList, result) { /* * check each object to see if any define their corresponding * action property */ for (local i = 1, local j = 1, local len = objList.length() ; i <= len ; ++i, j += 3) { /* check each of the associated handler properties */ for (local k = 0 ; k < 3 ; ++k) { /* check this handler property */ if (objList[i].propDefined(propList[j + k])) { /* we've found a handler, so we can proceed */ return result; } } } /* * None of the objects defines an appropriate action method, so * this verifies as illogical. If there's no result list so * far, create one. */ if (result == nil) result = new VerifyResultList(); /* add an "illogical" status to the results */ result.addResult(new IllogicalVerifyResult(&cannotDoThatMsg)); /* return the result */ return result; } /* * Call the beforeAction or afterAction method for each object in * the notification list. */ notifyBeforeAfter(prop) { local lst; /* get a table of potential notification receivers */ lst = getNotifyTable(); /* go through the table and notify each object */ lst.forEachAssoc({obj, val: obj.(prop)()}); } /* * Get the list of objects to notify before or after the action has * been performed. */ getNotifyTable() { local tab; local curObjs; local actor; /* stash the current actor in a local for faster reference */ actor = gActor; /* start with everything connected by containment to the actor */ tab = actor.connectionTable(); /* add the actor's explicitly registered notification list */ foreach (local cur in actor.getActorNotifyList()) tab[cur] = true; /* add the items explicitly registered in the actor's location(s) */ actor.forEachContainer( {loc: loc.getRoomNotifyList().forEach( {obj: tab[obj] = true}) }); /* get the list of objects directly involved in the command */ curObjs = getCurrentObjects(); /* * add any objects explicitly registered with the objects * directly involved in the command */ foreach (local cur in curObjs) { /* add each item in the object's notify list */ foreach (local ncur in cur.getObjectNotifyList()) tab[ncur] = true; } /* * Remove from the list all of the actor's containers. These * will be notified via the more specific room notification * methods, so we don't want to send them generic notifiers as * well. */ tab.forEachAssoc(function(obj, val) { if (actor.isIn(obj)) tab.removeElement(obj); }); /* if we have any explicitly registered objects, add them */ if (beforeAfterObjs != nil) { /* add each object in the list of explicitly registered objects */ foreach (local cur in beforeAfterObjs) tab[cur] = true; } /* return the final table */ return tab; } /* * Register an object for explicit inclusion in beforeAction and * afterAction notifications. This can be used to register objects * that might not be connected by containment or otherwise * notifiable by normal means. If this is called after the * beforeAction notification loop has already started, then the * object will only be sent an afterAction notification. */ addBeforeAfterObj(obj) { /* if we don't yet have a before/after list, create one */ if (beforeAfterObjs == nil) beforeAfterObjs = new Vector(16); /* add the object to the list */ beforeAfterObjs.append(obj); } /* vector of objects requiring explicit before/after notification */ beforeAfterObjs = nil /* * Get the list of all of the objects (direct object, indirect * object, and any additional objects for actions with three or more * object roles) involved in the current execution. This is valid * only during a call to doActionOnce(), since that's the only time * a particular set of objects are selected for the action. * * By default, an action has no objects roles at all, so we'll just * return an empty list. */ getCurrentObjects() { return []; } /* * Set the current objects. This takes a list of the same form * returned by getCurrentObjects(). */ setCurrentObjects(lst) { } /* * Check any pre-conditions for the action. * * This should check all of the conditions that must be met for the * action to proceed. If any pre-condition can be met by running an * implicit command first, that implicit command should be executed * here. If any pre-condition cannot be met, this routine should * notify the actor and throw an ExitSignal. * * Returns true if any implicit commands are executed, nil if not. * Implicit commands can only be attempted if allowImplicit is true; * if this is nil, a precondition must simply fail (by displaying an * appropriate failure report and using 'exit') without attempting * an implicit command if its assertion does not hold. */ checkPreConditions(allowImplicit) { /* check each condition in our action preconditions */ return callPreConditions(getPreCondDescList(), allowImplicit); } /* * Get the precondition descriptor list for the action. For the base * intransitive action type, this simply returns the list of * conditions for the action itself. */ getPreCondDescList() { /* get the action-level preconditions */ return getObjPreCondDescList(self, &preCond, nil, nil); } /* * Get a precondition descriptor list for the given object. This * returns a list of PreCondDesc objects that wrap the preconditions * for the given object in the given role for this action. */ getObjPreCondDescList(obj, preCondProp, checkArg, whichObj) { local lst; /* get the list of preconditions for the given object in its role */ lst = getObjPreConditions(obj, preCondProp, whichObj); /* if there are no preconditions, return an empty list */ if (lst == nil) return []; /* * wrap the precondition objects in descriptors, so that we can * keep track of the check argument that goes with these * preconditions */ return lst.mapAll({x: new PreCondDesc(x, checkArg)}); } /* * Call a method on all of the precondition objects in the * precondition list obtained from the given property of the given * object. */ callPreConditions(lst, allowImplicit) { local ret; local i; /* presume we won't call any implicit commands */ ret = nil; /* * note the original descriptor list order, so that we can retain * the current ordering when the execution order doesn't require * changes */ i = 0; lst.forEach({x: x.index_ = i++}); /* sort the precondition list by execution order */ lst = lst.sort(SortAsc, function(a, b) { local delta; /* if the execution orders differ, sort by execution order */ delta = a.cond_.preCondOrder - b.cond_.preCondOrder; if (delta != 0) return delta; /* otherwise, retain the original list order */ return a.index_ - b.index_; }); /* catch any 'exit' signals within the preconditions */ try { /* invoke the check method for each condition in the list */ foreach (local cur in lst) { /* * call this precondition; if it runs an implicit * command, note that we have run an implicit command */ if (cur.checkPreCondition(allowImplicit)) ret = true; } } catch (ExitSignal es) { /* * any 'exit' that occurs within a precondition is a failure * for the enclosing action */ gTranscript.noteFailure(); /* re-throw the 'exit' to cancel the enclosing action */ throw es; } /* return true if any implicit commands were executed */ return ret; } /* * Our list of action-level pre-condition objects. These are the * conditions that apply to the overall action, not to the * individual objects involved. (Object-level pre-conditions are * attached to the objects, not to the action.) */ preCond = [] /* * Finish the result list for a resolved noun phrase. This is used * just before disambiguation. We'll give each object in the list a * chance to filter the list with filterResolveList, and we'll note * the noun phrase we matched in each resolved object. */ finishResolveList(lst, whichObj, np, requiredNum) { /* give each object a chance to filter the list */ foreach (local cur in lst) lst = cur.obj_.filterResolveList( lst, self, whichObj, np, requiredNum); /* stash the noun phrase in each object in the list */ foreach (local cur in lst) cur.np_ = np; /* return the list */ return lst; } /* * Get a list of verification results for the given ResolveInfo * objects, sorted from best to worst. Each entry in the returned * list will be a VerifyResultList object whose obj_ property is set * to the ResolveInfo object for which it was generated. */ getSortedVerifyResults(lst, verProp, preCondProp, remapProp, whichObj, np, requiredNum) { local results; local idx; /* if there's nothing in the list, we're done */ if (lst == []) return lst; /* * Before we run verification, give each object in the list a * chance to do its own filtering on the entire list. This form * of filtering allows an object to act globally on the list, so * that it can take special action according to the presence or * absence of other objects, and can affect the presence of other * objects. */ lst = finishResolveList(lst, whichObj, np, requiredNum); /* create a vector to hold the verification results */ results = new Vector(lst.length()); /* * Call the given verifier method on each object, noting each * result. */ idx = 0; foreach (local cur in lst) { local curResult; /* call the verifier method and note the current result */ curResult = callVerifyProp(cur.obj_, verProp, preCondProp, remapProp, nil, whichObj); /* * save the ResolveInfo in the verify result list object, so * that we can figure out later (after sorting the results) * which original ResolveInfo this verification result * applies to */ curResult.obj_ = cur; /* remember the original list order */ curResult.origOrder = idx++; /* add this verify result to our result vector */ results.append(curResult); } /* * Sort the results in descending order of logicalness, and * return the sorted list. When results are equivalently * logical, keep the results in their existing order. */ return results.toList().sort(SortDesc, function(x, y) { /* compare the logicalness */ local c = x.compareTo(y); /* * if it's the same, keep in ascending pluralOrder - note * that we must reverse the sense of this comparison, since * we're sorting the overall list in descending order */ if (c == 0) c = (y.obj_.obj_.pluralOrder - x.obj_.obj_.pluralOrder); /* if they're otherwise the same, preserve the original order */ if (c == 0) c = (y.origOrder - x.origOrder); /* return the result */ return c; }); } /* * Combine any remapped verify results in the given verify result * list. We will remove any result that was remapped to a different * object if and only if the target of the remapping appears in the * list and has the same results as the remapped original. When * objects are remapped in this fashion, they become effectively * equivalent for the purposes of this command, so we don't have to * distinguish between them for disambiguation or defaulting * purposes. */ combineRemappedVerifyResults(lst, role) { /* scan each element in the list */ for (local i = 1, local len = lst.length() ; i <= len ; ++i) { local cur = lst[i]; /* if this element has been remapped, consider it further */ if (cur.remapTarget_ != nil) { local other; /* * scan for another entry in the list that matches us * for remapping purposes */ other = lst.indexWhich( {x: x != cur && cur.matchForCombineRemapped(x, self, role)}); /* * if we found another entry, delete the other entry, * since it is indistinguishable from this entry */ if (other != nil) { /* remove this element from the list */ lst -= cur; /* adjust our list counters for the deletion */ --i; --len; } } } /* return the updated list */ return lst; } /* * Filter an ambiguous object list using the given verification * method. We call the given verification method on each object, * noting the result, then find the best (most logical) result in * the list. We reduce the set to the objects that all have the * same best value - everything else in the list is less logical, so * we discard it. This gives us a set of objects that are all of * equivalent likelihood and all of the best likelihood of all the * objects. * * This is the typical way that we disambiguate a list of objects, * but this is merely a service routine, so individual actions can * choose to use this or other mechanisms as appropriate. */ filterAmbiguousWithVerify(lst, requiredNum, verProp, preCondProp, remapProp, whichObj, np) { local results; local discards; local keepers; local bestResult; local uniqueKeepers; /* if there's nothing in the list, there's nothing to do */ if (lst == []) return lst; /* first, filter out redundant facets */ lst = filterFacets(lst); /* * Call the verifier method on each object, and sort the results * from best to worst. */ results = getSortedVerifyResults(lst, verProp, preCondProp, remapProp, whichObj, np, requiredNum); /* note the best result value */ bestResult = results[1]; /* * ask the noun phrase to filter this list down to the ones it * wants to keep */ keepers = np.getVerifyKeepers(results); /* * Count the number of unique keepers - this is the number of * items in the keepers list that don't have any equivalents in * the keepers list. * * To calculate this number, start with the total number of * items in the list, and reduce it by one for each item with an * earlier equivalent in the list. Note that we only ignore * items with unique equivalents *earlier* in the list so that * we keep exactly one of each equivalent - if we ignored every * element with a unique equivalent elsewhere in the list, we'd * ignore every equivalent item, so we'd only count the items * with no equivalents at all. */ uniqueKeepers = keepers.length(); for (local i = 1, local cnt = keepers.length() ; i <= cnt ; ++i) { local eqIdx; /* check for a unique equivalent earlier in the list */ eqIdx = keepers.indexWhich( {x: x.obj_.obj_.isVocabEquivalent(keepers[i].obj_.obj_)}); if (eqIdx != nil && eqIdx < i) { /* * this one has an earlier equivalent, so don't include * it in the unique item count */ --uniqueKeepers; } } /* * If we found more items to keep than were required by the * caller, we were not able to reduce the set to an unambiguous * subset. In this case, keep *all* of the items that are * logical. */ if (uniqueKeepers > requiredNum) { local allAllowed; /* filter so that we keep all of the logical results */ allAllowed = results.subset({x: x.allowAction}); /* if that list is non-empty, use it as the keepers */ if (allAllowed.length() != 0) keepers = allAllowed; } /* * Get the list of discards - this is the balance of the * original result list after removing the best ones. */ discards = results - keepers; /* * We now have the set of objects we want, but the entries in * the list are all VerifyResultList instances, and we just want * the objects - pull out a list of just the ResolveInfo, and * return that. */ keepers = keepers.mapAll({x: x.obj_}); /* * Check to see if we should set flags in the results. If we * eliminated any objects, flag the remaining objects as having * been selected through disambiguation. If the best of the * discarded objects were logical, flag the survivors as * "unclear," because we only selected them as better than the * discards, and not because they were the only possible * choices. */ if (discards != []) { local addedFlags; /* * We have reduced the set. If the best of the discards was * ranked as highly as the survivors at the coarsest level, * flag the survivors as having been "unclearly" * disambiguated; otherwise, mark them as "clearly" * disambiguated. */ if (keepers.indexOf(bestResult.obj_) != nil && (discards[1].getEffectiveResult().resultRank == bestResult.getEffectiveResult().resultRank)) { /* * we had to make a choice that discarded possibilities * that were valid, though not as good as the one we * chose - mark the objects as being not perfectly clear */ addedFlags = UnclearDisambig; /* * if the keepers and the rejects are all basic * equivalents, don't bother flagging this as unclear, * since there's no point in mentioning that we chose * one basic equivalent over another, as they all have * the same name */ if (BasicResolveResults.filterWithDistinguisher( keepers + discards.mapAll({x: x.obj_}), basicDistinguisher).length() == 1) { /* they're all basic equivalents - mark as clear */ addedFlags = ClearDisambig; } } else { /* the choice is clear */ addedFlags = ClearDisambig; } /* add the flags to each survivor */ foreach (local cur in keepers) cur.flags_ |= addedFlags; } /* return the results */ return keepers; } /* * Filter a plural list with a verification method. We'll reduce * the list to the subset of objects that verify as logical, if * there are any. If there are no logical objects in the list, * we'll simply return the entire original list. */ filterPluralWithVerify(lst, verProp, preCondProp, remapProp, whichObj, np) { local results; /* if there's nothing in the list, there's nothing to do */ if (lst == []) return lst; /* first, filter out redundant facets */ lst = filterFacets(lst); /* * Call the verifier method on each object, and sort the results * from best to worst. */ results = getSortedVerifyResults(lst, verProp, preCondProp, remapProp, whichObj, np, nil); /* * If the best (and thus first) result allows the action, filter * the list to keep only the "keepers," as determined by the noun * phrase. Otherwise, there are no allowed results, so return * the original list. */ if (results[1].allowAction) results = np.getVerifyKeepers(results); /* return the resolve results objects from the list */ return results.mapAll({x: x.obj_}); } /* * Filter out redundant facets of the same object. The various * facets of an object are equivalent to the parser. An object that * has multiple facets is meant to appear to be one game world * object from the perspective of a character - the multiple facet * objects are an internal implementation detail. */ filterFacets(lst) { local result; local actor = gActor; /* * create a vector for the result list, presuming we'll keep all * of the original list elements */ result = new Vector(lst.length(), lst); /* check for facets */ foreach (local cur in lst) { local allFacets; /* if this item has any facets, check for them in our results */ allFacets = cur.obj_.getFacets() + cur.obj_; if (allFacets.length() != 0) { local inScopeFacets; local best; /* make a new list of the facets that appear in the results */ inScopeFacets = allFacets.subset( {f: result.indexWhich({r: r.obj_ == f}) != nil}); /* pick the best of those facets */ best = findBestFacet(actor, inScopeFacets); /* * remove all of the facets besides the best one from the * result list - this will ensure that we have only the * single best facet in the final results */ foreach (local r in result) { /* * if this result list item is in the facet list, and * it's not the best facet, delete it from the result * list */ if (r.obj_ != best && inScopeFacets.indexOf(r.obj_) != nil) result.removeElement(r); } } } /* return the result list */ return result.toList(); } /* * Get a default object using the given verification method. We'll * start with the 'all' list, then use the verification method to * reduce the list to the most likely candidates. If we find a * unique most likely candidate, we'll return a ResolveInfo list * with that result; otherwise, we'll return nothing, since there is * no suitable default. */ getDefaultWithVerify(resolver, verProp, preCondProp, remapProp, whichObj, np) { local lst; local results; local bestResult; /* * Start with the 'all' list for this noun phrase. This is the * set of every object that we consider obviously suitable for * the command, so it's a good starting point to guess at a * default object. */ lst = resolver.getAllDefaults(); /* eliminate objects that can't be defaults for this action */ lst = lst.subset({x: !x.obj_.hideFromDefault(self)}); /* * reduce equivalent items to a single instance of each * equivalent - if we have several equivalent items that are * equally good as defaults, we can pick just one */ lst = resolver.filterAmbiguousEquivalents(lst, np); /* if we have no entries in the list, there is no default */ if (lst == []) return nil; /* * get the verification results for these objects, sorted from * best to worst */ results = getSortedVerifyResults(lst, verProp, preCondProp, remapProp, whichObj, np, nil); /* eliminate redundant remapped objects */ results = combineRemappedVerifyResults(results, whichObj); /* note the best result */ bestResult = results[1]; /* * if the best item is not allowed as an implied object, there * is no default */ if (!bestResult.allowImplicit) return nil; /* * The best item must be uniquely logical in order to be a * default - if more than one item is equally good, it makes no * sense to assume anything about what the user meant. So, if * we have more than one item, and the second item is equally as * logical as the first item, we cannot supply a default. (The * second item cannot be better than the first item, because of * the sorting - at most, it can be equally good.) */ if (results.length() != 1 && bestResult.compareTo(results[2]) == 0) return nil; /* * We have a uniquely logical item, so we can assume the user * must have been referring to this item. Return the * ResolveInfo for this item (which the verify result sorter * stashed in the obj_ property of the verify result object). * * Before returning the list, clear the 'all' flag in the result * (getAll() set that flag), and replace it with the 'default' * flag, since the object is an implied default. */ bestResult.obj_.flags_ &= ~MatchedAll; bestResult.obj_.flags_ |= DefaultObject; /* return the result list */ return [bestResult.obj_]; } /* * A synthesized Action (one that's generated by something other than * parsing a command line, such as an event action or nested action) * won't have a parser token list attached to it. If we're asked to * get the token list, we need to check for this possibility. If we * don't have a token list, but we do have a parent action, we'll * defer to the parent action. Otherwise, we'll simply return nil. */ getOrigTokenList() { /* if we don't have a token list, look elsewhere */ if (tokenList == nil) { /* if we have a parent action, defer to it */ if (parentAction != nil) return parentAction.getOrigTokenList(); /* we have nowhere else to look, so return an empty list */ return []; } /* inherit the standard handling from BasicProd */ return inherited(); } /* * Create a topic qualifier resolver. This type of resolver is used * for qualifier phrases (e.g., possessives) within topic phrases * within objects of this verb. Topics *usually* only apply to * TopicActionBase subclasses, but not exclusively: action remappings * can sometimes require a topic phrase from one action to be * resolved in the context of another action that wouldn't normally * involve a topic phrase. That's why this is needed on the base * Action class. */ createTopicQualifierResolver(issuingActor, targetActor) { /* create and return a topic qualifier object resolver */ return new TopicQualifierResolver(self, issuingActor, targetActor); } /* * List of objects that verified okay on a prior pass. This is a * scratch-pad for use by verifier routines, to keep track of work * they've already done. A few verifiers use this as a way to * detect when an implicit action actually finished the entire job, * which would in many cases result in a verify failure if not * checked (because a command that effects conditions that already * hold is normally illogical); by tracking that the verification * previously succeeded, the verifier can know that the action * should be allowed to proceed and do nothing. */ verifiedOkay = [] /* * Get the missing object response production for a given resolver * role. (The base action doesn't have any objects, so there's no * such thing as a missing object query.) */ getObjResponseProd(resolver) { return nil; } ; /* * Call the roomBeforeAction method on a given room's containing rooms, * then on the room itself. */ callRoomBeforeAction(room) { /* first, call roomBeforeAction on the room's containers */ room.forEachContainer(callRoomBeforeAction); /* call roomBeforeAction on this room */ room.roomBeforeAction(); } /* * Call the roomAfterAction method on a given room, then on the room's * containing rooms. */ callRoomAfterAction(room) { /* first, call roomAfterAction on this room */ room.roomAfterAction(); /* next, call roomAfterAction on the room's containers */ room.forEachContainer(callRoomAfterAction); } /* ------------------------------------------------------------------------ */ /* * Intransitive Action class - this is an action that takes no objects. * In general, each subclass should implement its action handling in its * execAction() method. */ class IAction: Action /* * resolve my noun phrases to objects */ resolveNouns(issuingActor, targetActor, results) { /* * We have no objects to resolve. The only thing we have to do * is note in the results our number of structural noun slots * for the verb, which is zero, since we have no objects at all. */ results.noteNounSlots(0); } /* * Execute the action. */ doActionMain() { /* * we have no objects to iterate, so simply run through the * execution sequence once */ doActionOnce(); } ; /* ------------------------------------------------------------------------ */ /* * "All" and "Default" are a pseudo-actions used for dobjFor(All), * iobjFor(Default), and similar catch-all handlers. These are never * executed as actual actions, but we define them so that dobjFor(All) * and the like won't generate any warnings for undefined actions. */ class AllAction: object ; class DefaultAction: object ; /* ------------------------------------------------------------------------ */ /* * Transitive Action class - this is an action that takes a direct * object. * * For simplicity, this object is its own object resolver - we really * don't need a separate resolver object because we have only one object * list for this verb. (In contrast, an action with both a direct and * indirect object might need separate resolution rules for the two * objects, and hence would need separate resolver objects for the two.) * * The advantage of implementing the Resolver behavior in this object, * rather than using a separate object, is that it's less trouble to * override object resolution rules - simply override the resolver * methods in the subclass where you define the grammar rule for the * action. */ class TAction: Action, Resolver construct() { /* inherit only the Action constructor */ inherited Action.construct(); } resetAction() { /* inherit default handling */ inherited(); /* discard our cached resolver */ dobjResolver_ = nil; } /* * Create an action for retrying an original action with changes. */ createForRetry(orig) { local action; /* create the new action based on the original action */ action = createActionFrom(orig); /* * do not include the new command in undo, since it's merely * part of the enclosing explicit command */ action.includeInUndo = nil; /* mark the action as nested */ action.setNested(); /* * Even if the original action is implicit, don't announce this * action as implicit, because it's good enough to have * announced the original implicit action. Now, this new * command actually still is implicit if the original was - we * simply don't want to announce it as such. To suppress the * extra announcement while still retaining the rest of the * desired implicitness, simply set the implicitMsg property of * the new action to nil; when there's no implicitMsg, there's * no announcement. */ action.implicitMsg = nil; /* return the new action */ return action; } /* * Retry an intransitive action as a single-object action. We'll * obtain a indirect object using the normal means (first looking * for a default, then prompting the player if we can't find a * suitable default). 'orig' is the original zero-object action. * * This routine terminates with 'exit' if it doesn't throw some * other error. */ retryWithMissingDobj(orig, asker) { /* resolve and execute the replacement action */ resolveAndReplaceAction(createForMissingDobj(orig, asker)); } /* * Retry an action as a single-object action with an ambiguous * direct object. We'll ask which of the given possible objects is * intended. */ retryWithAmbiguousDobj(orig, objs, asker, objPhrase) { local action; local resolver; /* create a missing-direct-object replacement action */ action = createForMissingDobj(orig, asker); /* reduce the object list to the objects in scope */ resolver = action.getDobjResolver(gIssuingActor, gActor, true); objs = objs.subset({x: resolver.objInScope(x)}); /* plug in the ambiguous direct object list */ action.dobjMatch = new PreResolvedAmbigProd(objs, asker, objPhrase); /* resolve and execute the replacement action */ resolveAndReplaceAction(action); } /* * Test to see if askForDobj() would find a default direct object. * Returns true if there's a default, nil if not. If this returns * true, then askForDobj() will simply take the default and proceed; * otherwise, it will have to actually ask the user for the missing * information. */ testRetryDefaultDobj(orig) { /* create the new action for checking for a direct object */ local action = createForMissingDobj(orig, ResolveAsker); /* get the default dobj */ local def = action.getDefaultDobj( action.dobjMatch, action.getDobjResolver(gIssuingActor, gActor, nil)); /* if there's exactly one result, then we have a default */ return (def != nil && def.length() == 1); } /* * Create an instance of this action for retrying a given * single-object action with a missing direct object. */ createForMissingDobj(orig, asker) { /* create the action for a retry */ local action = createForRetry(orig); /* use an empty noun phrase for the new action's direct object */ action.dobjMatch = new EmptyNounPhraseProd(); /* set our custom response production and ResolveAsker */ action.dobjMatch.setPrompt(action.askDobjResponseProd, asker); /* initialize the new action with any pre-resolved parts */ action.initForMissingDobj(orig); /* return the new action */ return action; } /* * Initialize this action in preparation for retrying with a missing * direct object. This routine must copy any phrases from the * original action that have already been resolved. This base * TAction implementation obviously can't have anything pre-resolved * in the original, since the original must simply be an IAction. * Subclasses must override as appropriate for the kinds of base * actions from which they can be retried. */ initForMissingDobj(orig) { } /* * The root production to use to parse missing direct object * responses. By default, this is nounList, but individual actions * can override this as appropriate. * * Note that language modules might want to override this to allow * for special responses. For example, in English, some verbs might * want to override this with a specialized production that allows * the appropriate preposition in the response. */ askDobjResponseProd = nounList /* get the missing object response production for a given role */ getObjResponseProd(resolver) { /* if it's the direct object, return the dobj response prod */ if (resolver.whichObject == DirectObject) return askDobjResponseProd; /* otherwise use the default handling */ return inherited(resolver); } /* * Can the direct object potentially resolve to the given simulation * object? This only determines if the object is a *syntactic* * match, meaning that it can match at a vocabulary and grammar * level. This doesn't test it for logicalness or check that it's an * otherwise valid resolution. */ canDobjResolveTo(obj) { /* check our dobj match tree to see if it can resolve to 'obj' */ return dobjMatch.canResolveTo( obj, self, issuer_, actor_, DirectObject); } /* * Resolve objects. This is called at the start of command * execution to resolve noun phrases in the command to specific * objects. */ resolveNouns(issuingActor, targetActor, results) { /* note that we have a single noun slot (the direct object) */ results.noteNounSlots(1); /* * Ask the direct object noun phrase list to resolve itself, and * store the resulting object list. Since we're starting a * resolution pass through our objects, reset the resolver if * we're reusing it. */ dobjList_ = dobjMatch.resolveNouns( getDobjResolver(issuingActor, targetActor, true), results); } /* a transitive action has one noun phrase: the direct object */ predicateNounPhrases = [&dobjMatch] /* get the role of an object */ getRoleFromIndex(idx) { /* we only take a single object role - the direct object */ return (idx == 1 ? DirectObject : inherited(idx)); } /* get the resolved object in a given role */ getObjectForRole(role) { /* return the direct object if requested */ return (role == DirectObject ? getDobj() : inherited(role)); } /* get the match tree for the noun phrase in the given role */ getMatchForRole(role) { /* return the direct object match tree if requested */ return (role == DirectObject ? dobjMatch : inherited(role)); } /* get the 'verify' property for a given object role */ getVerifyPropForRole(role) { return (role == DirectObject ? verDobjProp : inherited(role)); } /* get the 'preCond' property for a given object role */ getPreCondPropForRole(role) { return (role == DirectObject ? preCondDobjProp : inherited(role)); } /* get the 'remap' property for a given object role */ getRemapPropForRole(role) { return (role == DirectObject ? remapDobjProp : inherited(role)); } /* get the ResolveInfo for the given object */ getResolveInfo(obj, oldRole) { local info = nil; /* scan our resolved direct object list for the given object */ if (dobjList_ != nil) info = dobjList_.valWhich({x: x.obj_ == obj}); /* if we didn't find one, create one from scratch */ if (info == nil) { /* * if there's anything in the old dobj role, copy the flags * and noun phrase to the new role */ if (dobjList_.length() > 0) { /* get the flags and noun phrase from the old role */ info = new ResolveInfo( obj, dobjList_[1].flags_, dobjList_[1].np_); } else { /* there's no old role, so start from scratch */ info = new ResolveInfo(obj, 0, nil); } } /* return what we found (or created) */ return info; } /* get the list of resolved objects in the given role */ getResolvedObjList(which) { return (which == DirectObject ? getResolvedDobjList() : inherited(which)); } /* get the list of resolved direct objects */ getResolvedDobjList() { /* * if we have a direct object list, return the objects from it; * otherwise, return an empty list */ return (dobjList_ == nil ? nil : dobjList_.mapAll({x: x.obj_})); } /* manually set the resolved objects - we'll set the direct object */ setResolvedObjects(dobj) { /* set the resolved direct object */ setResolvedDobj(dobj); } /* set the resolved direct object */ setResolvedDobj(dobj) { /* * set the direct object tree to a fake grammar tree that * resolves to our single direct object, in case we're asked to * resolve explicitly */ dobjMatch = new PreResolvedProd(dobj); /* * Set the resolved direct object list to the single object or * to the list of objects, depending on what we received. */ dobjList_ = makeResolveInfoList(dobj); /* set the current object as well */ dobjCur_ = (dobjList_.length() > 0 ? dobjList_[1].obj_ : nil); dobjInfoCur_ = (dobjList_.length() > 0 ? dobjList_[1] : nil); } /* manually set the unresolved object noun phrase match trees */ setObjectMatches(dobj) { /* * if it's a ResolveInfo, handle it as a resolved object; * otherwise handle it as a match tree to be resolved */ if (dobj.ofKind(ResolveInfo)) setResolvedDobj(dobj); else dobjMatch = dobj; } /* check that the resolved objects are in scope */ resolvedObjectsInScope() { /* check the direct object */ return getDobjResolver(gIssuingActor, gActor, true) .objInScope(dobjList_[1].obj_); } /* * Get a message parameter object for the action. We define 'dobj' * as the direct object, in addition to any inherited targets. */ getMessageParam(objName) { switch(objName) { case 'dobj': /* return the current direct object */ return dobjCur_; default: /* inherit default handling */ return inherited(objName); } } /* * Execute the action. We'll run through the execution sequence * once for each resolved direct object. */ doActionMain() { /* * Set the direct object list as the antecedent, using the * language-specific pronoun setter. Don't set pronouns for a * nested command, because the player didn't necessarily refer to * the objects in a nested command. */ if (parentAction == nil) gActor.setPronoun(dobjList_); /* we haven't yet canceled the iteration */ iterationCanceled = nil; /* pre-calculate the multi-object announcement text for each dobj */ cacheMultiObjectAnnouncements(dobjList_, DirectObject); /* run through the sequence once for each direct object */ for (local i = 1, local len = dobjList_.length() ; i <= len && !iterationCanceled ; ++i) { /* make this object our current direct object */ dobjCur_ = dobjList_[i].obj_; dobjInfoCur_ = dobjList_[i]; /* announce the object if appropriate */ announceActionObject(dobjList_[i], len, whichMessageObject); /* run the execution sequence for the current direct object */ doActionOnce(); /* if we're top-level, count the iteration in the transcript */ if (parentAction == nil) gTranscript.newIter(); } } /* get the precondition descriptor list for the action */ getPreCondDescList() { /* * return the inherited preconditions plus the conditions that * apply to the direct object */ return inherited() + getObjPreCondDescList(dobjCur_, preCondDobjProp, dobjCur_, DirectObject); } /* * Get the list of active objects. We have only a direct object, so * we'll return a list with the current direct object. */ getCurrentObjects() { return [dobjCur_]; } /* set the current objects */ setCurrentObjects(lst) { dobjCur_ = lst[1]; dobjInfoCur_ = nil; } /* * Verify the action. */ verifyAction() { local result; /* invoke any general (non-object) pre-condition verifiers */ result = callVerifyPreCond(nil); /* * invoke the verifier routine ("verXxx") on the current direct * object and return the result */ result = callVerifyProp(dobjCur_, verDobjProp, preCondDobjProp, remapDobjProp, result, DirectObject); /* verify that the action routine ("doXxx") exists on the object */ return verifyHandlersExist( [dobjCur_], [verDobjProp, actionDobjProp, checkDobjProp], result); } /* initialize tentative resolutions for other noun phrases */ initTentative(issuingActor, targetActor, whichObj) { /* * we have only one noun phrase, so there's nothing else to * tentatively resolve */ } /* * Check for remapping */ checkRemapping() { local remapInfo; /* check for remapping in the direct object */ if ((remapInfo = dobjCur_.(remapDobjProp)) != nil) { /* * we have a remapping, so apply it - note that this won't * return, since this will completely replace the command * and thus terminate the old command with 'exit' */ remapAction(nil, DirectObject, remapInfo); } } /* * Check the command. * * For a single-object transitive action, this runs the catch-all * 'check' properties (the dobjFor(Default) and dobjFor(All) 'check' * methods) on the direct object, then calls the individual 'check' * routine for this specific action. */ checkAction() { try { /* call the catch-all 'check' properties */ if (!callCatchAllProp(dobjCur_, checkDobjProp, &checkDobjDefault, &checkDobjAll)) { /* * the action-specific check routine overrides any * Default catch-all, so call the direct object's check * routine (its "checkXxx") method */ dobjCur_.(checkDobjProp)(); } } catch (ExitSignal es) { /* mark the action as a failure in the transcript */ gTranscript.noteFailure(); /* re-throw the signal */ throw es; } } /* * Execute the command. */ execAction() { /* call the catch-all 'action' properties */ if (!callCatchAllProp(dobjCur_, actionDobjProp, &actionDobjDefault, &actionDobjAll)) { /* * the verb-specific 'action' handler overrides any Default * action handler, so call the verb-specific 'action' * routine in the direct object (the "doXxx" method) */ dobjCur_.(actionDobjProp)(); } } /* * The direct object preconditions, verifier, remapper, check, and * action methods for this action. Each concrete action must define * these appropriately. By convention, the methods are named like * so: * *. preconditions: preCondDobjAction *. verifier: verDobjAction *. remap: remapDobjAction *. check: checkDobjAction *. action: actionDobjAction * * where the 'Action' suffix is replaced by the name of the action. * The DefineTAction macro applies this convention, so in most cases * library and game authors will never have to create all of those * property names manually. */ verDobjProp = nil preCondDobjProp = nil remapDobjProp = nil checkDobjProp = nil actionDobjProp = nil /* * Get my direct object resolver. If I don't already have one, * create one and cache it; if I've already cached one, return it. * Note that we cache the resolver because it can sometimes take a * bit of work to set one up (the scope list can in some cases be * complicated to calculate). We use the resolver only during the * object resolution phase; since game state can't change during * this phase, it's safe to keep a cached copy. */ getDobjResolver(issuingActor, targetActor, reset) { /* create a new resolver if we don't already have one cached */ if (dobjResolver_ == nil) dobjResolver_ = createDobjResolver(issuingActor, targetActor); /* reset the resolver if desired */ if (reset) dobjResolver_.resetResolver(); /* return it */ return dobjResolver_; } /* * Create a resolver for the direct object. By default, we are our * own resolver. Some actions might want to override this to create * and return a specialized resolver instance if special resolution * rules are needed. */ createDobjResolver(issuingActor, targetActor) { /* initialize myself as a resolver */ initResolver(issuingActor, targetActor); /* return myself */ return self; } /* * Does this action allow "all" to be used in noun phrases? By * default, we allow it or not according to a gameMain property. * * Note that the inventory management verbs (TAKE, TAKE FROM, DROP, * PUT IN, PUT ON) override this to allow "all" to be used, so * disallowing "all" here (or via gameMain) won't disable "all" for * those verbs. */ actionAllowsAll = (gameMain.allVerbsAllowAll) /* * Resolve 'all' for the direct object, given a list of everything * in scope. By default, we'll simply return everything in scope; * some actions might want to override this to return a more * specific list of objects suitable for 'all'. */ getAllDobj(actor, scopeList) { return scopeList; } /* filter an ambiguous direct object noun phrase */ filterAmbiguousDobj(lst, requiredNum, np) { /* filter using the direct object verifier method */ return filterAmbiguousWithVerify(lst, requiredNum, verDobjProp, preCondDobjProp, remapDobjProp, DirectObject, np); } /* filter a plural phrase */ filterPluralDobj(lst, np) { /* filter using the direct object verifier method */ return filterPluralWithVerify(lst, verDobjProp, preCondDobjProp, remapDobjProp, DirectObject, np); } /* get the default direct object */ getDefaultDobj(np, resolver) { /* get a default direct object using the verify method */ return getDefaultWithVerify(resolver, verDobjProp, preCondDobjProp, remapDobjProp, DirectObject, np); } /* get the current direct object of the command */ getDobj() { return dobjCur_; } /* get the full ResolveInfo associated with the current direct object */ getDobjInfo() { return dobjInfoCur_; } /* get the object resolution flags for the direct object */ getDobjFlags() { return dobjInfoCur_ != nil ? dobjInfoCur_.flags_ : 0; } /* get the number of direct objects */ getDobjCount() { return dobjList_ != nil ? dobjList_.length() : 0; } /* get the original token list of the current direct object phrase */ getDobjTokens() { return dobjInfoCur_ != nil && dobjInfoCur_.np_ != nil ? dobjInfoCur_.np_.getOrigTokenList() : nil; } /* get the original words (as a list of strings) of the current dobj */ getDobjWords() { local l = getDobjTokens(); return l != nil ? l.mapAll({x: getTokOrig(x)}) : nil; } /* the predicate must assign the direct object production tree here */ dobjMatch = nil /* my resolved list of direct objects */ dobjList_ = [] /* * The resolved direct object on which we're currently executing the * command. To execute the command, we iterate through the direct * object list, calling the execution sequence for each object in * the list. We set this to the current object in each iteration. */ dobjCur_ = nil /* the full ResolveInfo associated with dobjCur_ */ dobjInfoCur_ = nil /* my cached direct object resolver */ dobjResolver_ = nil /* -------------------------------------------------------------------- */ /* * Resolver interface implementation - for the moment, we don't need * any special definitions here, since the basic Resolver * implementation (which we inherit) is suitable for a single-object * action. */ /* -------------------------------------------------------------------- */ /* * private Resolver implementation details */ /* * Initialize me as a resolver. */ initResolver(issuingActor, targetActor) { /* remember the actors */ issuer_ = issuingActor; actor_ = targetActor; /* I'm the action as well as the resolver */ action_ = self; /* cache the actor's default scope list */ cacheScopeList(); } /* issuing actor */ issuer_ = nil /* target actor */ actor_ = nil /* * By default, our direct object plays the direct object role in * generated messages. Subclasses can override this if the resolved * object is to play a different role. Note that this only affects * generated messages; for parsing purposes, our object is always in * the DirectObject role. */ whichMessageObject = DirectObject ; /* ------------------------------------------------------------------------ */ /* * "Tentative" noun resolver results gather. This type of results * gatherer is used to perform a tentative pre-resolution of an object * of a multi-object action. * * Consider what happens when we resolve a two-object action, such as * "put <dobj> in <iobj>". Since we have two objects, we obviously must * resolve one object or the other first; but this means that we must * resolve one object with no knowledge of the resolution of the other * object. This often makes it very difficult to resolve that first * object intelligently, because we'd really like to know something * about the other object. For example, if we first resolve the iobj of * "put <dobj> in <iobj>", it would be nice to know which dobj we're * talking about, since we could reduce the likelihood that the iobj is * the dobj's present container. * * Tentative resolution addresses this need by giving us some * information about a later-resolved object while resolving an * earlier-resolved object, even though we obviously can't have fully * resolved the later-resolved object. In tentative resolution, we * perform the resolution of the later-resolved object, completely in * the dark about the earlier-resolved object(s), and come up with as * much information as we can. The important thing about this stage of * resolution is that we don't ask any interactive questions and we * don't count anything for ranking purposes - we simply do the best we * can and note the results, leaving any ranking or interaction for the * true resolution phase that we'll perform later. */ class TentativeResolveResults: ResolveResults construct(target, issuer) { setActors(target, issuer); } /* * ignore most resolution problems, since this is only a tentative * resolution pass */ noMatch(action, txt) { } noMatchPoss(action, txt) { } noVocabMatch(action, txt) { } noMatchForAll() { } noteEmptyBut() { } noMatchForAllBut() { } noMatchForListBut() { } noMatchForPronoun(typ, txt) { } reflexiveNotAllowed(typ, txt) { } wrongReflexive(typ, txt) { } noMatchForPossessive(owner, txt) { } noMatchForLocation(loc, txt) { } noteBadPrep() { } nothingInLocation(loc) { } unknownNounPhrase(match, resolver) { return []; } noteLiteral(txt) { } emptyNounPhrase(resolver) { return []; } zeroQuantity(txt) { } insufficientQuantity(txt, matchList, requiredNum) { } uniqueObjectRequired(txt, matchList) { } noteAdjEnding() { } noteIndefinite() { } noteMiscWordList(txt) { } notePronoun() { } noteMatches(matchList) { } incCommandCount() { } noteActorSpecified() { } noteNounSlots(cnt) { } noteWeakPhrasing(level) { } allowActionRemapping = nil /* * during the tentative phase, keep all equivalents - we don't want * to make any arbitrary choices among equivalents during this * phase, because doing so could improperly force a choice among * otherwise ambiguous resolutions to the other phrase */ allowEquivalentFiltering = nil /* * for ambiguous results, don't attempt to narrow things down - just * keep the entire list */ ambiguousNounPhrase(keeper, asker, txt, matchList, fullMatchList, scopeList, requiredNum, resolver) { return matchList; } /* * no interaction is allowed, so return nothing if we need to ask * for a missing object */ askMissingObject(asker, resolver, responseProd) { /* note that we have a missing noun phrase */ npMissing = true; /* return nothing */ return nil; } /* * no interaction is allowed, so return no tokens if we need to ask * for a literal */ askMissingLiteral(action, which) { return []; } /* no interaction is allowed during tentative resolution */ canResolveInteractively() { return nil; } /* * flag: the noun phrase we're resolving is a missing noun phrase, * which means that we'll ask for it to be filled in when we get * around to resolving it for real */ npMissing = nil ; /* * A dummy object that we use for the *tentative* resolution of a noun * phrase when the noun phrase doesn't match anything. This lets us * distinguish cases where we have a noun phrase that has an error from a * noun phrase that's simply missing. */ dummyTentativeObject: object ; dummyTentativeInfo: ResolveInfo obj_ = dummyTentativeObject flags_ = 0 ; /* ------------------------------------------------------------------------ */ /* * Transitive-with-indirect Action class - this is an action that takes * both a direct and indirect object. We subclass the basic one-object * action to add the indirect object handling. */ class TIAction: TAction resetAction() { /* inherit defaulting handling */ inherited(); /* discard our cached iobj resolver */ iobjResolver_ = nil; } /* * Retry a single-object action as a two-object action. We'll treat * the original action's direct object list as our direct object * list, and obtain an indirect object using the normal means (first * looking for a default, then prompting the player if we can't find * a suitable default). 'orig' is the original single-object action. * * This routine terminates with 'exit' if it doesn't throw some * other error. */ retryWithMissingIobj(orig, asker) { /* resolve and execute the replacement action */ resolveAndReplaceAction(createForMissingIobj(orig, asker)); } /* * Retry an action as a two-object action with an ambiguous indirect * object. We'll ask which of the given possible objects is * intended. */ retryWithAmbiguousIobj(orig, objs, asker, objPhrase) { /* create a missing-indirect-object replacement action */ local action = createForMissingIobj(orig, asker); /* reduce the object list to the objects in scope */ local resolver = action.getIobjResolver(gIssuingActor, gActor, true); objs = objs.subset({x: resolver.objInScope(x)}); /* plug in the ambiguous indirect object list */ action.iobjMatch = new PreResolvedAmbigProd(objs, asker, objPhrase); /* resolve and execute the replacement action */ resolveAndReplaceAction(action); } /* * Test to see if askForIobj() would find a default indirect object. * Returns true if there's a default, nil if not. If this returns * true, then askForIobj() will simply take the default and proceed; * otherwise, it will have to actually ask the user for the missing * information. */ testRetryDefaultIobj(orig) { local action; local def; /* create the new action for checking for an indirect object */ action = createForMissingIobj(orig, ResolveAsker); /* get the default iobj */ def = action.getDefaultIobj( action.iobjMatch, action.getIobjResolver(gIssuingActor, gActor, nil)); /* if there's exactly one result, then we have a default */ return (def != nil && def.length() == 1); } /* * Create an instance of this action for retrying a given * single-object action with a missing indirect object. */ createForMissingIobj(orig, asker) { /* create the new action based on the original action */ local action = createForRetry(orig); /* use an empty noun phrase for the new action's indirect object */ action.iobjMatch = new EmptyNounPhraseProd(); /* set our custom response production and ResolveAsker */ action.iobjMatch.setPrompt(action.askIobjResponseProd, asker); /* copy what we've resolved so far */ action.initForMissingIobj(orig); /* return the action */ return action; } /* * Initialize the action for retrying with a missing direct object. * * If we're trying a TIAction, we can only be coming from a TAction * (since that's the only kind of original action that can turn into * a two-object, at least in the base library). That means the * original action already has a direct object. Now, since we're * asking for a MISSING direct object, the only possibility is that * the original action's direct object is our INDIRECT object. For * example: SWEEP WITH BROOM is turning into SWEEP <what> WITH * BROOM. */ initForMissingDobj(orig) { local origDobj = orig.getDobj(); /* * Set the indirect object in the new action to the direct object * from the original action. If there's no individual direct * object yet, we must be retrying the overall command, before we * started iterating through the individual dobjs, so copy the * entire dobj list from the original. */ iobjMatch = new PreResolvedProd(origDobj != nil ? origDobj : orig.dobjList_ ); } /* * Initialize the action for retrying with a missing indirect object. * * We can only be coming from a TAction, so the TAction will have a * direct object already. Simply copy that as our own direct * object. For example: UNLOCK DOOR is turning into UNLOCK DOOR * WITH <what>. */ initForMissingIobj(orig) { local origDobj = orig.getDobj(); /* * Copy the direct object from the original. If there's no * individual direct object yet, we must be retrying the overall * command, before we started iterating through the individual * dobjs, so copy the entire dobj list. */ dobjMatch = new PreResolvedProd(origDobj != nil ? origDobj : orig.dobjList_); } /* * The root production to use to parse missing indirect object * responses. By default, this is singleNoun, but individual * actions can override this as appropriate. * * Note that language modules might want to override this to allow * for special responses. For example, in English, most verbs will * want to override this with a specialized production that allows * the appropriate preposition in the response. */ askIobjResponseProd = singleNoun /* get the missing object response production for a given role */ getObjResponseProd(resolver) { /* if it's the indirect object, return the iobj response prod */ if (resolver.whichObject == IndirectObject) return askIobjResponseProd; /* otherwise use the default handling */ return inherited(resolver); } /* * Resolution order - returns DirectObject or IndirectObject to * indicate which noun phrase to resolve first in resolveNouns(). * By default, we'll resolve the indirect object first, but * individual actions can override this to resolve in a non-default * order. */ resolveFirst = IndirectObject /* * Empty phrase resolution order. This is similar to the standard * resolution order (resolveFirst), but is used only when both the * direct and indirect objects are empty phrases. * * When both phrases are empty, we will either use a default or * prompt interactively for the missing phrase. In most cases, it * is desirable to prompt interactively for a missing direct object * first, regardless of the usual resolution order. */ resolveFirstEmpty = DirectObject /* * Determine which object to call first for action processing. By * default, we execute in the same order as we resolve, but this can * be overridden if necessary. */ execFirst = (resolveFirst) /* * Can the indirect object potentially resolve to the given * simulation object? This only determines if the object is a * *syntactic* match, meaning that it can match at a vocabulary and * grammar level. This doesn't test it for logicalness or check that * it's an otherwise valid resolution. */ canIobjResolveTo(obj) { /* check our iobj match tree to see if it can resolve to 'obj' */ return iobjMatch.canResolveTo( obj, self, issuer_, actor_, IndirectObject); } /* * resolve our noun phrases to objects */ resolveNouns(issuingActor, targetActor, results) { local first; local objMatch1, objMatch2; local objList1, objList2; local getResolver1, getResolver2; local objCur1; local remapProp; local reResolveFirst; /* * note in the results that we have two noun slots (the direct * and indirect objects) */ results.noteNounSlots(2); /* we have no current known direct or indirect object yet */ dobjCur_ = nil; iobjCur_ = nil; /* * presume we won't have to re-resolve the first noun phrase, * and clear out our record of anaphor bindings */ reResolveFirst = nil; needAnaphoricBinding_ = nil; lastObjList_ = nil; /* * Determine which object we want to resolve first. If both * phrases are empty, use the special all-empty ordering; * otherwise, use the standard ordering for this verb. */ if (dobjMatch.isEmptyPhrase && iobjMatch.isEmptyPhrase) { /* both phrases are empty - use the all-empty ordering */ first = resolveFirstEmpty; } else { /* * we have at least one non-empty phrase, so use our * standard ordering */ first = resolveFirst; } /* * The resolution process is symmetrical for the two possible * resolution orders (direct object first or indirect object * first); all we need to do is to set up the parameters we'll * need according to which order we're using. * * This parameterized approach makes the code further below a * little mind-boggling, because it's using so much indirection. * But the alternative is worse: the alternative is to * essentially make two copies of the code below (one for the * dobj-first case and one for the iobj-first case), which is * prone to maintenance problems because of the need to keep the * two copies synchronized when making any future changes. */ if (first == DirectObject) { objMatch1 = dobjMatch; objMatch2 = iobjMatch; objList1 = &dobjList_; objList2 = &iobjList_; getResolver1 = &getDobjResolver; getResolver2 = &getIobjResolver; objCur1 = &dobjCur_; remapProp = remapDobjProp; } else { objMatch1 = iobjMatch; objMatch2 = dobjMatch; objList1 = &iobjList_; objList2 = &dobjList_; getResolver1 = &getIobjResolver; getResolver2 = &getDobjResolver; objCur1 = &iobjCur_; remapProp = remapIobjProp; } /* * Get the unfiltered second-resolved object list - this will * give the first-resolved object resolver access to some * minimal information about the possible second-resolved object * or objects. * * Note that we're not *really* resolving the second object here * - it is the second-resolved object, after all. What we're * doing is figuring out the *potential* set of objects that * could resolve to the second phrase, with minimal * disambiguation, so that the first object resolver is not * totally in the dark about the second object's potential * resolution. */ initTentative(issuingActor, targetActor, first); /* resolve the first-resolved noun phrase */ self.(objList1) = objMatch1.resolveNouns( self.(getResolver1)(issuingActor, targetActor, true), results); /* * If the first-resolved noun phrase asked for an anaphoric * binding, we'll need to go back and re-resolve that noun * phrase after we resolve our other noun phrase, since the * first-resolved phrase refers to the second-resolved phrase. */ reResolveFirst = needAnaphoricBinding_; /* * if the second-resolved phrase uses an anaphoric pronoun, the * anaphor can be taken as referring to the first-resolved * phrase's results */ lastObjList_ = self.(objList1); /* * if the first-resolved phrase resolves to just one object, we * can immediately set the current resolved object, so that we * can use it while resolving the second-resolved object list */ if (self.(objList1).length() == 1) { /* set the current first-resolved object */ self.(objCur1) = self.(objList1)[1].obj_; /* if remapping is allowed at this point, look for a remapping */ if (results.allowActionRemapping) { withParserGlobals(issuingActor, targetActor, self, function() { local remapInfo; /* check for a remapping */ if ((remapInfo = self.(objCur1).(remapProp)) != nil) { /* * we have a remapping, so apply it - note that * we're still in the process of resolving noun * phrases (since we've only resolved one of our * two noun phrases so far), so pass 'true' for * the inResolve parameter */ remapAction(true, first, remapInfo); } }); } } /* resolve the second-resolved object */ self.(objList2) = objMatch2.resolveNouns( self.(getResolver2)(issuingActor, targetActor, true), results); /* * if we have to re-resolve the first noun phrase due to an * anaphoric pronoun, go back and do that now */ if (reResolveFirst) { /* * use the second-resolved noun phrase as the referent of * the anaphoric pronoun(s) in the first-resolved phrase */ lastObjList_ = self.(objList2); /* re-resolve the first object list */ self.(objList1) = objMatch1.resolveNouns( self.(getResolver1)(issuingActor, targetActor, true), results); } } /* we have a direct and indirect object */ predicateNounPhrases = [&dobjMatch, &iobjMatch] /* get an object role */ getRoleFromIndex(idx) { /* * the second object is always our indirect object; for other * roles, defer to the inherited behavior */ return (idx == 2 ? IndirectObject : inherited(idx)); } /* get the resolved object in a given role */ getObjectForRole(role) { /* return the indirect object if requested; otherwise inherit */ return (role == IndirectObject ? getIobj() : inherited(role)); } /* get the ResolveInfo for the given resolved object */ getResolveInfo(obj, oldRole) { local info; /* scan our resolved direct object list for the given object */ if (dobjList_ != nil) info = dobjList_.valWhich({x: x.obj_ == obj}); /* if we didn't find it there, try the indirect object list */ if (info == nil && iobjList_ != nil) info = iobjList_.valWhich({x: x.obj_ == obj}); /* if we didn't find one, create one from scratch */ if (info == nil) { /* get the list for the old role */ local lst = (oldRole == DirectObject ? dobjList_ : iobjList_); /* * if there's anything in the old role, copy its flags and * noun phrase to the new role */ if (lst.length() > 0) { /* copy the old role's attributes */ info = new ResolveInfo(obj, lst[1].flags_, lst[1].np_); } else { /* nothing in the old role - start from scratch */ info = new ResolveInfo(obj, 0, nil); } } /* return what we found (or created) */ return info; } /* get the OtherObject role for the given role */ getOtherObjectRole(role) { /* the complementary roles are DirectObject and IndirectObject */ return (role == IndirectObject ? DirectObject : IndirectObject); } /* get the match tree for the noun phrase in the given role */ getMatchForRole(role) { /* return the indirect object match tree if requested; else inherit */ return (role == IndirectObject ? iobjMatch : inherited(role)); } /* get the 'verify' property for a given object role */ getVerifyPropForRole(role) { return (role == IndirectObject ? verIobjProp : inherited(role)); } /* get the 'preCond' property for a given object role */ getPreCondPropForRole(role) { return (role == IndirectObject ? preCondIobjProp : inherited(role)); } /* get the 'remap' property for a given object role */ getRemapPropForRole(role) { return (role == IndirectObject ? remapIobjProp : inherited(role)); } /* get the list of resolved objects in the given role */ getResolvedObjList(which) { return (which == IndirectObject ? getResolvedIobjList() : inherited(which)); } /* get the list of resolved indirect objects */ getResolvedIobjList() { /* * if we have an indirect object list, return the objects from * it; otherwise, return an empty list */ return (iobjList_ == nil ? nil : iobjList_.mapAll({x: x.obj_})); } /* * Manually set the resolved objects. We'll set our direct and * indirect objects. */ setResolvedObjects(dobj, iobj) { /* inherit the base class handling to set the direct object */ inherited(dobj); /* set the resolved iobj */ setResolvedIobj(iobj); } /* set a resolved iobj */ setResolvedIobj(iobj) { /* build a pre-resolved production for the indirect object */ iobjMatch = new PreResolvedProd(iobj); /* set the resolved indirect object */ iobjList_ = makeResolveInfoList(iobj); /* set the current indirect object as well */ iobjCur_ = (iobjList_.length() > 0 ? iobjList_[1].obj_ : nil); iobjInfoCur_ = (iobjList_.length() > 0 ? iobjList_[1] : nil); } /* manually set the unresolved object noun phrase match trees */ setObjectMatches(dobj, iobj) { /* inherit default handling */ inherited(dobj); /* * if the iobj is a ResolveInfo, set it as a resolved object; * otherwise set it as an unresolved match tree */ if (iobj.ofKind(ResolveInfo)) setResolvedIobj(iobj); else iobjMatch = iobj; } /* * Get the anaphoric binding for the noun phrase we're currently * resolving. */ getAnaphoricBinding(typ) { /* if we've resolved a prior noun phrase, return it */ if (lastObjList_ != nil) return lastObjList_; /* * we don't have a prior noun phrase - make a note that we have * to come back and re-resolve the current noun phrase */ needAnaphoricBinding_ = true; /* * return an empty list to indicate that the anaphor is * acceptable in this context but has no binding yet */ return []; } /* * The last object list we resolved. We keep track of this so that * we can provide it as the anaphoric binding, if an anaphor binding * is requested. */ lastObjList_ = nil /* * Flag: we have been asked for an anaphoric binding, but we don't * have a binding available. We'll check this after resolving the * first-resolved noun phrase so that we can go back and re-resolve * it again after resolving the other noun phrase. */ needAnaphoricBinding_ = nil /* check that the resolved objects are in scope */ resolvedObjectsInScope() { /* * check the indirect object, and inherit the base class handling * to check the direct object */ return (inherited() && (getIobjResolver(gIssuingActor, gActor, true) .objInScope(iobjList_[1].obj_))); } /* * get our indirect object resolver, or create one if we haven't * already cached one */ getIobjResolver(issuingActor, targetActor, reset) { /* if we don't already have one cached, create a new one */ if (iobjResolver_ == nil) iobjResolver_ = createIobjResolver(issuingActor, targetActor); /* reset the resolver if desired */ if (reset) iobjResolver_.resetResolver(); /* return the cached resolver */ return iobjResolver_; } /* * Create our indirect object resolver. By default, we'll use a * basic indirect object resolver. */ createIobjResolver(issuingActor, targetActor) { /* create and return a new basic indirect object resolver */ return new IobjResolver(self, issuingActor, targetActor); } /* * Resolve 'all' for the indirect object. By default, we'll return * everything in the scope list. */ getAllIobj(actor, scopeList) { return scopeList; } /* filter an ambiguous indirect object noun phrase */ filterAmbiguousIobj(lst, requiredNum, np) { /* filter using the indirect object verifier method */ return filterAmbiguousWithVerify(lst, requiredNum, verIobjProp, preCondIobjProp, remapIobjProp, IndirectObject, np); } /* filter a plural phrase */ filterPluralIobj(lst, np) { /* filter using the direct object verifier method */ return filterPluralWithVerify(lst, verIobjProp, preCondIobjProp, remapIobjProp, IndirectObject, np); } /* get the default indirect object */ getDefaultIobj(np, resolver) { /* get a default indirect object using the verify method */ return getDefaultWithVerify(resolver, verIobjProp, preCondIobjProp, remapIobjProp, IndirectObject, np); } /* * Execute the action. We'll run through the execution sequence * once for each resolved object in our direct or indirect object * list, depending on which one is the list and which one is the * singleton. */ doActionMain() { local lst; local preAnnouncedDobj; local preAnnouncedIobj; /* * Get the list of resolved objects for the multiple object. If * neither has multiple objects, it doesn't matter which is * iterated, since we'll just do the command once anyway. */ lst = (iobjList_.length() > 1 ? iobjList_ : dobjList_); /* * Set the pronoun antecedents, using the game-specific pronoun * setter. Don't set an antecedent for a nested command. */ if (parentAction == nil) { /* * Set both direct and indirect objects as potential * antecedents. Rather than trying to figure out right now * which one we might want to refer to in the future, remember * both - we'll decide which one is the logical antecedent * when we find a pronoun to resolve in a future command. */ gActor.setPronounMulti(dobjList_, iobjList_); /* * If one or the other object phrase was specified in the * input as a pronoun, keep the meaning of that pronoun the * same, overriding whatever we just did. Note that the * order we use here doesn't matter: if a given pronoun * appears in only one of the two lists, then the list where * it's not set has no effect on the pronoun, hence it * doesn't matter which comes first; if a pronoun appears in * both lists, it will have the same value in both lists, so * we'll just do the same thing twice, so, again, order * doesn't matter. */ setPronounByInput(dobjList_); setPronounByInput(iobjList_); } /* * pre-announce the non-list object if appropriate - this will * provide a common pre-announcement if we iterate through * several announcements of the main list objects */ if (lst == dobjList_) { /* pre-announce the single indirect object if needed */ preAnnouncedIobj = preAnnounceActionObject( iobjList_[1], dobjList_, IndirectObject); /* we haven't announced the direct object yet */ preAnnouncedDobj = nil; /* pre-calculate the multi-object announcements */ cacheMultiObjectAnnouncements(dobjList_, DirectObject); } else { /* pre-announce the single direct object if needed */ preAnnouncedDobj = preAnnounceActionObject( dobjList_[1], iobjList_, DirectObject); /* we haven't announced the indirect object yet */ preAnnouncedIobj = nil; /* pre-calculate the multi-object announcements */ cacheMultiObjectAnnouncements(iobjList_, IndirectObject); } /* we haven't yet canceled the iteration */ iterationCanceled = nil; /* iterate over the resolved list for the multiple object */ for (local i = 1, local len = lst.length() ; i <= len && !iterationCanceled ; ++i) { local dobjInfo; local iobjInfo; /* * make the current list item the direct or indirect object, * as appropriate */ if (lst == dobjList_) { /* the direct object is the multiple object */ dobjInfo = dobjInfoCur_ = lst[i]; iobjInfo = iobjInfoCur_ = iobjList_[1]; } else { /* the indirect object is the multiple object */ dobjInfo = dobjInfoCur_ = dobjList_[1]; iobjInfo = iobjInfoCur_ = lst[i]; } /* get the current dobj and iobj from the resolve info */ dobjCur_ = dobjInfo.obj_; iobjCur_ = iobjInfo.obj_; /* * if the action was remapped, and we need to announce * anything, announce the entire action */ if (isRemapped()) { /* * We were remapped. The entire phrasing of the new * action might have changed from what the player typed, * so it might be nonsensical to show the objects as we * usually would, as sentence fragments that are meant * to combine with what the player actually typed. So, * instead of showing the usual sentence fragments, show * the entire phrasing of the command. * * Only show the announcement if we have a reason to: we * have unclear disambiguation in one of the objects, or * one of the objects is defaulted. * * If we don't want to announce the remapped action, * still consider showing a multi-object announcement, * if we would normally need to do so. */ if (needRemappedAnnouncement(dobjInfo) || needRemappedAnnouncement(iobjInfo)) { /* show the remapped announcement */ gTranscript.announceRemappedAction(); } else { /* announce the multiple dobj if necessary */ if (!preAnnouncedDobj) maybeAnnounceMultiObject( dobjInfo, dobjList_.length(), DirectObject); /* announce the multiple iobj if necessary */ if (!preAnnouncedIobj) maybeAnnounceMultiObject( iobjInfo, iobjList_.length(), IndirectObject); } } else { /* announce the direct object if appropriate */ if (!preAnnouncedDobj) announceActionObject(dobjInfo, dobjList_.length(), DirectObject); /* announce the indirect object if appropriate */ if (!preAnnouncedIobj) announceActionObject(iobjInfo, iobjList_.length(), IndirectObject); } /* run the execution sequence for the current direct object */ doActionOnce(); /* if we're top-level, count the iteration in the transcript */ if (parentAction == nil) gTranscript.newIter(); } } /* * Set the pronoun according to the pronoun type actually used in * the input. For example, if we said PUT BOX ON IT, we want IT to * continue referring to whatever IT referred to before this command * - we specifically do NOT want IT to refer to the BOX in this * case. */ setPronounByInput(lst) { local objs; /* get the subset of the list that was specified by pronoun */ lst = lst.subset({x: x.pronounType_ != nil}); /* * Get a list of the unique objects in the list. This will * ensure that we can distinguish THEM from IT AND IT AND IT: if * we have the same pronoun appearing with multiple objects, * we'll know that it's because the pronoun was actually plural * (THEM) rather than a singular pronoun that was repeated (IT * AND IT AND IT). */ objs = lst.mapAll({x: x.obj_}).getUnique(); /* * Now retain one 'lst' element for each 'objs' element. This * is a bit tricky: we're mapping each element in 'objs' to pick * out one element of 'lst' where the 'lst' element points to * the 'objs' element. */ lst = objs.mapAll({o: lst.valWhich({l: l.obj_ == o})}); /* * Now we can set the pronouns. Go through the list, and set * each different pronoun type that appears. Set it to the * subset of the list with that matches that pronoun type, and * then remove that subset and keep iterating to pick up the * remaining pronoun types. */ while (lst.length() != 0) { local cur; local typ; /* on this iteration, handle the first pronoun type in the list */ typ = lst[1].pronounType_; /* pick out the subset with the current pronoun type */ cur = lst.subset({x: x.pronounType_ == typ}); /* set the current pronoun to the current list */ gActor.setPronounByType(typ, cur); /* remove the subset we just handled from the remaining list */ lst -= cur; } } /* * Determine if we need to announce this action when the action was * remapped, based on the resolution information for one of our * objects. We need to announce a remapped action when either * object had unclear disambiguation or was defaulted. */ needRemappedAnnouncement(info) { /* * if it's a defaulted object that hasn't been announced, or it * was unclearly disambiguated, we need an announcement */ return (((info.flags_ & DefaultObject) != 0 && (info.flags_ & AnnouncedDefaultObject) == 0) || (info.flags_ & UnclearDisambig) != 0); } /* * Verify the action. */ verifyAction() { local result; /* invoke any general (non-object) pre-condition verifiers */ result = callVerifyPreCond(nil); /* check the direct object */ result = callVerifyProp(dobjCur_, verDobjProp, preCondDobjProp, remapDobjProp, result, DirectObject); /* * Check the indirect object, combining the results with the * direct object results. We combine the results for the two * objects because we're simply looking for any reason that we * can't perform the command. */ result = callVerifyProp(iobjCur_, verIobjProp, preCondIobjProp, remapIobjProp, result, IndirectObject); /* * check that the action method ("doXxx") is defined on at least * one of the objects */ return verifyHandlersExist( [dobjCur_, iobjCur_], [verDobjProp, checkDobjProp, actionDobjProp, verIobjProp, checkIobjProp, actionIobjProp], result); } /* initialize tentative resolutions for other noun phrases */ initTentative(issuingActor, targetActor, whichObj) { local tRes; local ti, td; /* * remember the old tentative direct and indirect objects, then * set them to empty lists - this will ensure that we don't * recursively try to get a tentative resolution for the object * we're working on right now, which would cause infinite * recursion */ td = tentativeDobj_; ti = tentativeIobj_; /* set them to empty lists to indicate they don't need resolving */ tentativeDobj_ = []; tentativeIobj_ = []; /* make sure our built-in dobj resolver is initialized */ issuer_ = issuingActor; actor_ = targetActor; /* make sure we set things back when we're done */ try { /* initialize the other noun phrase */ if (whichObj == DirectObject && ti == nil) { /* tentatively resolve the indirect object */ tRes = new TentativeResolveResults(targetActor, issuingActor); ti = iobjMatch.resolveNouns( getIobjResolver(issuingActor, targetActor, true), tRes); /* * if the list is empty, and we didn't have a missing * noun phrase, use a dummy object as the tentative * resolution - this distinguishes erroneous noun phrases * from those that are simply missing */ if (ti == [] && !tRes.npMissing) ti = [dummyTentativeInfo]; } else if (whichObj == IndirectObject && td == nil) { /* tentatively resolve the direct object */ tRes = new TentativeResolveResults(targetActor, issuingActor); td = dobjMatch.resolveNouns( getDobjResolver(issuingActor, targetActor, true), tRes); /* use a dummy object if appropriate */ if (td == [] && !tRes.npMissing) td = [dummyTentativeInfo]; } } finally { /* set the original (or updated) tentative lists */ tentativeDobj_ = td; tentativeIobj_ = ti; } } /* * Check for remapping */ checkRemapping() { local remapInfo; local role; /* presume we'll find remapping for the first-resolved object */ role = resolveFirst; /* check remapping for each object, in the resolution order */ if (resolveFirst == DirectObject) { /* the direct object is resolved first, so try it first */ if ((remapInfo = dobjCur_.(remapDobjProp)) == nil) { remapInfo = iobjCur_.(remapIobjProp); role = IndirectObject; } } else { /* the indirect object is resolved first, so remap it first */ if ((remapInfo = iobjCur_.(remapIobjProp)) == nil) { remapInfo = dobjCur_.(remapDobjProp); role = DirectObject; } } /* if we found a remapping, apply it */ if (remapInfo != nil) remapAction(nil, role, remapInfo); } /* * Check the command. * * For a two-object action, this first calls the catch-all 'check' * methods (the dobjFor(Default) and dobjFor(All) methods) on the two * objects (indirect object first), then calls the 'check' methods * for this specific action (direct object first). */ checkAction() { try { /* invoke the catch-all 'check' methods on each object */ local defIo = callCatchAllProp( iobjCur_, checkIobjProp, &checkIobjDefault, &checkIobjAll); local defDo = callCatchAllProp( dobjCur_, checkDobjProp, &checkDobjDefault, &checkDobjAll); /* * invoke the 'check' method on each object, as long as it * overrides any corresponding Default 'check' handler */ if (!defDo) dobjCur_.(checkDobjProp)(); if (!defIo) iobjCur_.(checkIobjProp)(); } catch (ExitSignal es) { /* mark the action as a failure in the transcript */ gTranscript.noteFailure(); /* re-throw the signal */ throw es; } } /* * Execute the command. */ execAction() { local defIo, defDo; /* invoke the catch-all 'action' method on each object */ defIo = callCatchAllProp(iobjCur_, actionIobjProp, &actionIobjDefault, &actionIobjAll); defDo = callCatchAllProp(dobjCur_, actionDobjProp, &actionDobjDefault, &actionDobjAll); /* * Invoke the action method on each object, starting with the * non-list object. Call these only if the corresponding * default 'action' methods didn't override the verb-specific * methods. */ if (execFirst == DirectObject) { if (!defDo) dobjCur_.(actionDobjProp)(); if (!defIo) iobjCur_.(actionIobjProp)(); } else { if (!defIo) iobjCur_.(actionIobjProp)(); if (!defDo) dobjCur_.(actionDobjProp)(); } } /* get the precondition descriptor list for the action */ getPreCondDescList() { /* * return the inherited preconditions plus the conditions that * apply to the indirect object */ return inherited() + getObjPreCondDescList(iobjCur_, preCondIobjProp, iobjCur_, IndirectObject); } /* * Get a message parameter object for the action. We define 'dobj' * as the direct object and 'iobj' as the indirect object, in * addition to any inherited targets. */ getMessageParam(objName) { switch(objName) { case 'iobj': /* return the current indirect object */ return iobjCur_; default: /* inherit default handling */ return inherited(objName); } } /* get the current indirect object being executed */ getIobj() { return iobjCur_; } /* get the full ResolveInfo associated with the current indirect object */ getIobjInfo() { return iobjInfoCur_; } /* get the object resolution flags for the indirect object */ getIobjFlags() { return iobjInfoCur_ != nil ? iobjInfoCur_.flags_ : 0; } /* get the number of direct objects */ getIobjCount() { return iobjList_ != nil ? iobjList_.length() : 0; } /* get the original token list of the current indirect object phrase */ getIobjTokens() { return iobjInfoCur_ != nil && iobjInfoCur_.np_ != nil ? iobjInfoCur_.np_.getOrigTokenList() : nil; } /* get the original words (as a list of strings) of the current iobj */ getIobjWords() { local l = getIobjTokens(); return l != nil ? l.mapAll({x: getTokOrig(x)}) : nil; } /* * Get the list of active objects. We have a direct and indirect * object. */ getCurrentObjects() { return [dobjCur_, iobjCur_]; } /* set the current objects */ setCurrentObjects(lst) { /* remember the current direct and indirect objects */ dobjCur_ = lst[1]; iobjCur_ = lst[2]; /* we don't have ResolveInfo objects for this call */ dobjInfoCur_ = iobjInfoCur_ = nil; } /* * Copy one tentative object list to the other. This is useful when * an object's verifier for one TIAction wants to forward the call * to the other object verifier for a different TIAction. */ copyTentativeObjs() { /* copy whichever tentative list is populated to the other slot */ if (tentativeDobj_ != nil) tentativeDobj_ = tentativeIobj_; else tentativeIobj_ = tentativeDobj_; } /* * Get the tentative direct/indirect object resolution lists. A * tentative list is available for the later-resolved object while * resolving the earlier-resolved object. */ getTentativeDobj() { return tentativeDobj_; } getTentativeIobj() { return tentativeIobj_; } /* * the predicate grammar must assign the indirect object production * tree to iobjMatch */ iobjMatch = nil /* the indirect object list */ iobjList_ = [] /* current indirect object being executed */ iobjCur_ = nil /* the full ResolveInfo associated with iobjCur_ */ iobjInfoCur_ = nil /* my cached indirect object resolver */ iobjResolver_ = nil /* * The tentative direct and indirect object lists. A tentative list * is available for the later-resolved object while resolving the * earlier-resolved object. */ tentativeDobj_ = nil tentativeIobj_ = nil /* * Verification and action properties for the indirect object. By * convention, the verification method for the indirect object of a * two-object action is verIobjXxx; the check method is * checkIobjXxx; and the action method is actionIobjXxx. */ verIobjProp = nil preCondIobjProp = nil checkIobjProp = nil actionIobjProp = nil /* * Action-remap properties for the indirect object. By convention, * the remapper properties are named remapDobjAction and * remapIobjAction, for the direct and indirect objects, * respectively, where Action is replaced by the root name of the * action. */ remapIobjProp = nil ; /* ------------------------------------------------------------------------ */ /* * Common base class for actions involving literal phrases. This is a * mix-in class that can be combined with Action subclasses to create * specific kinds of literal actions. */ class LiteralActionBase: object /* * Get a message parameter. We define 'literal' as the text of the * literal phrase, in addition to inherited targets. */ getMessageParam(objName) { switch(objName) { case 'literal': /* return the text of the literal phrase */ return text_; default: /* inherit default handling */ return inherited(objName); } } /* manually set the resolved objects */ setResolvedObjects(txt) { /* remember the literal text */ text_ = txt; } /* manually set the pre-resolved match trees */ setObjectMatches(lit) { /* if it's not already a PreResolvedLiteralProd, wrap it */ if (!lit.ofKind(PreResolvedLiteralProd)) { /* save the literal text */ text_ = lit; /* wrap it in a match tree */ lit = new PreResolvedLiteralProd(lit); } /* note the new literal match tree */ literalMatch = lit; } /* get the current literal text */ getLiteral() { return text_; } /* the text of the literal phrase */ text_ = nil ; /* ------------------------------------------------------------------------ */ /* * An action with a literal phrase as its only object, such as "say <any * text>". We'll accept anything as the literal phrase - a number, a * quoted string, or arbitrary words - and treat them all simply as text. * * The grammar rules that produce these actions must set literalMatch to * the literal phrase's match tree. * * Because we don't have any actual resolved objects, we're based on * IAction. Subclasses that implement particular literal actions should * override execAction() to carry out the action; this method can call * the getLiteral() method of self to get a string giving the literal * text. */ class LiteralAction: LiteralActionBase, IAction /* * Resolve objects. We don't actually have any objects to resolve, * but we do have to get the text for the literal phrase. */ resolveNouns(issuingActor, targetActor, results) { /* the literal phrase counts as one noun slot */ results.noteNounSlots(1); /* * "Resolve" our literal phrase. The literal phrase doesn't * resolve to an object list the way a regular noun phrase would, * but rather just resolves to a text string giving the original * literal contents of the phrase. */ literalMatch.resolveLiteral(results); /* retrieve the text of the phrase, exactly as the player typed it */ text_ = literalMatch.getLiteralText(results, self, DirectObject); } /* we have a literal phrase as our only noun phrase */ predicateNounPhrases = [&literalMatch] ; /* ------------------------------------------------------------------------ */ /* * An action with a direct object and a literal, such as "turn dial to * <setting>" or "type <string> on keypad". We'll accept anything as the * literal phrase - a number, a quoted string, or arbitrary words - and * treat them all simply as text. * * The grammar rules that produce these actions must set dobjMatch to the * resolvable object of the command, and must set literalMatch to the * literal phrase's match tree. Note that we use dobjMatch as the * resolvable object even if the object serves grammatically as the * indirect object - this is a simplification, and the true grammatical * purpose of the object isn't important since there's only one true * object in the command. * * When referring to objects by role (such as in remapTo), callers should * ALWAYS refer to the resolvable object as DirectObject, and the literal * phrase as IndirectObject. * * Each subclass must set the property whichMessageLiteral to the * grammatical role (DirectObject, IndirectObject) the literal phrase * plays for message generation purposes. This only affects messages; it * doesn't affect anything else; in particular, regardless of the * whichMessageLiteral setting, callers should always refer to the * literal as IndirectObject when calling getObjectForRole() and the * like, and should always call getDobj() to get the resolved version of * the resolvable object phrase. */ class LiteralTAction: LiteralActionBase, TAction /* * Resolve objects. */ resolveNouns(issuingActor, targetActor, results) { /* * the literal phrase counts as one noun slot, and the direct * object as another */ results.noteNounSlots(2); /* * If the literal phrase serves as the direct object in generated * messages, ask for its literal text first, so that we ask for * it interactively first. Otherwise, get the tentative literal * text, in case it's useful to have in resolving the other * object. */ if (whichMessageLiteral == DirectObject) text_ = literalMatch.getLiteralText(results, self, DirectObject); else text_ = literalMatch.getTentativeLiteralText(); /* resolve the direct object */ dobjList_ = dobjMatch.resolveNouns( getDobjResolver(issuingActor, targetActor, true), results); /* * "Resolve" the literal, ignoring the result - we call this so * that the literal can do any scoring it wants to do during * resolution; but since we're treating it literally, it * obviously has no actual resolved value. */ literalMatch.resolveLiteral(results); /* the literal phrase resolves to the text only */ text_ = literalMatch.getLiteralText(results, self, whichMessageLiteral); } /* we have a direct object and a literal phrase */ predicateNounPhrases = [&dobjMatch, &literalMatch] /* get an object role */ getRoleFromIndex(idx) { /* * index 2 is the literal object, which is always in the indirect * object role; for others, inherit the default handling */ return (idx == 2 ? IndirectObject : inherited(idx)); } /* get the OtherObject role for the given role */ getOtherObjectRole(role) { /* the complementary roles are DirectObject and IndirectObject */ return (role == DirectObject ? IndirectObject : DirectObject); } /* get the resolved object in a given role */ getObjectForRole(role) { /* * the literal is in the IndirectObject role; inherit the default * for others */ return (role == IndirectObject ? text_ : inherited(role)); } /* get the match tree for the given role */ getMatchForRole(role) { /* * the literal is in the IndirectObject role; inherit the default * for anything else */ return (role == IndirectObject ? literalMatch : inherited(role)); } /* manually set the resolved objects */ setResolvedObjects(dobj, txt) { /* inherit default TAction handling for the direct object */ inherited TAction(dobj); /* inherit the LiteralActionBase handling for the literal text */ inherited LiteralActionBase(txt); } /* manually set the pre-resolved match trees */ setObjectMatches(dobj, lit) { /* inherit default TAction handling for the direct object */ inherited TAction(dobj); /* inherit the default LiteralActionBase handling for the literal */ inherited LiteralActionBase(lit); } /* * Get a list of the current objects. We include only the direct * object here, since the literal text is not a resolved object but * simply literal text. */ getCurrentObjects() { return [dobjCur_]; } /* set the current objects */ setCurrentObjects(lst) { dobjCur_ = lst[1]; dobjInfoCur_ = nil; } /* * Retry a single-object action as an action taking both an object * and a literal phrase. We'll treat the original action's direct * object list as our direct object list, and obtain a literal * phrase interactively. * * This routine terminates with 'exit' if it doesn't throw some * other error. */ retryWithMissingLiteral(orig) { local action; /* create the new action based on the original action */ action = createForRetry(orig); /* use an empty literal phrase for the new action */ action.literalMatch = new EmptyLiteralPhraseProd(); /* initialize for the missing literal phrase */ action.initForMissingLiteral(orig); /* resolve and execute the replacement action */ resolveAndReplaceAction(action); } /* initialize with a missing direct object phrase */ initForMissingDobj(orig) { /* * Since we have a missing direct objet, we must be coming from * a LiteralAction. The LiteralAction will already have a * literal phrase, so copy it. For example: WRITE HELLO -> * WRITE HELLO ON <what>. */ literalMatch = new PreResolvedLiteralProd(orig.getLiteral()); } /* initialize for a missing literal phrase */ initForMissingLiteral(orig) { local origDobj = orig.getDobj(); /* * Since we have a missing literal, we must be coming from a * TAction. The TAction will already have a direct object, * which we'll want to keep as our own direct object. For * example: WRITE ON SLATE -> WRITE <what> ON SLATE. */ dobjMatch = new PreResolvedProd(origDobj != nil ? origDobj : orig.dobjList_); } /* object role played by the literal phrase */ whichMessageLiteral = nil /* -------------------------------------------------------------------- */ /* * Direct Object Resolver implementation. We serve as our own * direct object resolver, so we define any special resolver * behavior here. */ /* * the true grammatical role of the resolved object is always the * direct object */ whichObject = DirectObject /* * What we call our direct object might actually be playing the * grammatical role of the indirect object - in order to inherit * easily from TAction, we call our resolved object our direct * object, regardless of which grammatical role it actually plays. * For the most part it doesn't matter which is which; but for the * purposes of our resolver, we actually do care about its real role. * So, override the resolver method whichMessageObject so that it * returns whichever role is NOT served by the topic object. */ whichMessageObject = (whichMessageLiteral == DirectObject ? IndirectObject : DirectObject) ; /* ------------------------------------------------------------------------ */ /* * Base class for actions that include a "topic" phrase. This is a * mix-in class that can be used in different types of topic actions. In * all cases, the topic phrase must be assigned to the 'topicMatch' * property in grammar rules based on this class. */ class TopicActionBase: object /* * Resolve the topic phrase. This resolves the match tree in out * 'topicMatch' property, and stores the result in topicList_. This * is for use in resolveNouns(). */ resolveTopic(issuingActor, targetActor, results) { /* get the topic resolver */ local resolver = getTopicResolver(issuingActor, targetActor, true); /* resolve the topic match tree */ topicList_ = topicMatch.resolveNouns(resolver, results); /* make sure it's properly packaged as a ResolvedTopic */ topicList_ = resolver.packageTopicList(topicList_, topicMatch); } /* * Set the resolved topic to the given object list. This is for use * in setResolvedObjects(). */ setResolvedTopic(topic) { /* if the topic isn't given as a ResolvedTopic, wrap it in one */ if (!topic.ofKind(ResolvedTopic)) topic = ResolvedTopic.wrapObject(topic); /* use the match object from the ResolvedTopic */ topicMatch = topic.topicProd; /* finally, make a ResolveInfo list out of the ResolvedTopic */ topicList_ = makeResolveInfoList(topic); } /* * Set the topic match tree. This is for use in setObjectMatches(). */ setTopicMatch(topic) { /* * If it's given as a ResolveInfo, wrap it in a PreResolvedProd. * Otherwise, if it's not a topic match (a TopicProd object), and * it has tokens, re-parse it as a topic match to make sure we * get the grammar optimized for global scope. */ if (topic.ofKind(ResolveInfo)) { /* it's a resolved object, so wrap it in a fake match tree */ topic = new PreResolvedProd(topic); } else if (!topic.ofKind(TopicProd)) { /* * Re-parse it as a topic phrase. If our dobj resolver knows * the actors, use those; otherwise default to the player * character. */ local issuer = (issuer_ != nil ? issuer_ : gPlayerChar); local target = (actor_ != nil ? actor_ : gPlayerChar); topic = reparseMatchAsTopic(topic, issuer, target); } /* save the topic match */ topicMatch = topic; } /* * Re-parse a match tree as a topic phrase. Returns a TopicProd * match tree, if possible. */ reparseMatchAsTopic(topic, issuingActor, targetActor) { local toks; /* we can only proceed if the match tree has a token list */ if ((toks = topic.getOrigTokenList()) != nil && toks.length() > 0) { /* parse it as a topic phrase */ local match = topicPhrase.parseTokens(toks, cmdDict); /* * if we parsed it successfully, and we have more than one * match, get the best of the bunch */ if (match != nil && match.length() > 1) { /* set up a topic resolver for the match */ local resolver = getTopicResolver( issuingActor, targetActor, true); /* sort it and return the best match */ return CommandRanking.sortByRanking(match, resolver)[1].match; } else if (match != nil && match.length() > 0) { /* we found exactly one match, so use it */ return match[1]; } } /* * if we make it this far, we couldn't parse it as a topic, so * just return the original */ return topic; } /* * get the topic resolver */ getTopicResolver(issuingActor, targetActor, reset) { /* create one if we don't already have one */ if (topicResolver_ == nil) topicResolver_ = createTopicResolver(issuingActor, targetActor); /* reset the resolver if desired */ if (reset) topicResolver_.resetResolver(); /* return the cached resolver */ return topicResolver_; } /* * Create the topic resolver. */ createTopicResolver(issuingActor, targetActor) { return new TopicResolver( self, issuingActor, targetActor, topicMatch, whichMessageTopic, getTopicQualifierResolver(issuingActor, targetActor, nil)); } /* * Get a message parameter by name. We'll return the topic for the * keyword 'topic', and inherit the default handling for anything * else. */ getMessageParam(objName) { switch(objName) { case 'topic': /* return the topic */ return getTopic(); default: /* inherit default handling */ return inherited(objName); } } /* get the current topic */ getTopic() { /* * Because the topic list always contains one entry (a * ResolvedTopic object encapsulating the topic information), * our current topic is always simply the first and only element * of the topic list. */ return topicList_[1].obj_; } /* * Get the resolver for qualifiers we find in the topic phrase * (qualifiers that might need resolution include things like * possessive adjectives and locational phrases). */ getTopicQualifierResolver(issuingActor, targetActor, reset) { /* if we don't already have a qualifier resolver, create one */ if (topicQualResolver_ == nil) topicQualResolver_ = createTopicQualifierResolver( issuingActor, targetActor); /* reset it if desired */ if (reset) topicQualResolver_.resetResolver(); /* return it */ return topicQualResolver_; } /* the topic qualifier resolver */ topicQualResolver_ = nil /* the resolved topic object list */ topicList_ = nil /* my cached topic resolver */ topicResolver_ = nil ; /* ------------------------------------------------------------------------ */ /* * An action with a topic phrase as its only object, such as "think about * <topic>". * * The grammar rules that produce these actions must set topicMatch to * the topic match tree. * * Because we don't have any actual resolved objects, we're based on * IAction. Subclasses that implement particular topic actions should * override execAction() to carry out the action; this method can call * the getTopic() method of self to get a string giving the topic. */ class TopicAction: TopicActionBase, IAction /* * Resolve objects. We don't actually have any normal objects to * resolve, but we do have to get the resolved topic phrase. */ resolveNouns(issuingActor, targetActor, results) { /* the topic phrase counts as one noun slot */ results.noteNounSlots(1); /* resolve the topic */ resolveTopic(issuingActor, targetActor, results); } /* manually set the resolved objects */ setResolvedObjects(topic) { /* set the resolved topic */ setResolvedTopic(topic); } /* manually set the pre-resolved match trees */ setObjectMatches(topic) { /* note the new topic match tree */ setTopicMatch(topic); } /* we have a topic noun phrase */ predicateNounPhrases = [&topicMatch] ; /* ------------------------------------------------------------------------ */ /* * An Action with a direct object and a topic, such as "ask <actor> about * <topic>". Topics differ from ordinary noun phrases in scope: rather * than resolving to simulation objects based on location, we resolve * these based on the actor's knowledge. * * The grammar rules that produce these actions must set dobjMatch to the * resolvable object of the command (the <actor> in "ask <actor> about * <topic>"), and must set topicMatch to the topic match tree object, * which must be a TopicProd object. Note that, in some cases, the * phrasing might make the dobjMatch the indirect object, grammatically * speaking: "type <topic> on <object>"; even in such cases, use * dobjMatch for the resolvable object. * * When we resolve the topic, we will always resolve it to a single * object of class ResolvedTopic. This contains the literal tokens of * the original command plus a list of simulation objects matching the * topic name, ordered from best to worst. This is different from the * way most commands work, since we do not resolve the topic to a simple * game world object. We keep all of this extra information because we * don't want to perform disambiguation in the normal fashion, but * instead resolve as much as we can with what we're given, and then give * the specialized action code as much information as we can to let the * action code figure out how to respond to the topic. */ class TopicTAction: TopicActionBase, TAction /* * reset the action */ resetAction() { /* reset the inherited state */ inherited(); /* forget our topic resolver */ topicResolver_ = nil; } /* * resolve our noun phrases to objects */ resolveNouns(issuingActor, targetActor, results) { local dobjRes; /* * the topic phrase counts as one noun slot, and the direct * object as another */ results.noteNounSlots(2); /* resolve the direct object, if we have one */ if (dobjMatch != nil) { /* presume we won't find an unbound anaphor */ needAnaphoricBinding_ = nil; /* * if the direct object phrase is an empty phrase, resolve * the topic first */ if (dobjMatch.isEmptyPhrase) resolveTopic(issuingActor, targetActor, results); /* get the direct object resolver */ dobjRes = getDobjResolver(issuingActor, targetActor, true); /* resolve the direct object */ dobjList_ = dobjMatch.resolveNouns(dobjRes, results); /* * if that turned up an anaphor (LOOK UP BOOK IN ITSELF), * resolve the topic phrase as though it were the direct * object */ if (needAnaphoricBinding_ && topicMatch != nil) dobjList_ = topicMatch.resolveNouns(dobjRes, results); } /* resolve the topic */ resolveTopic(issuingActor, targetActor, results); } /* * Filter the resolved topic. This is called by our * TActionTopicResolver, which refers the resolution back to us. */ filterTopic(lst, np, resolver) { /* by default, simply put everything in an undifferentiated list */ return new ResolvedTopic(lst, [], [], topicMatch); } /* * Retry a single-object action as an action taking both an object * and a topic phrase. We'll treat the original action's direct * object list as our direct object list, and we'll obtain a topic * phrase interactively. * * This routine terminates with 'exit' if it doesn't throw some other * error. */ retryWithMissingTopic(orig) { local action; /* create the new action based on the original action */ action = createForRetry(orig); /* use an empty topic phrase for the new action */ action.topicMatch = new EmptyTopicPhraseProd(); /* initialize for the missing topic */ action.initForMissingTopic(orig); /* resolve and execute the replacement action */ resolveAndReplaceAction(action); } /* initialize a new action we're retrying for a missing direct object */ initForMissingDobj(orig) { /* * The original action must have been a TopicAction, so we * already have a topic. Simply copy the original topic to use * as our own topic. For example: ASK ABOUT BOOK -> ASK <whom> * ABOUT BOOK. */ topicMatch = new PreResolvedProd(orig.getTopic()); } /* initialize for retrying with a missing topic phrase */ initForMissingTopic(orig) { local origDobj = orig.getDobj(); /* * The original action must have been a TAction, so we already * have a direct object. Simply copy the original direct object * for use as our own. For example: ASK BOB -> ASK BOB ABOUT * <what>. */ dobjMatch = new PreResolvedProd(origDobj != nil ? origDobj : orig.dobjList_); } /* create our TAction topic resolver */ createTopicResolver(issuingActor, targetActor) { return new TActionTopicResolver( self, issuingActor, targetActor, topicMatch, whichMessageTopic, getTopicQualifierResolver(issuingActor, targetActor, nil)); } /* * In the topic phrase, we can use an anaphoric pronoun to refer * back to the direct object. Since we resolve the direct object * phrase first, we can simply return the direct object list as the * binding. If the direct object isn't resolved yet, make a note to * come back and re-bind the anaphor. */ getAnaphoricBinding(typ) { /* if we've already resolved the direct object phrase, return it */ if (dobjList_ not in (nil, [])) return dobjList_; /* no dobj yet - make a note that we need to re-bind the anaphor */ needAnaphoricBinding_ = true; /* * return an empty list for now to indicate that the anaphor is * acceptable but has no binding yet */ return []; } /* * Flag: we have been asked for an anaphoric binding, but we don't * have a binding available. We'll check this after resolving the * first-resolved noun phrase so that we can go back and re-resolve * it again after resolving the other noun phrase. */ needAnaphoricBinding_ = nil /* we have a direct object and a topic phrase */ predicateNounPhrases = [&dobjMatch, &topicMatch] /* get an object role */ getRoleFromIndex(idx) { /* * index 2 is the topic object, which is always in the indirect * object role; for others, inherit the default handling */ return (idx == 2 ? IndirectObject : inherited(idx)); } /* get the OtherObject role for the given role */ getOtherObjectRole(role) { /* the complementary roles are DirectObject and IndirectObject */ return (role == DirectObject ? IndirectObject : DirectObject); } /* get the resolved object in a given role */ getObjectForRole(role) { /* * the topic is in the IndirectObject role; inherit the default * for others */ return (role == IndirectObject ? getTopic() : inherited(role)); } /* get the match tree for the given role */ getMatchForRole(role) { /* * the topic is in the IndirectObject role; inherit the default * for anything else */ return (role == IndirectObject ? topicMatch : inherited(role)); } /* * Manually set the resolved objects. We'll set our direct and * indirect objects. */ setResolvedObjects(dobj, topic) { /* inherit default handling for the direct object */ inherited(dobj); /* set the resolved topic */ setResolvedTopic(topic); } /* manually set the pre-resolved match trees */ setObjectMatches(dobj, topic) { /* inherit default handling for the direct object */ inherited(dobj); /* note the new topic match tree */ setTopicMatch(topic); } /* * Get the list of active objects. We return only our direct * object, since our topic isn't actually a simulation object. */ getCurrentObjects() { return [dobjCur_]; } /* set the current objects */ setCurrentObjects(lst) { dobjCur_ = lst[1]; dobjInfoCur_ = nil; } /* the resolved topic object list */ topicList_ = nil /* my cached topic resolver */ topicResolver_ = nil /* grammatical role played by topic phrase in generated messages */ whichMessageTopic = nil /* -------------------------------------------------------------------- */ /* * Direct Object Resolver implementation. We serve as our own * direct object resolver, so we define any special resolver * behavior here. */ /* the true role of the resolved object is always as the direct object */ whichObject = DirectObject /* * What we call our direct object might actually be playing the * grammatical role of the indirect object - in order to inherit * easily from TAction, we call our resolved object our direct * object, regardless of which grammatical role it actually plays. * For the most part it doesn't matter which is which; but for the * purposes of our resolver, we actually do care about its real role. * So, override the resolver method whichMessageObject so that it * returns whichever role is NOT served by the topic object. */ whichMessageObject = (whichMessageTopic == DirectObject ? IndirectObject : DirectObject) ; /* * "Conversation" TopicTAction. Many TopicTAction verbs involve * conversation with an actor, who's specified as the direct object: ASK * <actor> ABOUT <topic>, TELL <actor> ABOUT <topic>, ASK <actor> FOR * <topic>. For these common cases, the most likely default direct * object is the last interlocutor of the actor performing the command - * that is, ASK ABOUT BOOK should by default be directed to whomever we * were speaking to last. * * This subclass is suitable for such verbs. When asked for a default * direct object, we'll check for a current interlocutor, and use it as * the default if available. If no interlocutor is available, we'll * inherit the standard default handling. */ class ConvTopicTAction: TopicTAction getDefaultDobj(np, resolver) { /* * check to see if the actor has a default interlocutor; if so, * use it as the default actor to be addressed here, otherwise * use the default handling */ local obj = resolver.getTargetActor().getCurrentInterlocutor(); if (obj != nil) return [new ResolveInfo(obj, 0, nil)]; else return inherited(np, resolver); } /* * Create the topic resolver. Use a conversational topic resolver * for this type of action. */ createTopicResolver(issuingActor, targetActor) { return new ConvTopicResolver( self, issuingActor, targetActor, topicMatch, whichMessageTopic, getTopicQualifierResolver(issuingActor, targetActor, nil)); } ; /* ------------------------------------------------------------------------ */ /* * A conversational intransitive action. This class is for actions such * as Hello, Goodbye, Yes, No - conversational actions that don't involve * any topics or other objects, but whose expression is entirely * contained in the verb. */ class ConvIAction: IAction /* this action is conversational */ isConversational(issuer) { return true; } ; /* ------------------------------------------------------------------------ */ /* * Resolved Topic object. The topic of a TopicTAction always resolves to * one of these objects. */ class ResolvedTopic: object construct(inScope, likely, others, prod) { /* * Remember our lists of objects. We keep three separate lists, * so that the action knows how we've classified the objects * matching our phrase. We keep a list of objects that are in * scope; a list of objects that aren't in scope but which the * actor thinks are likely topics; and a list of all of the other * matches. * * We keep only the simulation objects in these retained lists - * we don't keep the full ResolveInfo data here. */ inScopeList = inScope.mapAll({x: x.obj_}); likelyList = likely.mapAll({x: x.obj_}); otherList = others.mapAll({x: x.obj_}); /* keep the production match tree */ topicProd = prod; /* * But it might still be interesting to have the ResolveInfo * data, particularly for information on the vocabulary match * strength. Keep that information in a separate lookup table * indexed by simulation object. */ local rt = new LookupTable(); others.forEach({x: rt[x.obj_] = x}); likely.forEach({x: rt[x.obj_] = x}); inScope.forEach({x: rt[x.obj_] = x}); /* save the ResolvedInfo lookup table */ resInfoTab = rt; } /* * Static method: create a ResolvedTopic to represent an object * that's already been resolved to a game object for the current * action. 'role' is the object role to wrap (DirectObject, * IndirectObject, etc). */ wrapActionObject(role) { local rt; /* create a ResolvedTopic with just the match tree */ rt = new ResolvedTopic([], [], [], gAction.getMatchForRole(role)); /* * add the current resolved object in the role as the one * in-scope match */ rt.inScopeList += gAction.getObjectForRole(role); /* return the ResolvedTopic wrapper */ return rt; } /* * Static method: create a ResolvedTopic to represent the given * object. */ wrapObject(obj) { local rt; /* create a ResolvedTopic with a PreResolvedProd for the object */ rt = new ResolvedTopic([], [], [], new PreResolvedProd(obj)); /* add the given object or objects as the in-scope match list */ rt.inScopeList += obj; /* return the ResolvedTopic wrapper */ return rt; } /* * Get the best object match to the topic. This is a default * implementation that can be changed by game authors or library * extensions to implement different topic-matching strategies. This * implementation simply picks an object arbitrarily from the * "strongest" of the three lists we build: if there's anything in * the inScopeList, we choose an object from that list; otherwise, if * there's anything in the likelyList, we choose an object from that * list; otherwise we choose an object from the otherList. */ getBestMatch() { /* if there's an in-scope match, pick one */ if (inScopeList.length() != 0) return inScopeList[1]; /* nothing's in scope, so try to pick a likely match */ if (likelyList.length() != 0) return likelyList[1]; /* * there's not even anything likely, so pick an "other", if * there's anything in that list */ if (otherList.length() != 0) return otherList[1]; /* there are no matches at all - return nil */ return nil; } /* * Is the given object among the possible matches for the topic? */ canMatchObject(obj) { return inScopeList.indexOf(obj) != nil || likelyList.indexOf(obj) != nil || otherList.indexOf(obj) != nil; } /* get the original text of the topic phrase */ getTopicText() { return topicProd.getOrigText(); } /* * Are we allowed to match the topic text literally, for parsing * purposes? If this is true, it means that we can match the literal * text the player entered against strings, regular expressions, * etc.; for example, we can match a TopicMatchTopic's matchPattern * regular expression. If this is nil, it means that we can only * interpret the meaning of the resolved topic by looking at the * various topic match lists (inScopeList, likelyList, otherList). * * By default, we simply return true. Note that the base library * never has any reason of its own to disallow literal matching of * topic text; this property is purely for the use of language * modules, to handle language-specific input that parses at a high * level as a topic phrase but which has some idiomatic or * grammatical function that makes it in appropriate to try to * extract the meaning of the resolved topic from the literal text of * the topic phrase in isolation. This case doesn't seem to arise in * English, but does occur in other languages: Michel Nizette cites * "parlez-en a Bob" as an example in French, where "en" is * essentially a particle modifying the verb, not a full-fledged * phrase that we can interpret separately as a topic. */ canMatchLiterally = true /* * get the original tokens of the topic phrase, in canonical * tokenizer format */ getTopicTokens() { return topicProd.getOrigTokenList(); } /* * get the original word strings of the topic phrase - this is * simply a list of the original word strings (in their original * case), without any of the extra information of the more * complicated canonical tokenizer format */ getTopicWords() { /* return just the original text strings from the token list */ return topicProd.getOrigTokenList().mapAll({x: getTokOrig(x)}); } /* * Get the parser ResolveInfo object for a given matched object. * This recovers the ResolveInfo describing the parsing result for * any object in the resolved object lists (inScopeList, etc). */ getResolveInfo(obj) { return resInfoTab[obj]; } /* * Our lists of resolved objects matching the topic phrase, * separated by classification. */ inScopeList = nil likelyList = nil otherList = nil /* * The production match tree object that matched the topic phrase in * the command. This can be used to obtain the original tokens of * the command or the original text of the phrase. */ topicProd = nil /* * ResolveInfo table for the resolved objects. This is a lookup * table indexed by simulation object. Each entry in the resolved * object lists (inScopeList, etc) has have a key in this table, with * the ResolveInfo object as the value for the key. This can be used * to recover the ResolveInfo object describing the parser results * for this object. */ resInfoTab = nil ; /* * A special topic for "nothing." It's occasionally useful to be able * to construct a TopicTAction with an empty topic phrase. For the * topic phrase to be well-formed, we need a valid ResolvedTopic object, * even though it won't refer to anything. */ resolvedTopicNothing: ResolvedTopic inScopeList = [] likelyList = [] otherList = [] getTopicText() { return ''; } getTopicTokens() { return []; } getTopicWords() { return []; } ; /* * Topic Resolver */ class TopicResolver: Resolver construct(action, issuingActor, targetActor, prod, which, qualifierResolver) { /* inherit the base class constructor */ inherited(action, issuingActor, targetActor); /* the topic phrase doesn't have a true resolved role */ whichObject = nil; /* remember the grammatical role of our object in the command */ whichMessageObject = which; /* remember our topic match tree */ topicProd = prod; /* remember the resolver for qualifier phrases */ qualifierResolver_ = qualifierResolver; } resetResolver() { /* inherit the default handling */ inherited(); /* reset our qualifier resolver as well */ qualifierResolver_.resetResolver(); } /* * package a resolved topic list - if it's not already represented as * a ResolvedTopic object, we'll apply that wrapping */ packageTopicList(lst, match) { /* * if the topic is other than a single ResolvedTopic object, run * it through the topic resolver's ambiguous noun phrase filter * to get it into that canonical form */ if (lst != nil && (lst.length() > 1 || (lst.length() == 1 && !lst[1].obj_.ofKind(ResolvedTopic)))) { /* it's not in canonical form, so get it in canonical form now */ lst = filterAmbiguousNounPhrase(lst, 1, match); } /* return the result */ return lst; } /* our qualifier resolver */ qualifierResolver_ = nil /* get our qualifier resolver */ getQualifierResolver() { return qualifierResolver_; } getPossessiveResolver() { return qualifierResolver_; } /* * Determine if the object is in scope. We consider any vocabulary * match to be in scope for the purposes of a topic phrase, since the * subject matter of topics is mere references to things, not the * things themselves; we can, for example, ASK ABOUT things that * aren't physically present, or even about completely abstract * ideas. */ objInScope(obj) { return true; } /* * our scope is global, because we don't limit the scope to the * physical senses */ isGlobalScope = true /* * Determine if an object is in physical scope. We'll accept * anything that's in physical scope, and we'll also accept any topic * object that the actor knows about. * * Note that this isn't part of the basic Resolver interface. It's * instead provided as a service routine for our subclasses, so that * they can easily determine the physical scope of an object if * needed. */ objInPhysicalScope(obj) { /* * it's in physical scope, or if the actor knows about it as a * topic, it's in scope */ return (scope_.indexOf(obj) != nil); } /* * Filter an ambiguous noun phrase list using the strength of * possessive qualification, if any. For a topic phrase, we want to * keep all of the possibilities. */ filterPossRank(lst, num) { return lst; } /* * Filter an ambiguous noun list. * * It is almost always undesirable from a user interface perspective * to ask for help disambiguating a topic phrase. In the first * place, since all topics tend to be in scope all the time, we * might reveal too much about the inner model of the story if we * were to enumerate all of the topic matches to a phrase. In the * second place, topics are used in conversational contexts, so it * almost never make sense for the parser to ask for clarification - * the other member of the conversation might ask, but not the * parser. So, we'll always filter the list to the required number, * even if it means we choose arbitrarily. * * As a first cut, we prefer objects that are physically in scope to * those not in scope: if the player is standing next to a control * panel and types "ask bob about control panel," it makes little * sense to consider any other control panels in the simulation. * * As a second cut, we'll ask the actor to filter the list. Games * that keep track of the actor's knowledge can use this to filter * according to topics the actor is likely to know about. */ filterAmbiguousNounPhrase(lst, requiredNum, np) { /* ask the action to create the ResolvedTopic */ local rt = resolveTopic(lst, requiredNum, np); /* wrap the ResolvedTopic in the usual ResolveInfo list */ return [new ResolveInfo(rt, 0, np)]; } /* * Resolve the topic phrase. This returns a ResolvedTopic object * encapsulating the resolution of the phrase. * * This default base class implementation simply creates a resolved * topic list with the whole set of possible matches * undifferentiated. Subclasses for specialized actions might want * to differentiate the items in the list, based on things like the * actor's knowledge so far or what's in physical scope. */ resolveTopic(lst, requiredNum, np) { /* return a ResolvedTopic that lumps everything in the main list */ return new ResolvedTopic(lst, [], [], topicProd); } /* * Resolve an unknown phrase. We allow unknown words to be used in * topics; we simply return a ResolvedTopic that doesn't refer to * any simulation objects at all. */ resolveUnknownNounPhrase(tokList) { /* * Create our ResolvedTopic object for the results. We have * words we don't know, so we're not referring to any objects, * so our underlying simulation object list is empty. */ local rt = new ResolvedTopic([], [], [], topicProd); /* return a resolved topic object with the empty list */ return [new ResolveInfo(rt, 0, new TokenListProd(tokList))]; } /* filter a plural */ filterPluralPhrase(lst, np) { /* * Handle this the same way we handle an ambiguous noun phrase, * so that we yield only one object. Topics are inherently * singular; we'll allow asking about a grammatically plural * term, but we'll turn it into a single topic result. */ return filterAmbiguousNounPhrase(lst, 1, np); } /* get a default object */ getDefaultObject(np) { /* there is never a default for a topic */ return nil; } /* it's fine not to match a topic phrase */ noVocabMatch(action, txt) { } noMatch(action, txt) { } noMatchPoss(action, txt) { } /* we don't allow ALL or provide defaults */ getAll(np) { return []; } getAllDefaults() { return []; } /* the production match tree for the topic phrase we're resolving */ topicProd = nil ; /* * A topic resolver specialized for TopicTActions - actions involving a * topic and a physical object, such as CONSULT ABOUT. For these topics, * we'll let the action handle the resolution. */ class TActionTopicResolver: TopicResolver resolveTopic(lst, requiredNum, np) { /* let the action handle it */ return action_.filterTopic(lst, np, self); } ; /* * A topic resolver specialized for conversational actions (ASK ABOUT, * TELL ABOUT, etc). When we resolve the topic, we'll differentiate the * resolution to differentiate based on the knowledge of the actor who's * performing the command. */ class ConvTopicResolver: TopicResolver /* * Resolve the topic phrase. We'll break up the vocabulary matches * into three sublists: the objects that are either in physical scope * or known to the actor performing the command; objects that the * actor considers likely topics; and everything else. */ resolveTopic(lst, requiredNum, np) { local inScope; local actorPrefs; /* * First, get the subset of items that are in conversational * scope - we'll consider this the best set of matches. */ inScope = lst.subset({x: objInConvScope(x.obj_)}); /* * eliminate the in-scope items from the list, so we can * consider only what remains */ lst -= inScope; /* * ask the actor to pick out the most likely set of topics from * the ones that remain */ actorPrefs = lst.subset({x: actor_.isLikelyTopic(x.obj_)}); /* eliminate those items from the list */ lst -= actorPrefs; /* create our ResolvedTopic object and return it */ return new ResolvedTopic(inScope, actorPrefs, lst, topicProd); } /* * Determine if an object is in "conversational" scope - this returns * true if the object is in physical scope or it's known to the actor * performing the command. */ objInConvScope(obj) { /* * if it's in physical scope, or the actor knows about it, it's * in conversation scope */ return (objInPhysicalScope(obj) || actor_.knowsTopic(obj)); } ; /* ------------------------------------------------------------------------ */ /* * System action. These actions are for out-of-game meta-verbs (save, * restore, undo). These verbs take no objects, must be performed by * the player (thus by the player character, not an NPC), and consume no * game clock time. */ class SystemAction: IAction /* execute the action */ execAction() { /* * Conceptually, system actions are performed by the player * directly, not by any character in the game (not even the * player character). However, we don't distinguish in the * command-entry user interface between a command the player is * performing and a command to the player character, hence we * can merely ensure that the command is not directed to a * non-player character. */ if (!gActor.isPlayerChar) { gLibMessages.systemActionToNPC(); exit; } /* * system actions sometimes need to prompt for interactive * responses, so deactivate the report list - this will allow * interactive prompts to be shown immediately, not treated as * reports to be deferred until the command is finished */ gTranscript.showReports(true); gTranscript.clearReports(); /* perform our specific action */ execSystemAction(); /* re-activate the transcript for the next command */ gTranscript.activate(); } /* each subclass must override this to perform its actual action */ execSystemAction() { } /* * Ask for an input file. We call the input manager, which freezes * the real-time clock, displays the appropriate local file selector * dialog, and restarts the clock. */ getInputFile(prompt, dialogType, fileType, flags) { return inputManager.getInputFile(prompt, dialogType, fileType, flags); } /* system actions consume no game time */ actionTime = 0 ; /* ------------------------------------------------------------------------ */ /* * An exception class for remappings that we can't handle. */ class ActionRemappingTooComplexError: Exception ;
TADS 3 Library Manual
Generated on 5/16/2013 from TADS version 3.1.3
Generated on 5/16/2013 from TADS version 3.1.3