Doors
Standard Doors
A Door is an object that can be attached to the direction property of a Room, and which can be open and closed and optionally locked and unlocked, and which has to be open to allow an actor to pass through it. As in the adv3 library, so also in adv3Lite a physical door is represented by two game objects, each representing one side of the door (in each of the two locations connected by the door). To create a door, then, it is necessary to define a Door object in each location attached to the appropriate direction property; to let the library know that these are two sides of the same door it is further necessary to set the otherSide property of each Door object to point to the other Door object, for example:
hall: Room 'Hall' 'hall' "The front door lies to the west. " west = frontDoor ; + frontDoor: Door 'front door; solid oak' "It's a solid oak front door, strong enough to resist a siege. " otherSide = frontDoorOutside lockability = lockableWithKey isLocked = true ; drive: Room 'Front Drive' 'front drive' "The front drive sweeps round from the northwest and comes to an end just in front of the house, which stands directly to the east. A narrow path runs round the side of the house to the southeast. " east = frontDoorOutside ; + frontDoorOutside: Door 'front door' otherSide = frontDoor lockability = lockableWithKey isLocked = true ;
Note that as in the above example, to make a door lockable we define its lockability property just as we would on any other Thing.
Another point to note is that in order to keep everything in sync we defined the otherSide property on both sides of the Door and isLocked = true on both sides of the Door. While this is good practice, it is not absolutely necessary, however, since if we forget to do it the library will attempt to correct it for us. In particular, if we are compiling for debugging, the library will warn us at the start of the game if it finds any Doors with an otherSide of nil, and whether or not we're compiling for debugging it will attempt to correct mismatched otherSide and isLocked properties (with isLocked = true taking precedence over isLocked = nil). Note, however, that there is no need for the lockability properties of both sides of the same door to be the same. It is quite in order for each side of a door to have a different locking mechanism (e.g. a key on one side and a keypad on the other) or for only one side of the door to be lockable (and hence unlockable). IF you deliberately don't want a door to have another side (for example, because it's a fake door that's never opened, or it's one side of a lift door whose other side may change in the course of the game) you can suppress the warning messages by setting its otherSide property to self instead of nil. In that case the exit leading through the door will be shown in the exit lister as visited, which may or may not be what you want; on the one hand it can betray the fact that the door doesn't go anywhere, while on the other by so doing it may helpfully tell players that they don't need to try to find a way through the door since it can never be opened. You may thus also want to override the destination property of such a door to unknownDest_, which will make it show up in the exit listing as an unvisited exit. You could then subsequently change the destination to getOutermostRoom (which will make it show up as a visited exit) once something happens to tell the player that there's in fact no way through the door.
The library takes care of a few other details for us besides, in particular:
- A Door is defined as being fixed in place and not listed (isFixed = true and isListed = nil) by default, since these settings apply to virtually all Doors.
- A Door is defined as being visible in the dark (i.e. visibleInDark becomes true) if the room on its other side is illuminated (this allows the player character to travel from a dark room to an illuminated room through a Door, so is usually the desired behaviour).
- The makeOpen(stat) and makeLocked(stat) of a Door automatically keep both sides of a door synchronized (i.e. calling these methods on one side of a Door automatically updates the appropriate property — isOpen or isLocked — on both sides of the Door).
- When the player character passes through a door the isDestinationKnown property is set to true on both sides of the door (mainly to allow subsequent pathfinding through the door).
- If an actor tries to pass through a door that is closed, the game will attempt to open the door via an implicit action and will block the travel if the door remains closed because it was locked, unless the door's autoUnlock property is true, in which case attempting to open the door will trigger an attempt to unlock it via an implicit action provided the attempt has a plausible chance of success (either because the door doesn't need a key or because the actor possesses a key that might plausibly unlock the door).
Finally, note that to define the front door in the example above we had to define two objects, frontDoor and frontDoorOutside, each representing one side of the door. This is nearly always the case when using the Door class (unless you're using it to define a dummy door that can never be opened). For doors that are the same both sides you may find it easier to use the DSDoor (double sided door) class desscribed below or the SymDoor class defined in the symconn extension, since this allows you to define a door using only one game object instead of two. To take advantage of this class you would need to explicitly include the symconn extension in your build.
Double Sided Doors
The DSDoor (double sided door) class allows you to define each door as a single object. Where both sides of a door are pretty much the same, this may be easier and simpler than having to define each side of the door as a separater object. The DSDoor class inherits from the Door class and shares many of its properties and methods, apart from the differences noted below.
To create a DSDoor, set its room1 and room2 properties to each of the two rooms the door connects. Do not define its location property (either explicitly or via the Template or with the + notation, the reason being that a DSDoor is a MultiLoc), and do not define its otherSide property. For example, by using a DSDoor in place of two Door objects, we can rewrite the previous example thus:
hall: Room 'Hall' 'hall' "The front door lies to the west. " west = frontDoor ; frontDoor: DSDoor 'front door; solid oak' "It's a solid oak front door, strong enough to resist a siege. " lockability = lockableWithKey isLocked = true room1 = hall room2 = drive ; drive: Room 'Front Drive' 'front drive' "The front drive sweeps round from the northwest and comes to an end just in front of the house, which stands directly to the east. A narrow path runs round the side of the house to the southeast. " east = frontDoor ;
A DSDoor can be given a different description on each side by defining its description property to check where it's being viewed from. For example:
hall: Room 'Hall' 'hall' frontDoor: DSDoor 'front door; solid oak' "It's a solid oak front door, strong enough to resist a siege, <<if gRoom == room2>> and sporting a large brass knocker<<else>> although a series of scratch marks suggest Fido has tried to break out<<end>>." lockability = lockableWithKey isLocked = true room1 = hall room2 = drive ;
Where gRoom is a library-defined macro that expands to gSafePlayerChar.getOutermostRoom(), i.e. the player character's current room.
The above description might suggest there needs to be a knocker object on one side of the door and a scratch mark object on the other side of the door. These could be implemented as Fixtures or Decorations in drive and hall respectively, but it you prefer to keep these objects with the door, you could do this by defing them as components of the door using their isHidden property to hide them from view on the 'wrong' side of the door, for example:
+ knocker: Component 'brass knocker; big large' "The knocker is in the form of a lion's head with a ring through its nose. " isHidden = (gRoom != drive) ;
If you want to include the direction a door leads in its description, you could do so by testing which room the player character is in and using an expression like <<if gRoom == room2 ? 'west' : 'east'>> in your description, or you could instead leverage the DSDoor's doorDir() method to use <<doorDir().name>> to achieve the same result a bit more economically, or dirName to achieve it even more economically stii. A DSDoor's doorDir() method returns the Direction object corresponding to the door's direction relative to the player character, while the dirName property returns the name of that direction as a single-quoted string.
You can also use the methods inRoom1 and inRoom2 as a shorthand way of testing whether the door is being viewed (or traversed) from room1 or room2. Alternatively, the function byRoom([room1Str1, room2Str]) will return room1str if the player character is (or is starting from) room1 and room2Str otherwise.
If the two sides of a DSDoor (or other DS object such as DSPassage) have substantially different descriptions, these can be defined used the room1desc and room2desc properties (instead of defining the desc property - don't try defining this as well). These should be defined as double-quoted strings or methods that output the relevant descriptions.
In addition, on a DSDoor you can define room1Lockability and room2Lockability properties to make the lockability work in different ways on each side of the door (although if you want the same lockability on both sides of the door you can just override its lockability property). The one thing you can't do is define different keys to work on the different sides of the door. If you really wanted to do that you'd be better off using the regular Door class to define the door as two different objects.
Although it's a little fiddly to give the different sides of a DSDoor different vocab (to allow the player to refer to them with different words), the DSDoor class automatically takes care of the case where the player refers to a door by its direction, e.g. X EAST DOOR or OPEN NW DOOR. There is no need to add such direction terms to the definition of the DSDoor (indeed, you should not do so), since the parser will automatically recognize the direction in which a DSDoor lies relative to the player character as referring to that DSDoor. If you don't want that behaviour on a particular DSDoor, simply override its attachedDir property to nil.
If you want to DSDoor to be called 'north door' on one side and 'south door' on the other, this is probably most easily done using the byRoom() method to define its name:
frontDoor: DSDoor 'door; solid oak' "It's a solid oak front door, strong enough to resist a siege, <<if gRoom == room2>> and sporting a large brass knocker<<else>> although a series of scratch marks suggest Fido has tried to break out<<end>>." name = byRoom(['north door', 'south door']) lockability = lockableWithKey isLocked = true room1 = hall room2 = drive ;
If the front door were blue on one side and red on the other, you could change both its name and its vocab according to which room the player character was viewing it from. The best way to achieve the latter would probably be to define a custom State object applying just to that one object and switching adjectives according to the value of the door's inRoom1 property:
frontDoor: DSDoor 'door; solid oak' "It's a solid oak front door, strong enough to resist a siege, <<if inRoom1>> and sporting a large brass knocker<<else>> although a series of scratch marks suggest Fido has tried to break out<<end>>." name = byRoom(['red door', 'blue door']) lockability = lockableWithKey isLocked = true room1 = hall room2 = drive ; frontDoorState: State appliesTo(obj) { return obj == frontDoor; } stateProp = &inRoom1 adjectives = [[true, ['red']], [nil, ['blue']]] ;
The frontDoorState would then add the adjective 'red' or 'blue' to the front door's vocab according to whether the player character was in room1 (the hall) or room2 (the drive).
Anything more complicated than these cases is probably best handled by implementing your door with two Door objects to represent the two different sides. The other possibility is to include the symconn extension in your game and use the SymDoor class.