/**
* Module that registers the outdoors functionality
*/
var Outside = {
name: _("Outside"),
_STORES_OFFSET: 0,
_GATHER_DELAY: 60,
_TRAPS_DELAY: 90,
_POP_DELAY: [0.5, 3],
_HUT_ROOM: 4,
_INCOME: {
'gatherer': {
name: _('gatherer'),
delay: 10,
stores: {
'wood': 1
}
},
'hunter': {
name: _('hunter'),
delay: 10,
stores: {
'fur': 0.5,
'meat': 0.5
}
},
'trapper': {
name: _('trapper'),
delay: 10,
stores: {
'meat': -1,
'bait': 1
}
},
'tanner': {
name: _('tanner'),
delay: 10,
stores: {
'fur': -5,
'leather': 1
}
},
'charcutier': {
name: _('charcutier'),
delay: 10,
stores: {
'meat': -5,
'wood': -5,
'cured meat': 1
}
},
'iron miner': {
name: _('iron miner'),
delay: 10,
stores: {
'cured meat': -1,
'iron': 1
}
},
'coal miner': {
name: _('coal miner'),
delay: 10,
stores: {
'cured meat': -1,
'coal': 1
}
},
'sulphur miner': {
name: _('sulphur miner'),
delay: 10,
stores: {
'cured meat': -1,
'sulphur': 1
}
},
'steelworker': {
name: _('steelworker'),
delay: 10,
stores: {
'iron': -1,
'coal': -1,
'steel': 1
}
},
'armourer': {
name: _('armourer'),
delay: 10,
stores: {
'steel': -1,
'sulphur': -1,
'bullets': 1
}
}
},
TrapDrops: [
{
rollUnder: 0.5,
name: 'fur',
message: _('scraps of fur')
},
{
rollUnder: 0.75,
name: 'meat',
message: _('bits of meat')
},
{
rollUnder: 0.85,
name: 'scales',
message: _('strange scales')
},
{
rollUnder: 0.93,
name: 'teeth',
message: _('scattered teeth')
},
{
rollUnder: 0.995,
name: 'cloth',
message: _('tattered cloth')
},
{
rollUnder: 1.0,
name: 'charm',
message: _('a crudely made charm')
}
],
init: function(options) {
this.options = $.extend(
this.options,
options
);
if(Engine._debug) {
this._GATHER_DELAY = 0;
this._TRAPS_DELAY = 0;
}
// Create the outside tab
this.tab = Header.addLocation(_("A Silent Forest"), "outside", Outside);
// Create the Outside panel
this.panel = $('
').attr('id', "outsidePanel")
.addClass('location')
.appendTo('div#locationSlider');
//subscribe to stateUpdates
$.Dispatch('stateUpdate').subscribe(Outside.handleStateUpdates);
if(typeof $SM.get('features.location.outside') == 'undefined') {
$SM.set('features.location.outside', true);
if(!$SM.get('game.buildings')) $SM.set('game.buildings', {});
if(!$SM.get('game.population')) $SM.set('game.population', 0);
if(!$SM.get('game.workers')) $SM.set('game.workers', {});
}
this.updateVillage();
Outside.updateWorkersView();
Outside.updateVillageIncome();
Engine.updateSlider();
// Create the gather button
new Button.Button({
id: 'gatherButton',
text: _("gather wood"),
click: Outside.gatherWood,
cooldown: Outside._GATHER_DELAY,
width: '80px'
}).appendTo('div#outsidePanel');
Outside.updateTrapButton();
},
getMaxPopulation: function() {
return $SM.get('game.buildings["hut"]', true) * Outside._HUT_ROOM;
},
increasePopulation: function() {
var space = Outside.getMaxPopulation() - $SM.get('game.population');
if(space > 0) {
var num = Math.floor(Math.random()*(space/2) + space/2);
if(num === 0) num = 1;
if(num == 1) {
Notifications.notify(null, _('a stranger arrives in the night'));
} else if(num < 5) {
Notifications.notify(null, _('a weathered family takes up in one of the huts.'));
} else if(num < 10) {
Notifications.notify(null, _('a small group arrives, all dust and bones.'));
} else if(num < 30) {
Notifications.notify(null, _('a convoy lurches in, equal parts worry and hope.'));
} else {
Notifications.notify(null, _("the town's booming. word does get around."));
}
Engine.log('population increased by ' + num);
$SM.add('game.population', num);
}
Outside.schedulePopIncrease();
},
killVillagers: function(num) {
$SM.add('game.population', num * -1);
if($SM.get('game.population') < 0) {
$SM.set('game.population', 0);
}
var remaining = Outside.getNumGatherers();
if(remaining < 0) {
var gap = -remaining;
for(var k in $SM.get('game.workers')) {
var numWorkers = $SM.get('game.workers["'+k+'"]');
if(numWorkers < gap) {
gap -= numWorkers;
$SM.set('game.workers["'+k+'"]', 0);
} else {
$SM.add('game.workers["'+k+'"]', gap * -1);
break;
}
}
}
},
destroyHuts: function(num, allowEmpty) {
var dead = 0;
for(var i = 0; i < num; i++){
var population = $SM.get('game.population', true);
var rate = population / Outside._HUT_ROOM;
var full = Math.floor(rate);
// by default this is used to destroy full or half-full huts
// pass allowEmpty to include empty huts in the armageddon
var huts = (allowEmpty) ? $SM.get('game.buildings["hut"]', true) : Math.ceil(rate);
if(!huts) {
break;
}
// random can be 0 but not 1; however, 0 as a target is useless
var target = Math.floor(Math.random() * huts) + 1;
var inhabitants = 0;
if(target <= full){
inhabitants = Outside._HUT_ROOM;
} else if(target == full + 1){
inhabitants = population % Outside._HUT_ROOM;
}
$SM.set('game.buildings["hut"]', ($SM.get('game.buildings["hut"]') - 1));
if(inhabitants){
Outside.killVillagers(inhabitants);
dead += inhabitants;
}
}
// this method returns the total number of victims, for further actions
return dead;
},
schedulePopIncrease: function() {
var nextIncrease = Math.floor(Math.random()*(Outside._POP_DELAY[1] - Outside._POP_DELAY[0])) + Outside._POP_DELAY[0];
Engine.log('next population increase scheduled in ' + nextIncrease + ' minutes');
Outside._popTimeout = Engine.setTimeout(Outside.increasePopulation, nextIncrease * 60 * 1000);
},
updateWorkersView: function() {
var workers = $('div#workers');
// If our population is 0 and we don't already have a workers view,
// there's nothing to do here.
if(!workers.length && $SM.get('game.population') === 0) return;
var needsAppend = false;
if(workers.length === 0) {
needsAppend = true;
workers = $('
').attr('id', 'workers').css('opacity', 0);
}
var numGatherers = $SM.get('game.population');
var gatherer = $('div#workers_row_gatherer', workers);
for(var k in $SM.get('game.workers')) {
var lk = _(k);
var workerCount = $SM.get('game.workers["'+k+'"]');
var row = $('div#workers_row_' + k.replace(' ', '-'), workers);
if(row.length === 0) {
row = Outside.makeWorkerRow(k, workerCount);
var curPrev = null;
workers.children().each(function(i) {
var child = $(this);
var cName = child.children('.row_key').text();
if(cName != 'gatherer') {
if(cName < lk) {
curPrev = child.attr('id');
}
}
});
if(curPrev == null && gatherer.length === 0) {
row.prependTo(workers);
} else if(curPrev == null) {
row.insertAfter(gatherer);
} else {
row.insertAfter(workers.find('#'+ curPrev));
}
} else {
$('div#' + row.attr('id') + ' > div.row_val > span', workers).text(workerCount);
}
numGatherers -= workerCount;
if(workerCount === 0) {
$('.dnBtn', row).addClass('disabled');
$('.dnManyBtn', row).addClass('disabled');
} else {
$('.dnBtn', row).removeClass('disabled');
$('.dnManyBtn', row).removeClass('disabled');
}
}
if(gatherer.length === 0) {
gatherer = Outside.makeWorkerRow('gatherer', numGatherers);
gatherer.prependTo(workers);
} else {
$('div#workers_row_gatherer > div.row_val > span', workers).text(numGatherers);
}
if(numGatherers === 0) {
$('.upBtn', '#workers').addClass('disabled');
$('.upManyBtn', '#workers').addClass('disabled');
} else {
$('.upBtn', '#workers').removeClass('disabled');
$('.upManyBtn', '#workers').removeClass('disabled');
}
if(needsAppend && workers.children().length > 0) {
workers.appendTo('#outsidePanel').animate({opacity:1}, 300, 'linear');
}
},
getNumGatherers: function() {
var num = $SM.get('game.population');
for(var k in $SM.get('game.workers')) {
num -= $SM.get('game.workers["'+k+'"]');
}
return num;
},
makeWorkerRow: function(key, num) {
name = Outside._INCOME[key].name;
if(!name) name = key;
var row = $('
')
.attr('key', key)
.attr('id', 'workers_row_' + key.replace(' ','-'))
.addClass('workerRow');
$('
').addClass('row_key').text(name).appendTo(row);
var val = $('
').addClass('row_val').appendTo(row);
$('
').text(num).appendTo(val);
if(key != 'gatherer') {
$('').addClass('upBtn').appendTo(val).click([1], Outside.increaseWorker);
$('
').addClass('dnBtn').appendTo(val).click([1], Outside.decreaseWorker);
$('
').addClass('upManyBtn').appendTo(val).click([10], Outside.increaseWorker);
$('
').addClass('dnManyBtn').appendTo(val).click([10], Outside.decreaseWorker);
}
$('
').addClass('clear').appendTo(row);
var tooltip = $('
').addClass('tooltip bottom right').appendTo(row);
var income = Outside._INCOME[key];
for(var s in income.stores) {
var r = $('
').addClass('storeRow');
$('
').addClass('row_key').text(_(s)).appendTo(r);
$('
').addClass('row_val').text(Engine.getIncomeMsg(income.stores[s], income.delay)).appendTo(r);
r.appendTo(tooltip);
}
return row;
},
increaseWorker: function(btn) {
var worker = $(this).closest('.workerRow').attr('key');
if(Outside.getNumGatherers() > 0) {
var increaseAmt = Math.min(Outside.getNumGatherers(), btn.data);
Engine.log('increasing ' + worker + ' by ' + increaseAmt);
$SM.add('game.workers["'+worker+'"]', increaseAmt);
}
},
decreaseWorker: function(btn) {
var worker = $(this).closest('.workerRow').attr('key');
if($SM.get('game.workers["'+worker+'"]') > 0) {
var decreaseAmt = Math.min($SM.get('game.workers["'+worker+'"]') || 0, btn.data);
Engine.log('decreasing ' + worker + ' by ' + decreaseAmt);
$SM.add('game.workers["'+worker+'"]', decreaseAmt * -1);
}
},
updateVillageRow: function(name, num, village) {
var id = 'building_row_' + name.replace(' ', '-');
var lname = _(name);
var row = $('div#' + id, village);
if(row.length === 0 && num > 0) {
row = $('
').attr('id', id).addClass('storeRow');
$('
').addClass('row_key').text(lname).appendTo(row);
$('
').addClass('row_val').text(num).appendTo(row);
$('
').addClass('clear').appendTo(row);
var curPrev = null;
village.children().each(function(i) {
var child = $(this);
if(child.attr('id') != 'population') {
var cName = child.children('.row_key').text();
if(cName < lname) {
curPrev = child.attr('id');
}
}
});
if(curPrev == null) {
row.prependTo(village);
} else {
row.insertAfter('#' + curPrev);
}
} else if(num > 0) {
$('div#' + row.attr('id') + ' > div.row_val', village).text(num);
} else if(num === 0) {
row.remove();
}
},
updateVillage: function(ignoreStores) {
var village = $('div#village');
var population = $('div#population');
var needsAppend = false;
if(village.length === 0) {
needsAppend = true;
village = $('
').attr('id', 'village').css('opacity', 0);
population = $('
').attr('id', 'population').appendTo(village);
}
for(var k in $SM.get('game.buildings')) {
if(k == 'trap') {
var numTraps = $SM.get('game.buildings["'+k+'"]');
var numBait = $SM.get('stores.bait', true);
var traps = numTraps - numBait;
traps = traps < 0 ? 0 : traps;
Outside.updateVillageRow(k, traps, village);
Outside.updateVillageRow('baited trap', numBait > numTraps ? numTraps : numBait, village);
} else {
if(Outside.checkWorker(k)) {
Outside.updateWorkersView();
}
Outside.updateVillageRow(k, $SM.get('game.buildings["'+k+'"]'), village);
}
}
/// TRANSLATORS : pop is short for population.
population.text(_('pop ') + $SM.get('game.population') + '/' + this.getMaxPopulation());
var hasPeeps;
if($SM.get('game.buildings["hut"]', true) === 0) {
hasPeeps = false;
village.attr('data-legend', _('forest'));
} else {
hasPeeps = true;
village.attr('data-legend', _('village'));
}
if(needsAppend && village.children().length > 1) {
village.prependTo('#outsidePanel');
village.animate({opacity:1}, 300, 'linear');
}
if(hasPeeps && typeof Outside._popTimeout == 'undefined') {
Outside.schedulePopIncrease();
}
this.setTitle();
if(!ignoreStores && Engine.activeModule === Outside && village.children().length > 1) {
$('#storesContainer').css({top: village.height() + 26 + Outside._STORES_OFFSET + 'px'});
}
},
checkWorker: function(name) {
var jobMap = {
'lodge': ['hunter', 'trapper'],
'tannery': ['tanner'],
'smokehouse': ['charcutier'],
'iron mine': ['iron miner'],
'coal mine': ['coal miner'],
'sulphur mine': ['sulphur miner'],
'steelworks': ['steelworker'],
'armoury' : ['armourer']
};
var jobs = jobMap[name];
var added = false;
if(typeof jobs == 'object') {
for(var i = 0, len = jobs.length; i < len; i++) {
var job = jobs[i];
if(typeof $SM.get('game.buildings["'+name+'"]') == 'number' &&
typeof $SM.get('game.workers["'+job+'"]') != 'number') {
Engine.log('adding ' + job + ' to the workers list');
$SM.set('game.workers["'+job+'"]', 0);
added = true;
}
}
}
return added;
},
updateVillageIncome: function() {
for(var worker in Outside._INCOME) {
var income = Outside._INCOME[worker];
var num = worker == 'gatherer' ? Outside.getNumGatherers() : $SM.get('game.workers["'+worker+'"]');
if(typeof num == 'number') {
var stores = {};
if(num < 0) num = 0;
var tooltip = $('.tooltip', 'div#workers_row_' + worker.replace(' ', '-'));
tooltip.empty();
var needsUpdate = false;
var curIncome = $SM.getIncome(worker);
for(var store in income.stores) {
stores[store] = income.stores[store] * num;
if(curIncome[store] != stores[store]) needsUpdate = true;
var row = $('
').addClass('storeRow');
$('
').addClass('row_key').text(_(store)).appendTo(row);
$('
').addClass('row_val').text(Engine.getIncomeMsg(stores[store], income.delay)).appendTo(row);
row.appendTo(tooltip);
}
if(needsUpdate) {
$SM.setIncome(worker, {
delay: income.delay,
stores: stores
});
}
}
}
Room.updateIncomeView();
},
updateTrapButton: function() {
var btn = $('div#trapsButton');
if($SM.get('game.buildings["trap"]', true) > 0) {
if(btn.length === 0) {
new Button.Button({
id: 'trapsButton',
text: _("check traps"),
click: Outside.checkTraps,
cooldown: Outside._TRAPS_DELAY,
width: '80px'
}).appendTo('div#outsidePanel');
} else {
Button.setDisabled(btn, false);
}
} else {
if(btn.length > 0) {
Button.setDisabled(btn, true);
}
}
},
setTitle: function() {
var numHuts = $SM.get('game.buildings["hut"]', true);
var title;
if(numHuts === 0) {
title = _("A Silent Forest");
} else if(numHuts == 1) {
title = _("A Lonely Hut");
} else if(numHuts <= 4) {
title = _("A Tiny Village");
} else if(numHuts <= 8) {
title = _("A Modest Village");
} else if(numHuts <= 14) {
title = _("A Large Village");
} else {
title = _("A Raucous Village");
}
if(Engine.activeModule == this) {
document.title = title;
}
$('#location_outside').text(title);
},
onArrival: function(transition_diff) {
Outside.setTitle();
if(!$SM.get('game.outside.seenForest')) {
Notifications.notify(Outside, _("the sky is grey and the wind blows relentlessly"));
$SM.set('game.outside.seenForest', true);
}
Outside.updateTrapButton();
Outside.updateVillage(true);
Engine.moveStoresView($('#village'), transition_diff);
// set music
var numberOfHuts = $SM.get('game.buildings["hut"]', true);
if(numberOfHuts === 0) {
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_SILENT_FOREST);
} else if(numberOfHuts == 1) {
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_LONELY_HUT);
} else if(numberOfHuts <= 4) {
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_TINY_VILLAGE);
} else if(numberOfHuts <= 8) {
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_MODEST_VILLAGE);
} else if(numberOfHuts <= 14) {
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_LARGE_VILLAGE);
} else {
AudioEngine.playBackgroundMusic(AudioLibrary.MUSIC_RAUCOUS_VILLAGE);
}
},
gatherWood: function() {
Notifications.notify(Outside, _("dry brush and dead branches litter the forest floor"));
var gatherAmt = $SM.get('game.buildings["cart"]', true) > 0 ? 50 : 10;
$SM.add('stores.wood', gatherAmt);
AudioEngine.playSound(AudioLibrary.GATHER_WOOD);
},
checkTraps: function() {
var drops = {};
var msg = [];
var numTraps = $SM.get('game.buildings["trap"]', true);
var numBait = $SM.get('stores.bait', true);
var numDrops = numTraps + (numBait < numTraps ? numBait : numTraps);
for(var i = 0; i < numDrops; i++) {
var roll = Math.random();
for(var j in Outside.TrapDrops) {
var drop = Outside.TrapDrops[j];
if(roll < drop.rollUnder) {
var num = drops[drop.name];
if(typeof num == 'undefined') {
num = 0;
msg.push(drop.message);
}
drops[drop.name] = num + 1;
break;
}
}
}
/// TRANSLATORS : Mind the whitespace at the end.
var s = _('the traps contain ');
for(var l = 0, len = msg.length; l < len; l++) {
if(len > 1 && l > 0 && l < len - 1) {
s += ", ";
} else if(len > 1 && l == len - 1) {
/// TRANSLATORS : Mind the whitespaces at the beginning and end.
s += _(" and ");
}
s += msg[l];
}
var baitUsed = numBait < numTraps ? numBait : numTraps;
drops['bait'] = -baitUsed;
Notifications.notify(Outside, s);
$SM.addM('stores', drops);
AudioEngine.playSound(AudioLibrary.CHECK_TRAPS);
},
handleStateUpdates: function(e){
if(e.category == 'stores'){
Outside.updateVillage();
} else if(e.stateName.indexOf('game.workers') === 0 || e.stateName.indexOf('game.population') === 0){
Outside.updateVillage();
Outside.updateWorkersView();
Outside.updateVillageIncome();
}
}
};