var World = {
RADIUS: 30,
VILLAGE_POS: [30, 30],
TILE: {
VILLAGE: 'A',
IRON_MINE: 'I',
COAL_MINE: 'C',
SULPHUR_MINE: 'S',
FOREST: ';',
FIELD: ',',
BARRENS: '.',
ROAD: '#',
HOUSE: 'H',
CAVE: 'V',
TOWN: 'O',
CITY: 'Y',
OUTPOST: 'P',
SHIP: 'W',
BOREHOLE: 'B',
BATTLEFIELD: 'F',
SWAMP: 'M',
CACHE: 'U',
EXECUTIONER: 'X'
},
TILE_PROBS: {},
LANDMARKS: {},
STICKINESS: 0.5, // 0 <= x <= 1
LIGHT_RADIUS: 2,
BASE_WATER: 10,
MOVES_PER_FOOD: 2,
MOVES_PER_WATER: 1,
DEATH_COOLDOWN: 120,
FIGHT_CHANCE: 0.20,
BASE_HEALTH: 10,
BASE_HIT_CHANCE: 0.8,
MEAT_HEAL: 8,
MEDS_HEAL: 20,
HYPO_HEAL: 30,
FIGHT_DELAY: 3, // At least three moves between fights
NORTH: [ 0, -1],
SOUTH: [ 0, 1],
WEST: [-1, 0],
EAST: [ 1, 0],
Weapons: {
'fists': {
verb: _('punch'),
type: 'unarmed',
damage: 1,
cooldown: 2
},
'bone spear': {
verb: _('stab'),
type: 'melee',
damage: 2,
cooldown: 2
},
'iron sword': {
verb: _('swing'),
type: 'melee',
damage: 4,
cooldown: 2
},
'steel sword': {
verb: _('slash'),
type: 'melee',
damage: 6,
cooldown: 2
},
'bayonet': {
verb: _('thrust'),
type: 'melee',
damage: 8,
cooldown: 2
},
'rifle': {
verb: _('shoot'),
type: 'ranged',
damage: 5,
cooldown: 1,
cost: { 'bullets': 1 }
},
'laser rifle': {
verb: _('blast'),
type: 'ranged',
damage: 8,
cooldown: 1,
cost: { 'energy cell': 1 }
},
'grenade': {
verb: _('lob'),
type: 'ranged',
damage: 15,
cooldown: 5,
cost: { 'grenade': 1 }
},
'bolas': {
verb: _('tangle'),
type: 'ranged',
damage: 'stun',
cooldown: 15,
cost: { 'bolas': 1 }
},
'plasma rifle': {
verb: _('disintigrate'),
type: 'ranged',
damage: 12,
cooldown: 1,
cost: { 'energy cell': 1 }
},
'energy blade': {
verb: _('slice'),
type: 'melee',
damage: 10,
cooldown: 2
},
'disruptor': {
verb: _('stun'),
type: 'ranged',
damage: 'stun',
cooldown: 15
}
},
name: 'World',
options: {}, // Nothing for now
init: function(options) {
this.options = $.extend(
this.options,
options
);
// Setup probabilities. Sum must equal 1.
World.TILE_PROBS[World.TILE.FOREST] = 0.15;
World.TILE_PROBS[World.TILE.FIELD] = 0.35;
World.TILE_PROBS[World.TILE.BARRENS] = 0.5;
// Setpiece definitions
World.LANDMARKS[World.TILE.OUTPOST] = { num: 0, minRadius: 0, maxRadius: 0, scene: 'outpost', label: _('An Outpost') };
World.LANDMARKS[World.TILE.IRON_MINE] = { num: 1, minRadius: 5, maxRadius: 5, scene: 'ironmine', label: _('Iron Mine') };
World.LANDMARKS[World.TILE.COAL_MINE] = { num: 1, minRadius: 10, maxRadius: 10, scene: 'coalmine', label: _('Coal Mine') };
World.LANDMARKS[World.TILE.SULPHUR_MINE] = { num: 1, minRadius: 20, maxRadius: 20, scene: 'sulphurmine', label: _('Sulphur Mine') };
World.LANDMARKS[World.TILE.HOUSE] = { num: 10, minRadius: 0, maxRadius: World.RADIUS * 1.5, scene: 'house', label: _('An Old House') };
World.LANDMARKS[World.TILE.CAVE] = { num: 5, minRadius: 3, maxRadius: 10, scene: 'cave', label: _('A Damp Cave') };
World.LANDMARKS[World.TILE.TOWN] = { num: 10, minRadius: 10, maxRadius: 20, scene: 'town', label: _('An Abandoned Town') };
World.LANDMARKS[World.TILE.CITY] = { num: 20, minRadius: 20, maxRadius: World.RADIUS * 1.5, scene: 'city', label: _('A Ruined City') };
World.LANDMARKS[World.TILE.SHIP] = { num: 1, minRadius: 28, maxRadius: 28, scene: 'ship', label: _('A Crashed Starship')};
World.LANDMARKS[World.TILE.BOREHOLE] = { num: 10, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'borehole', label: _('A Borehole')};
World.LANDMARKS[World.TILE.BATTLEFIELD] = { num: 5, minRadius: 18, maxRadius: World.RADIUS * 1.5, scene: 'battlefield', label: _('A Battlefield')};
World.LANDMARKS[World.TILE.SWAMP] = { num: 1, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'swamp', label: _('A Murky Swamp')};
World.LANDMARKS[World.TILE.EXECUTIONER] = { num: 1, minRadius: 28, maxRadius: 28, scene: 'executioner', 'label': _('A Ravaged Battleship')};
// Only add the cache if there is prestige data
if($SM.get('previous.stores')) {
World.LANDMARKS[World.TILE.CACHE] = { num: 1, minRadius: 10, maxRadius: World.RADIUS * 1.5, scene: 'cache', label: _('A Destroyed Village')};
}
if(typeof $SM.get('features.location.world') == 'undefined') {
$SM.set('features.location.world', true);
$SM.set('features.executioner', true);
$SM.setM('game.world', {
map: World.generateMap(),
mask: World.newMask()
});
}
else if (!$SM.get('features.executioner')) {
// Place the Executioner in previously generated maps that don't have it
const map = $SM.get('game.world.map');
const landmark = World.LANDMARKS[World.TILE.EXECUTIONER]
for(let l = 0; l < landmark.num; l++) {
World.placeLandmark(landmark.minRadius, landmark.maxRadius, World.TILE.EXECUTIONER, map);
}
$SM.set('game.world.map', map);
$SM.set('features.executioner', true);
}
// Create the World panel
this.panel = $('
').attr('id', "worldPanel").addClass('location').appendTo('#outerSlider');
// Create the shrink wrapper
var outer = $('
').attr('id', 'worldOuter').appendTo(this.panel);
// Create the bag panel
$('
').attr('id', 'bagspace-world').append($('
')).appendTo(outer);
$('
').attr('id', 'backpackTitle').appendTo(outer);
$('
').attr('id', 'backpackSpace').appendTo(outer);
$('
').attr('id', 'healthCounter').appendTo(outer);
Engine.updateOuterSlider();
// Map the ship and show compass tooltip
World.ship = World.mapSearch(World.TILE.SHIP,$SM.get('game.world.map'),1);
World.dir = World.compassDir(World.ship[0]);
// compass tooltip text
Room.compassTooltip(World.dir);
// Check if everything has been seen
World.testMap();
//subscribe to stateUpdates
$.Dispatch('stateUpdate').subscribe(World.handleStateUpdates);
},
clearDungeon: function() {
Engine.event('progress', 'dungeon cleared');
World.state.map[World.curPos[0]][World.curPos[1]] = World.TILE.OUTPOST;
World.drawRoad();
},
drawRoad: function() {
var findClosestRoad = function(startPos) {
// We'll search in a spiral to find the closest road tile
// We spiral out along manhattan distance contour
// lines to ensure we draw the shortest road possible.
// No attempt is made to reduce the search space for
// tiles outside the map.
var searchX, searchY, dtmp,
x = 0,
y = 0,
dx = 1,
dy = -1;
for (var i = 0; i < Math.pow(World.getDistance(startPos, World.VILLAGE_POS) + 2, 2); i++) {
searchX = startPos[0] + x;
searchY = startPos[1] + y;
if (0 < searchX && searchX < World.RADIUS * 2 && 0 < searchY && searchY < World.RADIUS * 2) {
// check for road
var tile = World.state.map[searchX][searchY];
if (
tile === World.TILE.ROAD ||
(tile === World.TILE.OUTPOST && !(x === 0 && y === 0)) || // outposts are connected to roads
tile === World.TILE.VILLAGE // all roads lead home
) {
return [searchX, searchY];
}
}
if (x === 0 || y === 0) {
// Turn the corner
dtmp = dx;
dx = -dy;
dy = dtmp;
}
if (x === 0 && y <= 0) {
x++;
} else {
x += dx;
y += dy;
}
}
return World.VILLAGE_POS;
};
var closestRoad = findClosestRoad(World.curPos);
var xDist = World.curPos[0] - closestRoad[0];
var yDist = World.curPos[1] - closestRoad[1];
var xDir = Math.abs(xDist)/xDist;
var yDir = Math.abs(yDist)/yDist;
var xIntersect, yIntersect;
if(Math.abs(xDist) > Math.abs(yDist)) {
xIntersect = closestRoad[0];
yIntersect = closestRoad[1] + yDist;
} else {
xIntersect = closestRoad[0] + xDist;
yIntersect = closestRoad[1];
}
for(var x = 0; x < Math.abs(xDist); x++) {
if(World.isTerrain(World.state.map[closestRoad[0] + (xDir*x)][yIntersect])) {
World.state.map[closestRoad[0] + (xDir*x)][yIntersect] = World.TILE.ROAD;
}
}
for(var y = 0; y < Math.abs(yDist); y++) {
if(World.isTerrain(World.state.map[xIntersect][closestRoad[1] + (yDir*y)])) {
World.state.map[xIntersect][closestRoad[1] + (yDir*y)] = World.TILE.ROAD;
}
}
World.drawMap();
},
updateSupplies: function() {
var supplies = $('div#bagspace-world > div');
if(!Path.outfit) {
Path.outfit = {};
}
// Add water
var water = $('div#supply_water');
if(World.water > 0 && water.length === 0) {
water = World.createItemDiv('water', World.water);
water.prependTo(supplies);
} else if(World.water > 0) {
$('div#supply_water', supplies).text(_('water:{0}' , World.water));
} else {
water.remove();
}
var total = 0;
for(var k in Path.outfit) {
var item = $('div#supply_' + k.replace(' ', '-'), supplies);
var num = Path.outfit[k];
total += num * Path.getWeight(k);
if(num > 0 && item.length === 0) {
item = World.createItemDiv(k, num);
if(k == 'cured meat' && World.water > 0) {
item.insertAfter(water);
} else if(k == 'cured meat') {
item.prependTo(supplies);
} else {
item.appendTo(supplies);
}
} else if(num > 0) {
$('div#' + item.attr('id'), supplies).text(_(k) + ':' + num);
} else {
item.remove();
}
}
// Update label
var t = _('pockets');
if($SM.get('stores.rucksack', true) > 0) {
t = _('rucksack');
}
$('#backpackTitle').text(t);
// Update bagspace
$('#backpackSpace').text(_('free {0}/{1}', Math.floor(Path.getCapacity() - total) , Path.getCapacity()));
},
setWater: function(w) {
World.water = w;
if(World.water > World.getMaxWater()) {
World.water = World.getMaxWater();
}
World.updateSupplies();
},
setHp: function(hp) {
if(typeof hp == 'number' && !isNaN(hp)) {
World.health = hp;
if(World.health > World.getMaxHealth()) {
World.health = World.getMaxHealth();
}
$('#healthCounter').text(_('hp: {0}/{1}', World.health , World.getMaxHealth()));
}
},
createItemDiv: function(name, num) {
var div = $('
').attr('id', 'supply_' + name.replace(' ', '-'))
.addClass('supplyItem')
.text(_('{0}:{1}',_(name), num));
return div;
},
moveNorth: function() {
Engine.log('North');
if(World.curPos[1] > 0) World.move(World.NORTH);
},
moveSouth: function() {
Engine.log('South');
if(World.curPos[1] < World.RADIUS * 2) World.move(World.SOUTH);
},
moveWest: function() {
Engine.log('West');
if(World.curPos[0] > 0) World.move(World.WEST);
},
moveEast: function() {
Engine.log('East');
if(World.curPos[0] < World.RADIUS * 2) World.move(World.EAST);
},
move: function(direction) {
var oldTile = World.state.map[World.curPos[0]][World.curPos[1]];
World.curPos[0] += direction[0];
World.curPos[1] += direction[1];
World.narrateMove(oldTile, World.state.map[World.curPos[0]][World.curPos[1]]);
World.lightMap(World.curPos[0], World.curPos[1], World.state.mask);
World.drawMap();
World.doSpace();
// play random footstep
var randomFootstep = Math.floor(Math.random() * 5) + 1;
AudioEngine.playSound(AudioLibrary['FOOTSTEPS_' + randomFootstep]);
if(World.checkDanger()) {
if(World.danger) {
Notifications.notify(World, _('dangerous to be this far from the village without proper protection'));
} else {
Notifications.notify(World, _('safer here'));
}
}
},
keyDown: function(event) {
switch(event.which) {
case 38: // Up
case 87:
World.moveNorth();
break;
case 40: // Down
case 83:
World.moveSouth();
break;
case 37: // Left
case 65:
World.moveWest();
break;
case 39: // Right
case 68:
World.moveEast();
break;
default:
break;
}
},
swipeLeft: function(e) {
World.moveWest();
},
swipeRight: function(e) {
World.moveEast();
},
swipeUp: function(e) {
World.moveNorth();
},
swipeDown: function(e) {
World.moveSouth();
},
click: function(event) {
var map = $('#map'),
// measure clicks relative to the centre of the current location
centreX = map.offset().left + map.width() * World.curPos[0] / (World.RADIUS * 2),
centreY = map.offset().top + map.height() * World.curPos[1] / (World.RADIUS * 2),
clickX = event.pageX - centreX,
clickY = event.pageY - centreY;
if (clickX > clickY && clickX < -clickY) {
World.moveNorth();
}
if (clickX < clickY && clickX > -clickY) {
World.moveSouth();
}
if (clickX < clickY && clickX < -clickY) {
World.moveWest();
}
if (clickX > clickY && clickX > -clickY) {
World.moveEast();
}
},
checkDanger: function() {
World.danger = typeof World.danger == 'undefined' ? false: World.danger;
if(!World.danger) {
if($SM.get('stores["i armour"]', true) === 0 && World.getDistance() >= 8) {
World.danger = true;
return true;
}
if($SM.get('stores["s armour"]', true) === 0 && World.getDistance() >= 18) {
World.danger = true;
return true;
}
} else {
if(World.getDistance() < 8) {
World.danger = false;
return true;
}
if(World.getDistance < 18 && $SM.get('stores["i armour"]', true) > 0) {
World.danger = false;
return true;
}
}
return false;
},
useSupplies: function() {
World.foodMove++;
World.waterMove++;
// Food
var movesPerFood = World.MOVES_PER_FOOD;
movesPerFood *= $SM.hasPerk('slow metabolism') ? 2 : 1;
if(World.foodMove >= movesPerFood) {
World.foodMove = 0;
var num = Path.outfit['cured meat'];
num--;
if(num === 0) {
Notifications.notify(World, _('the meat has run out'));
} else if(num < 0) {
// Starvation! Hooray!
num = 0;
if(!World.starvation) {
Notifications.notify(World, _('starvation sets in'));
World.starvation = true;
} else {
$SM.set('character.starved', $SM.get('character.starved', true));
$SM.add('character.starved', 1);
if($SM.get('character.starved') >= 10 && !$SM.hasPerk('slow metabolism')) {
$SM.addPerk('slow metabolism');
}
World.die();
return false;
}
} else {
World.starvation = false;
World.setHp(World.health + World.meatHeal());
}
Path.outfit['cured meat'] = num;
}
// Water
var movesPerWater = World.MOVES_PER_WATER;
movesPerWater *= $SM.hasPerk('desert rat') ? 2 : 1;
if(World.waterMove >= movesPerWater) {
World.waterMove = 0;
var water = World.water;
water--;
if(water === 0) {
Notifications.notify(World, _('there is no more water'));
} else if(water < 0) {
water = 0;
if(!World.thirst) {
Notifications.notify(World, _('the thirst becomes unbearable'));
World.thirst = true;
} else {
$SM.set('character.dehydrated', $SM.get('character.dehydrated', true));
$SM.add('character.dehydrated', 1);
if($SM.get('character.dehydrated') >= 10 && !$SM.hasPerk('desert rat')) {
$SM.addPerk('desert rat');
}
World.die();
return false;
}
} else {
World.thirst = false;
}
World.setWater(water);
World.updateSupplies();
}
return true;
},
meatHeal: function() {
return World.MEAT_HEAL * ($SM.hasPerk('gastronome') ? 2 : 1);
},
medsHeal: function() {
return World.MEDS_HEAL;
},
hypoHeal: () => World.HYPO_HEAL,
checkFight: function() {
World.fightMove = typeof World.fightMove == 'number' ? World.fightMove : 0;
World.fightMove++;
if(World.fightMove > World.FIGHT_DELAY) {
var chance = World.FIGHT_CHANCE;
chance *= $SM.hasPerk('stealthy') ? 0.5 : 1;
if(Math.random() < chance) {
World.fightMove = 0;
Events.triggerFight();
}
}
},
doSpace: function() {
var curTile = World.state.map[World.curPos[0]][World.curPos[1]];
if(curTile == World.TILE.VILLAGE) {
World.goHome();
} else if(curTile === World.TILE.EXECUTIONER) {
const scene = World.state.executioner ? 'executioner-antechamber' : 'executioner-intro';
const sceneData = Events.Executioner[scene];
Events.startEvent(sceneData);
} else if(typeof World.LANDMARKS[curTile] != 'undefined') {
if(curTile != World.TILE.OUTPOST || !World.outpostUsed()) {
Events.startEvent(Events.Setpieces[World.LANDMARKS[curTile].scene]);
}
} else {
if(World.useSupplies()) {
World.checkFight();
}
}
},
getDistance: function(from, to) {
from = from || World.curPos;
to = to || World.VILLAGE_POS;
return Math.abs(from[0] - to[0]) + Math.abs(from[1] - to[1]);
},
getTerrain: function() {
return World.state.map[World.curPos[0]][World.curPos[1]];
},
getDamage: function(thing) {
return World.Weapons[thing].damage;
},
narrateMove: function(oldTile, newTile) {
var msg = null;
switch(oldTile) {
case World.TILE.FOREST:
switch(newTile) {
case World.TILE.FIELD:
msg = _("the trees yield to dry grass. the yellowed brush rustles in the wind.");
break;
case World.TILE.BARRENS:
msg = _("the trees are gone. parched earth and blowing dust are poor replacements.");
break;
}
break;
case World.TILE.FIELD:
switch(newTile) {
case World.TILE.FOREST:
msg = _("trees loom on the horizon. grasses gradually yield to a forest floor of dry branches and fallen leaves.");
break;
case World.TILE.BARRENS:
msg = _("the grasses thin. soon, only dust remains.");
break;
}
break;
case World.TILE.BARRENS:
switch(newTile) {
case World.TILE.FIELD:
msg = _("the barrens break at a sea of dying grass, swaying in the arid breeze.");
break;
case World.TILE.FOREST:
msg = _("a wall of gnarled trees rises from the dust. their branches twist into a skeletal canopy overhead.");
break;
}
break;
}
if(msg != null) {
Notifications.notify(World, msg);
}
},
newMask: function() {
var mask = new Array(World.RADIUS * 2 + 1);
for(var i = 0; i <= World.RADIUS * 2; i++) {
mask[i] = new Array(World.RADIUS * 2 + 1);
}
World.lightMap(World.RADIUS, World.RADIUS, mask);
return mask;
},
lightMap: function(x, y, mask) {
var r = World.LIGHT_RADIUS;
r *= $SM.hasPerk('scout') ? 2 : 1;
World.uncoverMap(x, y, r, mask);
return mask;
},
uncoverMap: function(x, y, r, mask) {
mask[x][y] = true;
for(var i = -r; i <= r; i++) {
for(var j = -r + Math.abs(i); j <= r - Math.abs(i); j++) {
if(y + j >= 0 && y + j <= World.RADIUS * 2 &&
x + i <= World.RADIUS * 2 &&
x + i >= 0) {
mask[x+i][y+j] = true;
}
}
}
},
testMap: function() {
if(!World.seenAll) {
var dark;
var mask = $SM.get('game.world.mask');
loop:
for(var i = 0; i < mask.length; i++) {
for(var j = 0; j < mask[i].length; j++) {
if(!mask[i][j]) {
dark = true;
break loop;
}
}
}
World.seenAll = !dark;
}
},
applyMap: function() {
if(!World.seenAll){
var x,y,mask = $SM.get('game.world.mask');
do {
x = Math.floor(Math.random() * (World.RADIUS * 2 + 1));
y = Math.floor(Math.random() * (World.RADIUS * 2 + 1));
} while (mask[x][y]);
World.uncoverMap(x, y, 5, mask);
}
World.testMap();
},
generateMap: function() {
var map = new Array(World.RADIUS * 2 + 1);
for(var i = 0; i <= World.RADIUS * 2; i++) {
map[i] = new Array(World.RADIUS * 2 + 1);
}
// The Village is always at the exact center
// Spiral out from there
map[World.RADIUS][World.RADIUS] = World.TILE.VILLAGE;
for(var r = 1; r <= World.RADIUS; r++) {
for(var t = 0; t < r * 8; t++) {
var x, y;
if(t < 2 * r) {
x = World.RADIUS - r + t;
y = World.RADIUS - r;
} else if(t < 4 * r) {
x = World.RADIUS + r;
y = World.RADIUS - (3 * r) + t;
} else if(t < 6 * r) {
x = World.RADIUS + (5 * r) - t;
y = World.RADIUS + r;
} else {
x = World.RADIUS - r;
y = World.RADIUS + (7 * r) - t;
}
map[x][y] = World.chooseTile(x, y, map);
}
}
// Place landmarks
for(var k in World.LANDMARKS) {
var landmark = World.LANDMARKS[k];
for(var l = 0; l < landmark.num; l++) {
var pos = World.placeLandmark(landmark.minRadius, landmark.maxRadius, k, map);
}
}
return map;
},
mapSearch: function(target,map,required){
var max = World.LANDMARKS[target].num;
if(!max){
// this restrict the research to numerable landmarks
return null;
}
// restrict research if only a fixed number (usually 1) is required
max = (required) ? Math.min(required,max) : max;
var index = 0;
var targets = [];
search: // label for coordinate research
for(var i = 0; i <= World.RADIUS * 2; i++){
for(var j = 0; j <= World.RADIUS * 2; j++){
if(map[i][j].charAt(0) === target){
// search result is stored as an object;
// items are listed as they appear in the map, tl-br
// each item has relative coordinates and a compass-type direction
targets[index] = {
x : i - World.RADIUS,
y : j - World.RADIUS,
};
index++;
if(index === max){
// optimisation: stop the research if maximum number of items has been reached
break search;
}
}
}
}
return targets;
},
compassDir: function(pos){
var dir = '';
var horz = pos.x < 0 ? 'west' : 'east';
var vert = pos.y < 0 ? 'north' : 'south';
if(Math.abs(pos.x) / 2 > Math.abs(pos.y)) {
dir = horz;
} else if(Math.abs(pos.y) / 2 > Math.abs(pos.x)){
dir = vert;
} else {
dir = vert + horz;
}
return dir;
},
placeLandmark: function(minRadius, maxRadius, landmark, map) {
var x = World.RADIUS, y = World.RADIUS;
while(!World.isTerrain(map[x][y])) {
var r = Math.floor(Math.random() * (maxRadius - minRadius)) + minRadius;
var xDist = Math.floor(Math.random() * r);
var yDist = r - xDist;
if(Math.random() < 0.5) xDist = -xDist;
if(Math.random() < 0.5) yDist = -yDist;
x = World.RADIUS + xDist;
if(x < 0) x = 0;
if(x > World.RADIUS * 2) x = World.RADIUS * 2;
y = World.RADIUS + yDist;
if(y < 0) y = 0;
if(y > World.RADIUS * 2) y = World.RADIUS * 2;
}
map[x][y] = landmark;
return [x, y];
},
isTerrain: function(tile) {
return tile == World.TILE.FOREST || tile == World.TILE.FIELD || tile == World.TILE.BARRENS;
},
chooseTile: function(x, y, map) {
var adjacent = [
y > 0 ? map[x][y-1] : null,
y < World.RADIUS * 2 ? map[x][y+1] : null,
x < World.RADIUS * 2 ? map[x+1][y] : null,
x > 0 ? map[x-1][y] : null
];
var chances = {};
var nonSticky = 1;
var cur;
for(var i in adjacent) {
if(adjacent[i] == World.TILE.VILLAGE) {
// Village must be in a forest to maintain thematic consistency, yo.
return World.TILE.FOREST;
} else if(typeof adjacent[i] == 'string') {
cur = chances[adjacent[i]];
cur = typeof cur == 'number' ? cur : 0;
chances[adjacent[i]] = cur + World.STICKINESS;
nonSticky -= World.STICKINESS;
}
}
for(var t in World.TILE) {
var tile = World.TILE[t];
if(World.isTerrain(tile)) {
cur = chances[tile];
cur = typeof cur == 'number' ? cur : 0;
cur += World.TILE_PROBS[tile] * nonSticky;
chances[tile] = cur;
}
}
var list = [];
for(var j in chances) {
list.push(chances[j] + '' + j);
}
list.sort(function(a, b) {
var n1 = parseFloat(a.substring(0, a.length - 1));
var n2 = parseFloat(b.substring(0, b.length - 1));
return n2 - n1;
});
var c = 0;
var r = Math.random();
for(var l in list) {
var prob = list[l];
c += parseFloat(prob.substring(0,prob.length - 1));
if(r < c) {
return prob.charAt(prob.length - 1);
}
}
return World.TILE.BARRENS;
},
markVisited: function(x, y) {
World.state.map[x][y] = World.state.map[x][y] + '!';
},
drawMap: function() {
var map = $('#map');
if(map.length === 0) {
map = new $('
').attr('id', 'map').appendTo('#worldOuter');
// register click handler
map.click(World.click);
}
var mapString = "";
for(var j = 0; j <= World.RADIUS * 2; j++) {
for(var i = 0; i <= World.RADIUS * 2; i++) {
var ttClass = "";
if(i > World.RADIUS) {
ttClass += " left";
} else {
ttClass += " right";
}
if(j > World.RADIUS) {
ttClass += " top";
} else {
ttClass += " bottom";
}
if(World.curPos[0] == i && World.curPos[1] == j) {
mapString += '
@'+_('Wanderer')+'
';
} else if(World.state.mask[i][j]) {
var c = World.state.map[i][j];
switch(c) {
case World.TILE.VILLAGE:
mapString += '
' + c + ''+_('The Village')+'
';
break;
default:
if(typeof World.LANDMARKS[c] != 'undefined' && (c != World.TILE.OUTPOST || !World.outpostUsed(i, j))) {
mapString += '
' + c + '' + World.LANDMARKS[c].label + '
';
} else {
if(c.length > 1) {
c = c[0];
}
mapString += c;
}
break;
}
} else {
mapString += ' ';
}
}
mapString += '
';
}
map.html(mapString);
},
die: function() {
if(!World.dead) {
World.dead = true;
Engine.log('player death');
Engine.event('game event', 'death');
Engine.keyLock = true;
// Dead! Discard any world changes and go home
Notifications.notify(World, _('the world fades'));
World.state = null;
Path.outfit = {};
$SM.remove('outfit');
AudioEngine.playSound(AudioLibrary.DEATH);
$('#outerSlider').animate({opacity: '0'}, 600, 'linear', function() {
$('#outerSlider').css('left', '0px');
$('#locationSlider').css('left', '0px');
$('#storesContainer').css({'top': '0px', 'right': '0px'});
Engine.activeModule = Room;
$('div.headerButton').removeClass('selected');
Room.tab.addClass('selected');
Engine.setTimeout(function(){
Room.onArrival();
$('#outerSlider').animate({opacity:'1'}, 600, 'linear');
Button.cooldown($('#embarkButton'));
Engine.keyLock = false;
Engine.tabNavigation = true;
}, 2000, true);
});
}
},
goHome: function() {
// Home safe! Commit the changes.
$SM.setM('game.world', World.state);
World.testMap();
if(World.state.sulphurmine && $SM.get('game.buildings["sulphur mine"]', true) === 0) {
$SM.add('game.buildings["sulphur mine"]', 1);
Engine.event('progress', 'sulphur mine');
}
if(World.state.ironmine && $SM.get('game.buildings["iron mine"]', true) === 0) {
$SM.add('game.buildings["iron mine"]', 1);
Engine.event('progress', 'iron mine');
}
if(World.state.coalmine && $SM.get('game.buildings["coal mine"]', true) === 0) {
$SM.add('game.buildings["coal mine"]', 1);
Engine.event('progress', 'coal mine');
}
if(World.state.ship && !$SM.get('features.location.spaceShip')) {
Ship.init();
Engine.event('progress', 'ship');
}
if (World.state.executioner && !$SM.get('features.location.fabricator')) {
Fabricator.init();
Notifications.notify(null, _('builder knows the strange device when she sees it. takes it for herself real quick. doesn’t ask where it came from.'));
Engine.event('progress', 'fabricator');
}
World.redeemBlueprints();
World.state = null;
if(Path.outfit['cured meat'] > 0) {
Button.setDisabled($('#embarkButton'), false);
}
World.returnOutfit();
$('#outerSlider').animate({left: '0px'}, 300);
Engine.activeModule = Path;
Path.onArrival();
Engine.restoreNavigation = true;
},
redeemBlueprints: () => {
let redeemed = false;
const redeem = (blueprint, item) => {
if (Path.outfit[blueprint]) {
$SM.set(`character.blueprints['${item}']`, true);
delete Path.outfit[blueprint];
redeemed = true;
}
};
redeem('hypo blueprint', 'hypo');
redeem('kinetic armour blueprint', 'kinetic armour');
redeem('disruptor blueprint', 'disruptor');
redeem('plasma rifle blueprint', 'plasma rifle');
redeem('stim blueprint', 'stim');
redeem('glowstone blueprint', 'glowstone');
if (redeemed) {
Notifications.notify(null, 'blueprints feed into the fabricator data port. possibilities grow.');
}
},
returnOutfit: () => {
for(var k in Path.outfit) {
$SM.add('stores["'+k+'"]', Path.outfit[k]);
if(World.leaveItAtHome(k)) {
Path.outfit[k] = 0;
}
}
},
leaveItAtHome: function(thing) {
return thing != 'cured meat' && thing != 'bullets' && thing != 'energy cell' &&
thing != 'charm' && thing != 'medicine' && thing != 'stim' && thing != 'hypo' &&
typeof World.Weapons[thing] == 'undefined' && typeof Room.Craftables[thing] == 'undefined';
},
getMaxHealth: function() {
if($SM.get('stores["kinetic armour"]', true) > 0) {
return World.BASE_HEALTH + 75;
} else if($SM.get('stores["s armour"]', true) > 0) {
return World.BASE_HEALTH + 35;
} else if($SM.get('stores["i armour"]', true) > 0) {
return World.BASE_HEALTH + 15;
} else if($SM.get('stores["l armour"]', true) > 0) {
return World.BASE_HEALTH + 5;
}
return World.BASE_HEALTH;
},
getHitChance: function() {
if($SM.hasPerk('precise')) {
return World.BASE_HIT_CHANCE + 0.1;
}
return World.BASE_HIT_CHANCE;
},
getMaxWater: function() {
if($SM.get('stores["fluid recycler"]', true) > 0) {
return World.BASE_WATER + 100;
} else if($SM.get('stores["water tank"]', true) > 0) {
return World.BASE_WATER + 50;
} else if($SM.get('stores.cask', true) > 0) {
return World.BASE_WATER + 20;
} else if($SM.get('stores.waterskin', true) > 0) {
return World.BASE_WATER + 10;
}
return World.BASE_WATER;
},
outpostUsed: function(x, y) {
x = typeof x == 'number' ? x : World.curPos[0];
y = typeof y == 'number' ? y : World.curPos[1];
var used = World.usedOutposts[x + ',' + y];
return typeof used != 'undefined' && used === true;
},
useOutpost: function() {
Notifications.notify(null, _('water replenished'));
World.setWater(World.getMaxWater());
// Mark this outpost as used
World.usedOutposts[World.curPos[0] + ',' + World.curPos[1]] = true;
},
onArrival: function() {
Engine.tabNavigation = false;
// Clear the embark cooldown
Button.clearCooldown($('#embarkButton'));
Engine.keyLock = false;
// Explore in a temporary world-state. We'll commit the changes if you return home safe.
World.state = $.extend(true, {}, $SM.get('game.world'));
World.setWater(World.getMaxWater());
World.setHp(World.getMaxHealth());
World.foodMove = 0;
World.waterMove = 0;
World.starvation = false;
World.thirst = false;
World.usedOutposts = {};
World.curPos = World.copyPos(World.VILLAGE_POS);
World.drawMap();
World.setTitle();
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_WORLD);
World.dead = false;
$('div#bagspace-world > div').empty();
World.updateSupplies();
$('#bagspace-world').width($('#map').width());
},
setTitle: function() {
document.title = _('A Barren World');
},
copyPos: function(pos) {
return [pos[0], pos[1]];
},
handleStateUpdates: function(e){
}
};