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:

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 gPlayerChar.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. A DSDoor's doorDir() method returns the Direction object corresponding to the door's direction relative to the player character.

Although you can't readily give the two 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 can be done, but needs a bit of caution. The following will cause a run-time error at the start of the game:

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 = (gRoom == room1 ? 'north door' : 'south door')
    lockability = lockableWithKey
    isLocked = true    
    room1 = hall
    room2 = drive
;

The reason being that gPlayerChar may not have been assigned a value when the game starts up, so that when it tries to initialize the frontDoor it runs into a nil object reference (through testing the location of nil). If your game contains an unchanging player character object such as me it would be safer to write name = (me.getOutermostRoom == room1 ? 'north door' : 'south door').

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.