#charset "us-ascii"
#include "advlite.h"
/*
* ***************************************************************************
* query.t
*
* This module forms part of the adv3Lite library (c) 2012-13 Eric Eve
*
* Based substantially on the query.t module in the Mercury Library (c) 2012
* Michael J. Roberts.
*/
/* ------------------------------------------------------------------------ */
/*
* Q is the general-purpose global Query object. Its various methods are
* used to ask questions about the game state.
*
* For any query, there are two sources of answers. First, there's the
* standard answer based on the basic "physics" of the adventure world
* model. Second, there are any number of custom answers from Special
* objects, which define customizations that apply to specific
* combinations of actors, locations, objects, times, or just about
* anything else that the game can model.
*
* The standard physics-based answer is the default. It provides the
* answer if there are no active Special objects that provide custom
* answers.
*
* If there are active Specials, the only ones that matter for a
* particular query are the ones that define that query's method. If
* there are any active Special objects that define a query method,
* calling Q.foo() actually calls the highest-priority Special's version
* of the foo() method. That Special method can in turn call the next
* lower priority Special using next(). If there are no active Special
* objects defining a query method, the default handler in QDefaults will
* be used automatically.
*/
Q: object
/*
* Get the list of objects that are in scope for the given actor.
* Returns a ScopeList object containing the scope. You can convert
* the ScopeList to an ordinary list of objects via toList().
*/
scopeList(actor)
{ return Special.first(&scopeList).scopeList(actor); }
knownScopeList()
{ return Special.first(&knownScopeList).knownScopeList;}
topicScopeList()
{ return Special.first(&topicScopeList).topicScopeList;}
/*
* Is A in the light? This determines if there's light shining on
* the exterior surface of A.
*/
inLight(a)
{ return Special.first(&inLight).inLight(a); }
/*
* Can A see B?
*/
canSee(a, b)
{ return Special.first(&canSee).canSee(a, b); }
/*
* Determine if there's anything blocking the sight path from A to B.
* Returns a list of objects blocking sight; if there's no
* obstruction, returns an empty list. If the two objects are in
* separate rooms, the outermost room containing 'a' represents the
* room separation. If there's no obstruction, returns an empty
* list.
*/
sightBlocker(a, b)
{ return Special.first(&sightBlocker).sightBlocker(a, b); }
/*
* Can we reach from A to B? We return true if there's nothing in
* the way, nil otherwise.
*/
canReach(a, b)
{ return Special.first(&canReach).canReach(a, b); }
/*
* Determine if A can reach B, and if not, what stands in the way.
* Returns a list of containers along the path between A and B that
* obstruct the reach. If the two objects are in separate rooms, the
* top-level room containing A is in the list to represent the room
* separation. If there's no obstruction, we return an empty list.
*/
reachBlocker(a, b)
{ return Special.first(&reachBlocker).reachBlocker(a, b); }
/*
* Determine if there is a problem with A reaching B, and if so what it
* is. If there is a problem return a ReachProblem object describing what
* the problem is, otherwise return nil.
*/
reachProblem(a, b)
{ return Special.first(&reachProblem).reachProblem(a, b); }
reachProblemVerify(a, b)
{ return Special.first(&reachProblemVerify).reachProblemVerify(a, b); }
reachProblemCheck(a, b)
{ return Special.first(&reachProblemCheck).reachProblemCheck(a, b); }
/*
* Can A hear B?
*/
canHear(a, b)
{ return Special.first(&canHear).canHear(a, b); }
/*
* Determine if A can hear B, and if not, what stands in the way. We
* return a list of the obstructions to sound between A and B. If
* the two objects are in separate rooms, the top level room
* containing A represents the room separation. If there are no
* sound obstructions, returns an empty list.
*/
soundBlocker(a, b)
{ return Special.first(&soundBlocker).soundBlocker(a, b); }
/*
* Can A smell B?
*/
canSmell(a, b)
{ return Special.first(&canSmell).canSmell(a, b); }
/*
* Determine if A can smell B, and if not, what stands in the way.
* Returns a list of obstructions to scent between A and B. If the
* two objects are in separate rooms, the outermost room containing A
* represents the room separation. If there are no obstructions,
* returns an empty list.
*/
scentBlocker(a, b)
{ return Special.first(&scentBlocker).scentBlocker(a, b); }
/* Determine if A can talk to B. */
canTalkTo(a, b)
{ return Special.first(&canTalkTo).canTalkTo(a, b); }
/* Determine if A can Throw something to B. */
canThrowTo(a, b)
{ return Special.first(&canThrowTo).canThrowTo(a, b); }
/*
* Are the active conditions on Specials dynamic (i.e. such as to change
* during the course of the game)? By default we'll assume that some of
* them may be.
*/
dynamicSpecials = true
;
/*
* Query Defaults. This provides the default handlers for all query
* methods. These are the results that you get using the basic adventure
* game "physics" model to answer the questions, ignoring any special
* exceptions defined by the game.
*
* This is the lowest-ranking Special object, and is always active.
*/
QDefaults: Special
/* this is the defaults object, so it has the lower priority */
priority = 0
/* this is the defaults object, so it's always active */
active = true
/*
* Get the list of objects that are in scope for the given actor.
* Returns a ScopeList object containing the scope. You can convert
* the ScopeList to an ordinary list of objects via toList().
*/
scopeList(actor)
{
/* start a new scope list */
local s = new ScopeList();
/* everything the actor is directly holding is in scope */
s.addAll(actor.directlyHeld);
local c = actor.outermostVisibleParent();
/*
* If we're in a lighted area, add the actor's outermost visible
* container and its contents. In the dark, add the actor's
* immediate container only (not its contents), on the assumption
* that the actor is in physical contact with it and thus can
* refer to it and manipulate it even without seeing it.
*/
if (inLight(actor))
{
/* lit area - add the outermost container and its contents */
s.addOnly(c);
s.addWithin(c);
}
else
{
/* in the dark - add only the immediate container */
s.addOnly(actor.location);
/* plus anything that's self illuminating */
s.addSelfIlluminatingWithin(c);
}
/* close the scope */
s.close();
/* return the ScopeList we've built */
return s;
}
/* Get a list of all objects that are known to the player char */
knownScopeList()
{
local vec = new Vector(30);
for(local obj = firstObj(Thing); obj != nil; obj = nextObj(obj, Thing))
{
if(obj.known)
vec += obj;
}
return vec.toList;
}
/*
* Get a list of all known mentionable objects, which we assume will
* include both known Things and known Topics
*/
topicScopeList()
{
return World.universalScope.subset({o: o.known});
}
/*
* Is A in the light? This determines if there's light shining on
* the exterior surface of A.
*/
inLight(a)
{
/* A is lit if it's a Room and it's illuminated */
if(a.ofKind(Room))
return a.isIlluminated;
/* Otherwise a may be lit if it's visible in the dark */
if(a.visibleInDark)
return true;
/* A is lit if its enclosing parent is lit within */
local par = a.interiorParent();
return par != nil && par.litWithin();
}
/*
* Can A see B? We return true if and only if B is in light and there's a
* clear sight path from A to B. Also A can't see B is B is explicitly
* hidden.
*/
canSee(a, b)
{
if(a.isIn(nil) || b.isIn(nil) || b.isHidden)
return nil;
/* we can see it if it's in light and there's a clear path to it */
return inLight(b)
&& sightBlocker(a, b).indexWhich({x: x not in (a, b)}) == nil;
}
/*
* Determine if there's anything blocking the sight path from A to B.
* Returns a list of objects blocking sight; if there's no
* obstruction, returns an empty list. If the two objects are in
* separate rooms, the outermost room containing 'a' represents the
* room separation. If there's no obstruction, returns an empty
* list.
*/
sightBlocker(a, b)
{
/* scan for sight blockages along the containment path */
return a.containerPathBlock(b, &canSeeOut, &canSeeIn);
}
/*
* Can we reach from A to B? We return true if there's a clear reach path
* from A to B, which we take to be the case if we can't find any problems
* in reaching from A to B.
*/
canReach(a, b)
{
return Q.reachProblem(a, b) == [];
}
/*
* Determine if there is anything preventing or hindering A from reaching
* B; if so return a ReachProblem object describing the problem in a way
* that a check or verify routine can act on (possibly with an implicit
* action to remove the problem). If not, return an empty list.
*
* NOTE: if you provide your own version of this method on a Special it
* must return either an empty list (to indicate that there are no
* problems with reaching from A to B) or a list of one or more
* ReachProblem objects describing what is preventing A from reaching B.
*
* Your own Special should normally leave reachProblem() alone and
* override reachProblemVerify() and/or reachProblemCheck().
*/
reachProblem(a, b)
{
/*
* A list of issues that might prevent reaching from A to B. If we
* encounter a fatal one we return the list straight away rather than
* carrying out more checks.
*/
local issues = Q.reachProblemVerify(a, b);
if(issues.length > 0)
return issues;
return Q.reachProblemCheck(a, b);
}
/* Return a list of reach issues that might occur at the verify stage. */
reachProblemVerify(a, b)
{
/*
* A list of issues that might prevent reaching from A to B. If we
* encounter a fatal one we return the list straight away rather than
* carrying out more checks.
*/
local issues = [];
if(a.isIn(nil) || b.isIn(nil))
{
issues += new ReachProblemDistance(a, b);
return issues;
}
local lst = Q.reachBlocker(a, b);
/*
* If there's a blocking object but the blocking object is the one
* we're trying to reach, then presumably we can reach it after all
* (e.g. an actor inside a closed box. Otherwise if there's a
* blocking object then reach is impossible.
*/
if(lst.length > 0 && lst[1] != b)
{
/*
* If the blocking object is a room, then the problem is that the
* other object is too far away.
*/
if(lst[1].ofKind(Room))
issues += new ReachProblemDistance(a, b);
/* Otherwise some enclosing object is in the way */
else
issues += new ReachProblemBlocker(b, lst[1]);
return issues;
}
/*
* If a defines a verifyReach method, check whether running it adds a
* new verify result to the current action's verifyTab table. If so,
* there's a problem with reaching so return a ReachProblemVerifyReach
* object.
*/
if(b.propDefined(&verifyReach))
{
local tabCount = gAction.verifyTab.getEntryCount();
b.verifyReach(a);
if(gAction.verifyTab.getEntryCount > tabCount)
issues += new ReachProblemVerifyReach(a, b);
}
/* Return our list of issues. */
return issues;
}
reachProblemCheck(a, b)
{
/*
* A list of issues that might prevent reaching from A to B. If we
* encounter a fatal one we return the list straight away rather than
* carrying out more checks.
*/
local issues = [];
local checkMsg = nil;
/*
* Next check whether the actor is in a nested room that does not
* contain the object being reached, and if said nested room does not
* allow reaching out.
*/
local loc = a.location;
if(loc != a.getOutermostRoom && !b.isOrIsIn(loc) &&
!loc.allowReachOut(b))
issues += new ReachProblemReachOut(b);
try
{
/*
* Determine whether there's any problem with b reached from a
* defined in b's checkReach (from a) method.
*/
checkMsg = gOutStream.captureOutputIgnoreExit({: b.checkReach(a)});
/*
* If the checkReach method generates a non-empty string, add a
* new ReachProblemCheckReach object that encapsulates it
*/
if(checkMsg not in (nil, ''))
issues += new ReachProblemCheckReach(b, checkMsg);
/*
* Next check whether there's any problem reaching inside B from A
* defined in B's checkReachIn method or the checkReach in method
* of anything that contains B.
*/
local cpar = b.commonContainingParent(a);
if(cpar != nil)
{
for(loc = b.location; loc != cpar; loc = loc.location)
{
checkMsg = gOutStream.captureOutputIgnoreExit(
{: loc.checkReachIn(a, b)});
if(checkMsg not in (nil, ''))
issues += new ReachProblemCheckReach(b, checkMsg);
}
}
}
/*
* Game authors aren't meant to use the exit macro in check methods,
* but in case they do we handle it here.
*/
catch (ExitSignal ex)
{
/*
* If for some reason a check method uses exit without displaying
* a method, we supply a dummy failure message at this point.
*/
if(checkMsg is in (nil, ''))
checkMsg = gAction.failCheckMsg;
issues += new ReachProblemCheckReach(b, checkMsg);
}
/* Return our list of issues */
return issues;
}
/*
* Determine if A can reach B, and if not, what stands in the way. Returns
* a list of containers along the path between A and B that obstruct the
* reach. If the two objects are in separate rooms, the top-level room
* containing A is in the list to represent the room separation. If
* there's no obstruction, we return an empty list.
*/
reachBlocker(a, b)
{
return a.containerPathBlock(b, &canReachOut, &canReachIn);
}
/*
* Can A hear B? We return true if there's a clear sound path from A to
* B.
*/
canHear(a, b)
{
if(a.isIn(nil) || b.isIn(nil))
return nil;
return soundBlocker(a, b).indexWhich({x: x not in (a, b)}) == nil;
}
/*
* Determine if A can hear B, and if not, what stands in the way. We
* return a list of the obstructions to sound between A and B. If
* the two objects are in separate rooms, the top level room
* containing A represents the room separation. If there are no
* sound obstructions, returns an empty list.
*/
soundBlocker(a, b)
{
return a.containerPathBlock(b, &canHearOut, &canHearIn);
}
/*
* Can A smell B? We return true if there's a clear scent path from
* A to B.
*/
canSmell(a, b)
{
if(a.isIn(nil) || b.isIn(nil))
return nil;
return scentBlocker(a, b).indexWhich({x: x not in (a, b)}) == nil;
}
/*
* Determine if A can smell B, and if not, what stands in the way.
* Returns a list of obstructions to scent between A and B. If the
* two objects are in separate rooms, the outermost room containing A
* represents the room separation. If there are no obstructions,
* returns an empty list.
*/
scentBlocker(a, b)
{
return a.containerPathBlock(b, &canSmellOut, &canSmellIn);
}
/*
* Determine if A can talk to B. In the base situation A can talk to B if
* A can hear B.
*/
canTalkTo(a, b)
{
return Q.canHear(a, b);
}
/*
* Determine if A can throw something to B. In the base situation A can
* throw to B if A can reach B.
*
*/
canThrowTo(a, b)
{
return canReach(a, b);
}
;
/* ------------------------------------------------------------------------ */
/*
* A Special defines a set of custom overrides to standard Query
* questions that apply under specific conditions.
*
* At any given time, a Special is either active or inactive. This is
* determined by the active() method.
*/
class Special: object
/*
* Am I active? Each instance should override this to define the
* conditions that activate the Special.
*/
active = nil
/*
* My priority. This is an integer value that determines which
* Special takes precedence when two or more Specials are active at
* the same time, and they both/all define a given query method. In
* such a situation, Q calls the active Specials in ascending
* priority order (lowest first, highest last), and takes the last
* one's answer as the true answer to the question. This means that
* the Special with the highest priority takes precedence, and can
* override any lower-ranking Special that's active at the same time.
*
* The library uses the following special priority values:
*
* 0 = the basic library defaults. The defaults must have the lowest
* priority, meaning that all Special objects defined by a game or
* extension must use priorities higher than 0.
*
* Other than the special priorities listed above, the priority is
* simply a relative ordering, so games and extensions can use
* whatever range of values they like.
*
* Note that priorities can't change while running. This is a
* permanent feature of the object. We take advantage of this to
* avoid re-sorting the active list every time we build it. We sort
* the master list at initialization and assume it stays sorted, so
* that any subset is inherently sorted. If it's important to the
* game to dynamically change priorities, you just need to re-sort
* the allActive_ list at appropriate times. If priorities can only
* change when the game-world state changes, you can simply sort the
* list in allActive() each time it's rebuilt. If priorities can
* change at other times (which doesn't seem like it'd be useful, but
* just in case), you'd need to re-sort the list on every call to
* allActive(), even when the list isn't rebuilt.
*/
priority = 1
/*
* Call the same method in the next lower priority Special. This can
* be used in any Special query method to invoke the "default"
* version that would have been used if the current Special had not
* been active.
*
* This is analogous to using 'inherited' to inherit the superclass
* version of a method from an overriding version in a subclass. As
* with 'inherited', you can only call this directly from the method
* that you want to pass to the default handling, because this
* routine determines what to call based on the caller.
*/
next()
{
/* get the caller's stack trace information */
local stk = t3GetStackTrace(2);
local prop = stk.prop_;
/* find the 'self' object in the currently active Specials list */
local slst = Special.allActive();
local idx = slst.indexOf(stk.self_);
/* get the next Special that defines the method */
while (!slst[++idx].propDefined(prop)) ;
/* call the query method in the next Special, returning the result */
return slst[idx].(prop)(stk.argList_...);
}
/*
* Get the first active Special (the one with the highest priority)
* that defines the given method. This is used by the Q query
* methods to invoke the correct current Special version of the
* method.
*/
first(prop)
{
/*
* If the active conditions on one or more Specials may change during
* the course of the game, invalidate the list of active Specials to
* force it to be rebuilt.
*/
if(Q.dynamicSpecials)
Special.allActive_ = nil;
/* get the active Specials */
local slst = Special.allActive();
/* find the first definer of the method */
local idx = 0;
while (!slst[++idx].propDefined(prop)) ;
/* return the one we found */
return slst[idx];
}
/* Class method: get the list of active Specials. */
allActive()
{
local a;
/* if the cache is empty, rebuild it */
if ((a = allActive_) == nil)
a = allActive_ = all.subset({ s: s.active() });
/* return the list */
return a;
}
/*
* Class property: cache of all currently active Specials. This is
* set whenever someone asks for the list and it's not available, and
* is cleared whenever an Effect modifies the game state. (Callers
* shouldn't access this directly - this is an internal cache. Use
* the allActive() method instead.)
*/
allActive_ = nil
/* during initialization, build the list of all Specials */
classInit()
{
/* build the list of all Specials */
local v = new Vector(128);
forEachInstance(Special, { s: v.append(s) });
/*
* Sort it in ascending priority order. Since we assume that
* priorities are fixed, this eliminates the need to sort when
* creating active subsets - the subsets will automatically come
* up in priority order because they're taken from a list that
* starts in priority order.
*/
v.sort(SortDesc, { a, b: a.priority - b.priority });
/* save it as a list */
all = v.toList();
}
/*
* Class property: the list of all Special objects throughout the
* game. This is set up during preinit.
*/
all = []
;
/*------------------------------------------------------------------------- */
/*
* A commLink is a Special that establishes a communications link between the
* player character and one or more actors in remote locations.
*
* To activate the commLink with another actor, call
* commLink.connectTo(other). To make it a video link as well as an audio
* link, call commLink.connectTo(other, true).
*
* To disconnect the call with a specific actor, call
* commLink.disconnectFrom(other); to terminate the commLink with all actors,
* call commLink.disconnect()
*
*/
commLink: Special
/*
* Our scope list must include all the actors we're currently connected
* to.
*/
scopeList(actor)
{
local s = next();
s.vec_ += connectionList.mapAll({x: x[1]});
s.vec_ = s.vec_.getUnique();
return s;
}
/* We can hear an actor if s/he's in our connection list */
canHear(a, b)
{
/*
* We assume that if a can hear b, b can hear a, but the link is only
* between the player character an another actor. If b is the player
* character swap a and b so that the tests that follow will still
* apply.
*/
if(b == gPlayerChar)
{
b = a;
a = gPlayerChar;
}
/*
* If one of the actors is the player character and the other is in
* our connection list, then they can hear each other.
*/
if(a == gPlayerChar && isConnectedTo(b))
return true;
/* Otherwise use the next special. */
return next();
}
canSee(a, b)
{
/*
* We assume that if a can see b, b can see a, but the link is only
* between the player character and another actor. If b is the player
* character swap a and b so that the tests that follow will still
* apply.
*/
if(b == gPlayerChar)
{
b = a;
a = gPlayerChar;
}
/*
* If one of the actors is the player character and the other is in
* our connection list with a video value of true, then they can see
* each other.
*/
if(a == gPlayerChar && isConnectedTo(b) == VideoLink)
return true;
/* Otherwise use the next special. */
return next();
}
canTalkTo(a, b)
{
/*
* We assume that if a can talk to b, b can talk to a, but the link is
* only between the player character and another actor. If b is the
* player character swap a and b so that the tests that follow will
* still apply.
*/
if(b == gPlayerChar)
{
b = a;
a = gPlayerChar;
}
/*
* If one of the actors is the player character and the other is in
* our connection list, then they can talk to each other.
*/
if(a == gPlayerChar && isConnectedTo(b))
return true;
/* Otherwise use the next special. */
return next();
}
/*
* The list of actors we're currently connected to. This is a list of two
* element lists in the form [actor, video], where actor is the actor
* we're connected to and video is true or nil according to whether the
* link to that actor is a video link as well as an audio link.
*/
connectionList = []
/* This Special is active is there's anything in its connectionList. */
active = connectionList.length > 0
/*
* Connect this comms link to other; if video is specified and is true,
* the comms links is also a video link.
*/
connectTo(other, video = nil)
{
/*
* In case the video parameter is supplied as AudioLink or VideoLink
* (some game authors may try this even though it's not documented),
* we should first translate the video parameter into true or nil as
* appropriate.
*/
if(video == AudioLink)
video = nil;
if(video == VideoLink)
video = true;
/* Add other to our connection list. */
connectionList = connectionList.append([other, video]);
/* Force the Special class to rebuild its list of active Specials. */
Special.allActive_ = nil;
}
/* Disconnect this commLink from everyone */
disconnect()
{
/* Empty our out connectionList */
connectionList = [];
/* Force the Special class to rebuild its list of active Specials. */
Special.allActive_ = nil;
}
/*
* Disconnect this commLink from lst, where lst may be a single actor or a
* list of actors.
*/
disconnectFrom(lst)
{
/* Convert the lst parameter to a list if it isn't one already */
lst = valToList(lst);
/*
* Reduce our connectionList to a subset of members that aren't in
* lst.
*/
connectionList = connectionList.subset({x: lst.indexOf(x[1]) == nil});
/* Force the Special class to rebuild its list of active Specials. */
Special.allActive_ = nil;
}
/*
* Is there a communications link with obj? Return nil if there is none,
* AudioLink if there's an audio connection only and VideoLink if there's
* a video connection as well.
*/
isConnectedTo(obj)
{
local conn = connectionList.valWhich({x: x[1] == obj});
if(conn == nil)
return nil;
return conn[2] ? VideoLink : AudioLink;
}
/*
* Give this Special a higher priority that the QSenseRegion Special so
* that it takes precedence when its active.
*/
priority = 5
;
/* ------------------------------------------------------------------------ */
/*
* A ScopeList is a helper object used to build the list of objects in
* scope. This object provides methods for the common ways of adding
* objects to scope.
*
* The ScopeList isn't a true Collection object, but it mimics one by
* providing most of the standard methods. You can use length() and the
* [] operator to scan the list, perform a foreach or for..in loop with a
* ScopeList to iterate over the items in scope, you can use find() to
* check if a given object is in scope, and you can use subset() to get a
* list of in-scope objects satisfying some condition.
*/
class ScopeList: object
/*
* Add an object and its contents to the scope.
*/
add(obj)
{
/*
* if we've already visited this object in full-contents mode,
* there's no need to repeat all that
*/
local tstat = status_[obj];
if (tstat == 2)
return;
/* if the object isn't already in the list at all, add it */
if (tstat == nil)
vec_.append(obj);
/* promote it to status 2: added with contents */
status_[obj] = 2;
/*
* if we can see in, add all of the contents, interior and
* exterior; otherwise add just the exterior contents
*/
if (obj.canSeeIn)
addAll(obj.contents);
else
addAll(obj.extContents);
}
/*
* Add all of the objects in the given list
*/
addAll(lst)
{
for (local i = 1, local len = lst.length() ; i <= len ; ++i)
add(lst[i]);
}
/*
* Add the interior contents of an object to the scope. This adds
* only the contents, not the object itself.
*/
addWithin(obj)
{
/* add each object in the interior contents */
addAll(obj.intContents);
}
/* add each self-illuminating object in the interior contents */
addSelfIlluminatingWithin(obj)
{
addAll(obj.intContents.subset({x: x.visibleInDark}));
}
/*
* Add a single object to the scope. This doesn't add anything
* related to the object (such as its contents) - just the object
* itself.
*/
addOnly(obj)
{
/*
* If this object is already in the status table with any status,
* there's no need to add it again. We also don't want to change
* its existing status, because if we've already added it with
* its contents, adding it redundantly by itself doesn't change
* the fact that we've added its contents.
*/
if (status_[obj] != nil)
return;
/* add it to the vector */
vec_.append(obj);
/* set the status to 1: we've added only this object */
status_[obj] = 1;
}
/* "close" the scope list - this converts the vector to a list */
close()
{
vec_ = vec_.toList();
status_ = nil;
}
/* get the number of items in scope */
length() { return vec_.length(); }
/* get an item from the list */
operator[](idx) { return vec_[idx]; }
/* is the given object in scope? */
find(obj) { return status_[obj] != nil; }
/* get the subset of the objects in scope matching the given condition */
subset(func) { return vec_.subset(func); }
/* return the scope as a simple list of objects */
toList() { return vec_; }
/* create an iterator, for foreach() */
createIterator() { return vec_.createIterator(); }
/* create a live iterator */
createLiveIterator() { return vec_.createLiveIterator(); }
/* a vector with the objects in scope */
vec_ = perInstance(new Vector(50))
/*
* A LookupTable with the objects already added to the list. We use
* this to avoid redundantly scanning containment trees for objects
* that we've already added. For each object, we set status_[obj] to
* a status indicator:
*
*. nil (unset) - the object has never been visited
*. 1 - we've added the object only, not its contents
*. 2 - we've added the object and its contents
*/
status_ = perInstance(new LookupTable(64, 128))
;
/*
* An object describing a reach problem; such objects are used by the Query
* object to communicate problems with one object touching another to the
* touchObj PreCondition (see also precond.t). ReachProblem objects are
* normally created dynamicallty as required, although it is usually one of
* the subclasses of ReachProblem that it used.
*/
class ReachProblem: object
/*
* Problems which reaching an object that occur at the verify stage and
* which might affect the choice of object. If the verify() method of a
* ReachProblem object wishes to rule out an action it should do so using
* illogical(), inaccessible() or other such verification macros.
*/
verify() { }
/*
* The check() method of a ReachProblem should check whether the target
* object can be reached by the source object. If allowImplicit is true
* the check method may attempt an implicit action to bring the target
* object within reach.
*
* Return true if the target object is within reach, and nil otherwise.
*
* Note that the check() method of a ReachProblem will normally be called
* from the checkPreCondition() method of touchObj.
*/
check(allowImplicit) { return true; }
construct(target)
{
target_ = target;
}
/* The object we're trying to reach */
target_ = nil
;
/*
* A ReachProblem object for when the target object is too far away (because
* it's in another room).
*/
class ReachProblemDistance: ReachProblem
verify()
{
inaccessible(tooFarAwayMsg);
}
tooFarAwayMsg()
{
return source_.getOutermostRoom.cannotReachTargetMsg(target_);
}
construct(source, target)
{
source_ = source;
inherited(target);
}
/* The object that's trying to reach the target */
source_ = nil;
;
/*
* A ReachProblem object for when access to the target is blocked by a closed
* container along the path from the source to the target.
*/
class ReachProblemBlocker: ReachProblem
verify()
{
inaccessible(reachBlockedMsg);
}
/*
* Delegate defining the message explaining that blocking is reached to
* the blocking object.
*/
reachBlockedMsg()
{
return obstructor_.reachBlockedMsg(target_);
}
/*
* The closed container that is preventing access to the target object the
* actor is trying to reach.
*/
obstructor_ = nil
construct(target, obstructor)
{
inherited(target);
obstructor_ = obstructor;
}
;
/*
* A ReachProblem resulting from the verifyReach() method of the target
* object.
*/
class ReachProblemVerifyReach: ReachProblem
verify()
{
source_.verifyReach(target_);
}
construct(source, target)
{
inherited(target);
source_ = source;
}
source_ = nil
;
/*
* A ReachProblem resulting from reach being prohibited by the checkReach()
* method or checkReachIn() method of the target object or an object along the
* reach path.
*/
class ReachProblemCheckReach: ReachProblem
errMsg_ = nil
construct(target, errMsg)
{
inherited(target);
errMsg_ = errMsg;
}
check(allowImplicit)
{
say(errMsg_);
return nil;
}
;
/*
* A ReachProblem object for when the actor can't reach the target from the
* actor's (non top-level room) container.
*/
class ReachProblemReachOut: ReachProblem
/*
* If allowImplicit is true we can try moving the actor out of its
* immediate container to see if this solves the problem. If it does,
* return true; otherwise return nil.
*/
check(allowImplicit)
{
/* Note the actor's immediate location. */
local loc = gActor.location;
/* Note the target we're trying to reach. */
local obj = target_;
/*
* The action needed by the actor to leave the actor's immediate
* location (GetOff or GetOutOf, depending whether the actor is on a
* Platform or in a Booth).
*/
local getOutAction;
/*
* Keep trying to move the actor out of its immediate location until
* the actor is in its outermost room, or until we can't move the
* actor, or until the actor can reach the target object.
*/
while(loc != gActor.getOutermostRoom && !obj.isOrIsIn(loc) &&
!loc.allowReachOut(obj))
{
/*
* The action we need to move the actor out of its immediate
* location will be GetOff or GetOutOf depending on the contType
* of the actor's location.
*/
getOutAction = loc.contType == On ? GetOff : GetOutOf;
/*
* If we're allowed to attempt an implicit action, and the actor's
* location is happy to allow the actor to leave it via an
* implicit action for the purpose of reaching, then try an
* implicit action to take the actor out of its location.
*/
if(allowImplicit && loc.autoGetOutToReach
&& tryImplicitAction(getOutAction, loc))
{
/*
* If the actor is still in its original location, then we
* were unable to move it out, so return nil to signal that
* the reachability condition can't be met.
*/
if(gActor.isIn(loc))
return nil;
}
/*
* Otherwise, if we can't perform an implicit action to remove the
* actor from its immediate location, display a message explaining
* the problem and return nil to signal that the reachability
* condition can't be met.
*/
else
{
say(loc.cannotReachOutMsg(obj));
return nil;
}
/*
* If we've reached this point we haven't failed either check in
* this loop, so note that the actor's location is now the
* location of its former location and then continue round the
* loop.
*/
loc = loc.location;
}
/*
* If we've reached this point, we must have met the reachability
* condition, so return true.
*/
return true;
}
;
Adv3Lite Library Reference Manual
Generated on 25/04/2024 from adv3Lite version 2.0