Attributes for rooms - ThePix/QuestJS GitHub Wiki

Note: Attributes will only be saved if they are string, number, Boolean, string array or number array. Functions, other arrays, dictionaries and objects will not be saved.

For attributes on exits, go here.

name, alias, properName

Room names work the same as item names. The important attributes are name (set from the createRoom function), alias and properName. For full details go here.

The alias is used when NPC's are wondering around.

headingAlias

If you use a heading for the locations, you might set it up like this:

settings.roomTemplate = [
  "#{cap:{hereName}}",
  "{terse:{hereDesc}}",
  "{objectsHere:Mary can see {objects} here.}",
]

The "hereName" text processor directive will then use the room's "headingAlias". If you do not explicitly set a value, one will be created using settings.getDefaultRoomHeading. You may choose to modify that, for example:

settings.getDefaultRoomHeading = function(item) { return titleCase(lang.addDefiniteArticle(item) + item.alias) }

desc

This can be a string or a function that returns a string, and will be used when a room is entered.

Note that locations use "desc" while items use "examine".

As a string

Note that you can use a vertical bar, |, to signal a new paragraph.

As a function

If "desc" is a function, it must return the string to be printed rather than print itself (this is to be compatible with settings.roomTemplates). A good general approach is to create a string with let, and then to add to it as required, before returning it.

  desc:function() {
    let s = "The cloakroom is {cloakHere:dimly:brightly} lit, and is little more than a cupboard. "
    if (w.cloak.isAtLoc('hook')) {
      s += "Your cloak is hung from the only hook."
    }
    else if (w.cloak.isAtLoc(this)) {
      s += "There is a single hook, which apparently was not good enough for you, to judge from the cloak on the floor."
    }
    else {
      s +="There is a single hook, which strikes you as strange for a cloakroom."
    }
    s += " The only way out is back to the east. "
    return s
  },

Using Immutable Strings

What are immutable strings? Simply strings that you do not change during the course of the game. Consider a point in the game where the player has to blow open a door. One way to handle that would be to change the "desc" property of the room when the explosive goes off. You are now changing the string, rather than using an immutable string. A better way, in my view, would be via a text processor directive (or a function). The string does not change, but the output from it does.

There is more on how to handle dynamic room descriptions here.

If you are creating a large game, I would suggest you only use immutable string descriptions (and function descriptions), and tell Quest not to save them. This will significantly reduce the size of game files. You can tell quest not to save them, by adding this line to settings.js.

settings.saveLoadExcludedAtts.push("desc")

Note that Quest will not warn you if change an immutable string!

Based on state

If the item can be in various states, one approach is to give it a array of descriptions, and have the "examine" string use the text processor to pick one.

Let us suppose a laboratory. We will track the state of the laboratory in an attribute called "state" which starts from 0 and rises to 2, and put the descriptions in a list called "descs". The "desc" then uses the "select" text directive to pick the right one.

createRoom("laboratory" {
  state:0,
  descs:[
    "A deserted laboratory; nothing happening here.",
    "A laboratory; you see retorts and flasks and spiral tubing, with brightly coloured liquids in them.",
    "The remains of a laborary, bit os shattered glass everywhere.",
  ],
  desc:"{select:laboratory:descs:state}",
}

darkDesc

An alternative description used when the location is dark. Works just like "desc".

scenery

For items the "scenery" attribute is a Boolean that flags the item as scenery. For locations, the same attribute can be used to generate those items.

The attribute should be an array where each element is an item in the location. Elements can be just a string, which will be used as the alias for the item, and Quest will give it a description saying it is just scenery. Alternatively, you can provide a dictionary, with "alias" and "examine" attributes, allowing you give an actual description. The "alias" attribute can be an array of alternative names. This example illustrates some options:

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'),
  west:new Exit("dining_room"),
  scenery:[
    'tv',
    {alias:['old settee', 'couch', 'sofa']},
    {alias:'rug', examine:'It might have been blue at one time. Maybe.'},
  ],
});

The default description is lang.default_description; you can change the value of that (say in code.js) if you want to customise it.

lang.default_description = 'Nothing you need worry about.'

Note that the "scenery" array is deleted after it is used, in part to prevent it getting saved - the example above would throw an error as Quest would think it is a string array, so will try to save it, then choke on the dictionary.

Once the game starts there is no difference between an item created from the "scenery" attribute of a location and an item created normal with its "scenery" attribute set to true. I think an advantage of this method is its keeps the less important items within the location that they are for. I would not use it for items that do anything, even if they are (at first) scenery.

Direction attributes

An Exit object.

Note that the attribute names depend on what you have set in lang.exit_list, which by default is set in the language file. If you are producing a game in a language other than English, the attributes will be the compass directions in your language. If your game is set on a ship, and you are using "port" and "starboard", the attributes will be "port", "starboard", etc. (see here for how that can be done).

Create an exit like this:

  west:new Exit("lounge"),
  north:new Exit("garage", {use:useWithDoor, door:"garage_door", doorName:"garage door"},),
  down:new Exit('basement', {msg:function(isMultiple, char) {
    if (char === game.player) {
      msg("You go through the trapdoor, and down the ladder.");
    } else {
      msg("You watch " + char.byname({article:DEFINITE}) + " disappear through the trapdoor.");
    }
  }}),

The first example is a simple exit from one room to another. The other two show how an exit can be customised.

The "use" attribute is a script that is run when the player uses the exit. In this case it is set to a built-in function that links it to a door.

The default "use" function will look for an attribute called "msg", which can be a string or function. If you have NPCs, this should be a function so it can react to the player or NPC, but only needs to print a message - everything else is done for you (note that this uses the same system as items, so the function will be sent a Boolean and then the character, even though the Boolean is meaningless in this context).

You can also use your own (it will be sent the exit itself, the character using it and the direction as parameters).

Note that exits are not saved, so any attributes set on the exit, as opposed to the room, will not get saved.

Exits are discussed more here.

When the game starts, every location (in fact, every object) will be checked to ensure all direction attributes are Exit objects, and other attributes are not. It will also check the exit destination exists - if you want your exit to go nowhere, use the special name "_".

Custom directions

You can readily add your own custom directions, by adding them to lang.exit_list.

lang.exit_list.push({
  name:'climb', abbrev:'Cl', niceDir:"above",
  not_that_way:'Nothing to climb here.',
})

You can then use it as any other exit.

  climb:new Exit("observatory_up"),

That said, it is generally better to add it as an "alsoDir", so if the user does EXITS, she will see "up" listed, rather than "climb".

  up:new Exit("observatory_up", {alsoDir:['climb']}),

Be aware that the mapping features might struggle to cope. This version includes details that the map needs. That works for CLIMB, where we can be sure you are just going straight up, it might not for LEAP.

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

Look directions

If you want the player to be able to look through an exit, use a look direction. There are four ways to do this - setting an attribute on the room or the exit, which can either be a string or a function.

createRoom("lounge", {
  desc:'A smelly room.',
  east:new Exit('kitchen'),
  west:new Link("dining_room"),
  south:new Exit("conservatory", {look:'Though the doorway to the south you see the conservatory.'}),
  look_north:function() {
    msg("You look north, imagining another exit that way, a portal out of this mad house.")
    return true
  },
  look_east:'Though the doorway to the east, you can see the kitchen. is there something on the table?',
})

Function or string attribute on the exit: Give your exit an attribute, "look", as you see for south in the example above. For simple exits the is the best way in my opinion because it keeps everything for the exit with the exit. However, exit attributes are not saved; if you are using a string attribute it must not change during the game.

Function attribute on the room: Give your room an attribute, "look_[direction]", as you see for north in the example above. If this is a function, Quest will give you full control - and responsibility. If the uses types LOOK NORTH, and there is a "look_north" function, the function will run whether the exit is visible or not or even if there is no exit. It should return true if successful, and false otherwise, and should give the player a message either way.

String attribute on the room: If it is a string, like "look_east" in the example, Quest will display it only if the exit exists, is visible and is unlocked (but if there is a door, it will not check the door is open). If you want the text to change as the game progresses, using this attribute is one way to do it - though using a function might be a better way.

Functions are passed a dictionary with "dir" set to the direction, "exit" set to the exit object and (except for functions on the room) "dest" set to the destination location. Strings can use these as text processor parameters.

What if you want dynamic descriptions? We might want to change the LOOK NORTH so that later in the game there really is a portal that way.

  look_north:function() {
    if (w.portal.visible) {
      msg("You look north, imagining another exit that way, a portal out of this mad house.")
      return true
    }
    else {
      msg("You look north, imagining another exit that way, a portal out of this mad house.")
      return false
    }
  },

Perhaps the conservatory gets hit by a mortar attack. We cannot change a string attribute on the exit, but can handle it as a function.

  south:new Exit("conservatory", {look:function() {
    if (w.conservatory.wrecked) {
      msg('Though the doorway to the south you see the ruins of the conservatory.')
    }
    else {
      msg('Though the doorway to the south you see the conservatory.')
    }
  }}),

In fact, you could do that with some text processor trickery.

  south:new Exit("conservatory", {look:'Though the doorway to the south you see{if:conservatory:wrecked: the ruins of} the conservatory.'}),

For LOOK EAST, you might want to change the description when the player enters the kitchen and can see what is on the table. This code could be in the "afterEnter" function of the kitchen:

  w.lounge.look_east = 'Though the doorway to the east, you can see the kitchen, with the horrible pumpkin head on the table.'

If the player looks in a direction where there is an exit, but you have provided no look text, the default message is "You look west; definitely an exit that way." You may prefer to have it default to at least telling the user what room is that direction. You can do that by changing the language text. Do this either in code.js, or inside your "settings.setup" function in the settings.js file.

  lang.default_look_exit = "{nv:char:look:true} {show:dir}, and can see {nm:dest:the}."

lightSource()

A function, defaults to LIGHT_FULL for rooms and LIGHT_NONE for items.

beforeFirstEnter(), beforeEnter(), afterEnter(), afterFirstEnter(), afterExit()

Functions that run when the player enters or exits a room. Note that the TRANSIT template uses the "beforeEnter" attribute; if you want to add this to a location that uses that template you will need to copy-and-paste the code from the template to your custom function.

These function will have the exit sent to it, and you could, for example, check the "name" attribute of that to see where the player is going. Note that exit may be undefined in some situations, in particular at the start of the game and if you call "moveChar" for the player or NPC without an exit.

afterEnterIf()

A further function that runs when the player enters a room. It is often the case, I have found, that you want a one off-event when the player enters the room, but not necessarily the first time. To handle this you could use afterEnter(), checking the condition, and adding a flag to test if it has already happened, however, this is perhaps a better alternative.

The attribute should be a dictionary. Each entry in the dictionary should itself be a dictionary, with a test function and an action function. The "test" function should return true when you want the event to trigger. The "action" function should do the work.

This example has two events, but you can have as many or as few as you like.

  afterEnterIf:{
    playerWearingHat:{
      test:function() { return game.player.hasHat },
      action:function() {
        msg("Everyone in the room applauds your hat.")
      },
    playerHasCompleted:{
      test:function() { return game.player.state === 7 },
      action:function() {
        msg("Everyone in the room is stunned you have managed to actually complete the game.")
      }
    },
  }

Note that each event will only ever fire once. You can check which have been done by looking at the room's "afterEnterIfFlags" attribute, which will be a string of the event names, separated by spaces.

description()

This is called when the player enters a room or does LOOK, and gives the room description, based on the template set up in settings.room_template, and the "desc" attribute. You will probably never need to touch this.

visited

A count of how many times the player has visited this room.

This is updated after the beforeEnter and beforeFirstEnter scripts, and the description has been shown, but before the afterEnter and afterFirstEnter scripts run. So, for example, in the afterFirstEnter() function, this will be one - because the player has now visited the location - but in beforeFirstEnter() it will be zero - the player has not got there yet.

smell and listen

Add these to a room to add a smell or sound, to be displayed if the user does SMELL or LISTEN.

hasExit, getExitObjs, getExits, getExitDirs, findExit

A set of functions for when you are interested in the exists for the room. They all take a set of options where you can set "excludeAlsoDir" (defaults to true), "excludeLocked" and "excludeScenery". Exits for which "isHidden" returns true will always be excluded.

The "hasExit" function should have a direction passed as well, before the options, while "FindExit" should have a destination, and returns the Exit object.

todo

You can add a "todo" string attribute to a location to note that you still have work to do here. It will be displayed in game unless the playmode is set to "play".

How it is displayed is controlled by settings.roomTemplate.

Notes

When a location is created

You can use settings.roomCreateFunc to do something each time a location is created. You could use this to set an attribute on every location, in the example the region is set to the current value of the global variable region (eg see here). You could include a counter too.

settings.roomCreateFunc = function(o) {
  o.region = region
}

Both item and room

You can create objects that are both items and rooms. This example uses createRoom and passes the defaults for items, but you can also do it the ither way around.

createRoom('large_box', DEFAULT_ITEM, {
  ...

That said, I would discourage doing so. When I started Quest 6, I followed the Quest 5 practice, which freely allows this, but as time has passed, the dividing line between item and room has widened, and at this point I think you are better keeping the two separate. One issue is that rooms are flagged with the "room" attribute; items are taken to be anything without the "room" attribute - that is going to fail if te object is both.

A better approach is to have two objects, one the room and one the item. Or even three, and have the item as seen when inside it, the item as seen from outside it and the item as a room. This example is for some boots the player can climb inside.

createItem("boots", {
  loc:"secret_room",
  scenery:true,
  parserPronouns:lang.pronouns.plural,
  alias:"pair of boots",
  examine:"Giant boots! So big you could probably get inside one of them.",
})

createRoom("boots_room", {
  out:new Exit("room_small", {msg:"You climb out of the giant boot, thankful to be out of there."}),
  alias:'inside a boot',
  headingAlias:"Inside a left boot",
  desc:"The interior of the boot is no more pleasant than the exterior; just darker.",
})

createItem("boots_from_inside", {
  loc:"boots_room",
  scenery:true,
  alias:'boot',
  examine:function() { msg(w.boots_room.desc) },
  goOutDirection:'out',
})