var Engine = { /* TODO *** MICHAEL IS A LAZY BASTARD AND DOES NOT WANT TO REFACTOR *** * Here is what he should be doing: * - All updating values (store numbers, incomes, etc...) should be objects that can register listeners to * value-change events. These events should be fired whenever a value (or group of values, I suppose) is updated. * That would be so elegant and awesome. */ SITE_URL: encodeURIComponent("http://adarkroom.doublespeakgames.com"), MAX_STORE: 99999999999999, SAVE_DISPLAY: 30 * 1000, Perks: { 'boxer': { desc: 'punches do more damage', notify: 'learned to throw punches with purpose' }, 'martial artist': { desc: 'punches do even more damage.', notify: 'learned to fight quite effectively without weapons' }, 'unarmed master': { desc: 'punch twice as fast, and with even more force', notify: 'learned to strike faster without weapons' }, 'barbarian': { desc: 'melee weapons deal more damage', notify: 'learned to swing weapons with force' }, 'slow metabolism': { desc: 'go twice as far without eating', notify: 'learned how to ignore the hunger' }, 'desert rat': { desc: 'go twice as far without drinking', notify: 'learned to love the dry air' }, 'evasive': { desc: 'dodge attacks more effectively', notify: "learned to be where they're not" }, 'precise': { desc: 'land blows more often', notify: 'learned to predict their movement' }, 'scout': { desc: 'see farther', notify: 'learned to look ahead' }, 'stealthy': { desc: 'better avoid conflict in the wild', notify: 'learned how not to be seen' }, 'gastronome': { desc: 'restore more health when eating', notify: 'learned to make the most of food' } }, options: { state: null, debug: false, log: false }, init: function(options) { this.options = $.extend( this.options, options ); this._debug = this.options.debug; this._log = this.options.log; // Check for HTML5 support if(!Engine.browserValid()) { window.location = 'browserWarning.html'; } // Check for mobile if(Engine.isMobile()) { window.location = 'mobileWarning.html'; } if(this.options.state != null) { window.State = this.options.state; } else { Engine.loadGame(); } $('
').attr('id', 'locationSlider').appendTo('#main'); $('') .addClass('deleteSave') .text('restart.') .click(Engine.confirmDelete) .appendTo('body'); $('
') .addClass('share') .text('share.') .click(Engine.share) .appendTo('body'); // Register keypress handlers $('body').off('keydown').keydown(Engine.keyDown); $('body').off('keyup').keyup(Engine.keyUp); Notifications.init(); Events.init(); Room.init(); if(Engine.storeAvailable('wood')) { Outside.init(); } if(Engine.getStore('compass') > 0) { Path.init(); } if(State.ship) { Ship.init(); } Engine.travelTo(Room); }, browserValid: function() { return location.search.indexOf('ignorebrowser=true') >= 0 || ( typeof Storage != 'undefined' && !oldIE); }, isMobile: function() { return location.search.indexOf('ignorebrowser=true') < 0 && /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); }, saveGame: function() { if(typeof Storage != 'undefined' && localStorage) { if(Engine._saveTimer != null) { clearTimeout(Engine._saveTimer); } if(typeof Engine._lastNotify == 'undefined' || Date.now() - Engine._lastNotify > Engine.SAVE_DISPLAY){ $('#saveNotify').css('opacity', 1).animate({opacity: 0}, 1000, 'linear'); Engine._lastNotify = Date.now(); } localStorage.gameState = JSON.stringify(State); } }, loadGame: function() { try { var savedState = JSON.parse(localStorage.gameState); if(savedState) { State = savedState; Engine.upgradeState(); Engine.log("loaded save!"); } } catch(e) { State = { version: 1.2, stores: {}, perks: {} }; Engine.event('progress', 'new game'); } }, upgradeState: function() { /* Use this function to make old * save games compatible with newer versions */ if(typeof State.version != 'number') { Engine.log('upgraded save to v1.0'); State.version = 1.0; } if(State.version == 1.0) { // v1.1 introduced the Lodge, so get rid of lodgeless hunters delete State.outside.workers.hunter; delete State.income.hunter; Engine.log('upgraded save to v1.1'); State.version = 1.1; } if(State.version == 1.1) { //v1.2 added the Swamp to the map, so add it to already generated maps if(State.world) { World.placeLandmark(15, World.RADIUS * 1.5, World.TILE.SWAMP, State.world.map); } Engine.log('upgraded save to v1.2'); State.version = 1.2; } }, event: function(cat, act) { if(typeof ga === 'function') { ga('send', 'event', cat, act); } }, confirmDelete: function() { Events.startEvent({ title: 'Restart?', scenes: { start: { text: ['restart the game?'], buttons: { 'yes': { text: 'yes', nextScene: 'end', onChoose: Engine.deleteSave }, 'no': { text: 'no', nextScene: 'end' } } } } }); }, deleteSave: function() { if(typeof Storage != 'undefined' && localStorage) { localStorage.clear(); } location.reload(); }, share: function() { Events.startEvent({ title: 'Share', scenes: { start: { text: ['bring your friends.'], buttons: { 'facebook': { text: 'facebook', nextScene: 'end', onChoose: function() { window.open('https://www.facebook.com/sharer/sharer.php?u=' + Engine.SITE_URL, 'sharer', 'width=626,height=436,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no'); } }, 'google': { text:'google+', nextScene: 'end', onChoose: function() { window.open('https://plus.google.com/share?url=' + Engine.SITE_URL, 'sharer', 'width=480,height=436,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no'); } }, 'twitter': { text: 'twitter', onChoose: function() { window.open('https://twitter.com/intent/tweet?text=A%20Dark%20Room&url=' + Engine.SITE_URL, 'sharer', 'width=660,height=260,location=no,menubar=no,resizable=no,scrollbars=yes,status=no,toolbar=no'); }, nextScene: 'end' }, 'reddit': { text: 'reddit', onChoose: function() { window.open('http://www.reddit.com/submit?url=' + Engine.SITE_URL, 'sharer', 'width=960,height=700,location=no,menubar=no,resizable=no,scrollbars=yes,status=no,toolbar=no'); }, nextScene: 'end' }, 'close': { text: 'close', nextScene: 'end' } } } } }, {width: '400px'}); }, // Gets a guid getGuid: function() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); return v.toString(16); }); }, activeModule: null, travelTo: function(module) { if(Engine.activeModule != module) { var currentIndex = Engine.activeModule ? $('.location').index(Engine.activeModule.panel) : 1; Engine.activeModule = module; $('div.headerButton').removeClass('selected'); module.tab.addClass('selected'); var slider = $('#locationSlider'); var panelIndex = $('.location').index(module.panel); var diff = Math.abs(panelIndex - currentIndex); slider.animate({left: -(panelIndex * 700) + 'px'}, 300 * diff); module.onArrival(); Notifications.printQueue(module); } }, addPerk: function(name) { if(!State.perks) { State.perks = {}; } State.perks[name] = true; Notifications.notify(null, Engine.Perks[name].notify); if(Engine.activeModule == Path) { Path.updatePerks(); } }, hasPerk: function(name) { return typeof State.perks == 'object' && State.perks[name] == true; }, setStore: function(name, number) { if(typeof State.stores == 'undefined') { State.stores = {}; } if(number > Engine.MAX_STORE) number = Engine.MAX_STORE; State.stores[name] = number; Room.updateStoresView(); Room.updateBuildButtons(); if(State.outside) { Outside.updateVillage(); } Engine.saveGame(); }, setStores: function(list) { if(typeof State.stores == 'undefined') { State.stores = {}; } for(k in list) { State.stores[k] = list[k] > Engine.MAX_STORE ? Engine.MAX_STORE : list[k]; } Room.updateStoresView(); Room.updateBuildButtons(); if(State.outside) { Outside.updateVillage(); } Engine.saveGame(); }, addStore: function(name, number) { if(typeof State.stores == 'undefined') { State.stores = {}; } var num = State.stores[name]; if(typeof num != 'number' || isNaN(num) || num < 0) num = 0; num += number; if(num > Engine.MAX_STORE) num = Engine.MAX_STORE; State.stores[name] = num; Room.updateStoresView(); Room.updateBuildButtons(); Outside.updateVillage(); if(Engine.activeModule == Path) { Path.updateOutfitting(); } Engine.saveGame(); }, addStores: function(list, ignoreCosts) { if(typeof State.stores == 'undefined') { State.stores = {}; } // Make sure any income costs can be paid if(!ignoreCosts) { for(k in list) { var num = State.stores[k]; if(typeof num != 'number' || isNaN(num) || num < 0) num = 0; if(num + list[k] < 0) { return false; } } } // Actually do the update for(k in list) { var num = State.stores[k]; if(typeof num != 'number') num = 0; num += list[k]; num = num < 0 ? 0 : num; num = num > Engine.MAX_STORE ? Engine.MAX_STORE : num; State.stores[k] = num; } Room.updateStoresView(); Room.updateBuildButtons(); Outside.updateVillage(); if(Engine.activeModule == Path) { Path.updateOutfitting(); } Engine.saveGame(); return true; }, storeAvailable: function(name) { return typeof State.stores[name] == 'number'; }, getStore: function(name) { if(typeof State.stores == 'undefined' || typeof State.stores[name] == 'undefined' ) { return 0; } return State.stores[name]; }, setIncome: function(source, options) { if(typeof State.income == 'undefined') { State.income = {}; } var existing = State.income[source]; if(typeof existing != 'undefined') { options.timeLeft = existing.timeLeft; } State.income[source] = options; }, getIncome: function(source) { if(typeof State.income == 'undefined') { State.income = {}; } var existing = State.income[source]; if(typeof existing != 'undefined') { return existing; } return {}; }, removeIncome: function(source) { if(State.income) { delete State.income[source]; } Room.updateIncomeView(); }, collectIncome: function() { if(typeof State.income != 'undefined' && Engine.activeModule != Space) { var changed = false; for(var source in State.income) { var income = State.income[source]; if(typeof income.timeLeft != 'number') { income.timeLeft = 0; } income.timeLeft--; if(income.timeLeft <= 0) { Engine.log('collection income from ' + source); if(source == 'thieves') { Engine.addStolen(income.stores); } changed = Engine.addStores(income.stores) || changed; if(typeof income.delay == 'number') { income.timeLeft = income.delay; } } } if(changed) { Room.updateStoresView(); Room.updateBuildButtons(); Engine.saveGame(); if(Events.activeEvent() != null) { Events.updateButtons(); } } } Engine._incomeTimeout = setTimeout(Engine.collectIncome, 1000); }, openPath: function() { Path.init(); Engine.event('progress', 'path'); Notifications.notify(Room, 'the compass points ' + World.dir); }, addStolen: function(stores) { if(!State.stolen) State.stolen = {}; for(var k in stores) { if(!State.stolen[k]) State.stolen[k] = 0; State.stolen[k] -= stores[k]; } }, startThieves: function() { State.thieves = 1; Engine.setIncome('thieves', { delay: 10, stores: { 'wood': -10, 'fur': -5, 'meat': -5 } }); Room.updateIncomeView(); }, num: function(name, craftable) { switch(craftable.type) { case 'good': case 'tool': case 'weapon': case 'upgrade': return Engine.getStore(name); case 'building': return Outside.numBuilding(name); } }, log: function(msg) { if(this._log) { console.log(msg); } }, updateSlider: function() { var slider = $('#locationSlider'); slider.width((slider.children().length * 700) + 'px'); }, updateOuterSlider: function() { var slider = $('#outerSlider'); slider.width((slider.children().length * 700) + 'px'); }, getIncomeMsg: function(num, delay) { return (num > 0 ? "+" : "") + num + " per " + delay + "s"; }, keyDown: function(e) { if(!Engine.keyPressed && !Engine.keyLock) { Engine.pressed = true; if(Engine.activeModule.keyDown) { Engine.activeModule.keyDown(e); } } return false; }, keyUp: function(e) { Engine.pressed = false; if(Engine.activeModule.keyUp) { Engine.activeModule.keyUp(e); } return false; } }; $(function() { Engine.init(); });