Attributes for exits - ThePix/QuestJS GitHub Wiki

Exits are created by giving a location an attribute that is a direction. The attribute has to be a new Exit (or a derivative of Exit), and at its simplest just has the name of the destination.

createRoom("lounge", {
  desc:'A smelly room with an old settee and a tv. There is a tatty rug on the floor.',
  east:new Exit('kitchen'),
  south:new Exit("conservatory"),
})

The directions you can use are defined in lang.exit_list. This means if you are using another language or custom directions (eg port and starboard), you can use those directions.

Handling the player looking in a direction (eg LOOK EAST) is discussed on the rooms page. Using port and starboard is discussed here.

Notes

Saving attributes: Attributes of Exits are not saved by default. It is generally better to have a function on the exit that checks the state of something else, such as the room (if you really want to save the attributes, you can add preSave and postLoad functions to a room, but I would advise looking for an alternative way of doing it).

This means that all the attributes on an exit can be divided between: those you set when creating them; those that are actually on the room; and a number of utility functions you can use during play.

If you need to dynamically create exits, use the EXIT_FAKER template for the room.

Locked exits: For locked exits, depending on the situation, consider using the LOCKED_DOOR template.

Location attributes: See here.

Nowhere: If your exit goes nowhere, use "_" as the destination - Quest will assume a bug if you just leave the destination name blank). Consider using NonExit or BarredExit if your exit goes nowhere (see below).

  south:new Exit("_"),
})

Moving message: By default, QuestJS gives a message about the player moving from one location to another before giving the room description. To customise that across your entire game set lang.go_successful in code.js. Set it to false to prevent any message appearing.

Go with item: You can set up items to stand in for exits (when the user types, for example, CLIMB TREE, GO DOWN MANHOLE, GO THROUGH PORTAL). See here; there is also an example at the end of this article.

Not all there at the start! When you create an exit it has a limited set of attributes. Before the game starts, Quest will set others, such as "origin" and "dir" so they are available during play. However, if you are doing fancy stuff during set up, be aware they may not exist yet.

Other Exit types

These are Exits, but they have some added features built in. They are basically short-cuts for common situations.

Link

A Link is an type of Exit that flags that Quest should automatically generate a second exit going the opposite direction. They are set up exactly like Exits, except you use the word "Link" rather than "Exit". You cannot set any attributes on the return exit or choose a different direction - you just get the defaults (if you need to do that, use two exits). You can, however, set attributes as normal for the forward direction.

createRoom("lounge", {
  desc:'A smelly room with an old settee and a tv. There is a tatty rug on the floor.',
  east:new Link('kitchen'),
  south:new Exit("conservatory"),
})

Whether Links are a good idea is for you to decide. There is an argument that using Exits you can immediately see what is in each location, and that this is how the player will see it. There is also less chance of Exits colliding.

You can also made an Exit into a Link by setting "linkFlag" to true. The "east" exit in the above example could be done like this.

  east:new Exit('kitchen', {linkFlag:true}),

This could be useful in conjunction with some of the other derived types.

NonExit and BarredExit

These are further types of Exits that can be used for directions the user might try, but do not actually exist - they do not go anywhere. The difference between them is that a BarredExit will appear on the compass rose and in the list of exits for a location, while a NonExit will not.

In this example, the user will see exits going east and west, but will discover the door to the east is locked. She might try to use the painted on exit on the north wall, so a NonExit is used to handle that.

createRoom("lounge", {
  desc:"The lounge is boring. A door is painted on the wall to the north, but there are real doors east and west",
  north:new NonExit('The door is painted on!'),
  east:new BarredExit('That way is permanently locked.'),
  west:new Exit('hall'),
})

Both NonExit and BarredExit take a single string argument - the message the user will see.

WayIn and WayOut

Use these for exits that go in and out as well as a compass direction. The WayIn also needs the name of a item the player will be entering (which must be at this location).

Here is our WayIn. The player can do EAST, IN, ENTER or ENTER HOUSE to go that way. The house is mentioned in the description, so should be implemented anyway.

createRoom("garden", {
  desc:"The garden is boring, the author really needs to put stuff in it. The house is to the east.",
  east:new WayIn('lounge', 'house'),

createItem('house', {
  loc:'garden',
  examine:'A fine old house.',
})

The WayOut does not have an item. The player can do WEST, OUT or EXIT.

createRoom("lounge", {
  desc:"The lounge is boring, the author really needs to put stuff in it.",
  west:new WayOut('garden')
}),

You can give the exit other attributes as with any other exit type. For example:

  east:new WayIn('lounge', 'house', {msg:'Heart pounding, you enter the spooky house.'}),

ClimbExit

The ClimbExit should be given to an "up" or "down" attribute, and must be given the name of an item at this location - whatever the player will climb. The player can use UP, CLIMB, CLIMB TREE or CLIMB UP TREE to go up, or DOWN or CLIMB DOWN TREE to go down.

createRoom("garden", {
  desc:"The garden is boring, the author really needs to put stuff in it.",
  up:new ClimbExit('up_tree', 'tree_item'),
})
createRoom("up_tree", {
  desc:"The garden is no less boring from up here.",
  down:new ClimbExit('garden', 'tree_item_up'),
})
createItem('tree_item', {
  alias:'tree',
  loc:'garden',
  examine:'An old oak tree.',
})
createItem('tree_item_up', {
  alias:'tree',
  loc:'up_tree',
  examine:'An old oak tree that feels rather steady when you are up in its branches.',
})

You can give the exit other attributes as with any other exit type. For example:

  up:new ClimbExit('up_tree', 'tree_item', {msg:'You haul yourself up into the lower branches of the tree.'}),

StairsUp and StairsDown

These are similar to the ClimbExit, but should be assigned to a compass direction. Again they require item names.

For this example I am doing the items as scenery items in the location; you can, of course, do them as proper item, and conversely use scenery items for the previous exit types.

createRoom("lounge", {
  desc:"The lounge is boring, the author really needs to put stuff in it.",
  south:new StairsUp('bedroom', 'lounge_scenery_stairs'),
  scenery:[
    'stairs',
  ],
})

createRoom("bedroom", {
  desc:"The bedroom is boring, the author really needs to put stuff in it.",
  north:new StairsDown('lounge', 'bedroom_scenery_stairs'),
  scenery:[
    'stairs',
  ],
})

The player can use UP, SOUTH, GO UP STAIRS or ASCEND STAIRS to go up, or DOWN, NORTH or GO DOWN STAIRS to go down.

Basic Attributes

name, dir and origin

These attributes are set implicitly when you create the exit; "name" is the name of the location the exit goes to, "origin" is the location (not the name) where it comes from, and "dir" is the direction.

Consider:

  new Room("lounge", {
    examine:"A smelly room with an old settee and a tv.",
    east:new Exit('kitchen'),
  }),

In this example, the "name" attribute will get set to "kitchen" (the string), the "dir" to "east" and the "origin" to w.lounge (the object).

NOTE: If you are doing something fancy that involves creating rooms and exits in settings.setup, you will need to set "dir" and "origin" for them yourself. Quest sets them during initialisation, and will do that before your fancy exits are created.

Using Exits

This is about what happens when the player or an NPC use an exit.

use

This is the script that runs when the player uses the exit. The default is just to move the player to the exit's destination,

If this is a door (locked or not), you can use the built-in util.useWithDoor function.

  north:new Exit("garage", {use:util.useWithDoor, door:"garage_door", doorName:"garage door"},),

If the exit can never be used - it is just decoration or leads off-scene - use util.cannotUse (this has been superceded by BarredExit and NonExit, but is still available). The default message will say the door is locked, but you can add your own.

  south:new Exit("_", {use:util.cannotUse, msg:'It is generally a bad idea to go into air locks.'}),

If you write your own use function, it should take the character as a parameter, and return true if the character moves, false otherwise. In addition, you can access the exit itself (and so any attributes of it) using this.

Depending on what you have in your game, the function should be able to handle NPCs as well as the player, should check the character is able to move (with "testMobility"), possibly check if the door is locked and check any carried object that has a "testCarry" function.

simpleUse

However, you can let the default handle all that, and just jump in at this point with "simpleUse", which just has to handle postures, the messages and the actual move.

Here are two examples. The first checks a couple of situations where the exit cannot be used, and just prints a message and returns false. If the exit can be used, the default function is used to sort it all out. The second prints a message depending on the current situation, but then - to avoid the default message - moves the player itself.

  west:new Exit("ballroom_hall", {simpleUse:function(char) {
    if (w.ballroom.state === 1) return falsemsg("You wonder if you can slip out unnoticed. Probably not.")
    if (w.ballroom.state === 2) return falsemsg("The door is locked.")
    return util.defaultSimpleExitUse(char, this)
  }}),

  east:new Exit("library", {simpleUse:function(char) {
    char.msg(lang.stop_posture(char))
    if (w.ballroom.state === 1) {
      msg("You slip out the door to the library quietly.")
    }
    else {
      msg("You head east.")
    }
    char.moveChar(this)
    return true
  }}),

The util.defaultSimpleExitUse function expects the exit as the second parameter, but you can fool it into using something else. This is useful if you want the exit to take the character to different places. This example sets dest depending on the value of another attribute. Then, rather than pass the exit to util.defaultSimpleExitUse, it creates a disposable exit on the fly, using the original exit via this.

  east:new Exit("_", {simpleUse:function(char) {
    if (!w.room_big.accessedFrom) w.room_big.accessedFrom = 'gallery_south'
    const dest = w.room_big.accessedFrom === 'gallery' ? w.room_big : w.room_small
    return util.defaultSimpleExitUse(char, new Exit(dest, this))
  }}),

This example tests a condition. If false, it again creates an exit on the fly, but this time starting from scratch, as we want to add a message.

  in:new Exit('lift', {
    alsoDir:['southeast'],
    msg:'She steps into the lift.',
    simpleUse:function(char) {
      if (w.lift.getTransitDestLocation() !== this.origin) {
        return util.defaultSimpleExitUse(char, new Exit('lift_shaft', {origin:this.origin, dir:this.dir, msg:"She heads through the doorway."}))
      }
      return util.defaultSimpleExitUse(char, this)
    },
  }),

For exits that are not apparent, it is a good idea to use the "isHidden" function discussed later, to ensure the exit is not listed until it is visible.

Messages When Exits Are Used

msg

Used when the player uses the exit (if the default use script is used, or your custom script uses util.defaultSimpleExitUse). Can be a string or a function.

You can use a string in place of a dictionary and Quest will assume this is the "msg" attribute. These two statements do the same thing. Obviously this will only work if you have no attributes to set.

north:new Exit("nursery", {msg:'You have to stoop to get through the narrow door to the north.'}),

north:new Exit("nursery", 'You have to stoop to get through the narrow door to the north.'),

npcLeaveMsg and npcEnterMsg

These are like "msg", but for NPCs. When an NPC uses the exit to leave the current location, "npcLeaveMsg" is used. When the NPC enters the current location, "npcEnterMsg" is used. Like "msg", these can strings or functions. Functions will be passed a dictionary with the attributes "npc", "room" and "dir", while strings can use the same as text processor parameters. Note that for enter, things are reversed; if the NPC uses the "north" exit from the lounge, in the kitchen the NPC is assumed to enter from the south. Quest will only call the functions once it has determined they are applicable (the player can see the NPC leaving the current room, or entering it), so no need to test.

createRoom("kitchen", {
  desc:'A clean room{if:clock:scenery:, a clock hanging on the wall}. There is a sink in the corner.',
  west:new Exit("lounge"),
  down:new Exit('basement', {
    msg:"You go through the trapdoor, and down the ladder.",
    npcLeaveMsg:function(options) {
      msg("You watch {nv:npc:disappear} through the trapdoor.", options);
    },
    npcEnterMsg:function(options) {
      msg("{nv:npc:come:true} through the trapdoor, and {cj:npc:climb} down the ladder to join you in the basement.", options);
    },
  }),
})

msgNPC

For more control, give your exit a "msgNPC" attribute. This should be a function, with the NPC as the parameter. It will be up to you to decide how to deal with different NPCs, and to take account of where the player is and hence whether the player can see it.

As an aside, you can also use the "movingMsg" function attribute on an NPC to handle this.

Hidden, locked and lit

These are three things that we might expect to change during the game so all are handled the same way.

The issue here is that the exit is not saved, so we need to be setting an attribute on the room instead, but this is all handled behind the scenes.

isHidden(), isLocked(), isLit()

These are built-in functions, but you may want to override it to do your own thing. If they return true, the exit will be hidden, locked or illuminated.

setHidden(state), setLocked(state), setLit(state)

These are built-in functions, they will only work if you are using the built-in versions of the previous functions. Pass true and the exit will be hidden, locked or illuminated; pass false and it will not.

Attributes on the location

For each state, there is a pair of functions and a set of attributes on the location itself. For locked, these are isExitLocked(dir), setExitLocked(dir, state) and exit_east_locked etc.

Initial state

To have an exit start locked, set the "locked" attribute on the exit itself. During initialisation, Quest will convert that to the proper state on the location. Similarly you can set "hidden" and "lit" (or "illuminated" for backwards compatibility).

Additional states

You can add your own state simply by adding it to EXIT_STATES.

settings.exit_states.push('Cursed')

Other attributes on the Exit

door and doorName

Used by the useWithDoor function.

lockedmsg

If isLocked() returns true, the player will be unable to use this exit, getting a message instead, a default message that it is locked, or lockedmsg if that is set.

scenery

Set scenery to true (as with objects) to make an exit scenery. Note that by default this will not get saved when the user saves the game, so do not change the value during play.

An exit set to scenery will not appear on the compass rose.

alsoDir

Sometimes you want an exit to be two (or more?) directions, for example, a staircase to the west could be used with UP or WEST. For a simple exit with no attributes, it is not much bother to just create two exits. If more complicated, however, just create one, but add an alsoDir attribute. This should be an array listing all the other directions.

  west:Exit("cargo_bay", {
    msg:"You walk down the narrow stair way to the middle deck.",
    alsoDir:["up"],
  }),

Behind the scenes, Quest will create the extra direction for you during the initialisation process.

The added direction will be flagged as scenery. This means that it will not appear on the compass rose (if it did, the user might conclude they were two different exits). In the example above, the user can type WEST or UP, but only W will be on the compass rose.

Note that this can confuse the node-map!

Exit attributes on the location

If an attribute can change during play, it should be on the location, rather than the exit. There are three built-in, discussed earlier, for locking, hiding and lighting up exits.

hasExit(dir, options)

Returns true if there is an exit in the given direction (including if it is an "alsoDir"). The options attribute is optional, but can be used with these flags (which all default to false): excludeAlsoDir, excludeLocked, excludeScenery, includeHidden.

getExits() and getExitDirs()

These functions return a list of exits for the room, the former a list of the exit objects, the latter a list of directions as strings. Both can be passed options as for hasExit.

findExit(dest)

Will get the exit on this room that goes to the given destination, or null otherwise. Can be passed options as for hasExit.

getRandomExit()

Gets a random exit, or null if none are found. Can be passed options as for hasExit.

Exit utility functions

getExitObject()

Gets the object from lang.exit_list for this direction. This can be useful because there is quite a bit of data attached to it.

nice()

Returns a string as it might be said in a sentence, eg "the north" (such as "Lara enters from the north").

reverse()

Returns the opposite direction as a string, eg "south" if this exit goes north. This will not work until the "dir" attribute is set, so works fine during play, but not during set up.

const rev = myExit.reverse()

If you do not have an Exit and want to know the reverse direction of a string, dir, you can use this code:

const rev = lang.exit_list.find(x => x.name === dir).opp

reverseNice()

Returns the opposite direction as a "nice" string for NPCs.

Novel directions

Climb is now a part of QuestJS, ad you would simply use the ClimbExit now. However, this section still illustrates the technique.

Occasionally you might want to add a new direction. A typical example might be "climb" - a command that has no item, and takes the player to another location. How can we do that? Simply add it as an exit type (say in code.js or settings.setup()). Here we will add both CLIMB UP and CLIMB DOWN.

lang.exit_list.push({
  name:'climb', alt:'climb up', abbrev:'Cl', niceDir:"above",
  type:'vertical', x:0 ,y:0, z:1, opp:'down',
  not_that_way:'Nothing to climb here.'
})

lang.exit_list.push({
  name:'climb_down', alt:'climb down', abbrev:'CD', niceDir:"below",
  type:'vertical', x:0 ,y:0, z:-1, opp:'climb',
  not_that_way:'Nothing to climb down here.'
})

You can then use it as a regular exit. In fact, it is probably best combined with "up", so for example:

createRoom("forest", {
  up:new Exit("tree_top", {alsoDir:['climb'], msg:'You climb the biggest tree.'}),
}

You should also implement the tree, and allow CLIMB TREE. First, here is the command (it will use the "climbverb" attribute, as "climb" is now an exit attribute).

commands.unshift(new Cmd('ClimbVerb', {
  regex:/^(?:climb up|climb|go up|ascend) (.+)$/,
  objects:[
    {scope:parser.isHere},
  ],
  defmsg:"{pv:item:'be:true} not something you can climb.",
}))

In your item, just call "use" for the exit.

createItem('big_tree', {
  scenery:true,
  climbverb:function(options) {
    return w.forest.up.use(options.char)
  },
})