action.t
#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