/** * Module that handles the random event system */ var Events = { _EVENT_TIME_RANGE: [3, 6], // range, in minutes _PANEL_FADE: 200, _FIGHT_SPEED: 100, _EAT_COOLDOWN: 5, _MEDS_COOLDOWN: 7, _LEAVE_COOLDOWN: 1, STUN_DURATION: 4000, BLINK_INTERVAL: false, init: function(options) { this.options = $.extend( this.options, options ); // Build the Event Pool Events.EventPool = [].concat( Events.Global, Events.Room, Events.Outside, Events.Marketing ); Events.eventStack = []; Events.scheduleNextEvent(); //subscribe to stateUpdates $.Dispatch('stateUpdate').subscribe(Events.handleStateUpdates); //check for stored delayed events Events.initDelay(); }, options: {}, // Nothing for now delayState: 'wait', activeScene: null, loadScene: function(name) { Engine.log('loading scene: ' + name); Events.activeScene = name; var scene = Events.activeEvent().scenes[name]; // onLoad if(scene.onLoad) { scene.onLoad(); } // Notify the scene change if(scene.notification) { Notifications.notify(null, scene.notification); } // Scene reward if(scene.reward) { $SM.addM('stores', scene.reward); } $('#description', Events.eventPanel()).empty(); $('#buttons', Events.eventPanel()).empty(); if(scene.combat) { Events.startCombat(scene); } else { Events.startStory(scene); } }, startCombat: function(scene) { Engine.event('game event', 'combat'); Events.fought = false; var desc = $('#description', Events.eventPanel()); $('
').text(scene.notification).appendTo(desc); // Draw pause button /* Disable for now, because it doesn't work and looks weird var pauseBox = $('
').attr('id', 'pauseButton').appendTo(desc); var pause = new Button.Button({ id: 'pause', text: '', cooldown: Events._PAUSE_COOLDOWN, click: Events.togglePause }).appendTo(pauseBox); $('').addClass('text').insertBefore(pause.children('.cooldown')); $('
').addClass('clear').appendTo(pauseBox); Events.setPause(pause, 'set'); Events.removePause(pause, 'set'); */ var fightBox = $('
').attr('id', 'fight').appendTo(desc); // Draw the wanderer Events.createFighterDiv('@', World.health, World.getMaxHealth()).attr('id', 'wanderer').appendTo(fightBox); // Draw the enemy Events.createFighterDiv(scene.chara, scene.health, scene.health).attr('id', 'enemy').appendTo(fightBox); // Draw the action buttons var btns = $('#buttons', Events.eventPanel()); var attackBtns = $('
').appendTo(btns).attr('id','attackButtons'); var numWeapons = 0; for(var k in World.Weapons) { var weapon = World.Weapons[k]; if(typeof Path.outfit[k] == 'number' && Path.outfit[k] > 0) { if(typeof weapon.damage != 'number' || weapon.damage === 0) { // Weapons that deal no damage don't count numWeapons--; } else if(weapon.cost){ for(var c in weapon.cost) { var num = weapon.cost[c]; if(typeof Path.outfit[c] != 'number' || Path.outfit[c] < num) { // Can't use this weapon, so don't count it numWeapons--; } } } numWeapons++; Events.createAttackButton(k).appendTo(attackBtns); } } if(numWeapons === 0) { // No weapons? You can punch stuff! Events.createAttackButton('fists').prependTo(attackBtns); } $('
').addClass('clear').appendTo(attackBtns); var healBtns = $('
').appendTo(btns).attr('id','healButtons'); Events.createEatMeatButton().appendTo(healBtns); if((Path.outfit['medicine'] || 0) !== 0) { Events.createUseMedsButton().appendTo(healBtns); } $('
').addClass('clear').appendTo(healBtns); Events.setHeal(healBtns); // Set up the enemy attack timer Events._enemyAttackTimer = Engine.setInterval(Events.enemyAttack, scene.attackDelay * 1000); }, setPause: function(btn, state){ if(!btn) { btn = $('#pause'); } var event = btn.closest('#event'); var string, log; if(state == 'set') { string = 'start.'; log = 'loaded'; } else { string = 'resume.'; log = 'paused'; } btn.children('.text').first().text( _(string) ); Events.paused = (state == 'auto') ? 'auto' : true; event.addClass('paused'); Button.clearCooldown(btn); $('#buttons').find('.button').each(function(i){ if($(this).data('onCooldown')){ $(this).children('.cooldown').stop(true,false); } }); Engine.log('fight '+ log +'.'); }, removePause: function(btn, state){ if(!btn) { btn = $('#pause'); } var event = btn.closest('#event'); var log, time, target; if(state == 'auto' && Events.paused != 'auto') { return; } switch(state){ case 'set': Button.cooldown(btn, Events._LEAVE_COOLDOWN); log = 'started'; time = Events._LEAVE_COOLDOWN * 1000; target = $(); break; case 'end': Button.setDisabled(btn, true); log = 'ended'; time = Events._FIGHT_SPEED; target = $(); break; case 'auto': Button.cooldown(btn); /* falls through */ default: log = 'resumed'; time = Events._PAUSE_COOLDOWN * 1000; target = $('#buttons').find('.button'); break; } Engine.setTimeout(function(){ btn.children('.text').first().text( _('pause.') ); Events.paused = false; event.removeClass('paused'); target.each(function(i){ if($(this).data('onCooldown')){ Button.cooldown($(this), 'pause'); } }); Engine.log('Event '+ log); }, time); }, togglePause: function(btn, auto){ if(!btn) { btn = $('#pause'); } if((auto) && (document.hasFocus() == !Events.paused)) { return; } var f = (Events.paused) ? Events.removePause : Events.setPause; var state = (auto) ? 'auto' : false; f(btn, state); }, createEatMeatButton: function(cooldown) { if (cooldown == null) { cooldown = Events._EAT_COOLDOWN; } var btn = new Button.Button({ id: 'eat', text: _('eat meat'), cooldown: cooldown, click: Events.eatMeat, cost: { 'cured meat': 1 } }); if(Path.outfit['cured meat'] === 0) { Button.setDisabled(btn, true); } return btn; }, createUseMedsButton: function(cooldown) { if (cooldown == null) { cooldown = Events._MEDS_COOLDOWN; } var btn = new Button.Button({ id: 'meds', text: _('use meds'), cooldown: cooldown, click: Events.useMeds, cost: { 'medicine': 1 } }); if((Path.outfit['medicine'] || 0) === 0) { Button.setDisabled(btn, true); } return btn; }, createAttackButton: function(weaponName) { var weapon = World.Weapons[weaponName]; var cd = weapon.cooldown; if(weapon.type == 'unarmed') { if($SM.hasPerk('unarmed master')) { cd /= 2; } } var btn = new Button.Button({ id: 'attack_' + weaponName.replace(' ', '-'), text: weapon.verb, cooldown: cd, click: Events.useWeapon, cost: weapon.cost }); if(typeof weapon.damage == 'number' && weapon.damage > 0) { btn.addClass('weaponButton'); } for(var k in weapon.cost) { if(typeof Path.outfit[k] != 'number' || Path.outfit[k] < weapon.cost[k]) { Button.setDisabled(btn, true); break; } } return btn; }, drawFloatText: function(text, parent) { $('
').text(text).addClass('damageText').appendTo(parent).animate({ 'bottom': '50px', 'opacity': '0' }, 300, 'linear', function() { $(this).remove(); }); }, setHeal: function(healBtns) { if(!healBtns){ healBtns = $('#healButtons'); } healBtns = healBtns.children('.button'); var canHeal = (World.health < World.getMaxHealth()); healBtns.each(function(i){ Button.setDisabled($(this), !canHeal); }); return canHeal; }, doHeal: function(healing, cured, btn) { if(Path.outfit[healing] > 0) { Path.outfit[healing]--; World.updateSupplies(); if(Path.outfit[healing] === 0) { Button.setDisabled(btn, true); } var hp = World.health + cured; hp = Math.min(World.getMaxHealth(),hp); World.setHp(hp); Events.setHeal(); if(Events.activeEvent()) { var w = $('#wanderer'); w.data('hp', hp); Events.updateFighterDiv(w); Events.drawFloatText('+' + cured, '#wanderer .hp'); var takeETbutton = Events.setTakeAll(); Events.canLeave(takeETbutton); } } }, eatMeat: function(btn) { Events.doHeal('cured meat', World.meatHeal(), btn); AudioEngine.playSound(AudioLibrary.EAT_MEAT); }, useMeds: function(btn) { Events.doHeal('medicine', World.medsHeal(), btn); AudioEngine.playSound(AudioLibrary.USE_MEDS); }, useWeapon: function(btn) { if(Events.activeEvent()) { var weaponName = btn.attr('id').substring(7).replace('-', ' '); var weapon = World.Weapons[weaponName]; if(weapon.type == 'unarmed') { if(!$SM.get('character.punches')) $SM.set('character.punches', 0); $SM.add('character.punches', 1); if($SM.get('character.punches') == 50 && !$SM.hasPerk('boxer')) { $SM.addPerk('boxer'); } else if($SM.get('character.punches') == 150 && !$SM.hasPerk('martial artist')) { $SM.addPerk('martial artist'); } else if($SM.get('character.punches') == 300 && !$SM.hasPerk('unarmed master')) { $SM.addPerk('unarmed master'); } } if(weapon.cost) { var mod = {}; var out = false; for(var k in weapon.cost) { if(typeof Path.outfit[k] != 'number' || Path.outfit[k] < weapon.cost[k]) { return; } mod[k] = -weapon.cost[k]; if(Path.outfit[k] - weapon.cost[k] < weapon.cost[k]) { out = true; } } for(var m in mod) { Path.outfit[m] += mod[m]; } if(out) { Button.setDisabled(btn, true); var validWeapons = false; $('.weaponButton').each(function(){ if(!Button.isDisabled($(this)) && $(this).attr('id') != 'attack_fists') { validWeapons = true; return false; } }); if(!validWeapons) { // enable or create the punch button var fists = $('#attack_fists'); if(fists.length === 0) { Events.createAttackButton('fists').prependTo('#buttons', Events.eventPanel()); } else { Button.setDisabled(fists, false); } } } World.updateSupplies(); } var dmg = -1; if(Math.random() <= World.getHitChance()) { dmg = weapon.damage; if(typeof dmg == 'number') { if(weapon.type == 'unarmed' && $SM.hasPerk('boxer')) { dmg *= 2; } if(weapon.type == 'unarmed' && $SM.hasPerk('martial artist')) { dmg *= 3; } if(weapon.type == 'unarmed' && $SM.hasPerk('unarmed master')) { dmg *= 2; } if(weapon.type == 'melee' && $SM.hasPerk('barbarian')) { dmg = Math.floor(dmg * 1.5); } } } var attackFn = weapon.type == 'ranged' ? Events.animateRanged : Events.animateMelee; // play variation audio for weapon type var r = Math.floor(Math.random() * 2) + 1; switch (weapon.type) { case 'unarmed': AudioEngine.playSound(AudioLibrary['WEAPON_UNARMED_' + r]); break; case 'melee': AudioEngine.playSound(AudioLibrary['WEAPON_MELEE_' + r]); break; case 'ranged': AudioEngine.playSound(AudioLibrary['WEAPON_RANGED_' + r]); break; } attackFn($('#wanderer'), dmg, function() { if($('#enemy').data('hp') <= 0 && !Events.won) { // Success! Events.winFight(); } }); } }, damage: function(fighter, enemy, dmg, type) { var enemyHp = enemy.data('hp'); var msg = ""; if(typeof dmg == 'number') { if(dmg < 0) { msg = _('miss'); dmg = 0; } else { msg = '-' + dmg; enemyHp = ((enemyHp - dmg) < 0) ? 0 : (enemyHp - dmg); enemy.data('hp', enemyHp); if(fighter.attr('id') == 'enemy') { World.setHp(enemyHp); Events.setHeal(); } Events.updateFighterDiv(enemy); // play variation audio for weapon type var r = Math.floor(Math.random() * 2) + 1; switch (type) { case 'unarmed': AudioEngine.playSound(AudioLibrary['WEAPON_UNARMED_' + r]); break; case 'melee': AudioEngine.playSound(AudioLibrary['WEAPON_MELEE_' + r]); break; case 'ranged': AudioEngine.playSound(AudioLibrary['WEAPON_RANGED_' + r]); break; } } } else { if(dmg == 'stun') { msg = _('stunned'); enemy.data('stunned', Events.STUN_DURATION); } } Events.drawFloatText(msg, $('.hp', enemy)); }, animateMelee: function(fighter, dmg, callback) { var start, end, enemy; if(fighter.attr('id') == 'wanderer') { start = {'left': '50%'}; end = {'left': '25%'}; enemy = $('#enemy'); } else { start = {'right': '50%'}; end = {'right': '25%'}; enemy = $('#wanderer'); } fighter.stop(true, true).animate(start, Events._FIGHT_SPEED, function() { Events.damage(fighter, enemy, dmg, 'melee'); $(this).animate(end, Events._FIGHT_SPEED, callback); }); }, animateRanged: function(fighter, dmg, callback) { var start, end, enemy; if(fighter.attr('id') == 'wanderer') { start = {'left': '25%'}; end = {'left': '50%'}; enemy = $('#enemy'); } else { start = {'right': '25%'}; end = {'right': '50%'}; enemy = $('#wanderer'); } $('
').css(start).addClass('bullet').text('o').appendTo('#description') .animate(end, Events._FIGHT_SPEED * 2, 'linear', function() { Events.damage(fighter, enemy, dmg, 'ranged'); $(this).remove(); if(typeof callback == 'function') { callback(); } }); }, enemyAttack: function() { // Events.togglePause($('#pause'),'auto'); var scene = Events.activeEvent().scenes[Events.activeScene]; if(!$('#enemy').data('stunned')) { var toHit = scene.hit; toHit *= $SM.hasPerk('evasive') ? 0.8 : 1; var dmg = -1; if(Math.random() <= toHit) { dmg = scene.damage; } var attackFn = scene.ranged ? Events.animateRanged : Events.animateMelee; attackFn($('#enemy'), dmg, function() { if($('#wanderer').data('hp') <= 0) { // Failure! clearTimeout(Events._enemyAttackTimer); Events.endEvent(); World.die(); AudioEngine.playSound(AudioLibrary.LOSE_FIGHT); } }); } }, endFight: function() { Events.fought = true; clearTimeout(Events._enemyAttackTimer); Events.removePause($('#pause'), 'end'); }, winFight: function() { Engine.setTimeout(function() { if(Events.fought) { return; } Events.endFight(); // AudioEngine.playSound(AudioLibrary.WIN_FIGHT); $('#enemy').animate({opacity: 0}, 300, 'linear', function() { Engine.setTimeout(function() { var scene = Events.activeEvent().scenes[Events.activeScene]; var leaveBtn = false; var desc = $('#description', Events.eventPanel()); var btns = $('#buttons', Events.eventPanel()); desc.empty(); btns.empty(); $('
').text(scene.deathMessage).appendTo(desc); var takeETbtn = Events.drawLoot(scene.loot); var exitBtns = $('
').appendTo(btns).attr('id','exitButtons'); if(scene.buttons) { // Draw the buttons leaveBtn = Events.drawButtons(scene); } else { leaveBtn = new Button.Button({ id: 'leaveBtn', cooldown: Events._LEAVE_COOLDOWN, click: function() { if(scene.nextScene && scene.nextScene != 'end') { Events.loadScene(scene.nextScene); } else { Events.endEvent(); } }, text: _('leave') }); Button.cooldown(leaveBtn.appendTo(exitBtns)); var healBtns = $('
').appendTo(btns).attr('id','healButtons'); Events.createEatMeatButton(0).appendTo(healBtns); if((Path.outfit['medicine'] || 0) !== 0) { Events.createUseMedsButton(0).appendTo(healBtns); } $('
').addClass('clear').appendTo(healBtns); Events.setHeal(healBtns); } $('
').addClass('clear').appendTo(exitBtns); Events.allowLeave(takeETbtn, leaveBtn); }, 1000, true); }); }, Events._FIGHT_SPEED); }, loseFight: function(){ Events.endFight(); Events.endEvent(); World.die(); }, drawDrop:function(btn) { var name = btn.attr('id').substring(5).replace('-', ' '); var needsAppend = false; var weight = Path.getWeight(name); var freeSpace = Path.getFreeSpace(); if(weight > freeSpace) { // Draw the drop menu Engine.log('drop menu'); var dropMenu; if($('#dropMenu').length){ dropMenu = $('#dropMenu'); $('#dropMenu').empty(); } else { dropMenu = $('
').attr({'id': 'dropMenu', 'data-legend': _('drop:')}); needsAppend = true; } for(var k in Path.outfit) { if(name == k) continue; var itemWeight = Path.getWeight(k); if(itemWeight > 0) { var numToDrop = Math.ceil((weight - freeSpace) / itemWeight); if(numToDrop > Path.outfit[k]) { numToDrop = Path.outfit[k]; } if(numToDrop > 0) { var dropRow = $('
').attr('id', 'drop_' + k.replace(' ', '-')) .text(_(k) + ' x' + numToDrop) .data('thing', k) .data('num', numToDrop) .click(Events.dropStuff) .mouseenter(function(e){ e.stopPropagation(); }); dropRow.appendTo(dropMenu); } } } $('
').attr('id','no_drop') .text(_('nothing')) .mouseenter(function(e){ e.stopPropagation(); }) .click(function(e){ e.stopPropagation(); dropMenu.remove(); }) .appendTo(dropMenu); if(needsAppend){ dropMenu.appendTo(btn); } btn.one("mouseleave", function() { $('#dropMenu').remove(); }); } }, drawLootRow: function(name, num){ var id = name.replace(' ', '-'); var lootRow = $('
').attr('id','loot_' + id).data('item', name).addClass('lootRow'); var take = new Button.Button({ id: 'take_' + id, text: _(name) + ' [' + num + ']', click: Events.getLoot }).addClass('lootTake').data('numLeft', num).appendTo(lootRow); take.mouseenter(function(){ Events.drawDrop(take); }); var takeall = new Button.Button({ id: 'all_take_' + id, text: _('take') + ' ', click: Events.takeAll }).addClass('lootTakeAll').appendTo(lootRow); $('').insertBefore(takeall.children('.cooldown')); $('
').addClass('clear').appendTo(lootRow); return lootRow; }, drawLoot: function(lootList) { var desc = $('#description', Events.eventPanel()); var lootButtons = $('
').attr({'id': 'lootButtons', 'data-legend': _('take:')}); for(var k in lootList) { var loot = lootList[k]; if(Math.random() < loot.chance) { var num = Math.floor(Math.random() * (loot.max - loot.min)) + loot.min; var lootRow = Events.drawLootRow(k, num); lootRow.appendTo(lootButtons); } } lootButtons.appendTo(desc); var takeET = null; if(lootButtons.children().length > 0) { var takeETrow = $('
').addClass('takeETrow'); takeET = new Button.Button({ id: 'loot_takeEverything', text: '', cooldown: Events._LEAVE_COOLDOWN, click: Events.takeEverything }).appendTo(takeETrow); $('').insertBefore(takeET.children('.cooldown')); $('
').addClass('clear').appendTo(takeETrow); takeETrow.appendTo(lootButtons); Events.setTakeAll(lootButtons); } else { var noLoot = $('
').addClass('noLoot').text( _('nothing to take') ); noLoot.appendTo(lootButtons); } return takeET || false; }, setTakeAll: function(lootButtons){ if(!lootButtons) { lootButtons = $('#lootButtons'); } var canTakeSomething = false; var free = Path.getFreeSpace(); var takeETbutton = lootButtons.find('#loot_takeEverything'); lootButtons.children('.lootRow').each(function(i){ var name = $(this).data('item'); var take = $(this).children('.lootTake').first(); var takeAll = $(this).children('.lootTakeAll').first(); var numLeft = take.data('numLeft'); var num = Math.min(Math.floor(Path.getFreeSpace() / Path.getWeight(name)), numLeft); takeAll.data('numLeft', num); free -= numLeft * Path.getWeight(name); if(num > 0){ takeAll.removeClass('disabled'); canTakeSomething = true; } else { takeAll.addClass('disabled'); } if(num < numLeft){ takeAll.children('span').first().text(num); } else { takeAll.children('span').first().text(_('all')); } }); Button.setDisabled(takeETbutton, !canTakeSomething); takeETbutton.data('canTakeEverything', (free >= 0) ? true : false); return takeETbutton; }, allowLeave: function(takeETbtn, leaveBtn){ if(takeETbtn){ if(leaveBtn){ takeETbtn.data('leaveBtn', leaveBtn); } Events.canLeave(takeETbtn); } }, canLeave: function(btn){ var basetext = (btn.data('canTakeEverything')) ? _('take everything') : _('take all you can'); var textbox = btn.children('span'); var takeAndLeave = (btn.data('leaveBtn')) ? btn.data('canTakeEverything') : false; var text = _(basetext); if(takeAndLeave){ Button.cooldown(btn); text += _(' and ') + btn.data('leaveBtn').text(); } textbox.text( text ); btn.data('canLeave', takeAndLeave); }, dropStuff: function(e) { e.stopPropagation(); var btn = $(this); var target = btn.closest('.button'); var thing = btn.data('thing'); var id = 'take_' + thing.replace(' ', '-'); var num = btn.data('num'); var lootButtons = $('#lootButtons'); Engine.log('dropping ' + num + ' ' + thing); var lootBtn = $('#' + id, lootButtons); if(lootBtn.length > 0) { var curNum = lootBtn.data('numLeft'); curNum += num; lootBtn.text(_(thing) + ' [' + curNum + ']').data('numLeft', curNum); } else { var lootRow = Events.drawLootRow(thing, num); lootRow.insertBefore($('.takeETrow', lootButtons)); } Path.outfit[thing] -= num; Events.getLoot(target); World.updateSupplies(); }, getLoot: function(btn, stateSkipButtonSet) { var name = btn.attr('id').substring(5).replace('-', ' '); if(btn.data('numLeft') > 0) { var skipButtonSet = stateSkipButtonSet || false; var weight = Path.getWeight(name); var freeSpace = Path.getFreeSpace(); if(weight <= freeSpace) { var num = btn.data('numLeft'); num--; btn.data('numLeft', num); // #dropMenu gets removed by this. btn.text(_(name) + ' [' + num + ']'); if(num === 0) { Button.setDisabled(btn); btn.animate({'opacity':0}, 300, 'linear', function() { $(this).parent().remove(); if($('#lootButtons').children().length == 1) { $('#lootButtons').remove(); } }); } var curNum = Path.outfit[name]; curNum = typeof curNum == 'number' ? curNum : 0; curNum++; Path.outfit[name] = curNum; World.updateSupplies(); if(!skipButtonSet){ Events.setTakeAll(); } } if(!skipButtonSet){ Events.drawDrop(btn); } } }, takeAll: function(btn){ var target = $('#'+ btn.attr('id').substring(4)); for(var k = 0; k < btn.data('numLeft'); k++){ Events.getLoot(target, true); } Events.setTakeAll(); }, takeEverything: function(btn){ $('#lootButtons').children('.lootRow').each(function(i){ var target = $(this).children('.lootTakeAll').first(); if(!target.hasClass('disabled')){ Events.takeAll(target); } }); if(btn.data('canLeave')){ btn.data('leaveBtn').click(); } }, createFighterDiv: function(chara, hp, maxhp) { var fighter = $('
').addClass('fighter').text(_(chara)).data('hp', hp).data('maxHp', maxhp).data('refname',chara); $('
').addClass('hp').text(hp+'/'+maxhp).appendTo(fighter); return fighter; }, updateFighterDiv: function(fighter) { $('.hp', fighter).text(fighter.data('hp') + '/' + fighter.data('maxHp')); }, startStory: function(scene) { // Write the text var desc = $('#description', Events.eventPanel()); var leaveBtn = false; for(var i in scene.text) { $('
').text(scene.text[i]).appendTo(desc); } if(scene.textarea != null) { var ta = $('