waves/public/assets/g/adarkroom/script/world.js
2025-04-09 17:11:14 -05:00

1110 lines
33 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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&nbsp;Outpost') };
World.LANDMARKS[World.TILE.IRON_MINE] = { num: 1, minRadius: 5, maxRadius: 5, scene: 'ironmine', label: _('Iron&nbsp;Mine') };
World.LANDMARKS[World.TILE.COAL_MINE] = { num: 1, minRadius: 10, maxRadius: 10, scene: 'coalmine', label: _('Coal&nbsp;Mine') };
World.LANDMARKS[World.TILE.SULPHUR_MINE] = { num: 1, minRadius: 20, maxRadius: 20, scene: 'sulphurmine', label: _('Sulphur&nbsp;Mine') };
World.LANDMARKS[World.TILE.HOUSE] = { num: 10, minRadius: 0, maxRadius: World.RADIUS * 1.5, scene: 'house', label: _('An&nbsp;Old&nbsp;House') };
World.LANDMARKS[World.TILE.CAVE] = { num: 5, minRadius: 3, maxRadius: 10, scene: 'cave', label: _('A&nbsp;Damp&nbsp;Cave') };
World.LANDMARKS[World.TILE.TOWN] = { num: 10, minRadius: 10, maxRadius: 20, scene: 'town', label: _('An&nbsp;Abandoned&nbsp;Town') };
World.LANDMARKS[World.TILE.CITY] = { num: 20, minRadius: 20, maxRadius: World.RADIUS * 1.5, scene: 'city', label: _('A&nbsp;Ruined&nbsp;City') };
World.LANDMARKS[World.TILE.SHIP] = { num: 1, minRadius: 28, maxRadius: 28, scene: 'ship', label: _('A&nbsp;Crashed&nbsp;Starship')};
World.LANDMARKS[World.TILE.BOREHOLE] = { num: 10, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'borehole', label: _('A&nbsp;Borehole')};
World.LANDMARKS[World.TILE.BATTLEFIELD] = { num: 5, minRadius: 18, maxRadius: World.RADIUS * 1.5, scene: 'battlefield', label: _('A&nbsp;Battlefield')};
World.LANDMARKS[World.TILE.SWAMP] = { num: 1, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'swamp', label: _('A&nbsp;Murky&nbsp;Swamp')};
World.LANDMARKS[World.TILE.EXECUTIONER] = { num: 1, minRadius: 28, maxRadius: 28, scene: 'executioner', 'label': _('A&nbsp;Ravaged&nbsp;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&nbsp;Destroyed&nbsp;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 = $('<div>').attr('id', "worldPanel").addClass('location').appendTo('#outerSlider');
// Create the shrink wrapper
var outer = $('<div>').attr('id', 'worldOuter').appendTo(this.panel);
// Create the bag panel
$('<div>').attr('id', 'bagspace-world').append($('<div>')).appendTo(outer);
$('<div>').attr('id', 'backpackTitle').appendTo(outer);
$('<div>').attr('id', 'backpackSpace').appendTo(outer);
$('<div>').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 = $('<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 $('<div>').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 += '<span class="landmark">@<div class="tooltip ' + ttClass + '">'+_('Wanderer')+'</div></span>';
} else if(World.state.mask[i][j]) {
var c = World.state.map[i][j];
switch(c) {
case World.TILE.VILLAGE:
mapString += '<span class="landmark">' + c + '<div class="tooltip' + ttClass + '">'+_('The&nbsp;Village')+'</div></span>';
break;
default:
if(typeof World.LANDMARKS[c] != 'undefined' && (c != World.TILE.OUTPOST || !World.outpostUsed(i, j))) {
mapString += '<span class="landmark">' + c + '<div class="tooltip' + ttClass + '">' + World.LANDMARKS[c].label + '</div></span>';
} else {
if(c.length > 1) {
c = c[0];
}
mapString += c;
}
break;
}
} else {
mapString += '&nbsp;';
}
}
mapString += '<br/>';
}
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. doesnt 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){
}
};