#charset "us-ascii"
#include "advlite.h"
/*
* ***************************************************************************
* extras.t
*
* This module forms part of the adv3Lite library (c) 2012-13 Eric Eve
*
*
*
*
* This file contains a number of (in principle) optional classes that can be
* used in addition to Thing. A game does not need to use any of these classes
* since authors can always just set the appropriate properties on thing, but
* some authors may find them a convenience, or may find that using them
* improves the clarity of their code.
*/
property tooFarAwayToHearMsg;
property tooFarAwayToSmellMsg;
property smellSize;
property soundSize;
/*
* We define SensoryEmanation to be the base class for Odor and Noise. It
* doesn't add any functionality here, but makes it possible for the sensory.t
* extension to add functionality to this class.
*/
class SensoryEmanation: Decoration
/*
* There's no point in including a SensoryEmanation in any command applying to ALL unless the
* command is one of our decoration actions.
*/
hideFromAll(action) { return decorationActions.indexOf(action) == nil; }
;
/*
* An Odor is an object representing a smell (as opposed to the object that
* might be emitting that smell). The desc property of an Odor is displayed in
* response to EXAMINE or SMELL; any other action is responded to with the
* notImportantMsg.
*/
class Odor: SensoryEmanation
/* An Odor responds to EXAMINE SOMETHING or SMELL SOMETHING */
decorationActions = [Examine, SmellSomething]
/*
* The message to display when any other action is attempted with an Odor
*/
notImportantMsg = BMsg(only smell, '{I} {can\'t} do that to a smell. ')
/* Treat Smelling an Odor as equivalent to Examining it. */
dobjFor(SmellSomething) asDobjFor(Examine)
dobjFor(Examine) { preCond = [objSmellable] }
/*
* Since we turn SMELL into EXAMINE we want our sightSize to be our
* smellSize.
*/
sightSize = smellSize
/*
* For the same reason we want to use our tooFarAwayToSmellMsg for our
* tooFarWayToSeeDetailMsg.
*/
tooFarAwayToSeeDetailMsg = tooFarAwayToSmellMsg
isOdor = true
;
/*
* A Noise is an object representing a sound (as opposed to the object that
* might be emitting that sound). The desc property of a Noise is displayed in
* response to EXAMINE or LISTEN TO; any other action is responded to with the
* notImportantMsg.
*/
class Noise: SensoryEmanation
/* A Noise responds to EXAMINE SOMETHING or LISTEN TO SOMETHING */
decorationActions = [Examine, ListenTo]
/*
* The message to display when any other action is attempted with a Noise
*/
notImportantMsg = BMsg(only listen, '{I} {can\'t} do that to a sound. ')
/* Treat Listening to a Noise as equivalent to Examining it. */
dobjFor(ListenTo) asDobjFor(Examine)
dobjFor(Examine) { preCond = [objAudible] }
/*
* Since we turn LISTEN TO into EXAMINE we want our sightSize to be our
* soundSize.
*/
sightSize = soundSize
/*
* For the same reason we want to use our tooFarAwayToHearlMsg for our
* tooFarWayToSeeDetailMsg.
*/
tooFarAwayToSeeDetailMsg = tooFarAwayToHearMsg
isNoise = true
;
/* A Container is a Thing that other things can be put inside */
class Container: Thing
/* Containers are open by default */
isOpen = true
/*
* The containment type of a container is In; i.e. things located in a
* Container are considered to be in it.
*/
contType = In
;
/* An OpenableContainer is a Container that can be opened and closed. */
class OpenableContainer: Container
/* Most OpenableContainers start out closed. */
isOpen = nil
/* An OpenableContainer is openable */
isOpenable = true
;
/*
* A LockableContainer is an OpenableContainer that can be locked and unlocked
* without the aid of a key.
*/
class LockableContainer: OpenableContainer
lockability = lockableWithoutKey
/* We usually want a LockableContainer to start out locked. */
isLocked = true
;
/*
* A KeyedContainer is an OpenableContainer that can be locked and unlocked
* only with the aid of a key.
*/
class KeyedContainer: OpenableContainer
lockability = lockableWithKey
/* We usually want a KeyedContainer to start out locked. */
isLocked = true
;
/* A Surface is a Thing that other things can be placed on top of */
class Surface: Thing
/* The contents of a Surface are considered to be on the Surface */
contType = On
/*
* Searching a Surface may involve look for what's on it as well as what's
* in it.
*
* We put this code here rather than checking for contType == On on a
* Thing to avoid burdening Thing with overcomplicated code for a rather
* special case.
*/
dobjFor(Search)
{
preCond = [objVisible, touchObj]
verify() {}
check() {}
action()
{
if(hiddenIn.length == 0
&& contents.countWhich({x: x.searchListed}) == 0)
{
say(nothingOnMsg);
return;
}
local onList = contents.subset({x: x.searchListed});
if(onList.length > 0)
listContentsOn(onList);
if(hiddenIn.length > 0)
findHidden(&hiddenIn, In);
}
}
nothingOnMsg = BMsg(nothing on, '{I} {find} nothing of interest on
{the dobj}. ')
/*
* List what's on me. We put this in a separate method to make it easier
* to customise on a per object basis.
*/
listContentsOn(lst)
{
lookInLister.show(lst, self, true);
}
;
/*
* A Plaftorm is a Surface that the player character (and other actors) can
* get on.
*/
class Platform: Surface
isBoardable = true
;
/*
* A Booth is a Container that the player character (and other actors) can get
* in.
*/
class Booth: Container
isEnterable= true
;
/* An Underside is a Thing that other things can be put under */
class Underside: Thing
/* The contents of an Underside are considered to be under it. */
contType = Under
;
/* An RearContainer is a Thing that other things can be put behind */
class RearContainer: Thing
/* The contents of an RearContainer are considered to be behind it. */
contType = Behind
;
/* A Wearable is a Thing that can be worn */
class Wearable: Thing
isWearable = true
;
/* A Food is a Thing that can be eaten */
class Food: Thing
isEdible = true
;
/*
* A Fixture is a Thing that can't be picked up and carried around (and so
* can't be put anywhere either).
*/
class Fixture: Thing
isFixed = true
;
/*
* A Decoration is a Fixture that can only be EXAMINEd. Any other action
* results in the display of its notImportantMsg. It's normally used for
* objects that are purely scenery.
*/
class Decoration: Fixture
isDecoration = true
/*
* Game code may wish to hide decorations from all commands applied to ALL. Tbis can be
* achieved by overriding hideFromAll() as shown below. This is not done in the library since
* making this change at version 1.61 would compromise backward compatibility.
*/
// hideFromAll(action) { return true; }
;
/*
* A Component is an object that's (usually permanently) part of something
* else, like the handle of a suitcase or a dial on a safe.
*/
class Component: Fixture
cannotTakeMsg = BMsg(cannot take component, '{I} {can\'t} have {that cobj},
{he dobj}{\'s} part of {1}. ', location.theName)
locType = PartOf
;
/*
* A Distant is a Decoration that's considered too far away to be interacted
* with by any command other than EXAMINE
*/
class Distant: ProxyDest, Decoration
/* Message to say that this object is too far away. */
notImportantMsg = BMsg(distant, '{The subj cobj} {is} too far away. ')
/*
* The base Decoration class includes GoTo as a decorationAction. For a
* Distant object (e.g. the sun or moon or a distanct mountain) this will
* often be inappropriate. Where an object is 'locally distant' as it
* were, i.e. a sign mounted high up on a wall, you can override the
* destination property to give the location that a GO TO action should
* take the player character to; decorationActions will then include GoTo,
* since GO TO SIGN would then be a reasonable way for the player to get
* to the location of the sign.
*/
decorationActions = destination ? [Examine, GoTo] : [Examine]
/*
* If this Distant represents an object that's notionally in another
* location, setting destination to that location will allow the GO TO
* command to take the actor to that location. If destination is specified
* it should normally be a room (usually one adjacent to that this Distant
* object is located in. For an object like the sky, the sun or a distant
* mountain this should be left as nil.
*/
destination = nil
/*
* Going To a Distant object is unlike a normal GoTo, since if the object
* is meant to be Distant, going to its location (the normal response to a
* GO TO command) is precisely what we don't want to happen. We therefore
* need to provide custom GoTo handling on a Distant object.
*/
dobjFor(GoTo)
{
verify()
{
/*
* Unless the actor is in our location and we have a destination,
* we don't want to field a GoTo command, so we make it as
* illogical as possible. If we don't have a destination we
* shouldn't reach this point anyway since GoTo shouldn't be one
* of our decorationActions, but writing the test this way will
* make this verify routine prevent the action even if
* decorationActions is overridden, if there's a nil destination.
*/
if(!gActor.isIn(getOutermostRoom) || destination == nil)
inaccessible(notImportantMsg);
}
action()
{
/*
* Calculate the route from the actor's current room to the
* location where the target object was last seen, using the
* routeFinder to carry out the calculations if it is present. In
* this case we use routeFinder rather than pcRouteFinder since
* the PC can presumably see the way s/he needs to go even if s/he
* hasn't gone that way before.
*/
local route = defined(routeFinder)? routeFinder.findPath(
gActor.getOutermostRoom, destination.getOutermostRoom) : nil;
/*
* If we don't find a route, just display a message saying we
* don't know how to get to our destination.
*/
if(route == nil)
sayDontKnowHowToGetThere();
/*
* If the route we find has only one element in its list, that
* means that we're where we last saw the target but it's no
* longer there, so we don't know where it's gone. In which case
* we display a message saying we don't know how to reach our
* target.
*/
else if(route.length == 1)
sayDontKnowHowToReach();
/*
* If the route we found has at least two elements, then use the
* first element of the second element as the direction in which
* we need to travel, and use the Continue action to take a step
* in that direction.
*/
else
{
local dir = route[2][1];
Continue.takeStep(dir, destination.getOutermostRoom);
};
}
}
;
/* An Unthing is an object that represents the absence of a thing */
class Unthing: Decoration
/* An Unthing can't respond to any actions, as it isn't there */
decorationActions = []
/*
* The message to display when the player character tries to interact with
* this object; by default we just say it isn't there, but game code will
* normally want to override this message to explain the reason for the
* absence.
*/
notImportantMsg = BMsg(unthing absent, '{The subj cobj} {isn\'t} {here}. ')
/*
* Users coming from adv3 may be used to Unthings having a notHereMsg, so
* we'll define this to be the same as the notImportantMsg
*/
notHereMsg = notImportantMsg
/* An Unthing should never be included in ALL */
hideFromAll(action) { return true; }
/*
* A player is more likely to be trying to refer to something that is
* present than something that isn't, so we give Unthings a substantially
* reduced vocabLikelihood
*/
vocabLikelihood = -100
/*
* If there's anything else in the match list, remove myself from the
* matches
*/
filterResolveList(np, cmd, mode)
{
if(np.matches.length > 1)
np.matches = np.matches.subset({m: m.obj != self});
}
/* Make Unthings verify with the lowest possible score */
dobjFor(Default)
{
verify()
{
inaccessible(notHereMsg);
}
}
iobjFor(Default)
{
verify()
{
inaccessible(notHereMsg);
}
}
/*
* Giving an order to an Unthing should give the same response as any
* other command involving an Unthing.
*/
handleCommand(action)
{
say(notHereMsg);
}
;
/*
* A CollectiveGroup is a an object that can represent a set of other objects
* for particular actions. For any of the objects in the myObjects list the
* CollectiveGroup will handle any of the actions in the myActions list; all
* the other actions will be handled by the individual objects.
*/
class CollectiveGroup: Fixture
/*
* The list of actions this CollectiveGroup will handle; all the rest will
* be handled by the individual objects.
*/
collectiveActions = [Examine]
/*
* Is action to be treated as a collective action by this group (i.e.
* handled by this CollectiveGroup object); by default it is if it's one
* of the actions listed in our collectiveActions property.
*/
isCollectiveAction(action)
{
return collectiveActions.indexWhich({a: action.ofKind(a)}) != nil;
}
/*
* If the current action is one of the collective Actions, then filter all
* myObjects from the resolve list; otherwise filter myself out from the
* resolve list.
*/
filterResolveList(np, cmd, mode)
{
/* If there are fewer than two matches, don't do any filtering */
if(np.matches.length < 2)
return;
if(isCollectiveAction(cmd.action))
np.matches = np.matches.subset(
{m: valToList(m.obj.collectiveGroups).indexOf(self) == nil});
else
np.matches = np.matches.subset({m: m.obj != self });
}
/* Obtain the list of the objects belonging to me that are in scope. */
myScopeObjects()
{
return Q.scopeList(gActor).toList.subset(
{o: valToList(o.collectiveGroups).indexOf(self) != nil});
}
/*
* The default descriptions of a CollectiveGroup: By default we just list
* those of our members that are in scope.
*/
desc()
{
/*
* Get a list of all the objects in scope that belong to this
* Collective Group.
*/
local lst = myScopeObjects();
/*
* Obtain the sublist of these that are currently held by the player
* character
*/
local heldLst = lst.subset({o: o.isIn(gPlayerChar)});
/*
* Subtract the list of held items from the list of in-scope items to
* obtain a list of other items in scope.
*/
lst -= heldLst;
/* If none of our items are in scope, say so */
if(nilToList(lst).length == 0 && nilToList(heldLst).length == 0)
DMsg(collective group empty, 'There{\'s} no {1} {here}. ', name);
/*
* Otherwise display lists of which of my members the player character
* is carrying and which others are also present.
*/
else
{
if(heldLst.length > 0)
DMsg(carrying collective group, '{I} {am} carrying {1}. ',
makeListStr(heldLst));
if(lst.length > 0)
DMsg(collective group members, 'There{\'s} {1} {here}. ',
makeListStr(lst));
}
}
/* A CollectiveGroup isn't normally listed as an item in its own right. */
isListed = nil
;
/*
* A Heavy is a Fixture that's too heavy to be picked up. We make Heavy a
* Fixture rather than an Immovable since it should normally be obvious that
* something is too heavy to be moved.
*/
class Heavy: Fixture
cannotTakeMsg = BMsg(too heavy, '{The subj cobj} {is} too heavy to move. ')
;
/*
* An Immovable is something that can't be picked up, although it may not be
* immediately obvious that it can't be moved. For that reason we rule out
* taking an Immovable at the check stage rather than the verify stage (this
* is what distinquished an Immovable from a Fixture).
*/
class Immovable: Thing
/* Respond to an attempt to TAKE by ruling it out at the check stage. */
dobjFor(Take)
{
check() { say(cannotTakeMsg); }
}
/* The message to display to explain why this object can't be taken. */
cannotTakeMsg = BMsg(cannot take immovable, '{I} {cannot} take {the cobj}.
')
/*
* Although an Immovable can't be picked up it's possible that it could be moved around, e.g.,
* by pushing or pulling it, and this must be the case if it can PushTravel and/or PullTravel.
*/
isMoveable = (canPushTravel || canPullTravel)
;
/*
* A StairwayUp is Thing the player character can climb up. It might represent
* an upward staircase, but it could also represent a tree, mast or hillside,
* for example. A StairwayUp is also a TravelConnector so it can be defined on
* the appropriate direction property of its enclosing room.
*/
class StairwayUp: TravelConnector, Thing
/* Climbing a StairwayUp is equivalent to travelling via it. */
dobjFor(Climb)
{
action() { travelVia(gActor); }
}
/* Climbing up a Stairway up is the same as Climbing it. */
dobjFor(ClimbUp) asDobjFor(Climb)
/* A StairwayUp is usually something fixed in place. */
isFixed = true
/* A StairwayUp is climbable */
isClimbable = true
/*
* The appropriate PushTravelAction for pushing something something up a
* StairwayUp.
*/
PushTravelVia = PushTravelClimbUp
/*
* Display message announcing that traveler (typically an NPC whose
* departure is witnessed by the player character) has left via this
* staircase.
*/
sayDeparting(traveler)
{
gMessageParams(traveler);
DMsg(say departing up stairs, '{The subj traveler} {goes} up
{1}. ', theName);
}
/*
* Display message announcing that follower is following leader up
* this staircase.
*/
sayActorFollowing(follower, leader)
{
/* Create message parameter substitutions for the follower and leader */
gMessageParams(follower, leader);
DMsg(say following up staircase, '{The subj follower} follow{s/ed} {the
leader} up {1}. ', theName);
}
traversalMsg = BMsg(traverse stairway up, 'up {1}', theName)
;
/*
* A StairwayDown is Thing the player character can climb down. It might
* represent an downward staircase, but it could also represent a tree, mast
* or hillside, for example. A StairwayDown is also a TravelConnector so it
* can be defined on the appropriate direction property of its enclosing room.
*/
class StairwayDown: TravelConnector, Thing
/* Climbing down a StairwayDown is equivalent to travelling via it. */
dobjFor(ClimbDown)
{
action { travelVia(gActor); }
}
/* A StairwayDown is usually something fixed in place. */
isFixed = true
/* A StairwayDown is something one can climb down */
canClimbDownMe = true
/*
* The appropriate PushTravelAction for pushing something something down a
* StairwayDown.
*/
PushTravelVia = PushTravelClimbDown
/*
* Display message announcing that traveler (typically an NPC whose
* departure is witnessed by the player character)has left via this
* staircase.
*/
sayDeparting(traveler)
{
gMessageParams(traveler);
DMsg(say departing down stairs, '{The subj traveler} {goes} down
{1}. ', theName);
}
/*
* Display message announcing that follower is following leader down
* this staircase.
*/
sayActorFollowing(follower, leader)
{
/* Create message parameter substitutions for the follower and leader */
gMessageParams(follower, leader);
DMsg(say following down staircase, '{The subj follower} follow{s/ed}
{the leader} down {1}. ', theName);
}
traversalMsg = BMsg(traverse stairway down, 'down {1}', theName)
cannotClimbMsg = BMsg(cannot climb stairway down, '{I} {can\'t} climb {the
dobj}, but {i} could go down {him dobj}. ')
;
/* A double sided (aks two-way) Stairway */
class DSStairway: DSCon, StairwayUp
/*
* The room at the upper end of this staircase. The library will try to determine which end of
* this stairway is the upper and end which the lower according to whether it's room1 or room2
* that points to us on its up or down property, so game code need not specify the upper or
* lower end unless neither room at the ends of this stairway point to us via up or down,
* either directly or via an asExit() macro. If the ends need to be specified manually, there
* is no need to specify both, since whiohever end is explicitly defined will implicitly
* define what the other end is.
*/
upperEnd = nil
/* The room at the lower end of this staircase */
lowerEnd = nil
/*
* initialise this SymStairway by first carrying out the inherited initialization and then
* trying to determine which end of the stairway is the upperEnd and which the lowerEnd.
*/
preinitThing
{
/* Carry out the inherited handling. */
inherited();
/* Attempt to determine which end of the Stairway is which. */
findUpperAndLowerEnds();
}
/*
* Method which attempts to determine which end of the staircase is the upper and which the
* lower based on the up and/or down properties defined in room1 and room2.
*/
findUpperAndLowerEnds()
{
/*
* If the lower end is not yet defined and room1 points to us on its up property, then our
* lower end must be room1.
*/
if(lowerEnd == nil && room1 && room1.getConnector(&up) == self)
lowerEnd = room1;
/*
* If the upper end is not yet defined and room1 points to us on its down property, then
* our upper end must be room 1.
*/
if(upperEnd == nil && room1 && room1.getConnector(&down) == self)
upperEnd = room1;
/*
* If the lower end is not yet defined and room2 points to us on its up property, then our
* lower end must be room 2.
*/
if(lowerEnd == nil && room2 && room2.getConnector(&up) == self)
lowerEnd = room2;
/*
* If the upper end is not yet defined and room2 points to us on its down property, then
* our upper end must be room 2.
*/
if(upperEnd == nil && room2 && room2.getConnector(&down) == self)
upperEnd = room2;
/*
* If the upper end is not yet defined but the lower end is, then our upper end must be
* whichever room isn't the lower end.
*/
if(upperEnd == nil && lowerEnd)
upperEnd = lowerEnd == room1 ? room2 : room1;
/*
* If the lower end is not yet defined but the upper end is, then our lower end must be
* whichever room isn't the upper end.
*/
if(lowerEnd == nil && upperEnd)
lowerEnd = upperEnd == room1 ? room2 : room1;
}
/*
* The appropriate PushTravelAction for pushing something something up or down a
* SymStairway.
*/
PushTravelVia = location.isOrIsIn(lowerEnd) ? PushTravelClimbUp : PushTravelClimbDown
/*
* Display message announcing that traveler (typically an NPC whose
* departure is witnessed by the player character) has left via this
* staircase.
*/
sayDeparting(traveler)
{
if(location.isOrIsIn(lowerEnd))
inherited(traveler);
else
delegated StairwayDown(traveler);
}
/*
* Display message announcing that follower is following leader up
* this staircase.
*/
sayActorFollowing(follower, leader)
{
/* Create message parameter substitutions for the follower and leader */
if(location.isOrIsIn(lowerEnd))
delegated StairwayUp(follower, leader);
else
delegated StairwayDown(follower, leader);
}
/* The message for traversing this stairway - we delegate to Thing's message. */
traversalMsg = location.isOrIsIn(lowerEnd) ? delegated StairwayUp : delegated StairwayDown
/* a trio of short service methods to provide convenient abbreviations in game code */
/* Is the player character in our upper end room? */
inUpper = (upperEnd && gPlayerChar.isIn(upperEnd))
/* Is the player character in our lower end room? */
inLower = (lowerEnd && gPlayerChar.isIn(lowerEnd))
/*
* Return a or b depending on whether or not the player character is in our upperEnd room.
* This is primarily intended to ease the writing of descriptions or travelDescs which vary
* slightly according to which end we're at, e.g. "The stairs lead steeply <<byUpLo('down',
* 'up')>> "
*/
byEnd(arg) { return inUpper ? arg[1] : arg[2]; }
/*
* Retuen 'down' or 'up' depending on whether we're at the upper or lower end of the stairway.
*/
upOrDown = inUpper ? downDir.name : upDir.name
dobjFor(Climb)
{
verify()
{
/* You can't climb up from the upper end of a staircase. */
if(gActor.isIn(upperEnd))
illogicalNow(cannotClimbMsg);
}
}
cannotClimbMsg = BMsg(cant climb from here, '{I} {can\'t} climb up {the dobj} from {here}. ')
dobjFor(ClimbDown)
{
verify()
{
/* You can't climb up from the upper end of a staircase. */
if(gActor.isIn(lowerEnd))
illogicalNow(cannotClimbDownMsg);
}
action()
{
delegated StairwayDown();
}
report()
{
delegated StairwayDown();
}
}
cannotClimbDownMsg = BMsg(cant climb doen from here,
'{I} {can\'t} climb down {the dobj} from {here}. ')
iobjFor(PushTravelClimbUp)
{
verify()
{
inherited();
if(gActor.isIn(upperEnd))
illogicalNow(cannotPushClimbMsg);
}
}
iobjFor(PushTravelClimbDown)
{
verify()
{
inherited();
if(gActor.isIn(lowerEnd))
illogicalNow(cannotPushClimbDownMsg);
}
}
cannotPushClimbMsg = BMsg(cannot push climb here, '{I} {can\'t} ascend {the iobj} from {here}.
')
cannotPushClimbDownMsg = BMsg(cannot push climb down here, '{I} {can\'t} descend {the iobj} from {here}.
')
;
/* A Passage represents a physical object an actor can travel through, like a
passage or portal. A Passage is also a TravelConnector so it
* can be defined on the appropriate direction property of its enclosing room.
*/
class Passage: TravelConnector, Thing
/* Going through a Passage is equivalent to travelling via it. */
dobjFor(GoThrough)
{
preCond = [travelPermitted, touchObj]
action() { travelVia(gActor); }
}
iobjFor(PushTravelThrough)
{
preCond = [travelPermitted, touchObj]
}
/* Entering a Passage is the same as going through it */
dobjFor(Enter) asDobjFor(GoThrough)
/* Going along a Passage is the same as going through it */
dobjFor(GoAlong) asDobjFor(GoThrough)
/* A Passage is usually something fixed in place. */
isFixed = true
/* A Passage is something one can go through */
canGoThroughMe = true
/*
* The appropriate PushTravelAction for pushing something something
* through a Passage.
*/
PushTravelVia = PushTravelThrough
;
/*
* A PathPassage is a Passage that represents a path, so that following it or
* going down it is equivalent to going through it.
*/
class PathPassage: Passage
/* Make following a path the same as going through it. */
dobjFor(Follow) asDobjFor(GoThrough)
/* Make going down a path the same as going through it. */
dobjFor(ClimbDown) asDobjFor(GoThrough)
/* Make going up a path the same as going through it. */
dobjFor(ClimbUp) asDobjFor(GoThrough)
/* One most naturally talks of going 'down' a path */
traversalMsg = BMsg(traverse path passage, 'down {1}', theName)
;
/* A double sided (i.e., two-way) Passage. */
class DSPassage: DSCon, Passage
;
/* A double sided (.e., two-way) PathPassage. */
class DSPathPassage: DSCon, PathPassage
;
/*
* A double-sided (or two-way) TravelConnector. The room1 and room2 properties must be defined to
* stipulate the two rooms the TravelConnector connects.
*/
class DSTravelConnector: DSBase, TravelConnector
;
/*
* ProxyDest is a mix-in class for use with an object that represents the
* exterior of a room, such as a hut in a field. Its purpose is to remove such
* an object as the possible target of a GO TO command provided there's
* another alternative. This prevents a GO TO command from taking the player
* character from the appropriate room to an inappropriate destination.
*/
class ProxyDest: object
filterResolveList(np, cmd, mode)
{
/* Carry out the inherited handling. */
inherited(np, cmd, mode);
/*
* If we're the potential target of a GO TO command and there are
* other possible matches, remove ourselves from the list of matches.
*/
if(cmd.action == GoTo && np.matches.length > 1)
np.matches = np.matches.subset({m: m.obj != self});
}
;
/*
* An Enterable is a Thing one can go inside. It is usually used to represent
* the exterior of an object like a building, so that going inside will take
* the actor to a new location representing the interior.
*/
class Enterable: ProxyDest, Fixture
/* To enter an Enterable the actor must travel via its connector. */
dobjFor(Enter)
{
verify() {}
action() { connector.travelVia(gActor); }
}
/* The handling of an attempt to push something inside this object */
iobjFor(PushTravelEnter)
{
/*
* To push something inside this object we must be able to touch this
* object.
*/
preCond = [touchObj]
/* Check whether our connector allows push travel through it */
check() { connector.checkPushTravel(); }
/* Carry out the attempt to push something inside us. */
action()
{
/*
* If our connector defines a PushTravelVia action, use that
* action to push the direct object via our connector. The use of
* replaceAction means that the handling will end there.
*/
if(connector.PushTravelVia)
replaceAction(connector.PushTravelVia, gDobj, connector);
/* Otherwise, make our direct object travel vis our connector */
connector.travelVia(gDobj);
/*
* If the push travel attempt was successful, which we assume it
* was if the direct object ended up in our connector's
* destination, display a message to say the push travel succeeded
* and make the actor travel via our connector.
*/
if(gDobj.isIn(connector.destination))
{
say(okayPushIntoMsg);
connector.travelVia(gActor);
}
}
}
/*
* Our connector is the TravelConnector via which an actor travels on
* entering this object. This may be a Room, or some other TravelConnector
* object such as a Door on our outside through which the actor must pass
* to get inside.
*
* We set connector = destination since on occasion it may seem more
* natural to authors to set the destination property (when it leads
* straight to a room) and sometimes more natural to set the connector
* property (when it's a door, say). With connector = (destination) by
* default, it should work either way.
*/
connector = (destination)
/*
* If entering this object leads straight to another room, it may seem
* more natural to define a destination property; provided we don't
* override connector as well, this has the same effect as pointing
* connector directly to the room.
*/
destination = nil
/* An Enterable is usually enterable */
isEnterable = true
;
/*
* A SecretDoor is a Door that doesn't appear to be a door when it's closed,
* and which must be opened by some external mechanism (other than by an OPEN
* command)
*/
class SecretDoor: Door
/* You can only go through a SecretDoor when it's open */
canGoThroughMe = isOpen
/* A SecretDoor only functions as a TravelConnector when it's open */
isConnectorApparent = isOpen
/*
* We can't use an OPEN command to open a SecretDoor, but by default we'll
* allow a CLOSE command to close it. To disallow this override isOpenable
* to nil
*/
isOpenable = isOpen
/*
* The vocab string (including the name) that applies to this SecretDoor
* when it's open. This might be somewhat different from that which
* applies when it's closed. For example, opening a bookcase might turn it
* into a passage or an opening. If you don't want to the vocab to change
* when this SecretDoor is opened and closed, leave vocabWhenOpen as nil.
*/
vocabWhenOpen = nil
/*
* The vocab string (including the name) that applies to this SecretDoor
* when it's closed. If the SecretDoor starts out closed there's no need
* to define this explicitly as it will be copied from the vocab property
* at preInit.
*
* To define a SecretDoor that's effectively invisible when closed, give
* it a vocab property comprising an empty string (i.e. '', not nil); this
* will make it impossible for the player to refer to it when it's closed.
*/
vocabWhenClosed = nil
/* Preinitialize a SecretDoor */
preinitThing()
{
/* Carry out the inherited handling */
inherited();
/*
* If the door starts out open, copy its initial vocab to its
* vocabWhenOpen property.
*/
if(isOpen)
vocabWhenOpen = vocab;
/*
* If the door starts out closed, copy its initial vocab to its
* vocabWhenClosed property.
*/
else
vocabWhenClosed = vocab;
}
/* Carry out opening or closing a SecretDoor */
makeOpen(stat)
{
/* Perform the inherited handling */
inherited(stat);
/*
* If we're opening the SecretDoor and it has a non-nil vocabWhenOpen
* property and its vocabWhenOpen property is different from its
* current vocab, then reinitialize its vocab from the vocabWhenOpen
* property.
*/
if(stat && vocabWhenOpen && vocab != vocabWhenOpen)
replaceVocab(vocabWhenOpen);
/*
* If we're closing the SecretDoor and it has a non-nil
* vocabWhenClosed property and its vocabWhenClosed property is
* different from its current vocab, then reinitialize its vocab from
* the vocabWhenClosed property.
*/
if(!stat && vocabWhenClosed && vocab != vocabWhenClosed)
replaceVocab(vocabWhenClosed);
}
;
/* A Switch is a Thing than can be turned on and off */
class Switch: Thing
/* A Switch is switchable */
isSwitchable = true
/* FLIP SWITCH is equivalent to SWITCH SWITCH */
dobjFor(Flip) asDobjFor(SwitchVague)
;
/*
* A Flashlight is a light source that can be turned on and off; in other
* words it's a Switch that's lit when on.
*/
class Flashlight: Switch
/* Turning a Flashlight on and off makes it lit or unlit */
makeOn(stat)
{
inherited(stat);
makeLit(stat);
}
/* Lighting a Flashlight is equivalent to switching it on */
dobjFor(Light) asDobjFor(SwitchOn)
/* Extinguishing a Flashlight is equivalent to switching it off */
dobjFor(Extinguish) asDobjFor(SwitchOff)
;
/*
* A ContainerDoor can be used as part of a multiply-containing object to
* represent the door of the container-like object defined on its remapIn
* property. A ContainerDoor is open or closed if the underlying container is
* open or closed, and remaps all container-appropriate actions to the remapIn
* object of its location.
*/
class ContainerDoor: Fixture
/* We're open if our location's container is open */
isOpen = (location.remapIn.isOpen)
/*
* Redirect all container-appropriate actions to the remapIn object of our
* location, so that opening, closing, locking and unlocking this door
* will perform the equivalent action on our container object.
*/
remapIn = location.remapIn
cannotTakeMsg = BMsg(cannot take container door, '{I} {can\'t} have {the
cobj}; {he dobj}{\'s} part of {1}. ', location.theName)
;
/*
* A MinorItem is an unobtrusive and possibly unimportant portable object that's worth
* implementing in the game but sufficiently minor as to be not worth mentioning (in response to X
* or FOO ALL) unless it's either directly held by the player character or directly in the
* enclosing room. (Based on Joey Cramsey's Trinket class).
*/
class MinorItem: Thing
/*
* We're listed in response to a LOOK command only if we're not fixed in place and we can't be
* ignored for the current action or our current location.
*/
lookListed = (!canBeIgnored(gAction) && !isFixed)
/* We're excluded from a FOO ALL action if we can be ignored for FOO. */
hideFromAll(action)
{
return canBeIgnored(action);
}
/*
* We can be ignored for action unless we're directly in our enclosing room or directly in the
* player character's location or carried by the player character or, if desired, the action
* is TAKEFROM or PutXX
*/
canBeIgnored(action)
{
/*
* If we are exposed, in the middle of the room or the actor's location (e.g. a
* platform or booth), then we cannot be ignored.
*/
if (location is in (getOutermostRoom(), gActor.location))
return nil;
/* Allow actions like DROP ALL if we are being directly carried. */
if (isDirectlyHeldBy(gActor))
return nil;
/* Allow an explicit TAKE FROM or PUT somewhere if our allowTakeFromPut flag allows it. */
if(action is in (TakeFrom, PutIn, PutOn, PutBehind, PutUnder))
return !includeTakeFromPutAll;
// Otherwise, pay us no mind in X ALL.
return true;
}
/*
* Flag: do we want to be included in TAKE ALL FROM X, and PUT ALL IN/ON/UNDER/BEHIND X? by
* default we do.
*/
includeTakeFromPutAll = true
;
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1