您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Base scripting tools and data tracking utilities to simplify
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/459367/1145200/Valkazaar%20Ikariam%20Developer%20Tools%20V050%2B.js
// ==UserScript== // @name Valkazaar Ikariam Developer Tools V0.5.0+ // @namespace AubergineAnodyne // @description Base scripting tools and data tracking utilities to simplify // writing Ikariam Greasemonkey scripts. // @author AubergineAnodyne // (very loosely based on Ikariam Developer Tools by PhasmaExMachina) // // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // // @version 0.30 // // @history 0.30 Added turkish translations and Fixed blackmarket and sea chart archive buildins. // @history 0.29 Fixed pillage of crystal not showing up. // @history 0.28 Added COLONIZE constant. // @history 0.27 Added helper methods for scripts to load/read Ikariam pages in the background. // @history 0.27 Added support for spy data (IkaTools.EmpireData.EspionageData). // @history 0.27 Added Romanian translation (by Peta). // @history 0.26 Added Hungarian translation (by Toroco). // @history 0.26 Updated for Ikariam changes in 5.3.2. // @history 0.25 Fix bug in parsing training batches for military. // @history 0.25 Fix bug in parsing returning colonization mission. Also correct resource calculation for colonization mission. // @history 0.25 Fix bug in parsing missions when pirate raid is in progress. // @history 0.24 Added support for Pirate Fortress (v0.5.3 new building). // @history 0.23 Added resetData function. // @history 0.23 Fixed a bug that future research levels >= 9 would not be parsed. // @history 0.22 Fixed a bug that stopped the script from running in some browser configurations. // @history 0.22 Added some debugging features. // @history 0.21 Added resizing of settings tab. // @history 0.20 Added building icon data. // @history 0.20 Added HTML setting type. // @history 0.20 Added some movement type data. // @history 0.20 Fixed a bug computing number of transports for transport missions. // @history 0.19 Added Polish translation (from pitmm). // @history 0.19 Fixed temple build resource requirements showing up incorrectly (marble instead of crystal). // @history 0.19 Changed how transition to resource and mine views works. // @history 0.18 Hopefully fixed population calculation crash when running the theology government. // @history 0.18 Fix for transport form on test server. (Probably coming to other servers with 0.5.1). // @history 0.17 Added German localization (translation by Cherry). // @history 0.17 Fixed date display bug with yesterday/today/tomorrow being incorrectly assigned due to timezone issue. // @history 0.16 Reworked how initial ajax response is determined so it works in Chrome. // @history 0.15 Removed CDATA section (hopefully will work in Chrome). // @history 0.14 UI support for script settings. // @history 0.14 Fixed corruption calculation for cities with no palace. // @history 0.14 Corrected loading time for deploy on the same island. // @history 0.13 Another tweak to work with TamperMonkey. // @history 0.12 Another tweak to work with TamperMonkey. // @history 0.11 Small tweak to work with TamperMonkey in Google Chrome. // @history 0.10 Initial version. // @require http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js // ==/UserScript== if (typeof IkaTools == 'undefined') { IkaTools = (function() { var ikaToolsVersion = 0; /** * Support functions for logging, profiling, and debugging. */ var Logging = (function() { var exceptionLog = []; var options = { debug: false, timings: false, profile: false, }; function getExceptionLog() { return exceptionLog; } /** * Analogous to console.log, but may have been disabled through options. */ function debug() { if (options.debug && console && console.log) { // console.log is not a true javascript function. In some browsers we can't call // it with console.log.apply syntax. Instead we just manually support up to 6 // arguments. switch (arguments.length) { case 0: console.log(); break; case 1: console.log(arguments[0]); break; case 2: console.log(arguments[0], arguments[1]); break; case 3: console.log(arguments[0], arguments[1], arguments[2]); break; case 4: console.log(arguments[0], arguments[1], arguments[2], arguments[3]); break; case 5: console.log(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]); break; default: console.log(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); break; } } } /** * Debug a caught exception. */ function debugException(text, ex) { exceptionLog.push({text: text, stack: ex.stack, message: ex.message}); if (console && console.error) { console.error('IkaTools.debugException (in %s)\n%s\n\n%s\n', text, ex, ex.stack); } } /** * Wrap a function to catch and log an exception. * If the time property is true in options (or absent) then time the function call. * (May set alwaysTime option to output time regardless of disabled logging settings.) * If the group property is true in options (or absent) then group the function call. */ function debuggable(debugOptions, func) { if (typeof(debugOptions) == 'string') { debugOptions = { label: debugOptions }; } debugOptions = $.extend({ time: true, group: true, profile: false, swallowException: false, }, debugOptions); if (!debugOptions.label) { debugOptions.label = Utils.nextId('_debuggable'); } return function debuggableWrapper() { var time = ((options.timings && debugOptions.time) || debugOptions.alwaysTime) && console && console.time; var group = debugOptions.group && (options.group) && console && console.group; var profile = debugOptions.profile && options.profile && console && console.profile; try { if (profile) { console.profile(debugOptions.label); } if (group) { console.group(debugOptions.label); } if (time) { console.time(debugOptions.label); } return func.apply(this, arguments); } catch (ex) { Logging.debugException(debugOptions.label, ex); if (!debugOptions.swallowException) { throw ex; } } finally { if (time) { console.timeEnd(debugOptions.label); } if (group) { console.groupEnd(debugOptions.label); } if (profile) { console.profileEnd(debugOptions.label); } } }; } /** * Wrap a console.time/timeEnd pair around a function. May be disabled * through options. */ function time(timeOptions, func) { return function time() { var time = (options.timings || timeOptions.alwaysTime) && console && console.time; try { if (time) { console.time(timeOptions.label); } return func.apply(this, arguments); } finally { if (time) { console.timeEnd(timeOptions.label); } } } } /** * Wrap a console.group/groupEnd pair around a function. May be disabled * through options. */ function group(groupOptions, func) { var label; var collapsed = false; return function group() { var group = (options.timings || options.debug) && console && console.group; try { if (group) { if (groupOptions.collapsed && console.groupCollapsed) { console.groupCollapsed(groupOptions.label); } else { console.group(groupOptions.label); } } return func.apply(this, arguments); } finally { if (group) { console.groupEnd(groupOptions.label); } } } } /** * Sets logging options. Properties are debug, timings, and profile. */ function setOptions(newOptions) { $.extend(options, newOptions); if (console && console.log) { //console.log("Set logging options to: ", options); } } /** * Allows logging options to be configured by browsing to various anchors * (and persisted for future page views). */ function setAndSaveOptionsFromPageAnchor() { var savedOptions = new Data.Value( 'debugOptions', { debug: false, timings: false, profile: false , group: false}, { useDomain: false, version: 0 }); savedOptions.get(); var anchor = window.location.hash; if (anchor.substring(0, 15) == '#ikaScriptTools') { if (anchor == '#ikaScriptToolsDebugAll') { savedOptions.get().debug = true; savedOptions.get().timings = true; savedOptions.get().profile = true; } else if (anchor == '#ikaScriptToolsDebugNone') { savedOptions.get().debug = false; savedOptions.get().timings = false; savedOptions.get().profile = false; } else if (anchor == '#ikaScriptToolsDebugOn') { savedOptions.get().debug = true; } else if (anchor == '#ikaScriptToolsDebugOff') { savedOptions.get().debug = false; } else if (anchor == '#ikaScriptToolsGroupOn') { savedOptions.get().group = true; } else if (anchor == '#ikaScriptToolsGroupOff') { savedOptions.get().group = false; } else if (anchor == '#ikaScriptToolsTimingsOn') { savedOptions.get().timings = true; } else if (anchor == '#ikaScriptToolsTimingsOff') { savedOptions.get().timings = false; } else if (anchor == '#ikaScriptToolsProfilesOn') { savedOptions.get().profile = true; } else if (anchor == '#ikaScriptToolsProfilesOff') { savedOptions.get().profile = false; } savedOptions.saveAsync(); } setOptions(savedOptions.get()); } return { debug: debug, debugException: debugException, debuggable: debuggable, time: time, group: group, setAndSaveOptionsFromPageAnchor: setAndSaveOptionsFromPageAnchor, getExceptionLog: getExceptionLog, }; })(); /** * Random utils that don't belong anywhere else. */ var Utils = function() { function thunk(func) { var computed = false; var value; function thunker() { if (!computed) { value = func(); computed = true; } return value; } return thunker; } function resettable(func) { var value = func(); function getValue() { return value; } getValue.reset = function() { var ret = value; value = func(); return ret; } getValue.set = function(v) { value = v; } return getValue; } function fixedFunction(value) { return function() { return value; } } var nextId = function() { var id = 10000; function nextId(prefix) { id++; if (prefix) { return prefix + id.toString(16); } else { return id; } }; return nextId; }(); var nextIntegerId = function() { var id = 100000; return function nextIntegerId() { return id++; }; }(); function EventDispatcher(name) { this.name = name; this.listeners = []; } $.extend(EventDispatcher.prototype, { addListener: function addListener(l) { var listener = Logging.debuggable( { label: 'EventDispatcher[' + this.name + '].ListenerWrapper', swallowException: true, }, function() { l.apply(null, arguments) }); this.listeners.push(listener); return { cancel: this._cancelListener.bind(this, listener), }; }, _cancelListener: function(listener) { for (var i = 0, len = this.listeners.length; i < len; i++) { if (this.listeners[i] === listener) { this.listeners.splice(i, 1); return; } } }, /*bindEventArgs: function bindEventArgs() { var that = this; var args = Array.prototype.slice.call(arguments); return { bindEventArgs: function boundBindEventArgs() { return that.bindEventArgs.apply(that, args.concat(Array.prototype.slice.call(arguments))); }, send: function boundSend() { that.send.apply(that, args.concat(Array.prototype.slice.call(arguments))); }, scheduleSend: function boundScheduleSend(name, delay, callback) { return that.scheduleSend.apply(that, [name, delay, callback].concat(args).concat(Array.prototype.slice.call(arguments, 3))); }, toJSON: function toJSON() { return undefined; }, }; },*/ send: function send() { var listeners = this.listeners.slice(); for (var i = 0, len = listeners.length; i < len; i++) { listeners[i].apply(null, arguments); } }, scheduleSend: function(name, delay, callback) { var that = this; var sendArgs = []; for (var i = 3; i < arguments.length; i++) { sendArgs.push(arguments[i]); } return clearTimeout.bind(null, setTimeout( function scheduledSend() { callback(); that.send.apply(that, sendArgs); }, Math.max(delay, 10))); }, /*startIntervalSend: function startIntervalSend(name, initialDelay, interval) { this.cancelIntervalSend(); var sendCall = this.send.bind(this); var sendArgs = []; for (var i = 2; i < arguments.length; i++) { sendArgs.push(arguments[i]); } this.cancelInterval = clearInterval.bind(null, setInterval( IkaTools.Logging.debuggable( 'IkaTools.Utils.EventDispatcher.intervalSend[' + name + ']', function() { sendCall.apply(null, sendArgs); }), interval)); }, cancelIntervalSend: function cancelIntevalSend() { if (this.cancelInterval) { this.cancelInterval(); } },*/ toJSON: function toJSON() { return undefined; }, }); function getVersion() { var parts = $.map( $('#GF_toolbar li.version span').text().match(/[0-9]+/g), function(x) { return parseInt(x); }).concat([0,0,0,0,0,0,0]); return { greaterThanOrEqual: function() { for (var i = 0; i < arguments.length; i++) { if (parts[i] != arguments[i]) { return parts[i] >= arguments[i]; } } return true; }, lessThan: function() { for (var i = 0; i < arguments.length; i++) { if (parts[i] != arguments[i]) { return parts[i] < arguments[i]; } } return false; }, }; } function isChrome() { return navigator.vendor.match(/Google/) || navigator.userAgent.match(/Chrome/); } function iterateIkariamAjaxResponse(response, f) { $.each(response, function iterateIkariamAjaxResponseItem(index, item) { f(index, item[0], item[1]); }); } function forEachIkariamAjaxResponseFunction(f) { return function forEachIkariamResponse(response) { iterateIkariamAjaxResponse(response, f); } } function backgroundFetchPage(url, callback, options) { options = $.extend({method: 'GET', data: ''}, options); var headers = { 'User-agent': navigator.userAgent, 'Cookie': document.cookie, 'Referer': 'http://' + document.domain + '/index.php', }; if(options.method == 'POST') { headers['Content-type'] = 'application/x-www-form-urlencoded'; } setTimeout(function() { GM_xmlhttpRequest ({ method: options.method, url: url, data: options.data, headers: headers, onload: Logging.debuggable('IkaTools.Utils.backgroundGetIkariamPage[' + url + ']', callback) }); }, 0); } function backgroundFetchIkariamAjaxPage(url, callback, options) { backgroundFetchPage(url, function(response) { callback(JSON.parse(response.responseText)); }, options); } var jsonResponseRegex = /ikariam.getClass\(ajax.Responder, (.*?)\);$/m; function backgroundFetchIkariamFullPage(url, callback, options) { backgroundFetchPage(url, function(response) { var match = jsonResponseRegex.exec(response.responseText); jsonResponse = []; if (match) { jsonResponse = JSON.parse(match[1]); } callback(response, jsonResponse); }, options); } var urlParamsRegex = /\?([^\#]*)(#|$)/; function parseUrlParams(url) { var paramsList = url.match(urlParamsRegex)[1].split('&'); var params = {}; $.each(paramsList, function(index, item) { var paramParts = item.split('='); if (paramParts.length == 2) { params[paramParts[0]] = decodeURIComponent(paramParts[1]); } }); return params; } var timestampRegex = /(\d+)\.(\d+)\.(\d+)\s+(\d+):(\d+):(\d+)/i; function parseIkariamTimestamp(timestamp){ var d = new Date(); //Get the local GMT offset in hours //Attempt to match the constituent parts of the timestamp var match = timestamp.match(timestampRegex); if (match){ d.setTime(0); d.setDate(parseInt(match[1], 10)); d.setMonth(parseInt(match[2], 10)-1); d.setFullYear(parseInt(match[3], 10)); d.setHours(parseInt(match[4], 10)); d.setMinutes(parseInt(match[5], 10)); d.setSeconds(parseInt(match[6], 10)); //Adjust the time to get its TZ correct d.setTime(d.getTime() - d.getTimezoneOffset() * Constants.Time.MILLIS_PER_MINUTE - Constants.Time.MILLIS_PER_HOUR); // Server time is german = GMT+1 } return d; } return { thunk: thunk, resettable: resettable, fixedFunction: fixedFunction, EventDispatcher: EventDispatcher, nextId: nextId, nextIntegerId: nextIntegerId, getVersion: thunk(getVersion), isChrome: thunk(isChrome), iterateIkariamAjaxResponse: iterateIkariamAjaxResponse, forEachIkariamAjaxResponseFunction: forEachIkariamAjaxResponseFunction, backgroundFetchIkariamAjaxPage: backgroundFetchIkariamAjaxPage, backgroundFetchIkariamFullPage: backgroundFetchIkariamFullPage, parseUrlParams: parseUrlParams, parseIkariamTimestamp: parseIkariamTimestamp, }; }(); /** * Internationalization and localization routines. */ var Intl = function() { function Localizer(allLanguages) { this.allLanguages = allLanguages; this.languages = ['en']; } $.extend(Localizer.prototype, { localize: function localize(identifier) { var identifierParts = identifier.split('.'); if (arguments.length > 1) { identifierParts = $.makeArray(arguments); } for (var i = 0; i < this.languages.length; i++) { var languageConfig = this.allLanguages[this.languages[i]]; if (languageConfig !== undefined) { var translation = this.find(languageConfig, identifierParts); if (translation !== undefined) { return translation; } } } return '?#!' + identifierParts.join(',') + '!#?'; }, delayedLocalize: function delayedLocalize() { var args = arguments; var t = this; return function doLocalize() { return t.localize.apply(t, args); }; }, find: function find(config, parts) { for (var i = 0; i < parts.length; i++) { config = config[parts[i]]; if (config === undefined) { return undefined; } } return config; }, setPreferredLanguage: function setPreferredLanguage(language) { this.languages.unshift(language); }, }); var baseLocalizer = new Localizer({ en: { formatting: { "thousandsSeparator": ",", "unknown": "?", hourFormatAMPM: true, }, timeunits: { long: { day: 'Day', hour: 'Hour', week: 'Week', }, short: { day: 'D', hour: 'h', minute: 'm', second: 's', }, complete: '-', yesterday: 'yesterday', tomorrow: 'tomorrow', today: 'today', am: 'AM', pm: 'PM', }, settings: { script_settings: 'Script Options', settings: 'Settings', save: 'Save', }, }, de: { formatting: { "thousandsSeparator": ".", "unknown": "?", hourFormatAMPM: false, }, timeunits: { long: { day: 'Tag', hour: 'Stunde.', week: 'Woche', }, short: { day: 'T', hour: 'h', minute: 'm', second: 's', }, complete: '-', yesterday: 'gestern', tomorrow: 'morgen', today: 'heute', am: 'AM', pm: 'PM', }, settings: { script_settings: 'Script Optionen', settings: 'Einstellungen', save: 'speichern', }, }, hu: { formatting: { "thousandsSeparator": ",", "unknown": "?", hourFormatAMPM: true, }, timeunits: { long: { day: 'Nap', hour: 'Óra', week: 'Hét', }, short: { day: 'n', hour: 'ó', minute: 'p', second: 'mp', }, complete: '-', yesterday: 'tegnap', tomorrow: 'holnap', today: 'ma', am: 'Délelőtt', pm: 'Délután', }, settings: { script_settings: 'Script Beállítások', settings: 'Beállítások', save: 'Mentés', }, }, pl: { formatting: { "thousandsSeparator": ",", "unknown": "?", hourFormatAMPM: true, }, timeunits: { long: { day: 'Dzien', hour: 'Godzina', week: 'Tydzien', }, short: { day: 'D', hour: 'h', minute: 'm', second: 's', }, complete: '-', yesterday: 'wczoraj', tomorrow: 'jutro', today: 'dzis', am: 'AM', pm: 'PM', }, settings: { script_settings: 'Opcje Skryptu', settings: 'Ustawienia', save: 'Zapisz', }, }, ro: { formatting: { "thousandsSeparator": ",", "unknown": "?", hourFormatAMPM: false, }, timeunits: { long: { day: 'Zi', hour: 'Ora', week: 'Saptamana', }, short: { day: 'D', hour: 'h', minute: 'm', second: 's', }, complete: '-', yesterday: 'ieri', tomorrow: 'maine', today: 'azi', am: 'AM', pm: 'PM', }, settings: { script_settings: 'Optiuni Script', settings: 'Setari', save: 'Salveaza', }, }, tr: { formatting: { "thousandsSeparator": ",", "unknown": "?", hourFormatAMPM: false, }, timeunits: { long: { day: 'Gün', hour: 'Saat', week: 'Hafta', }, short: { day: 'Gn', hour: 'Sa', minute: 'Dk', second: 'Sn', }, complete: '-', yesterday: 'dün', tomorrow: 'yarın', today: 'bugün', am: 'AM', pm: 'PM', }, settings: { script_settings: 'Script Seçenekleri', settings: 'Ayarlar', save: 'Kaydet', }, }, }); function formatInteger(number, forceShowSign) { if (number === undefined || isNaN(number)) { return baseLocalizer.localize('formatting.unknown'); } number = Math.floor(number); var separator = baseLocalizer.localize('formatting.thousandsSeparator'); var s = number.toString(); var sign = forceShowSign ? '+' : ''; if (number === 0) { sign = ''; } else if (number < 0) { sign = '-'; s = s.substring(1); } var i = s.length - 3; while (i > 0) { s = s.substring(0, i) + separator + s.substring(i); i -= 3; } return sign + s; }; function formatDecimal(number, digits, forceShowSign) { if (number === undefined || isNaN(number)) { return baseLocalizer.localize('formatting.unknown'); } var value = number.toFixed(digits); if (forceShowSign && number >= 0) { // .5 * Math.pow(10, -digits)) { value = '+' + value; } return value; } function formatRemainingTime(millis, complete, maxResolution) { maxResolution = maxResolution || 4; if (millis == Number.POSITIVE_INFINITY) { return '∞'; } var seconds = Math.ceil(millis / Constants.Time.MILLIS_PER_SECOND); if (seconds <= 0) { return complete || baseLocalizer.localize('timeunits','complete'); } var parts = []; if (maxResolution >= 1) { parts.push({value: Math.floor((seconds / 60 / 60 / 24)), label: 'day'}); } if (maxResolution >= 2) { parts.push({value: Math.floor((seconds / 60/ 60) % 24), label: 'hour' }); } if (maxResolution >= 3) { parts.push({value: Math.floor((seconds / 60) % 60), label: 'minute' }); } if (maxResolution >= 4) { parts.push({value: Math.floor((seconds) % 60), label: 'second' }); } while (parts[0] && !parts[0].value) { parts.shift(); } parts.splice(2); return $.map(parts, function(part) { return '%s%s'.format(part.value, baseLocalizer.localize('timeunits','short',part.label)); }).join(' '); } function padLeft(s, length, char) { while (s.length < length) { s = char + s; } return s; } function formatAbsoluteTime(date) { var now = new Date(); var dateDay = Math.floor(date.valueOf() / Constants.Time.MILLIS_PER_DAY - date.getTimezoneOffset() / Constants.Time.MINUTES_PER_DAY); var nowDay = Math.floor(now.valueOf() / Constants.Time.MILLIS_PER_DAY - now.getTimezoneOffset() / Constants.Time.MINUTES_PER_DAY); var dayString = ''; if (dateDay == nowDay + 1) { dayString = baseLocalizer.localize('timeunits','tomorrow'); } else if (dateDay == nowDay - 1) { dayString = baseLocalizer.localize('timeunits','yesterday'); } else if (dateDay == nowDay) { dayString = baseLocalizer.localize('timeunits','today'); } else { dayString = date.toLocaleDateString(); } var timeString = ''; if (baseLocalizer.localize('formatting', 'hourFormatAMPM')) { var m = ''; if (date.getHours() == 0) { timeString = '12'; m = baseLocalizer.localize('timeunits','am'); } else if (date.getHours() == 12) { timeString = '12'; m = baseLocalizer.localize('timeunits','pm'); } else if (date.getHours() > 12) { timeString = (date.getHours() - 12).toString(); m = baseLocalizer.localize('timeunits','pm'); } else { timeString = date.getHours().toString(); m = baseLocalizer.localize('timeunits','am'); } timeString = timeString + ':' + padLeft(date.getMinutes().toString(), 2, 0) + ' ' + m; } else { timeString = date.getHours().toString() + ':' + padLeft(date.getMinutes().toString(), 2, '0'); } return dayString + ' ' + timeString; } return { Localizer: Localizer, localizer: baseLocalizer, formatInteger: formatInteger, formatDecimal: formatDecimal, formatRemainingTime: formatRemainingTime, formatAbsoluteTime: formatAbsoluteTime, }; }(); var View = function() { function getDomain() { return document.domain; } function getCurrentCityId() { var relatedCityData = unsafeWindow.ikariam.model.relatedCityData; return relatedCityData[relatedCityData.selectedCity].id; } function getCurrentCity() { return EmpireData.getCity(getCurrentCityId()); } function isActiveCity(city) { return city.getId() == getCurrentCityId(); } function getCurrentIslandId() { // This is available in javascript data in island and city views (unfortunately at // different locations). It does not appear to be anywhere in world view data. return parseInt($("#js_islandBread").attr("href").match(/islandId=(\d+)/)[1]); }; function getViewType() { return unsafeWindow.ikariam.backgroundView.id; } function viewIsIsland() { return getViewType() == 'island'; } function viewIsCity() { return getViewType() == 'city'; } function getIkariamBaseViewParams() { var mainboxX = unsafeWindow.ikariam.mainbox_x; var mainboxY = unsafeWindow.ikariam.mainbox_y; var mainboxZ = unsafeWindow.ikariam.mainbox_z; if (mainboxX || mainboxY || mainboxZ) { return { mainbox_x: mainboxX, mainbox_y: mainboxY, mainbox_z: mainboxZ, }; } return {}; } function makeFullIkariamUrl(params, anchor) { return 'http://' + getDomain() + '/index.php?' + $.map(params, function(value, key) { return encodeURIComponent(key) + '=' + encodeURIComponent(value); }).join('&') + (anchor ? '#' + anchor : ''); } function makeLocalIkariamUrl(params) { return '?' + $.map(params, function(value, key) { return key + '=' + value; }).join('&'); } function loadLocalIkariamUrl(url) { Logging.debug("loadLocalIkariamUrl: ", url); // This is an odd way to make the ajaxHandlerCall rather than just calling it directly. // It is done this way so that in Chrome the actions run when the response is recieved // are run in the page's javascript context instead of the javascript context of the // TamperMonkey extension. This is necessary to properly evaluate script actions in // returned ikariam pages or stuff like transport sliders simply will not work. document.location = 'javascript:ajaxHandlerCall(' + JSON.stringify(url) + '); void(0);'; } function goToIkariamPage(city, mainView, mainParams, view, viewParams, anchor) { var changeParams = { // Whacked up logic I don't really understand that makes transitioning to // island mine/mill pages work. Yes, it's completely incomprehensible how Ikariam // developers could screw this up, but somehow they can make the mill go to the mine // if you say the old view is island when you want to go to an island page. // Truly incredible! oldView: mainView == 'island' ? getViewType() : mainView, action: 'header', function: 'changeCurrentCity', cityId: city.getId(), actionRequest: unsafeWindow.ikariam.model.actionRequest, }; $.extend(changeParams, getIkariamBaseViewParams(), mainParams); if (view) { $.extend(changeParams, viewParams); changeParams.templateView = view; } $.extend(changeParams, { backgroundView: mainView } ); if (mainView == 'island' && view && !anchor) { // Stupid ikariam developers still include the city preselect when we ask for a // specific view. Which will overwrite whatever view (mine/mill/wonder) we // actually want to see. Set this hack to suppress that transition when the page // loads. anchor = 'ikaScriptToolsSuppressCityPreselect'; } if (getViewType() == mainView) { loadLocalIkariamUrl(makeLocalIkariamUrl(changeParams)); return; } var url = makeFullIkariamUrl(changeParams, anchor); Logging.debug('goToIkariamPage: ', url); window.location.assign(url); } function goToLocalView(view, params) { loadLocalIkariamUrl(makeLocalIkariamUrl($.extend({view: view}, params))); } function goToCitysIslandView(city, view, params) { if (isActiveCity(city) && viewIsIsland() && getCurrentIslandId() == city.getIslandId()) { if (view) { loadLocalIkariamUrl(makeLocalIkariamUrl($.extend({view: view}, params))); } } else if (viewIsIsland()) { goToIkariamPage(city, 'island', null, view, params); } else { goToIkariamPage(city, 'island', null, null, null, makeIkariamLoadLocalPageAnchor($.extend({view: view}, params))); } } function goToIslandView(city, islandId, view, params) { if (isActiveCity(city) && viewIsIsland() && getCurrentIslandId() == islandId) { if (view) { loadLocalIkariamUrl(makeLocalIkariamUrl($.extend({view: view}, params))); } } else { goToIkariamPage(city, 'island', { currentIslandId: islandId }, view, params); } } function goToIkariamFullPage(params, anchor) { url = makeFullIkariamUrl(params, anchor); Logging.debug('goToIkariamFullPage: ', url); window.location.replace(url); } function makeIkariamLoadLocalPageAnchor(params, doNotSuppressFirstCityInfo) { if (doNotSuppressFirstCityInfo) { return 'ikaScriptToolsLoadLocalIkariamUrl_DoNotSuppressFirstCityInfo=' + encodeURIComponent(makeLocalIkariamUrl(params)); } else { return 'ikaScriptToolsLoadLocalIkariamUrl=' + encodeURIComponent(makeLocalIkariamUrl(params)); } } function goToCityView(city, view, params) { if (isActiveCity(city) && viewIsCity()) { if (view) { loadLocalIkariamUrl(makeLocalIkariamUrl($.extend({view: view}, params))); } } else { goToIkariamPage(city, 'city', null, view, params); } } function activateCity(city) { if (!isActiveCity(city)) { goToIkariamPage(city, getViewType()); } } var suppressingChangeView = false; var superSuppressChangeView = Utils.resettable(Utils.fixedFunction(false)); var initialPageAjaxResponse = Utils.thunk(function findInitialPageAjaxResponse() { var regex = /ikariam.getClass\(ajax.Responder, (.*)\);/; var response = []; $('script').each(function findInitialPageAjaxResponse(index, script) { var match = regex.exec(script.innerHTML); if (match) { response = JSON.parse(match[1]); } }); return response; }); unsafeWindow.ajax.Responder.changeView = function(changeView) { return Logging.debuggable( 'IkaTools.View.changeViewReplacement', function customChangeView(params) { if (suppressingChangeView && suppressingChangeView == params[0]) { Logging.debug("Suppressing change to view: ", params[0]); } else if (superSuppressChangeView() == params[0]) { superSuppressChangeView.reset(); Logging.debug("Super suppressing change to view", params[0]); } else { changeView.apply(this, arguments); } }); }(unsafeWindow.ajax.Responder.changeView); var ajaxResponseEvent = new Utils.EventDispatcher(); function registerIkariamAjaxResponseCallback(f, fireInitialPageView) { var canceller = ajaxResponseEvent.addListener(f); if (fireInitialPageView) { f(initialPageAjaxResponse()); } return canceller; } var suppressNextAjaxChangeView = Utils.resettable(Utils.fixedFunction(null)); function suppressChangeViewOfNextAjaxResponse(type) { suppressNextAjaxChangeView.set(type); } function suppressFirstChangeViewOfType(type) { superSuppressChangeView.set(type); } var nextAjaxResponseEvent = Utils.resettable(function() { return new Utils.EventDispatcher(); }); function registerNextIkariamAjaxRequestCallback(f) { return nextAjaxResponseEvent().addListener(f); } function replaceExecuteAjaxRequest(executeAjaxRequest) { return function customExecuteAjaxRequest() { var ajaxEvent = nextAjaxResponseEvent.reset(); var suppressChangeView = suppressNextAjaxChangeView.reset(); var args = $.makeArray(arguments); args.push(undefined); if (!args[1]) { args[1] = function customAjaxCallback(responseText) { suppressingChangeView = suppressChangeView; var responder = unsafeWindow.ikariam.getClass( unsafeWindow.ajax.Responder, responseText); unsafeWindow.ikariam.controller.ajaxResponder = responder; suppressingChangeView = null; ajaxResponseEvent.send(responder.responseArray); ajaxEvent.send(responder.responseArray); } args[1].isScriptInterceptor = true; } else if (args[1].isScriptInterceptor) { // Allows multiple instances of this script to work var func = args[1]; args[1] = function customAjaxCallbackWrapper() { suppressingChangeView = suppressChangeView; func.apply(this, arguments); suppressingChangeView = null; var responseArray = unsafeWindow.ikariam.controller.ajaxResponder.responseArray; ajaxResponseEvent.send(responseArray); ajaxEvent.send(responseArray); } } var ret = executeAjaxRequest.apply(this, args); }; } if (unsafeWindow.ikariam.controller) { unsafeWindow.ikariam.controller.executeAjaxRequest = replaceExecuteAjaxRequest(unsafeWindow.ikariam.controller.executeAjaxRequest); } else { unsafeWindow.ikariam.Controller.executeAjaxRequest = replaceExecuteAjaxRequest(unsafeWindow.ikariam.Controller.executeAjaxRequest); } var ajaxFormEvent = new Utils.EventDispatcher(); unsafeWindow.ajaxHandlerCallFromForm = function(ajaxHandlerCallFromForm) { return function customerAjaxHandlerCallFromForm(form) { ajaxFormEvent.send(form); return ajaxHandlerCallFromForm.apply(this, arguments); }; }(unsafeWindow.ajaxHandlerCallFromForm); function registerAjaxFormSubmitCallback(f) { return ajaxFormEvent.addListener(f); } var gameTimeDifference = 0; function setGameTimeDifference(value) { Logging.debug("Game time difference: ", value); gameTimeDifference = value; } function getGameTimeDifference() { return gameTimeDifference; } function gameTimeNow() { return new Date().getTime() - gameTimeDifference; } return { getDomain: getDomain, getCurrentCityId: getCurrentCityId, getCurrentCity: getCurrentCity, isActiveCity: isActiveCity, getCurrentIslandId: getCurrentIslandId, getViewType: getViewType, viewIsIsland: viewIsIsland, viewIsCity: viewIsCity, goToCitysIslandView: goToCitysIslandView, goToCityView: goToCityView, goToIslandView: goToIslandView, goToLocalView: goToLocalView, activateCity: activateCity, goToIkariamFullPage: goToIkariamFullPage, makeIkariamLoadLocalPageAnchor: makeIkariamLoadLocalPageAnchor, //registerViewChangedListener: registerViewChangedListener, suppressChangeViewOfNextAjaxResponse: suppressChangeViewOfNextAjaxResponse, suppressFirstChangeViewOfType: suppressFirstChangeViewOfType, registerNextIkariamAjaxRequestCallback: registerNextIkariamAjaxRequestCallback, registerIkariamAjaxResponseCallback: registerIkariamAjaxResponseCallback, registerAjaxFormSubmitCallback: registerAjaxFormSubmitCallback, loadLocalIkariamUrl: loadLocalIkariamUrl, setGameTimeDifference: setGameTimeDifference, getGameTimeDifference: getGameTimeDifference, gameTimeNow: gameTimeNow, }; }(); /** * Data value class for encapsulating GM_getValue/GM_setValue access and * serialization/deserialization. */ var Data = function() { function Value(key, defaultValue, options) { this.options = $.extend({ useDomain: true, loadCallback: function() {} }, options); if (this.options.useDomain) { this.key = View.getDomain() + "/" + key + "/" + (options.version || ikaToolsVersion) + '-' + ikaToolsVersion; } else { this.key = key + "/" + ikaToolsVersion; } this.defaultValue = defaultValue; this.data = defaultValue; this.needsSave = false; } $.extend(Value.prototype, { load: function load() { var rawValue = GM_getValue(this.key, "null"); if (rawValue !== undefined) { var data = JSON.parse(rawValue, this.options.reviver); Logging.debug('Loaded data "%s": %s -> %o', this.key, rawValue, data); if (data !== null) { this.data = data; } } else { } this.loaded = true; this.options.loadCallback(this.data); return this.data; }, save: function save() { return this.doSave(true); }, doSave: function doSave(force) { if (this.needsSave || force) { var value = JSON.stringify(this.data, this.options.stringifier); Logging.debug('Saved data "%s": %o -> %s', this.key, this.data, value); GM_setValue(this.key, value); this.needsSave = false; } return this.data; }, saveAsync: function saveAsync() { this.needsSave = true; setTimeout(Logging.debuggable('IkaTools.Data.Value[' + this.key + ']', this.doSave.bind(this, false)), 0); }, get: function get() { if (!this.loaded) { var value = this.load(); return value; } return this.data; }, set: function set(data) { this.data = data; return data; }, reset: function reset() { this.set(this.defaultValue); this.save(); }, }); return { Value: Value, }; }(); var UI = function() { function ToolTipHandler(toolTipClass, toolTipContainer, options) { this.toolTips = {}; this.options = $.extend({ delay: 200, activeClass: 'active', offsetX: 0, offsetY: 20, toolTipClass: toolTipClass, toolTipContainer: toolTipContainer || $('<div/>'), }, options); this.toolTipContainer = $('<div style="position: absolute; z-index: 100000; display:none;"/>'); this.toolTipContainer.append(this.options.toolTipContainer); this.activeToolTipElement = null; this.pendingShowEvent = null; this.activeToolTip = null; var body = $('body'); body.append(this.toolTipContainer); } $.extend(ToolTipHandler.prototype, { _getCurrentToolTip: function _getCurrentToolTip() { if (this.activeToolTipElement) { var id = this.activeToolTipElement.id; if (id) { return this.toolTips[id]; } } }, _reset: function _reset() { clearTimeout(this.pendingShowEvent); this.toolTipContainer.hide(); var toolTipInfo = this._getCurrentToolTip(); if (this.activeToolTip && this.activeToolTip.deactivated) { this.activeToolTip.deactivated($(this.activeToolTipElement)); } this.activeToolTip = null; this.options.toolTipContainer.empty(); this.activeToolTipElement = null; }, _showToolTip: function _showToolTip() { var toolTipInfo = this._getCurrentToolTip(); if (toolTipInfo) { this.activeToolTip = toolTipInfo.contentCreator($(this.activeToolTipElement)); this.options.toolTipContainer.append(this.activeToolTip); this.toolTipContainer.show(); } }, _mouseOver: function _mouseOver(e) { var toolTipElement = $(e.target).closest('.' + this.options.toolTipClass); if (toolTipElement.get(0) == this.activeToolTipElement) { return; } this._reset(); if (toolTipElement.length > 0) { this.activeToolTipElement = toolTipElement[0]; this.toolTipContainer.css({ left: (e.pageX + this.options.offsetX) + 'px', top: (e.pageY + this.options.offsetY) + 'px', }); this.pendingShowEvent = setTimeout(IkaTools.Logging.debuggable( 'IkaTools.UI.ToolTipHandler.showToolTip[' + this.options.toolTipClass + ']', this._showToolTip.bind(this)), this.options.delay); } }, _mouseOut: function _mouseOut(e) { if (this.activeToolTipElement) { var target = $(e.relatedTarget).closest('.' + this.options.toolTipClass); if (target.get(0) != this.activeToolTipElement) { this._reset(); return; } } }, _mouseMove: function _mouseMove(e) { if (this.activeToolTipElement && !this.activeToolTip) { this.toolTipContainer.css({ left: (e.pageX + this.options.offsetX)+ 'px', top: (e.pageY + this.options.offsetY) + 'px', }); } }, register: function registerToolTip(id, contentCreator) { this.toolTips[id] = { contentCreator: contentCreator, }; }, registerSimple: function registerSimpleToolTip(id, content) { this.register(id, function() { return $(content); }); }, registerRefreshable: function registerRefreshableToolTip(id, contentGenerator) { var toolTip = this; this.register(id, function() { var id = Utils.nextId(); var interval = setInterval(Logging.debuggable('IkaTools.ToolTip.refresh[' + id + ']', function refreshToolTip() { toolTip.options.toolTipContainer.html(contentGenerator()); }), Constants.Time.MILLIS_PER_SECOND); var tip = $(contentGenerator()); tip.deactivated = function() { clearInterval(interval); }; return tip; }); }, deregister: function deregister(id) { delete this.toolTips[id]; }, startHandling: function startHandling(element) { element.on('mouseover'/*, '.' + this.options.toolTipClass*/, Logging.debuggable( 'IkaTools.UI.ToolTipHandler.mouseOver[' + this.options.toolTipClass + ']', this._mouseOver.bind(this))); element.on('mouseout'/*, '.' + this.options.toolTipClass*/, Logging.debuggable( 'IkaTools.UI.ToolTipHandler.mouseOut[' + this.options.toolTipClass + ']', this._mouseOut.bind(this))) element.on('mousemove', '.' + this.options.toolTipClass, Logging.debuggable( 'IkaTools.UI.ToolTipHandler.mouseMove[' + this.options.toolTipClass + ']', this._mouseMove.bind(this))) } }); function LeftMenu(items, options) { this.items = items; this.active = false; this.options = $.extend({atTop: false }, options); } $.extend(LeftMenu.prototype, { ITEM_CONTRACTED_WIDTH: 53, ITEM_EXPANDED_WIDTH: 199, ITEM_Z_INDEX_EXPANDED: 120000, ITEM_Z_INDEX_CONTRACTED: 65, ANIMATION_DURATION: 300, display: function display() { // Add leftMenu div and "standard" contents if we are in a // view where it is not already present var leftMenuDiv = $('#leftMenu'); if (!leftMenuDiv.length) { leftMenuDiv = $('<div id="leftMenu" >' + '<div class="slot_menu city_menu" style="z-index: 65; ">' + '<ul class="menu_slots"/>' + '</div>' + '</div>'); $('#container').append(leftMenuDiv); } // Setup event handlers for (var i = 0; i < this.items.length; i++) { var item = this.items[i]; item.element.width(this.ITEM_COLLAPSED_WIDTH + 'px'); item.element.mouseenter(this.expand.bind(this, item)); item.element.mouseleave(this.contract.bind(this, item)); } this.holderDiv = $('.slot_menu', leftMenuDiv); this.holderDiv.hover(this.menuActivated.bind(this), this.menuPassivated.bind(this)); // Add elements to ui var menuSlots = $('ul.menu_slots', leftMenuDiv); if (this.options.atTop) { for (var i = this.items.length - 1; i >= 0; i--) { menuSlots.prepend(this.items[i].element); } } else { for (var i = 0; i < this.items.length; i++) { menuSlots.append(this.items[i].element); } } }, menuActivated: function menuActivated() { this.active = true; this.holderDiv.css('z-index', this.ITEM_Z_INDEX_EXPANDED); }, menuPassivated: function menuPassivated() { this.active = false; }, contract: function contract(item) { var holder = item.element.parent().parent(); item.element.animate( { width: this.ITEM_CONTRACTED_WIDTH }, 300, 'swing', this.contractComplete.bind(this) ); }, contractComplete: function contractComplete() { if (!this.active) { this.holderDiv.css('z-index', this.ITEM_Z_INDEX_CONTRACTED); } }, expand: function expand(item) { item.element.animate( { width: this.ITEM_EXPANDED_WIDTH }, 300, 'swing'); this.holderDiv.css('z-index', this.ITEM_Z_INDEX_EXPANDED); }, }); LeftMenu.Item = function LeftMenu_Item(element) { this.element = element; } function resizePopup() { unsafeWindow.ikariam.controller.adjustSizes(); } var destroyedTemplateViewEvent = Utils.thunk(function() { var dispatcher = new Utils.EventDispatcher(); var oldDestroyTemplateView = unsafeWindow.ikariam.TemplateView.destroyTemplateView; unsafeWindow.ikariam.TemplateView.destroyTemplateView = function customDestroyTemplateView() { oldDestroyTemplateView.apply(this, arguments); dispatcher.send(); }; return dispatcher; }); function PopupWindow(id, header, content, options) { this.id = id; this.headerElement = $(header); this.contentElement = $(content); this.options = $.extend({ sidebars: [], oversized: false, activatedCallback: function() {}, deactivatedCallback: function() {}, }, options); this.isActive = false; destroyedTemplateViewEvent().addListener(this._popupDestroyed.bind(this)); } $.extend(PopupWindow.prototype, { _popupDestroyed: function _popupDestroyed() { if (this.isActive) { this.options.deactivatedCallback(this); } this.isActive = false; }, display: function display(skipResize) { // Always display it. There's no good way to track if it is // already displayed because there is no callback when it is destroyed. // (One can replace unsafeWindow.ikariam.destroyTemplateView, but there // are still some issues with quickly switching between views that can // mess things up and will still be considered "active" when its not visible. templateViewArg = { boxId: this.id, headerElem: this.headerElement.html(), contentElem: '<div><div id="ikaPopupTempHolder"></div></div>', sidebarEls: this.options.sidebars, oversized: this.options.oversized, replaceBox: true, keepSidebars: false }; this.isActive = true; this.activePopup = unsafeWindow.ikariam.createTemplateView(templateViewArg); // Null out the id or submitting the change city form will send it as the // templateView unsafeWindow.ikariam.templateView.id = null; unsafeWindow.ikariam.model.viewParams = null; $('#ikaPopupTempHolder').replaceWith(this.contentElement); this.options.activatedCallback(this); if (skipResize) { unsafeWindow.ikariam.controller.adjustSizes(); } }, close: function close() { if (this.isActive) { unsafeWindow.ikariam.TemplateView.destroyTemplateView(); } } }); function TabPane(tabs, options) { this.tabs = tabs; this.options = $.extend({ tabActivatedCallback: function() {} }, options); this.currentTab = null; this.container = $("<div/>"); var tabsContainer = $('<ul class="tabmenu"/>'); this.container.append(tabsContainer); for (var i = 0; i < tabs.length; i++) { var tab = tabs[i]; tab.tabPane = this; tabsContainer.append(tab.tabHolder) this.container.append(tab.contentHolder); tab.contentHolder.hide(); tab.tabHolder.click( IkaTools.Logging.debuggable('IkaTools.TabPane.Tab.click', this.activate.bind(this, tab))); } } $.extend(TabPane.prototype, { getContainer: function getContainer() { return this.container; }, activate: function activate(tab) { if (this.currentTab !== tab) { if (this.currentTab) { this.currentTab.contentHolder.hide(); this.currentTab.tabHolder.removeClass('selected'); this.currentTab.deactivated(); } tab.contentHolder.show(); tab.tabHolder.addClass('selected'); } tab.activated(); this.options.tabActivatedCallback(this.currentTab, tab); this.currentTab = tab; } }); TabPane.Tab = function Tab(tab, content, options) { this.tab = $(tab); this.content = $(content); this.options = $.extend({ activatedCallback: function() {}, deactivatedCallback: function() {} }, options); this.contentHolder = $('<div/>'); this.contentHolder.append(this.content); this.tabHolder = $('<li class="tab"/>'); this.tabHolder.append(this.tab); } $.extend(TabPane.Tab.prototype, { activate: function activate() { this.tabPane.activate(this); }, activated: function activated() { this.options.activatedCallback(this); }, deactivated: function deactivated() { this.options.deactivatedCallback(this); }, }); function SettingsWindow(id, scriptName, settings, settingGroups) { this.id = id; this.settings = settings; this.scriptName = scriptName; this.settingGroups = settingGroups; this.saveEvent = new Utils.EventDispatcher(); } $.extend(SettingsWindow.prototype, { show: function show() { var tabs = $.map(this.settingGroups, function makeTab(group, index) { var content = $.map(group.getSettings(), this.renderSetting.bind(this)).join(''); content = '<div class="contentBox01h">' + '<h3 class="header">' + Intl.localizer.localize('settings','settings') + '</h3>' + '<div class="content">' + '<table class="table01"><tbody>' + content + '</tbody></table>' + '</div>' + '<div class="footer"/>' + '</div>' + '<div class="centerButton">' + '<a class="ikaScriptToolsSaveOptions button">' + Intl.localizer.localize('settings','save') + '</a>' + '</div>'; return new TabPane.Tab('<b>' + group.getName() + '</b>', content, {}); }.bind(this)); var tabPane = new TabPane(tabs, { tabActivatedCallback: function() { IkaTools.UI.resizePopup(); }, }); var popup = new PopupWindow( 'options', $('<div>' + Intl.localizer.localize('settings','script_settings') + ': ' + this.scriptName + '</div>'), tabPane.getContainer()); tabs[0].activate(); popup.display(); $.each(this.settingGroups, function postRenderSettingsGroup(index, group) { $.each(group.getSettings(), this.postRenderSetting.bind(this)); }.bind(this)); $('.ikaScriptToolsSaveOptions').click(Logging.debuggable( 'IkaTools.UI.SettingsWindow.saveSettings', this._save.bind(this))); }, renderSetting: function renderSetting(setting, index) { var type = setting.getType(); var html = '<tr><td>' + setting.getLabel()() + '</td><td class="left">'; if (type == Settings.Type.BOOLEAN) { html += '<input id="ikaScriptToolsSettingInput_' + setting.getName() + '" type="checkbox" ' + (setting.isEnabled() ? 'checked' : '' ) + '/>'; } else if (type == Settings.Type.CHOICE) { html += '<select id="ikaScriptToolsSettingInput_' + setting.getName() + '">'; $.each(setting.getChoices(), function renderOption(key, value) { html += '<option value="' + value + '"' + (setting.getValue() == value ? 'selected="selected"' : '') + '>' + key + '</option>'; }) html += '</select>'; } else if (type == Settings.Type.HTML) { html += setting.getHtml()(); } else if (type == Settings.Type.TEXT) { html += '<input id="ikaScriptToolsSettingInput_' + setting.getName() + '" value="' + setting.getValue() + '"/>'; } html += '</td>'; return html; }, postRenderSetting: function postRenderSetting(index, setting) { var type = setting.getType(); if (type == Settings.Type.HTML) { setting.getPostRender()(); } }, _save: function save() { $.each(this.settingGroups, function saveGroup(index, group) { $.each(group.getSettings(), this._saveSetting.bind(this)); }.bind(this)); this.settings.save(); this.saveEvent.send(this); }, _saveSetting: function saveSetting(index, setting) { var type = setting.getType(); if (type == Settings.Type.BOOLEAN) { setting.setEnabled( $('#ikaScriptToolsSettingInput_' + setting.getName()).is(':checked')); } else if (type == Settings.Type.CHOICE) { setting.setValue( $('#ikaScriptToolsSettingInput_' + setting.getName()).val()); } else if (type == Settings.Type.TEXT) { setting.setValue( $('#ikaScriptToolsSettingInput_' + setting.getName()).val()); } }, registerSavedSettingsHandler: function registerSavedSettingsHandler(f) { return this.saveEvent.addListener(f); }, addAsScriptOptionsLink: function addAsScriptOptionsLink() { if($('#IkaScriptToolSettingsDropdown').size() == 0) { GM_addStyle( '#IkaScriptToolSettingsDropdown { ' + ' position:absolute; ' + '}' + '#IkaScriptToolSettingsDropdown:hover {' + ' padding-bottom:20px;' + '}' + '#IkaScriptToolSettingsDropdown #IkaScriptToolsSettingsDropdownLinks { ' + ' display:none;' + '}' + '#IkaScriptToolSettingsDropdown:hover #IkaScriptToolsSettingsDropdownLinks {' + ' display:block;' + '}' + '#IkaScriptToolsSettingsDropdownLinks { ' + ' background-color:#FFF5E1; ' + ' padding:.5em; ' + ' padding-bottom:0; ' + ' border:1px solid #666; ' + ' position:absolute; ' + ' right:-80px; ' + ' margin-top:2px; ' + ' width:170px;' + '}' + '#IkaScriptToolsSettingsDropdownLinks a { ' + ' color:#666; ' + ' cursor:pointer; ' + ' margin-left:0; ' + ' padding-left:.2em; ' + ' display:block; ' + ' margin-bottom:.5em;' + '}' ); var li = document.createElement('li'); li.id = 'IkaOptionsDropdown'; $('#GF_toolbar ul').append($( '<li id="IkaScriptToolSettingsDropdown">' + '<a href="javascript:void(0);">' + Intl.localizer.localize('settings','script_settings') + '</a>' + '<div id="IkaScriptToolsSettingsDropdownLinks">' + '</li>')); } var link = $('<a>' + this.scriptName + '</a>'); link.click(Logging.debuggable('IkaTools.UI.SettingsWindow.showSettings', this.show.bind(this))); $('#IkaScriptToolsSettingsDropdownLinks').append(link); }, }); SettingsWindow.Group = function(name, settings) { this.name = name; this.settings = settings; } $.extend(SettingsWindow.Group.prototype, { getName: function getName() { return this.name; }, getSettings: function getSettings() { return this.settings; }, }); return { ToolTipHandler: ToolTipHandler, LeftMenu: LeftMenu, resizePopup: resizePopup, PopupWindow: PopupWindow, TabPane: TabPane, SettingsWindow: SettingsWindow, }; }(); var Settings = function() { var Type = { BOOLEAN: 1, CHOICE: 2, HTML: 3, TEXT: 4, } function Settings(name) { this.name = name; this.data = new Data.Value('scriptOptions_' + name, { }, { version: 1 }); } $.extend(Settings.prototype, { _getValue: function getValue(name, defaultValue) { var value = this.data.get()[name]; return value === undefined ? defaultValue : value; }, _setValue: function setValue(name, value) { this.data.get()[name] = value; }, save: function save() { this.data.save(); }, boolean: function boolean(name, enabled, labelFunc) { return new Boolean(this, name, this._getValue(name, enabled), labelFunc); }, choice: function choice(name, value, choices, labelFunc) { return new Choice(this, name, this._getValue(name, value), choices, labelFunc); }, html: function html(htmlFunc, postRender, labelFunc) { return new Html(htmlFunc, postRender, labelFunc); }, text: function text(name, value, labelFunc) { return new Text(this, name, this._getValue(name, value), labelFunc); } }); function Boolean(settings, name, enabled, labelFunc) { this.settings = settings; this.name = name; this.enabled = enabled; this.labelFunc= labelFunc; } $.extend(Boolean.prototype, { isEnabled: function isEnabled() { return this.enabled; }, setEnabled: function(enabled) { this.enabled = enabled; this.settings._setValue(this.name, enabled); }, getName: function getName() { return this.name; }, getType: function getType() { return Type.BOOLEAN; }, getLabel: function getLabel() { return this.labelFunc; }, }); function Choice(settings, name, value, choices, labelFunc) { this.settings = settings; this.name = name; this.value = value; this.choices = choices; this.labelFunc = labelFunc; } $.extend(Choice.prototype, { getValue: function getValue() { return this.value; }, setValue: function setValue(value) { this.value = value; this.settings._setValue(this.name, value); }, getChoices: function getChoices() { return this.choices; }, getName: function getName() { return this.name; }, getType: function getType() { return Type.CHOICE; }, getLabel: function getLabel() { return this.labelFunc; }, }); function Html(htmlFunc, postRender, labelFunc) { this.labelFunc = labelFunc; this.htmlFunc = htmlFunc; this.postRender = postRender; } $.extend(Html.prototype, { getHtml: function getHtml() { return this.htmlFunc; }, getPostRender: function getPostRender() { return this.postRender; }, getType: function getType() { return Type.HTML; }, getLabel: function getLabel() { return this.labelFunc; }, }); function Text(settings, name, value, labelFunc) { this.settings = settings; this.name = name; this.value = value; this.labelFunc = labelFunc; } $.extend(Text.prototype, { getValue: function getValue() { return this.value; }, setValue: function setValue(value) { this.value = value; this.settings._setValue(this.name, value); }, getName: function getName() { return this.name; }, getType: function getType() { return Type.TEXT; }, getLabel: function getLabel() { return this.labelFunc; }, }); return { Settings: Settings, Type: Type, }; }(); var Constants = { Resources: { WOOD: 'wood', WINE: 'wine', MARBLE: 'marble', GLASS: 'glass', SULFUR: 'sulfur', POPULATION: 'population', CITIZENS: 'citizens', SCIENTISTS: 'scientists', ACTION_POINTS: 'actionPoints', CULTURAL_GOODS: 'culturalGoods', TAVERN_WINE_LEVEL: 'tavernWineLevel', PRIESTS: 'priests', }, CivilizationData: { GOVERNMENT: 'government', RESEARCH: 'research', MOVEMENT: 'movement', PREMIUM_FEATURE: 'premiumFeature', }, PremiumFeatures: { DOUBLED_STORAGE_CAPACITY: 'doubledStorageCapacity', DOUBLED_SAFE_CAPACITY: 'doubledSafeCapacity', }, Movements: { Mission: { TRANSPORT: 'transport', DEPLOY_ARMY: 'deployarmy', DEPLOY_NAVY: 'deployfleet', PLUNDER: 'plunder', }, Stage: { LOADING: 'loading', EN_ROUTE: 'en_route', RETURNING: 'returning', }, EventType: { DATA_UPDATED: 'dataUpdated', STAGE_CHANGED: 'stageChanged', CANCELLED: 'cancelled', COMPLETED: 'completed', }, MissionData: { transport: { icon: 'skin/interface/mission_transport.png', }, deployarmy: { icon: 'skin/interface/mission_deployarmy.png', }, deployfleet: { icon: 'skin/interface/mission_deployfleet.png', }, plunder: { icon: 'skin/interface/mission_plunder.png', }, piracyRaid: { icon: 'skin/interface/mission_piracyRaid.png', }, } }, BuildingEventType: { DATA_REFRESH: 'dataRefresh', UPGRADE_COMPLETE: 'upgradeComplete', }, Buildings: { TOWN_HALL: 'townHall', PALACE: 'palace', GOVERNORS_RESIDENCE: 'palaceColony', TAVERN: 'tavern', MUSEUM: 'museum', ACADEMY: 'academy', WORKSHOP: 'workshop', TEMPLE: 'temple', EMBASSY: 'embassy', WAREHOUSE: 'warehouse', DUMP: 'dump', TRADING_PORT: 'port', TRADING_POST: 'branchOffice', BLACK_MARKET: 'blackMarket', MARINE_CHART_ARCHIVE: 'marineChartArchive', WALL: 'wall', HIDEOUT: 'safehouse', BARRACKS: 'barracks', SHIPYARD: 'shipyard', PIRATE_FORTRESS: 'pirateFortress', FORESTER: 'forester', CARPENTER: 'carpentering', WINERY: 'winegrower', WINE_PRESS: 'vineyard', STONEMASON: 'stonemason', ARCHITECT: 'architect', GLASSBLOWER: 'glassblowing', OPTICIAN: 'optician', ALCHEMISTS_TOWER: 'alchemist', FIREWORK_TEST_AREA: 'fireworker', }, // Time data from http://ikariam.wikia.com/wiki/User_blog:Warrior_fr/Actual_building_Time_formula // Rest of data from http://ikariam.wikia.com/ building pages. BuildingData: { academy: { maxlevel:50, wood:[55,58,98,226,328,538,844,1143,1723,2291,3367,4434,6403,8387,10965,1562,20374,28767,37471,48786,63496,88974,124014,150549,209779,272798,378372,461226,639658,883624,1081231,1493547,2063095,2849833,3936586,5437761,7511392,10375779,14332469,19797999,27347750,37776516,52182177,72081279,99568686,137538116,189986771,262436146,362513298,500753777], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], glass:[0,0,0,0,193,368,639,936,1503,211,3255,4485,6761,9226,12555,18599,25216,36997,50063,67702,91516,133177,192765,243011,351634,474841,683916,865717,1246777,1788499,2272591,3259901,4676140,6707654,9621744,13801839,19797943,28399010,40736746,58434518,83820952,120236331,172472097,247401296,354882919,509059121,730216007,1047452831,1502510795,2155265251], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:1440, b:1, c:1.2, d:720}, icon :'/cdn/all/both/img/city/academy_l.png', maxScientists: [0, 8, 12, 16, 22, 28, 35, 43, 51, 60, 69, 79, 89, 100, 111, 122, 134, 146, 159, 172, 185, 198, 212, 227, 241, 256, 271, 287, 302, 318, 335, 351, 368 ], }, alchemist: { maxlevel:61, wood:[235,401,617,898,1263,1738,2354,3157,4199,5554,7316,9607,12585,16456,21488,28030,36535,47591,61963,80649,104938,136516,177565,230931,300306,390494,507737,660154,858296,1115880,1450740,1886057,2451998,3187758,4144296,5387857,7004569,9106400,11838919,15391374,20009798,26014054,33819980,43968198,57161548,74313771,96612788,125602976,163292127,212290500,275991607,358807234,466472992,606445556,788419092,1024996652,1332563034,1732419548,2252259306,2928085165,3806703211], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,99,219,374,577,840,1182,1627,2205,2955,3931,5202,6852,8997,11786,15412,20125,26253,34219,44574,58037,75538,98289,127865,166315,216300,281279,365753,475568,618329,803918,1045183,1358855,1766663,2296859,2986173,3882359,5047500,6562314,8531740,11092214,14421117,18749061,24375871,31691353,41202296,53567581,69643833,90544754,117718283,153046903,198978051,258693667,336330631,437267348,568496341,739108674,960923743,1249308082,1624239900,2111693096], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:72000, b:11, c:1.1, d:6120}, icon :'/cdn/all/both/img/city/alchemist_l.png', }, architect: { maxlevel:50, wood:[159,250,355,477,619,783,974,1195,1452,1750,2095,2495,2960,3500,4125,4850,5692,6668,7800,9114,10637,12404,14454,16832,19591,22791,26503,30809,35804,41599,48319,56116,65170,75686,87899,102082,118554,137684,159900,185702,215666,250465,290880,337815,392325,455629,529148,614530,713689,828848], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[91,137,190,253,325,408,504,615,743,890,1060,1255,1480,1739,2037,2379,2774,3227,3748,4348,5037,5829,6738,7784,8986,10367,11953,13774,15867,18271,21031,24201,27848,32046,36876,42434,48830,56190,64659,74406,85621,98526,113377,130466,150131,172760,198799,228763,263244,302922], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:125660, b:37, c:1.06, d:2628}, icon :'/cdn/all/both/img/city/architect_l.png', }, barracks: { maxlevel:49, wood:[42,98,167,254,361,493,658,862,1115,1429,1818,2301,2899,3641,4561,5701,7116,887,11044,13741,17086,21233,26375,32751,40658,50461,62618,77693,96385,119564,148305,183944,228137,282936,350886,435146,539626,669183,829833,1029039,1276055,1582354,1962165,2433131,3017129,3741286,4639240,5752704,7133399], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,0,0,0,0,0,153,370,640,975,1389,1904,2542,3332,4312,5528,7037,8907,11224,14099,17664,22084,27566,34363,42791,53241,662,82268,102193,126901,157539,195528,242636,30105,373483,4633,574672,712774,884021,1096368,1359677,1686180,2091044,2593076,3215595,3987519,4944704], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:25200, b:11, c:1.1, d:1728}, icon :'/cdn/all/both/img/city/barracks_l.png', }, branchOffice: { maxlevel:72, wood:[41,148,297,499,770,113,1602,2218,3017,4047,5367,7054,9201,11924,15369,19716,25185,32054,40663,51434,64885,81661,10255,12853,1608,200835,250454,311885,387872,481778,597732,740796,917337,1135187,1404015,1735749,2145109,2650258,3273613,4043584,4994655,6169425,7620506,9412889,11626849,14361545,17739456,21911869,27065655,33431640,41294936,51007721,63005003,77824109,96128747,118738731,146666701,181163475,223774071,276406902,341419250,421722842,520914258,643436013,794775525,981710881,1212614410,1497827655,1850124544,2285283504,2822794124,3486730050], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,0,464,681,965,1337,1818,2439,3235,4252,5547,7188,9265,11885,15182,19323,24512,31004,39114,49226,61819,77479,96933,121067,150978,188009,233815,290424,360322,446564,552986,684311,846366,1046343,1293113,1597627,1973398,2437552,3010878,3719054,4593796,5674284,7008907,8657442,10693723,13208947,16315766,20153328,24893505,30748599,37980844,46914155,57948631,71578478,88414142,109209651,134896382,166624778,205815871,254224933,314020081,387879387,479110822,591800408,730995225,902929453,1115303588,1377629325,1701655566,2101894617], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], capacity:[400,1600,3600,6400,10000,14400,19600,25600,32400,40000,48400,57600,67600,78400,90000,102400,115600,129600,144400,160000,176400,193600,211600,230400,250000,270400,291600,313600,336400,360000,384400,409600,435600,462400,490000,518400,547600,577600,608400,640842,675014,711009,748923,788858,830924,875232,921903,971062,1022844,1077386,1134836,1195351,1259092,1326232,1396952,1471443,1549906,1632554,1719608,1811305,1907891,2009628,2116789,2229665,2348560,2473795,2605708,2744655,2891011,3045172,3207553,3378593], time :{a:108000, b:11, c:1.1, d:9360}, icon :'/cdn/all/both/img/city/branchoffice_l.png', }, blackMarket: { maxlevel:25, wood:[378,762,1169,1625,2163,2827,3666,4734,6093,7813,9967,12634,15900,19855,24596,30222,36841,44565,53507,63790,75540,88886,103963,120912,139876], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[223,451,694,968,1297,1709,2236,2915,3786,4895,6290,8024,10154,12738,15841,19528,23871,28942,34817,41579,49307,58089,68014,79175,91664], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:0, b:0, c:0, d:0}, icon :'/cdn/all/both/img/city/blackmarket_l.png', }, marineChartArchive: { maxlevel:40, wood:[497,1116,1834,2667,3634,4755,6056,7564,9314,11344,13698,16431,19599,23275,27538,32484,38221,44877,52596,61551,71939,83990,97968,114183,132992,154810,180120,209478,243534,283039,328865,382024,443687,515217,598191,694442,806092,935607,1085844,1260119], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[297,916,1647,2509,3526,4727,6143,7815,9787,12115,14861,18103,21926,26438,31764,38047,45461,54210,64533,76715,91089,108051,128066,151684,179552,212438,251242,297031,351062,414819,490052,578827,683582,807192,953052,1125168,1328263,1567917,1850707,2184400], glass:[138,525,982,1521,2156,2906,3792,4837,6069,7525,9241,11266,13656,16476,19804,23731,28366,33834,40285,47899,56883,67484,79993,94754,112173,132726,156978,185596,219366,259214,306235,361719,427191,504447,595611,703182,830117,979901,1156644,1365203], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:0, b:0, c:0, d:0}, icon :'/cdn/all/both/img/city/marinechartarchive_l.png', }, carpentering: { maxlevel:50, wood:[54,104,165,235,319,417,533,668,827,1013,1231,1487,1787,2137,2549,3030,3593,4252,5023,5925,6980,8213,9656,11343,13316,15621,18317,21468,25150,29454,34482,40359,47238,55289,64713,75743,88653,103763,121449,142149,166377,194734,227925,266773,312242,365461,427751,500657,585990,685867], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,0,0,0,0,308,381,469,575,701,853,1036,1254,1517,1832,2211,2664,3208,3862,4645,5586,6715,8070,9696,11646,13987,16796,20167,24212,29067,34894,41891,50291,60374,72479,87013,104459,125403,150548,180733,216971,260475,312702,375400,450669,541030,649509,779739], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:125660, b:37, c:1.06, d:2808}, icon :'/cdn/all/both/img/city/carpentering_l.png', }, dump: { maxlevel:80, wood:[550,990,1518,2153,2913,3827,4922,6237,7815,9708,11980,14706,17978,21904,26615,32268,39052,47193,56962,68685,82752,99633,119890,144198,173369,208372,250377,300784,361270,433855,520956,625478,750903,901415,1082027,1298764,1558847,1870946,2245466,2694889,3234264,3881593,4658484,5590867,6709864,8052825,9664577,11598917,13920408,16706541,20050310,24063326,28879536,34659697,41596742,49922218,59914014,71905643,86297363,103569548,124298713,149176766,179034093,214867283,257872389,309484852,371427409,445767602,534986785,642062945,770570110,924797636,1109893386,1332035549,1598638868,1918602120,2302605153,2763465357,3316565485,3980367111], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[427,801,1242,1763,2375,3103,3959,4969,6161,7567,9226,11184,13494,16221,19437,23233,27713,32999,39235,46595,55279,65527,77620,91888,108725,128594,152037,179702,212345,250864,296317,349952,413240,487921,576043,680028,802731,947520,1118371,1319975,1557920,1838759,2170225,2561442,3023182,3568157,4211372,4970538,5866554,6924092,8172266,9645444,11384185,13436361,15858473,18717208,22091275,26073570,30773734,36321177,42868631,50596365,59717144,70482083,83187570,98183417,115882499,136772114,161427405,190527194,224872670,265409450,313253612,369722426,436370616,515033175,607875879,717454917,846787273,999433788], glass:[602,985,1434,1959,2572,3032,4130,5113,6263,7608,9183,11024,13179,15701,18650,22102,26139,30864,36391,42859,50426,59279,69637,81756,95936,112525,131936,154646,173476,212303,248675,291232,341021,399276,467434,547178,640478,749641,877361,1026793,1201676,1406345,1645873,1926197,2254267,2638213,3087553,3613423,4228860,4949118,5792050,6778550,7933070,9284227,10865512,12716122,14881927,17416611,20383001,23854625,27917535,32672439,38237196,44749739,52371496,61291388,71730512,83947623,98245548,114978689,134561813,157480325,184302310,215692604,252429279,295422930,345739242,404625407,473541040,554194356], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], capacity:[32000,65401,101073,139585,181437,227119,277128,331991,392268,458564,531535,611896,700427,797983,905498,1024000,1154614,1298578,1457248,1632119,1824830,2037185,2271165,2528951,2812939,3125764,3470326,3849813,4267731,4727939,5234678,5792619,6406896,7083160,7827629,8647143,9549229,10542172,11635086,12838003,14161964,15619122,17222851,18987875,20930401,23068269,25421121,28010583,30860463,33996977,37448993,41248299,45429902,50032358,55098129,60673986,66811447,73567262,81003948,89190382,98202448,108123754,119046431,131072000,144312338,158890744,174943109,192619216,212084166,233519956,257127222,283127160,311763649,343305589,378049493,416322336,458484710,504934306,556109751,612494861], time :{a:32000, b:13, c:1.17, d:2160}, icon :'/cdn/all/both/img/city/dump_l.png', }, embassy: { maxlevel:78, wood:[208,356,535,750,1008,1317,1689,2134,2668,331,4078,5002,611,7439,9036,1095,13247,16004,19313,23283,28048,33764,40625,48857,58737,70592,84817,101888,122373,146955,176454,211853,254352,305378,366639,440191,528497,634519,761809,914635,1098120,1318413,1582899,1900444,2281690,2739419,3288971,3948770,4740930,5692004,6833873,8204811,9850772,11826928,14199519,17048074,20468075,24574160,29503963,35422730,42528856,51060537,61303752,73601851,88367060,106094308,127377805,152930967,183610329,220444254,264667405,317762127,381508140,458042191,549929678,660250643,792703010,951726544], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[133,294,491,731,1023,1381,1816,2347,2996,3787,4753,593,7366,9119,11257,13865,17048,20931,25667,31446,38497,47097,57591,70393,86012,105066,128312,156673,191273,233485,284984,347812,424491,518075,632291,771688,941816,1149450,1402860,1712137,2089598,2550275,3112514,3798706,4636175,5658276,6905710,8428156,10286243,12553968,15321640,18699477,22822000,27853382,33993991,41488371,50634975,61798058,75422175,92049891,112343384,137110819,167338529,204230298,249255296,304206590,371272550,453123999,553020573,674940534,823739201,1005342303,1226981968,1497484733,1827623048,2230544282,2722294292,3322456440], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:96000, b:7, c:1.05, d:10080}, icon :'/cdn/all/both/img/city/embassy_l.png', }, fireworker: { maxlevel:50, wood:[234,303,382,473,578,699,837,996,1180,1391,1633,1911,2232,2601,3024,3512,4072,4717,5458,6311,7291,8419,9715,11206,12921,14893,17161,19768,22767,26216,30182,34744,39994,46038,52996,61005,70225,80839,93056,107119,123308,141943,163395,188088,216514,249236,286902,330262,380174,437629], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[116,182,259,348,452,571,711,872,1060,1277,1529,1823,2162,2555,3012,3542,4157,4869,5696,6655,7768,9059,10556,12292,14307,16644,19356,22500,26148,30379,35288,40981,47593,55271,64188,74543,86571,100537,116757,135594,157471,182876,212380,246644,286436,332648,386316,448642,521023,605081], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:125660, b:37, c:1.06, d:2628}, icon :'/cdn/all/both/img/city/fireworker_l.png', }, forester: { maxlevel:61, wood:[215,369,571,832,1173,1615,2189,2936,3907,5171,6812,8946,1172,15327,20015,26111,34034,44334,57725,75133,97764,127184,165429,215148,279783,36381,473043,615046,79965,1039635,1351616,1757192,2284467,2969962,3861151,5019757,6526023,8484269,11030122,14339902,18642840,24236948,31509665,40964688,53256856,69237501,90013417,117023507,152138445,197790230,257140627,334300142,434612711,565025809,734571624,954992608,1241554740,1614104820,2098445027,2728119932,3546739737], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,89,203,352,546,798,1125,155,2103,2822,3756,4971,6550,8603,11272,14742,19252,25115,32738,42647,55529,72276,94047,122348,159140,206971,269149,349982,455064,591671,769260,1000126,1300278,1690511,2197858,2857466,3715034,4829968,6279511,8164084,10614242,13799729,17941227,23325646,30326008,39427279,51259973,66643826,86644597,112647886,146455135,190408425,247552729,321846860,418437727,544016901,707284190,919550337,1195520604,1554313513,2020785330], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:72000, b:11, c:1.1, d:6120}, icon :'/cdn/all/both/img/city/forester_l.png', }, glassblowing: { maxlevel:61, wood:[235,401,617,898,1263,1738,2354,3157,4199,5554,7316,9607,12585,16456,21488,2803,36535,47591,61963,80649,104938,136516,177565,230931,300306,390494,507737,660154,858296,1115880,1450740,1886057,2451998,3187758,4144296,5387857,7004569,9106400,11838919,15391374,20009798,26014054,33819980,43968198,57161548,74313771,96612788,125602976,163292127,212290500,275991607,358807234,466472992,606445556,788419092,1024996652,1332563034,1732419548,2252259306,2928085165,3806703211], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,99,219,374,577,840,1182,1627,2205,2955,3931,5202,6852,8997,11786,15412,20125,26253,34219,44574,58037,75538,98289,127865,166315,216300,281279,365753,475568,618329,803918,1045183,1358855,1766663,2296859,2986173,3882359,5047500,6562314,8531740,11092214,14421117,18749061,24375871,31691353,41202296,53567581,69643833,90544754,117718283,153046903,198978051,258693667,336330631,437267348,568496341,739108674,960923743,1249308082,1624239900,2111693096], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:72000, b:11, c:1.1, d:6120}, icon :'/cdn/all/both/img/city/glassblowing_l.png', }, museum: { maxlevel:36, wood:[481,1234,2363,4055,6595,10405,16119,2469,37548,56833,85762,129155,194244,291878,438329,658006,987521,1481794,2223204,3335317,5003487,7505999,11260150,16891952,25340520,38014669,57027835,85550503,128338880,192528010,288821553,433276641,649981434,975071871,1462757402,2194360517], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[240,1023,2212,4021,6769,10946,17296,26948,41618,63917,97812,149332,227642,346674,527603,802613,1220630,1856015,2821801,4289796,6521148,9913144,15069498,22907948,34823590,52937193,80472645,122330751,185961486,282689952,429732042,653258546,993053083,1509592842,2294812419,3488466488], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:18000, b:1, c:1.1, d:14040}, icon :'/cdn/all/both/img/city/museum_r.png', }, optician: { maxlevel:50, wood:[102,161,231,311,405,513,638,784,952,1148,1376,1639,1944,2298,2710,3187,3741,4382,5127,5990,6992,8154,9503,11066,12881,14984,17426,20257,23541,27352,31771,36898,42851,49765,57795,67121,77951,90528,105135,122100,141801,164681,191253,222113,257950,299572,347908,404045,469238,544951], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,30,82,143,214,296,391,502,630,778,951,1150,1382,1652,1963,2325,2745,3232,3797,4453,5213,6094,7117,8304,9681,11277,13129,15277,17770,20661,24014,27905,32425,37679,43783,50877,59119,68698,79827,92761,107789,125252,145545,169125,196525,228365,265363,308354,358312,416362], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:125660, b:37, c:1.06, d:2772}, icon :'/cdn/all/both/img/city/optician_l.png', }, palace: { maxlevel:20, wood:[612,5008,13801,31386,66557,136898,27758,558944,1121673,2247131,4079425,7405756,13444352,24406772,44307863,80436146,146023147,265089275,481240988,873641108], wine:[0,0,0,9372,19014,38299,76868,154007,308284,616838,1233946,2468433,4937948,9878058,19760441,39529536,79076377,158187373,316443997,633026529], marble:[0,1233,3909,9262,19967,41378,84199,169841,341125,683694,1368832,2740554,5486893,10985369,21993924,44034272,88161489,176509062,353390684,707527273], glass:[0,0,0,0,18221,36464,72948,145917,291856,583733,1167487,2335016,4670116,9340398,18681126,37362913,74727147,149456937,298919160,597848890], sulfur:[0,0,2656,8858,21263,46072,95691,194928,393402,790351,1584248,3175603,6365454,12759465,25576175,51267094,102764189,205989415,412902967,827658356], time :{a:11520, b:1, c:1.4, d:0}, icon :'/cdn/all/both/img/city/palace_l.png', }, palaceColony:{ maxlevel:20, wood:[612,5008,13801,31386,66557,136898,27758,558944,1121673,2247131,4079425,7405756,13444352,24406772,44307863,80436146,146023147,265089275,481240988,873641108], wine:[0,0,0,9372,19014,38299,76868,154007,308284,616838,1233946,2468433,4937948,9878058,19760441,39529536,79076377,158187373,316443997,633026529], marble:[0,1233,3909,9262,19967,41378,84199,169841,341125,683694,1368832,2740554,5486893,10985369,21993924,44034272,88161489,176509062,353390684,707527273], glass:[0,0,0,0,18221,36464,72948,145917,291856,583733,1167487,2335016,4670116,9340398,18681126,37362913,74727147,149456937,298919160,597848890], sulfur:[0,0,2656,8858,21263,46072,95691,194928,393402,790351,1584248,3175603,6365454,12759465,25576175,51267094,102764189,205989415,412902967,827658356], time :{a:11520, b:1, c:1.4, d:0}, icon :'/cdn/all/both/img/city/palaceColony_l.png', }, pirateFortress:{ maxlevel:30, wood:[387,779,1194,1664,2229,2947,3883,5117,6737,8844,11549,14976,19258,24539,30972,38724,47969,58894,71694,86577,103757,123463,145929,171405,200146,232419,268500,308676,353243,402507], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[215,434,673,956,1319,1808,2479,3396,4633,6274,8412,11149,14594,18866,24096,30418,37979,46932,57441,69677,83818,100053,118579,139599,163326,189984,219798,253009,289861,330608], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:1550, b:1, c:1.2, d:1800}, icon :'/cdn/all/both/img/city/pirateFortress_l.png', }, port: { maxlevel:74, wood:[51,129,235,368,547,768,1038,1414,1811,2352,3041,3863,4892,6108,7611,954,11808,14673,18143,22329,27356,33703,41278,50493,61881,75359,92107,112468,136757,166786,20283,246402,299897,364631,441994,537638,652033,790936,959771,1164024,1412356,1711592,2073513,2513976,3045758,3690710,4471434,5417311,6563277,7951657,9633732,11671630,14140619,17131894,20755934,25146596,30466049,36910766,44718784,54178492,65639285,79524466,96346887,116727883,141420228,171335934,207579939,251490917,304690720,369144286,447232209,541838670,656457962,795323552], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,0,0,151,280,464,680,978,1374,1871,2518,3318,4343,57,7366,9536,12267,15687,19949,25492,32366,41024,52078,65675,83109,105055,132228,166917,210098,264169,332782,418793,525453,6616,830582,1043000,1310271,1645242,2066850,2593492,3253373,4084635,5124763,6431252,8069741,10125666,12705379,15942324,20003946,25100346,31495153,39519164,49587450,62220829,78072811,97963398,122921504,154238178,193533392,242839836,304708068,382338449,479746700,601971622,755335750,947772410,1189236100,1492217420,1872389200,2349417230,2947977544,3699032889], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], loadingSpeed:[30,60,93,129,169,213,261,315,373,437,508,586,672,766,869,983,1108,1246,1398,1565,1748,195,2172,2416,2685,298,3305,3663,4056,4489,4965,5488,6064,6698,7394,8161,9004,9931,10951,12073,13308,14666,16159,17802,19609,21597,23784,2616,288,3174,3498,3852,4242,4668,5142,5664,624,687,7566,8334,9174,10104,1113,12258,13494,14862,16368,1803,19854,21864,24078,26514,29202,3216], time :{a:50400, b:23, c:1.15, d:1512}, icon :'/cdn/all/both/img/city/port_r.png', }, safehouse: { maxlevel:60, wood:[97,213,345,497,669,866,1089,1345,1636,1967,2346,2777,3268,3829,4467,5196,6026,6972,8052,9281,10683,12282,14104,16181,1855,21249,24327,27836,31836,36396,41593,47519,54288,62022,70857,80952,92484,105659,120712,137908,157555,180,205642,234938,268407,306644,350328,400235,457252,522392,596811,681832,778964,889934,1016714,1161553,1327026,1516072,1732050,1978795], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,110,169,236,314,405,509,632,774,937,1128,1349,1604,1902,2247,2647,3110,3648,4272,4996,5836,6810,7940,9251,10772,12536,14582,16955,19708,22902,26613,30927,35939,41764,48532,56397,65538,76159,88501,102844,119511,13888,161387,187542,217936,253256,294299,341995,39742,461827,536672,623647,724717,842167,978652,1137256,1321562,1535739], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:96000, b:7, c:1.05, d:12960}, icon :'/cdn/all/both/img/city/safehouse_l.png', }, shipyard: { maxlevel:38, wood:[90,173,278,410,577,786,105,1383,1802,2331,2997,3835,4892,6224,7903,10017,12681,16039,20268,25597,32312,40774,51434,64868,81792,103119,129989,163847,206506,260258,327985,41332,520843,656322,827026,1042112,1313121,1654593], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,0,0,669,904,1201,1575,2047,2641,339,4332,5521,7018,8904,11281,14276,1805,22804,28796,36344,45856,5784,7294,91966,11594,146145,184205,23216,292584,368717,464645,585515,737811,929703,1171488,1476136], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:64800, b:7, c:1.05, d:7128}, icon :'/cdn/all/both/img/city/shipyard_l.png', }, stonemason: { maxlevel:61, wood:[235,401,617,898,1263,1738,2354,3157,4199,5554,7316,9607,12585,16456,21488,28030,36535,47591,61963,80649,104938,136516,177565,230931,300306,390494,507737,660154,858296,1115880,1450740,1886057,2451998,3187758,4144296,5387857,7004569,9106400,11838919,15391374,20009798,26014054,33819980,43968198,57161548,74313771,96612788,125602976,163292127,212290500,275991607,358807234,466472992,606445556,788419092,1024996652,1332563034,1732419548,2252259306,2928085165,3806703211], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,99,219,374,577,840,1182,1627,2205,2955,3931,5202,6852,8997,11786,15412,20125,26253,34219,44574,58037,75538,98289,127865,166315,216300,281279,365753,475568,618329,803918,1045183,1358855,1766663,2296859,2986173,3882359,5047500,6562314,8531740,11092214,14421117,18749061,24375871,31691353,41202296,53567581,69643833,90544754,117718283,153046903,198978051,258693667,336330631,437267348,568496341,739108674,960923743,1249308082,1624239900,2111693096], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:72000, b:11, c:1.1, d:6120}, icon :'/cdn/all/both/img/city/stonemason_l.png', }, temple: { maxlevel:56, wood:[185,196,286,399,514,653,823,1029,1231,1524,1816,2160,2650,3143,3833,4408,5359,6163,7471,8812,10134,12236,14407,16568,19939,22931,27543,31674,37201,43672,51248,58934,69131,82618,93217,111324,128023,149952,175636,205722,240960,282234,330578,387204,453528,531214,622206,728784,853617,999835,1171097,1371695,1606653,1881858,2204204,2581763], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[148,163,249,363,487,646,850,1109,1384,1788,2223,2760,3533,4372,5565,6677,8471,10166,12858,15825,18990,23928,29398,35277,44302,53162,66630,79955,97989,120036,146983,176379,215889,269226,316976,395001,474001,579334,708075,865425,1057743,1292797,1580085,1931216,2360375,2884904,3525994,4309550,5267229,6437726,7868332,9616852,11753933,14365921,17558351,21460211], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:2160, b:1, c:1.1, d:0}, icon :'/cdn/all/both/img/city/temple_l.png', }, tavern: { maxlevel:70, wood:[86,190,315,465,645,860,1119,143,1803,225,2787,3431,4203,5131,6244,758,9183,11106,13414,16183,19507,23495,28281,34023,40915,49185,59108,71017,85306,102455,123032,147725,177357,212916,255585,306789,368233,441967,530448,636624,764036,916929,1100402,1320569,1584770,1901810,2282259,2738814,3286701,3944191,4733208,5680066,6816336,8179914,9816267,11779966,14136495,16964436,20358093,24430637,29317873,35182779,42220933,50667036,60802744,72966054,87562577,105079069,126099653,151325310], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,80,104,135,177,229,299,388,504,657,853,1109,1442,1875,2438,3169,412,5356,6963,9052,11768,15298,19887,25854,3361,43693,56801,73841,95994,124792,16223,210899,274168,356419,463345,602349,783054,1017969,1323361,1720368,2236479,2907424,3779650,4913547,6387610,8303891,10795057,14033573,18243642,23716732,30831747,40081266,52105638,67737320,88058503,114476037,148818827,193464446,251503743,326954818,425041200,552553478,718319416,933815102,1213959454,1578147056,2051590869,2667067736], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:10800, b:1, c:1.06, d:10440}, icon :'/cdn/all/both/img/city/taverne_r.png', wineUse: [0, 4, 8, 13, 18, 24, 30, 37, 44, 51, 60, 68, 78, 88, 99, 110, 122, 136, 150, 165, 180, 197, 216, 235, 255, 277, 300, 325, 351, 378, 408, 439, 472, 507, 544, 584, 626, 670, 717, 766, 818, 874, 933, 995, 1060, 1129, 1202, 1280, 1362], }, townHall: { maxlevel:66, wood:[0,135,288,535,793,1195,1732,2327,3148,4107,5308,6943,8841,11199,14124,18047,21863,27765,34599,42385,52638,64331,80802,9721,12177,146383,180609,222632,270815,333385,405226,492419,59823,735066,892521,1095676,1315122,1613532,1957605,2374710,2880686,3494469,4239031,5142235,6237884,7566981,9179268,11135082,13507617,16385666,19876937,24112088,29249616,35481790,43041845,52212708,63337596,76832847,93203512,113062251,137152263,166375099,201824404,244826841,296991745,360271352], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,0,245,473,804,1213,1798,2532,3501,4871,6567,8784,11674,15698,19995,26678,34915,44905,58539,75091,98986,12498,164305,207293,26843,347289,443409,572956,731026,932491,1189231,1534000,1955370,2520083,3175628,4090537,5210432,6636074,8451790,10764309,13709563,17460677,22238144,28322787,36072268,45942107,58512461,74522227,94912471,120881750,153956559,196081062,249731375,318061105,405086733,515923697,657087086,836874602,1065854305,1357485815,1728911472,2201963988,2804449784,3571783476], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:1800, b:1, c:1.17, d:-1080}, icon :'/cdn/all/both/img/city/townhall_l.png', }, vineyard: { maxlevel:50, wood:[291,363,447,542,651,778,923,1091,1283,1504,1758,2050,2386,2773,3217,3728,4316,4992,5769,6664,7691,8874,10234,11797,13595,15664,18041,20776,23921,27538,31697,36480,41984,48318,55608,63998,73654,84767,97556,112275,129215,148711,171148,196970,226689,260891,300253,345554,397690,457692], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[105,170,245,332,433,550,686,843,1026,1238,1484,1769,2100,2484,2930,3446,4046,4741,5547,6482,7568,8826,10286,11979,13944,16223,18866,21932,25489,29615,34401,39953,46401,53891,62588,72689,84421,98046,113870,132247,153591,178380,207168,240604,279436,324534,376912,437743,508391,590441], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:125660, b:37, c:1.06, d:2232}, icon :'/cdn/all/both/img/city/vineyard_l.png', }, wall: { maxlevel:48, wood:[98,310,565,870,1237,1677,2205,2839,3599,4512,5608,6922,8498,10391,12662,15387,18657,22581,2729,32941,39722,47859,57623,6934,83401,100275,120522,144819,173976,208964,250949,301332,361791,434343,521404,625877,751246,901687,1082217,1298853,1558817,1870773,2245120,2694337,3233397,3880269,4656516,5588011], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,174,443,767,1155,1621,218,285,3655,4621,578,717,8839,10842,13245,16129,19589,23742,28725,34705,41881,50491,60824,73223,88103,105958,127384,153096,18395,220975,265404,31872,382698,459473,551602,662157,794823,954022,1145061,1374308,1649405,1979520,2375658,2851025,3421465,4105993,4927426,5913145], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:57600, b:11, c:1.1, d:3240}, icon :'/cdn/all/both/img/city/wall.png', }, warehouse: { maxlevel:85, wood:[137,247,380,538,728,957,123,1559,1953,2426,2995,3676,4494,5476,6653,8066,9763,11798,1424,17171,20688,24908,29972,36049,43342,52093,62594,75195,90318,108464,130239,156369,187725,225353,270506,324691,389711,467736,561366,673722,808565,970396,1164618,1397712,1677459,2013197,2416131,2899711,3480078,4176604,5012536,6015778,7219815,8664836,10399072,12480410,14978319,17976177,21574046,25892014,31074209,37293603,44757785,53715898,64466945,77369775,92855062,111439673,133743930,160512304,192638273,231194140,277466828,333000831,399649768,479638253,575636150,690847686,829118402,995063513,1194221950,1433241242,1720099398,2064371199,2477547780], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,0,0,82,181,300,442,614,819,1066,1362,1717,2143,2653,3268,4004,4887,5946,7218,8745,10577,12775,15412,18577,22376,26934,32403,38966,46842,56293,67634,81245,97576,117174,140691,168911,202776,243415,29218,350699,420938,505244,606436,727895,87368,1048662,1258691,1510786,1813370,2176555,2612483,3135717,3763747,4517560,5422349,6508351,7811859,9376439,11254377,13508432,16213936,19461305,23359065,28037479,33652897,40392985,48482997,58193295,69848398,83837814,100629067,120783314,144974107,174009893,208861041,250692266,300901556,361166892,433502324,520325282,624537363,749621309,899757388,1079963105,1296260886], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], capacity:[8000,16401,25455,35331,46181,58159,71421,86138,102493,120687,140942,163502,188637,216646,24786,282647,321416,364622,412768,466416,526189,592779,666959,749584,841609,944094,1058219,1185297,1326787,1484315,1659690,1854922,2072252,2314171,2583453,2883186,3216807,3588142,4001450,4461476,4973499,5543400,6177729,6883779,7669673,8544460,9518219,10602179,11808851,13152172,14647676,16312668,18166439,20230485,22528769,25088000,27937955,31111829,34646637,38583648,42968887,47853679,53295269,59357506,66111616,73637056,82022473,91366775,101780329,113386298,126322135,140741251,156814887,174734197,194712581,216988297,241827374,269526873,300418536,334872863,373303675,416173213,463997848,517354466,576887609], time :{a:2880, b:1, c:1.14, d:2160}, icon :'/cdn/all/both/img/city/warehouse_l.png', }, winegrower: { maxlevel:61, wood:[235,401,617,898,1263,1738,2354,3157,4199,5554,7316,9607,12585,16456,21488,28030,36535,47591,61963,80649,104938,136516,177565,230931,300306,390494,507737,660154,858296,1115880,1450740,1886057,2451998,3187758,4144296,5387857,7004569,9106400,11838919,15391374,20009798,26014054,33819980,43968198,57161548,74313771,96612788,125602976,163292127,212290500,275991607,358807234,466472992,606445556,788419092,1024996652,1332563034,1732419548,2252259306,2928085165,3806703211], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[0,99,219,374,577,840,1182,1627,2205,2955,3931,5202,6852,8997,11786,15412,20125,26253,34219,44574,58037,75538,98289,127865,166315,216300,281279,365753,475568,618329,803918,1045183,1358855,1766663,2296859,2986173,3882359,5047500,6562314,8531740,11092214,14421117,18749061,24375871,31691353,41202296,53567581,69643833,90544754,117718283,153046903,198978051,258693667,336330631,437267348,568496341,739108674,960923743,1249308082,1624239900,2111693096], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:72000, b:11, c:1.1, d:6120}, icon :'/cdn/all/both/img/city/winegrower_l.png', }, workshop: { maxlevel:32, wood:[189,329,489,671,879,1117,1387,1695,2046,2447,2904,3424,4017,4693,5465,6344,7346,8488,9791,11275,12967,14896,17096,19604,22462,25721,29436,33671,38498,44002,50277,57429], wine:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], marble:[81,143,215,300,396,509,639,791,967,1171,1407,1682,2000,2369,2797,3294,3870,4539,5314,6214,7257,8468,9871,11500,13390,15581,18123,21072,24493,28461,33064,38404], glass:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], sulfur:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], time :{a:96000, b:7, c:1.05, d:11880}, icon :'/cdn/all/both/img/city/workshop_l.png', }, }, Research: { Seafaring: { CARPENTRY: 2150, DECK_WEAPONS: 1010, PIRACY: 1170, SHIP_MAINTENANCE: 1020, DRAFT: 1130, EXPANSION: 1030, FOREIGN_CULTURES: 1040, PITCH: 1050, MARKET: 2070, GREEK_FIRE: 1060, COUNTERWEIGHT: 1070, DIPLOMACY: 1080, SEA_MAPS: 1090, PADDLE_WHEEL_ENGINE: 1100, CAULKING: 1140, MORTAR_ATTACHMENT: 1110, MASSIVE_RAM: 1150, OFFSHORE_BASE: 1160, SEAFARING_FUTURE: 1999, }, Economy: { CONSERVATION: 2010, PULLEY: 2020, WEALTH: 2030, WINE_CULTURE: 2040, IMPROVED_RESOURCE_GATHERING: 2130, GEOMETRY: 2060, ARCHITECTURE: 1120, HOLIDAY: 2080, LEGISLATION: 2170, CULINARY_SPECIALITIES: 2050, HELPING_HANDS: 2090, SPIRIT_LEVEL: 2100, WINE_PRESS: 2140, DEPOT: 2160, BUREACRACY: 2110, UTOPIA: 2120, ECONOMIC_FUTURE: 2999, }, Science: { WELL_CONSTRUCTION: 3010, PAPER: 3020, ESPIONAGE: 3030, POLYTHEISM: 3040, INK: 3050, GOVERNMENT_FORMATION: 3150, INVENTION: 3140, CULTURAL_EXCHANGE: 3060, ANATOMY: 3070, OPTICS: 3080, EXPERIMENTS: 3081, MECHANICAL_PEN: 3090, BIRDS_FLIGHT: 3100, LETTER_CHUTE: 3110, STATE_RELIGION: 3160, PRESSURE_CHAMBER: 3120, ARCHIMEDEAN_PRINCIPLE: 3130, SCIENTIFIC_FUTURE: 3999, }, Military: { DRY_DOCKS: 4010, MAPS: 4020, PROFESSIONAL_ARMY: 4030, SEIGE: 4040, CODE_OF_HONOR: 4050, BALLISTICS: 4060, LAW_OF_THE_LEVEL: 4070, GOVERNOR: 4080, PYROTECHNICS: 4130, LOGISTICS: 4090, GUNPOWDER: 4100, ROBOTICS: 4110, CANNON_CASTING: 4120, MILITARISTIC_FUTURE: 4999, }, }, Government: { ANARCHY: 'anarchie', IKACRACY: 'ikakratie', ARISTOCRACY: 'aristokratie', DICTATORSHIP: 'diktatur', DEMOCRACY: 'demokratie', NOMOCRACY: 'nomokratie', OLIGARCHY: 'oligarchie', TECHNOCRACY: 'technokratie', THEOCRACY: 'theokratie', }, TradeGoodOrdinals: { WINE: 1, MARBLE: 2, GLASS: 3, SULFUR: 4, }, Time: { SECONDS_PER_HOUR: 3600, SECONDS_PER_MINUTE: 60, MILLIS_PER_DAY: 1000 * 60 * 60 * 24, MILLIS_PER_HOUR: 1000 * 60 * 60, MILLIS_PER_SECOND: 1000, MILLIS_PER_MINUTE: 60000, MINUTES_PER_DAY: 24 * 60, MINUTES_PER_HOUR: 60, HOURS_PER_DAY: 24, HOURS_PER_WEEK: 24 * 7, SAFE_TIME_DELTA: 1000, INITIAL_PAGE_LOAD_DELTA: 2000, }, GamePlay: { BUILDING_SPOTS: 24, HAPPINESS_PER_CULTURAL_GOOD: 50, HAPPINESS_PER_WINE_SERVING_LEVEL: 60, BASE_RESOURCE_PROTECTION: 100, RESOURCES_PER_TRANSPORT: 500, RESOURCE_PROTECTION_WAREHOUSE: 480, RESOURCE_PROTECTION_WAREHOUSE_INACTIVE: 80, }, BaseView: { ISLAND: 'island', CITY: 'city', WORLD: 'worldview', }, Military: { // Army HOPLITE: 'phalanx', STEAM_GIANT: 'steamgiant', SPEARMAN: 'spearman', SWORDSMAN: 'swordsman', SLINGER: 'slinger', ARCHER: 'archer', GUNNER: 'marksman', BATTERING_RAM: 'ram', CATAPULT: 'catapult', MORTAR: 'mortar', GYROCOPTER: 'gyrocopter', BALLOON_BOMBADIER: 'bombardier', COOK: 'cook', DOCTOR: 'medic', ARMY: 'army', // Navy RAM_SHIP: 'ship_ram', FLAME_THROWER: 'ship_flamethrower', STEAM_RAM: 'ship_steamboat', BALLISTA_SHIP: 'ship_ballista', CATAPULT_SHIP: 'ship_catapult', MORTAR_SHIP: 'ship_mortar', SUBMARINE: 'ship_submarine', PADDLE_SPEED_SHIP: 'ship_paddlespeedship', BALLOON_CARRIER: 'ship_ballooncarrier', TENDER: 'ship_tender', ROCKET_SHIP: 'ship_rocketship', NAVY: 'navy', }, UnitData: { spearman: { minimumBuildingLevelToBuild: 1, baseBuildTime: 60, isArmy: true, speed: 60, cargoSize: 3, }, slinger: { minimumBuildingLevelToBuild: 2, baseBuildTime: 90, isArmy: true, speed: 60, cargoSize: 3, }, ram: { minimumBuildingLevelToBuild: 3, baseBuildTime: 600, isArmy: true, speed: 40, cargoSize: 30, }, phalanx: { minimumBuildingLevelToBuild: 4, baseBuildTime: 300, isArmy: true, speed: 60, cargoSize: 5, }, cook: { minimumBuildingLevelToBuild: 5, baseBuildTime: 1200, isArmy: true, speed: 40, cargoSize: 20, }, swordsman: { minimumBuildingLevelToBuild: 6, baseBuildTime: 180, isArmy: true, speed: 60, cargoSize: 3, }, archer: { minimumBuildingLevelToBuild: 7, baseBuildTime: 240, isArmy: true, speed: 60, cargoSize: 5, }, catapult: { minimumBuildingLevelToBuild: 8, baseBuildTime: 1800, isArmy: true, speed: 40, cargoSize: 30, }, medic: { minimumBuildingLevelToBuild: 9, baseBuildTime: 1200, isArmy: true, speed: 60, cargoSize: 10, }, gyrocopter: { minimumBuildingLevelToBuild: 10, baseBuildTime: 900, isArmy: true, speed: 80, cargoSize: 15, }, bombardier: { minimumBuildingLevelToBuild: 11, baseBuildTime: 1800, isArmy: true, speed: 20, cargoSize: 30, }, steamgiant: { minimumBuildingLevelToBuild: 12, baseBuildTime: 900, isArmy: true, speed: 40, cargoSize: 15, }, marksman: { minimumBuildingLevelToBuild: 13, baseBuildTime: 600, isArmy: true, speed: 60, cargoSize: 5, }, mortar: { minimumBuildingLevelToBuild: 14, baseBuildTime: 2400, isArmy: true, speed: 40, cargoSize: 30, }, barbarian: { minimumBuildingLevelToBuild: 1, baseBuildTime: 1, isArmy: true, speed: 40, cargoSize: 5, }, ship_ram: { minimumBuildingLevelToBuild: 1, baseBuildTime: 2400, isArmy: false, speed: 40, cargoSize: 0, }, ship_flamethrower: { minimumBuildingLevelToBuild: 4, baseBuildTime: 1800, isArmy: false, speed: 40, cargoSize: 0, }, ship_submarine: { minimumBuildingLevelToBuild: 19, baseBuildTime: 3600, isArmy: false, speed: 40, cargoSize: 0, }, ship_ballista: { minimumBuildingLevelToBuild: 3, baseBuildTime: 3000, isArmy: false, speed: 40, cargoSize: 0, }, ship_catapult: { minimumBuildingLevelToBuild: 3, baseBuildTime: 3000, isArmy: false, speed: 40, cargoSize: 0, }, ship_mortar: { minimumBuildingLevelToBuild: 17, baseBuildTime: 3000, isArmy: false, speed: 30, cargoSize: 0, }, ship_steamboat: { minimumBuildingLevelToBuild: 15, baseBuildTime: 2400, isArmy: false, speed: 40, cargoSize: 0, }, ship_rocketship: { minimumBuildingLevelToBuild: 11, baseBuildTime: 3600, isArmy: false, speed: 30, cargoSize: 0, }, ship_paddlespeedship: { minimumBuildingLevelToBuild: 13, baseBuildTime: 1800, isArmy: false, speed: 60, cargoSize: 0, }, ship_ballooncarrier: { minimumBuildingLevelToBuild: 7, baseBuildTime: 3900, isArmy: false, speed: 20, cargoSize: 0, }, ship_tender: { minimumBuildingLevelToBuild: 9, baseBuildTime: 2400, isArmy: false, speed: 30, cargoSize: 0, }, }, UnitIds: { 301: 'slinger', 302: 'swordsman', 303: 'phalanx', 304: 'marksman', 305: 'mortar', 306: 'catapult', 307: 'ram', 308: 'steamgiant', 309: 'bombardier', 310: 'cook', 311: 'medic', 312: 'gyrocopter', 313: 'archer', 315: 'spearman', 316: 'barbarian', 210: 'ship_ram', 211: 'ship_flamethrower', 212: 'ship_submarine', 213: 'ship_ballista', 214: 'ship_catapult', 215: 'ship_mortar', 216: 'ship_steamboat', 217: 'ship_rocketship', 218: 'ship_paddlespeedship', 219: 'ship_ballooncarrier', 220: 'ship_tender', }, IkariamAjaxResponseType: { RELOAD: 'reload', PROVIDE_FEEDBACK: 'provideFeedback', QUEST_DATA: 'questData', UPDATE_GLOBAL_DATA: 'updateGlobalData', UPDATE_TEMPLATE_DATA: 'updateTemplateData', UPDATE_BACKGROUND_DATA: 'updateBackgroundData', CLOSE_VIEW: 'closeView', CHANGE_VIEW: 'changeView', ADD_WINDOW: 'addWindow', CREATE_VIEW: 'createView', EVAL_SCRIPT: 'evalScript', }, CityType: { OWN: 'ownCity', DEPLOYMENT: 'deployedCities', OCCUPATION: 'occupiedCities', }, View: { CITY_DETAIL: 'cityDetails', CITY_MILITARY: 'cityMilitary', RELATED_CITIES: 'relatedCities', ACADEMY: 'academy', PALACE: 'palace', MUSEUM: 'museum', ASSIGN_CULTURAL_POSSESSIONS: 'culturalPossessions_assign', TOWN_HALL: 'townHall', TEMPLE: 'temple', RESEARCH_ADVISOR: 'researchAdvisor', FINANCES: 'finances', BARRACKS: 'barracks', SHIPYARD: 'shipyard', PIRATE_FORTRESS: 'pirateFortress', MILITARY_ADVISOR: 'militaryAdvisor', MILITARY_ADVISOR_REPORT: 'militaryAdvisorReportView', PREMIUM: 'premium', TRANSPORT: 'transport', DEPLOY: 'deployment', BRANCH_OFFICE: 'branchOffice', BLACK_MARKET: 'blackMarket', TAKE_OFFER: 'takeOffer', RESOURCE: 'resource', TRADE_GOOD: 'tradegood', ABOLISH_CITY: 'abolishCity', HIDEOUT: 'safehouse', PILLAGE: 'plunder', BLOCKADE: 'blockade', SEND_SPY: 'sendSpy', SPY_MISSION: 'spyMissions', HIGH_SCORE: 'highscore', ALLIANCE_PAGE: 'allyPage', OCCUPY: 'occupy', COLONIZE: 'colonize', }, PlayerState: { INACTIVE: 'inactive', NORMAL: '', VACATION: 'vacation', NEW: 'noob', }, CombatType: { BLOCKADE: 'blockade', PILLAGE: 'plunder', }, }; var EmpireData = function() { function Military(city) { this._ikaToolsType = 'military'; this.lastArmyUpdate = 0; this.lastNavyUpdate = 0; this.present = new MilitaryUnits(); this.armyTrainingBatches = []; this.navyTrainingBatches = []; this._setCity(city); } $.extend(Military.prototype, { _setCity: function setCity(city) { if (city) { this.city = Utils.fixedFunction(city); this._startArmyTrainingTimers(); this._startNavyTrainingTimers(); } }, _startTrainingTimers: function startTrainingTimers(batches) { var military = this; while (batches.length > 0 && batches[0].completionTime <= View.gameTimeNow()) { var batch = batches.shift(); if ((batch.type == Constants.Military.ARMY && batch.getCompletionTime() > this.lastArmyUpdate) || (batch.type == Constants.Military.NAVY && batch.getCompletionTime() > this.lastNavyUpdate)) { military.present._increment(batch.getUnits()); } } $.each(batches, function startTrainingBatchTimers(index, batch) { if (batch.completionEvent) { batch.completionEvent(); } batch.completionEvent = militaryChangedEvent().scheduleSend( 'MilitaryTrainingComplete[' + military.city().getId() + ']', batch.getCompletionTime() - View.gameTimeNow() + Constants.Time.SAFE_TIME_DELTA, function militaryTrainingComplete() { military.present._increment(batches.shift().getUnits()); empireData.saveAsync(); }, [{ city: military.city(), military: military, type: 'training_complete', }]); }); }, _startArmyTrainingTimers: function startArmyTrainingTimers() { this._startTrainingTimers(this.armyTrainingBatches); }, _startNavyTrainingTimers: function startNavyTrainingTimers() { this._startTrainingTimers(this.navyTrainingBatches); }, _updatePresent: function updatePresent(unit, count) { return this.present._setCount(unit, count); }, _markPresentUpdated: function markPresentUpdated(army, navy) { if (army || army === undefined) { this.lastArmyUpdate = View.gameTimeNow(); } if (navy || navy === undefined) { this.lastNavyUpdate = View.gameTimeNow(); } }, _setArmyTrainingBatches: function setArmyTrainingBatches(batches) { $.each(this.armyTrainingBatches, function cancelTrainingBatchTimer(index, batch) { if (batch.completionEvent) { batch.completionEvent(); } }); this.armyTrainingBatches = batches; this._startArmyTrainingTimers(); }, _setNavyTrainingBatches: function setNavyTrainingBatches(batches) { $.each(this.navyTrainingBatches, function cancelTrainingBatchTimer(index, batch) { if (batch.completionEvent) { batch.completionEvent(); } }); this.navyTrainingBatches = batches; this._startNavyTrainingTimers(); }, _increment: function increment(units) { this.present._increment(units); }, _decrement: function decrement(units) { this.present._decrement(units); }, _clear: function clear() { this.present._clear(); this.armyTrainingBatches = []; this.navyTrainingBatches = []; }, getTrainingBatches: function getTrainingBatches(batches) { return $.merge($.merge([], this.armyTrainingBatches), this.navyTrainingBatches); }, getPresent: function getPresent() { return this.present; }, }); function MilitaryUnits() { this._ikaToolsType = 'militaryUnits'; this.units = {}; } $.extend(MilitaryUnits.prototype, { _setCount: function setCount(unit, count) { var oldCount = this.units[unit]; this.units[unit] = count; return oldCount != count; }, _increment: function increment(units) { $.each(units.units, function(unit, count) { this._setCount(unit, (this.getCount(unit) || 0) + count); }.bind(this)); }, _decrement: function decrement(units) { $.each(units.units, function(unit, count) { this._setCount(unit, Math.max(0, (this.getCount(unit) || 0) - count)); }.bind(this)); }, _clear: function clear() { this.units = {}; }, getCount: function getCount(unit) { return this.units[unit]; }, getCounts: function getCounts() { return this.units; }, getCargoSize: function getCargoSize() { var cargoSize = 0; $.each(this.units, function addCargoSize(unit, count) { cargoSize += Constants.UnitData[unit].cargoSize * count; }); return cargoSize; }, }); function TrainingBatch(type, completionTime, units) { this._ikaToolsType = 'trainingBatch'; this.type = type; this.units = units; this.completionTime = completionTime; } $.extend(TrainingBatch.prototype, { getUnits: function getUnits() { return this.units; }, getCompletionTime: function getCompletionTime() { return this.completionTime; }, _getType: function getType() { return this.type; }, }); function City(id, type) { this._ikaToolsType = 'city'; if (id) { this.id = id; this.type = type; this.level = 0; this.resources = { wood: new City.Resource(this), wine: new City.Resource(this), marble: new City.Resource(this), glass: new City.Resource(this), sulfur: new City.Resource(this), }; this.buildings = new Array(Constants.GamePlay.BUILDING_SPOTS); for (var i = 0; i < Constants.GamePlay.BUILDING_SPOTS; i++) { this.buildings[i] = new City.Building(this); } this.military = new Military(); this.actionPoints = 0; this.scientists = 0; this.culturalGoods = 0; this.priests = 0; this.tavernWineLevel = 0; this.population = undefined; this.resourceUpdateTime = 0; this.islandCoordinates = undefined; } } $.extend(City.prototype, { _postLoad: function postLoad() { while (this.buildings.length < Constants.GamePlay.BUILDING_SPOTS) { this.buildings.push(new City.Building(this)); } for (var i = 0; i < Constants.GamePlay.BUILDING_SPOTS; i++) { this.buildings[i]._setCity(this); } if (this.isOwn()) { this.resources[Constants.Resources.WOOD]._setCity(this); this.resources[Constants.Resources.WINE]._setCity(this); this.resources[Constants.Resources.MARBLE]._setCity(this); this.resources[Constants.Resources.GLASS]._setCity(this); this.resources[Constants.Resources.SULFUR]._setCity(this); } this.military._setCity(this); }, _updateFromGlobalData: function _updateFromGlobalData(data, correctWineConsumption) { var changes = []; this._updateActionPoints(data.maxActionPoints, changes); if (this.isOwn()) { var wineConsumption = data.wineSpendings; var baseWineConsumption = data.wineSpendings; var winePress = this.getBuildingByType(Constants.Buildings.WINE_PRESS); if (correctWineConsumption) { if (winePress) { wineConsumption = wineConsumption * (100 - winePress.getLevel()) / 100; } } else { if (winePress) { baseWineConsumption = Math.floor( wineConsumption / (100 - winePress.getLevel()) * 100); } } var use = Constants.BuildingData[Constants.Buildings.TAVERN].wineUse; for (var i = 0; i < 48; i++) { if (use[i] >= baseWineConsumption) { this._updateTavernWineLevel(i, changes); break; } } this._updateResources(data.currentResources, data.maxResources, data.resourceProduction, data.tradegoodProduction, wineConsumption / Constants.Time.SECONDS_PER_HOUR, changes); } raiseResourcesChanged(changes); }, _updateFromBackgroundData: function _updateFromBackgroundData(data) { this.islandId = parseInt(data.islandId); if (this.isOwn()) { this._updateBuildings(data.position); } }, _updateBuildings: function _updateBuildings(buildingsData) { var changes = []; for (var i = 0; i < Constants.GamePlay.BUILDING_SPOTS; i++) { var building = this.buildings[i]; if (buildingsData[i]) { if (building._update(i, buildingsData[i])) { changes.push({ city: this, building: building, type: Constants.BuildingEventType.DATA_REFRESH, }); } } } this.level = this.buildings[0].getLevel(); raiseBuildingsChanged(changes); }, _updateResources: function updateResources( currentInfo, maxInfo, woodProduction, resourceProduction, wineConsumption, changedAccumulator) { this._updateResource(Constants.Resources.WOOD, changedAccumulator, currentInfo["resource"], woodProduction); this._updateResource(Constants.Resources.WINE, changedAccumulator, currentInfo["1"], this.getTradeGoodType() == Constants.Resources.WINE ? resourceProduction : undefined, wineConsumption); this._updateResource(Constants.Resources.MARBLE, changedAccumulator, currentInfo["2"], this.getTradeGoodType() == Constants.Resources.MARBLE ? resourceProduction : undefined); this._updateResource(Constants.Resources.GLASS, changedAccumulator, currentInfo["3"], this.getTradeGoodType() == Constants.Resources.GLASS ? resourceProduction : undefined); this._updateResource(Constants.Resources.SULFUR, changedAccumulator, currentInfo["4"], this.getTradeGoodType() == Constants.Resources.SULFUR ? resourceProduction : undefined); this._updatePopulation(currentInfo.population, changedAccumulator); this.resourceUpdateTime = View.gameTimeNow(); }, _updateResource: function updateResource( name, changedAccumulator, current, max, production, consumption) { var resource = this.resources[name]; if (resource._update(current, max, production, consumption)) { changedAccumulator.push({ city: this, type: name, value: resource, }); } }, _incrementResource: function incrementResource(name, changedAccumulator, delta) { var resource = this.resources[name]; if (delta) { resource._increment(delta); changedAccumulator.push({ city: this, type: name, value: resource, }); } }, _updateActionPoints: function updateActionPoints(actionPoints, changedAccumulator) { if (this.actionPoints != actionPoints) { changedAccumulator.push({ city: this, type: Constants.Resources.ACTION_POINTS, value: actionPoints, }); } this.actionPoints = actionPoints; }, _updateActionPointsBy: function updateActionPointsBy(delta, changedAccumulator) { this._updateActionPoints( Math.max(0, Math.min(this.actionPoints + delta, this.getMaxActionPoints())), changedAccumulator); }, _updateScientists: function updateScientists(scientists, changedAccumulator) { if (this.scientists != scientists) { changedAccumulator.push({ city: this, type: Constants.Resources.SCIENTISTS, value: scientists, }); } this.scientists = scientists; }, _updateTavernWineLevel: function updateTavernWineLevel(level, changedAccumulator) { if (this.tavernWineLevel != level) { changedAccumulator.push({ city: this, type: Constants.Resources.TAVERN_WINE_LEVEL, value: level, }); } this.tavernWineLevel = level; }, _updateCulturalGoods: function updateCulturalGoods(culturalGoods, changedAccumulator) { if (this.culturalGoods != culturalGoods) { changedAccumulator.push({ city: this, type: Constants.Resources.CULTURAL_GOODS, value: culturalGoods, }); } this.culturalGoods = culturalGoods; }, _updatePopulation: function updatePopulation(population, changedAccumulator) { if (Math.abs(this.getPopulationData().population - population) >= 1) { changedAccumulator.push({ city: this, type: Constants.Resources.POPULATION, value: population, }); } this.population = population; }, _updatePriests: function updatePriests(priests, changedAccumulator) { if (this.priests != priests) { changedAccumulator.push({ city: this, type: Constants.Resources.PRIESTS, value: priests, }); } this.priests = priests; }, getLastResourceUpdate: function getLastResourceUpdate() { return this.resourceUpdateTime; }, getTimeSinceResourceUpdate: function getTimeSinceLastResourceUpdate() { return View.gameTimeNow() - this.resourceUpdateTime; }, isOwn: function isOwn() { return this.type == Constants.CityType.OWN; }, isDeployment: function isDeployment() { return this.type == Constants.CityType.DEPLOYMENT; }, isOccupation: function isOccupation() { return this.type == Constants.CityType.OCCUPATION; }, getType: function getType() { return this.type; }, getBuildingByPosition: function getBuildingByPosition(position) { return this.buildings[position]; }, getBuildingsByType: function getBuildingsByType(type) { return this.buildings.filter(function buildingsFilter(building) { return building.getType() == type; }); }, getBuildingByType: function getBuildingByType(type) { for (var i = 0; i < Constants.GamePlay.BUILDING_SPOTS; i++) { var building = this.buildings[i]; if (building && building.getType() == type) { return building; } } return null; }, getBuildings: function getBuildings() { return this.buildings; }, getMilitary: function getMilitary() { return this.military; }, getId: function getId() { return this.id; }, getName: function getName() { return this.name; }, getIslandId: function getIslandId() { return this.islandId; }, getTradeGoodType: function getTradeGoodType() { return this.tradeGoodType; }, getActionPoints: function getActionPoints() { return this.actionPoints; }, getMaxActionPoints: function getMaxActionPoints() { return 3 + Math.floor(this.level / 4) - (this.isOwn() ? 0 : 2); }, getCulturalGoods: function getCulturalGoods() { return this.culturalGoods; }, getTavernWineLevel: function getTavernWineLevel() { return this.tavernWineLevel; }, getPopulationData: function getPopulationData() { var max = 0; var happiness = 196; var townHall = this.getBuildingByType(Constants.Buildings.TOWN_HALL); var temple = this.getBuildingByType(Constants.Buildings.TEMPLE); var palace = this.getBuildingByType(Constants.Buildings.PALACE); var tavern = this.getBuildingByType(Constants.Buildings.TAVERN); var museum = this.getBuildingByType(Constants.Buildings.MUSEUM); var civData = getCivilizationData(); if (townHall) { // Formula from http://ikariam.wikia.com/wiki/Citizen max += Math.floor(10 * Math.pow(townHall.getLevel(), 1.5)) * 2 + 40; } if (civData.hasResearched(Constants.Research.Economy.HOLIDAY)) { max += 50; happiness += 25; } if (civData.hasResearched(Constants.Research.Economy.ECONOMIC_FUTURE)) { var level = civData.getResearchLevel(Constants.Research.Economy.ECONOMIC_FUTURE); max += 20 * level; happiness += 10 * level; } if (palace) { if (civData.hasResearched(Constants.Research.Science.WELL_CONSTRUCTION)) { max += 50; happiness += 50; } if (civData.hasResearched(Constants.Research.Economy.UTOPIA)) { max += 200; happiness += 200; } } if (tavern) { happiness += 12 * tavern.getLevel(); } happiness += Constants.GamePlay.HAPPINESS_PER_WINE_SERVING_LEVEL * this.getTavernWineLevel(); if (museum) { happiness += 20 * museum.getLevel(); } happiness += Constants.GamePlay.HAPPINESS_PER_CULTURAL_GOOD * this.getCulturalGoods(); var government = civData.getGovernment(); if (government == Constants.Government.DEMOCRACY) { happiness += 75; } else if (government == Constants.Government.DICTATORSHIP) { happiness -= 75; } else if (government == Constants.Government.THEOCRACY) { if (temple) { happiness += Math.min(150, this.getPriests() * 5 / max * 100 * 2); } else { happiness -= 20; } } happiness = happiness * (1 - this.getCorruption()); var happinessDelta = happiness - this.population; var currentPopulation = this.population + happinessDelta * (1 - Math.pow(Math.E, -(this.getTimeSinceResourceUpdate() / 50 / Constants.Time.MILLIS_PER_HOUR))); var population = Math.min(currentPopulation, max); return { population: population, max: max, happiness: happiness - population, growth: max == population && happiness > (population - 1) ? 0 : (happiness - population) / 50, }; }, getCorruption: function getCorruption() { var palace = this.getBuildingByType(Constants.Buildings.GOVERNORS_RESIDENCE) || this.getBuildingByType(Constants.Buildings.PALACE); var level = palace ? palace.getLevel() : 0; var corruption = 1 - (level + 1) / getOwnCities().length; var government = getCivilizationData().getGovernment(); if (government == Constants.Government.ARISTOCRACY || government == Constants.Government.OLIGARCHY) { corruption += .03; } else if (government == Constants.Government.NOMOCRACY) { corruption -= .05; } else if (government == Constants.Government.ANARCHY) { corruption += .25; } return Math.min(Math.max(corruption, 0), 1); }, getScientists: function getScientists() { return this.scientists; }, getResearch: function getResearch() { var civData = getCivilizationData(); var multiplier = 1.0; multiplier += civData.hasResearched( IkaTools.Constants.Research.Science.PAPER) ? .02 : 0; multiplier += civData.hasResearched( IkaTools.Constants.Research.Science.INK) ? .04 : 0; multiplier += civData.hasResearched( IkaTools.Constants.Research.Science.MECHANICAL_PEN) ? .08 : 0; multiplier += (civData.getResearchLevel( IkaTools.Constants.Research.Science.SCIENTIFIC_FUTURE) || 0) * .02; multiplier -= this.getCorruption(); return this.scientists * multiplier; }, getResource: function getResource(resourceName) { return this.resources[resourceName]; }, getResourceCapacity: function getResourceMaximumCapacity() { var total = 1500; var safe = 100; $.each(this.getBuildingsByType(Constants.Buildings.WAREHOUSE), function(i, building) { total += Constants.BuildingData.warehouse.capacity[building.getLevel()]//8000 * building.getLevel(); safe += 480 * building.getLevel(); }); $.each(this.getBuildingsByType(Constants.Buildings.DUMP), function(i, building) { total += Constants.BuildingData.dump.capacity[building.getLevel()]//8000 * building.getLevel(); }); var civilizationData = getCivilizationData(); if (civilizationData.isPremiumFeatureEnabled( Constants.PremiumFeatures.DOUBLED_STORAGE_CAPACITY)) { total *= 2; } if (civilizationData.isPremiumFeatureEnabled( Constants.PremiumFeatures.DOUBLED_SAFE_CAPACITY)) { safe *= 2; } return { maximum: total, safe: safe, }; }, getIslandCoordinates: function getIslandCoordinates() { return this.islandCoordinates; }, getLoadingSpeed: function getLoadingSpeed() { var speed = 10; var ports = this.getBuildingsByType(Constants.Buildings.TRADING_PORT); if (ports[0]) { speed = Constants.BuildingData[Constants.Buildings.TRADING_PORT] .loadingSpeed[ports[0].getLevel()]; } if (ports[1]) { speed += Constants.BuildingData[Constants.Buildings.TRADING_PORT] .loadingSpeed[ports[1].getLevel()]; } return speed / Constants.Time.SECONDS_PER_MINUTE; }, getPriests: function getPriests() { return this.priests; }, }); City.Resource = function City_Resource(city) { this._ikaToolsType = 'cityResource'; this._setCity(city); } $.extend(City.Resource.prototype, { _setCity: function setCity(city) { if (city) { this.city = Utils.fixedFunction(city); } }, _update: function _update(current, production, consumption) { var changed = Math.abs(current - this.getCurrent()) > 3 || this.production != production || this.consumption != consumption; this.current = current; this.production = production; this.consumption = consumption; return changed; }, _increment: function _increment(delta) { this.current = Math.max(this.current + delta, 0); }, getCurrent: function getCurrent() { if (this.current === undefined) { return undefined; } var current = this.current; var now = View.gameTimeNow(); var max = this.city().getResourceCapacity().maximum; var lastUpdate = this.city().getLastResourceUpdate(); if (this.production) { current += this.production * (now - lastUpdate) / Constants.Time.MILLIS_PER_SECOND; } if (this.consumption) { // Wine use takes place on the hour. var startHour = Math.floor(lastUpdate / Constants.Time.MILLIS_PER_HOUR); var nowHour = Math.floor(now / Constants.Time.MILLIS_PER_HOUR); current -= this.consumption * Constants.Time.SECONDS_PER_HOUR * (nowHour - startHour); } return Math.max(0, Math.min(max, current)); }, /** * In milliseconds. */ getTimeUntilFull: function getTimeUntilFull() { var overallProduction = (this.production || 0) - (this.consumption || 0); if (this.current === undefined) { return Number.POSITIVE_INFINITY; } else { var current = this.getCurrent(); var max = this.city().getResourceCapacity().maximum; var production = this.production; if (overallProduction > 0) { var secondsToNextHour = (Constants.Time.MILLIS_PER_HOUR - (View.gameTimeNow() % Constants.Time.MILLIS_PER_HOUR)) / Constants.Time.MILLIS_PER_SECOND; var atNextHour = current + secondsToNextHour * production; if (atNextHour >= max) { return (max - current) / production * Constants.Time.MILLIS_PER_SECOND; } else { var hours = Math.floor( (max - atNextHour) / overallProduction / Constants.Time.SECONDS_PER_HOUR); var atHours = atNextHour + overallProduction * hours * Constants.Time.SECONDS_PER_HOUR; return (secondsToNextHour + hours * Constants.Time.SECONDS_PER_HOUR + (max - atHours) / production) * Constants.Time.MILLIS_PER_SECOND; } } else if (this.current && this.getCurrent() == max) { // No production, but filled exactly to capacity return 0; } else { return Number.POSITIVE_INFINITY; } } }, /** * In milliseconds. */ getTimeUntilEmpty: function getTimeUntilEmpty() { if (this.current === undefined) { return Number.POSITIVE_INFINITY; } else if (this.consumption) { if (this.production > this.consumption) { // Could run out in first hour, but nobody is going to run their empire that // way so it's not worth the effort of calculating. return Number.POSITIVE_INFINITY; } else { // Wine use takes place on the hour. var current = this.getCurrent(); var production = this.production || 0; var secondsToNextHour = (Constants.Time.MILLIS_PER_HOUR - (View.gameTimeNow() % Constants.Time.MILLIS_PER_HOUR)) / Constants.Time.MILLIS_PER_SECOND; // Compute to end of next hour var atNextHour = current - this.consumption * Constants.Time.SECONDS_PER_HOUR + production * secondsToNextHour; if (atNextHour <= 0) { return secondsToNextHour * Constants.Time.MILLIS_PER_SECOND; } else { var hourlyDiff = (this.consumption - production) * Constants.Time.SECONDS_PER_HOUR; return Constants.Time.MILLIS_PER_SECOND * (secondsToNextHour + Math.ceil(atNextHour / hourlyDiff) * Constants.Time.SECONDS_PER_HOUR); } } } return Number.POSITIVE_INFINITY; }, getCapacity: function getCapacity() { return this.city().getResourceCapacity(); }, getProduction: function getProduction() { return this.production; }, getConsumption: function getConsumption() { return this.consumption; }, }); City.Building = function City_Building(city) { this._ikaToolsType = 'building'; this._setCity(city); this.position = null; this.type = null; this.level = 0; } $.extend(City.Building.prototype, { _setCity: function setCity(city) { if (city) { this.city = Utils.fixedFunction(city); this._scheduleUpgradeComplete(); } }, _update: function _update(position, data) { this.position = position; var changed = false; if (data.building.indexOf('buildingGround') >= 0) { changed = !this.isEmpty(); this.type = ''; delete this.level; delete this.completionTime; } else { var type = data.building.split(' ')[0]; var level = parseInt(data.level); var isUpgrading = 'completed' in data; changed = (type != this.getType() || level != this.getLevel() || isUpgrading != this.isUpgrading()); this.type = type; this.level = level; if (isUpgrading) { var completionTime = parseInt(data.completed) * 1000; if (this.completionTime != completionTime) { this.completionTime = completionTime; this._scheduleUpgradeComplete(); } } else { delete this.completionTime; } } return changed; }, _scheduleUpgradeComplete: function _scheduleUpgradeComplete() { if (this.upgradeEvent) { this.upgradeEvent(); } if (this.completionTime) { if (this.completionTime <= View.gameTimeNow()) { this.level = this.level + 1; delete this.completionTime; raiseBuildingsChanged([{ city: this.city(), building: this, type: Constants.BuildingEventType.UPGRADE_COMPLETE, }]); } else { this.upgradeEvent = buildingsChangedEvent().scheduleSend( this.type + "->" + (this.level + 1), // 0.5.0 still does a full page refresh if you're viewing the city when the // building completes. So we cheat a bit and send this event a few seconds // before it actually takes place so it doesn't get lost as part of a page // refresh that just sees it as a normal "info_refresh" event. this.completionTime - View.gameTimeNow() + Constants.Time.SAFE_TIME_DELTA, function() { this.level = this.level + 1; delete this.completionTime; empireData.saveAsync(); }.bind(this), [{ city: this.city(), building: this, type: Constants.BuildingEventType.UPGRADE_COMPLETE, }]); } } }, getPosition: function getPosition() { return this.position; }, isEmpty: function isEmpty() { return !this.type; }, getType: function getType() { return this.type; }, getLevel: function getLevel() { return this.level; }, isUpgrading: function isUpgrading() { return (this.completionTime > View.gameTimeNow()); }, getRemainingUpgradeTime: function getRemainingUpgradeTime() { var diff = this.completionTime - View.gameTimeNow(); return Math.max(diff, 0); }, getCompletionTime: function getCompletionTime() { return new Date(this.completionTime); }, getUpgradeCosts: function getUpgradeCost() { var city = this.city(); var civData = getCivilizationData(); var buildingCostData = Constants.BuildingData[this.getType()]; var level = this.getLevel() + (this.isUpgrading() ? 1 : 0); var timeParams = buildingCostData.time; var costs = { wood: buildingCostData.wood[level] || 0, wine: buildingCostData.wine[level] || 0, marble: buildingCostData.marble[level] || 0, glass: buildingCostData.glass[level] || 0, sulfur: buildingCostData.sulfur[level] || 0, time: Math.round(timeParams.a / timeParams.b * Math.pow(timeParams.c, level+1) - timeParams.d) * 1000, }; var multiplier = 1.0; multiplier -= civData.hasResearched(Constants.Research.Economy.PULLEY) ? .02 : 0; multiplier -= civData.hasResearched(Constants.Research.Economy.GEOMETRY) ? .04 : 0; multiplier -= civData.hasResearched(Constants.Research.Economy.SPIRIT_LEVEL) ? .08 : 0; var carpenter = city.getBuildingByType(Constants.Buildings.CARPENTER); var winePress = city.getBuildingByType(Constants.Buildings.WINE_PRESS); var architect = city.getBuildingByType(Constants.Buildings.ARCHITECT); var optician = city.getBuildingByType(Constants.Buildings.OPTICIAN); var fireworker = city.getBuildingByType(Constants.Buildings.FIREWORK_TEST_AREA); return { wood: costs.wood * (multiplier - (carpenter ? carpenter.getLevel() / 100 : 0)), wine: costs.wine * (multiplier - (winePress ? winePress.getLevel() / 100 : 0)), marble: costs.marble * (multiplier - (architect ? architect.getLevel() / 100 : 0)), glass: costs.glass * (multiplier - (optician ? optician.getLevel() / 100 : 0)), sulfur: costs.sulfur * (multiplier - (fireworker ? fireworker.getLevel() / 100 : 0)), time: costs.time, }; }, isMaxLevel: function isMaxLevel() { return (this.getLevel() + (this.isUpgrading() ? 1 : 0)) >= Constants.BuildingData[this.getType()].maxLevel; }, }); CivilizationData = function CivilizationData() { this._ikaToolsType = 'civilizationData'; this.research = {}; this.government = 'ikakratie'; this.movements = {}; this.premiumFeatures = {}; } $.extend(CivilizationData.prototype, { _startMovementTimers: function _startMovementTimers() { $.each(this.movements, function updateMovementsOnLoad(id, movement) { if (movement._updateAndStartTimer()) { delete this.movements[id]; } }.bind(this)); }, _updateGovernment: function updateGovernment(government, changedAccumulator) { if (this.government != government) { changedAccumulator.push({ type: Constants.CivilizationData.GOVERNMENT, government: government, }); } this.government = government; }, _updateResearch: function updateResearch(researchId, level, changedAccumulator) { var oldResearch = this.research[researchId]; if (!oldResearch || oldResearch.level != level) { changedAccumulator.push({ type: Constants.CivilizationData.RESEARCH, id: researchId, level: level, }); } this.research[researchId] = { level: level }; }, _updateMovement: function updateMovement(movement, changedAccumulator) { var existing = this.movements[movement.getId()]; if (existing) { existing._cancelTimer(); } this.movements[movement.getId()] = movement; movement._updateAndStartTimer(); if (!existing || (movement.getCompletionTime() != existing.getCompletionTime())) { changedAccumulator.push({ movement: movement, type: Constants.Movements.EventType.DATA_UPDATED, }); } }, _removeMovement: function removeMovement(movementId, changedAccumulator) { var movement = this.movements[movementId]; if (movement) { if (movement.stage == Constants.Movements.Stage.LOADING && movementId >= 0) { var originCity = movement.getOriginCity(); movement._updateCity(originCity, originCity); } movement._cancelTimer(); delete this.movements[movementId]; changedAccumulator.push({ movement: movement, type: Constants.Movements.EventType.CANCELLED, }); } }, _updatePremiumFeature: function updatePremiumFeature( changedAccumulator, feature, enabled) { var currentlyEnabled = this.premiumFeatures[feature] || false; if (currentlyEnabled != enabled) { changedAccumulator.push({ type: Constants.CivilizationData.PREMIUM_FEATURE, feature: feature, enabled: enabled, }); } this.premiumFeatures[feature] = enabled; }, hasResearched: function hasResearched(researchId) { var research = this.research[researchId]; return research ? research.level > 0 : undefined; }, getResearchLevel: function getResearchLevel(researchId) { var research = this.research[researchId]; return research ? research.level : undefined; }, getGovernment: function getGovernment() { return this.government; }, getMovements: function getMovements() { var movements = []; $.each(this.movements, function(id, movement) { movements.push(movement); }); movements.sort(function compareMovements(m1, m2) { return m1.getArrivalTime() - m2.getArrivalTime(); }); return movements; }, getMovement: function getMovement(movementId) { return this.movements[movementId]; }, isPremiumFeatureEnabled: function isPremiumFeatureEnabled(feature) { return this.premiumFeatures[feature]; }, }); function calculateTravelTime(island1Coords, island2Coords, units, transporters) { // same island if (island1Coords[0] == island2Coords[0] && island1Coords[1] == island2Coords[1]) { var baseTime = 10 * Constants.Time.MILLIS_PER_MINUTE; var multiplier = transporters ? 60 : 80; // fastest unit if (units) { $.each(units.getCounts(), function applyUnitSpeed(type, count) { if (count) { var data = Constants.UnitData[type]; multiplier = Math.min(multiplier, data.speed); } }); } return baseTime * 60 / multiplier; } else { var baseTime = 20 * Math.sqrt(Math.pow(island1Coords[0] - island2Coords[0], 2) + Math.pow(island1Coords[1] - island2Coords[1], 2)) * Constants.Time.MILLIS_PER_MINUTE; var multiplier = 60; // fastest ship if (units) { $.each(units.getCounts(), function applyUnitSpeed(type, count) { if (count) { var data = Constants.UnitData[type]; if (!data.isArmy) { multiplier = Math.min(multiplier, data.speed); } } }); } return baseTime * 60 / multiplier; } } function Movement( id, type, completionTime, mission, stage, originCityId, targetCityId, transports, units, resources, transportTime) { this._ikaToolsType = 'movement'; if (id) { this.id = id; this.completionTime = completionTime; this.mission = mission; this.stage = stage; this.originCityId = originCityId; this.targetCityId = targetCityId; this.units = units; this.transports = transports; this.resources = resources; this.transportTime = transportTime; this.type = type; var originCity = this.getOriginCity(); var targetCity = this.getTargetCity(); if (originCity && targetCity) { var originCoords = originCity.getIslandCoordinates(); var targetCoords = targetCity.getIslandCoordinates(); if (originCoords && targetCoords) { this.transportTime = calculateTravelTime( originCoords, targetCoords, this.units, this.transports); } } if (!this.transportTime) { this.transportTime = Number.POSITIVE_INFINITY; } if (this.completionTime <= new Date().getTime()) { this._toNextStage(); } } } $.extend(Movement.prototype, { _cancelTimer: function cancelTimer() { if (this.completionEvent) { this.completionEvent(); } }, _startTimer: function startTimer() { var remainingTime = this.getTimeRemaining(); if (isFinite(remainingTime)) { this.completionEvent = movementsChangedEvent().scheduleSend( 'Movement[' + this.id + ']', remainingTime + Constants.Time.SAFE_TIME_DELTA, function moveToNextStage() { if (this._toNextStage()) { getCivilizationData()._removeMovement(this.id, []); } empireData.saveAsync(); }.bind(this), [{ previousStage: this.getStage(), movement: this, type: this._isFinalStage() ? Constants.Movements.EventType.COMPLETED : Constants.Movements.EventType.STAGE_CHANGED, }]); } }, _updateCity: function updateCity(city, originCity) { var resourceChanges = []; if (city) { if (originCity) { originCity._updateActionPointsBy(1, resourceChanges); } if (city.isOwn() && (this.mission == Constants.Movements.Mission.TRANSPORT || this.mission == Constants.Movements.Mission.PLUNDER)) { $.each(this.resources, function updateCityResource(name, value) { city._incrementResource(name, resourceChanges, value); }); } raiseResourcesChanged(resourceChanges); if (this.mission == Constants.Movements.Mission.DEPLOY_ARMY || this.mission == Constants.Movements.Mission.DEPLOY_NAVY) { var military = city.getMilitary(); military._increment(this.units); raiseMilitaryChanged([{ military: military, city: city, type: 'deployment_arrived', }]); } } }, _updateAndStartTimer: function updateAndStartTimer() { this._cancelTimer(); if (this.completionTime <= View.gameTimeNow()) { return this._toNextStage(); } else { this._startTimer(); } }, _toNextStage: function toNextStage() { var isFinalStage = this._isFinalStage(); if (this.stage == Constants.Movements.Stage.LOADING) { this.stage = Constants.Movements.Stage.EN_ROUTE; this.completionTime += this.transportTime; this._startTimer(); } else if (this.stage == Constants.Movements.Stage.EN_ROUTE) { if (isFinalStage) { this._updateCity(this.getTargetCity(), this.getOriginCity()); } } else if (this.stage == Constants.Movements.Stage.RETURNING) { if (isFinalStage) { var originCity = this.getOriginCity(); this._updateCity(originCity, originCity); } } return isFinalStage; }, _isFinalStage: function isFinalStage() { if (this.stage == Constants.Movements.Stage.LOADING) { return false; } else if (this.stage == Constants.Movements.Stage.EN_ROUTE) { if (this.mission == Constants.Movements.Mission.TRANSPORT) { var city = getCity(this.targetCityId); return city && city.isOwn(); } else if (this.mission == Constants.Movements.Mission.DEPLOY_ARMY || this.mission == Constants.Movements.Mission.DEPLOY_NAVY) { return true; } } else { return true; } }, getId: function getId() { return this.id; }, getMission: function getMission() { return this.mission; }, getStage: function getStage() { return this.stage; }, getOriginCityId: function getOriginCityId() { return this.originCityId; }, getTargetCityId: function getTargetCityId() { return this.targetCityId; }, getOriginCity: function getOriginCity() { return this.originCityId && getCity(this.originCityId); }, getTargetCity: function getTargetCity() { return this.targetCityId && getCity(this.targetCityId); }, getCompletionTime: function getCompletionTime() { return this.completionTime; }, getTimeRemaining: function getTimeRemaining() { return this.completionTime - View.gameTimeNow(); }, getArrivalTime: function() { var time = this.getCompletionTime(); if (this.stage == Constants.Movements.Stage.LOADING) { time += this.transportTime; } return time; }, getUnits: function getUnits() { return this.units; }, getResource: function getResource(resourceName) { return this.resources[resourceName]; }, isHostile: function isHostile() { return this.type.indexOf('hostile') >= 0; }, isOwn: function isOwn() { return this.type.indexOf('own') >= 0; } }); var empireData = new Data.Value( 'empireData', { cities: {}, cityOrder: [], civilizationData: new CivilizationData(), }, { reviver: function empireDataReviver(key, value) { if (value && value._ikaToolsType) { var obj; switch(value._ikaToolsType) { case 'city': obj = new City(); break; case 'building': obj = new City.Building; break; case 'cityResource': obj = new City.Resource(); break; case 'military': obj = new Military(); break; case 'militaryUnits': obj = new MilitaryUnits(); break; case 'trainingBatch': obj = new TrainingBatch(); break; case 'civilizationData': obj = new CivilizationData(); break; case 'movement': obj = new Movement(); break; } $.extend(obj, value); if (obj._postLoad) { obj._postLoad(); } return obj; } return value; }, version: 28, loadCallback: function empireDataLoaded() { getCivilizationData()._startMovementTimers(); }, }); function raiseCivilizationDataChanged(changes) { if (changes.length) { civilizationDataChangedEvent().send(changes); } } var civilizationDataChangedEvent = Utils.thunk(function() { return new Utils.EventDispatcher(); }); function registerCivilizationDataChangedHandler(callback) { return civilizationDataChangedEvent().addListener(callback); } function raiseMovementsChanged(changes) { if (changes.length) { movementsChangedEvent().send(changes); } } var movementsChangedEvent = Utils.thunk(function() { return new Utils.EventDispatcher(); }); function registerMovementsChangedHandler(callback) { return movementsChangedEvent().addListener(callback); } function raiseResourcesChanged(changes) { if (changes.length) { resourcesChangedEvent().send(changes); } } var resourcesChangedEvent = Utils.thunk(function() { return new Utils.EventDispatcher(); }); function registerResourcesChangedHandler(callback) { return resourcesChangedEvent().addListener(callback); } var buildingsChangedEvent = Utils.thunk(function() { var dispatcher = new Utils.EventDispatcher(); return dispatcher; }); function raiseBuildingsChanged(changes) { if (changes.length) { buildingsChangedEvent().send(changes); } } function registerBuildingsChangedHandler(callback) { return buildingsChangedEvent().addListener(callback); } var militaryChangedEvent = Utils.thunk(function() { var dispatcher = new Utils.EventDispatcher(); return dispatcher; }); function raiseMilitaryChanged(changes) { if (changes.length) { militaryChangedEvent().send(changes); } } function registerMilitaryChangedHandler(callback) { return militaryChangedEvent().addListener(callback); }; var TRADE_GOOD_LOOKUP = { "1": Constants.Resources.WINE, "2": Constants.Resources.MARBLE, "3": Constants.Resources.GLASS, "4": Constants.Resources.SULFUR, }; function getCity(id) { return empireData.get().cities[id]; }; var coordsRegex = /\[(\d+):(\d+)\]/; function parseCoordinates(coords) { var match = coords.match(coordsRegex); return [parseInt(match[1]), parseInt(match[2])]; } function processTransportForm(form) { var city = View.getCurrentCity(); var transports = parseInt($('#transporterCount').val()) || 0; var resources = {}; resources[Constants.Resources.WOOD] = parseInt($('#textfield_wood').val()) || 0; resources[Constants.Resources.WINE] = parseInt($('#textfield_wine').val()) || 0; resources[Constants.Resources.MARBLE] = parseInt($('#textfield_marble').val()) || 0; resources[Constants.Resources.GLASS] = parseInt($('#textfield_glass').val()) || 0; resources[Constants.Resources.SULFUR] = parseInt($('#textfield_sulfur').val()) || 0; if ($('#createColony').length) { resources[Constants.Resources.WOOD] += 1250; } var destinationCityId = parseInt($(form.elements['destinationCityId']).val()); var totalResources = resources[Constants.Resources.WOOD] + resources[Constants.Resources.WINE] + resources[Constants.Resources.MARBLE] + resources[Constants.Resources.GLASS] + resources[Constants.Resources.SULFUR]; var loadingCompletion = View.gameTimeNow() + (totalResources / city.getLoadingSpeed() * Constants.Time.MILLIS_PER_SECOND); var movement = new Movement( -(new Date().getTime()), 'own', loadingCompletion, //TODO: multiple loads from same town (if own) stack Constants.Movements.Mission.TRANSPORT, Constants.Movements.Stage.LOADING, city.getId(), destinationCityId, transports, new MilitaryUnits(), resources // TODO: transport time for towns other than one's that are tracked ); View.registerNextIkariamAjaxRequestCallback(function saveTransportData(response) { Utils.iterateIkariamAjaxResponse(response, function lookForSuccessFeedback(index, name, data) { if (name == Constants.IkariamAjaxResponseType.PROVIDE_FEEDBACK && data[0].type == 10) { var changes = []; getCivilizationData()._updateMovement(movement, changes); raiseMovementsChanged(changes); } }); }); } function coordinatesEqual(coordinates1, coordinates2) { if (!coordinates1 || !coordinates2) { return false; } return coordinates1[0] == coordinates2[0] && coordinates1[1] == coordinates2[1]; } function processDeploymentForm(form) { var city = View.getCurrentCity(); var transports = parseInt($('#transporterCount').html()) || 0; var mission = $(form.elements['function']).val() == 'deployArmy' ? Constants.Movements.Mission.DEPLOY_ARMY : Constants.Movements.Mission.DEPLOY_NAVY; var destinationCityId = parseInt($(form.elements['destinationCityId']).val()); var destinationCity = getCity(destinationCityId); var units = new MilitaryUnits(); $.each(Constants.UnitIds, function countDeployingUnits(id, type) { var elementId; if (Constants.UnitData[type].isArmy) { elementId = '#cargo_army_' + id; } else { elementId = '#cargo_fleet_' + id; } units._setCount(type, parseInt($(elementId).val()) || 0); }); var cargoSize = units.getCargoSize(); var loadingCompletion = new Date().getTime() + (units.getCargoSize() / city.getLoadingSpeed() * Constants.Time.MILLIS_PER_SECOND); if (destinationCity && coordinatesEqual(destinationCity.getIslandCoordinates(), city.getIslandCoordinates())) { loadingCompletion = new Date().getTime(); } var movement = new Movement( -(new Date().getTime()), 'own', loadingCompletion, //TODO: multiple loads from same town (if own) stack mission, Constants.Movements.Stage.LOADING, city.getId(), destinationCityId, transports, units, {} // TODO: transport time for towns other than one's that are tracked ); View.registerNextIkariamAjaxRequestCallback(function saveDeploymentData(response) { Utils.iterateIkariamAjaxResponse(response, function lookForSuccessFeedback(index, name, data) { if (name == Constants.IkariamAjaxResponseType.PROVIDE_FEEDBACK && data[0].type == 10) { var military = city.getMilitary(); military._decrement(units); raiseMilitaryChanged([{ military: military, city: city, type: 'movement_started', }]); var changes = []; getCivilizationData()._updateMovement(movement, changes); raiseMovementsChanged(changes); } }); }); } function processPlunderForm(form) { var city = View.getCurrentCity(); var transports = parseInt($('#transporterCount').html()) || 0; var destinationCityId = parseInt($(form.elements['destinationCityId']).val()); var destinationCity = getCity(destinationCityId); var units = new MilitaryUnits(); $.each(Constants.UnitIds, function countDeployingUnits(id, type) { units._setCount(type, parseInt($('#cargo_army_' + id).val()) || 0); }); var cargoSize = units.getCargoSize(); var loadingCompletion = new Date().getTime() + (units.getCargoSize() / city.getLoadingSpeed() * Constants.Time.MILLIS_PER_SECOND); if (destinationCity && coordinatesEqual(destinationCity.getIslandCoordinates(), city.getIslandCoordinates())) { loadingCompletion = new Date().getTime(); } var movement = new Movement( -(new Date().getTime()), 'own', loadingCompletion, //TODO: multiple loads from same town (if own) stack Constants.Movements.Mission.PLUNDER, Constants.Movements.Stage.LOADING, city.getId(), destinationCityId, transports, units, {} // TODO: transport time for towns other than one's that are tracked ); View.registerNextIkariamAjaxRequestCallback(function savePlunderData(response) { Utils.iterateIkariamAjaxResponse(response, function lookForSuccessFeedback(index, name, data) { if (name == Constants.IkariamAjaxResponseType.PROVIDE_FEEDBACK && data[0].type == 10) { var military = city.getMilitary(); military._decrement(units); raiseMilitaryChanged([{ military: military, city: city, type: 'movement_started', }]); var changes = []; getCivilizationData()._updateMovement(movement, changes); raiseMovementsChanged(changes); } }); }); } function processCityMilitaryView(data) { var city = View.getCurrentCity(); var military = city.getMilitary(); var armyTds = $('#tabUnits').find('tr.count td'); var e = false; e |= military._updatePresent(Constants.Military.HOPLITE, parseInt(armyTds[0].innerHTML)); e |= military._updatePresent(Constants.Military.STEAM_GIANT, parseInt(armyTds[1].innerHTML)); e |= military._updatePresent(Constants.Military.SPEARMAN, parseInt(armyTds[2].innerHTML)); e |= military._updatePresent(Constants.Military.SWORDSMAN, parseInt(armyTds[3].innerHTML)); e |= military._updatePresent(Constants.Military.SLINGER, parseInt(armyTds[4].innerHTML)); e |= military._updatePresent(Constants.Military.ARCHER, parseInt(armyTds[5].innerHTML)); e |= military._updatePresent(Constants.Military.GUNNER, parseInt(armyTds[6].innerHTML)); e |= military._updatePresent(Constants.Military.BATTERING_RAM, parseInt(armyTds[7].innerHTML)); e |= military._updatePresent(Constants.Military.CATAPULT, parseInt(armyTds[8].innerHTML)); e |= military._updatePresent(Constants.Military.MORTAR, parseInt(armyTds[9].innerHTML)); e |= military._updatePresent(Constants.Military.GYROCOPTER, parseInt(armyTds[10].innerHTML)); e |= military._updatePresent(Constants.Military.BALLOON_BOMBADIER, parseInt(armyTds[11].innerHTML)); e |= military._updatePresent(Constants.Military.COOK, parseInt(armyTds[12].innerHTML)); e |= military._updatePresent(Constants.Military.DOCTOR, parseInt(armyTds[13].innerHTML)); var navyTds = $('#tabShips').find('tr.count td'); e |= military._updatePresent(Constants.Military.RAM_SHIP, parseInt(navyTds[2].innerHTML)); e |= military._updatePresent(Constants.Military.FLAME_THROWER, parseInt(navyTds[0].innerHTML)); e |= military._updatePresent(Constants.Military.STEAM_RAM, parseInt(navyTds[1].innerHTML)); e |= military._updatePresent(Constants.Military.BALLISTA_SHIP, parseInt(navyTds[4].innerHTML)); e |= military._updatePresent(Constants.Military.CATAPULT_SHIP, parseInt(navyTds[3].innerHTML)); e |= military._updatePresent(Constants.Military.MORTAR_SHIP, parseInt(navyTds[5].innerHTML)); e |= military._updatePresent(Constants.Military.SUBMARINE, parseInt(navyTds[7].innerHTML)); e |= military._updatePresent(Constants.Military.PADDLE_SPEED_SHIP, parseInt(navyTds[8].innerHTML)); e |= military._updatePresent(Constants.Military.BALLOON_CARRIER, parseInt(navyTds[9].innerHTML)); e |= military._updatePresent(Constants.Military.TENDER, parseInt(navyTds[10].innerHTML)); e |= military._updatePresent(Constants.Military.ROCKET_SHIP, parseInt(navyTds[6].innerHTML)); military._markPresentUpdated(); if (e) { raiseMilitaryChanged([{ city: city, military: military, type: 'data_updated', }]); } } function processRelatedCitiesView(data) { var city = View.getCurrentCity(); var military = city.getMilitary(); military._clear(); var changed = false; var info = $('#relatedCities .contentBox01h:eq(0)'); var whitespace = /\s+/; info.find('.troops .armybutton').each(function(i, element) { var type = element.className.split(whitespace)[1]; changed |= military._updatePresent(type, parseInt(element.innerHTML)); }); info.find('.troops .fleetbutton').each(function(i, element) { var type = element.className.split(whitespace)[1]; changed |= military._updatePresent(type, parseInt(element.innerHTML)); }); military._markPresentUpdated(); if (changed) { raiseMilitaryChanged([{ city: city, military: military, type: 'data_updated', }]); } } function parseMilitaryUnitsFromPending(container) { var idRegex = /\d+/; var units = new MilitaryUnits(); container.children('.army_wrapper').each(function(index, unitNode) { var unitNode = $(unitNode); var type = Constants.UnitIds[ parseInt(unitNode.find('.army').attr('class').match(idRegex)[0])]; var count = parseInt(unitNode.find('.unitcounttextlabel').html()); units._setCount(type, count); }); return units; } function parseTrainingBatches(viewHtmlText, type, buildingLevel) { var trainingBatches = []; var constructionList = $('#unitConstructionList'); if (constructionList.length) { var completionTime = parseInt(viewHtmlText.match( /showUnitCountdown.'buildCountDown', 'buildProgress', (\d+)/)[1]) * 1000; trainingBatches.push(new TrainingBatch(type, completionTime, parseMilitaryUnitsFromPending(constructionList))); constructionList.children('.constructionBlock').each(function() { var units = parseMilitaryUnitsFromPending($(this)); completionTime += computeTrainingTime(buildingLevel, units); trainingBatches.push(new TrainingBatch(type, completionTime, units)); }); } return trainingBatches; } function computeTrainingTime(barracksLevel, units) { var time = 0; $.each(units.getCounts(), function(type, count) { var data = Constants.UnitData[type]; time += count * Math.pow(0.95, barracksLevel - data.minimumBuildingLevelToBuild) * data.baseBuildTime; }); return time * Constants.Time.MILLIS_PER_SECOND; } function processBarracksView(viewHtmlText, data) { var city = View.getCurrentCity(); var military = city.getMilitary(); var barracks = city.getBuildingByType(Constants.Buildings.BARRACKS); var changed = false; function update(type, dataName) { if (data[dataName]) { changed = military._updatePresent(type, parseInt(data[dataName].text)); } } update(Constants.Military.HOPLITE, 'js_barracksUnitUnitsAvailable1'); update(Constants.Military.STEAM_GIANT, 'js_barracksUnitUnitsAvailable2'); update(Constants.Military.SPEARMAN, 'js_barracksUnitUnitsAvailable3'); update(Constants.Military.SWORDSMAN, 'js_barracksUnitUnitsAvailable4'); update(Constants.Military.SLINGER, 'js_barracksUnitUnitsAvailable5'); update(Constants.Military.ARCHER, 'js_barracksUnitUnitsAvailable6'); update(Constants.Military.GUNNER, 'js_barracksUnitUnitsAvailable7'); update(Constants.Military.BATTERING_RAM, 'js_barracksUnitUnitsAvailable8'); update(Constants.Military.CATAPULT, 'js_barracksUnitUnitsAvailable9'); update(Constants.Military.MORTAR, 'js_barracksUnitUnitsAvailable10'); update(Constants.Military.GYROCOPTER, 'js_barracksUnitUnitsAvailable11'); update(Constants.Military.BALLOON_BOMBADIER, 'js_barracksUnitUnitsAvailable12'); update(Constants.Military.COOK, 'js_barracksUnitUnitsAvailable13'); update(Constants.Military.DOCTOR, 'js_barracksUnitUnitsAvailable14'); military._markPresentUpdated(true, false); military._setArmyTrainingBatches( parseTrainingBatches(viewHtmlText, Constants.Military.ARMY, barracks.getLevel())); raiseMilitaryChanged([{ city: city, military: military, type: 'data_updated', }]); } function processShipyardView(viewHtmlText, data) { var city = View.getCurrentCity(); var military = city.getMilitary(); var shipyard = city.getBuildingByType(Constants.Buildings.SHIPYARD); var change = false; function update(type, dataName) { if (data[dataName]) { changed = military._updatePresent(type, parseInt(data[dataName].text)); } } update(Constants.Military.FLAME_THROWER, 'js_barracksUnitUnitsAvailable1'); update(Constants.Military.STEAM_RAM, 'js_barracksUnitUnitsAvailable2'); update(Constants.Military.RAM_SHIP, 'js_barracksUnitUnitsAvailable3'); update(Constants.Military.CATAPULT_SHIP, 'js_barracksUnitUnitsAvailable4'); update(Constants.Military.BALLISTA_SHIP, 'js_barracksUnitUnitsAvailable5'); update(Constants.Military.MORTAR_SHIP, 'js_barracksUnitUnitsAvailable6'); update(Constants.Military.ROCKET_SHIP, 'js_barracksUnitUnitsAvailable7'); update(Constants.Military.SUBMARINE, 'js_barracksUnitUnitsAvailable8'); update(Constants.Military.PADDLE_SPEED_SHIP, 'js_barracksUnitUnitsAvailable9'); update(Constants.Military.BALLOON_CARRIER, 'js_barracksUnitUnitsAvailable10'); update(Constants.Military.TENDER, 'js_barracksUnitUnitsAvailable11'); military._markPresentUpdated(false, true); military._setNavyTrainingBatches( parseTrainingBatches(viewHtmlText, Constants.Military.NAVY, shipyard.getLevel())); raiseMilitaryChanged([{ city: city, military: military, type: 'data_updated', }]); } function processAcademyView(data) { var changes = []; View.getCurrentCity()._updateScientists( parseInt(data['js_academy_research_tooltip_basic_production'].text), changes); raiseResourcesChanged(changes); } function processSetScientistsForm(form) { var scientists = parseInt($('#inputScientists').val()); View.registerNextIkariamAjaxRequestCallback(function saveScientstsData(response) { Utils.iterateIkariamAjaxResponse(response, function lookForSuccessFeedback(index, name, data) { if (name == Constants.IkariamAjaxResponseType.PROVIDE_FEEDBACK && data[0].type == 10) { var changes = []; View.getCurrentCity()._updateScientists(scientists, changes); raiseResourcesChanged(changes); } }); }); } function processPalaceView(data) { var changes = []; getCivilizationData()._updateGovernment( $('.government_pic img').attr('src').slice(16, -8), changes); raiseCivilizationDataChanged(changes); } function processMuseumView(data) { var changes = []; View.getCurrentCity()._updateCulturalGoods( parseInt(/\d+/.exec($('#val_culturalGoodsDeposit').parent().text())[0]), changes); raiseResourcesChanged(changes); } function processCulturalPossessionsAssignView(data) { // Have to delay this because the script elements in the changed view // need to run before we can access the cultural good information. // There is no feasible way to extract the data at this point. setTimeout(function() { var cityIdRegex = /textfield_city_(\d+)/ var changes = []; $('#moveCulturalGoods ul li input').each(function (index, item) { item = $(item); var city = getCity( parseInt(cityIdRegex.exec(item.attr('id'))[1])); city._updateCulturalGoods(parseInt(item.val()), changes); }); raiseResourcesChanged(changes); empireData.saveAsync(); }, 0); } function processTownHallView(data) { var changes = []; var city = View.getCurrentCity(); city._updatePriests( parseInt(data['js_TownHallPopulationGraphPriestCount'].text), changes); city._updateCulturalGoods( parseInt( data['js_TownHallSatisfactionOverviewCultureBoniTreatyBonusValue'].text) / 50, changes); city._updateTavernWineLevel( parseInt(data['js_TownHallSatisfactionOverviewWineBoniServeBonusValue'].text) / 60, changes); raiseResourcesChanged(changes); } function processTempleView(data) { var changes = []; View.getCurrentCity()._updatePriests( parseInt(data['js_TempleSlider'].slider.ini_value), changes); raiseResourcesChanged(changes); } function processResearchAdvisorView(data) { var civData = getCivilizationData(); var idRegex = /researchId=([0-9]+)/i var levelRegex = /\((\d+)\)/; var researches = JSON.parse(data['new_js_params'] || data['load_js'].params).currResearchType; var changes = []; $.each(researches, function (name, researchData) { var id = parseInt(idRegex.exec(researchData.aHref)[1]); var levelMatch = levelRegex.exec(name); var level = levelMatch ? parseInt(levelMatch[1]) - 1 : (researchData.liClass == 'explored' ? 1 : 0); civData._updateResearch(id, level, changes); }); raiseCivilizationDataChanged(changes); } function processFinancesView(data) { var cities = getOwnCities(); var scientistCost = 6; if (getCivilizationData().hasResearched(Constants.Research.Science.LETTER_CHUTE)) { scientistCost = 3; } var changes = [] $('#finances .table01:eq(1) tr').slice(1, -1).each(function(index, row) { var tds = $(row).children('td'); var city = cities[index]; if ($(tds[0]).text() == city.getName()) { city._updateScientists( Math.round(-parseInt($(tds[2]).text().replace(',', '')) / scientistCost), changes); } }); raiseResourcesChanged(changes); } function processMilitaryAdvisorView(data) { var civilizationData = getCivilizationData(); var movementIds = {}; var changes = []; $.each(civilizationData.getMovements(), function(index, movement) { movementIds[movement.getId()] = movement; }); var movementMainValueRegex = /^js_MilitaryMovementsEventRow(\d+)$/; var cityIdRegex = /cityId=(\d+)/; $.each(data, function(key, value) { var match = movementMainValueRegex.exec(key); if (match /*&& value.class.indexOf('own') > 0*/) { var movementId = parseInt(match[1]); delete movementIds[movementId]; var type = value.class; var completionTime = data['js_MilitaryMovementsEventRow' + movementId + 'ArrivalTime'] .countdown.enddate * Constants.Time.MILLIS_PER_SECOND; var originCityId = parseInt( data['js_MilitaryMovementsEventRow' + movementId + 'OriginLink'].href .match(cityIdRegex)[1]); var targetCityId = data['js_MilitaryMovementsEventRow' + movementId + 'TargetLink'].href ? parseInt(data['js_MilitaryMovementsEventRow' + movementId + 'TargetLink'].href .match(cityIdRegex)[1]) : 0; var mission = data['js_MilitaryMovementsEventRow' + movementId + 'MissionIcon'] .class.split(' ')[1]; var stage = Constants.Movements.Stage.LOADING; var statusClass = data['js_MilitaryMovementsEventRow' + movementId + 'Mission'].class; if (statusClass && statusClass.indexOf('arrow_right_green') >= 0) { stage = Constants.Movements.Stage.EN_ROUTE; } else if (statusClass && statusClass.indexOf('arrow_left_green') >= 0) { stage = Constants.Movements.Stage.RETURNING; } var transports = 0; var resources = {}; var units = new MilitaryUnits(); $.each( data['js_MilitaryMovementsEventRow' + movementId + 'UnitDetails'] .appendElement || [], function processUnit(index, item) { var count = parseInt(item.text); if (item.class.indexOf('ship_transport') >= 0) { transports = count; } if (item.class.indexOf(Constants.Resources.WOOD) >= 0) { resources[Constants.Resources.WOOD] = count; } else if (item.class.indexOf(Constants.Resources.WINE) >= 0) { resources[Constants.Resources.WINE] = count; } else if (item.class.indexOf(Constants.Resources.MARBLE) >= 0) { resources[Constants.Resources.MARBLE] = count; } else if (item.class.indexOf(Constants.Resources.GLASS) >= 0) { resources[Constants.Resources.GLASS] = count; } else if (item.class.indexOf(Constants.Resources.SULFUR) >= 0) { resources[Constants.Resources.SULFUR] = count; } $.each(Constants.Military, function findIsUnit(key, type) { if (item.class.indexOf(' ' + type) >= 0) { units._setCount(type, count); return false; } }); }); var movement = new Movement( movementId, type, completionTime, mission, stage, originCityId, targetCityId, transports, units, resources); civilizationData._updateMovement(movement, changes); } }); $.each(movementIds, function removeMissingMovements(id, value) { civilizationData._removeMovement(id, changes); }); raiseMovementsChanged(changes); } function processPremiumView(data) { var civilizationData = getCivilizationData(); var changes = []; civilizationData._updatePremiumFeature(changes, Constants.PremiumFeatures.DOUBLED_SAFE_CAPACITY, $('#js_buySafecapacityBonusActiveTime').hasClass('green')); civilizationData._updatePremiumFeature(changes, Constants.PremiumFeatures.DOUBLED_STORAGE_CAPACITY, $('#js_buyStoragecapacityBonusActiveTime').hasClass('green')); raiseCivilizationDataChanged(changes); } function updateAndStartTracking() { // Process all known cities that show up in the dropdown. // Drop any cities that are no longer there. var cities = { }; var cityOrder = []; function updateCurrentCity(globalData, backgroundData, correctWineConsumption) { var currentCity = View.getCurrentCity(); if (View.viewIsCity() && currentCity.getId() == parseInt(backgroundData.id)) { currentCity._updateFromBackgroundData(backgroundData); } currentCity._updateFromGlobalData(globalData, correctWineConsumption); Logging.debug("Current city %s[%s]: ", currentCity.name, currentCity.id, currentCity); } $.each(unsafeWindow.dataSetForView.relatedCityData, function updateFromPage_Each(key, value) { if (key.substring(0, 5) == "city_") { var city = empireData.get().cities[value.id] || new City(value.id, value.relationship); city.type = value.relationship; city.name = value.name; city.islandCoordinates = parseCoordinates(value.coords); if (value.tradegood) { city.tradeGoodType = TRADE_GOOD_LOOKUP[value.tradegood]; } empireData.get().cities[city.id] = city; cityOrder.push(city.id); Logging.debug("City %s[%s]: %o", city.name, city.id, city); } }); empireData.get().cityOrder = cityOrder; var globalData = { maxActionPoints: parseInt($('#js_GlobalMenu_maxActionPoints').text()), }; updateCurrentCity($.extend(globalData, unsafeWindow.dataSetForView), unsafeWindow.ikariam.backgroundView.screen.data, true); empireData.saveAsync(); function updateEmpireDataFromGlobalData(data) { View.setGameTimeDifference(new Date().getTime() - (data['time'] || data[1]) * Constants.Time.MILLIS_PER_SECOND); updateCurrentCity(data['headerData'] || data[10], data['backgroundData'] || data[11]); } View.registerIkariamAjaxResponseCallback( function updateEmpireDataFromAjaxResponse(response) { var globalData; var view; var viewHtml; var templateData; Utils.iterateIkariamAjaxResponse(response, function(index, name, data) { if (name == Constants.IkariamAjaxResponseType.UPDATE_GLOBAL_DATA) { globalData = data; } else if (name == Constants.IkariamAjaxResponseType.CHANGE_VIEW) { view = data[0]; viewHtml = data[1]; } else if (name == Constants.IkariamAjaxResponseType.UPDATE_TEMPLATE_DATA) { templateData = data; } }); if (globalData) { updateEmpireDataFromGlobalData(globalData); } if (view == Constants.View.CITY_MILITARY) { processCityMilitaryView(templateData); } else if (view == Constants.View.RELATED_CITIES) { processRelatedCitiesView(templateData); } else if (view == Constants.View.ACADEMY) { processAcademyView(templateData); } else if (view == Constants.View.PALACE) { processPalaceView(templateData); } else if (view == Constants.View.MUSEUM) { processMuseumView(templateData); } else if (view == Constants.View.ASSIGN_CULTURAL_POSSESSIONS) { processCulturalPossessionsAssignView(templateData); } else if (view == Constants.View.TOWN_HALL) { processTownHallView(templateData); } else if (view == Constants.View.TEMPLE) { processTempleView(templateData); } else if (view == Constants.View.RESEARCH_ADVISOR) { processResearchAdvisorView(templateData); } else if (view == Constants.View.FINANCES) { processFinancesView(templateData); } else if (view == Constants.View.BARRACKS) { processBarracksView(viewHtml, templateData); } else if (view == Constants.View.SHIPYARD) { processShipyardView(viewHtml, templateData); } else if (view == Constants.View.MILITARY_ADVISOR) { processMilitaryAdvisorView(templateData); } else if (view == Constants.View.PREMIUM) { processPremiumView(templateData); } if (unsafeWindow.ikariam.templateView) { if (unsafeWindow.ikariam.templateView.id == Constants.View.RESEARCH_ADVISOR) { processResearchAdvisorView(templateData); } } empireData.saveAsync(); }, true); View.registerAjaxFormSubmitCallback( function ajaxHandlerCallFromFormEmpireDataUpdate(form) { if (form.id == 'transport' || form.id == 'transportForm') { processTransportForm(form); } else if (form.id == 'deploymentForm') { processDeploymentForm(form); } else if (form.id == 'plunderForm') { processPlunderForm(form); } else if (form.id == 'setScientists') { processSetScientistsForm(form); } }); } function updateMovements(callback) { View.backgroundGetIkariamPage( 'http://' + document.domain + '/index.php?view=militaryAdvisor&activeTab=militaryMovements&ajax=1', function updateMovementsCallback(response) { var dataResponse = JSON.parse(response.responseText); Utils.iterateIkariamAjaxResponse(dataResponse, function(index, name, data) { if (name == Constants.IkariamAjaxResponseType.UPDATE_TEMPLATE_DATA) { processMilitaryAdvisorView(data); } }); empireData.saveAsync(); callback(dataResponse); }, 'POST'); } function getCities() { var data = empireData.get(); var cities = []; for (var i = 0; i < data.cityOrder.length; i++) { cities.push(data.cities[data.cityOrder[i]]); } return cities; } function getOwnCities() { return getCities().filter(function(city) { return city.isOwn(); }); } function getCivilizationData() { return empireData.get().civilizationData; } function getDebugString(includePrivateData) { return JSON.stringify(empireData.get(), function debugStringify(name, value) { if (name === 'name' || name === 'islandCoordinates') { return undefined; } return value; }); } function resetData() { empireData.reset(); } var Espionage = function() { function Target(id) { this._ikaToolsType = 'target'; if (id) { this.id = id; this.playerId = undefined; this.allianceId = undefined; this.townLevel = undefined; this.wallLevel = undefined; this.warehouseLevel = undefined; this.islandId = undefined; this.coords = undefined; this.occupierId = undefined; this.blockaderId = undefined; this.tradeGoodType = undefined; this.lastUpdateTime = 0; this.military = new MilitaryUnits(); this.otherMilitary = new MilitaryUnits(); this.militaryLastSpyMessageId = 0; this.militaryLastSpyTime = 0; this.resources = { wood: 0, wine: 0, marble: 0, glass: 0, sulfur: 0, lastSpyMessageId: 0, lastSpyTime: 0, }; this.combats = {}; } } function combatComparer(combat1, combat2) { return combat2.time - combat1.time; }; $.extend(Target.prototype, { getId: function getId() { return this.id; }, getName: function getName() { return this.name; }, getTownLevel: function getTownLevel() { return this.townLevel; }, getWallLevel: function getWallLevel() { return this.wallLevel; }, getIslandCoordinates: function getIslandCoordinates() { return this.coords; }, getIslandId: function getIslandId() { return this.islandId; }, getPlayer: function _getPlayer() { return getPlayer(this.playerId); }, getOccupier: function getOccupier() { if (this.occupierId) { return getPlayer(this.occupierId); } return null; }, getBlockader: function getBlockader() { if (this.blockaderId) { return getPlayer(this.blockaderId); } }, getTradeGoodType: function getTradeGoodType() { return this.tradeGoodType; }, getMilitary: function getMilitary() { return this.military; }, getOtherMilitary: function getOtherMilitary() { return this.otherMilitary; }, hasResourceInfo: function hasResourceInfo() { return this.resources.lastSpyMessageId > 0; }, hasMilitaryInfo: function hasMilitaryInfo() { return this.militaryLastSpyMessageId > 0; }, getLootableResources: function getLootableResources(type) { var available = this.resources[type]; $.each(this.getCombats(View.gameTimeNow() - this.resources.lastSpyTime), function subtractCombatResources(index, combat) { available -= combat.getLooted(type); }); return Math.max(0, available - (this.getPlayer().isInactive() ? Constants.GamePlay.RESOURCE_PROTECTION_WAREHOUSE_INACTIVE : Constants.GamePlay.RESOURCE_PROTECTION_WAREHOUSE) * this.warehouseLevel - Constants.GamePlay.BASE_RESOURCE_PROTECTION); }, getResourcesSpyTime: function getResourcesSpyTime() { return this.resources.lastSpyTime; }, getMilitarySpyTime: function getMilitarySpyTime() { return this.militaryLastSpyTime; }, getCombats: function getCombats(maxAge) { var combats = []; $.each(this.combats, function(index, combat) { if (View.gameTimeNow() - combat.time <= maxAge) { combats.push(combat); } }); combats.sort(combatComparer); return combats; }, remove: function remove() { delete espionageData.get().targets[this.id]; espionageData.saveAsync(); raiseEspionageChanged({ type: 'targetRemoved', targets: [this] }); }, _refresh: function refresh(hasSpiesPresent, callback) { if (View.gameTimeNow() - this.lastRefreshTime < Constants.Time.MILLIS_PER_HOUR) { console.log('Skipping refresh'); callback(); return; } this.lastRefreshTime = new Date().getTime(); var datasLoaded = hasSpiesPresent ? 2 : 1; function doneLoading() { if (--datasLoaded == 0) { callback(); } } Utils.backgroundFetchIkariamFullPage( 'http://' + document.domain + '/index.php?view=island&cityId=' + this.id, this._refreshIslandCallback.bind(this, doneLoading)); if (hasSpiesPresent) { Utils.backgroundFetchIkariamFullPage( 'http://' + document.domain + '/index.php?view=city&cityId=' + this.id, this._refreshCityCallback.bind(this, doneLoading)); } }, _refreshIslandCallback: function refreshIslandCallback(callback, response, ajaxResponse) { var target = this; Utils.iterateIkariamAjaxResponse(ajaxResponse, function refreshTargetData(index, name, data) { if (name == IkaTools.Constants.IkariamAjaxResponseType.UPDATE_BACKGROUND_DATA) { $.each(data.cities, function findTarget(index, city) { if (parseInt(city.id) == target.id) { var playerId = parseInt(city.ownerId); target.name = city.name; target.playerId = parseInt(city.ownerId); target.townLevel = parseInt(city.level); target.islandId = parseInt(data.id); target.coords = [parseInt(data.xCoord), parseInt(data.yCoord)]; target.tradeGoodType = TRADE_GOOD_LOOKUP[data.tradegood]; updateOrAddPlayer(playerId, city.ownerName, city.state, parseInt(city.ownerAllyId), city.ownerAllyTag, parseInt(data.avatarScores[playerId].army_score_main.split(',').join('').split(',').join('')) / 100); if (city.infos && city.infos['occupation']) { target.occupierId = city.infos['occupation'].id; updateOrAddPlayer(city.infos['occupation'].id, city.infos['occupation'].name); } else { target.occupierId = 0; } if (city.infos && city.infos['fleetAction']) { target.blockaderId = city.infos['fleetAction'].id; updateOrAddPlayer(city.infos['fleetAction'].id, city.infos['fleetAction'].name); } else { target.blockaderId = 0; } } }); } }); callback(this); }, _refreshCityCallback: function refreshCityCallback(callback, response, ajaxResponse) { var target = this; Utils.iterateIkariamAjaxResponse(ajaxResponse, function refreshTargetData(index, name, data) { if (name == IkaTools.Constants.IkariamAjaxResponseType.UPDATE_BACKGROUND_DATA) { target.wallLevel = parseInt(data.position[14].level) || 0; target.warehouseLevel = 0; $.each(data.position, function(index, item) { if (item.building == Constants.Buildings.WAREHOUSE) { target.warehouseLevel += parseInt(item.level); } }); } }); callback(this); }, _getOrAddCombat: function getOrAddCombat(id) { var combat = this.combats[id]; if (!combat) { combat = new Target.Combat(id); this.combats[id] = combat; } return combat; }, }); Target.Combat = function Target_Combat(id) { this._ikaToolsType = 'targetCombat'; if (id) { this.id = id; this.type = undefined; this.time = undefined; this.resources = { wood: 0, wine: 0, marble: 0, glass: 0, sulfur: 0, }; } } $.extend(Target.Combat.prototype, { getType: function getType() { return this.type; }, getTime: function getTime() { return this.time; }, getLooted: function getLooted(resourceType) { return this.resources[resourceType]; }, }); function Player(id) { this._ikaToolsType = 'player'; if (id) { this.id = id; this.name = null; this.allianceId = null; this.militaryScore = null; } } $.extend(Player.prototype, { _update: function update(name, state, allianceId, militaryScore) { this.name = name; if (state !== undefined) { this.allianceId = allianceId; this.militaryScore = militaryScore; this.state = state; } }, getAlliance: function getAlliance() { if (this.allianceId) { return espionageData.get().alliances[this.allianceId]; } else { return; } }, getName: function getName() { return this.name; }, getState: function getState() { return this.state; }, getMilitaryScore: function getMilitaryScore() { return this.militaryScore; }, isInactive: function isInactive() { return this.state == Constants.PlayerState.INACTIVE; }, }); function updateOrAddPlayer(id, name, state, allianceId, allianceName, militaryScore) { var players = espionageData.get().players; var player = players[id]; if (!player) { player = new Player(id); players[id] = player; } player._update(name, state, allianceId, militaryScore); updateOrAddAlliance(allianceId, allianceName); } function Alliance(id) { this._ikaToolsType = 'alliance'; if (id) { this.id = id; this.name = null; } } $.extend(Alliance.prototype, { _update: function update(name) { this.name = name; }, getName: function getName() { return this.name; }, getId: function getId() { return this.id; } }); function updateOrAddAlliance(id, name) { if (id) { var alliances = espionageData.get().alliances; var alliance = alliances[id]; if (!alliance) { alliance = new Alliance(id); alliances[id] = alliance; } alliance._update(name); } } function addTargetById(id, hasSpiesPresent, callback) { var targets = espionageData.get().targets; var target = targets[id]; if (!target) { var target = new Target(id); target._refresh(hasSpiesPresent, function() { targets[id] = target; callback(target); }); } else { target._refresh(hasSpiesPresent, function() { callback(target); }); } } function getTargets() { var targets = []; $.each(espionageData.get().targets, function(index, target) { targets.push(target); }); return targets; } function getTarget(id) { return espionageData.get().targets[id]; } function getPlayer(id) { return espionageData.get().players[id]; } function getPlayers() { return espionageData.get().players; } var espionageData = new Data.Value( 'espionageData', { targets: {}, alliances: {}, players: {}, }, { reviver: function espionageDataReviver(key, value) { if (value && value._ikaToolsType) { var obj; switch(value._ikaToolsType) { case 'target': obj = new Target(); break; case 'targetCombat': obj = new Target.Combat(); break; case 'player': obj = new Player(); break; case 'alliance': obj = new Alliance(); break; case 'militaryUnits': obj = new MilitaryUnits(); break; } $.extend(obj, value); if (obj._postLoad) { obj._postLoad(); } return obj; } return value; }, version: 6, loadCallback: function espionageDataLoaded() { }, }); var espionageChangedEvent = Utils.thunk(function() { return new Utils.EventDispatcher(); }); function registerEspionageChangedHandler(callback) { return espionageChangedEvent().addListener(callback); } function raiseEspionageChanged(changes) { espionageChangedEvent().send(changes); } function startTracking() { var messageIdRegex = /message(\d+)/ espionageData.load(); View.registerIkariamAjaxResponseCallback( function processHideoutView(response) { IkaTools.Utils.iterateIkariamAjaxResponse(response, function(index, name, data) { if (name == IkaTools.Constants.IkariamAjaxResponseType.CHANGE_VIEW) { if (data[0] == Constants.View.HIDEOUT) { var targetCount = 0; var targets = []; $('#tabSafehouse li.city a').each(function(index, item) { targetCount++; addTargetById(parseInt(Utils.parseUrlParams($(item).attr('href'))['cityId']), true, function targetAdded(target) { targets.push(target); targetCount--; if (targetCount == 0) { espionageData.saveAsync(); raiseEspionageChanged({ type: 'targetsChanged', targets: targets }); } }); }); var reportHeaders = $( '#espionageReports tr.espionageReports, #espionageReports tr.espionageReportsalt'); if (reportHeaders.length) { var changedTargets = []; reportHeaders.each(function(index, reportHeader) { var success = $('td.resultImage img', reportHeader).attr('src') == 'skin/buttons/yes.png'; var target = getTarget(parseInt( Utils.parseUrlParams($('td.targetCity a', reportHeader).attr('href'))['selectCity'])); var messageId = parseInt(reportHeader.id.match(messageIdRegex)[1]); var tableMailMessage = $('#tbl_mail' + messageId); if (success && target) { if ($('td.money', reportHeader).length && messageId > target.resources.lastSpyMessageId) { // Warehouse resources mission var resourceTds = $('#tbl_mail' + messageId + ' td.count'); target.resources.wood = parseInt(resourceTds.get(0).textContent.replace(',', '')); target.resources.wine = parseInt(resourceTds.get(1).textContent.replace(',', '')); target.resources.marble = parseInt(resourceTds.get(2).textContent.replace(',', '')); target.resources.glass = parseInt(resourceTds.get(3).textContent.replace(',', '')); target.resources.sulfur = parseInt(resourceTds.get(4).textContent.replace(',', '')); target.resources.lastSpyMessageId = messageId; target.resources.lastSpyTime = Utils.parseIkariamTimestamp($('td.date', reportHeader).text()).getTime(); target._refresh(false, function() { espionageData.saveAsync(), raiseEspionageChanged({ type: 'targetsRefreshed', targets: [target], }); }); changedTargets.push(target); } else if ($('td.garrison', reportHeader).length && messageId > target.militaryLastSpyMessageId && tableMailMessage.find(' table.reportTable').length) { readSpiedMilitary(target.getMilitary(), tableMailMessage.find('table.reportTable tr:nth-child(2) td.count')); readSpiedMilitary(target.getOtherMilitary(), tableMailMessage.find('table.reportTable tr:nth-child(3) td.count')); target.militaryLastSpyMessageId = messageId; target.militaryLastSpyTime = Utils.parseIkariamTimestamp($('td.date', reportHeader).text()).getTime(); target._refresh(false, function() { espionageData.saveAsync(), raiseEspionageChanged({ type: 'targetsRefreshed', targets: [target], }); }); changedTargets.push(target); } } }); if (changedTargets.length) { espionageData.saveAsync(); raiseEspionageChanged({ type: 'targetsChanged', targets: changedTargets }); } } } else if (data[0] == Constants.View.MILITARY_ADVISOR_REPORT) { var report = $('#troopsReport'); var defender = report.find('.defender b:first a:first'); if (defender.length) { var target = getTarget(parseInt( Utils.parseUrlParams(defender.attr('href'))['cityId'])); if (target) { var header = report.find('.header'); var result = report.find('div.result'); var combatId = parseInt(Utils.parseUrlParams( report.find('div p.link:first a.button:first',report).attr('href'))['detailedCombatId']); var combatTime = Utils.parseIkariamTimestamp(report.find('.header .date').text()).getTime(); var type = report.find('.overview .fleet').length ? IkaTools.Constants.CombatType.BLOCKADE : IkaTools.Constants.CombatType.PILLAGE; var combat = target._getOrAddCombat(combatId); combat.type = type; combat.time = combatTime; result.find('.resources li.value').each(function(index, item) { var resourceInfo = $(item); var type = resourceInfo.find('img').attr('src').match(/icon_([a-z]*)_small.png/)[1]; if (type == 'crystal') { type = Constants.Resources.GLASS; } var amount = parseInt(resourceInfo.text()); combat.resources[type] = amount; }); target._refresh(false, function() { espionageData.saveAsync(), raiseEspionageChanged({ type: 'targetsRefreshed', targets: [target], }); }); } espionageData.saveAsync(); raiseEspionageChanged({ type: 'combatUpdated', targets: [target], }); } } } else if (name == Constants.IkariamAjaxResponseType.BACKGROUND_DATA) { } }); }, true); } function readSpiedMilitary(military, unitTds) { var baseArmyCount = true; var baseNavyCount = true; var navyOffset = 0; if (unitTds.length == 14) { // army only baseNavyCount = 0; } else if (unitTds.length == 11) { // navy only baseArmyCount = 0; navyOffset = -14; } else if (unitTds.length == 0) { // nothing baseNavyCount = 0; baseArmyCount = 0; } military._setCount(Constants.Military.HOPLITE, baseArmyCount && parseInt($(unitTds[0]).text()) || 0); military._setCount(Constants.Military.STEAM_GIANT, baseArmyCount && parseInt($(unitTds[1]).text()) || 0); military._setCount(Constants.Military.SPEARMAN, baseArmyCount && parseInt($(unitTds[2]).text()) || 0); military._setCount(Constants.Military.SWORDSMAN, baseArmyCount && parseInt($(unitTds[3]).text()) || 0); military._setCount(Constants.Military.SLINGER, baseArmyCount && parseInt($(unitTds[4]).text()) || 0); military._setCount(Constants.Military.ARCHER, baseArmyCount && parseInt($(unitTds[5]).text()) || 0); military._setCount(Constants.Military.GUNNER, baseArmyCount && parseInt($(unitTds[6]).text()) || 0); military._setCount(Constants.Military.BATTERING_RAM, baseArmyCount && parseInt($(unitTds[7]).text()) || 0); military._setCount(Constants.Military.CATAPULT, baseArmyCount && parseInt($(unitTds[8]).text()) || 0); military._setCount(Constants.Military.MORTAR, baseArmyCount && parseInt($(unitTds[9]).text()) || 0); military._setCount(Constants.Military.GYROCOPTER, baseArmyCount && parseInt($(unitTds[10]).text()) || 0); military._setCount(Constants.Military.BALLOON_BOMBADIER, baseArmyCount && parseInt($(unitTds[11]).text()) || 0); military._setCount(Constants.Military.COOK, baseArmyCount && parseInt($(unitTds[12]).text()) || 0); military._setCount(Constants.Military.DOCTOR, baseArmyCount && parseInt($(unitTds[13]).text()) || 0); military._setCount(Constants.Military.RAM_SHIP, parseInt(baseNavyCount && $(unitTds[16 + navyOffset]).text()) || 0); military._setCount(Constants.Military.FLAME_THROWER, parseInt(baseNavyCount && $(unitTds[14 + navyOffset]).text()) || 0); military._setCount(Constants.Military.STEAM_RAM, parseInt(baseNavyCount && $(unitTds[15 + navyOffset]).text()) || 0); military._setCount(Constants.Military.BALLISTA_SHIP, parseInt(baseNavyCount && $(unitTds[18 + navyOffset]).text()) || 0); military._setCount(Constants.Military.CATAPULT_SHIP, parseInt(baseNavyCount && $(unitTds[17 + navyOffset]).text()) || 0); military._setCount(Constants.Military.MORTAR_SHIP, parseInt(baseNavyCount && $(unitTds[19 + navyOffset]).text()) || 0); military._setCount(Constants.Military.SUBMARINE, parseInt(baseNavyCount && $(unitTds[21 + navyOffset]).text()) || 0); military._setCount(Constants.Military.PADDLE_SPEED_SHIP, parseInt(baseNavyCount && $(unitTds[22 + navyOffset]).text()) || 0); military._setCount(Constants.Military.BALLOON_CARRIER, parseInt(baseNavyCount && $(unitTds[23 + navyOffset]).text()) || 0); military._setCount(Constants.Military.TENDER, parseInt(baseNavyCount && $(unitTds[24 + navyOffset]).text()) || 0); military._setCount(Constants.Military.ROCKET_SHIP, parseInt(baseNavyCount && $(unitTds[20 + navyOffset]).text()) || 0); } function getDebugString(includePrivateData) { return JSON.stringify(espionageData.get(), function debugStringify(name, value) { if (name === 'name' || name === 'coordinates') { return undefined; } return value; }); } function resetData() { espionageData.reset(); } return { startTracking: startTracking, registerEspionageChangedHandler: registerEspionageChangedHandler, getTargets: getTargets, getPlayers: getPlayers, getDebugString: getDebugString, resetData: resetData, }; }(); return { updateAndStartTracking: Logging.debuggable( { label: 'IkaTools.EmpireData.updateAndStartTracking', alwaysTime: true }, updateAndStartTracking), updateMovements: updateMovements, calculateTravelTime: calculateTravelTime, getCities: Utils.thunk(getCities), getOwnCities: Utils.thunk(getOwnCities), getCivilizationData: getCivilizationData, getCity: getCity, getDebugString: getDebugString, resetData: resetData, registerCivilizationDataChangedHandler: registerCivilizationDataChangedHandler, registerResourcesChangedHandler: registerResourcesChangedHandler, registerBuildingsChangedHandler: registerBuildingsChangedHandler, registerMilitaryChangedHandler: registerMilitaryChangedHandler, registerMovementsChangedHandler: registerMovementsChangedHandler, Espionage:Espionage, }; }(); function processAnchor() { var anchor = window.location.hash; if (anchor == '#ikaScriptToolsSuppressCityPreselect') { document.location.hash = ''; //unsafeWindow.ikariam.backgroundView.screen.preselectCity = // function suppressPreselectCity() { }; View.suppressFirstChangeViewOfType('cityDetails'); } if (anchor.substring(0, 35) == '#ikaScriptToolsLoadLocalIkariamUrl=') { var url = decodeURIComponent(anchor.substring(35)); document.location.hash = ''; IkaTools.View.loadLocalIkariamUrl(url); if (IkaTools.View.viewIsIsland()) { View.suppressFirstChangeViewOfType('cityDetails'); } } else if (anchor.substring(0, 62) == '#ikaScriptToolsLoadLocalIkariamUrl_DoNotSuppressFirstCityInfo=') { var url = decodeURIComponent(anchor.substring(62)); document.location.hash = ''; IkaTools.View.loadLocalIkariamUrl(url); } } function initialize(options) { processAnchor(); $(window).bind('hashchange', processAnchor); options = $.extend({ trackData: true }, options); View.setGameTimeDifference(new Date().getTime() - unsafeWindow.ikariam.model.requestTime * Constants.Time.MILLIS_PER_SECOND - Constants.Time.INITIAL_PAGE_LOAD_DELTA); if (options.trackData) { IkaTools.EmpireData.updateAndStartTracking(); } } return { Logging: Logging, Utils: Utils, View: View, Data: Data, Intl: Intl, EmpireData: EmpireData, Constants: Constants, UI: UI, Settings: Settings, initialize: initialize, processAnchor: processAnchor, }; })(); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址