Image based Maps - ThePix/QuestJS GitHub Wiki

In this approach to mapping, you create a map in your preferred drawing package, and Quest will display it on the page, with a marker indicating the player position. It can be set up so that the user can use the mouse wheel to scroll in and out, and drag to move; the map will re-centre after every command. Alternatively, the user can click on "hot-spots" on the map, and these can be set up to run a function, parse a certain command or move the player.

quest-alt-map

The Map Image

The first thing you need is the image.

You have a number of options; I recommend GIMP if you want a pixel editor or Inkscape for vector graphics, as both have a lot of features and both are free; they are both available on Windows, macOS and Linux. Alternatively, draw it on paper, and scan it into your computer. The file should be a format browsers will all handle, so PNG, JPG/JPEG or SVG are best.

I suggest a lot of space around your map, as the player will sometimes be visiting the outer lying locations. You might want to divide the image into thirds, both horizontally and vertically, and do all your drawing in the central block. I would recommend making your image at least 1000 pixels on each side.

You might want to get a fancy background, like the ancient paper in the image above. Make sure the image is royalty free and has no watermarks, and is of a high enough resolution. Have the background on one layer, and draw on another layer, so you can erase mistakes. For the above, I used a GIMP paintbrush that varied the pen and dark brown rather than black to make it more like it was hand-drawn.

Settings

You need to tell Quest to import the library file.

settings.libraries.push('image-map')

There are then a number of settings. The most import is the "mapImages" attribute. This is an array of dictionaries, with each element of the array corresponding to a map image. In this example, two map images are used. Each needs a filename (with path and extension), the width and height of the image in pixels, and the name.

settings.mapImages = [
  {
    name:'Halmuth',
    file:'game-alt-map/halmuth.png',
    width:1000,
    height:1000,
  },
  {
    name:'Small scale',
    file:'game-alt-map/country.png',
    width:1000,
    height:1000,
  },
]

These are optional settings to customise it:

settings.mapScrollSpeed = 1
settings.mapStyle = {
  right:'0',
  top:'200px',
  width:'400px',
  height:'400px',
  'background-color':'#ddd', 
  border:'3px black solid',
}
settings.mapMarker = function(loc) {
  return map.polygon(loc, [
    [0,0], [-5,-25], [-7, -20], [-18, -45], [-20, -40], [-25, -42], [-10, -18], [-15, -20]
  ], 'stroke:none;fill:black;pointer-events:none;opacity:0.5')
}

The first determines how fast the map scrolls. The next two determine how the map itself and the scroll arrows are displayed, while the last is used to draw the marker.

Locations

Having done that, the locations are very easy - you just need to set their x and y values. Note that the coordinates start in the top left corner of the map.

createRoom("street_of_the_gods", {
  desc:"The street is boring, the author really needs to put stuff in it.",
  west:new Exit('museum_of_curios'),
  mapX:400,
  mapY:450,
})

createRoom("museum_of_curios", {
  desc:"The museum is boring, the author really needs to put stuff in it.",
  east:new Exit('street_of_the_gods'),
  up:new Exit('museum_of_curios_upstairs'),
  mapX:300,
  mapY:480,
})

createRoom("museum_of_curios_upstairs", {
  desc:"The upper level of the museum is boring, the author really needs to put stuff in it.",
  down:new Exit('museum_of_curios'),
})

If a location has no coordinates, Quest will assume they are the same as the previous room. You MUST set the coordinates for the starting location.

You can also set the "mapRegion" attribute if this is the first location on a different map. This should be the name you gave the map in settings.mapImages. If you do not set this for the starting location, Quest will use the first one in the list.

Flagging a location

You can flag points on the map to let the player know it is somewhere to go. For example, if the player is given a quest to get the Eye of Dathmir, you could flag on the map of the land what city she needs to go to, and then on the city map flag which building to search.

These locations are set in an array called settings.mapPointsOfInterest. If the point is not on the first map, you need to set the mapRegion. You also need to set the coordinates, the fill colour and, of course, the text.

settings.mapPointsOfInterest = [
  {mapX:620, mapY:470, fill:'red', text:'Eye of Dathmi'},
  {mapX:140, mapY:830, mapRegion:'Small scale', fill:'red', text:'Eye of Dathmi'},
]

That will be here for the entire game. You might want it only to appear once the player has been asked to get the thing, and to disappear once she has it. This can be done by adding an "isActive" attribute, a function that returns true only when the marker should appear. Remember that only objects in the world are saved, so the quest state has to be stored on such an object; in this example on the eye itself.

settings.mapPointsOfInterest = [
  {
    mapX:620, mapY:470, fill:'red',
    text:'Eye of Dathmi',
    isActive:function() { return w.eye_of_dathmir.questActive },
  },
  {
    mapX:140, mapY:830, mapRegion:'Small scale', fill:'red',
    text:'Eye of Dathmi',
    isActive:function() { return w.eye_of_dathmir.questActive },
  },
]

Hot-spots

The above gives you a map that can be dragged around and zoomed. The alternative approach is a map with hot-spots the user can click to make something happen.

To do that, set up some map regions, in settings.js. This example creates four hot-spots, illustratng the four different types. The first has "w" and "h", this creates a reactangle with the given width and height. The second with "r" gives a circle with the given radius. The third, with "rx" and "ry", gives an ellipse, whilst the last is a series of points defining a polygon that can be any shape you want.

settings.mapRegions = [
  {x:500, y:300, w:50, h:30,                      script:function() {log('blue!')}},
  {x:300, y:300, r:30,             fill:'red',    jump:'wheat_road'},
  {x:400, y:400, rx:20, ry:50,     fill:'green',  cmd:'get hat'},
  {pts:'400,500, 480,500 440,560', fill:'yellow', script:function() {log('yellow!')}},
]

Regions default to blue, but you can set them to any colour with the "fill" attribute. You may want them to be invisible, but setting them to a colour ca be useful when setting up so you can see if there are in the right place.

This brings us to our next setting...

settings.mapRegionOpacity = 0.5

This defines where we can see though the shapes that make up the regions. A value of 1 will be opaque, and value of 0 with make them invisible.

So what can we do with a region?

If a region has a "script" attribute, this function will be run when the user clicks on the hotspot.

Otherwise, if a region has a "jump" attribute, the player will be moved to the location named. This will be as though the player used an exit with the direction "teleport".

Otherwise, if a region has a "cmd" attribute, the given string will be given to the parser exactly as though the user have typed it.

To be able to handle the "jump" option, you might want to change the value of lang.go_successful. You can detect if this was a teleport, rather than using a directional exit.

lang.go_successful = "{if:exit:dir:teleport:Teleport!:{nv:char:head:true} {show:dir}.}"

Note that you can add "isActive" attributes to regions just as with points of interest.