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){ } };