Empire Overview

Script for Ikariam V 0.5.x, Overview tables for Ress, buildings and mititary, desktop versions in the style of Ikariam Empire Board

当前为 2014-06-05 提交的版本,查看 最新版本

// ==UserScript==
// @name                 Empire Overview
// @author               germano
// @description          Script for Ikariam V 0.5.x, Overview tables for Ress, buildings and mititary, desktop versions in the style of Ikariam Empire Board
// @namespace            Test
// @grant                GM_getValue
// @grant                GM_setValue
// @grant                GM_addStyle
// @grant                GM_registerMenuCommand
// @grant                GM_deleteValue
// @grant                GM_xmlhttpRequest
// @grant                GM_openInTab
// @include              http://s*.ikariam.gameforge.*/index.php*
// @exclude              http://board.*.ikariam.gameforge.com*
// @exclude              http://*.ikariam.gameforge.*/board
//
// @require              http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js
// @require              http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/jquery-ui.min.js
//
// @version              1.1661
// 
// ==/UserScript==



/***********************************************************************************************************************
 * Includes
 ********************************************************************************************************************* */
;(function($){
var jQuery = $;

if(window.navigator.vendor.match(/Google/)) {
  var isChrome = true;
}
if(!isChrome) {
  this.$ = this.jQuery = jQuery.noConflict(true);
}

$.extend({
  exclusive     : function(arr) {
    return $.grep(arr, function(v, k) {
      return $.inArray(v, arr) === k;
    });
  },

  mergeValues   : function(a, b, c) {
    var length = arguments.length;
    if(length == 1 || typeof arguments[0] !== "object" || typeof arguments[1] !== "object") {
      return arguments[0];
    }
    var args = jQuery.makeArray(arguments);
    var i = 1;
    var target = args[0];
    for(; i < length; i++) {
      var copy = args[i];
      for(var name in copy) {
        if(!target.hasOwnProperty(name)) {
          target[name] = copy[name];
          continue;
        }
        if(typeof target[name] == "object" && typeof copy[name] == "object") {
          target[name] = jQuery.mergeValues(target[name], copy[name])
        } else if(copy.hasOwnProperty(name) && copy[name] != undefined) {
          target[name] = copy[name]
        }
      }
    }
    return target
  },
  decodeUrlParam: function(string) {
    var str = string.split('?').pop().split('&');
    var obj = {};
    for(var i = 0; i < str.length; i++) {
      var param = str[i].split('=');
      if(param.length !== 2) {
        continue;
      }
      obj[param[0]] = decodeURIComponent(param[1].replace(/\+/g, " "))
    }
    return obj
  }
});

var events = (function() {
  var _events = {};
  var retEvents = function(id) {
    var callbacks, topic = id && _events[ id ];
    if(!topic) {
      callbacks = $.Callbacks("");
      topic = {
        pub  : callbacks.fire,
        sub  : callbacks.add,
        unsub: callbacks.remove
      };
      if(id) {
        _events[ id ] = topic;
      }
    }
    return topic;
  };

  retEvents.scheduleAction = function(callback, time) {
    return clearTimeout.bind(undefined, setTimeout(callback, time || 0));
  };

  retEvents.scheduleActionAtTime = function(callback, time) {
    return retEvents.scheduleAction(callback, (time - $.now() > 0 ? time - $.now() : 0))
  };

  retEvents.scheduleActionAtInterval = function(callback, time) {
    return clearInterval.bind(undefined, setInterval(callback, time));
  };
  return retEvents
})();

/***********************************************************************************************************************
 * Globals
 **********************************************************************************************************************/
var debug = false;
var log = false;
var timing = false;
if(!unsafeWindow) unsafeWindow = window;

/***********************************************************************************************************************
 * Inject button into page before the page renders the YUI menu or it will not be animated (less work)
 **********************************************************************************************************************/
$('.menu_slots > .expandable:last').after('<li class="expandable slot99 empire_Menu" onclick=""><div class="empire_Menu image" style="background-image: url(skin/minimized/weltinfo.png); background-position: 0px 0px; background-size:33px auto"></div></div><div class="name"><span class="namebox">Empire Overview</span></div></li>');

/***********************************************************************************************************************
 * Utility Functions
 **********************************************************************************************************************/
var Utils = {
  wrapInClosure     : function(obj) {
    return (function(x) {
      return function() {
        return x
      }
    })(obj)
  },
  existsIn          : function(input, test) {
    try {
      var ret = input.indexOf(test) !== -1
    } catch(e) {
      return false
    }
    return ret;
  },
  estimateTravelTime: function(city1, city2) {
    if(!city1 || !city2) return 0;
    if(city1[0] == city2[0] && city1[1] == city2[1]) {
      var time = 1200 / 60 * 0.5;
    } else {
      time = 1200 / 60 * (Math.sqrt(Math.pow((city2[0] - city1[0]), 2) + Math.pow((city2[1] - city1[1]), 2)));
    }
    return Math.floor(time * 60 * 1000);
  },
  addStyleSheet     : function(style) {
    var getHead = document.getElementsByTagName("HEAD")[0];
    var cssNode = window.document.createElement('style');
    var elementStyle = getHead.appendChild(cssNode);
    elementStyle.innerHTML = style;
    return elementStyle;
  },
  escapeRegExp      : function(str) {
    return str.replace(/[\[\]\/\{\}\(\)\-\?\$\*\+\.\\\^\|]/g, "\\$&");
  },
  format            : function(inputString, replacements) {
    var str = '' + inputString;
    var keys = Object.keys(replacements);
    var i = keys.length;
    while(i--) {
      str = str.replace(new RegExp(this.escapeRegExp('{' + keys[i] + '}'), 'g'), replacements[keys[i]])
    }
    return str
  },
  cacheFunction     : function(toExecute, expiry) {
    expiry = expiry || 1000;
    var cachedTime = $.now;
    var cachedResult = undefined;
    return function() {
      if(cachedTime < $.now() - expiry || cachedResult === undefined) {
        cachedResult = toExecute();
        cachedTime = $.now()
      }
      return cachedResult
    }
  },
  getClone  : function($node){
    if ($node.hasClass('ui-sortable-helper') || $node.parent().find('.ui-sortable-helper').length){
    return $node;}
    return $($node.get(0).cloneNode(true))
  },
  setClone : function($node, $clone){
    if ($node.hasClass('ui-sortable-helper') || $node.parent().find('.ui-sortable-helper').length){
      return $node;
    }
    $node.get(0).parentNode.replaceChild($clone.get(0), $node.get(0));
    return $node
  },
  replaceNode       : function(node, html) {
    var t = node.cloneNode(false);
    t.innerHTML = html;
    node.parentNode.replaceChild(t, node);
    return t
  },
	 FormatTimeLengthToStr : function(timeString, precision, spacer) {
	 	var lang = database.settings.languageChange.value;
		timeString = timeString || 0;
		precision = precision || 2;
		spacer = spacer || " ";
		if(!isFinite(timeString)) {
			return  ' \u221E ';
		}
		if(timeString < 0) timeString *= -1;
		var factors = [];
		var locStr = [];
	    var server = ikariam.Nationality();
		factors.day = 86400;
		factors.hour = 3600;
		factors.minute = 60;
		factors.second = 1;
		locStr.day = Constant.LanguageData[lang].day;
		locStr.hour = database.getGlobalData.getLocalisedString('h');
		locStr.minute = database.getGlobalData.getLocalisedString('m');
		locStr.second = database.getGlobalData.getLocalisedString('s');
	      if (server == 'ir' || server == 'ae') {
	  		locStr.hour = database.getGlobalData.getLocalisedString('س');
			locStr.minute = database.getGlobalData.getLocalisedString('د');
			locStr.second = database.getGlobalData.getLocalisedString('ث');
			}
		timeString = Math.ceil(timeString / 1000);
		var retString = "";
		for(var fact in factors) {
			var timeInSecs = Math.floor(timeString / factors[fact]);
			if(isNaN(timeInSecs)) {
				return retString;
			}
			if(precision > 0 && (timeInSecs > 0 || retString != "")) {
				timeString = timeString - timeInSecs * factors[fact];
				if(retString != "") {
					retString += spacer;
				}
				retString += timeInSecs + locStr[fact];
				precision--;
			}
		}
		return retString;
	}, 
	FormatFullTimeToDateString:	function (timeString, precise) {
	    var lang = database.settings.languageChange.value;
		precise = precise || true;
		timeString = timeString || 0;
		var sInDay = 86400000;
		var day = '';
		var compDate = new Date(timeString);
		if(precise) {
			switch(Math.floor(compDate.getTime() / sInDay) - Math.floor($.now() / sInDay)) {
				case 0  :
					day = Constant.LanguageData[lang].today; 
					break;
				case 1  :
					day = Constant.LanguageData[lang].tomorrow;
					break;
				case -1 :
					day = Constant.LanguageData[lang].yesterday;
					break;
				default :
					day = (!isChrome ? compDate.toLocaleFormat('%a %d %b') : compDate.toString().split(' ').splice(0, 3).join(' ')) //Dienstag
			}
		}
		if(day != '') {
			day += ', '
		}
		return day + compDate.toLocaleTimeString();
	},
	FormatTimeToDateString:	function (timeString) {
		timeString = timeString || 0;
		var compDate = new Date(timeString);
		return compDate.toLocaleTimeString();
	},
	FormatRemainingTime : function(time, brackets) {
			brackets = brackets || false;
			var arrInTime = Utils.FormatTimeLengthToStr(time, 3, ' ');
			return (arrInTime == '') ? '' : (brackets ? '(' : '') + arrInTime + (brackets ? ')' : '');
		},
	FormatNumToStr : function(inputNum, outputSign, precision) {
	    var lang = database.settings.languageChange.value;
		precision = precision ? "10e" + (precision - 1) : 1;
		var ret, val, sign, i, j;
		var tho = Constant.LanguageData[lang].thousandSeperator;
		var dec = Constant.LanguageData[lang].decimalPoint;
		if(!isFinite(inputNum)) {
			return '\u221E'
		}
		sign = inputNum > 0 ? 1 : inputNum === 0 ? 0 : -1;
		if(sign) {
			val = (( Math.floor(Math.abs(inputNum * precision)) / precision ) + '').split('.');
			ret = val[1] != undefined ? [dec, val[1]] : [];
			val = val[0].split('');
			i = val.length;
			j = 1;
			while(i--) {
				ret.unshift(val.pop());
				if(i && j % 3 == 0) {
					ret.unshift(tho)
				}
				j++
			}
			if(outputSign) {
				ret.unshift(sign == 1 ? '+' : '-');
			}
			return ret.join('');
		}
		else return inputNum;
	}
};

/***********************************************************************************************************************
 * CLASSES
 **********************************************************************************************************************/
function Movement(id, originCityId, targetCityId, arrivalTime, mission, loadingTime, resources, military, ships) {
  if(typeof id === "object") {
    this._id = id._id || null;
    this._originCityId = id._originCityId || null;
    this._targetCityId = id._targetCityId || null;
    this._arrivalTime = id._arrivalTime || null;
    this._mission = id._mission || null;
    this._loadingTime = id._loadingTime || null;
    this._resources = id._resources || {wood: 0, wine: 0, marble: 0, glass: 0, sulfur: 0, gold: 0};
    this._military = id._military || new MilitaryUnits();
    this._ships = id._ships || null;
    this._updatedCity = id._updatedCity || false;
    this._complete = id._complete || false;
    this._updateTimer = id._updateTimer || null;

  } else {
    this._id = id || null;
    this._originCityId = originCityId || null;
    this._targetCityId = targetCityId || null;
    this._arrivalTime = arrivalTime || null;
    this._mission = mission || null;
    this._loadingTime = loadingTime || null;
    this._resources = resources || {wood: 0, wine: 0, marble: 0, glass: 0, sulfur: 0, gold: 0};
    this._military = military || new MilitaryUnits();
    this._ships = ships || null;
    this._updatedCity = false;
    this._complete = false;
    this._updateTimer = null;
  }
}
Movement.prototype = {
  startUpdateTimer       : function() {
    this.clearUpdateTimer();
    if(this.isCompleted) {
      this.updateTransportComplete()
    } else {
      this._updateTimer = events.scheduleActionAtTime(this.updateTransportComplete.bind(this), this._arrivalTime + 1000)
    }
  },
  clearUpdateTimer       : function() {
    var ret = !this._updateTimer || this._updateTimer();
    this._updateTimer = null;
    return ret
  },
  get getId() {
    return this._id
  },
  get getOriginCityId() {
    return this._originCityId
  },
  get getTargetCityId() {
    return this._targetCityId
  },
  get getArrivalTime() {
    return this._arrivalTime
  },
  get getMission() {
    return this._mission
  },
  get getLoadingTime() {
    return this._loadingTime - $.now()
  },
  get getResources() {
    return this._resources
  },
  getResource            : function(resourceName) {
    return this._resources[resourceName]
  },
  get getMilitary() {
    return this._military
  },
  get getShips() {
    return this._ships
  },
  get isCompleted() {
    return this._arrivalTime < $.now()
  },
  get isLoading() {
    return this._loadingTime > $.now()
  },
  get getRemainingTime() {
    return this._arrivalTime - $.now()
  },
  updateTransportComplete: function() {
    if(this.isCompleted && !this._updatedCity) {
      var city = database.getCityFromId(this._targetCityId);
      var changes = [];
      if(city) {
        for(var resource in Constant.Resources) {
          if(this.getResource(Constant.Resources[resource])) {
            changes.push(Constant.Resources[resource])
          }
          city.getResource(Constant.Resources[resource]).increment(this.getResource(Constant.Resources[resource]));
        }		
        this._updatedCity = true;
        city = database.getCityFromId(this.originCityId);
        if(city) {
          city.updateActionPoints(city.getAvailableActions + 1)
        }
        if(changes.length) {
          events(Constant.Events.MOVEMENTS_UPDATED).pub([this.getTargetCityId]);
          events(Constant.Events.RESOURCES_UPDATED).pub(this.getTargetCityId, changes);
        }
        events.scheduleAction(function() {
          database.getGlobalData.removeFleetMovement(this._id)
        }.bind(this));
        return true
      }

    } else if(this._updatedCity) {
      events.scheduleAction(function() {
        database.getGlobalData.removeFleetMovement(this._id)
      }.bind(this))
    }
    return false;
  }
};

function Resource(city, name) {
  this._current = 0;
  this._production = 0;
  this._consumption = 0;
  this._currentChangedDate = $.now();
  this.city = Utils.wrapInClosure(city);
  this._name = name;
  return this
}

Resource.prototype = {
  get name() {
    return this._name
  },
  update   : function(current, production, consumption) {
    var changed = (current % this._current > 10) || (production != this._production) || (consumption != this._consumption);
    this._current = current;
    this._production = production;
    this._consumption = consumption;
    this._currentChangedDate = $.now();
    return changed;
  },
  project  : function() {
    var limit = Math.floor($.now() / 1000);
    var start = Math.floor(this._currentChangedDate / 1000);
    while(limit > start) {
      this._current += this._production;
      if(Math.floor(start / 3600) != Math.floor((start + 1) / 3600))
        if(this._current > this._consumption) {
          this._current -= this._consumption
        } else {
          this.city().projectPopData(start * 1000);
          this._consumption = 0;
        }

      start++
    }
    this._currentChangedDate = limit * 1000;
    this.city().projectPopData(limit * 1000)

  },
  increment: function(amount) {
    if(amount !== 0) {
      this._current += amount;
      return true;
    }
    return false;
  },
  get getEmptyTime() {
    var net = this.getProduction * 3600 - this.getConsumption;
    return (net < 0) ? this.getCurrent / net * -1 : Infinity; 
  },
  get getFullTime() {
    var net = this.getProduction * 3600 - this.getConsumption;
    return (net > 0) ? (this.city().maxResourceCapacities.capacity - this.getCurrent) / net : 0;
  },
  get getCurrent() {
    return Math.floor(this._current);

  },
  get getProduction() {
    return this._production || 0
  }, 
  get getConsumption() {
    return this._consumption || 0
  } 
};

function Military(city) {
  this.city = Utils.wrapInClosure(city);
  this._units = new MilitaryUnits();
  this._advisorLastUpdate = 0;
  this.armyTraining = [];
  this._trainingTimer = null;
}
Military.prototype = {
  init                 : function() {
    this._trainingTimer = null;
    this._startTrainingTimer()
  },
	_getTrainingTotals: function (){
		var ret = {};
		$.each(this.armyTraining, function(index, training) {
			$.each(Constant.UnitData, function(unitId, info) {
        ret[unitId] = ret[unitId]?ret[unitId]+(training.units[unitId]||0):training.units[unitId]||0
			})
		});
		return ret
	},
  get getTrainingTotals() { 
	  if(!this._trainingTotals) {
		  this._trainingTotals = Utils.cacheFunction(this._getTrainingTotals.bind(this), 1000)
	  }
	  return this._trainingTotals()
  },
	_getIncomingTotals :function (){
		var ret = {};
		$.each(this.city().getIncomingMilitary, function(index, element) {
			for(var unitName in Constant.UnitData) {
        ret[unitName] = ret[unitName] ? ret[unitName] + (element.getMilitary.totals[unitName]||0) : element.getMilitary.totals[unitName]||0
			}
		});
		return ret
	},
  get getIncomingTotals() { 
	  if(!this._incomingTotals) {
		  this._incomingTotals = Utils.cacheFunction(this._getIncomingTotals.bind(this), 1000)
	  }
	  return this._incomingTotals()
  },
  getTrainingForUnit   : function(unit) {
    var ret = [];
    $.each(this.armyTraining, function(index, training) {
      $.each(training.units, function(unitId, count) {
        if(unitId === unit) {
          ret.push({count: count, time: training.completionTime})
        }
      })
    });
    return ret
  },
  setTraining          : function(trainingQueue) {
    if(!trainingQueue.length) return false;
    this._stopTrainingTimer();
    var type = trainingQueue[0].type;
    var changes = this._clearTrainingForType(type);
    $.each(trainingQueue, function(index, training) {
      this.armyTraining.push(training);
      $.each(training.units, function(unitId, count) {
        changes.push(unitId)
      })
    }.bind(this));
    this.armyTraining.sort(function(a, b) {
      return a.completionTime - b.completionTime
    });
    this._startTrainingTimer();
    return $.exclusive(changes)
  },
  _clearTrainingForType: function(type) {
    var oldTraining = this.armyTraining.filter(function(item) {
      return item.type === type
    });
    this.armyTraining = this.armyTraining.filter(function(item) {
      return item.type !== type
    });
    var changes = [];
    $.each(oldTraining, function(index, training) {
      $.each(training.units, function(unitId, count) {
        changes.push(unitId)
      })
    });
    return changes
  },
  _completeTraining    : function() {
    if(this.armyTraining.length) {
      if(this.armyTraining[0].completionTime < $.now() + 5000) {
        var changes = [];
        var training = this.armyTraining.shift();
        $.each(training.units, function(id, count) {
          this.getUnits.addUnit(id, count);
          changes.push(id)
        }.bind(this));
        if(changes.length)events(Constant.Events.MILITARY_UPDATED).pub(this.city().getId, changes)
      }
    }
    this._startTrainingTimer()
  },
  _startTrainingTimer  : function() {
    this._stopTrainingTimer();
    if(this.armyTraining.length) {
      this._trainingTimer = events.scheduleActionAtTime(this._completeTraining.bind(this), this.armyTraining[0].completionTime)
    }
  },
  _stopTrainingTimer   : function() {
    if(this._trainingTimer) {
      this._trainingTimer()
    }
    this._trainingTimer = null
  },
  updateUnits          : function(counts) {
    var changes = [];
    $.each(counts, function(unitId, count) {
      if(this._units.setUnit(unitId, count)) {
        changes.push(unitId)
      }
    }.bind(this));
    return changes;
  },
  get getUnits() {
    return this._units
  }
};
function MilitaryUnits(obj) {
  this._units = obj !== undefined ? obj._units: {};
}
MilitaryUnits.prototype = {
  getUnit   : function(unitId) {
    return this._units[unitId] || 0
  },
  setUnit   : function(unitId, count) {
    var changed = this._units[unitId] != count;
    this._units[unitId] = count;
    return changed
  },
  get totals() {
    return this._units
  },
  addUnit   : function(unitId, count) {
    return this.setUnit(unitId, this.getUnit(unitId) + count)
  },
  removeUnit: function(unitId, count) {
    count = Math.max(0, this.getUnit[unitId] - count);
    return this.setUnit(unitId, count)
  }
};
function Building(city, pos) {
  this._position = pos;
  this._level = 0;
  this._name = null;
  this.city = Utils.wrapInClosure(city);
  this._updateTimer = null
}
Building.prototype = {
  startUpgradeTimer               : function() {
    if(this._updateTimer) {
      this._updateTimer();
      delete this._updateTimer
    }
    if(this._completionTime) {
      if(this._completionTime - $.now() < 5000) {
        this.completeUpgrade()
      } else {
        this._updateTimer = events.scheduleActionAtTime(this.completeUpgrade.bind(this), this._completionTime - 4000)
      }
    }
    var statusPoll = function(a, b){
      return events.scheduleActionAtInterval(function(){
        if(a != this.isUpgradable || b != this.isUpgrading){
          var changes = {position: this._position, name: this.getName, upgraded: this.isUpgrading != b}
          events(Constant.Events.BUILDINGS_UPDATED).pub([changes]);
          a = this.isUpgradable;
          b = this.isUpgrading;
        }
      }.bind(this),3000)
    }(this.isUpgradable,this.isUpgrading)
  },
  update                          : function(data) {
    var changes;
    var name = data.building.split(' ')[0];
    var level = parseInt(data.level) || 0;
    database.getGlobalData.addLocalisedString(name, data.name); 
    var completion = ('undefined' !== typeof data['completed']) ? parseInt(data['completed']) : 0;
    var changed = (name !== this._name || level !== this._level || !!completion != this.isUpgrading);
    if(changed) {
      changes = {position: this._position, name: this.getName, upgraded: this.isUpgrading != !completion};
    }
    if(completion) {
      this._completionTime = completion * 1000;
      this.startUpgradeTimer()
    } else if(this._completionTime) {
      delete this._completionTime
    }
    this._name = name;
    this._level = level;
    if(changed) {
      return changes;
    }
    return false;
  },
  get getUrlParams() {
    return {
      view    : this.getName,
      cityId  : this.city().getId,
      position: this.getPosition
    };
  },
  get getUpgradeCost() {
    var carpenter, architect, vineyard, fireworker, optician;
    var level = this._level + this.isUpgrading;
    if(this.isEmpty) {
      return {
        wood  : Infinity,
        glass : 0,
        marble: 0,
        sulfur: 0,
        wine  : 0,
        time  : 0
      }
    }
    var time = Constant.BuildingData[this._name].time;
    var bon = 1;
    var bonTime = 1 + Constant.GovernmentData[database.getGlobalData.getGovernmentType].buildingTime;
    bon -= database.getGlobalData.getResearchTopicLevel(Constant.Research.Economy.PULLEY) ? .02 : 0;
    bon -= database.getGlobalData.getResearchTopicLevel(Constant.Research.Economy.GEOMETRY) ? .04 : 0;
    bon -= database.getGlobalData.getResearchTopicLevel(Constant.Research.Economy.SPIRIT_LEVEL) ? .08 : 0;
    return{
      wood  : Math.floor((Constant.BuildingData[this._name].wood[level] || 0) * (bon - (carpenter = this.city().getBuildingFromName(Constant.Buildings.CARPENTER), carpenter ? carpenter.getLevel / 100 : 0))),
      wine  : Math.floor((Constant.BuildingData[this._name].wine[level] || 0) * (bon - (vineyard = this.city().getBuildingFromName(Constant.Buildings.VINEYARD), vineyard ? vineyard.getLevel / 100 : 0))),
      marble: Math.floor((Constant.BuildingData[this._name].marble[level] || 0) * (bon - (architect = this.city().getBuildingFromName(Constant.Buildings.ARCHITECT), architect ? architect.getLevel / 100 : 0))),
      glass : Math.floor((Constant.BuildingData[this._name].glass[level] || 0) * (bon - (optician = this.city().getBuildingFromName(Constant.Buildings.OPTICIAN), optician ? optician.getLevel / 100 : 0))),
      sulfur: Math.floor((Constant.BuildingData[this._name].sulfur[level] || 0) * (bon - (fireworker = this.city().getBuildingFromName(Constant.Buildings.FIREWORK_TEST_AREA), fireworker ? fireworker.getLevel / 100 : 0))),
      time  : Math.round(time.a / time.b * Math.pow(time.c, level + 1) - time.d) * 1000 * bonTime
    }
  },
  get getName() {
    return this._name
  },
  get getType() {
    return Constant.BuildingData[this.getName].type
  },
  get getLevel() {
    return this._level
  },
  get isEmpty() {
    return this._name == 'buildingGround' || this._name == null
  },
  get isUpgrading() {
    return (this._completionTime > $.now())
  },
  subtractUpgradeResourcesFromCity: function() {
    var cost = this.getUpgradeCost;
    $.each(Constant.Resources, function(key, resourceName) {
      this.city().getResource(resourceName).increment(cost[resourceName] * -1)
    }.bind(this));
    this._completionTime = $.now() + cost.time;
  },
  get isUpgradable() {
    if(this.isEmpty || this.isMaxLevel) {
      return false
    }
    var cost = this.getUpgradeCost;
    var upgradable = true;
    $.each(Constant.Resources, function(key, value) {
      upgradable = upgradable && (!cost[value] || cost[value] <= this.city().getResource(value).getCurrent);
    }.bind(this));
    return upgradable;
  },
  get getCompletionTime() {
    return this._completionTime
  },
  get getCompletionDate() {
  },
  get isMaxLevel() {
    return Constant.BuildingData[this.getName].maxLevel === (this.getLevel)
  },
  get getPosition() {
    return this._position
  },
  completeUpgrade                 : function() {
    this._level++;
    delete this._completionTime;
    delete this._updateTimer; 
    events(Constant.Events.BUILDINGS_UPDATED).pub(this.city().getId, [
      {position: this._position, name: this.getName, upgraded: true}
    ])
  }
};
function CityResearch(city) {
  this._researchersLastUpdate = 0;
  this._researchers = 0;
  this._researchCostLastUpdate = 0;
  this._researchCost = 0;
  this.city = Utils.wrapInClosure(city);
}

CityResearch.prototype = {
  updateResearchers: function(researchers) { 
    var changed = this._researchers !== researchers;
    this._researchers = researchers;
    this._researchersLastUpdate = $.now();
    this._researchCost = this.getResearchCost;
    return changed;
  },
  updateCost       : function(cost) { 
    var changed = this._researchCost !== cost;
    this._researchCost = cost;
    this._researchCostLastUpdate = $.now();
    this._researchers = this.getResearchers;
    return changed;
  },
  get getResearchers() {
    if(this._researchersLastUpdate < this._researchCostLastUpdate) {
      return Math.floor(this._researchCost / this._researchCostModifier);
    } else {
      return this._researchers
    }
  },
  get getResearch() {
    return this.researchData.total
  },
  get researchData(){
    if (!this._researchData){
      this._researchData = Utils.cacheFunction(this.researchDataCached.bind(this),1000)
    }
    return this._researchData()
  },
  researchDataCached :function(){
    var resBon = 0 + Constant.GovernmentData[database.getGlobalData.getGovernmentType].researchBonus
                   + (database.getGlobalData.getResearchTopicLevel(Constant.Research.Science.PAPER) * 0.02)
                   + (database.getGlobalData.getResearchTopicLevel(Constant.Research.Science.INK) * 0.04)
                   + (database.getGlobalData.getResearchTopicLevel(Constant.Research.Science.MECHANICAL_PEN) * 0.08)
                   + (database.getGlobalData.getResearchTopicLevel(Constant.Research.Science.SCIENTIFIC_FUTURE) * 0.02);
    var premBon = database.getGlobalData.hasPremiumFeature(Constant.Premium.RESEARCH_POINTS_BONUS_EXTREME_LENGTH) ? (0 + Constant.PremiumData[Constant.Premium.RESEARCH_POINTS_BONUS_EXTREME_LENGTH].bonus) : database.getGlobalData.hasPremiumFeature(Constant.Premium.RESEARCH_POINTS_BONUS) ? (0 + Constant.PremiumData[Constant.Premium.RESEARCH_POINTS_BONUS].bonus) : 0;
    var goods = Constant.GovernmentData[database.getGlobalData.getGovernmentType].researchPerCulturalGood * this.city()._culturalGoods;
    var researchers = this.getResearchers;
    var corruptionSpend = researchers * this.city().getCorruption;
    var nonCorruptedResearchers = researchers * (1 - this.city().getCorruption);
    var premiumResBonus = nonCorruptedResearchers * premBon;
    var researchBonus = nonCorruptedResearchers * resBon;
    var premiumGoodsBonus = goods * premBon;
    var serverTyp = $('li.logout a[href="?view=normalServerStatus"]').text() ? 1 : 3;
    return {
      scientists: researchers,	  
      researchBonus: researchBonus,
      premiumScientistBonus: premiumResBonus,
      premiumResearchBonus: (researchBonus* premBon),
      culturalGoods: goods,
      premiumCulturalGoodsBonus: premiumGoodsBonus,
      corruption: corruptionSpend,
      total: (nonCorruptedResearchers + researchBonus + premiumResBonus + goods + premiumGoodsBonus + (researchBonus* premBon))* serverTyp
    }
  },
  get _researchCostModifier() {
    return 6 + Constant.GovernmentData[database.getGlobalData.getGovernmentType].researcherCost - (database.getGlobalData.getResearchTopicLevel(Constant.Research.Science.LETTER_CHUTE) * 3)
  },
  get getResearchCost() {
    return this.getResearchers * this._researchCostModifier;
  }
};

function Changes(city, type, changes) {
  this.city = city || null;
  this.type = type || null;
  this.changes = changes || [];
}
function Population(city) {
  this._population = 0;
  this._citizens = 0;
  this._resourceWorkers = 0;
  this._tradeWorkers = 0;
  this._priests = 0;
  this._culturalGoods = 0;

  this._popChanged = $.now();
  this._citizensChanged = $.now();
  this._culturalGoodsChanged = $.now();
  this._priestsChanged = $.now();
  this.city = Utils.wrapInClosure(city)
}
Population.prototype = {
  updatePopulationData: function(population, citizens, priests, culturalGoods) {
    var changes = [];
    if(population && population != this._population) {
      changes.push({population: true});
      this.population = population;
    }
    if(citizens && citizens != this._priests) {
      changes.push({citizens: true});
      this.citizens = citizens;
    }
    if(priests && priests != this._priests) {
      changes.push({priests: true});
      this.priests = priests;
    }
  },
  updateWorkerData    : function(resourceName, workers) {
  },
  updatePriests       : function(newCount) {
  },
  updateCulturalGoods : function(newCount) {
  },
  get population() {
    return this._population
  },
  set population(newVal) {
    this._population = newVal;
    this._popChanged = $.now()
  },
  get citizens() {
    return this._citizens
  },
  set citizens(newVal) {
    this._citizens = newVal;
    this._citizensChanged = $.now()
  },
  get priests() {
    return this._priests
  },
  set priests(newVal) {
    this._priests = newVal;
    this._priestsChanged = $.now()
  }
};

function City(id) {
  this._id = id || 0;
  this._name = '';
  this._resources = {
    gold  : new Resource(this, Constant.Resources.GOLD),
    wood  : new Resource(this, Constant.Resources.WOOD),
    wine  : new Resource(this, Constant.Resources.WINE),
    marble: new Resource(this, Constant.Resources.MARBLE),
    glass : new Resource(this, Constant.Resources.GLASS),
    sulfur: new Resource(this, Constant.Resources.SULFUR)
  };
  this._capacities = {
    capacity : 0,
    safe     : 0,
    buildings: {
      dump     : {storage: 0, safe: 0},
      warehouse: {storage: 0, safe: 0},
      townHall : {storage: 2500, safe: 100}
    },
    invalid  : true
  };
  this._tradeGoodID = 0;
  this.knownTime = $.now();
  this._