# Extras

The adv3Lite library has been devised to minimize the number of classes a game author has to remember, so that a huge number of game objects can simply be defined as belonging to the Thing class. That may suit some game authors but not all. Authors used to the adv3 library may prefer to use the classes they are familiar with from there. Also, while using a greater number of classes means you have to remember more initially, it may make your code more readable in the long term, since assigning an object to an appropriate class may make it more immediately obvious what its function is.

To give game authors the choice of using a larger array of classes if they so wish, the extras.t module defines a number of classes that work more or less the same as the similarly named classes in adv3. Note that the 'shallow' classes add little or no real functionality to the adv3Lite library, however, since in most cases they simply define one or two properties that game authors could equally well define for themselves on Thing. There are, however, one or two classes in the extras.t module that do a little more than that (particularly the TravelConnector-derived classes), and it will usually prove better to make use of these 'deeper' classes than attempting to roll your own on Thing if you want their full functionality.

## Shallow Equivalents to adv3 Classes

We start with the 'shallow' classes that simply define one or two properties on Thing (well, actually one or two of them do a bit more than that, but this will do as a first approximation):

- Container: Subclass of Thing with contType = In and isOpen = true.
- OpenableContainer: Subclass of Container with isOpen = nil and isOpenable = true.
- LockableContainer: Subclass of OpenableContainer with lockability = lockableWithoutKey.
- KeyedContainer: Subclass of OpenableContainer with lockability = lockableWithKey.
- Surface: Subclass of Thing with contType = On. Surface also defines specialized handling for the Search action, which reports what's ON a Surface (as well as anything in its hiddenIn list), whereas on the Thing class SEARCH simply redirects to LOOK IN.
- Platform: Subclass of Surface with isBoardable = true.
- Booth: Subclass of Container with isEnterable = true.
- Underside: Subclass of Thing with contType = Under.
- RearContainer: Subclass of Thing with contType = Behind.
- Wearable: Subclass of Thing with isWearable = true.
- Food: Subclass of Thing with isEdible = true.
- Fixture: Subclass of Thing with isFixed = true.
- Decoration Subclass of Thing with isDecoration = true. See note below on hideFromAll(action)
- Distant: Subclass of Decoration with a notImportantMsg that says the object is too far away. The Distant class also has a destination property for use with pathfinding.
- Component: Subclass of Fixture with a cannotTakeMsg that says the object can't be taken as it's part of its location.
- Heavy: Subclass of Fixture with a cannotTakeMsg that says the object is too heavy.
- Switch: Subclass of Thing with isSwitchable = true.
- Enterable: Subclass of Fixture that can be used to represent the outside of a building or the like. The connector property can be used to define the connector (typically a Door) that the player character should travel through when the Enterable is entered, or the destination property can be used to define the room the player character is taken to on entering the Enterable.To avoid certain problems with the GO TO pathfinding command, Enterable also descends from the ProxyDest class.

Remember, you can use as many or as few of these classes as you wish. In practice, using these clases probably makes game code more readable, as well as providing the ability to write code that iteratees over objects of a given class or modifies one of these classes to customize it to the requirements of a particular game.

Some game authors may wish to exclude all Decorations from any command applied to ALL (such as X ALL or FEEL ALL), since in a location with several decorations. players may find it needlessly distracting, or at least cluttered, to read several messages along the lines of 'X is not important' in response to such commands. So a commonly useful modification to the Decoration class might be:

    modify Decoration
        hideFromAll(action) { return true; }
    ;

This particular modification may be less useful in games that in any case define gameMain.allVerbsAllowAll = nil, but it illustrates the principle. It might nevertheless have been a good default to define in the library, but changing it in the library now would compromise backward compatibility.

## TravelConnector Classes

The following classes implement one or two TravelConnector objects that are also present as physical objects in the game world:

- StairwayUp: Can be used for anything the player character might climb or climb up, typically a flight of stairs leading up, but could be used for a tree, mast, ladder or hillside.
- StairwayDown Can be used for anything the player character might climb down.
- Passage: Can be used for anything the player character might enter or go through to reach another destination. Amongst other things, defining a Passage by be a useful alternative to defining a Room to implement a passagelike location in which nothing of interest occurs, not least if the passage is described and supplied with a travel message to describe traversing it.
- PathPassage: Like a Passage, except that following it also traverses it.
- ProxyRoom: Like a Passage, but designed to represent a room in a neighbouring location.

These classes work just a little differently from their adv3 equivalents, in that you don't have to use them in pairs. To use one of the above classes, put an instance in the appropriate room, set its destination property to the room you want it to lead to, and set the appropriate direction property of the room to point to it. For example, to implement a flight of stairs leading up from the hall to the landing you might write:

    hall: Room 'Hall'
        "A broad flight of stairs leads up to the landing above. The lounge is to
         the east. "
        up = hallStairs
        east = proxyLounge
    ;

    + hallStairs: StairwayUp 'flight[n] of stairs; broad; steps staircase'
         destination = landing
    ;

    + proxyLounge: ProxyRoom ->lounge;

Note also how we can use the ProxyRoom class here. The hall's room description mentions that the lounge is to the east. This might encourage the player to attempt commands like X LOUNGE or ENTER LOUNGE. Ideally, we might like to avoid the response 'You see no lounge here. ' Adding a ProxyRoom to represent the presence of the neighbouring lounge achieves that. Note that we only need to define its `destination` property, which we can do via the -> element of its template. You can define the `vocab` and `desc` properties of a `ProxyRoom` if you wish, but you often won't need to. A `ProxyRoom` automatically takes its vocab from its destination room (unless it's otherwise defined) and has a default desc of "You can't see much of it from here. "

As an alternative to creating separate named objects for doors, passages and stairways, you might prefer to define then as anonymous nests objects like this:

    hall: Room 'Hall'
        "A broad flight of stairs leads up to the landing above. The lounge is to
         the east. "
        up: StairwayUp 'flight[n] of stairs; broad; steps staircase' -> landing {}
        east = proxyLounge
    ;

This works because the StairwayUp class (along with StairwayDown, Passage, PathPasage and Door), defines `location = lexicalParent`, so there is no need to do this in user code in situations like these where it would otherwise be needed. This automatically gets overridden in separate user-defined objects such as the `hallStairs` example above and in the DS (double-sided) versions we shall go on to look at immediately below (which all inherit from MultiLoc and so use MultiLoc's definition of location.).

Each of the above classes (except ProxyRoom) has a double-sided (or two-way) equivalent to allow the two ends of a passage or stairway to be defined with a single object:

- DSStairway Can be used for anything the player character might climb up from one end down from the other.
- DSPassage: Can be used for anything the player character might enter or go through either end to reach the other side.
- DSPathPassage: Like a DSPassage, except that following it also traverses it.

Objects of all these DS classes can be set up in the aame way as for a DSDoor. That is the locations they connect are defined on their room1 and room2 properties. In addition, the DSStairway class defines the properties upperEnd and lowerEnd (mainly to determine which end the actor can go down from and which up from). The library attempts to determine which end is which by inspecting the up and down properties of room1 and room2. Only if these provide no clue will it be necessary for a game author to specify upperEnd or lowerEnd manually (specifying one automatically specifies the other).

These DS classes also provide most of the other double-sided (two-way) properties and methods available on DSDoor, including inRoom1, inRoom2, byRoom([room1str, room2str]), room1Desc, room2Desc, attachedDir(), doorDir() and dirName, for which see the description of the DSDoor class.

Finally, there is also a DSTravelConnector which can be used to implement a two-way TravelConnector. This must be defined as a separate (i.e. not embedded) object with its room1 and room2 properties set to the two rooms it connects. In addition to all the normal TravelConnector properties and methods it also implements inRoom1, inRoom2, byRoom([room1str, room2str]), attachedDir(), doorDir() and dirName. The name use of this class would mostly likely to be to set up common travel barriers or travel descriptions on a two-way connection, e.g,

    hall: Room 'Hall'
        "There's a very narrow gap to the east."
         east = gapCon
    ;

    gapCon: DSTravelConnector @hall @parlour
       travelDesc = "You just manage to squeeze through the gap. "

       canTravelerPass(actor)
       {
           return actor.getCarriedBulk() < 3;
       }

       explainTravelBarrier()
       {
           "You can't squeeze through carrying all that stuff. ";
       }
    ;

    parlour:Room 'Parlour'
       "The only way out of this tiny parlour appears to be the narrow
        gap to the west. "
        west = gapCon
    ;

## Sensory Emanation Classes

The adv3Lite library defines the classes Noise and Odor to represent a sound and a smell respectively. Users familiar with adv3 should note that these classes are much simpler than the adv3 classes with the same name (and are more like the adv3 SimpleNoise and SimpleOdor classes). They are simply Decoration objects that can be either listened to (for a Noise) or smelled (for an Odor), either of which is treated as the same as examining them. For anything else they respond with 'You can't do that to a noise|smell. ' Since they don't by default define smellDesc or listenDesc they won't normally respond to an intransitive SMELL or LISTEN command. They can, however, be used to provide simple implementations of any smells or sounds whose existence is suggested by smelling or listening to other objects, or by issuing an intransitive SMELL or LISTEN command. For example:

    + cooker: Thing 'cooker;blackened;oven stove top'
        "Normally, you keep it in pretty good shape (or your cleaner does) but right
        now it's looking suspiciously blackened, especially round the top. "

        isFixed = true
        isSwitchable = true
        isOn = true

        smellDesc = "There's a distinct smell of burning from the cooker. "
    ;

    + Odor 'smell of burning; acrid distinct'
        "It smells quite acrid. "
    ;

If you do want a Noise or Odor to respond to an intransitive SMELL or LISTEN command, you can simply define `listenDesc = desc` or `smellDesc = desc` on the Noise or Odor in question, or else modify the Noise and/or Odor classes in the same way if you want all or most of your Noises and Odors to respond to these intransitive commands.

Note finally that SensoryEmanations are hidden from any actions applied to ALL that are not relevant to the SensoryEmanation in question (i.e. EXAMINE and either SMELL or LISTEN TO).

If your game needs a more sophisticated handling of sounds and smells than these rather simple classes offer, you may want to consider including the Sensory extension.

## Other Miscellaneous Classes

The Flashlight is a subclass of Switch that lights up when its switched on, and goes out when its switched off. It can also be switched on and off with LIGHT and EXTINGUISH.

An Immovable is like a Fixture, except that taking it is blocked in check() rather than verify(). What this is means is that although it can't be taken, this doesn't affect the parser's choice of it as the target of a TAKE command. This could be used for anything that looks like it might be possible to take, but turns out not to be takeable (perhaps because it's heavier than it looks, or it's fastened in place but not obviously so). Note that while this means an Immovable can't be taken, it doesn't necessarily mean it can't be moved by other means, such as being pushed around by a PushTravel or MoveTo command. By default an Immovable is regarded as something that can't be moved (isMoveable = nil) unless it can be moved by PushTravel and or PullTravel (although this can, of course, be overridden in game code) since it would seem illogical to allow, e.g., PUSH THROUGH DOOR but not MOVE CRATE TO DOOR.

An Unthing is an object that used to represent the absence of something that a player might assume to be present. The purpose of an Unthing is simply to provide a message explaining why the thing in question isn't there. For example, if the player drops a key down a drain, you could then add an Unthing to the location to remind the player why the key is no longer available:

    unKey: Unthing 'small silver key'
       'Unfortunately, you dropped the silver key down the drain. '
    ;

Note that the second property we're defining with the template here is not the description but the notHereMsg, and that this must be a single-quoted string. Any attempt to perform any action with an Unthing will result in the display of its notHereMsg.

When choosing objects the parser will always prefer any other object to an Unthing. So, in the previous example, if the unKey was in scope at the same time as a large brass key, say, the parser will always choose the large brass key in respond to commands that just refer to a 'key', e.g. X KEY, TAKE KEY or UNLOCK DOOR WITH KEY.

The Unthing class inherits from Decoration. You can make selected actions work with an Unthing by overriding is decorationActions property. E.g. if you'd defined a RETRIEVE command which you wanted to work on the UnKey (to make the player character try to fish the real silver key out of the drain, maybe), you could define your unKey thus:

    unKey: Unthing 'small silver key'
       'Unfortunately, you dropped the silver key down the drain. '

       decorationActions = [Retrieve]
    ;

In this example, we've had to repeat the vocab property that we presumably defined on the original silver key object. If this were a more elaborate vocab property, or we later changed the vocab property of the actual key object, this could be prone to errors. We could instead define the unKey's unObject property to point to the key object the unKey is standing in for. Using the alternative Unthing template we could do this thus:

    unKey: Unthing @silverKey
       'Unfortunately, you dropped the silver key down the drain. '

       decorationActions = [Retrieve]
    ;

This will ensure that unKey has precisely the same name and vocab as the silverKey. Note that if we define the unObject property on an Unthing, we should not define its vocab property.

If an Unthing's unObject property is defined, the library will automatically set up the Unthing and its unObject to be facets of each other (via their respective getFacets properly). This ensures that a pronoun that would have matched one of them will match the other if the unObject goes in and out of scope. For example if the player issues the command DROP KEY with the result that the silver key vanishes down the drain an X IT on the following turn will be directed to the unKey.

Finally, Unthing's checkReach(obj) method is defined to display its notHereMsg. Although this unlikely to make much difference in most games, it will ensure that any call to canReach(unobj), where unobj is an Unthing, will return nil, which is surely the logically correct result. This may be helpful in situations where game code needs to test for reachability, for example when constructing a list of reachable objects.

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 or directly in the actor's location. Its includeTakeFromPutAll (which is true by default) determines whether it will be included in TAKE ALL FROM X or PUT ALL IN/ON/UNDER/BEHIND X when it's not directly held or directly in the enclosing room or actor's location.

A CollectiveGroup can be used to represent a collection of objects for certain actions. It's normally best used as a Fixture representing other Fixtures (see below if you need it to work with mobile objects). To use a CollectiveGroup define an object of the CollectiveGroup class in a particular location, and then the objects the CollectiveGroup represents in the same location, defining the collectiveGroups property of each of those other objects to point to the CollectiveGroup. For example, suppose we have a bank of switches comprising a red switch, a blue switch and a green switch; in outline we might do something like this:

    + switchBank: CollectiveGroup 'switches; of[prep]; row bank; them'
       "The bank comprises a row of three switches: one red, one blue, one green. "
       collectiveActions = [Examine, Take]
    ;

    + redSwitch: Switch 'red switch'
        isFixed = true
        collectiveGroups = [switchBank]
    ;

    + blueSwitch: Switch 'blue switch'
        isFixed = true
        collectiveGroups = [switchBank]
    ;

    + greenSwitch: Switch 'green switch'
        isFixed = true
        collectiveGroups = [switchBank]
    ;

With this in place the command X SWITCHES will give the description of the bank of switches, rather than of each individual switch, and TAKE SWITCHES will yield the message "The switches are fixed in place" rather than three messages to that effect, one for each switch. On the other hand the command FLIP SWITCHES will act on each of the individual switches in turn, since it's not one of the collectiveActions defined for the switchBank CollectiveGroup.

Note the use of the collectiveActions property to define which actions will be handled by the CollectiveGroup rather than by each of its members. By default, collectiveActions is simply [Examine], but, as here, we can override it to contain other actions instead or as well.

For this to work properly, the name section of the CollectiveGroup object should simply be the plural of the name common to each of its members (here 'switches' corresponding to 'switch').

If the desc property of a CollectiveGroup is not explicitly defined, it defaults to a list of those of its members that are in scope.

For some situations the Collective class (or its DispensingCollective subclass) defined in the Collective extension, may be a better bet than CollectiveGroup. The kind of situation where you'd want to use a Collective of DispensingCollective is where you have a group item (e.g. a bunch of grapes or stack of cans) from which you want to draw one or more individual items (e.g. grapes or cans).

If you need a CollectiveGroup to represent items that are not fixed in place, but might be moved around (a collection of short portable cables, for example), you can instead use the MobileCollectiveGroup class defined in the MobileCollectiveGroup extension.

A SecretDoor is a Door that only acts like a Door when it's open. When it's closed it's either totally invisible, or it appears to be something else, such as a bookcase or a panel.

To use a SecretDoor, define it just like a Door, but (assuming it starts out closed) define its vocab property to be whatever's suitable for its closed state, and a separate vocabWhenOpen property to define the name and other vocab to use when it's open. For example:

    cellar: Room 'Cellar' 'cellar'
        "It's not a pleasant place at the best of times, dark, dank and smelly, with
        piles of old junk strewn all over the place waiting for you to find time to
        sort them out (which you probably never will). A wine rack stands <<unless
          wineRack.isOpen>> empty against the east wall<<else>>open to the east,
        revealing a passage beyond<<end>>. "

        isLit = nil
        darkName = 'Cellar (in the dark)'
        darkDesc = "It's too dark to see anything down here, but you could just
            about find your way back up to the kitchen. "
        up = kitchen
        west = wineRack

        regions = downstairs
    ;

    + wineRack: SecretDoor 'wine rack; empty'
        "It's empty; you never got round to restocking it. <<if isOpen>>It's also
        open, revealing a dark passage behind.<<end>> "

        afterAction()
        {
           if(gActionIs(Jump) && !isOpen)
            {
                "The vibration causes the wine rack to swing open, revealing a dark
                passage beyond. ";
                makeOpen(true);
            }

        }
        otherSide = dpDoor

        vocabWhenOpen = 'dark passage; empty wine; rack'
    ;

Note that the OPEN command won't work on a SecretDoor when it's closed, but the CLOSE command will work on a SecretDoor when it's open (unless you override the isOpenable property to make it do otherwise). The default assumption is that a SecretDoor has to be opened by some non-standard and probably non-obvious means.

If (exceptionally) a SecretDoor starts out open you can define its vocabWhenClosed property to specify the name and vocab to use for it when it's closed.

If you want a SecretDoor to be effectively invisible when it's closed, you could give it a vocab property comprising an empty string and make sure nothing else mentions it when it's closed, but it's probably easier just to make it a Door and define isHidden = !isOpen, for example:

    loft: Room 'Hay Loft' 'hay loft'
        "There's not much up here, apart from a few stray strands of straw
        scattered across the bare boards. A ladder leads back down to the
        main part of the barn below. "
        down = ladderDown
        west = loftDoor

    ;

    + Decoration 'straw; stray of; strands'
    ;

    + Decoration 'bare boards;;them'
    ;

    + ladderDown: StairwayDown 'ladder'
        destination = barn

        dobjFor(Pull)
        {
            action()
            {
                if(loftDoor.isOpen)
                {
                    "Pulling the ladder causes the secret door to close again. ";
                    loftDoor.makeOpen(nil);
                }
                else
                {
                    "Pulling the ladder causes a secret door to open to the west,
                    revealing a small compartment beyond. ";
                    loftDoor.makeOpen(true);
                }
            }
        }
    ;

    + loftDoor: Door 'small compartment;secret;door''
        "It looks only just big enough to enter. "
        otherSide = compartmentDoor

        specialDesc = "A small compartment has opened up to the west. "
        useSpecialDesc = isOpen
        isHidden = !isOpen
    ;

    smallCompartment: Room 'Small Compartment' 'small compartment'
       "There's only just enough room to stand in here. "
        otherSide = loftDoor

        east = compartmentDoor
        out asExit(east)
    ;

    + compartmentDoor: Door 'small door'
        otherSide = loftDoor
    ;

Note that in both these example, the west exit will only be displayed in the exit lister when the corresponding SecretDoor is open. When a SecretDoor is closed then, for travel purposes, it behaves as if there's no exit through it, even though it's attached to an exit property like a standard Door.

A ContainerDoor, on the other hand, isn't really a door at all, but it can be used to represent the door of an openable container, such that opening, closing, locking and unlocking the ContainerDoor has the same effect as opening, closing, locking and unlocking the container. To use a ContainerDoor we must locate it in multipy-containing Thing that has an OpenableContainer defined on its remapIn property; we can't define a ContainerDoor directly as part of an openable container since the door would then be hidden inside the container when it was closed.

So, for example, to define a cooker/oven we can put things in or on and which has a door we should do this:

    cooker: Fixture 'cooker;; oven stove'
       remapIn: SubComponent { isOpenable = true }
       remapOn: SubComponent { }
    ;

    + cookerDoor: ContainerDoor 'cooker door; oven stove'
    ;

adv3Lite Library Manual
Table of Contents | Optional Modules > Extras
Prev: Exits Next:Facts