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 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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._