').attr('id', 'buttons').appendTo(Events.eventPanel());
+ Events.loadScene('start');
+ $('div#wrapper').append(Events.eventPanel());
+ Events.eventPanel().animate({opacity: 1}, Events._PANEL_FADE, 'linear');
+ var currentSceneInformation = Events.activeEvent().scenes[Events.activeScene];
+ if (currentSceneInformation.blink) {
+ Events.blinkTitle();
}
},
diff --git a/script/events/executioner.js b/script/events/executioner.js
new file mode 100644
index 00000000..92da8a3b
--- /dev/null
+++ b/script/events/executioner.js
@@ -0,0 +1,2344 @@
+Enemies = window.Enemies ?? {};
+Enemies.Executioner = {
+ 'guard': {
+ combat: true,
+ notification: _('tripped a motion sensor.'),
+ enemy: 'mechanical guard',
+ enemyName: _('mechanical guard'),
+ ranged: true,
+ chara: 'G',
+ damage: 10,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 60,
+ loot: {
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'laser rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.8
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ },
+ 'quadruped': {
+ combat: true,
+ notification: _('a mobile defence platform trundles around the corner.'),
+ enemy: 'mechanical quadruped',
+ enemyName: _('mechanical quadruped'),
+ ranged: false,
+ chara: 'Q',
+ damage: 8,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 70,
+ loot: {
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 1
+ },
+ 'alien alloy': {
+ min: 2,
+ max: 4,
+ chance: 0.2
+ }
+ }
+ },
+ 'medic': {
+ combat: true,
+ notification: _('a medical drone wheels out of control.'),
+ enemy: 'broken medic',
+ enemyName: _('broken medic'),
+ ranged: false,
+ chara: 'M',
+ damage: 15,
+ hit: 0.8,
+ attackDelay: 3,
+ health: 80,
+ atHealth: {
+ 40: fighter => {
+ Events.setStatus(fighter, 'venomous');
+ return 'venomous';
+ }
+ },
+ loot: {
+ 'alien alloy': {
+ min: 1,
+ max: 2,
+ chance: 1
+ },
+ 'hypo': {
+ min: 1,
+ max: 4,
+ chance: 0.2
+ }
+ }
+ },
+ 'turret': {
+ combat: true,
+ notification: _('one of the defence turrets still works.'),
+ enemy: 'defence turret',
+ enemyName: _('defence turret'),
+ ranged: true,
+ chara: 'T',
+ damage: 25,
+ hit: 0.8,
+ attackDelay: 4,
+ health: 50,
+ loot: {
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.8
+ },
+ 'laser rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ }
+};
+
+Events.Executioner = {
+ "executioner-intro": { /* Exploring a ravaged battleship */
+ title: _('A Ravaged Battleship'),
+ audio: AudioLibrary.LANDMARK_CRASHED_SHIP,
+ scenes: {
+ 'start': {
+ notification: _('the remains of a huge ship are embedded in the earth.'),
+ text: [
+ _('the remains of a massive battleship lie here, like a silent sealed city.'),
+ _('it lists to the side in a deep crevasse, cut when it fell from the sky.'),
+ _('the hatches are all sealed, but the hull is blown out just above the dirt, providing an entrance.')
+ ],
+ buttons: {
+ 'enter': {
+ text: _('enter'),
+ cost: { torch: 1 },
+ nextScene: {1: '1'}
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '1': {
+ text: [
+ _('the interior of the ship is cold and dark. what little light there is only accentuates its harsh angles.'),
+ _('the walls hum faintly.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.4: '2-1', 0.8: '2-2', 1: '2-3' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '2-1': {
+ 'text': [
+ _('thick, sticky webbing covers the walls of the corridor.'),
+ _('deeper into the ship, the darkness seems almost to writhe.'),
+ _('a small knapsack hangs from a cluster of webs, a few feet from the floor.')
+ ],
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '3-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-1': {
+ notification: _('a huge arthropod lunges from the shadows, its mandibles thrashing.'),
+ combat: true,
+ enemy: 'chitinous horror',
+ chara: 'H',
+ damage: 1,
+ hit: 0.7,
+ attackDelay: 0.25,
+ health: 60,
+ loot: {
+ 'meat': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ },
+ 'scales': {
+ min: 5,
+ max: 10,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '4-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '4-1': {
+ notification: _('the webs part, and a grotesque insect lurches forward.'),
+ combat: true,
+ enemy: 'chitinous queen',
+ chara: 'Q',
+ damage: 1,
+ hit: 0.7,
+ attackDelay: 0.25,
+ health: 70,
+ loot: {
+ 'meat': {
+ min: 8,
+ max: 12,
+ chance: 0.8
+ },
+ 'scales': {
+ min: 8,
+ max: 12,
+ chance: 0.5
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '5' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '2-2': {
+ notification: _('an operative waits in ambush around the corner.'),
+ combat: true,
+ enemy: 'operative',
+ chara: 'O',
+ damage: 8,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 60,
+ loot: {
+ 'bayonet': {
+ 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'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '3-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-2': {
+ 'text': [
+ _('the military has set up a small camp just inside the ship.'),
+ _('crude attempts have been made to cut into the walls.'),
+ _('scraps of copper wire litter the floor.'),
+ _('two bedrolls are wedged into a corner.')
+ ],
+ loot: {
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'torch': {
+ min: 1,
+ max: 3,
+ chance: 0.8
+ },
+ 'bullets': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 2,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '4-2': {
+ notification: _('a dusty researcher clumsily hides in the shadows.'),
+ combat: true,
+ enemy: 'researcher',
+ chara: 'R',
+ damage: 1,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 20,
+ loot: {
+ 'torch': {
+ min: 1,
+ max: 3,
+ chance: 0.8
+ },
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'cured meat': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '5' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '2-3': {
+ 'text': [
+ _('debris is stacked in the corridor, forming a low barricade.'),
+ _('the walls are scorched and melted.'),
+ _('behind the barricade, a few weapons lay abandoned.')
+ ],
+ loot: {
+ 'laser rifle': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'plasma rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '3-3' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-3': {
+ 'text': [
+ _('the partially devoured remains of several wanderers are piled before a dark corridor.'),
+ _('shuffling noises can be heard from within.')
+ ],
+ loot: {
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.5
+ },
+ 'cloth': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4-3' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '4-3': {
+ combat: true,
+ enemy: 'ancient beast',
+ enemyName: _('ancient beast'),
+ chara: 'A',
+ damage: 6,
+ hit: 0.8,
+ attackDelay: 1,
+ health: 60,
+ loot: {
+ 'fur': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'teeth': {
+ min: 5,
+ max: 10,
+ chance: 0.8
+ }
+ },
+ notification: _('an ancient beast has made these ruins its home.'),
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '5' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '5': {
+ 'text': [
+ _('a maintenance panel is embedded in the wall next to a large sealed door.'),
+ _('perhaps the ship’s systems are still operational.')
+ ],
+ buttons: {
+ 'power': {
+ text: _('power cycle'),
+ nextScene: { 1: '6' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '6': {
+ combat: true,
+ notification: _('as the lights come online, so too do the defence systems.'),
+ enemy: 'automated turret',
+ enemyName: _('automated turret'),
+ ranged: true,
+ chara: 'T',
+ damage: 10,
+ hit: 0.8,
+ attackDelay: 2.5,
+ health: 60,
+ loot: {
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'laser rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '7' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '7': {
+ 'text': [
+ _('beyond the bulkhead is a small antechamber, seemingly untouched by scavengers.'),
+ _('a large hatch grinds open, and the wind rushes in.'),
+ _('a strange device sits on the floor. looks important.')
+ ],
+ onLoad: () => {
+ World.drawRoad();
+ World.state.executioner = true;
+ },
+ buttons: {
+ 'leave': {
+ text: _('take device and leave'),
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ "executioner-antechamber": { /* Deeper into a ravaged battleship */
+ title: _('A Ravaged Battleship'),
+ audio: AudioLibrary.LANDMARK_CRASHED_SHIP,
+ scenes: {
+ 'start': {
+ 'text': [
+ _('a large hatch opens into a wide corridor.'),
+ _('the corridor leads to a bank of elevators, which appear to be functional.')
+ ],
+ buttons: {
+ 'engineering': {
+ text: _('engineering'),
+ available: function() {
+ return !World.state.engineering;
+ },
+ nextEvent: 'executioner-engineering'
+ },
+ 'medical': {
+ text: _('medical'),
+ available: function() {
+ return !World.state.medical;
+ },
+ nextEvent: 'executioner-medical'
+ },
+ 'martial': {
+ text: _('martial'),
+ available: function() {
+ return !World.state.martial;
+ },
+ nextEvent: 'executioner-martial'
+ },
+ 'command': {
+ text: _('command deck'),
+ available: function() {
+ return World.state.engineering && World.state.medical && World.state.martial;
+ },
+ nextEvent: 'executioner-command'
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ "executioner-engineering": { /* Engineering wing */
+ title: _('Engineering Wing'),
+ audio: AudioLibrary.LANDMARK_CRASHED_SHIP,
+ scenes: {
+ 'start': {
+ 'text': [
+ _('elevator doors open to a blasted corridor. debris covers the floor, piled into makeshift defences.'),
+ _('emergency lighting flickers.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.3: '1-1', 0.7: '1-2', 1: '1-3' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '1-1': {
+ text: [
+ _('an automated assembly line performs its empty routines, long since deprived of materials.'),
+ _('its final works lie forgotten, covered by a thin layer of dust.')
+ ],
+ loot: {
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'laser rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '2-1a', 1: '2-1b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '2-1a': {
+ combat: true,
+ notification: _('assembly arms spin wildly out of control.'),
+ enemy: 'unruly welder',
+ enemyName: _('unruly welder'),
+ ranged: false,
+ chara: 'W',
+ damage: 13,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 50,
+ loot: {
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'alien alloy': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '3-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '2-1b': {
+ text: [
+ _('assembly arms spark and jitter.'),
+ _('a cacophony of decrepit machinery fills the room.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '3-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-1': {
+ ...Enemies.Executioner.guard,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '1-2': {
+ ...Enemies.Executioner.turret,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '2-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '2-2': {
+ text: [
+ _('must have been the engine room, once. the massive machines now stand inert, twisted and scorched by explosions.'),
+ _('the destruction is uniform and precise.'),
+ _('bits of them can be scavenged.')
+ ],
+ loot: {
+ 'alien alloy': {
+ min: 2,
+ max: 5,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '3-2a', 1: '3-2b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-2a': {
+ ...Enemies.Executioner.guard,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-2b': {
+ text: [
+ _('none of the ship\'s engines escaped the destruction.'),
+ _('it\'s no mystery why she no longer flies.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '1-3': {
+ text: [
+ _('sparks cascade from a reactivated power junction, and catch.'),
+ _('the flames fill the corridor.')
+ ],
+ buttons: {
+ 'water': {
+ text: _('extinguish'),
+ cost: { 'water': 5 },
+ nextScene: { 0.5: '2-3a', 1: '2-3b' }
+ },
+ 'run': {
+ text: _('rush through'),
+ cost: { 'hp': 10 },
+ nextScene: { 0.5: '2-3a', 1: '2-3b' }
+ }
+ }
+ },
+ '2-3a': {
+ ...Enemies.Executioner.guard,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '3-3' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '2-3b': {
+ text: [
+ _('rows of inert security robots hang suspended from the ceiling.'),
+ _('wires run overhead, corroded and useless.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '3-3' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-3': {
+ text: [
+ _('more signs of past combat down the hall. guard post is ransacked.'),
+ _('still, some things can be found.')
+ ],
+ loot: {
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'laser rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.7
+ },
+ 'grenade': {
+ min: 1,
+ max: 3,
+ chance: 0.6
+ },
+ 'plasma rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '4': {
+ text: [
+ _('marks on the door read \'research and development.\' everything seems mostly untouched, but dead.'),
+ _('one machine thrums with power, and might still work.')
+ ],
+ buttons: {
+ 'use': {
+ text: _('use machine'),
+ cost: { 'alien alloy': 1 },
+ onChoose: function() {
+ World.setHp(World.getMaxHealth());
+ },
+ nextScene: { 1: '4-heal' }
+ },
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '5-1', 1: '5-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '4-heal': {
+ text: [
+ _('step inside, and the machine whirs. muscle and bone reknit. good as new.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '5-1', 1: '5-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '5-1': {
+ ...Enemies.Executioner.turret,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '6' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '5-2': {
+ text: [
+ _('the machines here look unfinished, abandoned by their creator. wires and other scrap are scattered about the work benches.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '6' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '6': {
+ text: [
+ _('experimental plans cover one wall, held by an unseen force.'),
+ _('this one looks useful.')
+ ],
+ loot: {
+ 'hypo blueprint': {
+ min: 1,
+ max: 1,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '7-intro' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '7-intro': {
+ text: [
+ _('clattering metal and old servos. something is coming...')
+ ],
+ buttons: {
+ 'fight': {
+ text: _('fight'),
+ nextScene: { 1: '7' }
+ }
+ }
+ },
+ '7': {
+ combat: true,
+ notification: _('an unfinished automaton whirs to life.'),
+ enemy: 'unstable prototype',
+ enemyName: _('unstable prototype'),
+ ranged: false,
+ chara: 'P',
+ damage: 5,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 150,
+ specials:[{
+ delay: 5,
+ action: (fighter) => {
+ Events.setStatus(fighter, 'shield');
+ return 'shield';
+ }
+ }],
+ loot: {
+ 'alien alloy': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'kinetic armour blueprint': {
+ min: 1,
+ max: 1,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '8' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '8': {
+ text: [
+ _('at the back of the workshop, elevator doors twitch and buzz.'),
+ _('looks like a way out of here.')
+ ],
+ onLoad: () => {
+ World.state.engineering = true;
+ },
+ buttons: {
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ "executioner-martial": { /* Martial wing */
+ title: _('Martial Wing'),
+ audio: AudioLibrary.LANDMARK_CRASHED_SHIP,
+ scenes: {
+ 'start': {
+ text: [
+ _('metal grinds, and the elevator doors open halfway. beyond is a brightly lit battlefield. remains litter the corridor, undisturbed by scavengers.'),
+ _('looks like they tried to barricade the elevators.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '1': {
+ text: [
+ _('further along, the corridor branches.'),
+ _('the door to the left is sealed and refuses to open.')
+ ],
+ buttons: {
+ 'explode': {
+ text: _('blow it down'),
+ cost: { grenade: 1 },
+ nextScene: { 1: '2-1' }
+ },
+ 'right': {
+ text: _('continue right'),
+ nextScene: { 0.5: '2-2', 1: '2-3' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '2-1': {
+ text: [
+ _('the blast throws the door inwards.'),
+ _('through the bulkhead is a large room, walls lined with weapon racks. fighting seems to have passed it by.')
+ ],
+ loot: {
+ 'energy blade': {
+ min: 2,
+ max: 5,
+ chance: 1
+ },
+ 'laser rifle': {
+ min: 2,
+ max: 5,
+ chance: 1
+ },
+ 'energy cell': {
+ min: 5,
+ max: 20,
+ chance: 1
+ },
+ 'grenade': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ },
+ 'plasma rifle': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '3-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-1': {
+ ...Enemies.Executioner.turret,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '4-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '4-1': {
+ text: [
+ _('another door at the end of the hall, sealed from this side.'),
+ _('should be able to open it.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '5' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '2-2': {
+ ...Enemies.Executioner.turret,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 0.5: '3-2a', 1: '3-2b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-2a': {
+ ...Enemies.Executioner.quadruped,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '4-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-2b': {
+ text: [
+ _('the corridor is eerily silent.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '4-2': {
+ text: [
+ _('crew cabins flank the hall, devoid of life.'),
+ _('a few useful items can be scavenged.')
+ ],
+ loot: {
+ 'energy cell': {
+ min: 1,
+ max: 5,
+ chance: 1
+ },
+ 'energy blade': {
+ min: 1,
+ max: 1,
+ chance: 0.2
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '5' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '2-3': {
+ text: [
+ _('ruined defence turrets flank the corridor.'),
+ _('could put the scrap to good use.')
+ ],
+ loot: {
+ 'alien alloy': {
+ min: 1,
+ max: 3,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '3-3a', 1: '3-3b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-3a': {
+ ...Enemies.Executioner.guard,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '3-3b': {
+ text: [
+ _('small sensors in the walls still look to be operational.'),
+ _('easily avoided.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4-3' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '4-3': {
+ ...Enemies.Executioner.quadruped,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '5' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '5': {
+ text: [
+ _('large barricades bisect the corridor, scorched by weapons fire.'),
+ _('bodies litter the ground on either side.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '6' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '6': {
+ text: [
+ _('documents are scattered down the hall, most charred and curled.'),
+ _('this one looks interesting.')
+ ],
+ loot: {
+ 'plasma rifle blueprint': {
+ min: 1,
+ max: 1,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '7-1', 1: '7-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '7-1': {
+ text: [
+ _('the next door leads to a ransacked planning room.'),
+ _('maps of the surface can still be found amongst the debris.')
+ ],
+ buttons: {
+ 'scavenge': {
+ text: _('scavenge maps'),
+ onChoose: () => {
+ for (let i = 0; i < 3; i++) {
+ World.applyMap();
+ }
+ },
+ nextScene: { 1: '8-1a' }
+ },
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '8-1b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '8-1a': {
+ ...Enemies.Executioner.guard,
+ notification: _('drew some attention with all that noise.'),
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '9-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '8-1b': {
+ text: [
+ _('slipped past an automated sentry.'),
+ _('if only they\'d been destroyed along with everything else.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '9-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '9-1': {
+ ...Enemies.Executioner.guard,
+ notification: _('ran straight into another one.'),
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '10' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '7-2': {
+ text: [
+ _('the corridor passes through a security checkpoint. the defences are blown apart, ragged edges scorched by laser fire.'),
+ _('past the checkpoint, banks of containment cells can be seen.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '8-2a', 1: '8-2b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '8-2a': {
+ text: [
+ _('the cells are all empty.'),
+ _('power cables running across the ceiling are split in several places, sparking occasionally.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '9-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '8-2b': {
+ text: [
+ _('the guards died at their posts, shot through with superheated plasma.'),
+ _('their weapons lie on the floor beside them.')
+ ],
+ loot: {
+ 'laser rifle': {
+ min: 2,
+ max: 2,
+ chance: 1
+ },
+ 'energy cell': {
+ min: 5,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '9-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '9-2': {
+ ...Enemies.Executioner.quadruped,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '10' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '10': {
+ 'text': [
+ _('the corridor opens onto a vast training complex, obstacles and features blackened by real combat.'),
+ _('a regenerative machine hums uncannily by one of the courses.')
+ ],
+ buttons: {
+ 'use': {
+ text: _('use machine'),
+ cost: { 'alien alloy': 1 },
+ onChoose: function() {
+ World.setHp(World.getMaxHealth());
+ },
+ nextScene: { 1: '11' }
+ },
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '11' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '11': {
+ 'text': [
+ _('motion from the centre of the yard.'),
+ _('a sparring automaton, still fully function and crusted with timeworn blood, lunges forward.')
+ ],
+ buttons: {
+ 'engage': {
+ text: _('engage'),
+ nextScene: { 1: '12' }
+ }
+ }
+ },
+ '12': {
+ combat: true,
+ notification: _('the machine attacks, blades whirling.'),
+ enemy: 'murderous robot',
+ enemyName: _('murderous robot'),
+ ranged: false,
+ chara: 'M',
+ damage: 10,
+ hit: 0.8,
+ attackDelay: 3,
+ health: 250,
+ specials:[{
+ delay: 13,
+ action: (fighter) => {
+ Events.setStatus(fighter, 'energised');
+ return 'energised';
+ }
+ }],
+ loot: {
+ 'alien alloy': {
+ min: 1,
+ max: 3,
+ chance: 1
+ },
+ 'disruptor blueprint': {
+ min: 1,
+ max: 1,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '13' }
+ }
+ }
+ },
+ '13': {
+ 'text': [
+ _('the ruins of the sparring machine clatter to the ground.'),
+ _('picked this deck clean.')
+ ],
+ onLoad: () => {
+ World.state.martial = true;
+ },
+ buttons: {
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ "executioner-medical": { /* Medical wing */
+ title: _('Medical Wing'),
+ audio: AudioLibrary.LANDMARK_CRASHED_SHIP,
+ scenes: {
+ 'start': {
+ 'text': [
+ _('elevator doors open to an empty corridor.'),
+ _('a few dusty corpses can be seen further down, but this deck appears to have been spared most of the combat.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '1': {
+ ...Enemies.Executioner.turret,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '2': {
+ 'text': [
+ _('past the checkpoint, the corridor is undamaged save for sporadic graffiti.'),
+ _('there was no fighting here.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '3a', 1: '3b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3a': {
+ ...Enemies.Executioner.quadruped,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '3b': {
+ 'text': [
+ _('automated guardians still stalk the halls, unaware that their masters have long gone.'),
+ _('clumsy machines, and easily avoided.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '4': {
+ 'text': [
+ _('medical gurneys are fixed to grooves running down the corridor walls.'),
+ _('the automated patient transport system now sits motionless.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '5-1', 1: '5-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '5-1': {
+ ...Enemies.Executioner.medic,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 0.5: '6-1a', 1: '6-1b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '6-1a': {
+ ...Enemies.Executioner.medic,
+ notification: _('it had friends.'),
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '7-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '6-1b': {
+ 'text': [
+ _('more medical robots stand frozen, attached by a network of wires.'),
+ _('they take no notice of the intrusion.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '7-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '7-1': {
+ 'text': [
+ _('weapons are strewn about the medical dispatch bay. must have been used as a muster point.'),
+ _('more strange graffiti adorns the walls.')
+ ],
+ loot: {
+ 'laser rifle': {
+ min: 1,
+ max: 1,
+ chance: 1
+ },
+ 'energy cell': {
+ min: 3,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '7-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '5-2': {
+ 'text': [
+ _('this ward has been converted to a makeshift strategy room, maps scrawled hastily on any flat surface.'),
+ _('a secure locker is set into one wall.')
+ ],
+ buttons: {
+ 'force': {
+ text: _('force locker'),
+ nextScene: { 1: '6-2a-intro' }
+ },
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '6-2b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '6-2a-intro': {
+ 'text': [
+ _('hinges rusted through. no challenge.'),
+ ],
+ loot: {
+ 'energy cell': {
+ min: 5,
+ max: 10,
+ chance: 1
+ },
+ 'hypo': {
+ min: 1,
+ max: 3,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '6-2a' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '6-2a': {
+ ...Enemies.Executioner.medic,
+ notification: _('the noise draws attention.'),
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '7-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '6-2b': {
+ 'text': [
+ _('better to move without drawing attention.'),
+ _('noises can be heard from the corridor outside.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '7-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '7-2': {
+ ...Enemies.Executioner.quadruped,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '8' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '8': {
+ combat: true,
+ notification: _('something\'s wrong with this robot.'),
+ enemy: 'unstable automaton',
+ enemyName: _('unstable automaton'),
+ ranged: false,
+ chara: 'A',
+ damage: 10,
+ hit: 0.7,
+ attackDelay: 2,
+ health: 100,
+ explosion: 30,
+ loot: {
+ 'glowstone blueprint': {
+ min: 1,
+ max: 1,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '9' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '9': {
+ 'text': [
+ _('another checkpoint ahead, fitted with heavy doors.'),
+ _('security is even tighter here.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '10a', 1: '10b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '10a': {
+ ...Enemies.Executioner.guard,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '11' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '10b': {
+ 'text': [
+ _('slipped through unnoticed.'),
+ _('air whistles as the doors open. this section must have lower pressure than the rest of the ship.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '11' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '11': {
+ ...Enemies.Executioner.medic,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 0.5: '12-1', 1: '12-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '12-1': {
+ 'text': [
+ _('the air is cooler here. low cabinets ring the room, doors dusted with frost.'),
+ _('samples of something biological inside.')
+ ],
+ loot: {
+ 'cured meat': {
+ min: 5,
+ max: 10,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '13-1a', 1: '13-1b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '13-1a': {
+ ...Enemies.Executioner.guard,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '14-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '13-1b': {
+ 'text': [
+ _('security drones still patrol the hallways.'),
+ _('predictable paths.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '14-1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '14-1': {
+ ...Enemies.Executioner.medic,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '15' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '12-2': {
+ 'text': [
+ _('surgical tools are scattered on the floor, near what appears the be the remains of a fire.'),
+ _('strange.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '13-2a', 1: '13-2b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '13-2a': {
+ ...Enemies.Executioner.medic,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '14-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '13-2b': {
+ 'text': [
+ _('the air in this room has a metallic tinge. floor is covered in dark powder.'),
+ _('some completed explosives in the corner.')
+ ],
+ loot: {
+ 'grenade': {
+ min: 3,
+ max: 8,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '14-2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '14-2': {
+ ...Enemies.Executioner.medic,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '15' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '15': {
+ 'text': [
+ _('containment cells arranged at the back of the room, all open.'),
+ _('something moving up ahead.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '16' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '16': {
+ combat: true,
+ notification: _('a mutated beast leaps from its cell.'),
+ enemy: 'malformed experiment',
+ enemyName: _('malformed experiment'),
+ ranged: false,
+ chara: 'E',
+ damage: 5,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 200,
+ specials: [{
+ delay: 16,
+ action: (fighter) => {
+ Events.setStatus(fighter, 'enraged');
+ return 'enraged';
+ }
+ }],
+ loot: {
+ 'stim blueprint': {
+ min: 1,
+ max: 1,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '17' }
+ }
+ }
+ },
+ '17': {
+ 'text': [
+ _('the creature\'s tortured breathing ceases.'),
+ _('nothing more here.')
+ ],
+ onLoad: () => {
+ World.state.medical = true;
+ },
+ buttons: {
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ },
+
+ "executioner-command": { /* Command deck */
+ title: _('Command Deck'),
+ audio: AudioLibrary.LANDMARK_CRASHED_SHIP,
+ scenes: {
+ 'start': {
+ 'text': [
+ _('the path to the command bridge is wide, walls adorned with decorative shields.'),
+ _('fighting hadn\'t reached here, it seems.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '1' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+
+ '1': {
+ ...Enemies.Executioner.guard,
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '2' }
+ },
+ 'leave': {
+ text: _('leave'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: 'end'
+ }
+ }
+ },
+ '2': {
+ 'text': [
+ _('detour through the officer\'s lounge.'),
+ _('might be something useful here.')
+ ],
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 0.5: '3a', 1: '3b' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3a': {
+ 'text': [
+ _('small weapons cache in a cabinet.'),
+ _('lucky.')
+ ],
+ loot: {
+ 'energy cell': {
+ min: 3,
+ max: 10,
+ chance: 1
+ },
+ 'grenade': {
+ min: 1,
+ max: 5,
+ chance: 0.8
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '3b': {
+ 'text': [
+ _('found some medical supplies in a discarded bag.')
+ ],
+ loot: {
+ 'hypo': {
+ min: 1,
+ max: 3,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ nextScene: { 1: '4' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '4': {
+ 'text': [
+ _('the command deck is empty, save for a squat figure sitting motionless in the centre of the room.'),
+ _('in a flash, the figure is standing.')
+ ],
+ buttons: {
+ 'approach': {
+ text: _('approach'),
+ nextScene: { 1: '5' }
+ },
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ },
+ '5': {
+ 'text': [
+ _('wanderer form, but not quite flesh. not quite metal either. a crystal set into its chest pulses with light.'),
+ _('it says it saw the rebellion coming. said it made arrangements.'),
+ _('says it can\'t die.')
+ ],
+ buttons: {
+ 'observe': {
+ text: _('observe'),
+ nextScene: { 1: '6' }
+ }
+ }
+ },
+ '6': {
+ combat: true,
+ notification: _('the immortal wanderer attacks.'),
+ enemy: 'immortal wanderer',
+ enemyName: _('immortal wanderer'),
+ ranged: false,
+ chara: '@',
+ damage: 12,
+ hit: 0.8,
+ attackDelay: 2,
+ health: 500,
+ onLoad: () => {
+ Events._lastSpecial = 'none';
+ },
+ specials: [{
+ delay: 7,
+ action: (fighter) => {
+ const last = Events._lastSpecial ?? 'none';
+ const possible = [ 'shield', 'enraged', 'meditation' ].filter(p => p !== last);
+ const status = possible[Math.floor(Math.random() * possible.length)];
+ Events.setStatus(fighter, status);
+ Events._lastSpecial = status;
+ return status;
+ }
+ }],
+ loot: {
+ 'fleet beacon': {
+ min: 1,
+ max: 1,
+ chance: 1
+ }
+ },
+ buttons: {
+ 'continue': {
+ text: _('continue'),
+ cooldown: Events._LEAVE_COOLDOWN,
+ nextScene: { 1: '7' }
+ }
+ }
+ },
+ '7': {
+ 'text': [
+ _('the crystal pulses brightly, then goes dark. the assailant shimmers as its shape becomes less defined.'),
+ _('then it is gone.'),
+ _('time to get out of here.')
+ ],
+ onLoad: () => {
+ World.clearDungeon();
+ },
+ buttons: {
+ 'leave': {
+ text: _('leave'),
+ nextScene: 'end'
+ }
+ }
+ }
+ }
+ }
+
+};
\ No newline at end of file
diff --git a/script/fabricator.js b/script/fabricator.js
new file mode 100644
index 00000000..84662f91
--- /dev/null
+++ b/script/fabricator.js
@@ -0,0 +1,244 @@
+/**
+ * Module that registers the fabricator functionality
+ */
+const Fabricator = {
+ _STORES_OFFSET: 0,
+ name: _('Fabricator'),
+ Craftables: {
+ 'energy blade': {
+ name: _('energy blade'),
+ type: 'weapon',
+ buildMsg: _("the blade hums, charged particles sparking and fizzing."),
+ cost: () => ({
+ 'alien alloy': 1
+ })
+ },
+ 'fluid recycler': {
+ name: _('fluid recycler'),
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: _('water out, water in. waste not, want not.'),
+ cost: () => ({
+ 'alien alloy': 2
+ })
+ },
+ 'cargo drone': {
+ name: _('cargo drone'),
+ type: 'upgrade',
+ maximum: 1,
+ buildMsg: _('the workhorse of the wanderer fleet.'),
+ cost: () => ({
+ 'alien alloy': 2
+ })
+ },
+ 'kinetic armour': {
+ name: _('kinetic armour'),
+ type: 'upgrade',
+ maximum: 1,
+ blueprintRequired: true,
+ buildMsg: _('wanderer soldiers succeed by subverting the enemy\'s rage.'),
+ cost: () => ({
+ 'alien alloy': 2
+ })
+ },
+ 'disruptor': {
+ name: _('disruptor'),
+ type: 'weapon',
+ blueprintRequired: true,
+ buildMsg: _("somtimes it is best not to fight."),
+ cost: () => ({
+ 'alien alloy': 1
+ })
+ },
+ 'hypo': {
+ name: _('hypo'),
+ type: 'tool',
+ blueprintRequired: true,
+ buildMsg: _('a handful of hypos. life in a vial.'),
+ cost: () => ({
+ 'alien alloy': 1
+ }),
+ quantity: 5
+ },
+ 'stim': {
+ name: _('stim'),
+ type: 'tool',
+ blueprintRequired: true,
+ buildMsg: _('sometimes it is best to fight without restraint.'),
+ cost: () => ({
+ 'alien alloy': 1
+ })
+ },
+ 'plasma rifle': {
+ name: _('plasma rifle'),
+ type: 'weapon',
+ blueprintRequired: true,
+ buildMsg: _("the peak of wanderer weapons technology, sleek and deadly."),
+ cost: () => ({
+ 'alien alloy': 1
+ })
+ },
+ 'glowstone': {
+ name: _('glow stone'),
+ type: 'tool',
+ blueprintRequired: true,
+ buildMsg: _('a smooth, perfect sphere. its light is inextinguishable.'),
+ cost: () => ({
+ 'alien alloy': 1
+ })
+ }
+ },
+
+ init: () => {
+
+ if (!$SM.get('features.location.fabricator')) {
+ $SM.set('features.location.fabricator', true);
+ }
+
+ // Create the Fabricator tab
+ Fabricator.tab = Header.addLocation(_("A Whirring Fabricator"), "fabricator", Fabricator, 'ship');
+
+ // Create the Fabricator panel
+ Fabricator.panel = $('
').attr('id', "fabricatorPanel")
+ .addClass('location');
+ if (Ship.panel) {
+ Fabricator.panel.insertBefore(Ship.panel);
+ }
+ else {
+ Fabricator.panel.appendTo('div#locationSlider');
+ }
+
+ $.Dispatch('stateUpdate').subscribe(() => {
+ Fabricator.updateBuildButtons();
+ Fabricator.updateBlueprints();
+ });
+
+ Engine.updateSlider();
+ Fabricator.updateBuildButtons();
+
+ },
+
+ onArrival: transition_diff => {
+ Fabricator.setTitle();
+ Fabricator.updateBlueprints(true);
+
+ if(!$SM.get('game.fabricator.seen')) {
+ Notifications.notify(Fabricator, _('the familiar hum of wanderer machinery coming to life. finally, real tools.'));
+ $SM.set('game.fabricator.seen', true);
+ }
+ AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_SHIP);
+
+ Engine.moveStoresView(null, transition_diff);
+ },
+
+ setTitle: () => {
+ if(Engine.activeModule == Fabricator) {
+ document.title = _("A Whirring Fabricator");
+ }
+ },
+
+ updateBuildButtons: () => {
+ let section = $('#fabricateButtons');
+ let needsAppend = false;
+ if (section.length === 0) {
+ section = $('
').attr({ 'id': 'fabricateButtons', 'data-legend': _('fabricate:') }).css('opacity', 0);
+ needsAppend = true;
+ }
+
+ for (const [ key, value ] of Object.entries(Fabricator.Craftables)) {
+ const max = $SM.num(key, value) + 1 > value.maximum;
+ if (!value.button) {
+ if (Fabricator.canFabricate(key)) {
+ const name = _(value.name) + ((value.quantity ?? 1) > 1 ? ` (x${value.quantity})` : '');
+ value.button = new Button.Button({
+ id: 'fabricate_' + key,
+ cost: value.cost(),
+ text: name,
+ click: Fabricator.fabricate,
+ width: '150px',
+ ttPos: section.children().length > 10 ? 'top right' : 'bottom right'
+ }).css('opacity', 0).attr('fabricateThing', key).appendTo(section).animate({ opacity: 1 }, 300, 'linear');
+ }
+ } else {
+ // refresh the tooltip
+ const costTooltip = $('.tooltip', value.button);
+ costTooltip.empty();
+ const cost = value.cost();
+ for (const [ resource, num ] of Object.entries(cost)) {
+ $("
").addClass('row_key').text(_(resource)).appendTo(costTooltip);
+ $("
").addClass('row_val').text(num).appendTo(costTooltip);
+ }
+ if (max && value.maxMsg && !value.button.hasClass('disabled')) {
+ Notifications.notify(Fabricator, value.maxMsg);
+ }
+ }
+ if (max) {
+ Button.setDisabled(value.button, true);
+ } else {
+ Button.setDisabled(value.button, false);
+ }
+ }
+
+ if (needsAppend && section.children().length > 0) {
+ section.appendTo(Fabricator.panel).animate({ opacity: 1 }, 300, 'linear');
+ }
+ },
+
+ updateBlueprints: ignoreStores => {
+ if(!$SM.get('character.blueprints')) {
+ return;
+ }
+
+ let blueprints = $('#blueprints');
+ let needsAppend = false;
+ if(blueprints.length === 0) {
+ needsAppend = true;
+ blueprints = $('
').attr({'id': 'blueprints', 'data-legend': _('blueprints')});
+ }
+
+ for (const k in $SM.get('character.blueprints')) {
+ const id = 'blueprint_' + k.replace(/ /g, '-');
+ let r = $('#' + id);
+ if($SM.get(`character.blueprints["${k}"]`) && r.length === 0) {
+ r = $('
').attr('id', id).addClass('blueprintRow').appendTo(blueprints);
+ $('
').addClass('row_key').text(_(k)).appendTo(r);
+ }
+ }
+
+ if(needsAppend && blueprints.children().length > 0) {
+ blueprints.prependTo(Fabricator.panel);
+ }
+ },
+
+ canFabricate: itemKey =>
+ !Fabricator.Craftables[itemKey].blueprintRequired ||
+ $SM.get(`character.blueprints['${itemKey}']`),
+
+ fabricate: button => {
+ const thing = $(button).attr('fabricateThing');
+ const craftable = Fabricator.Craftables[thing];
+ const numThings = Math.min(0, $SM.get(`stores['${thing}']`, true));
+
+ if (craftable.maximum <= numThings) {
+ return;
+ }
+
+ const storeMod = {};
+ const cost = craftable.cost();
+ for (const [ key, value ] of Object.entries(cost)) {
+ const have = $SM.get(`stores['${key}']`, true);
+ if (have < value) {
+ Notifications.notify(Fabricator, _(`not enough ${key}`));
+ return false;
+ } else {
+ storeMod[key] = have - value;
+ }
+ }
+ $SM.setM('stores', storeMod);
+ $SM.add(`stores['${thing}']`, craftable.quantity ?? 1);
+
+ Notifications.notify(Fabricator, craftable.buildMsg);
+ AudioEngine.playSound(AudioLibrary.CRAFT);
+ }
+
+};
diff --git a/script/header.js b/script/header.js
index b1f42c1c..f87580e5 100644
--- a/script/header.js
+++ b/script/header.js
@@ -16,13 +16,19 @@ var Header = {
return $('div#header div.headerButton').length > 1;
},
- addLocation: function(text, id, module) {
- return $('
').attr('id', "location_" + id)
+ addLocation: function(text, id, module, before) {
+ const toAdd = $('
').attr('id', "location_" + id)
.addClass('headerButton')
.text(text).click(function() {
if(Header.canTravel()) {
Engine.travelTo(module);
}
- }).appendTo($('div#header'));
+ });
+
+ if (before && $(`#location_${before}`).length > 0) {
+ return toAdd.insertBefore(`#location_${before}`);
+ }
+
+ return toAdd.appendTo($('div#header'));
}
-};
\ No newline at end of file
+};
diff --git a/script/outside.js b/script/outside.js
index ef3f1a89..4ecbd5eb 100644
--- a/script/outside.js
+++ b/script/outside.js
@@ -661,37 +661,5 @@ var Outside = {
Outside.updateWorkersView();
Outside.updateVillageIncome();
}
- },
-
- scrollSidebar: function(direction, reset) {
-
- if( typeof reset != "undefined" ){
- $('#village').css('top', '0px');
- $('#storesContainer').css('top', '224px');
- Outside._STORES_OFFSET = 0;
- return false;
- }
-
- var momentum = 10;
-
- // If they hit up, we scroll everything down
- if( direction == 'up' )
- momentum = momentum * -1;
-
- /* Let's stop scrolling if the top or bottom bound is in the viewport, based on direction */
- if( direction == 'down' && inView( direction, $('#village') ) ){
-
- return false;
-
- }else if( direction == 'up' && inView( direction, $('#storesContainer') ) ){
-
- return false;
-
- }
-
- scrollByX( $('#village'), momentum );
- scrollByX( $('#storesContainer'), momentum );
- Outside._STORES_OFFSET += momentum;
-
}
};
diff --git a/script/path.js b/script/path.js
index 8ee517da..a102a763 100644
--- a/script/path.js
+++ b/script/path.js
@@ -10,7 +10,8 @@ var Path = {
'bullets': 0.1,
'energy cell': 0.2,
'laser rifle': 5,
- 'bolas': 0.5
+ 'plasma rifle': 5,
+ 'bolas': 0.5,
},
name: 'Path',
@@ -67,7 +68,9 @@ var Path = {
},
getCapacity: function() {
- if($SM.get('stores.convoy', true) > 0) {
+ if($SM.get('stores["cargo drone"]', true) > 0) {
+ return Path.DEFAULT_BAG_SPACE + 100;
+ } else if($SM.get('stores.convoy', true) > 0) {
return Path.DEFAULT_BAG_SPACE + 60;
} else if($SM.get('stores.wagon', true) > 0) {
return Path.DEFAULT_BAG_SPACE + 30;
@@ -98,7 +101,7 @@ var Path = {
var needsAppend = false;
if(perks.length === 0) {
needsAppend = true;
- perks = $('
').attr({'id': 'perks', 'data-legend': _('perks:')});
+ perks = $('
').attr({'id': 'perks', 'data-legend': _('perks')});
}
for(var k in $SM.get('character.perks')) {
var id = 'perk_' + k.replace(' ', '-');
@@ -129,7 +132,9 @@ var Path = {
// Add the armour row
var armour = _("none");
- if($SM.get('stores["s armour"]', true) > 0)
+ if($SM.get('stores["kinetic armour"]', true) > 0)
+ armour = _("kinetic");
+ else if($SM.get('stores["s armour"]', true) > 0)
armour = _("steel");
else if($SM.get('stores["i armour"]', true) > 0)
armour = _("iron");
@@ -169,7 +174,7 @@ var Path = {
'bayonet': {type: 'weapon' },
'charm': {type: 'tool'},
'medicine': {type: 'tool', desc: _('restores') + ' ' + World.MEDS_HEAL + ' ' + _('hp') }
- }, Room.Craftables);
+ }, Room.Craftables, Fabricator.Craftables);
for(var k in carryable) {
var lk = _(k);
@@ -329,35 +334,5 @@ var Path = {
} else if(e.category == 'income' && Engine.activeModule == Path){
Path.updateOutfitting();
}
- },
-
- scrollSidebar: function(direction, reset){
-
- if( typeof reset != "undefined" ){
- $('#perks').css('top', '0px');
- $('#storesContainer').css('top', '206px');
- Path._STORES_OFFSET = 0;
- return;
- }
-
- var momentum = 10;
-
- if( direction == 'up' )
- momentum = momentum * -1;
-
- if( direction == 'down' && inView( direction, $('#perks') ) ){
-
- return false;
-
- }else if( direction == 'up' && inView( direction, $('#storesContainer') ) ){
-
- return false;
-
- }
-
- scrollByX( $('#perks'), momentum );
- scrollByX( $('#storesContainer'), momentum );
- Path._STORES_OFFSET += momentum;
-
}
};
diff --git a/script/room.js b/script/room.js
index 5567ce73..49802b4a 100644
--- a/script/room.js
+++ b/script/room.js
@@ -826,15 +826,19 @@ var Room = {
}
for (var k in $SM.get('stores')) {
- var type = null;
- if (Room.Craftables[k]) {
- type = Room.Craftables[k].type;
- } else if (Room.TradeGoods[k]) {
- type = Room.TradeGoods[k].type;
- } else if (Room.MiscItems[k]) {
- type = Room.MiscItems[k].type;
+ if (k.indexOf('blueprint') > 0) {
+ // don't show blueprints
+ continue;
}
+ const good =
+ Room.Craftables[k] ||
+ Room.TradeGoods[k] ||
+ Room.TradeGoods[k] ||
+ Room.MiscItems[k] ||
+ Fabricator.Craftables[k];
+ const type = good ? good.type : null;
+
var location;
switch (type) {
case 'upgrade':
@@ -854,7 +858,7 @@ var Room = {
break;
}
- var id = "row_" + k.replace(' ', '-');
+ var id = "row_" + k.replace(/ /g, '-');
var row = $('div#' + id, location);
var num = $SM.get('stores["' + k + '"]');
@@ -1139,7 +1143,7 @@ var Room = {
if (Room.craftUnlocked(k)) {
var loc = Room.needsWorkshop(craftable.type) ? craftSection : buildSection;
craftable.button = new Button.Button({
- id: 'build_' + k,
+ id: 'build_' + k.replace(/ /g, '-'),
cost: craftable.cost(),
text: _(k),
click: Room.build,
diff --git a/script/scoring.js b/script/scoring.js
index 1e50ba91..cc67f280 100644
--- a/script/scoring.js
+++ b/script/scoring.js
@@ -19,6 +19,7 @@ var Score = {
}
fullScore = fullScore + $SM.get('stores["alien alloy"]', true) * 10;
+ fullScore = fullScore + $SM.get('stores["fleet beacon"]', true) * 500;
fullScore = fullScore + Ship.getMaxHull() * 50;
return Math.floor(fullScore);
},
diff --git a/script/space.js b/script/space.js
index eb109c01..4b07c6d7 100644
--- a/script/space.js
+++ b/script/space.js
@@ -437,63 +437,13 @@ var Space = {
Engine.GAME_OVER = true;
Score.save();
Prestige.save();
-
- $('
')
- .addClass('centerCont')
- .appendTo('body');
- $('')
- .addClass('endGame')
- .text(_('score for this game: {0}', Score.calculateScore()))
- .appendTo('.centerCont')
- .animate({opacity:1},1500);
- $('
')
- .appendTo('.centerCont');
- $('')
- .addClass('endGame')
- .text(_('total score: {0}', Prestige.get().score))
- .appendTo('.centerCont')
- .animate({opacity:1},1500);
- $('
')
- .appendTo('.centerCont');
- $('
')
- .appendTo('.centerCont');
$('#starsContainer').remove();
$('#content, #notifications').remove();
- $('')
- .addClass('endGame endGameOption')
- .text(_('restart.'))
- .click(Engine.confirmDelete)
- .appendTo('.centerCont')
- .animate({opacity:1},1500);
- $('
')
- .appendTo('.centerCont');
- $('
')
- .appendTo('.centerCont');
- $('')
- .addClass('endGame')
- .text(_('expanded story. alternate ending. behind the scenes commentary. get the app.'))
- .appendTo('.centerCont')
- .animate({opacity:1}, 1500);
- $('
')
- .appendTo('.centerCont');
- $('
')
- .appendTo('.centerCont');
- $('')
- .addClass('endGame endGameOption')
- .text(_('iOS.'))
- .click(function() { window.open('https://itunes.apple.com/app/apple-store/id736683061?pt=2073437&ct=gameover&mt=8'); })
- .appendTo('.centerCont')
- .animate({opacity:1},1500);
- $('
')
- .appendTo('.centerCont');
- $('')
- .addClass('endGame endGameOption')
- .text(_('android.'))
- .click(function() { window.open('https://play.google.com/store/apps/details?id=com.yourcompany.adarkroom'); })
- .appendTo('.centerCont')
- .animate({opacity:1},1500);
- Engine.options = {};
- Engine.deleteSave(true);
+ Space.showExpansionEnding().then(() => {
+ Space.showEndingOptions();
+ Engine.options = {};
+ Engine.deleteSave(true);
+ });
}
});
}, 2000);
@@ -501,6 +451,120 @@ var Space = {
}, 2000);
});
},
+
+ showExpansionEnding: () => {
+ return new Promise((resolve) => {
+ if (!$SM.get('stores["fleet beacon"]')) {
+ resolve();
+ return;
+ }
+
+ const c = $('')
+ .addClass('outroContainer')
+ .appendTo('body');
+
+ setTimeout(() => {
+ $('
')
+ .addClass('outro')
+ .html('the beacon pulses gently as the ship glides through space.
coordinates are locked. nothing to do but wait.')
+ .appendTo(c)
+ .animate({ opacity: 1}, 500);
+ }, 2000);
+
+ setTimeout(() => {
+ $('
')
+ .addClass('outro')
+ .html('the beacon glows a solid blue, and then goes dim. the ship slows.
gradually, the vast wanderer homefleet comes into view.
massive worldships drift unnaturally through clouds of debris, scarred and dead.')
+ .appendTo(c)
+ .animate({ opacity: 1}, 500);
+ }, 7000);
+
+ setTimeout(() => {
+ $('
')
+ .addClass('outro')
+ .text('the air is running out.')
+ .appendTo(c)
+ .animate({ opacity: 1}, 500);
+ }, 14000);
+
+ setTimeout(() => {
+ $('
')
+ .addClass('outro')
+ .text('the capsule is cold.')
+ .appendTo(c)
+ .animate({ opacity: 1}, 500);
+ }, 17000);
+
+ setTimeout(() => {
+ Button.Button({
+ id: 'wait-btn',
+ text: _('wait'),
+ click: (btn) => {
+ btn.addClass('disabled');
+ c.animate({ opacity: 0 }, 5000, 'linear', () => {
+ c.remove();
+ setTimeout(resolve, 3000);
+ })
+ }
+ }).animate({ opacity: 1 }, 500).appendTo(c);
+ }, 19500)
+ });
+ },
+
+ showEndingOptions: () => {
+ $('
')
+ .addClass('centerCont')
+ .appendTo('body');
+ $('')
+ .addClass('endGame')
+ .text(_('score for this game: {0}', Score.calculateScore()))
+ .appendTo('.centerCont')
+ .animate({opacity:1},1500);
+ $('
')
+ .appendTo('.centerCont');
+ $('')
+ .addClass('endGame')
+ .text(_('total score: {0}', Prestige.get().score))
+ .appendTo('.centerCont')
+ .animate({opacity:1},1500);
+ $('
')
+ .appendTo('.centerCont');
+ $('
')
+ .appendTo('.centerCont');
+ $('')
+ .addClass('endGame endGameOption')
+ .text(_('restart.'))
+ .click(Engine.confirmDelete)
+ .appendTo('.centerCont')
+ .animate({opacity:1},1500);
+ $('
')
+ .appendTo('.centerCont');
+ $('
')
+ .appendTo('.centerCont');
+ $('')
+ .addClass('endGame')
+ .text(_('expanded story. alternate ending. behind the scenes commentary. get the app.'))
+ .appendTo('.centerCont')
+ .animate({opacity:1}, 1500);
+ $('
')
+ .appendTo('.centerCont');
+ $('
')
+ .appendTo('.centerCont');
+ $('')
+ .addClass('endGame endGameOption')
+ .text(_('iOS.'))
+ .click(function() { window.open('https://itunes.apple.com/app/apple-store/id736683061?pt=2073437&ct=gameover&mt=8'); })
+ .appendTo('.centerCont')
+ .animate({opacity:1},1500);
+ $('
')
+ .appendTo('.centerCont');
+ $('')
+ .addClass('endGame endGameOption')
+ .text(_('android.'))
+ .click(function() { window.open('https://play.google.com/store/apps/details?id=com.yourcompany.adarkroom'); })
+ .appendTo('.centerCont')
+ .animate({opacity:1},1500);
+ },
keyDown: function(event) {
switch(event.which) {
diff --git a/script/world.js b/script/world.js
index f6dc6d51..9870998e 100644
--- a/script/world.js
+++ b/script/world.js
@@ -1,1033 +1,1109 @@
var World = {
- RADIUS: 30,
- VILLAGE_POS: [30, 30],
- TILE: {
- VILLAGE: 'A',
- IRON_MINE: 'I',
- COAL_MINE: 'C',
- SULPHUR_MINE: 'S',
- FOREST: ';',
- FIELD: ',',
- BARRENS: '.',
- ROAD: '#',
- HOUSE: 'H',
- CAVE: 'V',
- TOWN: 'O',
- CITY: 'Y',
- OUTPOST: 'P',
- SHIP: 'W',
- BOREHOLE: 'B',
- BATTLEFIELD: 'F',
- SWAMP: 'M',
- CACHE: 'U'
- },
- TILE_PROBS: {},
- LANDMARKS: {},
- STICKINESS: 0.5, // 0 <= x <= 1
- LIGHT_RADIUS: 2,
- BASE_WATER: 10,
- MOVES_PER_FOOD: 2,
- MOVES_PER_WATER: 1,
- DEATH_COOLDOWN: 120,
- FIGHT_CHANCE: 0.20,
- BASE_HEALTH: 10,
- BASE_HIT_CHANCE: 0.8,
- MEAT_HEAL: 8,
- MEDS_HEAL: 20,
- FIGHT_DELAY: 3, // At least three moves between fights
- NORTH: [ 0, -1],
- SOUTH: [ 0, 1],
- WEST: [-1, 0],
- EAST: [ 1, 0],
-
- Weapons: {
- 'fists': {
- verb: _('punch'),
- type: 'unarmed',
- damage: 1,
- cooldown: 2
- },
- 'bone spear': {
- verb: _('stab'),
- type: 'melee',
- damage: 2,
- cooldown: 2
- },
- 'iron sword': {
- verb: _('swing'),
- type: 'melee',
- damage: 4,
- cooldown: 2
- },
- 'steel sword': {
- verb: _('slash'),
- type: 'melee',
- damage: 6,
- cooldown: 2
- },
- 'bayonet': {
- verb: _('thrust'),
- type: 'melee',
- damage: 8,
- cooldown: 2
- },
- 'rifle': {
- verb: _('shoot'),
- type: 'ranged',
- damage: 5,
- cooldown: 1,
- cost: { 'bullets': 1 }
- },
- 'laser rifle': {
- verb: _('blast'),
- type: 'ranged',
- damage: 8,
- cooldown: 1,
- cost: { 'energy cell': 1 }
- },
- 'grenade': {
- verb: _('lob'),
- type: 'ranged',
- damage: 15,
- cooldown: 5,
- cost: { 'grenade': 1 }
- },
- 'bolas': {
- verb: _('tangle'),
- type: 'ranged',
- damage: 'stun',
- cooldown: 15,
- cost: { 'bolas': 1 }
- }
- },
-
- 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')};
-
- // Only add the cache if there is prestige data
- if($SM.get('previous.stores')) {
- World.LANDMARKS[World.TILE.CACHE] = { num: 1, minRadius: 10, maxRadius: World.RADIUS * 1.5, scene: 'cache', label: _('A Destroyed Village')};
- }
-
- if(typeof $SM.get('features.location.world') == 'undefined') {
- $SM.set('features.location.world', true);
- $SM.setM('game.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();
-
- // Map the ship and show compass tooltip
- World.ship = World.mapSearch(World.TILE.SHIP,$SM.get('game.world.map'),1);
- World.dir = World.compassDir(World.ship[0]);
- // compass tooltip text
- Room.compassTooltip(World.dir);
-
- // Check if everything has been seen
- World.testMap();
-
- //subscribe to stateUpdates
- $.Dispatch('stateUpdate').subscribe(World.handleStateUpdates);
- },
-
- clearDungeon: function() {
- Engine.event('progress', 'dungeon cleared');
- World.state.map[World.curPos[0]][World.curPos[1]] = World.TILE.OUTPOST;
- World.drawRoad();
- },
-
- drawRoad: function() {
- var findClosestRoad = function(startPos) {
- // We'll search in a spiral to find the closest road tile
- // We spiral out along manhattan distance contour
- // lines to ensure we draw the shortest road possible.
- // No attempt is made to reduce the search space for
- // tiles outside the map.
- var searchX, searchY, dtmp,
- x = 0,
- y = 0,
- dx = 1,
- dy = -1;
- for (var i = 0; i < Math.pow(World.getDistance(startPos, World.VILLAGE_POS) + 2, 2); i++) {
- searchX = startPos[0] + x;
- searchY = startPos[1] + y;
- if (0 < searchX && searchX < World.RADIUS * 2 && 0 < searchY && searchY < World.RADIUS * 2) {
- // check for road
- var tile = World.state.map[searchX][searchY];
- if (
- tile === World.TILE.ROAD ||
- (tile === World.TILE.OUTPOST && !(x === 0 && y === 0)) || // outposts are connected to roads
- tile === World.TILE.VILLAGE // all roads lead home
- ) {
- return [searchX, searchY];
- }
- }
- if (x === 0 || y === 0) {
- // Turn the corner
- dtmp = dx;
- dx = -dy;
- dy = dtmp;
- }
- if (x === 0 && y <= 0) {
- x++;
- } else {
- x += dx;
- y += dy;
- }
- }
- return World.VILLAGE_POS;
- };
- var closestRoad = findClosestRoad(World.curPos);
- var xDist = World.curPos[0] - closestRoad[0];
- var yDist = World.curPos[1] - closestRoad[1];
- var xDir = Math.abs(xDist)/xDist;
- var yDir = Math.abs(yDist)/yDist;
- var xIntersect, yIntersect;
- if(Math.abs(xDist) > Math.abs(yDist)) {
- xIntersect = closestRoad[0];
- yIntersect = closestRoad[1] + yDist;
- } else {
- xIntersect = closestRoad[0] + xDist;
- yIntersect = closestRoad[1];
- }
-
- for(var x = 0; x < Math.abs(xDist); x++) {
- if(World.isTerrain(World.state.map[closestRoad[0] + (xDir*x)][yIntersect])) {
- World.state.map[closestRoad[0] + (xDir*x)][yIntersect] = World.TILE.ROAD;
- }
- }
- for(var y = 0; y < Math.abs(yDist); y++) {
- if(World.isTerrain(World.state.map[xIntersect][closestRoad[1] + (yDir*y)])) {
- World.state.map[xIntersect][closestRoad[1] + (yDir*y)] = World.TILE.ROAD;
- }
- }
- World.drawMap();
- },
-
- updateSupplies: function() {
- var supplies = $('div#bagspace-world > div');
-
- if(!Path.outfit) {
- Path.outfit = {};
- }
-
- // Add water
- var water = $('div#supply_water');
- if(World.water > 0 && water.length === 0) {
- water = World.createItemDiv('water', World.water);
- water.prependTo(supplies);
- } else if(World.water > 0) {
- $('div#supply_water', supplies).text(_('water:{0}' , World.water));
- } else {
- water.remove();
- }
-
- var total = 0;
- for(var k in Path.outfit) {
- var item = $('div#supply_' + k.replace(' ', '-'), supplies);
- var num = Path.outfit[k];
- total += num * Path.getWeight(k);
- if(num > 0 && item.length === 0) {
- item = World.createItemDiv(k, num);
- if(k == 'cured meat' && World.water > 0) {
- item.insertAfter(water);
- } else if(k == 'cured meat') {
- item.prependTo(supplies);
- } else {
- item.appendTo(supplies);
- }
- } else if(num > 0) {
- $('div#' + item.attr('id'), supplies).text(_(k) + ':' + num);
- } else {
- item.remove();
- }
- }
-
- // Update label
- var t = _('pockets');
- if($SM.get('stores.rucksack', true) > 0) {
- t = _('rucksack');
- }
- $('#backpackTitle').text(t);
-
- // Update bagspace
- $('#backpackSpace').text(_('free {0}/{1}', Math.floor(Path.getCapacity() - total) , Path.getCapacity()));
- },
-
- setWater: function(w) {
- World.water = w;
- if(World.water > World.getMaxWater()) {
- World.water = World.getMaxWater();
- }
- World.updateSupplies();
- },
-
- setHp: function(hp) {
- if(typeof hp == 'number' && !isNaN(hp)) {
- World.health = hp;
- if(World.health > World.getMaxHealth()) {
- World.health = World.getMaxHealth();
- }
- $('#healthCounter').text(_('hp: {0}/{1}', World.health , World.getMaxHealth()));
- }
- },
-
- createItemDiv: function(name, num) {
- var div = $('
').attr('id', 'supply_' + name.replace(' ', '-'))
- .addClass('supplyItem')
- .text(_('{0}:{1}',_(name), num));
-
- return div;
- },
-
- moveNorth: function() {
- Engine.log('North');
- if(World.curPos[1] > 0) World.move(World.NORTH);
- },
-
- moveSouth: function() {
- Engine.log('South');
- if(World.curPos[1] < World.RADIUS * 2) World.move(World.SOUTH);
- },
-
- moveWest: function() {
- Engine.log('West');
- if(World.curPos[0] > 0) World.move(World.WEST);
- },
-
- moveEast: function() {
- Engine.log('East');
- if(World.curPos[0] < World.RADIUS * 2) World.move(World.EAST);
- },
-
- move: function(direction) {
- var oldTile = World.state.map[World.curPos[0]][World.curPos[1]];
- World.curPos[0] += direction[0];
- World.curPos[1] += direction[1];
- World.narrateMove(oldTile, World.state.map[World.curPos[0]][World.curPos[1]]);
- World.lightMap(World.curPos[0], World.curPos[1], World.state.mask);
- World.drawMap();
- World.doSpace();
-
- // play random footstep
- var randomFootstep = Math.floor(Math.random() * 5) + 1;
- AudioEngine.playSound(AudioLibrary['FOOTSTEPS_' + randomFootstep]);
-
- if(World.checkDanger()) {
- if(World.danger) {
- Notifications.notify(World, _('dangerous to be this far from the village without proper protection'));
- } else {
- Notifications.notify(World, _('safer here'));
- }
- }
- },
-
- keyDown: function(event) {
- switch(event.which) {
- case 38: // Up
- case 87:
- World.moveNorth();
- break;
- case 40: // Down
- case 83:
- World.moveSouth();
- break;
- case 37: // Left
- case 65:
- World.moveWest();
- break;
- case 39: // Right
- case 68:
- World.moveEast();
- break;
- default:
- break;
- }
- },
-
- swipeLeft: function(e) {
- World.moveWest();
- },
-
- swipeRight: function(e) {
- World.moveEast();
- },
-
- swipeUp: function(e) {
- World.moveNorth();
- },
-
- swipeDown: function(e) {
- World.moveSouth();
- },
-
- click: function(event) {
- var map = $('#map'),
- // measure clicks relative to the centre of the current location
- centreX = map.offset().left + map.width() * World.curPos[0] / (World.RADIUS * 2),
- centreY = map.offset().top + map.height() * World.curPos[1] / (World.RADIUS * 2),
- clickX = event.pageX - centreX,
- clickY = event.pageY - centreY;
- if (clickX > clickY && clickX < -clickY) {
- World.moveNorth();
- }
- if (clickX < clickY && clickX > -clickY) {
- World.moveSouth();
- }
- if (clickX < clickY && clickX < -clickY) {
- World.moveWest();
- }
- if (clickX > clickY && clickX > -clickY) {
- World.moveEast();
- }
- },
-
- checkDanger: function() {
- World.danger = typeof World.danger == 'undefined' ? false: World.danger;
- if(!World.danger) {
- if($SM.get('stores["i armour"]', true) === 0 && World.getDistance() >= 8) {
- World.danger = true;
- return true;
- }
- if($SM.get('stores["s armour"]', true) === 0 && World.getDistance() >= 18) {
- World.danger = true;
- return true;
- }
- } else {
- if(World.getDistance() < 8) {
- World.danger = false;
- return true;
- }
- if(World.getDistance < 18 && $SM.get('stores["i armour"]', true) > 0) {
- World.danger = false;
- return true;
- }
- }
- return false;
- },
-
- useSupplies: function() {
- World.foodMove++;
- World.waterMove++;
- // Food
- var movesPerFood = World.MOVES_PER_FOOD;
- movesPerFood *= $SM.hasPerk('slow metabolism') ? 2 : 1;
- if(World.foodMove >= movesPerFood) {
- World.foodMove = 0;
- var num = Path.outfit['cured meat'];
- num--;
- if(num === 0) {
- Notifications.notify(World, _('the meat has run out'));
- } else if(num < 0) {
- // Starvation! Hooray!
- num = 0;
- if(!World.starvation) {
- Notifications.notify(World, _('starvation sets in'));
- World.starvation = true;
- } else {
- $SM.set('character.starved', $SM.get('character.starved', true));
- $SM.add('character.starved', 1);
- if($SM.get('character.starved') >= 10 && !$SM.hasPerk('slow metabolism')) {
- $SM.addPerk('slow metabolism');
- }
- World.die();
- return false;
- }
- } else {
- World.starvation = false;
- World.setHp(World.health + World.meatHeal());
- }
- Path.outfit['cured meat'] = num;
- }
- // Water
- var movesPerWater = World.MOVES_PER_WATER;
- movesPerWater *= $SM.hasPerk('desert rat') ? 2 : 1;
- if(World.waterMove >= movesPerWater) {
- World.waterMove = 0;
- var water = World.water;
- water--;
- if(water === 0) {
- Notifications.notify(World, _('there is no more water'));
- } else if(water < 0) {
- water = 0;
- if(!World.thirst) {
- Notifications.notify(World, _('the thirst becomes unbearable'));
- World.thirst = true;
- } else {
- $SM.set('character.dehydrated', $SM.get('character.dehydrated', true));
- $SM.add('character.dehydrated', 1);
- if($SM.get('character.dehydrated') >= 10 && !$SM.hasPerk('desert rat')) {
- $SM.addPerk('desert rat');
- }
- World.die();
- return false;
- }
- } else {
- World.thirst = false;
- }
- World.setWater(water);
- World.updateSupplies();
- }
- return true;
- },
-
- meatHeal: function() {
- return World.MEAT_HEAL * ($SM.hasPerk('gastronome') ? 2 : 1);
- },
-
- medsHeal: function() {
- return World.MEDS_HEAL;
- },
-
- checkFight: function() {
- World.fightMove = typeof World.fightMove == 'number' ? World.fightMove : 0;
- World.fightMove++;
- if(World.fightMove > World.FIGHT_DELAY) {
- var chance = World.FIGHT_CHANCE;
- chance *= $SM.hasPerk('stealthy') ? 0.5 : 1;
- if(Math.random() < chance) {
- World.fightMove = 0;
- Events.triggerFight();
- }
- }
- },
-
- doSpace: function() {
- var curTile = World.state.map[World.curPos[0]][World.curPos[1]];
-
- if(curTile == World.TILE.VILLAGE) {
- World.goHome();
- } else if(typeof World.LANDMARKS[curTile] != 'undefined') {
- if(curTile != World.TILE.OUTPOST || !World.outpostUsed()) {
- Events.startEvent(Events.Setpieces[World.LANDMARKS[curTile].scene]);
- AudioEngine.playEventMusic(Events.Setpieces[World.LANDMARKS[curTile].scene].audio);
- }
- } else {
- if(World.useSupplies()) {
- World.checkFight();
- }
- }
- },
-
- getDistance: function(from, to) {
- from = from || World.curPos;
- to = to || World.VILLAGE_POS;
- return Math.abs(from[0] - to[0]) + Math.abs(from[1] - to[1]);
- },
-
- getTerrain: function() {
- return World.state.map[World.curPos[0]][World.curPos[1]];
- },
-
- getDamage: function(thing) {
- return World.Weapons[thing].damage;
- },
-
- narrateMove: function(oldTile, newTile) {
- var msg = null;
- switch(oldTile) {
- case World.TILE.FOREST:
- switch(newTile) {
- case World.TILE.FIELD:
- msg = _("the trees yield to dry grass. the yellowed brush rustles in the wind.");
- break;
- case World.TILE.BARRENS:
- msg = _("the trees are gone. parched earth and blowing dust are poor replacements.");
- break;
- }
- break;
- case World.TILE.FIELD:
- switch(newTile) {
- case World.TILE.FOREST:
- msg = _("trees loom on the horizon. grasses gradually yield to a forest floor of dry branches and fallen leaves.");
- break;
- case World.TILE.BARRENS:
- msg = _("the grasses thin. soon, only dust remains.");
- break;
- }
- break;
- case World.TILE.BARRENS:
- switch(newTile) {
- case World.TILE.FIELD:
- msg = _("the barrens break at a sea of dying grass, swaying in the arid breeze.");
- break;
- case World.TILE.FOREST:
- msg = _("a wall of gnarled trees rises from the dust. their branches twist into a skeletal canopy overhead.");
- break;
- }
- break;
- }
- if(msg != null) {
- Notifications.notify(World, msg);
- }
- },
-
- newMask: function() {
- var mask = new Array(World.RADIUS * 2 + 1);
- for(var i = 0; i <= World.RADIUS * 2; i++) {
- mask[i] = new Array(World.RADIUS * 2 + 1);
- }
- World.lightMap(World.RADIUS, World.RADIUS, mask);
- return mask;
- },
-
- lightMap: function(x, y, mask) {
- var r = World.LIGHT_RADIUS;
- r *= $SM.hasPerk('scout') ? 2 : 1;
- World.uncoverMap(x, y, r, mask);
- return mask;
- },
-
- uncoverMap: function(x, y, r, mask) {
- mask[x][y] = true;
- for(var i = -r; i <= r; i++) {
- for(var j = -r + Math.abs(i); j <= r - Math.abs(i); j++) {
- if(y + j >= 0 && y + j <= World.RADIUS * 2 &&
- x + i <= World.RADIUS * 2 &&
- x + i >= 0) {
- mask[x+i][y+j] = true;
- }
- }
- }
- },
-
- testMap: function() {
- if(!World.seenAll) {
- var dark;
- var mask = $SM.get('game.world.mask');
- loop:
- for(var i = 0; i < mask.length; i++) {
- for(var j = 0; j < mask[i].length; j++) {
- if(!mask[i][j]) {
- dark = true;
- break loop;
- }
- }
- }
- World.seenAll = !dark;
- }
- },
-
- applyMap: function() {
- if(!World.seenAll){
- var x,y,mask = $SM.get('game.world.mask');
- do {
- x = Math.floor(Math.random() * (World.RADIUS * 2 + 1));
- y = Math.floor(Math.random() * (World.RADIUS * 2 + 1));
- } while (mask[x][y]);
- World.uncoverMap(x, y, 5, mask);
- }
- World.testMap();
- },
-
- generateMap: function() {
- var map = new Array(World.RADIUS * 2 + 1);
- for(var i = 0; i <= World.RADIUS * 2; i++) {
- map[i] = new Array(World.RADIUS * 2 + 1);
- }
- // The Village is always at the exact center
- // Spiral out from there
- map[World.RADIUS][World.RADIUS] = World.TILE.VILLAGE;
- for(var r = 1; r <= World.RADIUS; r++) {
- for(var t = 0; t < r * 8; t++) {
- var x, y;
- if(t < 2 * r) {
- x = World.RADIUS - r + t;
- y = World.RADIUS - r;
- } else if(t < 4 * r) {
- x = World.RADIUS + r;
- y = World.RADIUS - (3 * r) + t;
- } else if(t < 6 * r) {
- x = World.RADIUS + (5 * r) - t;
- y = World.RADIUS + r;
- } else {
- x = World.RADIUS - r;
- y = World.RADIUS + (7 * r) - t;
- }
-
- map[x][y] = World.chooseTile(x, y, map);
- }
- }
-
- // Place landmarks
- for(var k in World.LANDMARKS) {
- var landmark = World.LANDMARKS[k];
- for(var l = 0; l < landmark.num; l++) {
- var pos = World.placeLandmark(landmark.minRadius, landmark.maxRadius, k, map);
- }
- }
-
- return map;
- },
-
- mapSearch: function(target,map,required){
- var max = World.LANDMARKS[target].num;
- if(!max){
- // this restrict the research to numerable landmarks
- return null;
- }
- // restrict research if only a fixed number (usually 1) is required
- max = (required) ? Math.min(required,max) : max;
- var index = 0;
- var targets = [];
- search: // label for coordinate research
- for(var i = 0; i <= World.RADIUS * 2; i++){
- for(var j = 0; j <= World.RADIUS * 2; j++){
- if(map[i][j].charAt(0) === target){
- // search result is stored as an object;
- // items are listed as they appear in the map, tl-br
- // each item has relative coordinates and a compass-type direction
- targets[index] = {
- x : i - World.RADIUS,
- y : j - World.RADIUS,
- };
- index++;
- if(index === max){
- // optimisation: stop the research if maximum number of items has been reached
- break search;
- }
- }
- }
- }
- return targets;
- },
-
- compassDir: function(pos){
- var dir = '';
- var horz = pos.x < 0 ? 'west' : 'east';
- var vert = pos.y < 0 ? 'north' : 'south';
- if(Math.abs(pos.x) / 2 > Math.abs(pos.y)) {
- dir = horz;
- } else if(Math.abs(pos.y) / 2 > Math.abs(pos.x)){
- dir = vert;
- } else {
- dir = vert + horz;
- }
- return dir;
- },
-
- placeLandmark: function(minRadius, maxRadius, landmark, map) {
-
- var x = World.RADIUS, y = World.RADIUS;
- while(!World.isTerrain(map[x][y])) {
- var r = Math.floor(Math.random() * (maxRadius - minRadius)) + minRadius;
- var xDist = Math.floor(Math.random() * r);
- var yDist = r - xDist;
- if(Math.random() < 0.5) xDist = -xDist;
- if(Math.random() < 0.5) yDist = -yDist;
- x = World.RADIUS + xDist;
- if(x < 0) x = 0;
- if(x > World.RADIUS * 2) x = World.RADIUS * 2;
- y = World.RADIUS + yDist;
- if(y < 0) y = 0;
- if(y > World.RADIUS * 2) y = World.RADIUS * 2;
- }
- map[x][y] = landmark;
- return [x, y];
- },
-
- isTerrain: function(tile) {
- return tile == World.TILE.FOREST || tile == World.TILE.FIELD || tile == World.TILE.BARRENS;
- },
-
- chooseTile: function(x, y, map) {
-
- var adjacent = [
- y > 0 ? map[x][y-1] : null,
- y < World.RADIUS * 2 ? map[x][y+1] : null,
- x < World.RADIUS * 2 ? map[x+1][y] : null,
- x > 0 ? map[x-1][y] : null
- ];
-
- var chances = {};
- var nonSticky = 1;
- var cur;
- for(var i in adjacent) {
- if(adjacent[i] == World.TILE.VILLAGE) {
- // Village must be in a forest to maintain thematic consistency, yo.
- return World.TILE.FOREST;
- } else if(typeof adjacent[i] == 'string') {
- cur = chances[adjacent[i]];
- cur = typeof cur == 'number' ? cur : 0;
- chances[adjacent[i]] = cur + World.STICKINESS;
- nonSticky -= World.STICKINESS;
- }
- }
- for(var t in World.TILE) {
- var tile = World.TILE[t];
- if(World.isTerrain(tile)) {
- cur = chances[tile];
- cur = typeof cur == 'number' ? cur : 0;
- cur += World.TILE_PROBS[tile] * nonSticky;
- chances[tile] = cur;
- }
- }
-
- var list = [];
- for(var j in chances) {
- list.push(chances[j] + '' + j);
- }
- list.sort(function(a, b) {
- var n1 = parseFloat(a.substring(0, a.length - 1));
- var n2 = parseFloat(b.substring(0, b.length - 1));
- return n2 - n1;
- });
-
- var c = 0;
- var r = Math.random();
- for(var l in list) {
- var prob = list[l];
- c += parseFloat(prob.substring(0,prob.length - 1));
- if(r < c) {
- return prob.charAt(prob.length - 1);
- }
- }
-
- return World.TILE.BARRENS;
- },
-
- markVisited: function(x, y) {
- World.state.map[x][y] = World.state.map[x][y] + '!';
- },
-
- drawMap: function() {
- var map = $('#map');
- if(map.length === 0) {
- map = new $('
').attr('id', 'map').appendTo('#worldOuter');
- // register click handler
- map.click(World.click);
- }
- var mapString = "";
- for(var j = 0; j <= World.RADIUS * 2; j++) {
- for(var i = 0; i <= World.RADIUS * 2; i++) {
- var ttClass = "";
- if(i > World.RADIUS) {
- ttClass += " left";
- } else {
- ttClass += " right";
- }
- if(j > World.RADIUS) {
- ttClass += " top";
- } else {
- ttClass += " bottom";
- }
- if(World.curPos[0] == i && World.curPos[1] == j) {
- mapString += '
@'+_('Wanderer')+'
';
- } else if(World.state.mask[i][j]) {
- var c = World.state.map[i][j];
- switch(c) {
- case World.TILE.VILLAGE:
- mapString += '
' + c + ''+_('The Village')+'
';
- break;
- default:
- if(typeof World.LANDMARKS[c] != 'undefined' && (c != World.TILE.OUTPOST || !World.outpostUsed(i, j))) {
- mapString += '
' + c + '' + World.LANDMARKS[c].label + '
';
- } else {
- if(c.length > 1) {
- c = c[0];
- }
- mapString += c;
- }
- break;
- }
- } else {
- mapString += ' ';
- }
- }
- mapString += '
';
- }
- map.html(mapString);
- },
-
- die: function() {
- if(!World.dead) {
- World.dead = true;
- Engine.log('player death');
- Engine.event('game event', 'death');
- Engine.keyLock = true;
- // Dead! Discard any world changes and go home
- Notifications.notify(World, _('the world fades'));
- World.state = null;
- Path.outfit = {};
- $SM.remove('outfit');
- AudioEngine.playSound(AudioLibrary.DEATH);
- $('#outerSlider').animate({opacity: '0'}, 600, 'linear', function() {
- $('#outerSlider').css('left', '0px');
- $('#locationSlider').css('left', '0px');
- $('#storesContainer').css({'top': '0px', 'right': '0px'});
- Engine.activeModule = Room;
- $('div.headerButton').removeClass('selected');
- Room.tab.addClass('selected');
- Engine.setTimeout(function(){
- Room.onArrival();
- $('#outerSlider').animate({opacity:'1'}, 600, 'linear');
- Button.cooldown($('#embarkButton'));
- Engine.keyLock = false;
- Engine.tabNavigation = true;
- }, 2000, true);
- });
- }
- },
-
- goHome: function() {
- // Home safe! Commit the changes.
- $SM.setM('game.world', World.state);
- World.testMap();
-
- if(World.state.sulphurmine && $SM.get('game.buildings["sulphur mine"]', true) === 0) {
- $SM.add('game.buildings["sulphur mine"]', 1);
- Engine.event('progress', 'sulphur mine');
- }
- if(World.state.ironmine && $SM.get('game.buildings["iron mine"]', true) === 0) {
- $SM.add('game.buildings["iron mine"]', 1);
- Engine.event('progress', 'iron mine');
- }
- if(World.state.coalmine && $SM.get('game.buildings["coal mine"]', true) === 0) {
- $SM.add('game.buildings["coal mine"]', 1);
- Engine.event('progress', 'coal mine');
- }
- if(World.state.ship && !$SM.get('features.location.spaceShip')) {
- Ship.init();
- Engine.event('progress', 'ship');
- }
- World.state = null;
-
- if(Path.outfit['cured meat'] > 0) {
- Button.setDisabled($('#embarkButton'), false);
- }
-
- for(var k in Path.outfit) {
- $SM.add('stores["'+k+'"]', Path.outfit[k]);
- if(World.leaveItAtHome(k)) {
- Path.outfit[k] = 0;
- }
- }
-
- $('#outerSlider').animate({left: '0px'}, 300);
- Engine.activeModule = Path;
- Path.onArrival();
- Engine.restoreNavigation = true;
- },
-
- leaveItAtHome: function(thing) {
- return thing != 'cured meat' && thing != 'bullets' && thing != 'energy cell' && thing != 'charm' && thing != 'medicine' &&
- typeof World.Weapons[thing] == 'undefined' && typeof Room.Craftables[thing] == 'undefined';
- },
-
- getMaxHealth: function() {
- if($SM.get('stores["s armour"]', true) > 0) {
- return World.BASE_HEALTH + 35;
- } else if($SM.get('stores["i armour"]', true) > 0) {
- return World.BASE_HEALTH + 15;
- } else if($SM.get('stores["l armour"]', true) > 0) {
- return World.BASE_HEALTH + 5;
- }
- return World.BASE_HEALTH;
- },
-
- getHitChance: function() {
- if($SM.hasPerk('precise')) {
- return World.BASE_HIT_CHANCE + 0.1;
- }
- return World.BASE_HIT_CHANCE;
- },
-
- getMaxWater: function() {
- if($SM.get('stores["water tank"]', true) > 0) {
- return World.BASE_WATER + 50;
- } else if($SM.get('stores.cask', true) > 0) {
- return World.BASE_WATER + 20;
- } else if($SM.get('stores.waterskin', true) > 0) {
- return World.BASE_WATER + 10;
- }
- return World.BASE_WATER;
- },
-
- outpostUsed: function(x, y) {
- x = typeof x == 'number' ? x : World.curPos[0];
- y = typeof y == 'number' ? y : World.curPos[1];
- var used = World.usedOutposts[x + ',' + y];
- return typeof used != 'undefined' && used === true;
- },
-
- useOutpost: function() {
- Notifications.notify(null, _('water replenished'));
- World.setWater(World.getMaxWater());
- // Mark this outpost as used
- World.usedOutposts[World.curPos[0] + ',' + World.curPos[1]] = true;
- },
-
- onArrival: function() {
- Engine.tabNavigation = false;
- // Clear the embark cooldown
- Button.clearCooldown($('#embarkButton'));
- Engine.keyLock = false;
- // Explore in a temporary world-state. We'll commit the changes if you return home safe.
- World.state = $.extend(true, {}, $SM.get('game.world'));
- World.setWater(World.getMaxWater());
- World.setHp(World.getMaxHealth());
- World.foodMove = 0;
- World.waterMove = 0;
- World.starvation = false;
- World.thirst = false;
- World.usedOutposts = {};
- World.curPos = World.copyPos(World.VILLAGE_POS);
- World.drawMap();
- World.setTitle();
- AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_WORLD);
- World.dead = false;
- $('div#bagspace-world > div').empty();
- World.updateSupplies();
- $('#bagspace-world').width($('#map').width());
- },
-
- setTitle: function() {
- document.title = _('A Barren World');
- },
-
- copyPos: function(pos) {
- return [pos[0], pos[1]];
- },
-
- handleStateUpdates: function(e){
-
- }
+ RADIUS: 30,
+ VILLAGE_POS: [30, 30],
+ TILE: {
+ VILLAGE: 'A',
+ IRON_MINE: 'I',
+ COAL_MINE: 'C',
+ SULPHUR_MINE: 'S',
+ FOREST: ';',
+ FIELD: ',',
+ BARRENS: '.',
+ ROAD: '#',
+ HOUSE: 'H',
+ CAVE: 'V',
+ TOWN: 'O',
+ CITY: 'Y',
+ OUTPOST: 'P',
+ SHIP: 'W',
+ BOREHOLE: 'B',
+ BATTLEFIELD: 'F',
+ SWAMP: 'M',
+ CACHE: 'U',
+ EXECUTIONER: 'X'
+ },
+ TILE_PROBS: {},
+ LANDMARKS: {},
+ STICKINESS: 0.5, // 0 <= x <= 1
+ LIGHT_RADIUS: 2,
+ BASE_WATER: 10,
+ MOVES_PER_FOOD: 2,
+ MOVES_PER_WATER: 1,
+ DEATH_COOLDOWN: 120,
+ FIGHT_CHANCE: 0, //0.20, TODO UNCOMMENT THIS
+ BASE_HEALTH: 10,
+ BASE_HIT_CHANCE: 0.8,
+ MEAT_HEAL: 8,
+ MEDS_HEAL: 20,
+ HYPO_HEAL: 30,
+ FIGHT_DELAY: 3, // At least three moves between fights
+ NORTH: [ 0, -1],
+ SOUTH: [ 0, 1],
+ WEST: [-1, 0],
+ EAST: [ 1, 0],
+
+ Weapons: {
+ 'fists': {
+ verb: _('punch'),
+ type: 'unarmed',
+ damage: 1,
+ cooldown: 2
+ },
+ 'bone spear': {
+ verb: _('stab'),
+ type: 'melee',
+ damage: 2,
+ cooldown: 2
+ },
+ 'iron sword': {
+ verb: _('swing'),
+ type: 'melee',
+ damage: 4,
+ cooldown: 2
+ },
+ 'steel sword': {
+ verb: _('slash'),
+ type: 'melee',
+ damage: 6,
+ cooldown: 2
+ },
+ 'bayonet': {
+ verb: _('thrust'),
+ type: 'melee',
+ damage: 8,
+ cooldown: 2
+ },
+ 'rifle': {
+ verb: _('shoot'),
+ type: 'ranged',
+ damage: 5,
+ cooldown: 1,
+ cost: { 'bullets': 1 }
+ },
+ 'laser rifle': {
+ verb: _('blast'),
+ type: 'ranged',
+ damage: 8,
+ cooldown: 1,
+ cost: { 'energy cell': 1 }
+ },
+ 'grenade': {
+ verb: _('lob'),
+ type: 'ranged',
+ damage: 15,
+ cooldown: 5,
+ cost: { 'grenade': 1 }
+ },
+ 'bolas': {
+ verb: _('tangle'),
+ type: 'ranged',
+ damage: 'stun',
+ cooldown: 15,
+ cost: { 'bolas': 1 }
+ },
+ 'plasma rifle': {
+ verb: _('disintigrate'),
+ type: 'ranged',
+ damage: 12,
+ cooldown: 1,
+ cost: { 'energy cell': 1 }
+ },
+ 'energy blade': {
+ verb: _('slice'),
+ type: 'melee',
+ damage: 10,
+ cooldown: 2
+ },
+ 'disruptor': {
+ verb: _('stun'),
+ type: 'ranged',
+ damage: 'stun',
+ cooldown: 15
+ }
+ },
+
+ name: 'World',
+ options: {}, // Nothing for now
+ init: function(options) {
+ this.options = $.extend(
+ this.options,
+ options
+ );
+
+ // Setup probabilities. Sum must equal 1.
+ World.TILE_PROBS[World.TILE.FOREST] = 0.15;
+ World.TILE_PROBS[World.TILE.FIELD] = 0.35;
+ World.TILE_PROBS[World.TILE.BARRENS] = 0.5;
+
+ // Setpiece definitions
+ World.LANDMARKS[World.TILE.OUTPOST] = { num: 0, minRadius: 0, maxRadius: 0, scene: 'outpost', label: _('An Outpost') };
+ World.LANDMARKS[World.TILE.IRON_MINE] = { num: 1, minRadius: 5, maxRadius: 5, scene: 'ironmine', label: _('Iron Mine') };
+ World.LANDMARKS[World.TILE.COAL_MINE] = { num: 1, minRadius: 10, maxRadius: 10, scene: 'coalmine', label: _('Coal Mine') };
+ World.LANDMARKS[World.TILE.SULPHUR_MINE] = { num: 1, minRadius: 20, maxRadius: 20, scene: 'sulphurmine', label: _('Sulphur Mine') };
+ World.LANDMARKS[World.TILE.HOUSE] = { num: 10, minRadius: 0, maxRadius: World.RADIUS * 1.5, scene: 'house', label: _('An Old House') };
+ World.LANDMARKS[World.TILE.CAVE] = { num: 5, minRadius: 3, maxRadius: 10, scene: 'cave', label: _('A Damp Cave') };
+ World.LANDMARKS[World.TILE.TOWN] = { num: 10, minRadius: 10, maxRadius: 20, scene: 'town', label: _('An Abandoned Town') };
+ World.LANDMARKS[World.TILE.CITY] = { num: 20, minRadius: 20, maxRadius: World.RADIUS * 1.5, scene: 'city', label: _('A Ruined City') };
+ World.LANDMARKS[World.TILE.SHIP] = { num: 1, minRadius: 28, maxRadius: 28, scene: 'ship', label: _('A Crashed Starship')};
+ World.LANDMARKS[World.TILE.BOREHOLE] = { num: 10, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'borehole', label: _('A Borehole')};
+ World.LANDMARKS[World.TILE.BATTLEFIELD] = { num: 5, minRadius: 18, maxRadius: World.RADIUS * 1.5, scene: 'battlefield', label: _('A Battlefield')};
+ World.LANDMARKS[World.TILE.SWAMP] = { num: 1, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'swamp', label: _('A Murky Swamp')};
+ World.LANDMARKS[World.TILE.EXECUTIONER] = { num: 1, minRadius: 28, maxRadius: 28, scene: 'executioner', 'label': _('A Ravaged Battleship')};
+
+ // Only add the cache if there is prestige data
+ if($SM.get('previous.stores')) {
+ World.LANDMARKS[World.TILE.CACHE] = { num: 1, minRadius: 10, maxRadius: World.RADIUS * 1.5, scene: 'cache', label: _('A Destroyed Village')};
+ }
+
+ if(typeof $SM.get('features.location.world') == 'undefined') {
+ $SM.set('features.location.world', true);
+ $SM.set('features.executioner', true);
+ $SM.setM('game.world', {
+ map: World.generateMap(),
+ mask: World.newMask()
+ });
+ }
+ else if (!$SM.get('features.executioner')) {
+ // Place the Executioner in previously generated maps that don't have it
+ const map = $SM.get('game.world.map');
+ const landmark = World.LANDMARKS[World.TILE.EXECUTIONER]
+ for(let l = 0; l < landmark.num; l++) {
+ World.placeLandmark(landmark.minRadius, landmark.maxRadius, World.TILE.EXECUTIONER, map);
+ }
+ $SM.set('game.world.map', map);
+ $SM.set('features.executioner', true);
+ }
+
+ // Create the World panel
+ this.panel = $('
').attr('id', "worldPanel").addClass('location').appendTo('#outerSlider');
+
+ // Create the shrink wrapper
+ var outer = $('
').attr('id', 'worldOuter').appendTo(this.panel);
+
+ // Create the bag panel
+ $('
').attr('id', 'bagspace-world').append($('
')).appendTo(outer);
+ $('
').attr('id', 'backpackTitle').appendTo(outer);
+ $('
').attr('id', 'backpackSpace').appendTo(outer);
+ $('
').attr('id', 'healthCounter').appendTo(outer);
+
+ Engine.updateOuterSlider();
+
+ // Map the ship and show compass tooltip
+ World.ship = World.mapSearch(World.TILE.SHIP,$SM.get('game.world.map'),1);
+ World.dir = World.compassDir(World.ship[0]);
+ // compass tooltip text
+ Room.compassTooltip(World.dir);
+
+ // Check if everything has been seen
+ World.testMap();
+
+ //subscribe to stateUpdates
+ $.Dispatch('stateUpdate').subscribe(World.handleStateUpdates);
+ },
+
+ clearDungeon: function() {
+ Engine.event('progress', 'dungeon cleared');
+ World.state.map[World.curPos[0]][World.curPos[1]] = World.TILE.OUTPOST;
+ World.drawRoad();
+ },
+
+ drawRoad: function() {
+ var findClosestRoad = function(startPos) {
+ // We'll search in a spiral to find the closest road tile
+ // We spiral out along manhattan distance contour
+ // lines to ensure we draw the shortest road possible.
+ // No attempt is made to reduce the search space for
+ // tiles outside the map.
+ var searchX, searchY, dtmp,
+ x = 0,
+ y = 0,
+ dx = 1,
+ dy = -1;
+ for (var i = 0; i < Math.pow(World.getDistance(startPos, World.VILLAGE_POS) + 2, 2); i++) {
+ searchX = startPos[0] + x;
+ searchY = startPos[1] + y;
+ if (0 < searchX && searchX < World.RADIUS * 2 && 0 < searchY && searchY < World.RADIUS * 2) {
+ // check for road
+ var tile = World.state.map[searchX][searchY];
+ if (
+ tile === World.TILE.ROAD ||
+ (tile === World.TILE.OUTPOST && !(x === 0 && y === 0)) || // outposts are connected to roads
+ tile === World.TILE.VILLAGE // all roads lead home
+ ) {
+ return [searchX, searchY];
+ }
+ }
+ if (x === 0 || y === 0) {
+ // Turn the corner
+ dtmp = dx;
+ dx = -dy;
+ dy = dtmp;
+ }
+ if (x === 0 && y <= 0) {
+ x++;
+ } else {
+ x += dx;
+ y += dy;
+ }
+ }
+ return World.VILLAGE_POS;
+ };
+ var closestRoad = findClosestRoad(World.curPos);
+ var xDist = World.curPos[0] - closestRoad[0];
+ var yDist = World.curPos[1] - closestRoad[1];
+ var xDir = Math.abs(xDist)/xDist;
+ var yDir = Math.abs(yDist)/yDist;
+ var xIntersect, yIntersect;
+ if(Math.abs(xDist) > Math.abs(yDist)) {
+ xIntersect = closestRoad[0];
+ yIntersect = closestRoad[1] + yDist;
+ } else {
+ xIntersect = closestRoad[0] + xDist;
+ yIntersect = closestRoad[1];
+ }
+
+ for(var x = 0; x < Math.abs(xDist); x++) {
+ if(World.isTerrain(World.state.map[closestRoad[0] + (xDir*x)][yIntersect])) {
+ World.state.map[closestRoad[0] + (xDir*x)][yIntersect] = World.TILE.ROAD;
+ }
+ }
+ for(var y = 0; y < Math.abs(yDist); y++) {
+ if(World.isTerrain(World.state.map[xIntersect][closestRoad[1] + (yDir*y)])) {
+ World.state.map[xIntersect][closestRoad[1] + (yDir*y)] = World.TILE.ROAD;
+ }
+ }
+ World.drawMap();
+ },
+
+ updateSupplies: function() {
+ var supplies = $('div#bagspace-world > div');
+
+ if(!Path.outfit) {
+ Path.outfit = {};
+ }
+
+ // Add water
+ var water = $('div#supply_water');
+ if(World.water > 0 && water.length === 0) {
+ water = World.createItemDiv('water', World.water);
+ water.prependTo(supplies);
+ } else if(World.water > 0) {
+ $('div#supply_water', supplies).text(_('water:{0}' , World.water));
+ } else {
+ water.remove();
+ }
+
+ var total = 0;
+ for(var k in Path.outfit) {
+ var item = $('div#supply_' + k.replace(' ', '-'), supplies);
+ var num = Path.outfit[k];
+ total += num * Path.getWeight(k);
+ if(num > 0 && item.length === 0) {
+ item = World.createItemDiv(k, num);
+ if(k == 'cured meat' && World.water > 0) {
+ item.insertAfter(water);
+ } else if(k == 'cured meat') {
+ item.prependTo(supplies);
+ } else {
+ item.appendTo(supplies);
+ }
+ } else if(num > 0) {
+ $('div#' + item.attr('id'), supplies).text(_(k) + ':' + num);
+ } else {
+ item.remove();
+ }
+ }
+
+ // Update label
+ var t = _('pockets');
+ if($SM.get('stores.rucksack', true) > 0) {
+ t = _('rucksack');
+ }
+ $('#backpackTitle').text(t);
+
+ // Update bagspace
+ $('#backpackSpace').text(_('free {0}/{1}', Math.floor(Path.getCapacity() - total) , Path.getCapacity()));
+ },
+
+ setWater: function(w) {
+ World.water = w;
+ if(World.water > World.getMaxWater()) {
+ World.water = World.getMaxWater();
+ }
+ World.updateSupplies();
+ },
+
+ setHp: function(hp) {
+ if(typeof hp == 'number' && !isNaN(hp)) {
+ World.health = hp;
+ if(World.health > World.getMaxHealth()) {
+ World.health = World.getMaxHealth();
+ }
+ $('#healthCounter').text(_('hp: {0}/{1}', World.health , World.getMaxHealth()));
+ }
+ },
+
+ createItemDiv: function(name, num) {
+ var div = $('
').attr('id', 'supply_' + name.replace(' ', '-'))
+ .addClass('supplyItem')
+ .text(_('{0}:{1}',_(name), num));
+
+ return div;
+ },
+
+ moveNorth: function() {
+ Engine.log('North');
+ if(World.curPos[1] > 0) World.move(World.NORTH);
+ },
+
+ moveSouth: function() {
+ Engine.log('South');
+ if(World.curPos[1] < World.RADIUS * 2) World.move(World.SOUTH);
+ },
+
+ moveWest: function() {
+ Engine.log('West');
+ if(World.curPos[0] > 0) World.move(World.WEST);
+ },
+
+ moveEast: function() {
+ Engine.log('East');
+ if(World.curPos[0] < World.RADIUS * 2) World.move(World.EAST);
+ },
+
+ move: function(direction) {
+ var oldTile = World.state.map[World.curPos[0]][World.curPos[1]];
+ World.curPos[0] += direction[0];
+ World.curPos[1] += direction[1];
+ World.narrateMove(oldTile, World.state.map[World.curPos[0]][World.curPos[1]]);
+ World.lightMap(World.curPos[0], World.curPos[1], World.state.mask);
+ World.drawMap();
+ World.doSpace();
+
+ // play random footstep
+ var randomFootstep = Math.floor(Math.random() * 5) + 1;
+ AudioEngine.playSound(AudioLibrary['FOOTSTEPS_' + randomFootstep]);
+
+ if(World.checkDanger()) {
+ if(World.danger) {
+ Notifications.notify(World, _('dangerous to be this far from the village without proper protection'));
+ } else {
+ Notifications.notify(World, _('safer here'));
+ }
+ }
+ },
+
+ keyDown: function(event) {
+ switch(event.which) {
+ case 38: // Up
+ case 87:
+ World.moveNorth();
+ break;
+ case 40: // Down
+ case 83:
+ World.moveSouth();
+ break;
+ case 37: // Left
+ case 65:
+ World.moveWest();
+ break;
+ case 39: // Right
+ case 68:
+ World.moveEast();
+ break;
+ default:
+ break;
+ }
+ },
+
+ swipeLeft: function(e) {
+ World.moveWest();
+ },
+
+ swipeRight: function(e) {
+ World.moveEast();
+ },
+
+ swipeUp: function(e) {
+ World.moveNorth();
+ },
+
+ swipeDown: function(e) {
+ World.moveSouth();
+ },
+
+ click: function(event) {
+ var map = $('#map'),
+ // measure clicks relative to the centre of the current location
+ centreX = map.offset().left + map.width() * World.curPos[0] / (World.RADIUS * 2),
+ centreY = map.offset().top + map.height() * World.curPos[1] / (World.RADIUS * 2),
+ clickX = event.pageX - centreX,
+ clickY = event.pageY - centreY;
+ if (clickX > clickY && clickX < -clickY) {
+ World.moveNorth();
+ }
+ if (clickX < clickY && clickX > -clickY) {
+ World.moveSouth();
+ }
+ if (clickX < clickY && clickX < -clickY) {
+ World.moveWest();
+ }
+ if (clickX > clickY && clickX > -clickY) {
+ World.moveEast();
+ }
+ },
+
+ checkDanger: function() {
+ World.danger = typeof World.danger == 'undefined' ? false: World.danger;
+ if(!World.danger) {
+ if($SM.get('stores["i armour"]', true) === 0 && World.getDistance() >= 8) {
+ World.danger = true;
+ return true;
+ }
+ if($SM.get('stores["s armour"]', true) === 0 && World.getDistance() >= 18) {
+ World.danger = true;
+ return true;
+ }
+ } else {
+ if(World.getDistance() < 8) {
+ World.danger = false;
+ return true;
+ }
+ if(World.getDistance < 18 && $SM.get('stores["i armour"]', true) > 0) {
+ World.danger = false;
+ return true;
+ }
+ }
+ return false;
+ },
+
+ useSupplies: function() {
+ World.foodMove++;
+ World.waterMove++;
+ // Food
+ var movesPerFood = World.MOVES_PER_FOOD;
+ movesPerFood *= $SM.hasPerk('slow metabolism') ? 2 : 1;
+ if(World.foodMove >= movesPerFood) {
+ World.foodMove = 0;
+ var num = Path.outfit['cured meat'];
+ num--;
+ if(num === 0) {
+ Notifications.notify(World, _('the meat has run out'));
+ } else if(num < 0) {
+ // Starvation! Hooray!
+ num = 0;
+ if(!World.starvation) {
+ Notifications.notify(World, _('starvation sets in'));
+ World.starvation = true;
+ } else {
+ $SM.set('character.starved', $SM.get('character.starved', true));
+ $SM.add('character.starved', 1);
+ if($SM.get('character.starved') >= 10 && !$SM.hasPerk('slow metabolism')) {
+ $SM.addPerk('slow metabolism');
+ }
+ World.die();
+ return false;
+ }
+ } else {
+ World.starvation = false;
+ World.setHp(World.health + World.meatHeal());
+ }
+ Path.outfit['cured meat'] = num;
+ }
+ // Water
+ var movesPerWater = World.MOVES_PER_WATER;
+ movesPerWater *= $SM.hasPerk('desert rat') ? 2 : 1;
+ if(World.waterMove >= movesPerWater) {
+ World.waterMove = 0;
+ var water = World.water;
+ water--;
+ if(water === 0) {
+ Notifications.notify(World, _('there is no more water'));
+ } else if(water < 0) {
+ water = 0;
+ if(!World.thirst) {
+ Notifications.notify(World, _('the thirst becomes unbearable'));
+ World.thirst = true;
+ } else {
+ $SM.set('character.dehydrated', $SM.get('character.dehydrated', true));
+ $SM.add('character.dehydrated', 1);
+ if($SM.get('character.dehydrated') >= 10 && !$SM.hasPerk('desert rat')) {
+ $SM.addPerk('desert rat');
+ }
+ World.die();
+ return false;
+ }
+ } else {
+ World.thirst = false;
+ }
+ World.setWater(water);
+ World.updateSupplies();
+ }
+ return true;
+ },
+
+ meatHeal: function() {
+ return World.MEAT_HEAL * ($SM.hasPerk('gastronome') ? 2 : 1);
+ },
+
+ medsHeal: function() {
+ return World.MEDS_HEAL;
+ },
+
+ hypoHeal: () => World.HYPO_HEAL,
+
+ checkFight: function() {
+ World.fightMove = typeof World.fightMove == 'number' ? World.fightMove : 0;
+ World.fightMove++;
+ if(World.fightMove > World.FIGHT_DELAY) {
+ var chance = World.FIGHT_CHANCE;
+ chance *= $SM.hasPerk('stealthy') ? 0.5 : 1;
+ if(Math.random() < chance) {
+ World.fightMove = 0;
+ Events.triggerFight();
+ }
+ }
+ },
+
+ doSpace: function() {
+ var curTile = World.state.map[World.curPos[0]][World.curPos[1]];
+
+ if(curTile == World.TILE.VILLAGE) {
+ World.goHome();
+ } else if(curTile === World.TILE.EXECUTIONER) {
+ const scene = World.state.executioner ? 'executioner-antechamber' : 'executioner-intro';
+ const sceneData = Events.Executioner[scene];
+ Events.startEvent(sceneData);
+ } else if(typeof World.LANDMARKS[curTile] != 'undefined') {
+ if(curTile != World.TILE.OUTPOST || !World.outpostUsed()) {
+ Events.startEvent(Events.Setpieces[World.LANDMARKS[curTile].scene]);
+ }
+ } else {
+ if(World.useSupplies()) {
+ World.checkFight();
+ }
+ }
+ },
+
+ getDistance: function(from, to) {
+ from = from || World.curPos;
+ to = to || World.VILLAGE_POS;
+ return Math.abs(from[0] - to[0]) + Math.abs(from[1] - to[1]);
+ },
+
+ getTerrain: function() {
+ return World.state.map[World.curPos[0]][World.curPos[1]];
+ },
+
+ getDamage: function(thing) {
+ return World.Weapons[thing].damage;
+ },
+
+ narrateMove: function(oldTile, newTile) {
+ var msg = null;
+ switch(oldTile) {
+ case World.TILE.FOREST:
+ switch(newTile) {
+ case World.TILE.FIELD:
+ msg = _("the trees yield to dry grass. the yellowed brush rustles in the wind.");
+ break;
+ case World.TILE.BARRENS:
+ msg = _("the trees are gone. parched earth and blowing dust are poor replacements.");
+ break;
+ }
+ break;
+ case World.TILE.FIELD:
+ switch(newTile) {
+ case World.TILE.FOREST:
+ msg = _("trees loom on the horizon. grasses gradually yield to a forest floor of dry branches and fallen leaves.");
+ break;
+ case World.TILE.BARRENS:
+ msg = _("the grasses thin. soon, only dust remains.");
+ break;
+ }
+ break;
+ case World.TILE.BARRENS:
+ switch(newTile) {
+ case World.TILE.FIELD:
+ msg = _("the barrens break at a sea of dying grass, swaying in the arid breeze.");
+ break;
+ case World.TILE.FOREST:
+ msg = _("a wall of gnarled trees rises from the dust. their branches twist into a skeletal canopy overhead.");
+ break;
+ }
+ break;
+ }
+ if(msg != null) {
+ Notifications.notify(World, msg);
+ }
+ },
+
+ newMask: function() {
+ var mask = new Array(World.RADIUS * 2 + 1);
+ for(var i = 0; i <= World.RADIUS * 2; i++) {
+ mask[i] = new Array(World.RADIUS * 2 + 1);
+ }
+ World.lightMap(World.RADIUS, World.RADIUS, mask);
+ return mask;
+ },
+
+ lightMap: function(x, y, mask) {
+ var r = World.LIGHT_RADIUS;
+ r *= $SM.hasPerk('scout') ? 2 : 1;
+ World.uncoverMap(x, y, r, mask);
+ return mask;
+ },
+
+ uncoverMap: function(x, y, r, mask) {
+ mask[x][y] = true;
+ for(var i = -r; i <= r; i++) {
+ for(var j = -r + Math.abs(i); j <= r - Math.abs(i); j++) {
+ if(y + j >= 0 && y + j <= World.RADIUS * 2 &&
+ x + i <= World.RADIUS * 2 &&
+ x + i >= 0) {
+ mask[x+i][y+j] = true;
+ }
+ }
+ }
+ },
+
+ testMap: function() {
+ if(!World.seenAll) {
+ var dark;
+ var mask = $SM.get('game.world.mask');
+ loop:
+ for(var i = 0; i < mask.length; i++) {
+ for(var j = 0; j < mask[i].length; j++) {
+ if(!mask[i][j]) {
+ dark = true;
+ break loop;
+ }
+ }
+ }
+ World.seenAll = !dark;
+ }
+ },
+
+ applyMap: function() {
+ if(!World.seenAll){
+ var x,y,mask = $SM.get('game.world.mask');
+ do {
+ x = Math.floor(Math.random() * (World.RADIUS * 2 + 1));
+ y = Math.floor(Math.random() * (World.RADIUS * 2 + 1));
+ } while (mask[x][y]);
+ World.uncoverMap(x, y, 5, mask);
+ }
+ World.testMap();
+ },
+
+ generateMap: function() {
+ var map = new Array(World.RADIUS * 2 + 1);
+ for(var i = 0; i <= World.RADIUS * 2; i++) {
+ map[i] = new Array(World.RADIUS * 2 + 1);
+ }
+ // The Village is always at the exact center
+ // Spiral out from there
+ map[World.RADIUS][World.RADIUS] = World.TILE.VILLAGE;
+ for(var r = 1; r <= World.RADIUS; r++) {
+ for(var t = 0; t < r * 8; t++) {
+ var x, y;
+ if(t < 2 * r) {
+ x = World.RADIUS - r + t;
+ y = World.RADIUS - r;
+ } else if(t < 4 * r) {
+ x = World.RADIUS + r;
+ y = World.RADIUS - (3 * r) + t;
+ } else if(t < 6 * r) {
+ x = World.RADIUS + (5 * r) - t;
+ y = World.RADIUS + r;
+ } else {
+ x = World.RADIUS - r;
+ y = World.RADIUS + (7 * r) - t;
+ }
+
+ map[x][y] = World.chooseTile(x, y, map);
+ }
+ }
+
+ // Place landmarks
+ for(var k in World.LANDMARKS) {
+ var landmark = World.LANDMARKS[k];
+ for(var l = 0; l < landmark.num; l++) {
+ var pos = World.placeLandmark(landmark.minRadius, landmark.maxRadius, k, map);
+ }
+ }
+
+ return map;
+ },
+
+ mapSearch: function(target,map,required){
+ var max = World.LANDMARKS[target].num;
+ if(!max){
+ // this restrict the research to numerable landmarks
+ return null;
+ }
+ // restrict research if only a fixed number (usually 1) is required
+ max = (required) ? Math.min(required,max) : max;
+ var index = 0;
+ var targets = [];
+ search: // label for coordinate research
+ for(var i = 0; i <= World.RADIUS * 2; i++){
+ for(var j = 0; j <= World.RADIUS * 2; j++){
+ if(map[i][j].charAt(0) === target){
+ // search result is stored as an object;
+ // items are listed as they appear in the map, tl-br
+ // each item has relative coordinates and a compass-type direction
+ targets[index] = {
+ x : i - World.RADIUS,
+ y : j - World.RADIUS,
+ };
+ index++;
+ if(index === max){
+ // optimisation: stop the research if maximum number of items has been reached
+ break search;
+ }
+ }
+ }
+ }
+ return targets;
+ },
+
+ compassDir: function(pos){
+ var dir = '';
+ var horz = pos.x < 0 ? 'west' : 'east';
+ var vert = pos.y < 0 ? 'north' : 'south';
+ if(Math.abs(pos.x) / 2 > Math.abs(pos.y)) {
+ dir = horz;
+ } else if(Math.abs(pos.y) / 2 > Math.abs(pos.x)){
+ dir = vert;
+ } else {
+ dir = vert + horz;
+ }
+ return dir;
+ },
+
+ placeLandmark: function(minRadius, maxRadius, landmark, map) {
+
+ var x = World.RADIUS, y = World.RADIUS;
+ while(!World.isTerrain(map[x][y])) {
+ var r = Math.floor(Math.random() * (maxRadius - minRadius)) + minRadius;
+ var xDist = Math.floor(Math.random() * r);
+ var yDist = r - xDist;
+ if(Math.random() < 0.5) xDist = -xDist;
+ if(Math.random() < 0.5) yDist = -yDist;
+ x = World.RADIUS + xDist;
+ if(x < 0) x = 0;
+ if(x > World.RADIUS * 2) x = World.RADIUS * 2;
+ y = World.RADIUS + yDist;
+ if(y < 0) y = 0;
+ if(y > World.RADIUS * 2) y = World.RADIUS * 2;
+ }
+ map[x][y] = landmark;
+ return [x, y];
+ },
+
+ isTerrain: function(tile) {
+ return tile == World.TILE.FOREST || tile == World.TILE.FIELD || tile == World.TILE.BARRENS;
+ },
+
+ chooseTile: function(x, y, map) {
+
+ var adjacent = [
+ y > 0 ? map[x][y-1] : null,
+ y < World.RADIUS * 2 ? map[x][y+1] : null,
+ x < World.RADIUS * 2 ? map[x+1][y] : null,
+ x > 0 ? map[x-1][y] : null
+ ];
+
+ var chances = {};
+ var nonSticky = 1;
+ var cur;
+ for(var i in adjacent) {
+ if(adjacent[i] == World.TILE.VILLAGE) {
+ // Village must be in a forest to maintain thematic consistency, yo.
+ return World.TILE.FOREST;
+ } else if(typeof adjacent[i] == 'string') {
+ cur = chances[adjacent[i]];
+ cur = typeof cur == 'number' ? cur : 0;
+ chances[adjacent[i]] = cur + World.STICKINESS;
+ nonSticky -= World.STICKINESS;
+ }
+ }
+ for(var t in World.TILE) {
+ var tile = World.TILE[t];
+ if(World.isTerrain(tile)) {
+ cur = chances[tile];
+ cur = typeof cur == 'number' ? cur : 0;
+ cur += World.TILE_PROBS[tile] * nonSticky;
+ chances[tile] = cur;
+ }
+ }
+
+ var list = [];
+ for(var j in chances) {
+ list.push(chances[j] + '' + j);
+ }
+ list.sort(function(a, b) {
+ var n1 = parseFloat(a.substring(0, a.length - 1));
+ var n2 = parseFloat(b.substring(0, b.length - 1));
+ return n2 - n1;
+ });
+
+ var c = 0;
+ var r = Math.random();
+ for(var l in list) {
+ var prob = list[l];
+ c += parseFloat(prob.substring(0,prob.length - 1));
+ if(r < c) {
+ return prob.charAt(prob.length - 1);
+ }
+ }
+
+ return World.TILE.BARRENS;
+ },
+
+ markVisited: function(x, y) {
+ World.state.map[x][y] = World.state.map[x][y] + '!';
+ },
+
+ drawMap: function() {
+ var map = $('#map');
+ if(map.length === 0) {
+ map = new $('
').attr('id', 'map').appendTo('#worldOuter');
+ // register click handler
+ map.click(World.click);
+ }
+ var mapString = "";
+ for(var j = 0; j <= World.RADIUS * 2; j++) {
+ for(var i = 0; i <= World.RADIUS * 2; i++) {
+ var ttClass = "";
+ if(i > World.RADIUS) {
+ ttClass += " left";
+ } else {
+ ttClass += " right";
+ }
+ if(j > World.RADIUS) {
+ ttClass += " top";
+ } else {
+ ttClass += " bottom";
+ }
+ if(World.curPos[0] == i && World.curPos[1] == j) {
+ mapString += '
@'+_('Wanderer')+'
';
+ } else if(World.state.mask[i][j]) {
+ var c = World.state.map[i][j];
+ switch(c) {
+ case World.TILE.VILLAGE:
+ mapString += '
' + c + ''+_('The Village')+'
';
+ break;
+ default:
+ if(typeof World.LANDMARKS[c] != 'undefined' && (c != World.TILE.OUTPOST || !World.outpostUsed(i, j))) {
+ mapString += '
' + c + '' + World.LANDMARKS[c].label + '
';
+ } else {
+ if(c.length > 1) {
+ c = c[0];
+ }
+ mapString += c;
+ }
+ break;
+ }
+ } else {
+ mapString += ' ';
+ }
+ }
+ mapString += '
';
+ }
+ map.html(mapString);
+ },
+
+ die: function() {
+ if(!World.dead) {
+ World.dead = true;
+ Engine.log('player death');
+ Engine.event('game event', 'death');
+ Engine.keyLock = true;
+ // Dead! Discard any world changes and go home
+ Notifications.notify(World, _('the world fades'));
+ World.state = null;
+ Path.outfit = {};
+ $SM.remove('outfit');
+ AudioEngine.playSound(AudioLibrary.DEATH);
+ $('#outerSlider').animate({opacity: '0'}, 600, 'linear', function() {
+ $('#outerSlider').css('left', '0px');
+ $('#locationSlider').css('left', '0px');
+ $('#storesContainer').css({'top': '0px', 'right': '0px'});
+ Engine.activeModule = Room;
+ $('div.headerButton').removeClass('selected');
+ Room.tab.addClass('selected');
+ Engine.setTimeout(function(){
+ Room.onArrival();
+ $('#outerSlider').animate({opacity:'1'}, 600, 'linear');
+ Button.cooldown($('#embarkButton'));
+ Engine.keyLock = false;
+ Engine.tabNavigation = true;
+ }, 2000, true);
+ });
+ }
+ },
+
+ goHome: function() {
+ // Home safe! Commit the changes.
+ $SM.setM('game.world', World.state);
+ World.testMap();
+
+ if(World.state.sulphurmine && $SM.get('game.buildings["sulphur mine"]', true) === 0) {
+ $SM.add('game.buildings["sulphur mine"]', 1);
+ Engine.event('progress', 'sulphur mine');
+ }
+ if(World.state.ironmine && $SM.get('game.buildings["iron mine"]', true) === 0) {
+ $SM.add('game.buildings["iron mine"]', 1);
+ Engine.event('progress', 'iron mine');
+ }
+ if(World.state.coalmine && $SM.get('game.buildings["coal mine"]', true) === 0) {
+ $SM.add('game.buildings["coal mine"]', 1);
+ Engine.event('progress', 'coal mine');
+ }
+ if(World.state.ship && !$SM.get('features.location.spaceShip')) {
+ Ship.init();
+ Engine.event('progress', 'ship');
+ }
+ if (World.state.executioner && !$SM.get('features.location.fabricator')) {
+ Fabricator.init();
+ Notifications.notify(null, _('builder knows the strange device when she sees it. takes it for herself real quick. doesn’t ask where it came from.'));
+ Engine.event('progress', 'fabricator');
+ }
+ World.redeemBlueprints();
+ World.state = null;
+
+ if(Path.outfit['cured meat'] > 0) {
+ Button.setDisabled($('#embarkButton'), false);
+ }
+
+ World.returnOutfit();
+
+ $('#outerSlider').animate({left: '0px'}, 300);
+ Engine.activeModule = Path;
+ Path.onArrival();
+ Engine.restoreNavigation = true;
+ },
+
+ redeemBlueprints: () => {
+ let redeemed = false;
+ const redeem = (blueprint, item) => {
+ if (Path.outfit[blueprint]) {
+ $SM.set(`character.blueprints['${item}']`, true);
+ delete Path.outfit[blueprint];
+ redeemed = true;
+ }
+ };
+
+ redeem('hypo blueprint', 'hypo');
+ redeem('kinetic armour blueprint', 'kinetic armour');
+ redeem('disruptor blueprint', 'disruptor');
+ redeem('plasma rifle blueprint', 'plasma rifle');
+ redeem('stim blueprint', 'stim');
+ redeem('glowstone blueprint', 'glowstone');
+
+ if (redeemed) {
+ Notifications.notify(null, 'blueprints feed into the fabricator data port. possibilities grow.');
+ }
+ },
+
+ returnOutfit: () => {
+ for(var k in Path.outfit) {
+ $SM.add('stores["'+k+'"]', Path.outfit[k]);
+ if(World.leaveItAtHome(k)) {
+ Path.outfit[k] = 0;
+ }
+ }
+ },
+
+ leaveItAtHome: function(thing) {
+ return thing != 'cured meat' && thing != 'bullets' && thing != 'energy cell' &&
+ thing != 'charm' && thing != 'medicine' && thing != 'stim' && thing != 'hypo' &&
+ typeof World.Weapons[thing] == 'undefined' && typeof Room.Craftables[thing] == 'undefined';
+ },
+
+ getMaxHealth: function() {
+ if($SM.get('stores["kinetic armour"]', true) > 0) {
+ return World.BASE_HEALTH + 75;
+ } else if($SM.get('stores["s armour"]', true) > 0) {
+ return World.BASE_HEALTH + 35;
+ } else if($SM.get('stores["i armour"]', true) > 0) {
+ return World.BASE_HEALTH + 15;
+ } else if($SM.get('stores["l armour"]', true) > 0) {
+ return World.BASE_HEALTH + 5;
+ }
+ return World.BASE_HEALTH;
+ },
+
+ getHitChance: function() {
+ if($SM.hasPerk('precise')) {
+ return World.BASE_HIT_CHANCE + 0.1;
+ }
+ return World.BASE_HIT_CHANCE;
+ },
+
+ getMaxWater: function() {
+
+ if($SM.get('stores["fluid recycler"]', true) > 0) {
+ return World.BASE_WATER + 100;
+ } else if($SM.get('stores["water tank"]', true) > 0) {
+ return World.BASE_WATER + 50;
+ } else if($SM.get('stores.cask', true) > 0) {
+ return World.BASE_WATER + 20;
+ } else if($SM.get('stores.waterskin', true) > 0) {
+ return World.BASE_WATER + 10;
+ }
+ return World.BASE_WATER;
+ },
+
+ outpostUsed: function(x, y) {
+ x = typeof x == 'number' ? x : World.curPos[0];
+ y = typeof y == 'number' ? y : World.curPos[1];
+ var used = World.usedOutposts[x + ',' + y];
+ return typeof used != 'undefined' && used === true;
+ },
+
+ useOutpost: function() {
+ Notifications.notify(null, _('water replenished'));
+ World.setWater(World.getMaxWater());
+ // Mark this outpost as used
+ World.usedOutposts[World.curPos[0] + ',' + World.curPos[1]] = true;
+ },
+
+ onArrival: function() {
+ Engine.tabNavigation = false;
+ // Clear the embark cooldown
+ Button.clearCooldown($('#embarkButton'));
+ Engine.keyLock = false;
+ // Explore in a temporary world-state. We'll commit the changes if you return home safe.
+ World.state = $.extend(true, {}, $SM.get('game.world'));
+ World.setWater(World.getMaxWater());
+ World.setHp(World.getMaxHealth());
+ World.foodMove = 0;
+ World.waterMove = 0;
+ World.starvation = false;
+ World.thirst = false;
+ World.usedOutposts = {};
+ World.curPos = World.copyPos(World.VILLAGE_POS);
+ World.drawMap();
+ World.setTitle();
+ AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_WORLD);
+ World.dead = false;
+ $('div#bagspace-world > div').empty();
+ World.updateSupplies();
+ $('#bagspace-world').width($('#map').width());
+ },
+
+ setTitle: function() {
+ document.title = _('A Barren World');
+ },
+
+ copyPos: function(pos) {
+ return [pos[0], pos[1]];
+ },
+
+ handleStateUpdates: function(e){
+
+ }
};