query.t | documentation |
#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); } /* * Is B in gthe light from the perspective of A, where A may be either inside or outside B? */ inLightFor(a, b) { return Special.first(&inLightFor).inLightFor(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 Things 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(); } /* is B lit from the perspect of A, who may be inside B. */ inLightFor(a, b) { /* * The special case is where A wishes to view B and B is A's outermostVisibleParent, such * as a closed, opaque booth. In that case B is in light if it's lit from within. */ if(b == a.outermostVisibleParent()) return b.litWithin(); /* Otherwise b is lit for if b is in light. */ else return inLight(b); } /* * 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 we're outside it or it's our enclosing container and * it's lit within, and there's a clear path to it */ return inLightFor(a, 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 = []; /* * Set up a local variable to hold the check message that may result from running the * appropriatte checkReach() method. */ local checkMsg = nil; /* Store a list of the objects (if any) that are blocking a from reaching b. */ local lst = Q.reachBlocker(a, b); /* Set up a local variable that will be used to hold various locations. */ local loc; /* Note a's starting location. */ local startLoc = a.location; 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); } /* * If running checkReach() methods has resulted in any issues, stop here and return a list * of the issues. */ if(issues.length > 0) return issues; /* * 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. */ loc = a.location; if(loc != a.getOutermostRoom && !b.isOrIsIn(loc)) { /* * If we don't allow reaching out of a's location to touch b, then add a new * ReachProblemReachOut to our list of problems, so that when its check method is run * we can attempt to take the actor (a) out their location. */ if(!loc.allowReachOut(b)) issues += new ReachProblemReachOut(b); /* * If the common containing parent of a and b is the location of b, then having left * the location of a we'll have got where we need to go, so we can just return the * list of issuess we've accumulated so far. */ loc = a.commonContainingParent(b); if(b.location == loc) return issues; /* * If we reach here, a should be moved to the common containaing parent, which should * then be the starting location for moving inwards to the location of b. */ startLoc = loc; } /* * Next check whether the actor is not in a nested room that contains * the object the actor is attempting to reach, and if said nested room does not * allow reaching in. */ if(b.location.isIn(startLoc)) // change a.location to startLoc { /* * Set up a list to contain the list of nested rooms the actor needs to enter to reach * the target object, starting at the outermost and progressing inwards. */ local locList = []; /* Note the location of the target object we're trying to reach. */ loc = b.location; /* * Loop outwards from the target object's location to the actor's location to * construct the list of locations the actor needs to pass through to reach the * target. */ while(loc != startLoc && loc != a.getOutermostRoom) { /* * prepend this location to our list so that our list will end up working from * outermost to innermost. */ locList = locList.prepend(loc); /* * Then set loc to the next location working outwards towards the actor's * location. */ loc = loc.location; } /* * Loop through the locations in the list we've just constructed working inwards trom * the actor's location. */ foreach(loc in locList) { /* * If loc is not the actor's location and it doesn't allow reaching in and it's a * nested room (enterable or boardable) then create a new ReachProblemReachIn * object corresponding to loc to add to our list of issues. */ if(loc != startLoc && (loc.isEnterable || loc.isBoardable)) { if(!loc.allowReachIn(b)) issues += new ReachProblemReachIn(loc, b); } else if(b.isIn(loc) && lst.indexOf(loc)) issues += new ReachProblemBlocker(b, loc); } } /* 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 it's 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) { if(status_ == nil) status_ = new LookupTable(64, 128); 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() { /* * If the obstructor is closed but openable, there's a possibility that we could remove * the obstruction by opening it, so we'll allow the action to go ahead but give it a * sligjtly lower logical rank to nudge the parser towards choosing a more accessible * object if one is available. */ if(!obstructor_.isOpen && obstructor_.isOpenable) logicalRank(90); /* * Otherwise just rule out that the action for attempting to reach an inaccessible object. */ else inaccessible(reachBlockedMsg); } /* * If we've passed the verify stage the obstructor must be closed and openable, so all we need * to do here is to attampt to open the obstructor and see if that's resolved the problem with * reaching. */ check(allowImplicit) { /* * If the obstructor is closed and we're allowing an implicit action then try opening the * obstructor and return true or nil according to whether we can now reach in through the * obstructor. */ if(!obstructor_.isOpen && allowImplicit && tryImplicitAction(Open, obstructor_)) return obstructor_.canReachIn; /* * Otherwise disspay a message to say we can't reach in and then return nil to fail the * action. */ say(reachBlockedMsg); return nil; } /* * 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; } ; /* * A ReachProblem object that represents the need for the actor to be inside the blocking nested * room (Platform or Booth) to reach the target object. */ class ReachProblemReachIn: ReachProblem construct(block, targ) { /* Note the blocking nested room the actor needs to be able to get into on on. */ blocker = block; /* Note the target object the actor is trying to reach. */ target = targ; } /* The blocking nested room the actor needs to be able to get into or on. */ target = nil /* The target object the actor is trying to reach. */ blocker = nil check(allowImplicit) { /* * first check that the target and/or blocker is directly or indiractly in the actor's * location so that there is a possible path inwards. */ if(!target.isIn(gActor.location)) { say(reachBlockedMsg); //or say(loc.cannotReachInMsg(target, blocker)); return nil; } /* * If the blocking object is neither enterable nor boardable, the actor can't get in/on * it, but if it's closed and openable we can try opening it. We then return whether or * not the actor can reach into the blocking object to see if that's resolved the issue. * Otherwise we display a message saying access isn't possible and return nil to our * caller to signal that we can't touch the target. */ if(!blocker.isEnterable && !blocker.isBoardable) { if(!blocker.isOpen && blocker.isOpenable && allowImplicit && tryImplicitAction(Open, blocker)) return blocker.canReachIn; say(reachBlockedMsg); return nil; } /* Note the actor's immediate location. */ local loc = gActor.location; /* * The action needed by the actor to enter the location we're trying to reach (Board or * Enter, depending whether target is a Platform or a Booth). */ local getInAction; /* * Keep trying to move the actor into the target location until we reach it or * fail to move the actor. */ while(gActor.location != blocker) { /* * 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. */ getInAction = blocker.contType == On ? Board : Enter; /* * If we're allowed to attempt an implicit action, and the actor's * target is happy to allow the actor to enter 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 && blocker.autoGetInToReach && tryImplicitAction(getInAction, blocker)) { /* * If the actor is still in its original location, then we * were unable to move it in, so return nil to signal that * the reachability condition can't be met. */ if(gActor.location == 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.cannotReachInMsg(target, blocker)); return nil; } /* * If we've reached this point we haven't failed either check in this loop, so note * that the actor's new location and then continue round the loop. */ loc = gActor.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 26/02/2025 from adv3Lite version 2.2
Generated on 26/02/2025 from adv3Lite version 2.2