').attr('id', 'buttons').appendTo(Events.eventPanel());
+ Events.loadScene('start');
+ $('div#wrapper').append(Events.eventPanel());
+ Events.eventPanel().animate({opacity: 1}, Events._PANEL_FADE, 'linear');
+ }
+ },
+
+ scheduleNextEvent: function(scale) {
+ var nextEvent = Math.floor(Math.random()*(Events._EVENT_TIME_RANGE[1] - Events._EVENT_TIME_RANGE[0])) + Events._EVENT_TIME_RANGE[0];
+ if(scale > 0) { nextEvent *= scale }
+ Engine.log('next event scheduled in ' + nextEvent + ' minutes');
+ Events._eventTimeout = setTimeout(Events.triggerEvent, nextEvent * 60 * 1000);
+ },
+
+ endEvent: function() {
+ Events.eventPanel().animate({opacity:0}, Events._PANEL_FADE, 'linear', function() {
+ Events.eventPanel().remove();
+ Events.activeEvent().eventPanel = null;
+ Events.eventStack.shift();
+ Engine.log(Events.eventStack.length + ' events remaining');
+ Engine.keyLock = false;
+ // Force refocus on the body. I hate you, IE.
+ $('body').focus();
+ });
+ }
+};
\ No newline at end of file
diff --git a/script/events/encounters.js b/script/events/encounters.js
new file mode 100644
index 000000000..891f48dc7
--- /dev/null
+++ b/script/events/encounters.js
@@ -0,0 +1,325 @@
+/**
+ * Events that can occur when wandering around the world
+ **/
+Events.Encounters = [
+ /* Tier 1 */
+ { /* Snarling Beast */
+ title: 'A Snarling Beast',
+ isAvailable: function() {
+ return World.getDistance() <= 10 && World.getTerrain() == World.TILE.FOREST;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'snarling beast',
+ char: 'B',
+ damage: 1,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 5,
+ loot: {
+ 'fur': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'meat': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'teeth': {
+ min: 1,
+ max: 3,
+ chance: 0.8
+ }
+ },
+ notification: 'a snarling beast leaps out of the underbrush'
+ }
+ }
+ },
+ { /* Gaunt Man */
+ title: 'A Gaunt Man',
+ isAvailable: function() {
+ return World.getDistance() <= 10 && World.getTerrain() == World.TILE.BARRENS;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'gaunt man',
+ char: 'G',
+ damage: 2,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 6,
+ loot: {
+ 'cloth': {
+ min: 1,
+ max: 3,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 1,
+ max: 2,
+ chance: 0.8
+ },
+ 'leather': {
+ min: 1,
+ max: 2,
+ chance: 0.5
+ }
+ },
+ notification: 'a gaunt man approaches, a crazed look in his eye'
+ }
+ }
+ },
+ { /* Strange Bird */
+ title: 'A Strange Bird',
+ isAvailable: function() {
+ return World.getDistance() <= 10 && World.getTerrain() == World.TILE.FIELD;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'strange bird',
+ char: 'B',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 4,
+ loot: {
+ 'scales': {
+ min: 1,
+ max: 3,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 1,
+ max: 2,
+ chance: 0.5
+ },
+ 'meat': {
+ min: 1,
+ max: 3,
+ chance: 0.8
+ }
+ },
+ notification: 'a strange looking bird speeds across the plains'
+ }
+ }
+ },
+ /* Tier 2*/
+ { /* Man-eater */
+ title: 'A Man-Eater',
+ isAvailable: function() {
+ return World.getDistance() > 10 && World.getDistance() <= 20 && World.getTerrain() == World.TILE.FOREST;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'man-eater',
+ char: 'E',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 25,
+ loot: {
+ 'fur': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'teeth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ }
+ },
+ notification: 'a large creature attacks, claws freshly bloodied'
+ }
+ }
+ },
+ { /* Scavenger */
+ title: 'A Scavenger',
+ isAvailable: function() {
+ return World.getDistance() > 10 && World.getDistance() <= 20 && World.getTerrain() == World.TILE.BARRENS;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'scavenger',
+ char: 'S',
+ damage: 4,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 30,
+ loot: {
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'leather': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'iron': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ notification: 'a scavenger draws close, hoping for an easy score'
+ }
+ }
+ },
+ { /* Huge Lizard */
+ title: 'A Huge Lizard',
+ isAvailable: function() {
+ return World.getDistance() > 10 && World.getDistance() <= 20 && World.getTerrain() == World.TILE.FIELD;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'lizard',
+ char: 'L',
+ damage: 5,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 20,
+ loot: {
+ 'scales': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 5,
+ max: 10,
+ chance: 0.5
+ },
+ 'meat': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ }
+ },
+ notification: 'the grass thrashes wildly as a huge lizard pushes through'
+ }
+ }
+ },
+ /* Tier 3*/
+ { /* Feral Terror */
+ title: 'A Feral Terror',
+ isAvailable: function() {
+ return World.getDistance() > 20 && World.getTerrain() == World.TILE.FOREST;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'feral terror',
+ char: 'F',
+ damage: 6,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 45,
+ loot: {
+ 'fur': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'teeth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ }
+ },
+ notification: 'a beast, wilder than imagining, erupts out of the foliage'
+ }
+ }
+ },
+ { /* Soldier */
+ title: 'A Soldier',
+ isAvailable: function() {
+ return World.getDistance() > 20 && World.getTerrain() == World.TILE.BARRENS;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'soldier',
+ ranged: true,
+ char: 'D',
+ damage: 8,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 50,
+ loot: {
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ notification: 'a soldier opens fire from across the desert'
+ }
+ }
+ },
+ { /* Sniper */
+ title: 'A Sniper',
+ isAvailable: function() {
+ return World.getDistance() > 20 && World.getTerrain() == World.TILE.FIELD;
+ },
+ scenes: {
+ 'start': {
+ combat: true,
+ enemy: 'sniper',
+ char: 'S',
+ damage: 15,
+ hit: 0.8,
+ attackDelay: 4,
+ health: 30,
+ ranged: true,
+ loot: {
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ notification: 'a shot rings out, from somewhere in the long grass'
+ }
+ }
+ },
+];
\ No newline at end of file
diff --git a/script/events/global.js b/script/events/global.js
new file mode 100644
index 000000000..64f402158
--- /dev/null
+++ b/script/events/global.js
@@ -0,0 +1,65 @@
+/**
+ * Events that can occur when any module is active (Except World. It's special.)
+ **/
+Events.Global = [
+ { /* The Thief */
+ title: 'The Thief',
+ isAvailable: function() {
+ return (Engine.activeModule == Room || Engine.activeModule == Outside) && State.thieves == 1;
+ },
+ scenes: {
+ 'start': {
+ text: [
+ 'the villagers haul a filthy man out of the store room.',
+ "say his folk have been skimming the supplies.",
+ 'say he should be strung up as an example.'
+ ],
+ notification: 'a thief is caught',
+ buttons: {
+ 'kill': {
+ text: 'hang him',
+ nextScene: {1: 'hang'}
+ },
+ 'spare': {
+ text: 'spare him',
+ nextScene: {1: 'spare'}
+ }
+ }
+ },
+ 'hang': {
+ text: [
+ 'the villagers hang the thief high in front of the store room.',
+ 'the point is made. in the next few days, the missing supplies are returned.'
+ ],
+ onLoad: function() {
+ State.thieves = 2;
+ Engine.removeIncome('thieves');
+ Engine.addStores(State.stolen);
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'spare': {
+ text: [
+ "the man says he's grateful. says he won't come around any more.",
+ "shares what he knows about sneaking before he goes."
+ ],
+ onLoad: function() {
+ State.thieves = 2;
+ Engine.removeIncome('thieves');
+ Engine.addPerk('stealthy');
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ }
+];
\ No newline at end of file
diff --git a/script/events/outside.js b/script/events/outside.js
new file mode 100644
index 000000000..c6a1db02e
--- /dev/null
+++ b/script/events/outside.js
@@ -0,0 +1,127 @@
+/**
+ * Events that can occur when the Outside module is active
+ **/
+Events.Outside = [
+ { /* Ruined traps */
+ title: 'A Ruined Trap',
+ isAvailable: function() {
+ return Engine.activeModule == Outside && Outside.numBuilding('trap') > 0;
+ },
+ scenes: {
+ 'start': {
+ text: [
+ 'some of the traps have been torn apart.',
+ 'large prints lead away, into the forest.'
+ ],
+ onLoad: function() {
+ var numWrecked = Math.floor(Math.random() * Outside.numBuilding('trap')) + 1;
+ Outside.addBuilding('trap', -numWrecked);
+ Outside.updateVillage();
+ Outside.updateTrapButton();
+ },
+ notification: 'some traps have been destroyed',
+ buttons: {
+ 'track': {
+ text: 'track them',
+ nextScene: {0.5: 'nothing', 1: 'catch'}
+ },
+ 'ignore': {
+ text: 'ignore them',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'nothing': {
+ text: [
+ 'the tracks disappear after just a few minutes.',
+ 'the forest is silent.'
+ ],
+ buttons: {
+ 'end': {
+ text: 'go home',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'catch': {
+ text: [
+ 'not far from the village lies a large beast, its fur matted with blood.',
+ 'it puts up little resistance before the knife.'
+ ],
+ reward: {
+ fur: 100,
+ meat: 100,
+ teeth: 10
+ },
+ buttons: {
+ 'end': {
+ text: 'go home',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ { /* Beast attack */
+ title: 'A Beast Attack',
+ isAvailable: function() {
+ return Engine.activeModule == Outside && Outside.getPopulation() > 0;
+ },
+ scenes: {
+ 'start': {
+ text: [
+ 'a pack of snarling beasts pours out of the trees.',
+ 'the fight is short and bloody, but the beasts are repelled.',
+ 'the villagers retreat to mourn the dead.'
+ ],
+ onLoad: function() {
+ var numKilled = Math.floor(Math.random() * 10) + 1;
+ Outside.killVillagers(numKilled);
+ },
+ reward: {
+ fur: 100,
+ meat: 100,
+ teeth: 10
+ },
+ buttons: {
+ 'end': {
+ text: 'go home',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ { /* Soldier attack */
+ title: 'A Military Raid',
+ isAvailable: function() {
+ return Engine.activeModule == Outside && Outside.getPopulation() > 0 && State.cityCleared;
+ },
+ scenes: {
+ 'start': {
+ text: [
+ 'a gunshot rings through the trees.',
+ 'well armed men charge out of the forest, firing into the crowd.',
+ 'after a skirmish they are driven away, but not without losses.'
+ ],
+ onLoad: function() {
+ var numKilled = Math.floor(Math.random() * 40) + 1;
+ Outside.killVillagers(numKilled);
+ },
+ reward: {
+ bullets: 10,
+ 'cured meat': 50
+ },
+ buttons: {
+ 'end': {
+ text: 'go home',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ }
+];
+
\ No newline at end of file
diff --git a/script/events/room.js b/script/events/room.js
new file mode 100644
index 000000000..242342fbe
--- /dev/null
+++ b/script/events/room.js
@@ -0,0 +1,506 @@
+/**
+ * Events that can occur when the Room module is active
+ **/
+Events.Room = [
+ { /* The Nomad -- Merchant */
+ title: 'The Nomad',
+ isAvailable: function() {
+ return Engine.activeModule == Room && Engine.getStore('fur') > 0;
+ },
+ scenes: {
+ 'start': {
+ text: [
+ 'a nomad shuffles into view, laden with makeshift bags bound with rough twine.',
+ "won't say from where he came, but it's clear that he's not staying."
+ ],
+ notification: 'a nomad arrives, looking to trade',
+ buttons: {
+ 'buyScales': {
+ text: 'buy scales',
+ cost: { 'fur': 100 },
+ reward: { 'scales': 1 }
+ },
+ 'buyTeeth': {
+ text: 'buy teeth',
+ cost: { 'fur': 200 },
+ reward: { 'teeth': 1 }
+ },
+ 'buyBait': {
+ text: 'buy bait',
+ cost: { 'fur': 5 },
+ reward: { 'bait': 1 },
+ notification: 'traps are more effective with bait.'
+ },
+ 'buyCompass': {
+ available: function() {
+ return Engine.getStore('compass') < 1;
+ },
+ text: 'buy compass',
+ cost: { fur: 300, scales: 15, teeth: 5 },
+ reward: { 'compass': 1 },
+ notification: 'the old compass is dented and dusty, but it looks to work.',
+ onChoose: Engine.openPath
+ },
+ 'goodbye': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ }, { /* Noises Outside -- gain wood/fur */
+ title: 'Noises',
+ isAvailable: function() {
+ return Engine.activeModule == Room && Engine.storeAvailable('wood');
+ },
+ scenes: {
+ 'start': {
+ text: [
+ 'through the walls, shuffling noises can be heard.',
+ "can't tell what they're up to."
+ ],
+ notification: 'strange noises can be heard through the walls',
+ buttons: {
+ 'investigate': {
+ text: 'investigate',
+ nextScene: { 0.3: 'stuff', 1: 'nothing' }
+ },
+ 'ignore': {
+ text: 'ignore them',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'nothing': {
+ text: [
+ 'vague shapes move, just out of sight.',
+ 'the sounds stop.'
+ ],
+ buttons: {
+ 'backinside': {
+ text: 'go back inside',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'stuff': {
+ reward: { wood: 100, fur: 10 },
+ text: [
+ 'a bundle of stick lies just beyond the threshold, wrapped in course furs.',
+ 'the night is silent.'
+ ],
+ buttons: {
+ 'backinside': {
+ text: 'go back inside',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ { /* Noises Inside -- trade wood for better good */
+ title: 'Noises',
+ isAvailable: function() {
+ return Engine.activeModule == Room && Engine.storeAvailable('wood');
+ },
+ scenes: {
+ start: {
+ text: [
+ 'scratching noises can be heard from the store room.',
+ 'something\'s in there.'
+ ],
+ notification: 'something\'s in the store room',
+ buttons: {
+ 'investigate': {
+ text: 'investigate',
+ nextScene: { 0.5: 'scales', 0.8: 'teeth', 1: 'cloth' }
+ },
+ 'ignore': {
+ text: 'ignore them',
+ nextScene: 'end'
+ }
+ }
+ },
+ scales: {
+ text: [
+ 'some wood is missing.',
+ 'the ground is littered with small scales'
+ ],
+ onLoad: function() {
+ var numWood = Engine.getStore('wood');
+ numWood = Math.floor(numWood * 0.1);
+ if(numWood == 0) numWood = 1;
+ var numScales = Math.floor(numWood / 5);
+ if(numScales == 0) numScales = 1;
+ Engine.addStores({wood: -numWood, scales: numScales});
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ teeth: {
+ text: [
+ 'some wood is missing.',
+ 'the ground is littered with small teeth'
+ ],
+ onLoad: function() {
+ var numWood = Engine.getStore('wood');
+ numWood = Math.floor(numWood * 0.1);
+ if(numWood == 0) numWood = 1;
+ var numTeeth = Math.floor(numWood / 5);
+ if(numTeeth == 0) numTeeth = 1;
+ Engine.addStores({wood: -numWood, teeth: numTeeth});
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ cloth: {
+ text: [
+ 'some wood is missing.',
+ 'the ground is littered with scraps of cloth'
+ ],
+ onLoad: function() {
+ var numWood = Engine.getStore('wood');
+ numWood = Math.floor(numWood * 0.1);
+ if(numWood == 0) numWood = 1;
+ var numCloth = Math.floor(numWood / 5);
+ if(numCloth == 0) numCloth = 1;
+ Engine.addStores({wood: -numWood, cloth: numCloth});
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ { /* The Beggar -- trade fur for better good */
+ title: 'The Beggar',
+ isAvailable: function() {
+ return Engine.activeModule == Room && Engine.storeAvailable('fur');
+ },
+ scenes: {
+ start: {
+ text: [
+ 'a beggar arrives.',
+ 'asks for any spare furs to keep him warm at night.'
+ ],
+ notification: 'a beggar arrives',
+ buttons: {
+ '50furs': {
+ text: 'give 50',
+ cost: {fur: 50},
+ nextScene: { 0.5: 'scales', 0.8: 'teeth', 1: 'cloth' }
+ },
+ '100furs': {
+ text: 'give 100',
+ cost: {fur: 100},
+ nextScene: { 0.5: 'teeth', 0.8: 'scales', 1: 'cloth' }
+ },
+ 'deny': {
+ text: 'turn him away',
+ nextScene: 'end'
+ }
+ }
+ },
+ scales: {
+ reward: { scales: 20 },
+ text: [
+ 'the beggar thanks you.',
+ 'leaves a pile of small scales behind.'
+ ],
+ buttons: {
+ 'leave': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ },
+ teeth: {
+ reward: { teeth: 20 },
+ text: [
+ 'the beggar thanks you.',
+ 'leaves a pile of small teeth behind.'
+ ],
+ buttons: {
+ 'leave': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ },
+ cloth: {
+ reward: { cloth: 20 },
+ text: [
+ 'the beggar thanks you.',
+ 'leaves some scraps of cloth behind.'
+ ],
+ buttons: {
+ 'leave': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ { /* Mysterious Wanderer -- wood gambling */
+ title: 'The Mysterious Wanderer',
+ isAvailable: function() {
+ return Engine.activeModule == Room && Engine.storeAvailable('wood');
+ },
+ scenes: {
+ start: {
+ text: [
+ 'a wanderer arrives with an empty cart. says if he leaves with wood, he\'ll be back with more.',
+ "builder's not sure he's to be trusted."
+ ],
+ notification: 'a mysterious wanderer arrives',
+ buttons: {
+ '100wood': {
+ text: 'give 100',
+ cost: {wood: 100},
+ nextScene: { 1: '100wood'}
+ },
+ '500wood': {
+ text: 'give 500',
+ cost: {wood: 500},
+ nextScene: { 1: '500wood' }
+ },
+ 'deny': {
+ text: 'turn him away',
+ nextScene: 'end'
+ }
+ }
+ },
+ '100wood': {
+ text: [
+ 'the wanderer leaves, cart loaded with wood',
+ ],
+ onLoad: function() {
+ if(Math.random() < 0.5) {
+ setTimeout(function() {
+ Engine.addStore('wood', 300);
+ Notifications.notify(Room, 'the mysterious wanderer returns, cart piled high with wood.');
+ }, 60 * 1000);
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ },
+ '500wood': {
+ text: [
+ 'the wanderer leaves, cart loaded with wood',
+ ],
+ onLoad: function() {
+ if(Math.random() < 0.3) {
+ setTimeout(function() {
+ Engine.addStore('wood', 1500);
+ Notifications.notify(Room, 'the mysterious wanderer returns, cart piled high with wood.');
+ }, 60 * 1000);
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ { /* Mysterious Wanderer -- fur gambling */
+ title: 'The Mysterious Wanderer',
+ isAvailable: function() {
+ return Engine.activeModule == Room && Engine.storeAvailable('fur');
+ },
+ scenes: {
+ start: {
+ text: [
+ 'a wanderer arrives with an empty cart. says if she leaves with furs, she\'ll be back with more.',
+ "builder's not sure she's to be trusted."
+ ],
+ notification: 'a mysterious wanderer arrives',
+ buttons: {
+ '100fur': {
+ text: 'give 100',
+ cost: {fur: 100},
+ nextScene: { 1: '100fur'}
+ },
+ '500fur': {
+ text: 'give 500',
+ cost: {fur: 500},
+ nextScene: { 1: '500fur' }
+ },
+ 'deny': {
+ text: 'turn her away',
+ nextScene: 'end'
+ }
+ }
+ },
+ '100fur': {
+ text: [
+ 'the wanderer leaves, cart loaded with furs',
+ ],
+ onLoad: function() {
+ if(Math.random() < 0.5) {
+ setTimeout(function() {
+ Engine.addStore('fur', 300);
+ Notifications.notify(Room, 'the mysterious wanderer returns, cart piled high with furs.');
+ }, 60 * 1000);
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ },
+ '500fur': {
+ text: [
+ 'the wanderer leaves, cart loaded with furs',
+ ],
+ onLoad: function() {
+ if(Math.random() < 0.3) {
+ setTimeout(function() {
+ Engine.addStore('fur', 1500);
+ Notifications.notify(Room, 'the mysterious wanderer returns, cart piled high with furs.');
+ }, 60 * 1000);
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ { /* The Scout -- Map Merchant */
+ title: 'The Scout',
+ isAvailable: function() {
+ return Engine.activeModule == Room && typeof State.world == 'object';
+ },
+ scenes: {
+ 'start': {
+ text: [
+ "the scout says she's been all over.",
+ "willing to talk about it, for a price."
+ ],
+ notification: 'a scout stops for the night',
+ buttons: {
+ 'buyMap': {
+ text: 'buy map',
+ cost: { 'fur': 200, 'scales': 10 },
+ notification: 'the map uncovers a bit of the world',
+ onChoose: World.applyMap
+ },
+ 'learn': {
+ text: 'learn scouting',
+ cost: { 'fur': 1000, 'scales': 50, 'teeth': 20 },
+ available: function() {
+ return !Engine.hasPerk('scout');
+ },
+ onChoose: function() {
+ Engine.addPerk('scout');
+ }
+ },
+ 'leave': {
+ text: 'say goodbye',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ { /* The Wandering Master */
+ title: 'The Master',
+ isAvailable: function() {
+ return Engine.activeModule == Room && typeof State.world == 'object';
+ },
+ scenes: {
+ 'start': {
+ text: [
+ 'an old wanderer arrives.',
+ 'he smiles warmly and asks for lodgings for the night.'
+ ],
+ notification: 'an old wanderer arrives',
+ buttons: {
+ 'agree': {
+ text: 'agree',
+ cost: {
+ 'cured meat': 100,
+ 'fur': 100,
+ 'torch': 1
+ },
+ nextScene: {1: 'agree'}
+ },
+ 'deny': {
+ text: 'turn him away',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'agree': {
+ text: [
+ 'in exchange, the wanderer offers his wisdom.'
+ ],
+ buttons: {
+ 'evasion': {
+ text: 'evasion',
+ available: function() {
+ return !Engine.hasPerk('evasive');
+ },
+ onChoose: function() {
+ Engine.addPerk('evasive');
+ },
+ nextScene: 'end'
+ },
+ 'precision': {
+ text: 'precision',
+ available: function() {
+ return !Engine.hasPerk('precise');
+ },
+ onChoose: function() {
+ Engine.addPerk('precise');
+ },
+ nextScene: 'end'
+ },
+ 'force': {
+ text: 'force',
+ available: function() {
+ return !Engine.hasPerk('barbarian');
+ },
+ onChoose: function() {
+ Engine.addPerk('barbarian');
+ },
+ nextScene: 'end'
+ },
+ 'nothing': {
+ text: 'nothing',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/script/events/setpieces.js b/script/events/setpieces.js
new file mode 100644
index 000000000..1cf518d0a
--- /dev/null
+++ b/script/events/setpieces.js
@@ -0,0 +1,2730 @@
+/**
+ * Events that only occur at specific times. Launched manually.
+ **/
+Events.Setpieces = {
+ "outpost": { /* Friendly Outpost */
+ title: 'An Outpost',
+ scenes: {
+ 'start': {
+ text: [
+ 'a safe place in the wilds.'
+ ],
+ notification: 'a safe place in the wilds.',
+ loot: {
+ 'cured meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ }
+ },
+ onLoad: function() {
+ World.useOutpost();
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "swamp": { /* Swamp */
+ title: 'A Murky Swamp',
+ scenes: {
+ 'start': {
+ text: [
+ 'rotting reeds rise out of the swampy earth.',
+ 'a lone frog sits in the muck, silently.'
+ ],
+ notification: 'a swamp festers in the stagnant air.',
+ buttons: {
+ 'enter': {
+ text: 'enter',
+ nextScene: {1: 'cabin'}
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'cabin': {
+ text: [
+ 'deep in the swamp is a moss-covered cabin.',
+ 'an old wanderer sits inside, in a seeming trance.'
+ ],
+ buttons: {
+ 'talk': {
+ cost: {'charm': 1},
+ text: 'talk',
+ nextScene: {1: 'talk'}
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'talk': {
+ text: [
+ 'the wanderer takes the charm and nods slowly.',
+ 'he speaks of once leading the great fleets to fresh worlds.',
+ 'unfathomable destruction to fuel wanderer hungers.',
+ 'his time here, now, is his penance.'
+ ],
+ onLoad: function() {
+ Engine.addPerk('gastronome');
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "cave": { /* Cave */
+ title: 'A Damp Cave',
+ scenes: {
+ 'start': {
+ text: [
+ 'the mouth of the cave is wide and dark.',
+ "can't see what's inside."
+ ],
+ notification: 'the earth here is split, as if bearing an ancient wound',
+ buttons: {
+ 'enter': {
+ text: 'go inside',
+ cost: { torch: 1 },
+ nextScene: {0.3: 'a1', 0.6: 'a2', 1: 'a3'}
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'a1': {
+ combat: true,
+ enemy: 'beast',
+ char: 'B',
+ damage: 1,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 5,
+ notification: 'a startled beast defends its home',
+ loot: {
+ 'fur': {
+ min: 1,
+ max: 10,
+ chance: 1
+ },
+ 'teeth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'b1', 1: 'b2'}
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a2': {
+ text: [
+ 'the cave narrows a few feet in.',
+ "the walls are moist and moss-covered"
+ ],
+ buttons: {
+ 'continue': {
+ text: 'squeeze',
+ nextScene: {0.5: 'b2', 1: 'b3'}
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a3': {
+ text: [
+ 'the remains of an old camp sits just inside the cave.',
+ 'bedrolls, torn and blackened, lay beneath a thin layer of dust.'
+ ],
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'torch': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'leather': {
+ min: 1,
+ max: 5,
+ chance: 0.3
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'b3', 1: 'b4'}
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b1': {
+ text: [
+ 'the body of a wanderer lies in a small cavern.',
+ "rot's been to work on it, and some of the pieces are missing.",
+ "can't tell what left it here."
+ ],
+ loot: {
+ 'iron sword': {
+ min: 1,
+ max: 1,
+ chance: 1
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'torch': {
+ min: 1,
+ max: 3,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: { 1: 'c1' }
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b2': {
+ text: [
+ 'the torch sputters and dies in the damp air',
+ 'the darkness is absolute'
+ ],
+ notification: 'the torch goes out',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ cost: {'torch': 1},
+ nextScene: { 1: 'c1' }
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b3': {
+ combat: true,
+ enemy: 'beast',
+ char: 'B',
+ damage: 1,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 5,
+ notification: 'a startled beast defends its home',
+ loot: {
+ 'fur': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'teeth': {
+ min: 1,
+ max: 2,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'c2'}
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b4': {
+ combat: true,
+ enemy: 'cave lizard',
+ char: 'L',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 6,
+ notification: 'a cave lizard attacks',
+ loot: {
+ 'scales': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'teeth': {
+ min: 1,
+ max: 2,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'c2'}
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c1': {
+ combat: true,
+ enemy: 'beast',
+ char: 'B',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 10,
+ notification: 'a large beast charges out of the dark',
+ loot: {
+ 'fur': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'teeth': {
+ min: 1,
+ max: 3,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end1', 1: 'end2'}
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c2': {
+ combat: true,
+ enemy: 'lizard',
+ char: 'L',
+ damage: 4,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 10,
+ notification: 'a giant lizard shambles forward',
+ loot: {
+ 'scales': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'teeth': {
+ min: 1,
+ max: 3,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.7: 'end2', 1: 'end3'}
+ },
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'end1': {
+ text: [
+ 'the nest of a large animal lies at the back of the cave.'
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ },
+ loot: {
+ 'meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'fur': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'scales': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'teeth': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'end2': {
+ text: [
+ 'a small supply cache is hidden at the back of the cave.'
+ ],
+ loot: {
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'leather': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'iron': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'cured meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'steel': {
+ min: 5,
+ max: 10,
+ chance: 0.5
+ },
+ 'bolas': {
+ min: 1,
+ max: 3,
+ chance: 0.3
+ }
+ },
+ onLoad: function() {
+ World.clearDungeon();
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'end3': {
+ text: [
+ 'an old case is wedged behind a rock, covered in a thick layer of dust.'
+ ],
+ loot: {
+ 'steel sword': {
+ min: 1,
+ max: 1,
+ chance: 1
+ },
+ 'bolas': {
+ min: 1,
+ max: 3,
+ chance: 0.5
+ }
+ },
+ onLoad: function() {
+ World.clearDungeon();
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave cave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "town": { /* Town */
+ title: 'A Deserted Town',
+ scenes: {
+ 'start': {
+ text: [
+ 'a small suburb lays ahead, empty houses scorched and peeling.',
+ "broken streetlights stand, rusting. light hasn't graced this place in a long time."
+ ],
+ notification: "the town lies abandoned, its citizens long dead",
+ buttons: {
+ 'enter': {
+ text: 'explore',
+ nextScene: {0.5: 'a1', 1: 'a2'}
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'a1': {
+ text: [
+ "where the windows of the schoolhouse aren't shattered, they're blackened with soot.",
+ 'the double doors creak endlessly in the wind.'
+ ],
+ buttons: {
+ 'enter': {
+ text: 'enter',
+ nextScene: {0.5: 'b1', 1: 'b2'},
+ cost: {torch: 1}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a2': {
+ combat: true,
+ enemy: 'thug',
+ char: 'T',
+ damage: 4,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 30,
+ loot: {
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'leather': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ notification: 'ambushed on the street.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'b3', 1: 'b4'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b1': {
+ text: [
+ 'a small cache of supplies is tucked inside a rusting locker.'
+ ],
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'torch': {
+ min: 1,
+ max: 3,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.3
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'c1', 1: 'c2'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b2': {
+ combat: true,
+ enemy: 'scavenger',
+ char: 'S',
+ damage: 4,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 30,
+ loot: {
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'leather': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ notification: 'a scavenger waits just inside the door.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'c2', 1: 'c3'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b3': {
+ combat: true,
+ enemy: 'beast',
+ char: 'B',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 25,
+ loot: {
+ 'teeth': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'fur': {
+ min: 5,
+ max: 10,
+ chance: 1
+ }
+ },
+ notification: 'a beast stands alone in an overgrown park.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'c4', 1: 'c5'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b4': {
+ text: [
+ 'an overturned caravan is spread across the pockmarked street.',
+ "it's been picked over by scavengers, but there's still some things worth taking."
+ ],
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'torch': {
+ min: 1,
+ max: 3,
+ chance: 0.5
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.3
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'c5', 1: 'c6' }
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c1': {
+ combat: true,
+ enemy: 'thug',
+ char: 'T',
+ damage: 4,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 30,
+ loot: {
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'leather': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ notification: 'a thug moves out of the shadows.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'd1'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c2': {
+ combat: true,
+ enemy: 'beast',
+ char: 'B',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 25,
+ loot: {
+ 'teeth': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'fur': {
+ min: 5,
+ max: 10,
+ chance: 1
+ }
+ },
+ notification: 'a beast charges out of a ransacked classroom.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'd1'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c3': {
+ text: [
+ 'through the large gymnasium doors, footsteps can be heard.',
+ 'the torchlight casts a flickering glow down the hallway.',
+ 'the footsteps stop.'
+ ],
+ buttons: {
+ 'continue': {
+ text: 'enter',
+ nextScene: {1: 'd1'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c4': {
+ combat: true,
+ enemy: 'beast',
+ char: 'B',
+ damage: 4,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 25,
+ loot: {
+ 'teeth': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'fur': {
+ min: 5,
+ max: 10,
+ chance: 1
+ }
+ },
+ notification: 'another beast, draw by the noise, leaps out of a copse of trees.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'd2'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c5': {
+ text: [
+ "something's causing a commotion a ways down the road.",
+ "a fight, maybe."
+ ],
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'd2'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c6': {
+ text: [
+ 'a small basket of food is hidden under a park bench, with a note attached.',
+ "can't read the words."
+ ],
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'd2'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'd1': {
+ combat: true,
+ enemy: 'scavenger',
+ char: 'S',
+ damage: 5,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 30,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'leather': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'steel sword': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ }
+ },
+ notification: 'a panicked scavenger bursts through the door, screaming.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end1', 1: 'end2'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'd2': {
+ combat: true,
+ enemy: 'vigilante',
+ char: 'V',
+ damage: 6,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 30,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'leather': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'steel sword': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ }
+ },
+ notification: "a man stands over a dead wanderer. notices he's not alone.",
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end3', 1: 'end4'}
+ },
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'end1': {
+ text: [
+ 'scavenger had a small camp in the school.',
+ 'collected scraps spread across the floor like they fell from heaven.'
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ },
+ loot: {
+ 'steel sword': {
+ min: 1,
+ max: 1,
+ chance: 1
+ },
+ 'steel': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'cured meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'bolas': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'end2': {
+ text: [
+ "scavenger'd been looking for supplies in here, it seems.",
+ "a shame to let what he'd found go to waste."
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ },
+ loot: {
+ 'coal': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'cured meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'leather': {
+ min: 5,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'end3': {
+ text: [
+ "beneath the wanderer's rags, clutched in one of its many hands, a glint of steel.",
+ "worth killing for, it seems."
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ },
+ loot: {
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 1
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'end4': {
+ text: [
+ "eye for an eye seems fair.",
+ "always worked before, at least.",
+ "picking the bones finds some useful trinkets."
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ },
+ loot: {
+ 'cured meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'iron': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'torch': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'bolas': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave town',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "city": { /* City */
+ title: 'A Ruined City',
+ scenes: {
+ 'start': {
+ text: [
+ 'a battered highway sign stands guard at the entrance to this once-great city.',
+ "the towers that haven't crumbled jut from the landscape like the ribcage of some ancient beast.",
+ 'might be things worth having still inside.'
+ ],
+ notification: "the towers of a decaying city dominate the skyline",
+ buttons: {
+ 'enter': {
+ text: 'explore',
+ nextScene: {0.4: 'a1', 0.8: 'a2', 1: 'a3'}
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a1': {
+ text:[
+ 'the streets are empty.',
+ 'the air is filled with dust, driven relentlessly by the hard winds.'
+ ],
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'b1', 1: 'b2'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a2': {
+ text:[
+ 'orange traffic cones are set across the street, faded and cracked.',
+ 'lights flash through the alleys between buildings.'
+ ],
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'b3', 1: 'b4'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a3': {
+ text: [
+ 'a large shanty town sprawls across the streets.',
+ 'faces, darkened by soot and blood, stare out from crooked huts.',
+ ],
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'b5', 1: 'b6'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b1': {
+ text: [
+ 'the old tower seems mostly intact.',
+ 'the shell of a burned out car blocks the entrance.',
+ 'most of the windows at ground level are busted anyway.'
+ ],
+ buttons: {
+ 'enter': {
+ text: 'enter',
+ nextScene: {0.5: 'c1', 1: 'c2'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b2': {
+ combat: true,
+ notification: 'a huge lizard scrambles up out of the darkness of an old metro station.',
+ enemy: 'lizard',
+ char: 'L',
+ damage: 5,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 20,
+ loot: {
+ 'scales': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 5,
+ max: 10,
+ chance: 0.5
+ },
+ 'meat': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'descend': {
+ text: 'descend',
+ nextScene: {0.5: 'c2', 1: 'c3'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b3': {
+ notification: 'the shot echoes in the empty street.',
+ combat: true,
+ enemy: 'sniper',
+ char: 'S',
+ damage: 15,
+ hit: 0.8,
+ attackDelay: 4,
+ health: 30,
+ ranged: true,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'c4', 1: 'c5'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b4': {
+ notification: 'the soldier steps out from between the buildings, rifle raised.',
+ combat: true,
+ enemy: 'soldier',
+ ranged: true,
+ char: 'D',
+ damage: 8,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 50,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'c5', 1: 'c6'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b5': {
+ notification: 'a frail man stands defiantly, blocking the path.',
+ combat: true,
+ enemy: 'frail man',
+ char: 'M',
+ damage: 1,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 10,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'leather': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'c7', 1: 'c8'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'b6': {
+ text: [
+ 'nothing but downcast eyes.',
+ 'the people here were broken a long time ago.'
+ ],
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'c8', 1: 'c9'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'c1': {
+ notification: 'a thug is waiting on the other side of the wall.',
+ combat: true,
+ enemy: 'thug',
+ char: 'T',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 30,
+ loot: {
+ 'steel sword': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ },
+ 'cured meat': {
+ min: 1,
+ max: 3,
+ chance: 0.5
+ },
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'd1', 1: 'd2'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'c2': {
+ notification: 'a snarling beast jumps out from behind a car.',
+ combat: true,
+ enemy: 'beast',
+ char: 'B',
+ damage: 2,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 30,
+ loot: {
+ 'meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'fur': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'd2'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'c3': {
+ text: [
+ 'street above the subway platform is blown away.',
+ 'lets some light down into the dusty haze.',
+ 'a sound comes from the tunnel, just ahead.'
+ ],
+ buttons: {
+ 'enter': {
+ text: 'investigate',
+ cost: { 'torch': 1 },
+ nextScene: {0.5: 'd2', 1: 'd3'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'c4': {
+ text: [
+ 'looks like a camp of sorts up ahead.',
+ 'rusted chainlink is pulled across an alleyway.',
+ 'fires burn in the courtyard beyond.'
+ ],
+ buttons: {
+ 'enter': {
+ text: 'continue',
+ nextScene: {0.5: 'd4', 1: 'd5'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'c5': {
+ text: [
+ 'more voices can be heard ahead.',
+ 'they must be here for a reason.'
+ ],
+ buttons: {
+ 'enter': {
+ text: 'continue',
+ nextScene: {1: 'd5'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'c6': {
+ text: [
+ 'the sound of gunfire carries on the wind.',
+ 'the street ahead glows with firelight.'
+ ],
+ buttons: {
+ 'enter': {
+ text: 'continue',
+ nextScene: {0.5: 'd5', 1: 'd6'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'c7': {
+ text: [
+ 'more squatters are crowding around now.',
+ 'someone throws a stone.'
+ ],
+ buttons: {
+ 'enter': {
+ text: 'continue',
+ nextScene: {0.5: 'd7', 1: 'd8'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'c8': {
+ text: [
+ 'an improvised shop is set up on the sidewalk.',
+ 'the owner stands by, stoic.'
+ ],
+ loot: {
+ 'steel sword': {
+ min: 1,
+ max: 1,
+ chance: 0.8
+ },
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ },
+ 'bullets': {
+ min: 1,
+ max: 8,
+ chance: 0.25
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.01
+ }
+ },
+ buttons: {
+ 'enter': {
+ text: 'continue',
+ nextScene: {1: 'd8'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'c9': {
+ text: [
+ 'strips of meat hang drying by the side of the street.',
+ 'the people back away, avoiding eye contact.'
+ ],
+ loot: {
+ 'cured meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'enter': {
+ text: 'continue',
+ nextScene: {0.5: 'd8', 1: 'd9'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd1': {
+ notification: 'a large bird nests at the top of the stairs.',
+ combat: true,
+ enemy: 'bird',
+ char: 'B',
+ damage: 5,
+ hit: 0.7,
+ attackDelay: 1,
+ health: 45,
+ loot: {
+ 'meat': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end1', 1: 'end2'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd2': {
+ text: [
+ "the debris is denser here.",
+ "maybe some useful stuff in the rubble."
+ ],
+ loot: {
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'steel': {
+ min: 1,
+ max: 10,
+ chance: 0.8
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.01
+ },
+ 'cloth': {
+ min: 1,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'end2'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd3': {
+ notification: 'a swarm of rats rushes up the tunnel.',
+ combat: true,
+ enemy: 'rats',
+ plural: true,
+ char: 'RRR',
+ damage: 1,
+ hit: 0.8,
+ attackDelay: 0.25,
+ health: 60,
+ loot: {
+ 'fur': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 5,
+ max: 10,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end2', 1: 'end3'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd4': {
+ notification: 'a large man attacks, waving a bayonet.',
+ combat: true,
+ enemy: 'veteran',
+ char: 'V',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 45,
+ loot: {
+ 'bayonet': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end4', 1: 'end5'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd5': {
+ notification: 'a second soldier opens fire.',
+ combat: true,
+ enemy: 'soldier',
+ ranged: true,
+ char: 'D',
+ damage: 8,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 50,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'end5'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd6': {
+ notification: 'a masked soldier rounds the corner, gun drawn',
+ combat: true,
+ enemy: 'commando',
+ char: 'C',
+ ranged: true,
+ damage: 3,
+ hit: 0.9,
+ attackDelay: 2,
+ health: 55,
+ loot: {
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end5', 1: 'end6'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd7': {
+ notification: 'the crowd surges forward.',
+ combat: true,
+ enemy: 'squatters',
+ plural: true,
+ char: 'SSS',
+ damage: 2,
+ hit: 0.7,
+ attackDelay: 0.5,
+ health: 40,
+ loot: {
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end7', 1: 'end8'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd8': {
+ notification: 'a youth lashes out with a tree branch.',
+ combat: true,
+ enemy: 'youth',
+ char: 'Y',
+ damage: 2,
+ hit: 0.7,
+ attackDelay: 1,
+ health: 45,
+ loot: {
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {1: 'end8'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'd9': {
+ notification: 'a squatter stands firmly in the doorway of a small hut.',
+ combat: true,
+ enemy: 'squatter',
+ char: 'S',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 20,
+ loot: {
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'teeth': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: {0.5: 'end8', 1: 'end9'}
+ },
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'end1': {
+ text: [
+ 'bird must have liked shiney things.',
+ 'some good stuff woven into its nest.'
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ bullets: {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ bolas: {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'end2': {
+ text: [
+ 'not much here.',
+ 'scavengers much have gotten to this place already.'
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ torch: {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'end3': {
+ text: [
+ 'the tunnel opens up at another platform.',
+ 'the walls are scorched from an old battle.',
+ 'bodies and supplies from both sides litter the ground.'
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ rifle: {
+ min: 1,
+ max: 1,
+ chance: 0.8
+ },
+ bullets: {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'laser rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.3
+ },
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.3
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.3
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+
+ 'end4': {
+ text: [
+ 'the small military outpost is well supplied.',
+ 'arms and munitions, relics from the war, are neatly arranged on the store-room floor.',
+ 'just as deadly now as they were then.'
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ rifle: {
+ min: 1,
+ max: 1,
+ chance: 1
+ },
+ bullets: {
+ min: 1,
+ max: 10,
+ chance: 1
+ },
+ grenade: {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'end5': {
+ text: [
+ 'searching the bodies yields a few supplies.',
+ 'more soldiers will be on their way.',
+ 'time to move on.'
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ rifle: {
+ min: 1,
+ max: 1,
+ chance: 1
+ },
+ bullets: {
+ min: 1,
+ max: 10,
+ chance: 1
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'end6': {
+ text: [
+ 'the small settlement has clearly been burning a while.',
+ 'the bodies of the wanderers that lived here are still visible in the flames.',
+ "still time to rescue a few supplies."
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ 'laser rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ },
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'cured meat': {
+ min: 1,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+
+ 'end7': {
+ text: [
+ 'the remaining settlers flee from the violence, their belongings forgotten.',
+ "there's not much, but some useful things can still be found."
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ 'steel sword': {
+ min: 1,
+ max: 1,
+ chance: 0.8
+ },
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'cured meat': {
+ min: 1,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'end8': {
+ text: [
+ 'the young settler was carrying a canvas sack.',
+ "it contains travelling gear, and a few trinkets.",
+ "there's nothing else here."
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ 'steel sword': {
+ min: 1,
+ max: 1,
+ chance: 0.8
+ },
+ 'bolas': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'cured meat': {
+ min: 1,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ },
+
+ 'end9': {
+ text: [
+ 'inside the hut, a child cries.',
+ "a few belongings rest against the walls.",
+ "there's nothing else here."
+ ],
+ onLoad: function() {
+ World.clearDungeon();
+ State.cityCleared = true;
+ },
+ loot: {
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'bolas': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave city',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "house": { /* Abandoned House */
+ title: 'An Old House',
+ scenes: {
+ 'start': {
+ text: [
+ 'an old house remains here, once white siding yellowed and peeling.',
+ 'the door hangs open.'
+ ],
+ notification: 'the remains of an old house stand as a monument to simpler times',
+ buttons: {
+ 'enter': {
+ text: 'go inside',
+ nextScene: { 0.5: 'supplies', 1: 'occupied' }
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ },
+ },
+ 'supplies': {
+ text: [
+ 'the house is abandoned, but not yet picked over.',
+ 'still a few drops of water in the old well.'
+ ],
+ onLoad: function() {
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ World.setWater(World.getMaxWater());
+ Notifications.notify(null, 'water replenished');
+ },
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 10,
+ chance: 0.8
+ },
+ 'leather': {
+ min: 1,
+ max: 10,
+ chance: 0.2
+ },
+ 'cloth': {
+ min: 1,
+ max: 10,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'occupied': {
+ combat: true,
+ enemy: 'squatter',
+ char: 'S',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 10,
+ notification: 'a man charges down the hall, a rusty blade in his hand',
+ onLoad: function() {
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ },
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 10,
+ chance: 0.8
+ },
+ 'leather': {
+ min: 1,
+ max: 10,
+ chance: 0.2
+ },
+ 'cloth': {
+ min: 1,
+ max: 10,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "battlefield": { /* Discovering an old battlefield */
+ title: 'A Forgotten Battlefield',
+ scenes: {
+ 'start': {
+ text: [
+ 'a battle was fought here, long ago.',
+ 'battered technology from both sides lays dormant on the blasted landscape.'
+ ],
+ onLoad: function() {
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ },
+ loot: {
+ 'rifle': {
+ min: 1,
+ max: 3,
+ chance: 0.5
+ },
+ 'bullets': {
+ min: 5,
+ max: 20,
+ chance: 0.8
+ },
+ 'laser rifle': {
+ min: 1,
+ max: 3,
+ chance: 0.3
+ },
+ 'energy cell': {
+ min: 5,
+ max: 10,
+ chance: 0.5
+ },
+ 'grenade': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.3
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "borehole": { /* Admiring a huge borehole */
+ title: 'A Huge Borehole',
+ scenes: {
+ 'start': {
+ text: [
+ 'a huge hole is cut deep into the earth, evidence of the past harvest.',
+ 'they took what they came for, and left.',
+ 'castoff from the mammoth drills can still be found by the edges of the precipice.'
+ ],
+ onLoad: function() {
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ },
+ loot: {
+ 'alien alloy': {
+ min: 1,
+ max: 3,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "ship": { /* Finding a way off this rock */
+ title: 'A Crashed Ship',
+ scenes: {
+ 'start': {
+ onLoad: function() {
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ World.drawRoad();
+ World.state.ship = true;
+ },
+ text: [
+ 'the familiar curves of a wanderer vessel rise up out of the dust and ash. ',
+ "lucky that the natives can't work the mechanisms.",
+ 'with a little effort, it might fly again.'
+ ],
+ buttons: {
+ 'leavel': {
+ text: 'salvage',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "sulphurmine": { /* Clearing the Sulphur Mine */
+ title: 'The Sulphur Mine',
+ scenes: {
+ 'start': {
+ text: [
+ "the military is already set up at the mine's entrance.",
+ 'soldiers patrol the permitter, rifles slung over their shoulders.'
+ ],
+ notification: 'a military perimeter is set up around the mine.',
+ buttons: {
+ 'attack': {
+ text: 'attack',
+ nextScene: {1: 'a1'}
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a1': {
+ combat: true,
+ enemy: 'soldier',
+ ranged: true,
+ char: 'D',
+ damage: 8,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 50,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ notification: 'a soldier, alerted, opens fire.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: { 1: 'a2' }
+ },
+ 'run': {
+ text: 'run',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a2': {
+ combat: true,
+ enemy: 'soldier',
+ ranged: true,
+ char: 'D',
+ damage: 8,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 50,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ notification: 'a second soldier joins the fight.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: { 1: 'a3' }
+ },
+ 'run': {
+ text: 'run',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a3': {
+ combat: true,
+ enemy: 'veteran',
+ char: 'V',
+ damage: 10,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 65,
+ loot: {
+ 'bayonet': {
+ min: 1,
+ max: 1,
+ chance: 0.5
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ notification: 'a grizzled soldier attacks, waving a bayonet.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: { 1: 'cleared' }
+ }
+ }
+ },
+ 'cleared': {
+ text: [
+ 'the military presence has been cleared.',
+ 'the mine is now safe for workers.'
+ ],
+ notification: 'the sulphur mine is clear of dangers',
+ onLoad: function() {
+ World.drawRoad();
+ World.state.sulphurmine = true;
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "coalmine": { /* Clearing the Coal Mine */
+ title: 'The Coal Mine',
+ scenes: {
+ 'start': {
+ text: [
+ 'camp fires burn by the entrance to the mine.',
+ 'men mill about, weapons at the ready.'
+ ],
+ notification: 'this old mine is not abandoned',
+ buttons: {
+ 'attack': {
+ text: 'attack',
+ nextScene: {1: 'a1'}
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a1': {
+ combat: true,
+ enemy: 'man',
+ char: 'M',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 10,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ notification: 'a man joins the fight',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: { 1: 'a2' }
+ },
+ 'run': {
+ text: 'run',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a2': {
+ combat: true,
+ enemy: 'man',
+ char: 'M',
+ damage: 3,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 10,
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ notification: 'a man joins the fight',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: { 1: 'a3' }
+ },
+ 'run': {
+ text: 'run',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'a3': {
+ combat: true,
+ enemy: 'chief',
+ char: 'C',
+ damage: 5,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 20,
+ loot: {
+ 'cured meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'iron': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ notification: 'only the chief remains.',
+ buttons: {
+ 'continue': {
+ text: 'continue',
+ nextScene: { 1: 'cleared' }
+ }
+ }
+ },
+ 'cleared': {
+ text: [
+ 'the camp is still, save for the crackling of the fires.',
+ 'the mine is now safe for workers.'
+ ],
+ notification: 'the coal mine is clear of dangers',
+ onLoad: function() {
+ World.drawRoad();
+ World.state.coalmine = true;
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+ "ironmine": { /* Clearing the Iron Mine */
+ title: 'The Iron Mine',
+ scenes: {
+ 'start': {
+ text: [
+ 'an old iron mine sits here, tools abandoned and left to rust.',
+ 'bleached bones are strewn about the entrance. many, deeply scored with jagged grooves.',
+ 'feral howls echo out of the darkness.'
+ ],
+ notification: 'the path leads to an abandoned mine',
+ buttons: {
+ 'enter': {
+ text: 'go inside',
+ nextScene: { 1: 'enter' },
+ cost: { 'torch': 1 }
+ },
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ },
+ 'enter': {
+ combat: true,
+ enemy: 'beastly matriarch',
+ char: 'M',
+ damage: 4,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 10,
+ loot: {
+ 'teeth': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'scales': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'cloth': {
+ min: 5,
+ max: 10,
+ chance: 0.5
+ }
+ },
+ notification: 'a large creature lunges, muscles rippling in the torchlight',
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: { 1: 'cleared' }
+ }
+ }
+ },
+ 'cleared': {
+ text: [
+ 'the beast is dead.',
+ 'the mine is now safe for workers.'
+ ],
+ notification: 'the iron mine is clear of dangers',
+ onLoad: function() {
+ World.drawRoad();
+ World.state.ironmine = true;
+ World.markVisited(World.curPos[0], World.curPos[1]);
+ },
+ buttons: {
+ 'leave': {
+ text: 'leave',
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ }
+};
\ No newline at end of file
diff --git a/script/header.js b/script/header.js
new file mode 100644
index 000000000..8cf8cec92
--- /dev/null
+++ b/script/header.js
@@ -0,0 +1,28 @@
+/**
+ * Module that takes care of header buttons
+ */
+var Header = {
+
+ init: function(options) {
+ this.options = $.extend(
+ this.options,
+ options
+ );
+ },
+
+ options: {}, // Nothing for now
+
+ canTravel: function() {
+ return $('div#header div.headerButton').length > 1;
+ },
+
+ addLocation: function(text, id, module) {
+ return $('
').attr('id', "location_" + id)
+ .addClass('headerButton')
+ .text(text).click(function() {
+ if(Header.canTravel()) {
+ Engine.travelTo(module);
+ }
+ }).appendTo($('div#header'));
+ }
+};
\ No newline at end of file
diff --git a/script/notifications.js b/script/notifications.js
new file mode 100644
index 000000000..283d8599d
--- /dev/null
+++ b/script/notifications.js
@@ -0,0 +1,58 @@
+/**
+ * Module that registers the notification box and handles messages
+ */
+var Notifications = {
+
+ init: function(options) {
+ this.options = $.extend(
+ this.options,
+ options
+ );
+
+ // Create the notifications box
+ elem = $('
').attr({
+ id: 'notifications',
+ className: 'notifications'
+ });
+ // Create the transparency gradient
+ $('
').attr('id', 'notifyGradient').appendTo(elem);
+
+ elem.appendTo('div#wrapper');
+ },
+
+ options: {}, // Nothing for now
+
+ elem: null,
+
+ notifyQueue: {},
+
+ // Allow notification to the player
+ notify: function(module, text, noQueue) {
+ if(typeof text == 'undefined') return;
+ if(text.slice(-1) != ".") text += ".";
+ if(module != null && Engine.activeModule != module) {
+ if(!noQueue) {
+ if(typeof this.notifyQueue[module] == 'undefined') {
+ this.notifyQueue[module] = new Array();
+ }
+ this.notifyQueue[module].push(text);
+ }
+ } else {
+ Notifications.printMessage(text);
+ }
+ Engine.saveGame();
+ },
+
+ printMessage: function(text) {
+ var text = $('
').addClass('notification').css('opacity', '0').text(text).prependTo('div#notifications');
+ text.animate({opacity: 1}, 500, 'linear');
+ },
+
+ printQueue: function(module) {
+ if(typeof this.notifyQueue[module] != 'undefined') {
+ while(this.notifyQueue[module].length > 0) {
+ Notifications.printMessage(this.notifyQueue[module].shift());
+ }
+ }
+ }
+};
\ No newline at end of file
diff --git a/script/outside.js b/script/outside.js
new file mode 100644
index 000000000..9a74ca09b
--- /dev/null
+++ b/script/outside.js
@@ -0,0 +1,611 @@
+/**
+ * Module that registers the outdoors functionality
+ */
+var Outside = {
+ name: "Outside",
+
+ _GATHER_DELAY: 60,
+ _TRAPS_DELAY: 90,
+ _POP_DELAY: [0.5, 3],
+
+ _INCOME: {
+ 'gatherer': {
+ delay: 10,
+ stores: {
+ 'wood': 1
+ }
+ },
+ 'hunter': {
+ delay: 10,
+ stores: {
+ 'fur': 0.5,
+ 'meat': 0.5
+ }
+ },
+ 'trapper': {
+ delay: 10,
+ stores: {
+ 'meat': -1,
+ 'bait': 1
+ }
+ },
+ 'tanner': {
+ delay: 10,
+ stores: {
+ 'fur': -5,
+ 'leather': 1
+ }
+ },
+ 'charcutier': {
+ delay: 10,
+ stores: {
+ 'meat': -5,
+ 'wood': -5,
+ 'cured meat': 1
+ }
+ },
+ 'iron miner': {
+ delay: 10,
+ stores: {
+ 'cured meat': -1,
+ 'iron': 1
+ }
+ },
+ 'coal miner': {
+ delay: 10,
+ stores: {
+ 'cured meat': -1,
+ 'coal': 1
+ }
+ },
+ 'sulphur miner': {
+ delay: 10,
+ stores: {
+ 'cured meat': -1,
+ 'sulphur': 1
+ }
+ },
+ 'steelworker': {
+ delay: 10,
+ stores: {
+ 'iron': -1,
+ 'coal': -1,
+ 'steel': 1
+ }
+ },
+ 'armourer': {
+ delay: 10,
+ stores: {
+ 'steel': -1,
+ 'sulphur': -1,
+ 'bullets': 1
+ }
+ }
+ },
+
+ TrapDrops: [
+ {
+ rollUnder: 0.5,
+ name: 'fur',
+ message: 'scraps of fur'
+ },
+ {
+ rollUnder: 0.75,
+ name: 'meat',
+ message: 'bits of meat'
+ },
+ {
+ rollUnder: 0.85,
+ name: 'scales',
+ message: 'strange scales'
+ },
+ {
+ rollUnder: 0.93,
+ name: 'teeth',
+ message: 'scattered teeth'
+ },
+ {
+ rollUnder: 0.995,
+ name: 'cloth',
+ message: 'tattered cloth'
+ },
+ {
+ rollUnder: 1.0,
+ name: 'charm',
+ message: 'a crudely made charm'
+ }
+ ],
+
+ init: function(options) {
+ this.options = $.extend(
+ this.options,
+ options
+ );
+
+ if(Engine._debug) {
+ this._GATHER_DELAY = 0;
+ this._TRAPS_DELAY = 0;
+ }
+
+ // Create the outside tab
+ this.tab = Header.addLocation("A Silent Forest", "outside", Outside);
+
+ // Create the Outside panel
+ this.panel = $('
').attr('id', "outsidePanel")
+ .addClass('location')
+ .appendTo('div#locationSlider');
+
+ if(typeof State.outside == 'undefined') {
+ State.outside = {
+ buildings: {},
+ population: 0,
+ workers: {}
+ }
+ }
+
+ this.updateVillage();
+ Outside.updateWorkersView();
+
+ Engine.updateSlider();
+
+ // Create the gather button
+ new Button.Button({
+ id: 'gatherButton',
+ text: "gather wood",
+ click: Outside.gatherWood,
+ cooldown: Outside._GATHER_DELAY,
+ width: '80px'
+ }).appendTo('div#outsidePanel');
+ },
+
+ numBuilding: function(bName) {
+ return State.outside &&
+ State.outside.buildings &&
+ State.outside.buildings[bName] ? State.outside.buildings[bName] : 0;
+ },
+
+ addBuilding: function(bName, num) {
+ var cur = State.outside.buildings[bName];
+ if(typeof cur != 'number') cur = 0;
+ cur += num;
+ if(cur < 0) cur = 0;
+ State.outside.buildings[bName] = cur;
+ this.updateVillage();
+ Engine.saveGame();
+ },
+
+ addBuildings: function(list) {
+ for(k in list) {
+ var num = State.outside.buildings[k];
+ if(typeof num != 'number') num = 0;
+ num += list[k];
+ State.outside.buildings[k] = num;
+ }
+ this.updateVillage();
+ Engine.saveGame();
+ },
+
+ getMaxPopulation: function() {
+ return Outside.numBuilding('hut') * 4;
+ },
+
+ getPopulation: function() {
+ if(State.outside && State.outside.population) {
+ return State.outside.population;
+ }
+ return 0;
+ },
+
+ increasePopulation: function() {
+ var space = Outside.getMaxPopulation() - State.outside.population;
+ if(space > 0) {
+ var num = Math.floor(Math.random()*(space/2) + space/2);
+ if(num == 0) num = 1;
+ if(num == 1) {
+ Notifications.notify(null, 'a stranger arrives in the night');
+ } else if(num < 5) {
+ Notifications.notify(null, 'a weathered family takes up in one of the huts.');
+ } else if(num < 10) {
+ Notifications.notify(null, 'a small group arrives, all dust and bones.');
+ } else if(num < 30) {
+ Notifications.notify(null, 'a convoy lurches in, equal parts worry and hope.');
+ } else {
+ Notifications.notify(null, "the town's booming. word does get around.");
+ }
+ Engine.log('population increased by ' + num);
+ State.outside.population += num;
+ Outside.updateVillage();
+ Outside.updateWorkersView();
+ Outside.updateVillageIncome();
+ }
+ Outside.schedulePopIncrease();
+ },
+
+ killVillagers: function(num) {
+ State.outside.population -= num;
+ if(State.outside.population < 0) {
+ State.outside.population = 0;
+ }
+ var remaining = Outside.getNumGatherers();
+ if(remaining < 0) {
+ var gap = -remaining;
+ for(var k in State.outside.workers) {
+ var num = State.outside.workers[k];
+ if(num < gap) {
+ gap -= num;
+ State.outside.workers[k] = 0;
+ } else {
+ State.outside.workers[k] -= gap;
+ break;
+ }
+ }
+ }
+ Outside.updateVillage();
+ Outside.updateWorkersView();
+ Outside.updateVillageIncome();
+ },
+
+ schedulePopIncrease: function() {
+ var nextIncrease = Math.floor(Math.random()*(Outside._POP_DELAY[1] - Outside._POP_DELAY[0])) + Outside._POP_DELAY[0];
+ Engine.log('next population increase scheduled in ' + nextIncrease + ' minutes');
+ Outside._popTimeout = setTimeout(Outside.increasePopulation, nextIncrease * 60 * 1000);
+ },
+
+ updateWorkersView: function() {
+ if(State.outside.population == 0) return;
+ var workers = $('div#workers');
+ var needsAppend = false;
+ if(workers.length == 0) {
+ needsAppend = true;
+ workers = $('
').attr('id', 'workers').css('opacity', 0);
+ }
+
+ var numGatherers = State.outside.population;
+ var gatherer = $('div#workers_row_gatherer', workers);
+
+ for(var k in State.outside.workers) {
+ var row = $('div#workers_row_' + k.replace(' ', '-'), workers);
+ if(row.length == 0) {
+ row = Outside.makeWorkerRow(k, State.outside.workers[k]);
+
+ var curPrev = null;
+ workers.children().each(function(i) {
+ var child = $(this);
+ var cName = child.attr('id').substring(12).replace('-', ' ');
+ if(cName != 'gatherer') {
+ if(cName < k && (curPrev == null || cName > curPrev)) {
+ curPrev = cName;
+ }
+ }
+ });
+ if(curPrev == null && gatherer.length == 0) {
+ row.prependTo(workers);
+ }
+ else if(curPrev == null)
+ {
+ row.insertAfter(gatherer);
+ }
+ else
+ {
+ row.insertAfter(workers.find('#workers_row_' + curPrev.replace(' ', '-')));
+ }
+
+ } else {
+ $('div#' + row.attr('id') + ' > div.row_val > span', workers).text(State.outside.workers[k]);
+ }
+ numGatherers -= State.outside.workers[k];
+ if(State.outside.workers[k] == 0) {
+ $('.dnBtn', row).addClass('disabled');
+ } else {
+ $('.dnBtn', row).removeClass('disabled');
+ }
+ }
+
+ if(gatherer.length == 0) {
+ gatherer = Outside.makeWorkerRow('gatherer', numGatherers);
+ gatherer.prependTo(workers);
+ } else {
+ $('div#workers_row_gatherer > div.row_val > span', workers).text(numGatherers);
+ }
+
+ if(numGatherers == 0) {
+ $('.upBtn', '#workers').addClass('disabled');
+ } else {
+ $('.upBtn', '#workers').removeClass('disabled');
+ }
+
+
+ if(needsAppend && workers.children().length > 0) {
+ workers.appendTo('#outsidePanel').animate({opacity:1}, 300, 'linear');
+ }
+ },
+
+ getNumGatherers: function() {
+ var num = State.outside.population;
+ for(var k in State.outside.workers) {
+ num -= State.outside.workers[k];
+ }
+ return num;
+ },
+
+ makeWorkerRow: function(name, num) {
+ var row = $('
')
+ .attr('id', 'workers_row_' + name.replace(' ','-'))
+ .addClass('workerRow');
+ $('
').addClass('row_key').text(name).appendTo(row);
+ var val = $('
').addClass('row_val').appendTo(row);
+
+ $('
').text(num).appendTo(val);
+
+ if(name != 'gatherer') {
+ $('').addClass('upBtn').appendTo(val).click(Outside.increaseWorker);
+ $('
').addClass('dnBtn').appendTo(val).click(Outside.decreaseWorker);
+ }
+
+ $('
').addClass('clear').appendTo(row);
+
+ var tooltip = $('
').addClass('tooltip bottom right').appendTo(row);
+ var income = Outside._INCOME[name];
+ for(var s in income.stores) {
+ var r = $('
').addClass('storeRow');
+ $('
').addClass('row_key').text(s).appendTo(r);
+ $('
').addClass('row_val').text(Engine.getIncomeMsg(income.stores[s], income.delay)).appendTo(r);
+ r.appendTo(tooltip);
+ }
+
+ return row;
+ },
+
+ increaseWorker: function(btn) {
+ var worker = $(this).closest('.workerRow').children('.row_key').text();
+ if(Outside.getNumGatherers() > 0) {
+ Engine.log('increasing ' + worker);
+ State.outside.workers[worker]++;
+ Outside.updateVillageIncome();
+ Outside.updateWorkersView();
+ }
+ },
+
+ decreaseWorker: function(btn) {
+ var worker = $(this).closest('.workerRow').children('.row_key').text();
+ if(State.outside.workers[worker] > 0) {
+ Engine.log('decreasing ' + worker);
+ State.outside.workers[worker]--;
+ Outside.updateVillageIncome();
+ Outside.updateWorkersView();
+ }
+ },
+
+ updateVillageRow: function(name, num, village) {
+ var id = 'building_row_' + name.replace(' ', '-');
+ var row = $('div#' + id, village);
+ if(row.length == 0 && num > 0) {
+ var row = $('
').attr('id', id).addClass('storeRow');
+ $('
').addClass('row_key').text(name).appendTo(row);
+ $('
').addClass('row_val').text(num).appendTo(row);
+ $('
').addClass('clear').appendTo(row);
+ var curPrev = null;
+ village.children().each(function(i) {
+ var child = $(this);
+ if(child.attr('id') != 'population') {
+ var cName = child.attr('id').substring(13).replace('-', ' ');
+ if(cName < name && (curPrev == null || cName > curPrev)) {
+ curPrev = cName;
+ }
+ }
+ });
+ if(curPrev == null) {
+ row.prependTo(village);
+ } else {
+ row.insertAfter('#building_row_' + curPrev.replace(' ', '-'));
+ }
+ } else if(num > 0) {
+ $('div#' + row.attr('id') + ' > div.row_val', village).text(num);
+ } else if(num == 0) {
+ row.remove();
+ }
+ },
+
+ updateVillage: function() {
+ var village = $('div#village');
+ var pop = $('div#population');
+ var needsAppend = false;
+ if(village.length == 0) {
+ needsAppend = true;
+ village = $('
').attr('id', 'village').css('opacity', 0);
+ population = $('
').attr('id', 'population').appendTo(village);
+ }
+
+ for(var k in State.outside.buildings) {
+ if(k == 'trap') {
+ var numTraps = State.outside.buildings[k];
+ var numBait = Engine.getStore('bait');
+ var traps = numTraps - numBait;
+ traps = traps < 0 ? 0 : traps;
+ Outside.updateVillageRow(k, traps, village);
+ Outside.updateVillageRow('baited trap', numBait > numTraps ? numTraps : numBait, village);
+ } else {
+ if(Outside.checkWorker(k)) {
+ Outside.updateWorkersView();
+ }
+ Outside.updateVillageRow(k, State.outside.buildings[k], village);
+ }
+ }
+
+ population.text('pop ' + State.outside.population + '/' + this.getMaxPopulation());
+
+ var hasPeeps;
+ if(Outside.numBuilding('hut') == 0) {
+ hasPeeps = false;
+ village.addClass('noHuts');
+ } else {
+ hasPeeps = true;
+ village.removeClass('noHuts');
+ }
+
+ if(needsAppend && village.children().length > 1) {
+ village.appendTo('#outsidePanel');
+ village.animate({opacity:1}, 300, 'linear');
+ }
+
+ if(hasPeeps && typeof Outside._popTimeout == 'undefined') {
+ Outside.schedulePopIncrease();
+ }
+
+ this.setTitle();
+ },
+
+ checkWorker: function(name) {
+ var jobMap = {
+ 'lodge': ['hunter', 'trapper'],
+ 'tannery': ['tanner'],
+ 'smokehouse': ['charcutier'],
+ 'iron mine': ['iron miner'],
+ 'coal mine': ['coal miner'],
+ 'sulphur mine': ['sulphur miner'],
+ 'steelworks': ['steelworker'],
+ 'armoury' : ['armourer']
+ }
+
+ var jobs = jobMap[name];
+ var added = false;
+ if(typeof jobs == 'object') {
+ for(var i = 0, len = jobs.length; i < len; i++) {
+ var job = jobs[i];
+ if(typeof State.outside.buildings[name] == 'number' &&
+ typeof State.outside.workers[job] != 'number') {
+ Engine.log('adding ' + job + ' to the workers list')
+ State.outside.workers[job] = 0;
+ added = true;
+ }
+ }
+ }
+ return added;
+ },
+
+ updateVillageIncome: function() {
+ for(var worker in Outside._INCOME) {
+ var income = Outside._INCOME[worker];
+ var num = worker == 'gatherer' ? Outside.getNumGatherers() : State.outside.workers[worker];
+ if(typeof num == 'number') {
+ var stores = {};
+ if(num < 0) num = 0;
+ var tooltip = $('.tooltip', 'div#workers_row_' + worker.replace(' ', '-'));
+ tooltip.empty();
+ var needsUpdate = false;
+ var curIncome = Engine.getIncome(worker);
+ for(var store in income.stores) {
+ stores[store] = income.stores[store] * num;
+ if(curIncome[store] != stores[store]) needsUpdate = true;
+ var row = $('
').addClass('storeRow');
+ $('
').addClass('row_key').text(store).appendTo(row);
+ $('
').addClass('row_val').text(Engine.getIncomeMsg(stores[store], income.delay)).appendTo(row);
+ row.appendTo(tooltip);
+ }
+ if(needsUpdate) {
+ Engine.setIncome(worker, {
+ delay: income.delay,
+ stores: stores
+ });
+ }
+ }
+ }
+ Room.updateIncomeView();
+ },
+
+ updateTrapButton: function() {
+ var btn = $('div#trapsButton');
+ if(Outside.numBuilding('trap') > 0) {
+ if(btn.length == 0) {
+ new Button.Button({
+ id: 'trapsButton',
+ text: "check traps",
+ click: Outside.checkTraps,
+ cooldown: Outside._TRAPS_DELAY,
+ width: '80px'
+ }).appendTo('div#outsidePanel');
+ } else {
+ Button.setDisabled(btn, false);
+ }
+ } else {
+ if(btn.length > 0) {
+ Button.setDisabled(btn, true);
+ }
+ }
+ },
+
+ setTitle: function() {
+ var numHuts = this.numBuilding('hut');
+ var title;
+ if(numHuts == 0) {
+ title = "A Silent Forest";
+ } else if(numHuts == 1) {
+ title = "A Lonely Hut";
+ } else if(numHuts <= 4) {
+ title = "A Tiny Village";
+ } else if(numHuts <= 8) {
+ title = "A Modest Village";
+ } else if(numHuts <= 14) {
+ title = "A Large Village";
+ } else {
+ title = "A Raucous Village";
+ }
+
+ if(Engine.activeModule == this) {
+ document.title = title;
+ }
+ $('#location_outside').text(title);
+ },
+
+ onArrival: function() {
+ Outside.setTitle();
+ if(!State.seenForest) {
+ Notifications.notify(Outside, "the sky is grey and the wind blows relentlessly");
+ State.seenForest = true;
+ }
+ Outside.updateTrapButton();
+ },
+
+ gatherWood: function() {
+ Notifications.notify(Outside, "dry brush and dead branches litter the forest floor")
+ Engine.setStore('wood', Engine.getStore('wood') + (Outside.numBuilding('cart') > 0 ? 50 : 10));
+ },
+
+ checkTraps: function() {
+ var drops = {};
+ var msg = [];
+ var numTraps = Outside.numBuilding('trap');
+ var numBait = Engine.getStore('bait');
+ var numDrops = numTraps + (numBait < numTraps ? numBait : numTraps);
+ for(var i = 0; i < numDrops; i++) {
+ var roll = Math.random();
+ for(var j in Outside.TrapDrops) {
+ var drop = Outside.TrapDrops[j];
+ if(roll < drop.rollUnder) {
+ var num = drops[drop.name]
+ if(typeof num == 'undefined') {
+ num = 0;
+ msg.push(drop.message);
+ }
+ drops[drop.name] = num + 1;
+ break;
+ }
+ }
+ }
+ var s = 'the traps contain ';
+ for(var i = 0, len = msg.length; i < len; i++) {
+ if(len > 1 && i > 0 && i < len - 1) {
+ s += ", ";
+ } else if(len > 1 && i == len - 1) {
+ s += " and ";
+ }
+ s += msg[i];
+ }
+
+ var baitUsed = numBait < numTraps ? numBait : numTraps;
+ drops['bait'] = -baitUsed;
+
+ Notifications.notify(Outside, s);
+ Engine.addStores(drops);
+ }
+}
\ No newline at end of file
diff --git a/script/path.js b/script/path.js
new file mode 100644
index 000000000..762cac5a4
--- /dev/null
+++ b/script/path.js
@@ -0,0 +1,280 @@
+var Path = {
+
+ DEFAULT_BAG_SPACE: 10,
+
+ // Everything not in this list weighs 1
+ Weight: {
+ 'bone spear': 2,
+ 'iron sword': 3,
+ 'steel sword': 5,
+ 'rifle': 5,
+ 'bullets': 0.1,
+ 'energy cell': 0.2,
+ 'laser rifle': 5,
+ 'bolas': 0.5
+ },
+
+ name: 'Path',
+ options: {}, // Nuthin'
+ init: function(options) {
+ this.options = $.extend(
+ this.options,
+ options
+ );
+
+ // Init the World
+ World.init();
+
+ // Create the path tab
+ this.tab = Header.addLocation("A Dusty Path", "path", Path);
+
+ // Create the Path panel
+ this.panel = $('
').attr('id', "pathPanel")
+ .addClass('location')
+ .appendTo('div#locationSlider');
+
+ // Add the outfitting area
+ var outfitting = $('
').attr('id', 'outfitting').appendTo(this.panel);
+ var bagspace = $('
').attr('id', 'bagspace').appendTo(outfitting);
+
+ // Add the embark button
+ new Button.Button({
+ id: 'embarkButton',
+ text: "embark",
+ click: Path.embark,
+ width: '80px',
+ cooldown: World.DEATH_COOLDOWN
+ }).appendTo(this.panel);
+
+ Path.outfit = {};
+
+ Engine.updateSlider();
+ },
+
+ getWeight: function(thing) {
+ var w = Path.Weight[thing];
+ if(typeof w != 'number') w = 1;
+
+ return w;
+ },
+
+ getCapacity: function() {
+ if(Engine.getStore('convoy') > 0) {
+ return Path.DEFAULT_BAG_SPACE + 60;
+ } else if(Engine.getStore('wagon') > 0) {
+ return Path.DEFAULT_BAG_SPACE + 30;
+ } else if(Engine.getStore('rucksack') > 0) {
+ return Path.DEFAULT_BAG_SPACE + 10;
+ }
+ return Path.DEFAULT_BAG_SPACE;
+ },
+
+ getFreeSpace: function() {
+ var num = 0;
+ if(Path.outfit) {
+ for(var k in Path.outfit) {
+ var n = Path.outfit[k];
+ if(isNaN(n)) {
+ // No idea how this happens, but I will fix it here!
+ Path.outfit[k] = n = 0;
+ }
+ num += n * Path.getWeight(k);
+ }
+ }
+ return Path.getCapacity() - num;
+ },
+
+ updatePerks: function() {
+ if(State.perks) {
+ var perks = $('#perks');
+ var needsAppend = false;
+ if(perks.length == 0) {
+ needsAppend = true;
+ perks = $('
').attr('id', 'perks');
+ }
+ for(var k in State.perks) {
+ var id = 'perk_' + k.replace(' ', '-');
+ var r = $('#' + id);
+ if(State.perks[k] && r.length == 0) {
+ r = $('
').attr('id', id).addClass('perkRow').appendTo(perks);
+ $('
').addClass('row_key').text(k).appendTo(r);
+ $('
').addClass('tooltip bottom right').text(Engine.Perks[k].desc).appendTo(r);
+ }
+ }
+
+ if(needsAppend && perks.children().length > 0) {
+ perks.appendTo(Path.panel);
+ }
+ }
+ },
+
+ updateOutfitting: function() {
+ var outfit = $('div#outfitting');
+
+ if(!Path.outfit) {
+ Path.outfit = {};
+ }
+
+ // Add the armour row
+ var armour = "none";
+ if(Engine.getStore('s armour') > 0)
+ armour = "steel";
+ else if(Engine.getStore('i armour') > 0)
+ armour = "iron";
+ else if(Engine.getStore('l armour') > 0)
+ armour = "leather";
+ var aRow = $('#armourRow');
+ if(aRow.length == 0) {
+ aRow = $('
').attr('id', 'armourRow').addClass('outfitRow').prependTo(outfit);
+ $('
').addClass('row_key').text('armour').appendTo(aRow);
+ $('
').addClass('row_val').text(armour).appendTo(aRow);
+ $('
').addClass('clear').appendTo(aRow);
+ } else {
+ $('.row_val', aRow).text(armour);
+ }
+
+ // Add the water row
+ var wRow = $('#waterRow');
+ if(wRow.length == 0) {
+ wRow = $('
').attr('id', 'waterRow').addClass('outfitRow').insertAfter(aRow);
+ $('
').addClass('row_key').text('water').appendTo(wRow);
+ $('
').addClass('row_val').text(World.getMaxWater()).appendTo(wRow);
+ $('
').addClass('clear').appendTo(wRow);
+ } else {
+ $('.row_val', wRow).text(World.getMaxWater());
+ }
+
+
+ var space = Path.getFreeSpace();
+ var total = 0;
+ // Add the non-craftables to the craftables
+ var carryable = $.extend({
+ 'cured meat': { type: 'tool' },
+ 'bullets': { type: 'tool' },
+ 'grenade': {type: 'weapon' },
+ 'bolas': {type: 'weapon' },
+ 'laser rifle': {type: 'weapon' },
+ 'energy cell': {type: 'tool' },
+ 'bayonet': {type: 'weapon' },
+ 'charm': {type: 'tool'}
+ }, Room.Craftables);
+
+ for(var k in carryable) {
+ var store = carryable[k];
+ var have = State.stores[k];
+ var num = Path.outfit[k];
+ num = typeof num == 'number' ? num : 0;
+ var numAvailable = Engine.getStore(k);
+ var row = $('div#outfit_row_' + k.replace(' ', '-'), outfit);
+ if((store.type == 'tool' || store.type == 'weapon') && have > 0) {
+ total += num * Path.getWeight(k);
+ if(row.length == 0) {
+ row = Path.createOutfittingRow(k, num);
+
+ var curPrev = null;
+ outfit.children().each(function(i) {
+ var child = $(this);
+ if(child.attr('id').indexOf('outfit_row_') == 0) {
+ var cName = child.attr('id').substring(11).replace('-', ' ');
+ if(cName < k && (curPrev == null || cName > curPrev)) {
+ curPrev = cName;
+ }
+ }
+ });
+ if(curPrev == null) {
+ row.insertAfter(wRow);
+ }
+ else
+ {
+ row.insertAfter(outfit.find('#outfit_row_' + curPrev.replace(' ', '-')));
+ }
+ } else {
+ $('div#' + row.attr('id') + ' > div.row_val > span', outfit).text(num);
+ $('div#' + row.attr('id') + ' .tooltip .numAvailable', outfit).text(numAvailable - num);
+ }
+ if(num == 0) {
+ $('.dnBtn', row).addClass('disabled');
+ } else {
+ $('.dnBtn', row).removeClass('disabled');
+ }
+ if(num >= numAvailable || space < Path.getWeight(k)) {
+ $('.upBtn', row).addClass('disabled');
+ } else if(space >= Path.getWeight(k)) {
+ $('.upBtn', row).removeClass('disabled');
+ }
+ } else if(have == 0 && row.length > 0) {
+ row.remove();
+ }
+ }
+
+ // Update bagspace
+ $('#bagspace').text('free ' + Math.floor(Path.getCapacity() - total) + '/' + Path.getCapacity());
+
+ if(Path.outfit['cured meat'] > 0) {
+ Button.setDisabled($('#embarkButton'), false);
+ } else {
+ Button.setDisabled($('#embarkButton'), true);
+ }
+ },
+
+ createOutfittingRow: function(name, num) {
+ var row = $('
').attr('id', 'outfit_row_' + name.replace(' ', '-')).addClass('outfitRow');
+ $('
').addClass('row_key').text(name).appendTo(row);
+ var val = $('
').addClass('row_val').appendTo(row);
+
+ $('
').text(num).appendTo(val);
+ $('').addClass('upBtn').appendTo(val).click(Path.increaseSupply);
+ $('
').addClass('dnBtn').appendTo(val).click(Path.decreaseSupply);
+ $('
').addClass('clear').appendTo(row);
+
+ var numAvailable = Engine.getStore(name);
+ var tt = $('
').addClass('tooltip bottom right').appendTo(row);
+ $('
').addClass('row_key').text('weight').appendTo(tt);
+ $('
').addClass('row_val').text(Path.getWeight(name)).appendTo(tt);
+ $('
').addClass('row_key').text('available').appendTo(tt);
+ $('
').addClass('row_val').addClass('numAvailable').text(numAvailable).appendTo(tt);
+
+ return row;
+ },
+
+ increaseSupply: function() {
+ var supply = $(this).closest('.outfitRow').children('.row_key').text().replace('-', ' ');
+ Engine.log('increasing ' + supply);
+ var cur = Path.outfit[supply];
+ cur = typeof cur == 'number' ? cur : 0;
+ if(Path.getFreeSpace() >= Path.getWeight(supply) && cur < Engine.getStore(supply)) {
+ Path.outfit[supply] = cur + 1;
+ Path.updateOutfitting();
+ }
+ },
+
+ decreaseSupply: function() {
+ var supply = $(this).closest('.outfitRow').children('.row_key').text().replace('-', ' ');
+ Engine.log('decreasing ' + supply);
+ var cur = Path.outfit[supply];
+ cur = typeof cur == 'number' ? cur : 0;
+ if(cur > 0) {
+ Path.outfit[supply] = cur - 1;
+ Path.updateOutfitting();
+ }
+ },
+
+ onArrival: function() {
+ Path.setTitle();
+ Path.updateOutfitting();
+ Path.updatePerks();
+ },
+
+ setTitle: function() {
+ document.title = 'A Dusty Path';
+ },
+
+ embark: function() {
+ for(var k in Path.outfit) {
+ Engine.addStore(k, -Path.outfit[k]);
+ }
+ World.onArrival();
+ $('#outerSlider').animate({left: '-700px'}, 300);
+ Engine.activeModule = World;
+ }
+}
\ No newline at end of file
diff --git a/script/room.js b/script/room.js
new file mode 100644
index 000000000..6b2e27f16
--- /dev/null
+++ b/script/room.js
@@ -0,0 +1,1058 @@
+/**
+ * Module that registers the simple room functionality
+ */
+var Room = {
+ // times in (minutes * seconds * milliseconds)
+ _FIRE_COOL_DELAY: 5 * 60 * 1000, // time after a stoke before the fire cools
+ _ROOM_WARM_DELAY: 30 * 1000, // time between room temperature updates
+ _BUILDER_STATE_DELAY: 0.5 * 60 * 1000, // time between builder state updates
+ _STOKE_COOLDOWN: 10, // cooldown to stoke the fire
+ _NEED_WOOD_DELAY: 15 * 1000, // from when the stranger shows up, to when you need wood
+
+ Craftables: {
+ 'trap': {
+ button: null,
+ maximum: 10,
+ availableMsg: 'builder says she can make traps to catch any creatures might still be alive out there',
+ buildMsg: 'more traps to catch more creatures',
+ maxMsg: "more traps won't help now",
+ type: 'building',
+ cost: function() {
+ var n = Outside.numBuilding('trap');
+ return {
+ 'wood': 10 + (n*10)
+ };
+ }
+ },
+ 'cart': {
+ button: null,
+ maximum: 1,
+ availableMsg: 'builder says she can make a cart for carrying wood',
+ buildMsg: 'the rickety cart will carry more wood from the forest',
+ type: 'building',
+ cost: function() {
+ return {
+ 'wood': 30
+ };
+ }
+ },
+ 'hut': {
+ button: null,
+ maximum: 20,
+ availableMsg: "builder says there are more wanderers. says they'll work, too.",
+ buildMsg: 'builder puts up a hut, out in the forest. says word will get around.',
+ maxMsg: 'no more room for huts.',
+ type: 'building',
+ cost: function() {
+ var n = Outside.numBuilding('hut');
+ return {
+ 'wood': 100 + (n*50)
+ };
+ }
+ },
+ 'lodge': {
+ button: null,
+ maximum: 1,
+ availableMsg: 'villagers could help hunt, given the means',
+ buildMsg: 'the hunting lodge stands in the forest, a ways out of town',
+ type: 'building',
+ cost: function() {
+ return {
+ wood: 200,
+ fur: 10,
+ meat: 5
+ }
+ }
+ },
+ 'trading post': {
+ button: null,
+ maximum: 1,
+ availableMsg: "a trading post would make commerce easier",
+ buildMsg: "now the nomads have a place to set up shop, they might stick around a while",
+ type: 'building',
+ cost: function() {
+ return {
+ 'wood': 400,
+ 'fur': 100
+ };
+ }
+ },
+ 'tannery': {
+ button: null,
+ maximum: 1,
+ availableMsg: "builder says leather could be useful. says the villagers could make it.",
+ buildMsg: 'tannery goes up quick, on the edge of the village',
+ type: 'building',
+ cost: function() {
+ return {
+ 'wood': 500,
+ 'fur': 50
+ };
+ }
+ },
+ 'smokehouse': {
+ button: null,
+ maximum: 1,
+ availableMsg: "should cure the meat, or it'll spoil. builder says she can fix something up.",
+ buildMsg: 'builder finishes the smokehouse. she looks hungry.',
+ type: 'building',
+ cost: function() {
+ return {
+ 'wood': 600,
+ 'meat': 50
+ };
+ }
+ },
+ 'workshop': {
+ button: null,
+ maximum: 1,
+ availableMsg: "builder says she could make finer things, if she had the tools",
+ buildMsg: "workshop's finally ready. builder's excited to get to it",
+ type: 'building',
+ cost: function() {
+ return {
+ 'wood': 800,
+ 'leather': 100,
+ 'scales': 10
+ };
+ }
+ },
+ 'steelworks': {
+ button: null,
+ maximum: 1,
+ availableMsg: "builder says the villagers could make steel, given the tools",
+ buildMsg: "a haze falls over the village as the steelworks fires up",
+ type: 'building',
+ cost: function() {
+ return {
+ 'wood': 1500,
+ 'iron': 100,
+ 'coal': 100
+ };
+ }
+ },
+ 'armoury': {
+ button: null,
+ maximum: 1,
+ availableMsg: "builder says it'd be useful to have a steady source of bullets",
+ buildMsg: "armoury's done, welcoming back the weapons of the past.",
+ type: 'building',
+ cost: function() {
+ return {
+ 'wood': 3000,
+ 'steel': 100,
+ 'sulphur': 50
+ };
+ }
+ },
+ 'torch': {
+ button: null,
+ type: 'tool',
+ buildMsg: 'a torch to keep the dark away',
+ cost: function() {
+ return {
+ 'wood': 1,
+ 'cloth': 1
+ };
+ }
+ },
+ 'waterskin': {
+ button: null,
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: 'this waterskin\'ll hold a bit of water, at least',
+ cost: function() {
+ return {
+ 'leather': 50
+ };
+ }
+ },
+ 'cask': {
+ button: null,
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: 'the cask holds enough water for longer expeditions',
+ cost: function() {
+ return {
+ 'leather': 100,
+ 'iron': 20
+ };
+ }
+ },
+ 'water tank': {
+ button: null,
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: 'never go thirsty again',
+ cost: function() {
+ return {
+ 'iron': 100,
+ 'steel': 50
+ };
+ }
+ },
+ 'bone spear': {
+ button: null,
+ type: 'weapon',
+ buildMsg: "this spear's not elegant, but it's pretty good at stabbing",
+ cost: function() {
+ return {
+ 'wood': 100,
+ 'teeth': 5
+ };
+ }
+ },
+ 'rucksack': {
+ button: null,
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: 'carrying more means longer expeditions to the wilds',
+ cost: function() {
+ return {
+ 'leather': 200
+ };
+ }
+ },
+ 'wagon': {
+ button: null,
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: 'the wagon can carry a lot of supplies',
+ cost: function() {
+ return {
+ 'wood': 500,
+ 'iron': 100
+ };
+ }
+ },
+ 'convoy': {
+ button: null,
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: 'the convoy can haul mostly everything',
+ cost: function() {
+ return {
+ 'wood': 1000,
+ 'iron': 200,
+ 'steel': 100
+ };
+ }
+ },
+ 'l armour': {
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: "leather's not strong. better than rags, though.",
+ cost: function() {
+ return {
+ 'leather': 200,
+ 'scales': 20
+ };
+ }
+ },
+ 'i armour': {
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: "iron's stronger than leather",
+ cost: function() {
+ return {
+ 'leather': 200,
+ 'iron': 100
+ }
+ }
+ },
+ 's armour': {
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: "steel's stronger than iron",
+ cost: function() {
+ return {
+ 'leather': 200,
+ 'steel': 100
+ }
+ }
+ },
+ 'iron sword': {
+ button: null,
+ type: 'weapon',
+ buildMsg: "sword is sharp. good protection out in the wilds.",
+ cost: function() {
+ return {
+ 'wood': 200,
+ 'leather': 50,
+ 'iron': 20
+ };
+ }
+ },
+ 'steel sword': {
+ button: null,
+ type: 'weapon',
+ buildMsg: "the steel is strong, and the blade true.",
+ cost: function() {
+ return {
+ 'wood': 500,
+ 'leather': 100,
+ 'steel': 20
+ };
+ }
+ },
+ 'rifle': {
+ type: 'weapon',
+ buildMsg: "black powder and bullets, like the old days.",
+ cost: function() {
+ return {
+ 'wood': 200,
+ 'steel': 50,
+ 'sulphur': 50
+ }
+ }
+ }
+ },
+
+ TradeGoods: {
+ 'scales': {
+ type: 'good',
+ cost: function() {
+ return { fur: 150 };
+ }
+ },
+ 'teeth': {
+ type: 'good',
+ cost: function() {
+ return { fur: 300 };
+ }
+ },
+ 'iron': {
+ type: 'good',
+ cost: function() {
+ return {
+ 'fur': 150,
+ 'scales': 50
+ }
+ }
+ },
+ 'coal': {
+ type: 'good',
+ cost: function() {
+ return {
+ 'fur': 200,
+ 'teeth': 50
+ }
+ }
+ },
+ 'steel': {
+ type: 'good',
+ cost: function() {
+ return {
+ 'fur': 300,
+ 'scales': 50,
+ 'teeth': 50
+ }
+ }
+ },
+ 'bullets': {
+ type: 'good',
+ cost: function() {
+ return {
+ 'scales': 10
+ }
+ }
+ },
+ 'energy cell': {
+ type: 'good',
+ cost: function() {
+ return {
+ 'scales': 10,
+ 'teeth': 10
+ }
+ }
+ },
+ 'bolas': {
+ type: 'weapon',
+ cost: function() {
+ return {
+ 'teeth': 10
+ }
+ }
+ },
+ 'grenade': {
+ type: 'weapon',
+ cost: function() {
+ return {
+ 'scales': 100,
+ 'teeth': 50
+ }
+ }
+ },
+ 'bayonet': {
+ type: 'weapon',
+ cost: function() {
+ return {
+ 'scales': 500,
+ 'teeth': 250
+ }
+ }
+ },
+ 'alien alloy': {
+ type: 'good',
+ cost: function() {
+ return {
+ 'fur': 1500,
+ 'scales': 750,
+ 'teeth': 300
+ }
+ }
+ },
+ 'compass': {
+ type: 'upgrade',
+ maximum: 1,
+ cost: function() {
+ return {
+ fur: 400,
+ scales: 20,
+ teeth: 10
+ };
+ }
+ }
+ },
+
+ name: "Room",
+ init: function(options) {
+ this.options = $.extend(
+ this.options,
+ options
+ );
+
+ if(Engine._debug) {
+ this._ROOM_WARM_DELAY = 1;
+ this._BUILDER_STATE_DELAY = 1;
+ this._STOKE_COOLDOWN = 0;
+ this._NEED_WOOD_DELAY = 1;
+ }
+
+ if(typeof State.room == 'undefined') {
+ State.room = {
+ temperature: this.TempEnum.Cold,
+ fire: this.FireEnum.Dead,
+ buttons: {},
+ builder: -1
+ };
+ }
+
+ // Create the room tab
+ this.tab = Header.addLocation("A Dark Room", "room", Room);
+
+ // Create the Room panel
+ this.panel = $('
')
+ .attr('id', "roomPanel")
+ .addClass('location')
+ .appendTo('div#locationSlider');
+
+ Engine.updateSlider();
+
+ // Create the light button
+ var lbtn = new Button.Button({
+ id: 'lightButton',
+ text: 'light fire',
+ click: Room.lightFire,
+ cooldown: Room._STOKE_COOLDOWN,
+ width: '80px',
+ cost: {'wood': 5}
+ }).appendTo('div#roomPanel');
+
+ // Create the stoke button
+ var btn = new Button.Button({
+ id: 'stokeButton',
+ text: "stoke fire",
+ click: Room.stokeFire,
+ cooldown: Room._STOKE_COOLDOWN,
+ width: '80px',
+ cost: {'wood': 1}
+ }).appendTo('div#roomPanel');
+
+ // Create the stores container
+ $('
').attr('id', 'storesContainer').appendTo('div#roomPanel');
+
+ Room.updateButton();
+ Room.updateStoresView();
+ Room.updateIncomeView();
+ Room.updateBuildButtons();
+
+ Room._fireTimer = setTimeout(Room.coolFire, Room._FIRE_COOL_DELAY);
+ Room._tempTimer = setTimeout(Room.adjustTemp, Room._ROOM_WARM_DELAY);
+
+ /*
+ * Builder states:
+ * 0 - Approaching
+ * 1 - Collapsed
+ * 2 - Shivering
+ * 3 - Sleeping
+ * 4 - Helping
+ */
+ if(State.room.builder >= 0 && State.room.builder < 3) {
+ Room._builderTimer = setTimeout(Room.updateBuilderState, Room._BUILDER_STATE_DELAY);
+ }
+ if(State.room.builder == 1 && Engine.getStore('wood') < 0) {
+ setTimeout(Room.unlockForest, Room._NEED_WOOD_DELAY);
+ }
+ setTimeout(Engine.collectIncome, 1000);
+
+ Notifications.notify(Room, "the room is " + State.room.temperature.text);
+ Notifications.notify(Room, "the fire is " + State.room.fire.text);
+ },
+
+ options: {}, // Nothing for now
+
+ onArrival: function() {
+ Room.setTitle();
+ if(Room.changed) {
+ Notifications.notify(Room, "the fire is " + State.room.fire.text);
+ Notifications.notify(Room, "the room is " + State.room.temperature.text);
+ Room.changed = false;
+ }
+ if(State.room.builder == 3) {
+ State.room.builder++;
+ Engine.setIncome('builder', {
+ delay: 10,
+ stores: {'wood' : 2 }
+ });
+ Room.updateIncomeView();
+ Notifications.notify(Room, "the stranger is standing by the fire. she says she can help. says she builds things.")
+ }
+ },
+
+ TempEnum: {
+ fromInt: function(value) {
+ for(var k in this) {
+ if(typeof this[k].value != 'undefined' && this[k].value == value) {
+ return this[k];
+ }
+ }
+ return null;
+ },
+ Freezing: { value: 0, text: 'freezing' },
+ Cold: { value: 1, text: 'cold' },
+ Mild: { value: 2, text: 'mild' },
+ Warm: { value: 3, text: 'warm' },
+ Hot: { value: 4, text: 'hot' }
+ },
+
+ FireEnum: {
+ fromInt: function(value) {
+ for(var k in this) {
+ if(typeof this[k].value != 'undefined' && this[k].value == value) {
+ return this[k];
+ }
+ }
+ return null;
+ },
+ Dead: { value: 0, text: 'dead' },
+ Smoldering: { value: 1, text: 'smoldering' },
+ Flickering: { value: 2, text: 'flickering' },
+ Burning: { value: 3, text: 'burning' },
+ Roaring: { value: 4, text: 'roaring' }
+ },
+
+ setTitle: function() {
+ var title = State.room.fire.value < 2 ? "A Dark Room" : "A Firelit Room";
+ if(Engine.activeModule == this) {
+ document.title = title;
+ }
+ $('div#location_room').text(title);
+ },
+
+ updateButton: function() {
+ var light = $('#lightButton.button');
+ var stoke = $('#stokeButton.button');
+ if(State.room.fire.value == Room.FireEnum.Dead.value && stoke.css('display') != 'none') {
+ stoke.hide();
+ light.show();
+ if(stoke.hasClass('disabled')) {
+ Button.cooldown(light);
+ }
+ } else if(light.css('display') != 'none') {
+ stoke.show();
+ light.hide();
+ if(light.hasClass('disabled')) {
+ Button.cooldown(stoke);
+ }
+ }
+
+ if(!Engine.storeAvailable('wood')) {
+ light.addClass('free');
+ stoke.addClass('free');
+ } else {
+ light.removeClass('free');
+ stoke.removeClass('free');
+ }
+ },
+
+ _fireTimer: null,
+ _tempTimer: null,
+ lightFire: function() {
+ var wood = Engine.getStore('wood');
+ if(Engine.storeAvailable('wood') && wood < 5) {
+ Notifications.notify(Room, "not enough wood to get the fire going");
+ Button.clearCooldown($('#lightButton.button'));
+ return;
+ } else if(wood > 4) {
+ Engine.setStore('wood', wood - 5);
+ }
+ State.room.fire = Room.FireEnum.Burning;
+ Room.onFireChange();
+ },
+
+ stokeFire: function() {
+ var wood = Engine.getStore('wood');
+ if(Engine.storeAvailable('wood') && wood == 0) {
+ Notifications.notify(Room, "the wood has run out");
+ Button.clearCooldown($('#stokeButton.button'));
+ return;
+ }
+ if(wood > 0) {
+ Engine.setStore('wood', wood - 1);
+ }
+ if(State.room.fire.value < 4) {
+ State.room.fire = Room.FireEnum.fromInt(State.room.fire.value + 1);
+ }
+ Room.onFireChange();
+ },
+
+ onFireChange: function() {
+ if(Engine.activeModule != Room) {
+ Room.changed = true;
+ }
+ Notifications.notify(Room, "the fire is " + State.room.fire.text, true);
+ if(State.room.fire.value > 1 && State.room.builder < 0) {
+ State.room.builder = 0;
+ Notifications.notify(Room, "the light from the fire spills from the windows, out into the dark");
+ setTimeout(Room.updateBuilderState, Room._BUILDER_STATE_DELAY);
+ }
+ window.clearTimeout(Room._fireTimer);
+ Room._fireTimer = setTimeout(Room.coolFire, Room._FIRE_COOL_DELAY);
+ Room.updateButton();
+ Room.setTitle();
+ },
+
+ coolFire: function() {
+ if(State.room.fire.value <= Room.FireEnum.Flickering.value &&
+ State.room.builder > 3 && Engine.getStore('wood') > 0) {
+ Notifications.notify(Room, "builder stokes the fire", true);
+ Engine.setStore('wood', Engine.getStore('wood') - 1);
+ State.room.fire = Room.FireEnum.fromInt(State.room.fire.value + 1);
+ }
+ if(State.room.fire.value > 0) {
+ State.room.fire = Room.FireEnum.fromInt(State.room.fire.value - 1);
+ Room._fireTimer = setTimeout(Room.coolFire, Room._FIRE_COOL_DELAY);
+ Room.onFireChange();
+ }
+ },
+
+ adjustTemp: function() {
+ var old = State.room.temperature.value;
+ if(State.room.temperature.value > 0 && State.room.temperature.value > State.room.fire.value) {
+ State.room.temperature = Room.TempEnum.fromInt(State.room.temperature.value - 1);
+ Notifications.notify(Room, "the room is " + State.room.temperature.text, true);
+ }
+ if(State.room.temperature.value < 4 && State.room.temperature.value < State.room.fire.value) {
+ State.room.temperature = Room.TempEnum.fromInt(State.room.temperature.value + 1);
+ Notifications.notify(Room, "the room is " + State.room.temperature.text, true);
+ }
+ if(State.room.temperature.value != old) {
+ Room.changed = true;
+ }
+ Room._tempTimer = setTimeout(Room.adjustTemp, Room._ROOM_WARM_DELAY);
+ },
+
+ unlockForest: function() {
+ Engine.setStore('wood', 4);
+ Room.updateButton();
+ Outside.init();
+ Room.updateStoresView();
+ Notifications.notify(Room, "the wind howls outside");
+ Notifications.notify(Room, "the wood is running out");
+ Engine.event('progress', 'outside');
+ },
+
+ updateBuilderState: function() {
+ if(State.room.builder == 0) {
+ Notifications.notify(Room, "a ragged stranger stumbles through the door and collapses in the corner");
+ State.room.builder = 1;
+ setTimeout(Room.unlockForest, Room._NEED_WOOD_DELAY);
+ }
+ else if(State.room.builder < 3 && State.room.temperature.value >= Room.TempEnum.Warm.value) {
+ var msg;
+ switch(State.room.builder) {
+ case 1:
+ msg = "the stranger shivers, and mumbles quietly. her words are unintelligible.";
+ break;
+ case 2:
+ msg = "the stranger in the corner stops shivering. her breathing calms.";
+ break;
+ }
+ Notifications.notify(Room, msg);
+ if(State.room.builder < 3) {
+ State.room.builder++;
+ }
+ }
+ if(State.room.builder < 3) {
+ setTimeout(Room.updateBuilderState, Room._BUILDER_STATE_DELAY);
+ }
+ Engine.saveGame();
+ },
+
+ updateStoresView: function() {
+ var stores = $('div#stores');
+ var weapons = $('div#weapons');
+ var needsAppend = false, wNeedsAppend = false, newRow = false;
+ if(stores.length == 0) {
+ stores = $('
').attr({
+ id: 'stores'
+ }).css('opacity', 0);
+ needsAppend = true;
+ }
+ if(weapons.length == 0) {
+ weapons = $('
').attr({
+ id: 'weapons'
+ }).css('opacity', 0);
+ wNeedsAppend = true;
+ }
+ for(var k in State.stores) {
+
+ var type = null;
+ if(Room.Craftables[k]) {
+ type = Room.Craftables[k].type;
+ } else if(Room.TradeGoods[k]) {
+ type = Room.TradeGoods[k].type;
+ }
+
+ var location;
+ switch(type) {
+ case 'upgrade':
+ // Don't display upgrades on the Room screen
+ continue;
+ case 'weapon':
+ location = weapons;
+ break;
+ default:
+ location = stores;
+ break;
+ }
+
+ var id = "row_" + k.replace(' ', '-');
+ var row = $('div#' + id, location);
+ var num = State.stores[k];
+
+ if(typeof num != 'number' || isNaN(num)) {
+ // No idea how counts get corrupted, but I have reason to believe that they occassionally do.
+ // Build a little fence around it!
+ num = State.stores[k] = 0;
+ }
+
+
+ // thieves?
+ if(typeof State.thieves == 'undefined' && num > 5000 && State.world) {
+ Engine.startThieves();
+ }
+
+ if(row.length == 0 && num > 0) {
+ var row = $('
').attr('id', id).addClass('storeRow');
+ $('
').addClass('row_key').text(k).appendTo(row);
+ $('
').addClass('row_val').text(Math.floor(num)).appendTo(row);
+ $('
').addClass('clear').appendTo(row);
+ var curPrev = null;
+ location.children().each(function(i) {
+ var child = $(this);
+ var cName = child.attr('id').substring(4).replace('-', ' ');
+ if(cName < k && (curPrev == null || cName > curPrev)) {
+ curPrev = cName;
+ }
+ });
+ if(curPrev == null) {
+ row.prependTo(location);
+ } else {
+ row.insertAfter(location.find('#row_' + curPrev.replace(' ', '-')));
+ }
+ newRow = true;
+ } else if(num> 0){
+ $('div#' + row.attr('id') + ' > div.row_val', location).text(Math.floor(num));
+ } else if(num == 0) {
+ row.remove();
+ }
+ }
+
+ if(needsAppend && stores.children().length > 0) {
+ stores.appendTo('div#storesContainer');
+ stores.animate({opacity: 1}, 300, 'linear');
+ }
+
+ if(wNeedsAppend && weapons.children().length > 0) {
+ weapons.appendTo('div#storesContainer');
+ weapons.animate({opacity: 1}, 300, 'linear');
+ }
+
+ if(newRow) {
+ Room.updateIncomeView();
+ }
+ },
+
+ updateIncomeView: function() {
+ var stores = $('div#stores');
+ if(stores.length == 0 || typeof State.income == 'undefined') return;
+ $('div.storeRow', stores).each(function(index, el) {
+ el = $(el);
+ $('div.tooltip', el).remove();
+ var tt = $('
').addClass('tooltip bottom right');
+ var storeName = el.attr('id').substring(4).replace('-', ' ');
+ for(var incomeSource in State.income) {
+ var income = State.income[incomeSource];
+ for(var store in income.stores) {
+ if(store == storeName && income.stores[store] != 0) {
+ $('
').addClass('row_key').text(incomeSource).appendTo(tt);
+ $('
')
+ .addClass('row_val')
+ .text(Engine.getIncomeMsg(income.stores[store], income.delay))
+ .appendTo(tt);
+ }
+ }
+ }
+ if(tt.children().length > 0) {
+ tt.appendTo(el);
+ }
+ });
+ },
+
+ buy: function(buyBtn) {
+ var thing = $(buyBtn).attr('buildThing');
+ var good = Room.TradeGoods[thing];
+ var numThings = Engine.getStore(thing);
+ if(numThings < 0) numThings = 0;
+ if(good.maximum <= numThings) {
+ return;
+ }
+
+ var storeMod = {};
+ var cost = good.cost();
+ for(var k in cost) {
+ var have = Engine.getStore(k)
+ if(have < cost[k]) {
+ Notifications.notify(Room, "not enough " + k);
+ return false;
+ } else {
+ storeMod[k] = have - cost[k];
+ }
+ }
+ Engine.setStores(storeMod);
+
+ Notifications.notify(Room, good.buildMsg);
+
+ Engine.addStore(thing, 1);
+
+ Room.updateBuildButtons();
+
+ if(thing == 'compass') {
+ Engine.openPath();
+ }
+ },
+
+ build: function(buildBtn) {
+ var thing = $(buildBtn).attr('buildThing');
+ if(State.room.temperature.value <= Room.TempEnum.Cold.value) {
+ Notifications.notify(Room, "builder just shivers");
+ return false;
+ }
+ var craftable = Room.Craftables[thing];
+
+ var numThings = 0;
+ switch(craftable.type) {
+ case 'good':
+ case 'weapon':
+ case 'tool':
+ case 'upgrade':
+ numThings = Engine.getStore(thing);
+ break;
+ case 'building':
+ numThings = Outside.numBuilding(thing);
+ break;
+ }
+
+ if(numThings < 0) numThings = 0;
+ if(craftable.maximum <= numThings) {
+ return;
+ }
+
+ var storeMod = {};
+ var cost = craftable.cost();
+ for(var k in cost) {
+ var have = Engine.getStore(k)
+ if(have < cost[k]) {
+ Notifications.notify(Room, "not enough " + k);
+ return false;
+ } else {
+ storeMod[k] = have - cost[k];
+ }
+ }
+ Engine.setStores(storeMod);
+
+ Notifications.notify(Room, craftable.buildMsg);
+
+ switch(craftable.type) {
+ case 'good':
+ case 'weapon':
+ case 'upgrade':
+ case 'tool':
+ Engine.addStore(thing, 1);
+ break;
+ case 'building':
+ Outside.addBuilding(thing, 1);
+ break;
+ }
+
+ Room.updateBuildButtons();
+
+ },
+
+ needsWorkshop: function(type) {
+ return type == 'weapon' || type == 'upgrade' || type =='tool';
+ },
+
+ craftUnlocked: function(thing) {
+ if(typeof State.room != 'undefined' &&
+ typeof State.room.buttons != 'undefined' &&
+ State.room.buttons[thing]) {
+ return true;
+ }
+ if(State.room.builder < 4) return false;
+ var craftable = Room.Craftables[thing];
+ if(Room.needsWorkshop(craftable.type) && Outside.numBuilding('workshop') == 0) return false;
+ var cost = craftable.cost();
+
+ // Show buttons if we have at least 1/2 the wood, and all other components have been seen.
+ if(Engine.getStore('wood') < cost['wood'] * 0.5) {
+ return false;
+ }
+ for(var c in cost) {
+ if(!Engine.storeAvailable(c)) {
+ return false;
+ }
+ }
+
+ State.room.buttons[thing] = true;
+ Notifications.notify(Room, craftable.availableMsg);
+ return true;
+ },
+
+ buyUnlocked: function(thing) {
+ if(typeof State.room != 'undefined' &&
+ typeof State.room.buttons != 'undefined' &&
+ State.room.buttons[thing]) {
+ return true;
+ } else if(Outside.numBuilding('trading post') > 0) {
+ if(thing == 'compass' || Engine.storeAvailable(thing)) {
+ // Allow the purchase of stuff once you've seen it
+ return true;
+ }
+ }
+ return false;
+ },
+
+ updateBuildButtons: function() {
+ var buildSection = $('#buildBtns');
+ var needsAppend = false;
+ if(buildSection.length == 0) {
+ buildSection = $('
').attr('id', 'buildBtns').css('opacity', 0);
+ needsAppend = true;
+ }
+
+ var craftSection = $('#craftBtns');
+ var cNeedsAppend = false;
+ if(craftSection.length == 0 && Outside.numBuilding('workshop') > 0) {
+ craftSection = $('
').attr('id', 'craftBtns').css('opacity', 0);
+ cNeedsAppend = true;
+ }
+
+ var buySection = $('#buyBtns');
+ var bNeedsAppend = false;
+ if(buySection.length == 0 && Outside.numBuilding('trading post') > 0) {
+ buySection = $('
').attr('id', 'buyBtns').css('opacity', 0);
+ bNeedsAppend = true;
+ }
+
+ for(var k in Room.Craftables) {
+ craftable = Room.Craftables[k];
+ var max = Engine.num(k, craftable) + 1 > craftable.maximum;
+ if(craftable.button == null) {
+ if(Room.craftUnlocked(k)) {
+ var loc = Room.needsWorkshop(craftable.type) ? craftSection : buildSection;
+ craftable.button = new Button.Button({
+ id: 'build_' + k,
+ cost: craftable.cost(),
+ text: k,
+ click: Room.build,
+ width: '80px',
+ ttPos: loc.children().length > 10 ? 'top right' : 'bottom right'
+ }).css('opacity', 0).attr('buildThing', k).appendTo(loc).animate({opacity: 1}, 300, 'linear');
+ }
+ } else {
+ // refresh the tooltip
+ var costTooltip = $('.tooltip', craftable.button);
+ costTooltip.empty();
+ var cost = craftable.cost();
+ for(var k in cost) {
+ $("
").addClass('row_key').text(k).appendTo(costTooltip);
+ $("
").addClass('row_val').text(cost[k]).appendTo(costTooltip);
+ }
+ if(max && !craftable.button.hasClass('disabled')) {
+ Notifications.notify(Room, craftable.maxMsg);
+ }
+ }
+ if(max) {
+ Button.setDisabled(craftable.button, true);
+ } else {
+ Button.setDisabled(craftable.button, false);
+ }
+ }
+
+ for(var k in Room.TradeGoods) {
+ good = Room.TradeGoods[k];
+ var max = Engine.num(k, good) + 1 > good.maximum;
+ if(good.button == null) {
+ if(Room.buyUnlocked(k)) {
+ good.button = new Button.Button({
+ id: 'build_' + k,
+ cost: good.cost(),
+ text: k,
+ click: Room.buy,
+ width: '80px'
+ }).css('opacity', 0).attr('buildThing', k).appendTo(buySection).animate({opacity:1}, 300, 'linear');
+ }
+ } else {
+ // refresh the tooltip
+ var costTooltip = $('.tooltip', good.button);
+ costTooltip.empty();
+ var cost = good.cost();
+ for(var k in cost) {
+ $("
").addClass('row_key').text(k).appendTo(costTooltip);
+ $("
").addClass('row_val').text(cost[k]).appendTo(costTooltip);
+ }
+ if(max && !good.button.hasClass('disabled')) {
+ Notifications.notify(Room, good.maxMsg);
+ }
+ }
+ if(max) {
+ Button.setDisabled(good.button, true);
+ } else {
+ Button.setDisabled(good.button, false);
+ }
+ }
+
+ if(needsAppend && buildSection.children().length > 0) {
+ buildSection.appendTo('div#roomPanel').animate({opacity: 1}, 300, 'linear');
+ }
+ if(cNeedsAppend && craftSection.children().length > 0) {
+ craftSection.appendTo('div#roomPanel').animate({opacity: 1}, 300, 'linear');
+ }
+ if(bNeedsAppend && buildSection.children().length > 0) {
+ buySection.appendTo('div#roomPanel').animate({opacity: 1}, 300, 'linear');
+ }
+ }
+};
\ No newline at end of file
diff --git a/script/ship.js b/script/ship.js
new file mode 100644
index 000000000..cff19cf8f
--- /dev/null
+++ b/script/ship.js
@@ -0,0 +1,165 @@
+/**
+ * Module that registers the starship!
+ */
+var Ship = {
+ LIFTOFF_COOLDOWN: 120,
+ ALLOY_PER_HULL: 1,
+ ALLOY_PER_THRUSTER: 1,
+ BASE_HULL: 0,
+ BASE_THRUSTERS: 1,
+
+ name: "Ship",
+ init: function(options) {
+ this.options = $.extend(
+ this.options,
+ options
+ );
+
+ if(!State.ship) {
+ State.ship = {
+ hull: Ship.BASE_HULL,
+ thrusters: Ship.BASE_THRUSTERS
+ }
+ }
+
+ // Create the Ship tab
+ this.tab = Header.addLocation("An Old Starship", "ship", Ship);
+
+ // Create the Ship panel
+ this.panel = $('
').attr('id', "shipPanel")
+ .addClass('location')
+ .appendTo('div#locationSlider');
+
+ Engine.updateSlider();
+
+ // Draw the hull label
+ var hullRow = $('
').attr('id', 'hullRow').appendTo('div#shipPanel');
+ $('
').addClass('row_key').text('hull:').appendTo(hullRow);
+ $('
').addClass('row_val').text(State.ship.hull).appendTo(hullRow);
+ $('
').addClass('clear').appendTo(hullRow);
+
+ // Draw the thrusters label
+ var engineRow = $('
').attr('id', 'engineRow').appendTo('div#shipPanel');
+ $('
').addClass('row_key').text('engine:').appendTo(engineRow);
+ $('
').addClass('row_val').text(State.ship.thrusters).appendTo(engineRow);
+ $('
').addClass('clear').appendTo(engineRow);
+
+ // Draw the reinforce button
+ new Button.Button({
+ id: 'reinforceButton',
+ text: 'reinforce hull',
+ click: Ship.reinforceHull,
+ width: '100px',
+ cost: {'alien alloy': Ship.ALLOY_PER_HULL}
+ }).appendTo('div#shipPanel');
+
+ // Draw the engine button
+ new Button.Button({
+ id: 'engineButton',
+ text: 'upgrade engine',
+ click: Ship.upgradeEngine,
+ width: '100px',
+ cost: {'alien alloy': Ship.ALLOY_PER_THRUSTER}
+ }).appendTo('div#shipPanel');
+
+ // Draw the lift off button
+ var b = new Button.Button({
+ id: 'liftoffButton',
+ text: 'lift off',
+ click: Ship.checkLiftOff,
+ width: '100px',
+ cooldown: Ship.LIFTOFF_COOLDOWN
+ }).appendTo('div#shipPanel');
+
+ if(State.ship.hull <= 0) {
+ Button.setDisabled(b, true);
+ }
+
+ // Init Space
+ Space.init();
+ },
+
+ options: {}, // Nothing for now
+
+ onArrival: function() {
+ Ship.setTitle();
+ if(!State.seenShip) {
+ Notifications.notify(Ship, 'somewhere above the debris cloud, the wanderer fleet hovers. been on this rock too long.');
+ State.seenShip = true;
+ Engine.saveGame();
+ }
+ },
+
+ setTitle: function() {
+ if(Engine.activeModule == this) {
+ document.title = "An Old Starship";
+ }
+ },
+
+ reinforceHull: function() {
+ if(Engine.getStore('alien alloy') < Ship.ALLOY_PER_HULL) {
+ Notifications.notify(Ship, "not enough alien alloy");
+ return false;
+ }
+ Engine.addStore('alien alloy', -Ship.ALLOY_PER_HULL);
+ State.ship.hull++;
+ if(State.ship.hull > 0) {
+ Button.setDisabled($('#liftoffButton', Ship.panel), false);
+ }
+ $('#hullRow .row_val', Ship.panel).text(State.ship.hull);
+ },
+
+ upgradeEngine: function() {
+ if(Engine.getStore('alien alloy') < Ship.ALLOY_PER_THRUSTER) {
+ Notifications.notify(Ship, "not enough alien alloy");
+ return false;
+ }
+ Engine.addStore('alien alloy', -Ship.ALLOY_PER_THRUSTER);
+ State.ship.thrusters++;
+ $('#engineRow .row_val', Ship.panel).text(State.ship.thrusters);
+ },
+
+ getMaxHull: function() {
+ return State.ship.hull;
+ },
+
+ checkLiftOff: function() {
+ if(!State.ship.seenWarning) {
+ Events.startEvent({
+ title: 'Ready to Leave?',
+ scenes: {
+ 'start': {
+ text: [
+ "time to get out of this place. won't be coming back."
+ ],
+ buttons: {
+ 'fly': {
+ text: 'lift off',
+ onChoose: function() {
+ State.ship.seenWarning = true;
+ Ship.liftOff();
+ },
+ nextScene: 'end'
+ },
+ 'wait': {
+ text: 'linger',
+ onChoose: function() {
+ Button.clearCooldown($('#liftoffButton'));
+ },
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ });
+ } else {
+ Ship.liftOff();
+ }
+ },
+
+ liftOff: function () {
+ $('#outerSlider').animate({top: '700px'}, 300);
+ Space.onArrival();
+ Engine.activeModule = Space;
+ }
+};
\ No newline at end of file
diff --git a/script/space.js b/script/space.js
new file mode 100644
index 000000000..4a3bf440d
--- /dev/null
+++ b/script/space.js
@@ -0,0 +1,453 @@
+/**
+ * Module that registers spaaaaaaaaace!
+ */
+var Space = {
+ SHIP_SPEED: 3,
+ BASE_ASTEROID_DELAY: 500,
+ BASE_ASTEROID_SPEED: 1500,
+ FTB_SPEED: 60000,
+ STAR_WIDTH: 3000,
+ STAR_HEIGHT: 3000,
+ NUM_STARS: 200,
+ STAR_SPEED: 60000,
+ FRAME_DELAY: 100,
+
+ stars: null,
+ backStars: null,
+ ship: null,
+ lastMove: null,
+ done: false,
+ shipX: null,
+ shipY: null,
+
+ hull: 0,
+
+ name: "Space",
+ init: function(options) {
+ this.options = $.extend(
+ this.options,
+ options
+ );
+
+ // Create the Space panel
+ this.panel = $('
').attr('id', "spacePanel")
+ .addClass('location')
+ .appendTo('#outerSlider');
+
+ // Create the ship
+ Space.ship = $('
').text("@").attr('id', 'ship').appendTo(this.panel);
+
+ // Create the hull display
+ var h = $('
').attr('id', 'hullRemaining').appendTo(this.panel);
+ $('
').addClass('row_key').text('hull: ').appendTo(h);
+ $('
').addClass('row_val').appendTo(h);
+ },
+
+ options: {}, // Nothing for now
+
+ onArrival: function() {
+ Space.done = false;
+ Engine.keyLock = false;
+ Space.hull = Ship.getMaxHull();
+ Space.altitude = 0;
+ Space.setTitle();
+ Space.updateHull();
+
+ Space.up =
+ Space.down =
+ Space.left =
+ Space.right = false;
+
+ Space.ship.css({
+ top: '350px',
+ left: '350px'
+ });
+ Space.startAscent();
+ Space._shipTimer = setInterval(Space.moveShip, 33);
+ },
+
+ setTitle: function() {
+ if(Engine.activeModule == this) {
+ var t;
+ if(Space.altitude < 10) {
+ t = "Troposphere";
+ } else if(Space.altitude < 20) {
+ t = "Stratosphere";
+ } else if(Space.altitude < 30) {
+ t = "Mesosphere";
+ } else if(Space.altitude < 45) {
+ t = "Thermosphere";
+ } else if(Space.altitude < 60){
+ t = "Exosphere";
+ } else {
+ t = "Space";
+ }
+ document.title = t;
+ }
+ },
+
+ getSpeed: function() {
+ return Space.SHIP_SPEED + State.ship.thrusters;
+ },
+
+ updateHull: function() {
+ $('div#hullRemaining div.row_val', Space.panel).text(Space.hull + '/' + Ship.getMaxHull());
+ },
+
+ createAsteroid: function(noNext) {
+ var r = Math.random();
+ var c;
+ if(r < 0.2)
+ c = '#';
+ else if(r < 0.4)
+ c = '$'
+ else if(r < 0.6)
+ c = '%';
+ else if(r < 0.8)
+ c = '&';
+ else
+ c = 'H';
+
+ var x = Math.floor(Math.random() * 700);
+ var a = $('
').addClass('asteroid').text(c).appendTo('#spacePanel').css('left', x + 'px')
+ a.data({
+ xMin: x,
+ xMax: x + a.width(),
+ height: a.height()
+ });
+ a.animate({
+ top: '740px'
+ }, {
+ duration: Space.BASE_ASTEROID_SPEED - Math.floor(Math.random() * (Space.BASE_ASTEROID_SPEED * 0.65)),
+ easing: 'linear',
+ progress: function() {
+ // Collision detection
+ var t = $(this);
+ if(t.data('xMin') <= Space.shipX && t.data('xMax') >= Space.shipX) {
+ var aY = t.css('top');
+ aY = parseFloat(aY.substring(0, aY.length - 2));
+
+ if(aY <= Space.shipY && aY + t.data('height') >= Space.shipY) {
+ // Collision
+ Engine.log('collision');
+ t.remove();
+ Space.hull--;
+ Space.updateHull();
+ if(Space.hull == 0) {
+ Space.crash();
+ }
+ }
+ }
+ },
+ complete: function() {
+ $(this).remove();
+ }
+ });
+ if(!noNext) {
+
+ // Harder
+ if(Space.altitude > 10) {
+ Space.createAsteroid(true);
+ }
+
+ // HARDER
+ if(Space.altitude > 20) {
+ Space.createAsteroid(true);
+ Space.createAsteroid(true);
+ }
+
+ // HAAAAAARDERRRRR!!!!1
+ if(Space.altitude > 40) {
+ Space.createAsteroid(true);
+ Space.createAsteroid(true);
+ }
+
+ if(!Space.done) {
+ setTimeout(Space.createAsteroid, 1000 - (Space.altitude * 10));
+ }
+ }
+ },
+
+ moveShip: function() {
+ var x = Space.ship.css('left');
+ x = parseFloat(x.substring(0, x.length - 2));
+ var y = Space.ship.css('top');
+ y = parseFloat(y.substring(0, y.length - 2));
+
+ var dx = 0, dy = 0;
+
+ if(Space.up) {
+ dy -= Space.getSpeed();
+ } else if(Space.down) {
+ dy += Space.getSpeed();
+ }
+ if(Space.left) {
+ dx -= Space.getSpeed();
+ } else if(Space.right) {
+ dx += Space.getSpeed();
+ }
+
+ if(dx != 0 && dy != 0) {
+ dx = dx / Math.sqrt(2);
+ dy = dy / Math.sqrt(2);
+ }
+
+ if(Space.lastMove != null) {
+ var dt = Date.now() - Space.lastMove;
+ dx *= dt / 33;
+ dy *= dt / 33;
+ }
+
+ x = x + dx;
+ y = y + dy;
+ if(x < 10) {
+ x = 10;
+ } else if(x > 690) {
+ x = 690;
+ }
+ if(y < 10) {
+ y = 10;
+ } else if(y > 690) {
+ y = 690;
+ }
+
+ Space.shipX = x;
+ Space.shipY = y;
+
+ Space.ship.css({
+ left: x + 'px',
+ top: y + 'px',
+ });
+
+ Space.lastMove = Date.now();
+ },
+
+ startAscent: function() {
+ $('body').addClass('noMask').css({backgroundColor: '#FFFFFF'}).animate({
+ backgroundColor: '#000000'
+ }, {
+ duration: Space.FTB_SPEED,
+ easing: 'linear',
+ progress: function() {
+ var cur = $('body').css('background-color');
+ var s = 'linear-gradient(rgba' + cur.substring(3, cur.length - 1) + ', 0) 0%, rgba' +
+ cur.substring(3, cur.length - 1) + ', 1) 100%)';
+ $('#notifyGradient').attr('style', 'background-color:'+cur+';background:-webkit-' + s + ';background:' + s);
+ },
+ complete: Space.endGame
+ });
+ Space.drawStars();
+ Space._timer = setInterval(function() {
+ Space.altitude += 1;
+ if(Space.altitude % 10 == 0) {
+ Space.setTitle();
+ }
+ if(Space.altitude > 60) {
+ clearInterval(Space._timer);
+ }
+ }, 1000);
+
+ setTimeout(function() {
+ $('#spacePanel, .deleteSave, .share').animate({color: 'white'}, 500, 'linear');
+ }, Space.FTB_SPEED / 2);
+
+ Space.createAsteroid();
+ },
+
+ drawStars: function(duration) {
+ var starsContainer = $('
').attr('id', 'starsContainer').appendTo('body');
+ Space.stars = $('
').css('bottom', '0px').attr('id', 'stars').appendTo(starsContainer);
+ var s1 = $('
').css({
+ width: Space.STAR_WIDTH + 'px',
+ height: Space.STAR_HEIGHT + 'px'
+ });
+ var s2 = s1.clone();
+ Space.stars.append(s1).append(s2);
+ Space.drawStarAsync(s1, s2, 0);
+ Space.stars.data('speed', Space.STAR_SPEED);
+ Space.startAnimation(Space.stars);
+
+ Space.starsBack = $('
').css('bottom', '0px').attr('id', 'starsBack').appendTo(starsContainer);
+ s1 = $('
').css({
+ width: Space.STAR_WIDTH + 'px',
+ height: Space.STAR_HEIGHT + 'px'
+ });
+ s2 = s1.clone();
+ Space.starsBack.append(s1).append(s2);
+ Space.drawStarAsync(s1, s2, 0);
+ Space.starsBack.data('speed', Space.STAR_SPEED * 2);
+ Space.startAnimation(Space.starsBack);
+ },
+
+ startAnimation: function(el) {
+ el.animate({bottom: '-3000px'}, el.data('speed'), 'linear', function() {
+ $(this).css('bottom', '0px');
+ Space.startAnimation($(this));
+ });
+ },
+
+ drawStarAsync: function(el, el2, num) {
+ var top = Math.floor(Math.random() * Space.STAR_HEIGHT) + 'px';
+ var left = Math.floor(Math.random() * Space.STAR_WIDTH) + 'px';
+ $('
').text('.').addClass('star').css({
+ top: top,
+ left: left
+ }).appendTo(el);
+ $('
').text('.').addClass('star').css({
+ top: top,
+ left: left
+ }).appendTo(el2);
+ if(num < Space.NUM_STARS) {
+ setTimeout(function() { Space.drawStarAsync(el, el2, num + 1); }, 100);
+ }
+ },
+
+ crash: function() {
+ if(Space.done) return;
+ Engine.keyLock = true;
+ Space.done = true;
+ clearInterval(Space._timer);
+ clearInterval(Space._shipTimer);
+
+ // Craaaaash!
+ $('body').removeClass('noMask').stop().animate({
+ backgroundColor: '#FFFFFF'
+ }, {
+ duration: 300,
+ progress: function() {
+ var cur = $('body').css('background-color');
+ var s = 'linear-gradient(rgba' + cur.substring(3, cur.length - 1) + ', 0) 0%, rgba' +
+ cur.substring(3, cur.length - 1) + ', 1) 100%)';
+ $('#notifyGradient').attr('style', 'background-color:'+cur+';background:-webkit-' + s + ';background:' + s);
+ },
+ complete: function() {
+ Space.stars.remove();
+ Space.starsBack.remove();
+ Space.stars = Space.starsBack = null;
+ $('#starsContainer').remove();
+ }
+ });
+ $('#spacePanel, .deleteSave, .share').animate({color: 'black'}, 300, 'linear');
+ $('#outerSlider').animate({top: '0px'}, 300, 'linear');
+ Engine.activeModule = Ship;
+ Ship.onArrival();
+ Button.cooldown($('#liftoffButton'));
+ Engine.event('progress', 'crash');
+ },
+
+ endGame: function() {
+ if(Space.done) return;
+ Engine.event('progress', 'win');
+ Space.done = true;
+ clearInterval(Space._timer);
+ clearInterval(Space._shipTimer);
+ clearTimeout(Engine._saveTimer);
+ clearTimeout(Outside._popTimeout);
+ clearTimeout(Engine._incomeTimeout);
+ clearTimeout(Events._eventTimeout);
+ clearTimeout(Room._fireTimer);
+ clearTimeout(Room._tempTimer);
+ for(var k in Room.Craftables) {
+ Room.Craftables[k].button = null;
+ }
+ for(var k in Room.TradeGoods) {
+ Room.TradeGoods[k].button = null;
+ }
+ delete Outside._popTimeout;
+
+ $('#hullRemaining', Space.panel).animate({opacity: 0}, 500, 'linear');
+ Space.ship.animate({
+ top: '350px',
+ left: '240px'
+ }, 3000, 'linear', function() {
+ setTimeout(function() {
+ Space.ship.animate({
+ top: '-100px'
+ }, 200, 'linear', function() {
+ // Restart everything! Play FOREVER!
+ $('#outerSlider').css({'left': '0px', 'top': '0px'});
+ $('#locationSlider, #worldPanel, #spacePanel, #notifications').remove();
+ $('#header').empty();
+ setTimeout(function() {
+ $('body').removeClass('noMask').stop().animate({
+ opacity: 0,
+ 'background-color': '#FFF'
+ }, {
+ duration: 2000,
+ progress: function() {
+ var cur = $('body').css('background-color');
+ var s = 'linear-gradient(rgba' + cur.substring(3, cur.length - 1) + ', 0) 0%, rgba' +
+ cur.substring(3, cur.length - 1) + ', 1) 100%)';
+ $('#notifyGradient').attr('style', 'background-color:'+cur+';background:-webkit-' + s + ';background:' + s);
+ },
+ complete: function() {
+ $('#starsContainer, .deleteSave, .share').remove();
+ if(typeof Storage != 'undefined' && localStorage) {
+ localStorage.clear();
+ }
+ delete window.State;
+ Engine.options = {};
+ setTimeout(function() {
+ Engine.init();
+ $('body').animate({
+ opacity: 1
+ }, 500, 'linear');
+ }, 2000);
+ }
+ });
+ }, 2000);
+ });
+ }, 2000);
+ });
+ },
+
+ keyDown: function(event) {
+ switch(event.which) {
+ case 38: // Up
+ case 87:
+ Space.up = true;
+ Engine.log('up on');
+ break;
+ case 40: // Down
+ case 83:
+ Space.down = true;
+ Engine.log('down on');
+ break;
+ case 37: // Left
+ case 65:
+ Space.left = true;
+ Engine.log('left on');
+ break;
+ case 39: // Right
+ case 68:
+ Space.right = true;
+ Engine.log('right on');
+ break;
+ }
+ },
+
+ keyUp: function(event) {
+ switch(event.which) {
+ case 38: // Up
+ case 87:
+ Space.up = false;
+ Engine.log('up off');
+ break;
+ case 40: // Down
+ case 83:
+ Space.down = false;
+ Engine.log('down off');
+ break;
+ case 37: // Left
+ case 65:
+ Space.left = false;
+ Engine.log('left off');
+ break;
+ case 39: // Right
+ case 68:
+ Space.right = false;
+ Engine.log('right off');
+ break;
+ }
+ }
+};
\ No newline at end of file
diff --git a/script/world.js b/script/world.js
new file mode 100644
index 000000000..0e8146523
--- /dev/null
+++ b/script/world.js
@@ -0,0 +1,825 @@
+var World = {
+
+ RADIUS: 30,
+ TILE: {
+ VILLAGE: 'A',
+ IRON_MINE: 'I',
+ COAL_MINE: 'C',
+ SULPHUR_MINE: 'S',
+ FOREST: 'T',
+ FIELD: ',',
+ BARRENS: '.',
+ ROAD: '#',
+ HOUSE: 'H',
+ CAVE: 'V',
+ TOWN: 'O',
+ CITY: 'Y',
+ OUTPOST: 'P',
+ SHIP: 'W',
+ BOREHOLE: 'B',
+ BATTLEFIELD: 'F',
+ SWAMP: 'M'
+ },
+ 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: 10,
+ FIGHT_DELAY: 3, // At least three moves between fights
+
+ 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 }
+ }
+ },
+
+ 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'};
+
+ if(typeof State.world == 'undefined') {
+ State.world = {
+ map: World.generateMap(),
+ mask: World.newMask()
+ };
+ }
+
+ // 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();
+ },
+
+ clearDungeon: function() {
+ Engine.event('progress', 'dungeon cleared');
+ World.state.map[World.curPos[0]][World.curPos[1]] = World.TILE.OUTPOST;
+ World.drawRoad();
+ },
+
+ drawRoad: function() {
+ var xDist = World.curPos[0] - World.RADIUS;
+ var yDist = World.curPos[1] - World.RADIUS;
+ var xDir = Math.abs(xDist)/xDist;
+ var yDir = Math.abs(yDist)/yDist;
+ var xIntersect, yIntersect;
+ if(Math.abs(xDist) > Math.abs(yDist)) {
+ xIntersect = World.RADIUS;
+ yIntersect = World.RADIUS + yDist;
+ } else {
+ xIntersect = World.RADIUS + xDist;
+ yIntersect = World.RADIUS;
+ }
+
+ for(var x = 0; x < Math.abs(xDist); x++) {
+ if(World.isTerrain(World.state.map[World.RADIUS + (xDir*x)][yIntersect])) {
+ World.state.map[World.RADIUS + (xDir*x)][yIntersect] = World.TILE.ROAD;
+ }
+ }
+ for(var y = 0; y < Math.abs(yDist); y++) {
+ if(World.isTerrain(World.state.map[xIntersect][World.RADIUS + (yDir*y)])) {
+ World.state.map[xIntersect][World.RADIUS + (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:' + 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(Engine.getStore('rucksack') > 0) {
+ t = 'rucksack';
+ }
+ $('#backpackTitle').text(t);
+
+ // Update bagspace
+ $('#backpackSpace').text('free ' + 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: ' + World.health + '/' + World.getMaxHealth());
+ }
+ },
+
+ createItemDiv: function(name, num) {
+ var div = $('
').attr('id', 'supply_' + name.replace(' ', '-'))
+ .addClass('supplyItem')
+ .text(name + ':' + num);
+
+ return div;
+ },
+
+ keyDown: function(event) {
+ var moved = true;
+ var oldTile = World.state.map[World.curPos[0]][World.curPos[1]];
+ switch(event.which) {
+ case 38: // Up
+ case 87:
+ Engine.log('up');
+ if(World.curPos[1] > 0) World.curPos[1]--;
+ break;
+ case 40: // Down
+ case 83:
+ Engine.log('down');
+ if(World.curPos[1] < World.RADIUS * 2) World.curPos[1]++;
+ break;
+ case 37: // Left
+ case 65:
+ Engine.log('left');
+ if(World.curPos[0] > 0) World.curPos[0]--;
+ break;
+ case 39: // Right
+ case 68:
+ Engine.log('right');
+ if(World.curPos[0] < World.RADIUS * 2) World.curPos[0]++;
+ break;
+ default:
+ moved = false;
+ break;
+ }
+ if(moved) {
+ 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();
+ 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');
+ }
+ }
+ }
+ },
+
+ checkDanger: function() {
+ World.danger = typeof World.danger == 'undefined' ? false: World.danger;
+ if(!World.danger) {
+ if(!Engine.getStore('i armour') > 0 && World.getDistance() >= 8) {
+ World.danger = true;
+ return true;
+ }
+ if(!Engine.getStore('s armour') > 0 && World.getDistance() >= 18) {
+ World.danger = true;
+ return true;
+ }
+ } else {
+ if(World.getDistance() < 8) {
+ World.danger = false;
+ return true;
+ }
+ if(World.getDistance < 18 && Engine.getStore('i armour') > 0) {
+ World.danger = false;
+ return true;
+ }
+ }
+ return false;
+ },
+
+ useSupplies: function() {
+ World.foodMove++;
+ World.waterMove++;
+ // Food
+ var movesPerFood = World.MOVES_PER_FOOD;
+ movesPerFood *= Engine.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 {
+ State.starved = State.starved ? State.starved : 0;
+ State.starved++;
+ if(State.starved >= 10 && !Engine.hasPerk('slow metabolism')) {
+ Engine.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 *= Engine.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 {
+ State.dehydrated = State.dehydrated ? State.dehydrated : 0;
+ State.dehydrated++;
+ if(State.dehydrated >= 10 && !Engine.hasPerk('desert rat')) {
+ Engine.addPerk('desert rat');
+ }
+ World.die();
+ return false;
+ }
+ } else {
+ World.thirst = false;
+ }
+ World.setWater(water);
+ World.updateSupplies();
+ }
+ return true;
+ },
+
+ meatHeal: function() {
+ return World.MEAT_HEAL * (Engine.hasPerk('gastronome') ? 2 : 1);
+ },
+
+ 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 *= Engine.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(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() {
+ return Math.abs(World.curPos[0] - World.RADIUS) + Math.abs(World.curPos[1] - World.RADIUS);
+ },
+
+ getTerrain: function() {
+ return World.state.map[World.curPos[0]][World.curPos[1]];
+ },
+
+ 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 *= Engine.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;
+ }
+ }
+ }
+ },
+
+ applyMap: function() {
+ var x = Math.floor(Math.random() * (World.RADIUS * 2) + 1);
+ var y = Math.floor(Math.random() * (World.RADIUS * 2) + 1);
+ World.uncoverMap(x, y, 5, State.world.mask);
+ },
+
+ 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(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 i = 0; i < landmark.num; i++) {
+ var pos = World.placeLandmark(landmark.minRadius, landmark.maxRadius, k, map);
+ if(k == World.TILE.SHIP) {
+ var dx = pos[0] - World.RADIUS, dy = pos[1] - World.RADIUS;
+ var horz = dx < 0 ? 'west' : 'east';
+ var vert = dy < 0 ? 'north' : 'south';
+ if(Math.abs(dx) / 2 > Math.abs(dy)) {
+ World.dir = horz;
+ } else if(Math.abs(dy) / 2 > Math.abs(dx)){
+ World.dir = vert;
+ } else {
+ World.dir = vert + horz;
+ }
+ }
+ }
+ }
+
+ return map;
+ },
+
+ 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 log = x == World.RADIUS + 1 && y == World.RADIUS + 1;
+
+ 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;
+ 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') {
+ var 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)) {
+ var cur = chances[tile];
+ cur = typeof cur == 'number' ? cur : 0;
+ cur += World.TILE_PROBS[tile] * nonSticky;
+ chances[tile] = cur;
+ }
+ }
+
+ var list = [];
+ for(var t in chances) {
+ list.push(chances[t] + '' + t);
+ }
+ 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 i in list) {
+ var prob = list[i];
+ 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');
+ }
+ 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 = {};
+ $('#outerSlider').animate({opacity: '0'}, 600, 'linear', function() {
+ $('#outerSlider').css('left', '0px');
+ $('#locationSlider').css('left', '0px');
+ Engine.activeModule = Room;
+ $('div.headerButton').removeClass('selected');
+ Room.tab.addClass('selected');
+ setTimeout(function(){
+ Room.onArrival();
+ $('#outerSlider').animate({opacity:'1'}, 600, 'linear');
+ Button.cooldown($('#embarkButton'));
+ Engine.keyLock = false;
+ }, 2000);
+ });
+ }
+ },
+
+ goHome: function() {
+ // Home safe! Commit the changes.
+ State.world = World.state;
+ if(World.state.sulphurmine && Outside.numBuilding('sulphur mine') == 0) {
+ Outside.addBuilding('sulphur mine', 1);
+ Engine.event('progress', 'sulphur mine');
+ }
+ if(World.state.ironmine && Outside.numBuilding('iron mine') == 0) {
+ Outside.addBuilding('iron mine', 1);
+ Engine.event('progress', 'iron mine');
+ }
+ if(World.state.coalmine && Outside.numBuilding('coal mine') == 0) {
+ Outside.addBuilding('coal mine', 1);
+ Engine.event('progress', 'coal mine');
+ }
+ if(World.state.ship && !State.ship) {
+ Ship.init();
+ Engine.event('progress', 'ship');
+ }
+ World.state = null;
+
+ // Clear the embark cooldown
+ var btn = Button.clearCooldown($('#embarkButton'));
+ if(Path.outfit['cured meat'] > 0) {
+ Button.setDisabled(btn, false);
+ }
+
+ for(var k in Path.outfit) {
+ Engine.addStore(k, Path.outfit[k]);
+ if(World.leaveItAtHome(k)) {
+ Path.outfit[k] = 0;
+ }
+ }
+
+ $('#outerSlider').animate({left: '0px'}, 300);
+ Engine.activeModule = Path;
+ Path.onArrival();
+ },
+
+ leaveItAtHome: function(thing) {
+ return thing != 'cured meat' && thing != 'bullets' && thing != 'energy cell' && thing != 'charm'
+ && typeof World.Weapons[thing] == 'undefined' && typeof Room.Craftables[thing] == 'undefined';
+ },
+
+ getMaxHealth: function() {
+ if(Engine.getStore('s armour') > 0) {
+ return World.BASE_HEALTH + 35;
+ } else if(Engine.getStore('i armour') > 0) {
+ return World.BASE_HEALTH + 15;
+ } else if(Engine.getStore('l armour') > 0) {
+ return World.BASE_HEALTH + 5;
+ }
+ return World.BASE_HEALTH;
+ },
+
+ getHitChance: function() {
+ if(Engine.hasPerk('precise')) {
+ return World.BASE_HIT_CHANCE + 0.1;
+ }
+ return World.BASE_HIT_CHANCE;
+ },
+
+ getMaxWater: function() {
+ if(Engine.getStore('water tank') > 0) {
+ return World.BASE_WATER + 50;
+ } else if(Engine.getStore('cask') > 0) {
+ return World.BASE_WATER + 20;
+ } else if(Engine.getStore('waterskin') > 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());
+ // Save progress at outposts
+ State.world = World.state;
+ // Mark this outpost as used
+ World.usedOutposts[World.curPos[0] + ',' + World.curPos[1]] = true;
+ },
+
+ onArrival: function() {
+ Engine.keyLock = false;
+ // Explore in a temporary world-state. We'll commit the changes if you return home safe.
+ World.state = $.extend(true, {}, State.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.RADIUS, World.RADIUS];
+ World.drawMap();
+ World.setTitle();
+ World.dead = false;
+ $('div#bagspace-world > div').empty();
+ World.updateSupplies();
+ $('#bagspace-world').width($('#map').width());
+ },
+
+ setTitle: function() {
+ document.title = 'A Barren World';
+ }
+};
\ No newline at end of file