Kittens tools

Kittens tools (visual)

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Kittens tools
// @namespace    http://bloodrizer.ru/games/kittens/
// @version      1.226
// @description  Kittens tools (visual)
// @author       Anton
// @match        http://bloodrizer.ru/games/kittens/
// @require      https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.2.22/cytoscape.min.js
// ==/UserScript==

(function() {
	'use strict';

	var $ = jQuery, _br = '<br/>';

	var _Core = {
        secondsToTimeStr: function (seconds) {
            seconds = parseInt(seconds);
            if (seconds > 0) {
                var hours = parseInt(seconds / 3600);
                var minutes = parseInt((seconds - 3600 * hours) / 60);
                var sec = seconds - 3600 * hours - 60 * minutes;
                var _lz = function(x) { return (x < 10 ? '0' + x : x); };
                return _lz(hours) + ':' + _lz(minutes) + ':' + _lz(sec);
            }
            return '00:00:00';
        },
        clone: function (obj) {
            var copy;

            // Handle the 3 simple types, and null or undefined
            if (null == obj || 'object' != typeof obj) return obj;

            // Handle Date
            if (obj instanceof Date) {
                copy = new Date();
                copy.setTime(obj.getTime());
                return copy;
            }

            // Handle Array
            if (obj instanceof Array) {
                copy = [];
                for (var i = 0, len = obj.length; i < len; i++) {
                    copy[i] = _Core.clone(obj[i]);
                }
                return copy;
            }

            // Handle Object
            if (obj instanceof Object) {
                copy = {};
                for (var attr in obj) {
                    if (obj.hasOwnProperty(attr)) copy[attr] = _Core.clone(obj[attr]);
                }
                return copy;
            }

            throw new Error('Unable to copy obj! Its type isn\'t supported.');
        },
        getBotVersion: function () {
            return typeof GM_info == 'function' ? GM_info().script.version :
                (typeof GM_info == 'object' ? GM_info.script.version : '?');
        },
        getCurrentDateTimeStr: function () {
            var d = new Date();
            var getPaddedComp = function (comp) {
                return ((parseInt(comp) < 10) ? ('0' + comp) : comp)
            };
            var date = getPaddedComp(d.getDate()) + '.' + getPaddedComp(d.getMonth() + 1) + '.' + d.getFullYear();
            var time = getPaddedComp(d.getHours()) + ':' + getPaddedComp(d.getMinutes());
            return (date + " " + time);
        },
        floatToStr: function (num, maxDigits) {
            var resultStr = 0;
            num = parseFloat(num);
            var sign = num >= 0 ? 1 : -1;
            num = Math.abs(num);
            if (num >= 0.01) {
                if (typeof maxDigits === 'undefined') maxDigits = 2;
                resultStr = num.toFixed(maxDigits);
            } else if (num >= 0.001) {
                if (typeof maxDigits === 'undefined') maxDigits = 3;
                resultStr = num.toFixed(maxDigits);
            } else if (num >= 0.0001) {
                if (typeof maxDigits === 'undefined') maxDigits = 4;
                resultStr = num.toFixed(maxDigits);
            } else if (num >= 0.00001) {
                if (typeof maxDigits === 'undefined') maxDigits = 5;
                resultStr = num.toFixed(maxDigits);
            } else if (num >= 0.1) {
                if (typeof maxDigits === 'undefined') maxDigits = 1;
                resultStr = num.toFixed(maxDigits);
            } else {
                resultStr = num > 1 ? num.toFixed(0) : '0+';
            }
            return sign > 0 ? resultStr : '-' + resultStr;
        },
        percentToStr: function(chanceInPercent) {
            return _Core.floatToStr(chanceInPercent) + '%';
        },
        chanceToStr: function(chance) {
            return _Core.percentToStr(parseFloat(chance) * 100);
        }
    };

    var _Helpers = {
        _spaceBuildings: undefined,
        isGameReady: function() {
            return $('#game').css('display') === 'block';
        },
        isResourceUnlocked: function(res) {
            var r = game.resPool.get(res);
            if (r.unlocked || (r.visible && r.craftable && !r.isHidden)) {
                return true;
            } else {
                var workshop = game.workshop.crafts;
                for (var i in workshop) {
                    if (workshop.hasOwnProperty(i)) {
                        if (workshop[i].name === res && workshop[i].unlocked) {
                            return true;
                        }
                    }
                }
            }
            return false;
        },
        getMinCraft: function(res) {
            // craft no more than 2% of possible craft
            var allCount = game.workshop.getCraftAllCount(res);
            var craftLevel = _BotSettings.getCraftLevel();
            var ratioCount = Math.floor(allCount * craftLevel / 100);
            return ratioCount < 1 ? 1 : ratioCount;
        },
        getPricesForModel: function(model, isReligion, isSpace) {
            var ratio = model.priceRatio || (isSpace ? 1.15 : 1);
            var prices = _Core.clone(model.prices);
            if (isReligion) {
                prices = game.village.getEffectLeader('wise', prices);
            }
            for (var i = 0; i < prices.length; i++){
                if (isSpace && prices[i].name === "oil"){
                    prices[i].val = prices[i].val * Math.pow(1.05, model.val);
                    var reductionRatio = game.getHyperbolicEffect(game.getEffect("oilReductionRatio"), 0.75);
                    prices[i].val *= (1 - reductionRatio);
                } else {
                    prices[i].val = prices[i].val * Math.pow(ratio, model.val);
                }
            }
            return prices;
        },
        canBuyBuilding: function(bldName) {
            var prices = game.bld.getPrices(bldName);
            return _Helpers.isEnoughResourcesForPrice(prices);
        },
        isEnoughResourcesForPrice: function(prices) {
            if (isNaN(prices.length)) {
                return false; // if it is not an array
            }
            for (var x in prices) {
                if (prices.hasOwnProperty(x)) {
                    var resName = prices[x].name;
                    if (prices[x].val > game.resPool.get(resName).value) {
                        return false;
                    }
                    if (resName === 'catnip') {
                        if (!_Helpers.canSpendMint(prices[x].val)) {
                            return false;
                        }
                    }
                    if (_BotSettings.isCheckedResSetting(resName)) {
                        return false;
                    }
                }
            }
            return true;
        },
        getPromoteInfo: function(kitten, rank, doNotCheckGold) {
            if (doNotCheckGold) {
                var currentGold = parseFloat('+Infinity')
            } else {
                currentGold = game.resPool.get('gold').value;
            }

            var sim = game.village.sim;
            var kittenRank = kitten.rank;
            if (typeof(rank) == 'undefined') {
                rank = kitten.rank + 1;
            }
            var rankDiff = rank - kittenRank;
            var expToPromote = 0;
            var goldToPromote = 0;
            if (rankDiff > 0) {
                expToPromote = sim.expToPromote(kittenRank, rank, kitten.exp);
                goldToPromote = sim.goldToPromote(kittenRank, rank, currentGold);
            }
            return {
                rankDiff: rankDiff,
                exp: expToPromote,
                gold: goldToPromote
            };
        },
        canPromoteKitten: function(kitten, rank, doNotCheckGold) {
            var promoteInfo = _Helpers.getPromoteInfo(kitten, rank, doNotCheckGold);
            var canPromote = false;

            if (promoteInfo.rankDiff > 0) {
                if (promoteInfo.exp[0] && (promoteInfo.gold[0] || doNotCheckGold === true)) {
                    canPromote = true;
                } else if (promoteInfo.rankDiff > 1) { // If rank is unreachable, try one rank
                    return _Helpers.canPromoteKitten(kitten, undefined, doNotCheckGold);
                }
            }

            return {
                promoteInfo: promoteInfo,
                canPromote: canPromote
            };
        },
        getKittensForPromoteInfo: function(doNotCheckGold) {
            var promotedKittens = [];

            for (var i = 0; i < game.village.sim.kittens.length; i++) {
                var done = false;
                if(game.village.sim.kittens[i].engineerSpeciality != null) {
                    var tier = game.workshop.getCraft(game.village.sim.kittens[i].engineerSpeciality).tier;
                    if (game.village.sim.kittens[i].rank < tier) {
                        promotedKittens.push({'kitten': game.village.sim.kittens[i], 'rank': tier});
                        done = true;
                    }
                }
                if (!done) {
                    promotedKittens.push({'kitten': game.village.sim.kittens[i]});
                }
            }

            var promotedKittensCount = 0;
            var totalGoldNeeded = 0;
            if(promotedKittens.length) {
                for (i = 0; i < promotedKittens.length; i++) {
                    if (typeof(promotedKittens[i].rank) == 'number') {
                        var promoteInfo = _Helpers.canPromoteKitten(promotedKittens[i].kitten, promotedKittens[i].rank, doNotCheckGold);
                        promotedKittensCount += promoteInfo.canPromote ? 1 : 0;
                        totalGoldNeeded += promoteInfo.canPromote ? promoteInfo.promoteInfo.gold[1] : 0;
                    } else {
                        promoteInfo = _Helpers.canPromoteKitten(promotedKittens[i].kitten, undefined, doNotCheckGold);
                        promotedKittensCount += promoteInfo.canPromote ? 1 : 0;
                        totalGoldNeeded += promoteInfo.canPromote ? promoteInfo.promoteInfo.gold[1] : 0;
                    }
                }
            }

            return {
                count: promotedKittensCount,
                gold: totalGoldNeeded
            };
        },
        getZebraTitanium: function () {
            if (game.diplomacy.get('zebras').unlocked) {
                var shipVal = game.resPool.get('ship').value;
                var shipRate = shipVal * 0.35; //0.35% per ship to get titanium
                var titaniumAmt = 1.5;
                titaniumAmt += titaniumAmt * (shipVal / 100) * 2;	//2% more titanium per ship
                return {percent: shipRate + 15, value: titaniumAmt};
            }
            return {percent: 0, value: 0};
        },
        getGameStandingRatio: function () {
            var standingRatio = game.getEffect('standingRatio');
            standingRatio = standingRatio ? standingRatio : 0;
            if (game.prestige.getPerk('diplomacy').researched){
                standingRatio += 10;
            }
            return standingRatio;
        },
        getBuildingTitle: function (building_type) {
            var bld = game.bld.get(building_type);
            return bld.stages && bld.stages.length > 0 ? bld.stages[bld.stage].label : bld.label;
        },
        getResourceTitle: function (resId) {
            if (typeof game.resPool.resourceMap[resId] === 'object') {
                return game.resPool.resourceMap[resId].title;
            }
            return resId;
        },
        canSpendMint: function (mintAmount) {
            var calendar = game.calendar,
                winterDays = calendar.daysPerSeason -
                    (calendar.getCurSeason().name === 'winter' ? calendar.day : 0);
            var catnipPerTick = game.calcResourcePerTick('catnip', { modifiers:{
                    'catnip' : 0.25
                }});	//calculate estimate winter per tick for catnip;

            return (game.resPool.get('catnip').value - mintAmount + (winterDays * catnipPerTick / calendar.dayPerTick)) > 0;
        },
        getMagnetoBonus: function (addSteamwork, addMagneto) {
            if (typeof addSteamwork === 'undefined') addSteamwork = 0;
            if (typeof addMagneto === 'undefined') addMagneto = 0;

            var steamworks = game.bld.get('steamworks');
            var steamworksOn = steamworks.on + addSteamwork;
            var magnetoOn = game.bld.get('magneto').effects.magnetoRatio * addMagneto;
            var swRatio = steamworksOn > 0 ? (1+ steamworks.effects['magnetoBoostRatio'] * steamworksOn) : 1;
            return (game.getEffect('magnetoRatio') + magnetoOn) * swRatio;
        },
        getBuildingPrice: function (building_type) {
            return game.bld.getPrices(building_type);
        },
        isBuildingUnlocked: function(building_type) {
            return game.bld.isUnlocked(game.bld.get(building_type));
        },
        canBuildNow: function (building_type) {
            if (_Helpers.isBuildingUnlocked(building_type)) {
                var prices = _Helpers.getBuildingPrice(building_type);
                for (var i in prices) {
                    if (prices.hasOwnProperty(i)) {
                        var resName = prices[i].name;
                        var resVal = prices[i].val;
                        var res = game.resPool.get(resName);

                        if (!res.unlocked) return false;
                        if (res.maxValue > 0 && res.maxValue < resVal) return false;
                    }
                }
                return true;
            }
            return false;
        },
        getMaximumScienceResourcesToKeep: function () {
            var maxCompediums = 0;
            var maxParchment = 0;
            for (var i in game.science.techs) {
                if (game.science.techs.hasOwnProperty(i)) {
                    var tech = game.science.techs[i];
                    if (tech.unlocked && !tech.researched) {
                        var prices = tech.prices;
                        for (var p in prices) {
                            if (prices.hasOwnProperty(p)) {
                                if (prices[p].name === 'compedium') {
                                   if (prices[p].val > maxCompediums) {
                                       maxCompediums = prices[p].val;
                                   }
                                } else if (prices[p].name === 'parchment') {
                                    if (prices[p].val > maxParchment) {
                                        maxParchment = prices[p].val;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return {
                maxCompediums: maxCompediums,
                maxParchment: maxParchment
            };
        },
        getMaximumScienceResourcesForBuildings: function() {
            var maxParchments = 0, maxManuscript = 0;
            var currentSettingBuys = _BotSettings.settings[_BotSettings.settingIdx].buys;
            for (var bldName in currentSettingBuys) {
                if (currentSettingBuys.hasOwnProperty(bldName)) {
                    if (currentSettingBuys[bldName] === true) {
                        var bld = game.bld.get(bldName);
                        if (bld.unlocked && _Helpers.canBuildNow(bldName)) {
                            var prices = _Helpers.getBuildingPrice(bldName);
                            for (var x in prices) {
                                if (prices.hasOwnProperty(x)) {
                                    if (prices[x].name === 'parchment' && prices[x].val > maxParchments) {
                                        maxParchments = prices[x].val;
                                    }
                                    if (prices[x].name === 'manuscript' && prices[x].val > maxManuscript) {
                                        maxManuscript = prices[x].val;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return {
                maxManuscript: maxManuscript,
                maxParchment: maxParchments
            };
        },
        getUnicornRiftChance: function () {
            var unicornChanceRatio = 1;
            if (game.prestige.getPerk('unicornmancy').researched){
                unicornChanceRatio = 1.1;
            }
            unicornChanceRatio *= (1 + game.getEffect('timeRatio') * 0.25);
            var riftChance = game.getEffect('riftChance');	//5 OPTK
            return (riftChance * unicornChanceRatio) / 10000;
        },
        getAlicornChance: function () {
            var aliChance = game.getEffect('alicornChance');	//0.2 OPTK
            return aliChance / 100000;
        },
        getAstronomyChance: function () {
            var chanceRatio = 1;

            if (game.prestige.getPerk('chronomancy').researched){
                chanceRatio = 1.1;
            }

            chanceRatio *= (1 + game.getEffect('timeRatio') * 0.25);

            var chance = 25;									//25 OPTK of event per day	(0.25%)
            chance += (game.getEffect('starEventChance') * 10000);
            chance *= chanceRatio;

            if (game.prestige.getPerk('astromancy').researched){
                chance *= 2;
            }

            return chance / 10000;
        },
        getCorruptionSpeed: function () {
            var sorrow = game.resPool.get('sorrow').value * 0.1;
            var alicorns = game.resPool.get('alicorn');
            var corruption = 0;
            if (alicorns.value > 0){
                corruption =
                    (
                        game.getEffect('corruptionRatio') *
                        (1 + Math.sqrt( sorrow * game.getEffect('blsCorruptionRatio') ))
                    )
                    * (game.resPool.get('necrocorn').value > 0 ?
                    0.25 * (1 + game.getEffect('corruptionBoostRatio')) :	 //75% penalty
                    1);
            }
            return corruption;
        },
        getSecondsForOneCorruption: function () {
            if (game.religion.corruption < 1) {
                var corruptionSpeed = _Helpers.getCorruptionSpeed();
                if (corruptionSpeed > 0) {
                    return Math.floor((1 - game.religion.corruption) / corruptionSpeed / game.getRateUI());
                }
            }
            return 0;
        },
        getAlicornsPerSecond: function () {
            return game.getEffect('alicornPerTick') * game.getRateUI();
        },
        getEldersChance: function () {
            var pyramidVal = game.religion.getZU('blackPyramid').val;
            if ( pyramidVal > 0 ){
                var markerVal = game.religion.getZU('marker').val;
                var eldersChance = 35 * pyramidVal * (1 + 0.1 * markerVal);
                return eldersChance / 1000;
            }
            return 0;
        },
        getDiplomacyTradeValues: function (race) {
            var boughtResourceCollection = {},
                tradeRatio = 1 + game.diplomacy.getTradeRatio(),
                raceRatio = race.name === "leviathans" ? (1 + 0.02 * race.energy) : 1,
                currentSeason = game.calendar.getCurSeason().name;

            for(var i = 0; i < race.sells.length; i++){
                var sellResource = race.sells[i];
                var resourceSeasonTradeRatio = sellResource.seasons[currentSeason];
                var minBoughtAmount = (1 - sellResource.delta / 2);
                var maxBoughtAmount = (1 - sellResource.delta / 2) + sellResource.delta;

                var boughtAmountMin = minBoughtAmount * sellResource.value * resourceSeasonTradeRatio * tradeRatio * raceRatio;
                var boughtAmountMax = maxBoughtAmount * sellResource.value * resourceSeasonTradeRatio * tradeRatio * raceRatio;

                boughtResourceCollection[sellResource.name] = {
                    min: boughtAmountMin,
                    max: boughtAmountMax,
                    chance: sellResource.chance / 100
                };
            }

            return boughtResourceCollection;
        },
        getSolarPanelEnergyProductionDifference: function (seasonId, panelsCount) {
            var energyProduction = 2;
            var energyProductionDifference = 0;
            if (typeof seasonId === 'undefined') seasonId = game.calendar.season;
            if (typeof panelsCount === 'undefined') panelsCount = 1;
            energyProduction *= 1 + game.getEffect('solarFarmRatio');
            if (parseInt(seasonId) === 3) { // winter
                //energyProduction *= 0.75;
                energyProductionDifference = -energyProduction * 0.25 * panelsCount;
            } else if (parseInt(seasonId) === 1) { // summer
                //energyProduction /= 0.75;
                energyProductionDifference = energyProduction / 3 * panelsCount;
            }
            return energyProductionDifference;
        },
        getEnergyProduction: function (seasonId) {
            var energy = game.resPool.energyProd;
            var solarFarm = game.bld.get('pasture');
            if (parseInt(solarFarm.stage) === 1 && solarFarm.on > 0) {
                if (typeof seasonId === 'undefined') seasonId = game.calendar.season;
                if (parseInt(seasonId) !== parseInt(game.calendar.season)) {
                    var solarEnergyBonus = _Helpers.getSolarPanelEnergyProductionDifference(undefined, solarFarm.on);
                    var solarBonusForSeason = _Helpers.getSolarPanelEnergyProductionDifference(seasonId, solarFarm.on);

                    return energy - solarEnergyBonus + solarBonusForSeason;
                }
            }
            return energy;
        },
        getEnergyReserve: function (seasonId) {
            return _Helpers.getEnergyProduction(seasonId) - game.resPool.energyCons;
        },
        getEnergyReserves: function () {
            var result = [];
            for (var i in game.calendar.seasons) {
                if (game.calendar.seasons.hasOwnProperty(i)) {
                    var season = game.calendar.seasons[i];
                    result.push({
                        name: season.name,
                        title: season.title,
                        reserve: _Helpers.getEnergyReserve(i)
                    });
                }
            }
            return result;
        },
        getFaithRatio: function (withReset) {
            if (withReset) {
                return game.religion.faithRatio + game.religion.getApocryphaResetBonus(1.01);
            }
            return game.religion.faithRatio;
        },
        getFaithBonus: function (withReset) {
            var ratio = _Helpers.getFaithRatio(withReset);
            return game.getTriValue(ratio, 0.1) * 0.1;
        },
        getProductionBonus: function(isAtMaximum, addTranscendence, addObelisk) {
            if (typeof addTranscendence === 'undefined') addTranscendence = 0;
            if (typeof addObelisk === 'undefined') addObelisk = 0;
            var rate = game.religion.getRU("solarRevolution").on ? game.getTriValue(game.religion.faith, 1000) : 0;
            var atheismBonus = game.challenges.getChallenge("atheism").researched ? game.religion.getTranscendenceLevel() * 0.1 : 0;
            var blackObeliskBonus = (game.religion.getTranscendenceLevel() + addTranscendence) *
                (game.religion.getTU("blackObelisk").val + addObelisk) * 0.005;
            var baseRate = isAtMaximum ? 1000 : game.getHyperbolicEffect(rate, 1000);
            rate = baseRate * (1 + atheismBonus + blackObeliskBonus);
            return rate;
        },
        getNextTranscendPrice: function () {
            var tclevel = game.religion.getTranscendenceLevel();
            return game.religion.getTranscendenceRatio(tclevel + 1) - game.religion.getTranscendenceRatio(tclevel);
        },
        getGflopsValue: function () {
            return game.resPool.get('gflops').value;
        },
        getGflopsConsumption: function () {
            var bld = game.space.getBuilding('entangler');
            if (bld.on > 0) {
                return bld.effects['gflopsConsumption'] * game.getRateUI() * bld.on;
            }
            return 0;
        },
        getGflopsProduction: function () {
            var bld = game.bld.get('aiCore');
            if (bld.on > 0) {
                return bld.effects['gflopsPerTickBase'] * bld.on * game.getRateUI();
            }
            return 0;
        },
        getMaxBcoinPriceChangePerSecond: function () {
            return game.calendar.cryptoPrice * 0.01 / 400;
        },
        getVoidPerDay: function() {
            // -1, 0, 1, 2, 3 at start, 1 on average
            var maxPerDay = 2 + game.getEffect('temporalParadoxVoid');
            return {
                max: maxPerDay,
                min: -1,
                average: (maxPerDay - 1) / 2
            };
        },
        getDaysInParadox: function () {
            return 10 + game.getEffect('temporalParadoxDay');
        },
        getSpaceBuildings: function () {
            if (typeof _Helpers._spaceBuildings === 'undefined') {
                _Helpers._spaceBuildings = [];
                for (var i = game.space.planets.length - 1; i >= 0; i--) {
                    var planet = game.space.planets[i];
                    if (planet.buildings) {
                        for (var j = planet.buildings.length - 1; j >= 0; j--) {
                            var bld = planet.buildings[j];
                            _Helpers._spaceBuildings.push(bld);
                        }
                    }
                }
            }
            return _Helpers._spaceBuildings;
        }
    };

    var _Statistics = {
        _statTitle: 'Bot statistics',
        _collector: {
            crafts: {},
            craftsCount: {},
            counters: {},
            diplomacyCount: {}
        },
        addStatistics: function (group, name, value) {
            if (!_Statistics._collector[group].hasOwnProperty(name)) {
                _Statistics._collector[group][name] = parseFloat(value);
            } else {
                _Statistics._collector[group][name] += parseFloat(value);
            }
        },
        addCraft: function (name, value) {
            _Statistics.addStatistics('crafts', name, value);
        },
        addCraftCount: function (name, value) {
            _Statistics.addStatistics('craftsCount', name, value);
        },
        addCounter: function (name, value) {
            _Statistics.addStatistics('counters', name, value);
        },
        getStatsGroup: function () {
            var result = {
                group: [],
                title: _Statistics._statTitle
            };

            for (var statGrp in _Statistics._collector) {
                if (_Statistics._collector.hasOwnProperty(statGrp)) {
                    var groupName = statGrp;
                    var statItems = _Statistics._collector[statGrp];
                    for (var statItemIdx in statItems) {
                        if (statItems.hasOwnProperty(statItemIdx)) {
                            var item = statItems[statItemIdx];
                            result.group.push({
                                name: groupName + ': ' + statItemIdx,
                                title: groupName + ': ' + statItemIdx,
                                val: item,
                                unlocked: true
                            });
                        }
                    }
                }
            }

            return result;
        }
    };

    var _Logs = {
        _logs: {
            log2: [],
            hunt: [],
            wrkshp: [],
            trade: [],
            build: [],
            craft: []
        },
        mainLog: function(message) {
            var mes = 'BOT: ' + message;
            if (game && game.msg && game.ui) {
                game.msg(mes, 'msg');
                game.ui.renderConsoleLog();
            }
        },
        buildLog: function(message) {
            _Logs.logCustom('build', message, true);
        },
        log2: function(message) {
            _Logs.logCustom('log2', message);
        },
        logCustom: function(logName, message, withDateTime) {
            while (_Logs._logs[logName].length >= 100) {
                _Logs._logs[logName].shift();
            }
            if (withDateTime === true) {
                message = '[' + _Core.getCurrentDateTimeStr() + '] ' + message;
            }
            _Logs._logs[logName].push(message);

            if (_BotUI.currentPage === logName) {
                _BotUI.initCustomLogPage(logName);
            }
        },
        console: function(message) {
            if (console) console.log(message);
        },
        getCustomLogItem: function(logName, index) {
            if (_Logs._logs[logName].hasOwnProperty(index)) {
                return _Logs._logs[logName][index];
            }
            return '';
        },
        getCustomLogSize: function(logName) {
            return _Logs._logs[logName].length
        },
        clearCustomLog: function (logName) {
            _Logs._logs[logName] = [];
        },
        logTrade: function (message) {
            _Logs.logCustom('trade', message);
        },
        logCraft: function (message) {
            _Logs.logCustom('craft', message);
        }
    };

    var _BotSetting = {
	    auto: {
            Buy: false,
            PromoteKittens: true,
            CollectFaith: true,
            CreateSteel: true,
            CreatePlates: true,
            SendHunters: true,
            CreateManuscript: true,
            CreateWood: true,
            CreateBeams: true,
            CreateSlabs: true,
            CreateCompedium: true,
            CreateBlueprints: true,
            Trade: false,
            CreateKerosene: true,
            CreateThorium: true,
            CreateEludium: true,
            BuyReligion: true,
            FeedElders: false,
            TradeBcoins: true,
            CreateAlloy: false,
            BuySpace: true
        },
        craftLevel: 2,
        buys: {},
        space: {},
        keeps: {}
    };
	var zeroSetting = _Core.clone(_BotSetting);

	// noinspection JSUnusedGlobalSymbols
    var _BotSettingsMigrator = {
	    currentVersion: 11,
        migrate1to2: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.CreateBlueprints === 'undefined') {
                        _BotSettings.settings[i].auto.CreateBlueprints = true;
                    }
                    if (typeof _BotSettings.settings[i].auto.CreateCompedium === 'undefined') {
                        if (typeof _BotSettings.settings[i].auto['CreateCompendium'] !== 'undefined') {
                            _BotSettings.settings[i].auto.CreateCompedium = _BotSettings.settings[i].auto['CreateCompendium'];
                            delete _BotSettings.settings[i].auto['CreateCompendium'];
                        } else {
                            _BotSettings.settings[i].auto.CreateCompedium = true;
                        }
                    }
                }
            }
            _BotSettings.version = 2;
            _BotSettings.storeSetting();
        },
        migrate2to3: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.CreateKerosene === 'undefined') {
                        _BotSettings.settings[i].auto.CreateKerosene = true;
                    }
                }
            }
            _BotSettings.version = 3;
            _BotSettings.storeSetting();
        },
        migrate3to4: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.CreateThorium === 'undefined') {
                        _BotSettings.settings[i].auto.CreateThorium = true;
                    }
                }
            }
            _BotSettings.version = 4;
            _BotSettings.storeSetting();
        },
        migrate4to5: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.CreateEludium === 'undefined') {
                        _BotSettings.settings[i].auto.CreateEludium = true;
                    }
                }
            }
            _BotSettings.version = 5;
            _BotSettings.storeSetting();
        },
        migrate5to6: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.BuyReligion === 'undefined') {
                        _BotSettings.settings[i].auto.BuyReligion = true;
                    }
                }
            }
            _BotSettings.version = 6;
            _BotSettings.storeSetting();
        },
        migrate6to7: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].craftLevel === 'undefined') {
                        _BotSettings.settings[i].craftLevel = 2;
                    }
                }
            }
            _BotSettings.version = 7;
            _BotSettings.storeSetting();
        },
        migrate7to8: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.FeedElders === 'undefined') {
                        _BotSettings.settings[i].auto.FeedElders = false;
                    }
                }
            }
            _BotSettings.version = 8;
            _BotSettings.storeSetting();
        },
        migrate8to9: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.TradeBcoins === 'undefined') {
                        _BotSettings.settings[i].auto.TradeBcoins = true;
                    }
                }
            }
            _BotSettings.version = 9;
            _BotSettings.storeSetting();
        },
        migrate9to10: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.CreateAlloy === 'undefined') {
                        _BotSettings.settings[i].auto.CreateAlloy = false;
                    }
                }
            }
            _BotSettings.version = 10;
            _BotSettings.storeSetting();
        },
        migrate10to11: function() {
            for (var i in _BotSettings.settings) {
                if (_BotSettings.settings.hasOwnProperty(i)) {
                    if (typeof _BotSettings.settings[i].auto.BuySpace === 'undefined') {
                        _BotSettings.settings[i].auto.BuySpace = true;
                    }
                    if (typeof _BotSettings.settings[i].space !== 'object') {
                        _BotSettings.settings[i].space = {};
                    }
                }
            }
            _BotSettings.version = 11;
            _BotSettings.storeSetting();
        },
        migrateAll: function () {
	        for (var i = 1; i < _BotSettingsMigrator.currentVersion; i++) {
                if (_BotSettings.version === i) {
                    _BotSettingsMigrator['migrate' + i + 'to' + (i + 1)]();
                    _Logs.console('BOT: Migrated from ' + i + ' to ' + (i + 1));
                }
            }
        }
    };

    var _BotSettings = {
	    version: _BotSettingsMigrator.currentVersion,
        speed: 2000,
        settingIdx: 0,
	    settings: [zeroSetting],
	    init: function () {
	        for (var i = 1; i < 5; i++) {
                var newZeroSetting = _Core.clone(_BotSetting);
	            _BotSettings.settings.push(newZeroSetting);
            }
            _BotUI.initSettingsLink();
            _BotUI.initSettingsPage();
            _BotSettings.restoreAll();
        },
        toggleSetting: function (settingName) {
            var currentSettingAuto = _BotSettings.settings[_BotSettings.settingIdx].auto;
            if (typeof currentSettingAuto[settingName] === 'boolean') {
                currentSettingAuto[settingName] = !currentSettingAuto[settingName];
                _BotUI.initSettingsPage();
                _BotSettings.storeSetting();
            }
            return false;
        },
        toggleBuySetting: function (name) {
            var currentSettingBuys = _BotSettings.settings[_BotSettings.settingIdx].buys;
            if (typeof currentSettingBuys[name] === 'boolean') {
                currentSettingBuys[name] = !currentSettingBuys[name];
                _BotUI.initSettingsPage();
                _BotSettings.storeSetting();
            } else {
                currentSettingBuys[name] = true;
                _BotUI.initSettingsPage();
                _BotSettings.storeSetting();
            }
            return false;
        },
        toggleSpaceSetting: function (name) {
            var currentSettingSpace = _BotSettings.settings[_BotSettings.settingIdx].space;
            if (typeof currentSettingSpace[name] === 'boolean') {
                currentSettingSpace[name] = !currentSettingSpace[name];
                _BotUI.initSettingsPage();
                _BotSettings.storeSetting();
            } else {
                currentSettingSpace[name] = true;
                _BotUI.initSettingsPage();
                _BotSettings.storeSetting();
            }
            return false;
        },
        toggleResSetting: function (name) {
            var currentSettingKeeps = _BotSettings.settings[_BotSettings.settingIdx].keeps;
            if (typeof currentSettingKeeps[name] === 'boolean') {
                currentSettingKeeps[name] = !currentSettingKeeps[name];
                _BotUI.initSettingsPage();
                _BotSettings.storeSetting();
            } else {
                currentSettingKeeps[name] = true;
                _BotUI.initSettingsPage();
                _BotSettings.storeSetting();
            }
            return false;
        },
        restoreBuySettingOld: function() {
	        var data = _BotSettings.restore('buy', false);
            var currentSetting = _BotSettings.settings[_BotSettings.settingIdx];
	        if (typeof data === 'string') {
                currentSetting.buys = JSON.parse(data);
            } else {
                currentSetting.buys = {};
            }
        },
        restoreResSettingOld: function() {
	        var data = _BotSettings.restore('keeps', false);
            var currentSetting = _BotSettings.settings[_BotSettings.settingIdx];
	        if (typeof data === 'string') {
                currentSetting.keeps = JSON.parse(data);
            } else {
                currentSetting.keeps = {};
            }
        },
        store: function(name, value) {
            if (typeof(Storage) !== 'undefined') localStorage.setItem(name, value);
        },
        restore: function(name, asBool) {
            if (typeof asBool === 'undefined') asBool = false;
            if (typeof(Storage) !== 'undefined') {
                var aValue = localStorage.getItem(name);
                if (asBool && aValue !== null) {
                    return (aValue === 'true' || aValue === true);
                } else {
                    return aValue;
                }
            }
            else return null;
        },
        restoreAllNew: function() {
            var data = _BotSettings.restore('settings', data);
            if (data) {
                _BotSettings.settings = JSON.parse(data);
            }
            var settingIdx = _BotSettings.restore('settingsIdx');
            if (settingIdx) {
                _BotSettings.settingIdx = parseInt(settingIdx);
            }
            var version = _BotSettings.restore('version');
            if (version) {
                _BotSettings.version = parseInt(version);
            }
            var speed = _BotSettings.restore('speed');
            if (speed) {
                _BotSettings.speed = parseInt(speed);
            }

            _BotSettingsMigrator.migrateAll();
        },
        storeSetting: function() {
            var data = JSON.stringify(_BotSettings.settings);
            _BotSettings.store('settings', data);
            _BotSettings.store('settingsIdx', _BotSettings.settingIdx);
            _BotSettings.store('version', _BotSettings.version);
            _BotSettings.store('speed', _BotSettings.speed);
        },
        restoreAllOld: function() {
	        var autos = _BotSetting.auto;
	        var currentSetting = _BotSettings.settings[_BotSettings.settingIdx];
            for (var x in autos) {
                if (autos.hasOwnProperty(x)) {
                    var oldValue = _BotSettings.restore('auto' + x, true);
                    if (oldValue !== null) {
                        currentSetting.auto[x] = oldValue;
                    }
                }
            }

            _BotSettings.restoreBuySettingOld();
            _BotSettings.restoreResSettingOld();
        },
        removeOldSettings: function() {
            if (typeof(Storage) === 'undefined') return;

            var autos = _BotSetting.auto;
            for (var x in autos) {
                if (autos.hasOwnProperty(x)) {
                    localStorage.removeItem('auto' + x);
                }
            }

            localStorage.removeItem('buys');
            localStorage.removeItem('keeps');
        },
        restoreAll: function () {
	        if (_BotSettings.restore('version') === null) {
	            _BotSettings.restoreAllOld();
	            _BotSettings.removeOldSettings();
	            _BotSettings.storeSetting()
            } else {
                _BotSettings.restoreAllNew();
            }
        },
        isCheckedBuySetting: function (bld) {
            var currentSettingBuys = _BotSettings.settings[_BotSettings.settingIdx].buys;
            if (typeof currentSettingBuys[bld] !== 'undefined') {
                return currentSettingBuys[bld] === true;
            }
            return false;
        },
        isCheckedSpaceSetting: function (bld) {
            var currentSettingSpace = _BotSettings.settings[_BotSettings.settingIdx].space;
            if (typeof currentSettingSpace[bld] !== 'undefined') {
                return currentSettingSpace[bld] === true;
            }
            return false;
        },
        isCheckedResSetting: function (res) {
            var currentSettingKeeps = _BotSettings.settings[_BotSettings.settingIdx].keeps;
            if (typeof currentSettingKeeps[res] !== 'undefined') {
                return currentSettingKeeps[res] === true;
            }
            return false;
        },
        isAuto: function (autoSetting) {
            var currentSetting = _BotSettings.settings[_BotSettings.settingIdx];
            var r = currentSetting.auto[autoSetting];
            return typeof r !== 'undefined' && r;
        },
        getCraftLevel: function () {
            var currentSetting = _BotSettings.settings[_BotSettings.settingIdx];
            return currentSetting.craftLevel;
        },
        setCraftLevel: function (newLevel) {
            var currentSetting = _BotSettings.settings[_BotSettings.settingIdx].craftLevel;
            newLevel = parseInt(newLevel);
            if (newLevel > 0 && newLevel <= 100 && currentSetting !== newLevel) {
                _BotSettings.settings[_BotSettings.settingIdx].craftLevel = newLevel;
                _BotUI.initSettingsPage();
                _BotSettings.storeSetting();
            }
        }
    };

	var _BotActions = {
        craftAll: function(res) {
            if (_Helpers.isResourceUnlocked(res)) {
                _Logs.log2('Crafting ' + res);
                var minAmt = game.workshop.getCraftAllCount(res);
                if (minAmt > 0 && minAmt < Number.MAX_VALUE) {
                    var craftRatio = game.getResCraftRatio({name: res});
                    var bonus = minAmt * craftRatio;
                    _Statistics.addCraft(res, minAmt + bonus);

                    game.craftAll(res);
                    _Statistics.addCraftCount(res, minAmt);
                }
            }
        },
        craft: function(res, amount) {
            var craftRatio = game.getResCraftRatio({name: res});
            var bonus = amount * craftRatio;
            _Statistics.addCraft(res, amount + bonus);

            game.craft(res, amount);
            _Statistics.addCraftCount(res, amount);
        },
        collectAstronomy: function() {
            if (game.calendar.observeRemainingTime > 0) {
                if (typeof game.calendar.observeHandler === 'function') {
                    game.calendar.observeHandler();
                    _Statistics.addCounter('astronomy', 1);
                }
            }
        },
        catnipToWood: function() {
            var catnip = game.resPool.get('catnip');
            if (catnip.value >= catnip.maxValue) {
                var minWood = _Helpers.getMinCraft('wood');
                var wood = game.resPool.get('wood');
                if (wood.value + minWood <= wood.maxValue) {
                    _Logs.log2('Catnip to Wood x ' + minWood);

                    _BotActions.craft('wood', minWood);
                    _Statistics.addCraftCount('wood', minWood);
                }
            }
        },
        collectFaith: function() {
            var faith = game.resPool.get('faith');
            if (faith.value >= faith.maxValue) {
                _Logs.log2('Praise');

                game.religion.praise();
                _Statistics.addCounter('praise', 1);
            }
        },
        sendAllHunters: function() {
            var manpower = game.resPool.get('manpower');
            if (manpower.value >= manpower.maxValue) {
                _Logs.log2('Sending hunters');

                game.village.huntAll();
                _Statistics.addCounter('hunt', 1);

                _BotActions.craftAll('parchment');
            }
        },
        ironToSteel: function() {
            if (_Helpers.isResourceUnlocked('steel')) {
                var iron = game.resPool.get('iron');
                var coal = game.resPool.get('coal');
                if (iron.value >= iron.maxValue || coal.value >= coal.maxValue) {
                    if (coal.value >= 100 && iron.value >= 100) {
                        _Logs.log2('Iron to Steel x ALL');

                        _BotActions.craftAll('steel');
                    }
                }
            }
        },
        ironToPlates: function() {
            var iron = game.resPool.get('iron');
            var minPlate = _Helpers.getMinCraft('plate');
            if (iron.value >= (125 * minPlate) && _Helpers.isResourceUnlocked('plate')) {
                _Logs.log2('Iron to Plate x ' + minPlate);
                _BotActions.craft('plate', minPlate);
            }
        },
        woodToBeams: function() {
            var wood = game.resPool.get('wood');
            if (wood.value >= wood.maxValue && _Helpers.isResourceUnlocked('beam')) {
                var minVal = _Helpers.getMinCraft('beam');
                _Logs.log2('Wood to Beam x ' + minVal);
                _BotActions.craft('beam', minVal);
            }
        },
        mineralsToSlabs: function() {
            var minerals = game.resPool.get('minerals');
            if (minerals.value >= minerals.maxValue && _Helpers.isResourceUnlocked('slab')) {
                var minVal = _Helpers.getMinCraft('slab');
                _Logs.log2('Minerals to Slab x ' + minVal);
                _BotActions.craft('slab', minVal);
            }
        },
        oilToKerosene: function() {
            var oil = game.resPool.get('oil');
            if (oil.value >= oil.maxValue && _Helpers.isResourceUnlocked('kerosene')) {
                var minVal = _Helpers.getMinCraft('kerosene');
                _Logs.log2('Oil to Kerosene x ' + minVal);
                _BotActions.craft('kerosene', minVal);
            }
        },
        uraniumToThorium: function() {
            var uranium = game.resPool.get('uranium');
            if (uranium.value >= uranium.maxValue && _Helpers.isResourceUnlocked('thorium')) {
                var minVal = _Helpers.getMinCraft('thorium');
                _Logs.log2('Uranium to Thorium x ' + minVal);
                _BotActions.craft('thorium', minVal);
            }
        },
        cultureToManuscript: function() {
            var culture = game.resPool.get('culture');
            var parchment = game.resPool.get('parchment');
            var minVal = _Helpers.getMinCraft('manuscript');
            if (culture.value >= culture.maxValue && _Helpers.isResourceUnlocked('manuscript') && culture.value >= (400 * minVal) && parchment.value >= (25 * minVal)) {
                _Logs.log2('Culture to Manuscript x ' + minVal);
                _BotActions.craft('manuscript', minVal);
            }
        },
        makeABuy: function(itemName) {
            var btn = $('.bldGroupContainer').find('div.btnContent').find('span').filter(function(){
                var t = $(this).text();
                return t.indexOf(itemName + ' (') === 0 || t === itemName;
            });
            if (btn&& btn.length === 1) {
                _Logs.mainLog('Autobuy ' + itemName);
                _Logs.buildLog(itemName);
                btn.click();
            }
        },
        promoteKittens: function() {
            var gold = game.resPool.get('gold');
            if (!gold.unlocked) return;
            if (gold.value >= 15 && gold.value >= gold.maxValue && _Helpers.getKittensForPromoteInfo().count > 0) {
                game.village.promoteKittens();

                _Statistics.addCounter('promote', 1);
            }
        },
        scienceToCompedium: function() {
            var science = game.resPool.get('science');
            var manuscript = game.resPool.get('manuscript');
            var minVal = _Helpers.getMinCraft('compedium');
            if (science.value >= science.maxValue && _Helpers.isResourceUnlocked('compedium') && manuscript.value >= (50 * minVal) && science.value >= (10000 * minVal)) {
                _Logs.log2('Science/Manuscript to Compedium x ' + minVal);
                _BotActions.craft('compedium', minVal);
            }
        },
        scienceToBlueprints: function() {
            var science = game.resPool.get('science');
            var compediums = game.resPool.get('compedium');
            var minVal = _Helpers.getMinCraft('blueprint');
            if (science.value >= science.maxValue && _Helpers.isResourceUnlocked('blueprint') && compediums.value >= (25 * minVal) && science.value >= (25000 * minVal)) {
                _Logs.log2('Science/Compedium to Blueprint x ' + minVal);
                _BotActions.craft('blueprint', minVal);
            }
        },
        sendZebraCaravan: function () {
            var zebras = game.diplomacy.get('zebras');
            if (zebras.unlocked) {
                var titanium = game.resPool.get('titanium');
                if (titanium.value < titanium.maxValue) {
                    var gold = game.resPool.get('gold');
                    var power = game.resPool.get('manpower');
                    var slab = game.resPool.get('slab');
                    if (gold.value >= gold.maxValue && power.value >= 50 && slab.value >= 50) {
                        _Logs.logTrade('[Zebra trade] x 1');
                        game.diplomacy.tradeMultiple(zebras, 1);

                        _Statistics.addStatistics('diplomacyCount', 'zebras', 1);
                    }
                }
            }
        },
        sendNagasCaravan: function () {
            var nagas = game.diplomacy.get('nagas');
            if (nagas.unlocked) {
                var ivory = game.resPool.get('ivory');
                var gold = game.resPool.get('gold');
                if (ivory.value > 500 && ivory.perTickCached > 0 && gold.value >= gold.maxValue) {
                    var minerals = game.resPool.get('minerals');
                    var power = game.resPool.get('manpower');
                    if (minerals.value < minerals.maxValue && power.value >= 50) {
                        _Logs.logTrade('[Nagas trade] x 1');
                        game.diplomacy.tradeMultiple(nagas, 1);

                        _Statistics.addStatistics('diplomacyCount', 'nagas', 1);
                    }
                }
            }
        },
        sendDragonsCaravan: function () {
            var dragons = game.diplomacy.get('dragons');
            if (dragons.unlocked) {
                var uranium = game.resPool.get('uranium');
                if (uranium.unlocked && uranium.value < uranium.maxValue) {
                    var titanium = game.resPool.get('titanium');
                    var gold = game.resPool.get('gold');
                    var power = game.resPool.get('manpower');
                    if (titanium.value >= titanium.maxValue && titanium.value >= 250 && gold.value >= gold.maxValue && power.value >= 50) {
                        _Logs.logTrade('[Dragons trade] x 1');
                        game.diplomacy.tradeMultiple(dragons, 1);

                        _Statistics.addStatistics('diplomacyCount', 'dragons', 1);
                    }
                }
            }
        },
        turnOffMoonOutpost: function () {
            var bld = game.space.getBuilding('moonOutpost');
            if (bld.val > 0 && bld.on > 0) {
                _Logs.mainLog('No uranium: Turn Off MoonOutpost');
                bld.on--;
            }
        },
        unobtainiumToEludium: function () {
            var unobtainium = game.resPool.get('unobtainium');
            if (unobtainium.value >= unobtainium.maxValue && _Helpers.isResourceUnlocked('eludium')) {
                var alloy = game.resPool.get('alloy');
                if (unobtainium.value >= 1000 && alloy.value >= 2500) {
                    var minVal = _Helpers.getMinCraft('eludium');
                    _Logs.log2('Unobtainium to Eludium x ' + minVal);
                    _BotActions.craft('eludium', minVal);
                }
            }
        },
        buyModelBuilding: function(bld, isReligion, isSpace) {
            var prices = _Helpers.getPricesForModel(bld, isReligion, isSpace);
            if (_Helpers.isEnoughResourcesForPrice(prices)) {
                if (bld.breakIronWill) {
                    game.ironWill = false;
                }

                game.resPool.payPrices(prices);
                bld.val++;
                bld.on++;

                if (bld.unlocks) {
                    game.unlock(bld.unlocks);
                }

                if (bld.upgrades) {
                    game.upgrade(bld.upgrades);
                }

                _Logs.mainLog('Autobuy ' + bld.label);

                if (isReligion) {
                    _Logs.buildLog('Religion: ' + bld.label);
                    _Statistics.addCounter('buyReligion', 1);
                } else if (isSpace) {
                    _Logs.buildLog('Space: ' + bld.label);
                    _Statistics.addCounter('buySpace', 1);
                } else {
                    _Statistics.addCounter('buyModel', 1);
                }
            }
        },
        buyReligion: function () {
            if (game.religionTab.visible) {
                var blds = game.religion.religionUpgrades;
                for (var i in blds) {
                    if (blds.hasOwnProperty(i)) {
                        var bld = blds[i];
                        var isVisible = bld.faith <= game.religion.faith;
                        var needBuy = !bld.noStackable || bld.val < 1;
                        if (isVisible && needBuy) {
                            _BotActions.buyModelBuilding(bld, true);
                        }
                    }
                }
            }
        },
        craftResource: function(resName, minimumAmountInStorage) {
            var res = game.resPool.get(resName);
            if (res.value < minimumAmountInStorage && _Helpers.isResourceUnlocked(resName)) {
                var craftRatio = game.getResCraftRatio({name: resName});
                var craftAmt = 1 + craftRatio;
                var needCrafts = Math.ceil((minimumAmountInStorage - res.value) / craftAmt);
                _Logs.log2('"' + resName + '" craft x ' + needCrafts);
                _BotActions.craft(resName, needCrafts);
            }
        },
        feedElders: function () {
            var ncorns = game.resPool.get('necrocorn');
            if (ncorns.value >= 1) {
                var elders = game.diplomacy.get('leviathans');
                var markerCap = game.religion.getZU('marker').val * 5 + 5;
                if (elders.unlocked && elders.energy < markerCap) {
                    _Logs.mainLog('Feeding Elders');
                    _Logs.log2('Feeding Elders');
                    game.diplomacy.feedElders();
                }
            }
        },
        sellBcoins: function () {
            if (game.diplomacy.get("leviathans").unlocked) {
                if (game.science.get('blackchain').researched && game.resPool.get('blackcoin').value > 0) {
                    _Logs.log2('Sell all B-Coins');
                    game.diplomacy.sellEcoin();
                }
            }
        },
        buyBcoins: function () {
            if (game.diplomacy.get("leviathans").unlocked) {
                if (game.science.get('blackchain').researched && game.resPool.get('relic').value > 0) {
                    _Logs.log2('Buy B-Coins');
                    game.diplomacy.buyEcoin();
                }
            }
        },
        titaniumToAlloy: function () {
            var titanium = game.resPool.get('titanium');
            var steel = game.resPool.get('steel');
            if (titanium.value >= titanium.maxValue && steel.value > 75 && titanium.value > 10 && _Helpers.isResourceUnlocked('alloy')) {
                var minVal = _Helpers.getMinCraft('alloy');
                _Logs.log2('Titanium to Alloy x ' + minVal);
                _BotActions.craft('alloy', minVal);
            }
        }
	};

	var _Cytoscape = {
        cy: undefined,
        cyElements: undefined,
        $scienceLegend: undefined,
        $scienceLegendTitle: undefined,
        $scienceLegendDescr: undefined,
        $scienceLegendPrice: undefined,
        $scienceLegendUnlock: undefined,
        isCyInited: function() {
            return typeof _Cytoscape.cy !== 'undefined';
        },
        initCy: function() {
            var el = document.getElementById('scienceTree');
            if (!_Cytoscape.isCyInited() && typeof cytoscape !== 'undefined' && el !== null) {
                var elems = [], elemsPositions = {};

                var _addNode = function (name, connectTo, dx ,dy) {
                    var t = game.science.get(name);
                    if (!t) return;
                    while (typeof elemsPositions[dx + '_' + dy] !== 'undefined') {
                        dy += 100;
                    }
                    elemsPositions[dx + '_' + dy] = 1;
                    var nodeData = {
                        group: 'nodes',
                        data: {
                            id: t.name,
                            researched: t.researched,
                            unlocked: t.unlocked
                        },
                        position: {
                            x: dx,
                            y: dy
                        },
                        grabbable: false,
                        locked: false
                    };
                    /*if (typeof connectTo !== 'undefined') {
                        nodeData.data.parent = connectTo;
                    }*/
                    elems.push(nodeData);
                    if (typeof connectTo !== 'undefined') {
                        var linkName = t.name + '_' + connectTo;
                        elems.push({
                            group: 'edges',
                            data: {
                                id: linkName,
                                source: connectTo,
                                target: t.name
                            }
                        });
                    }
                    if (typeof t.unlocks !== 'undefined' && typeof t.unlocks.tech === 'object') {
                        var offsetY = 0;
                        for (var x in t.unlocks.tech) {
                            if (t.unlocks.tech.hasOwnProperty(x)) {
                                _addNode(t.unlocks.tech[x], name, dx + 150, dy + offsetY);
                                offsetY += 100;
                            }
                        }
                    }
                };
                _addNode('calendar', undefined, 100, 100);

                _Cytoscape.cyElements = elems;

                _Cytoscape.cy = cytoscape({
                    container: el, // container to render in
                    zoom: 0.5,
                    wheelSensitivity: 0.25,
                    style: [
                        {
                            selector: 'node',
                            style: {
                                'label': 'data(id)',
                                'background-color': function( ele ) {
                                    var unlocked = ele.data('unlocked');
                                    var researched = ele.data('researched');
                                    return researched ? 'green' : (unlocked ? 'lightblue' : 'gray');
                                }
                            }
                        },
                        {
                            selector: ':selected',
                            style: {
                                'background-color': 'white',
                                'border-width': '2px',
                                'border-style': 'solid',
                                'border-color': 'red'
                            }
                        }
                    ]
                });

                _Cytoscape.cy.add(_Cytoscape.cyElements);

                _Cytoscape.cy.on('click', _Cytoscape.onScienceNodeClick);
            }
        },
        openPanel: function () {
            if (_Cytoscape.isCyInited()) {
                $('#scienceTree').show();
            } else {
                _Cytoscape.initCy();
            }
        },
        onOpenScienceTreeClick: function () {
            _Cytoscape.openPanel();
        },
        onCloseScienceTreeClick: function() {
            $('#scienceTree').hide();
        },
        onScienceNodeClick: function (e) {
            var clickedNode = e.target;
            var data = clickedNode.hasOwnProperty('0') ? clickedNode[0]._private.data : undefined;
            if (typeof data !== 'undefined') {
                var techName = data.id;
                if (techName.indexOf('_') === -1) {
                    var tech = game.science.get(techName);
                    _Cytoscape.initScienceLabel(tech);
                    _Cytoscape.$scienceLegend.show();
                } else {
                    _Cytoscape.$scienceLegend.hide();
                }
            } else {
                _Cytoscape.$scienceLegend.hide();
            }
        },
        getTechPriceTableHtml: function(tech) {
            var prices = game.science.getPrices(tech);
            var priceTable = '<table border="0" cellpadding="0" cellspacing="0">';
            for (var pid in prices) {
                if (prices.hasOwnProperty(pid)) {
                    var price = prices[pid];
                    var resName = $I('resources.' + price.name + '.title');
                    var value = game.getDisplayValueExt(parseInt(price.val));
                    priceTable += '<tr><td>' + resName + '</td><td>' + value + '</td></tr>';
                }
            }
            priceTable += '</table>';

            return priceTable;
        },
        getSpaceUnlockedByTech: function(tech) {
            var unlockedSpace = [];
            var allSpacePrograms = game.space.metaCache;
            for (var i in allSpacePrograms) {
                if (allSpacePrograms.hasOwnProperty(i)) {
                    var program = allSpacePrograms[i];
                    if (typeof program.requiredTech !== 'undefined') {
                        for (var x in program.requiredTech) {
                            if (program.requiredTech.hasOwnProperty(x)) {
                                var unl = program.requiredTech[x];
                                if (unl === tech.name) {
                                    unlockedSpace.push({
                                        label: program.label
                                    });
                                }
                            }
                        }
                    }
                }
            }
            return unlockedSpace;
        },
        getTechUnlocksTableHtml: function(tech) {
            var br = '<br/>', hr = '<hr/>';
            var unlocksTable = '<table border="0" cellpadding="0" cellspacing="0"><tr>';
            var _addToUnlocksTable = function (title, techUnlocksUid, nameCallback) {
                var unlockArr = tech.unlocks[techUnlocksUid];
                if (typeof unlockArr === 'undefined') return;
                unlocksTable += '<td>' + title + ':' + hr;
                for (var iid in unlockArr) {
                    if (unlockArr.hasOwnProperty(iid)) {
                        var aName = nameCallback(unlockArr[iid]);
                        unlocksTable += aName + br;
                    }
                }
                unlocksTable += '</td>';
            };
            var _translate = function(key, defValue) {
                return i18nLang.messages[key] ? $I(key) : defValue;
            };

            _addToUnlocksTable( $I('tab.name.workshop'), 'crafts', function(name) {
                return $I('resources.' + name + '.title');
            });

            _addToUnlocksTable( $I('tab.name.science'), 'tech', function(name) {
                return game.science.get(name).label;
            });

            _addToUnlocksTable( $I('workshop.upgradePanel.label'), 'upgrades', function(name) {
                return _translate('workshop.' + name + '.label', name);
            });

            _addToUnlocksTable( 'Buildings', 'buildings', function(name) {
                return _translate('buildings.' + name + '.label', name);
            });

            _addToUnlocksTable( 'Jobs', 'jobs', function(name) {
                return $I('village.job.' + name);
            });

            _addToUnlocksTable( 'Tabs', 'tabs', function(name) {
                return $I('tab.name.' + name);
            });

            _addToUnlocksTable( 'Chronoforge', 'chronoforge', function(name) {
                return _translate('time.cfu.' + name + '.label', name);
            });

            _addToUnlocksTable( 'Void Space', 'voidSpace', function(name) {
                return _translate('time.vsu.' + name + '.label', name);
            });

            _addToUnlocksTable( $I('challendge.panel.label'), 'challenges', function(name) {
                return _translate('challendge.' + name + '.label', name);
            });

            _addToUnlocksTable( 'Stages', 'stages', function(name) {
                if (typeof name === 'object') {
                    if (name.bld && typeof name.stage !== 'undefined') {
                        var bld = game.bld.get(name.bld);
                        var newName = name.stage;
                        if (bld.stages && bld.stages[name.stage]) {
                            newName = bld.stages[name.stage].label;
                        }
                        return _translate('buildings.' + name.bld + '.label', name.bld) + ' (' + newName + ')';
                    } else {
                        return JSON.stringify(name);
                    }
                }
                return name;
            });

            var spaceUnlocked = _Cytoscape.getSpaceUnlockedByTech(tech);
            if (spaceUnlocked.length > 0) {
                unlocksTable += '<td>' + $I('tab.name.space') + ':' + hr;
                for (var i in spaceUnlocked) {
                    if (spaceUnlocked.hasOwnProperty(i)) {
                        unlocksTable += spaceUnlocked[i].label + br;
                    }
                }
                unlocksTable += '</td>';
            }

            unlocksTable += '</tr></table>';

            return unlocksTable;
        },
        initScienceLabel: function (tech) {
            _Cytoscape.$scienceLegendTitle.text(tech.label);
            _Cytoscape.$scienceLegendDescr.text(tech.description);

            var priceTable = _Cytoscape.getTechPriceTableHtml(tech);
            _Cytoscape.$scienceLegendPrice.html(priceTable);

            if (tech.unlocks) {
                var unlocksTable = _Cytoscape.getTechUnlocksTableHtml(tech);
                _Cytoscape.$scienceLegendUnlock.html(unlocksTable).show();
            } else {
                _Cytoscape.$scienceLegendUnlock.hide();
            }
        },
        initScienceLegend: function() {
            var header = '<div id="scienceLegendTitle" class="legendBlock">Название</div>';
            var descr = '<div id="scienceLegendDescr" class="legendBlock">Описание длинное</div>';
            var prices = '<div id="scienceLegendPrice" class="legendBlock">Цена: 1 crystal</div>';
            var unlocks = '<div id="scienceLegendUnlock" class="legendBlock">Открывает: Religion</div>';
            var blocks = header + descr + prices + unlocks;
            var inner = '<div id="scienceLegendInner">' + blocks + '</div>';
            var scienceDiv = '<div id="scienceLegend" style="display:none">' + inner + '</div>';
            $('#scienceTree').prepend(scienceDiv);
            _Cytoscape.$scienceLegend = $('#scienceLegend');
            _Cytoscape.$scienceLegendTitle = $('#scienceLegendTitle');
            _Cytoscape.$scienceLegendDescr = $('#scienceLegendDescr');
            _Cytoscape.$scienceLegendPrice = $('#scienceLegendPrice');
            _Cytoscape.$scienceLegendUnlock = $('#scienceLegendUnlock');
        },
        initScienceTree: function() {
            var closeDivButton = '<a href="#" id="closeScienceTree" onclick="KittenTools.CY.onCloseScienceTreeClick()">X</a>';
            var scienceDiv = '<div id="scienceTree" style="display:none">' + closeDivButton + '</div>';
            $('#gamePageContainer').prepend(scienceDiv);
        },
        init: function () {
            _Cytoscape.initScienceTree();
            _Cytoscape.initScienceLegend();
        }
    };

	var _Wiki = {
	    $wikiPopup: undefined,
        _wikiUrl: 'http://bloodrizer.ru/games/kittens/wiki/index.php?page=',
        wikiPages: [
            'Main page',
            'Resources',
            'Technologies',
            'Paragon',
            'Metaphysics',
            'Religion',
            'Space',
            'Time',
            'Achievements'
        ],
        getWikiPageLinksHtml: function() {
	        var result = [];
	        result.push('<div class="linkBlock">');
	        for (var i in _Wiki.wikiPages) {
	            if (_Wiki.wikiPages.hasOwnProperty(i)) {
	                var name = _Wiki.wikiPages[i];
	                var nameUrl = name.replace(' ', '+');
	                result.push('<a href="#" onclick="KittenTools.Wiki.onWikiClick(\'' + nameUrl + '\')">' + name + '</a>');
                }
            }
	        result.push('</div>');
	        return result.join('');
        },
	    init: function() {
            var closeDivButton = '<a href="#" id="closeWikiPopup" onclick="KittenTools.Wiki.onCloseWikiClick()">X</a>';
            var pageLinks = _Wiki.getWikiPageLinksHtml();
            var inner = '<iframe id="wikiFrame"></iframe>';
            var scienceDiv = '<div id="wikiPopup" style="display:none">' + closeDivButton + pageLinks + inner + '</div>';
            $('#gamePageContainer').prepend(scienceDiv);
            _Wiki.$wikiPopup = $('#wikiPopup');
        },
	    onOpenWikiClick: function () {
            _Wiki.$wikiPopup.show();
            return false;
        },
        onCloseWikiClick: function () {
            _Wiki.$wikiPopup.hide();
        },
        onWikiClick: function (pageName) {
            document.getElementById('wikiFrame').src = _Wiki._wikiUrl + pageName;
        }
    };

	var _BotInfoPage = {
        _oldEnergyReserves: undefined,
	    getButtonsHtml: function() {
	        var inner = '<a href="#" onclick="KittenTools.CY.onOpenScienceTreeClick()">Show Tech tree</a>';
            inner += ' | ';
            inner += '<a href="#" onclick="KittenTools.Wiki.onOpenWikiClick()">Wiki</a>' + _br;
            return inner;

        },
        getMainBlockHtml: function() {
            var inner = '';
            var kittensPromoteInfo = _Helpers.getKittensForPromoteInfo();
            inner += '- Need promote: ' + kittensPromoteInfo.count + _br;
            if (kittensPromoteInfo.count) {
                inner += '- Gold for promote: ' + kittensPromoteInfo.gold + _br;
            }
            inner += '- Rift chance: ' + _Core.chanceToStr(_Helpers.getUnicornRiftChance()) + _br;
            inner += '- Astronomy chance: ' + _Core.chanceToStr(_Helpers.getAstronomyChance()) + _br;
            var timeForCorruption = _Core.secondsToTimeStr(_Helpers.getSecondsForOneCorruption());
            inner += '- To Corruption: ' + timeForCorruption + _br;
            inner += '- Alicorn chance: ' + _Core.chanceToStr(_Helpers.getAlicornChance()) + _br;
            var alicornsPerSecond = _Helpers.getAlicornsPerSecond();
            inner += '- Alicorn/sec: ' + _Core.floatToStr(alicornsPerSecond, 5) + _br;
            var timeCrystalRatio = 1 + game.getEffect('tcRefineRatio');
            inner += '- Time crytals/sac: ' + _Core.floatToStr(timeCrystalRatio, 3) + _br;
            var eldersChance = _Helpers.getEldersChance();
            inner += '- Leviathans chance/year: ' + _Core.chanceToStr(eldersChance) + _br;

            return inner;

        },
        getChronoBlockHtml: function() {
            if (!game.bld.get('chronosphere').on) return null;

            var inner = '- Seasons to TemporalParadox: ' + game.calendar.futureSeasonTemporalParadox + _br;
            inner += '- Days in paradox: ' + _Helpers.getDaysInParadox() + _br;
            var voidInDay = _Helpers.getVoidPerDay();
            inner += '- Void/day: ' + voidInDay.min + '..' + voidInDay.max + ' (' + voidInDay.average + ' avg.)' + _br;
            inner += '- Void/paradox: ' + (_Helpers.getDaysInParadox() * voidInDay.average) + ' avg.' + _br;
            if (_BotUI.lastVoidGain) {
                inner += '- Last void gain: ' + _BotUI.lastVoidGain + _br;
            }
            return inner;
        },
        getAiCoreBlockHtml: function() {
            var gflops = game.resPool.get('gflops').value;
            if (gflops <= 0) return null;

            var inner = '';
            var gflopsProd = _Helpers.getGflopsProduction();
            var gflopsCons = _Helpers.getGflopsConsumption();
            inner += '- Gflops: ' + game.getDisplayValueExt(_Helpers.getGflopsValue(), false, false, 3) + _br;
            if (gflopsProd > 0) {
                inner += '- Gflops prod.: ' + game.getDisplayValueExt(gflopsProd, '+', false, 2) + _br;
            }
            if (gflopsCons > 0) {
                inner += '- Gflops cons.: -' + game.getDisplayValueExt(gflopsCons, false, false, 2) + _br;
            }
            if (gflopsProd > gflopsCons) {
                var maxGflops = Math.exp(15);
                if (gflops < maxGflops) {
                    var timeToAI = (maxGflops - gflops) / (gflopsProd - gflopsCons);
                    inner += '- Apocalypse in: ' + game.toDisplaySeconds(timeToAI) + _br;
                }
            } else if (gflopsProd < gflopsCons) {
                var timeToZero = gflops / (gflopsCons - gflopsProd);
                inner += '- Gflops to zero: ' + game.toDisplaySeconds(timeToZero) + _br;
            }
            return inner;
        },
        getEnergyBlockHtml: function() {
            var inner = '';
            if (typeof _BotInfoPage._oldEnergyReserves !== 'undefined' && game.calendar.day <= 1) {
                var reserves = _BotInfoPage._oldEnergyReserves;
            } else {
                reserves = _Helpers.getEnergyReserves();
                _BotInfoPage._oldEnergyReserves = reserves;
            }
            for (var i in reserves) {
                if (reserves.hasOwnProperty(i)) {
                    var reserve = reserves[i];
                    var energyStr = _Core.floatToStr(reserve.reserve, 3);
                    if (reserve.reserve < 0) {
                        energyStr = '<span style="color:red">' + energyStr + '</span>';
                    }
                    inner += '- ' + reserve.title + ': ' + energyStr + _br;
                }
            }
	        return inner;
        },
        getBcoinBlockHtml: function() {
            var inner = '';
            if (game.science.get('blackchain').researched || game.resPool.get('blackcoin').value > 0) {
                var bcoinPerSec = _Helpers.getMaxBcoinPriceChangePerSecond();
                inner += '- Max change/sec: ' + _Core.floatToStr(bcoinPerSec, 2) + _br;
                if (bcoinPerSec > 0) {
                    inner += '- Min value: ' + _Core.floatToStr(game.calendar.cryptoPriceMax * 0.7, 2) + _br;
                    var curValue = _Core.floatToStr(game.calendar.cryptoPrice, 2);
                    if ((game.calendar.cryptoPriceMax - game.calendar.cryptoPrice) / bcoinPerSec < 1000) {
                        curValue = '<span style="color:red">' + curValue + '</span>';
                    }
                    inner += '- Current value: ' + curValue + _br;
                    inner += '- Max value: ' + _Core.floatToStr(game.calendar.cryptoPriceMax, 2) + _br;
                    var timeToMax = (game.calendar.cryptoPriceMax - game.calendar.cryptoPrice) / (bcoinPerSec * 0.3 * 0.5) * 2; // 0.3 - chance to increase, 0.5 - random chance, 2 - seconds/game day
                    inner += '- To max: ' + game.toDisplaySeconds(timeToMax) + _br;
                    if (_BotSettings.isAuto('TradeBcoins')) {
                        inner += '- Auto sell: ' + _Core.floatToStr(game.calendar.cryptoPriceMax - 10 * bcoinPerSec, 2) + _br;
                    }
                }
            }
	        return inner;
        },
        getFaithBlockHtml: function() {
	        var inner = '';
            var faithBonusBeforeReset = _Helpers.getFaithBonus(false);
            var faithBonusAfterReset = _Helpers.getFaithBonus(true);
            inner += '- Faith reset: ' + game.getDisplayValueExt((faithBonusAfterReset - faithBonusBeforeReset) * 100, '+', false, 1) + '%' + _br;
            var needNextLevel = _Helpers.getNextTranscendPrice();
            inner += '- Faith ratio: ' + game.getDisplayValueExt(game.religion.faithRatio, false, false, 1) + _br;
            inner += '- Faith ratio on reset: ' + game.getDisplayValueExt(_Helpers.getFaithRatio(true), false, false, 1) + _br;
            inner += '- Transcend price: ' + game.getDisplayValueExt(needNextLevel, false, false, 1) + _br;
            if (game.religion.faithRatio < needNextLevel) {
                var progressPercentage = game.toDisplayPercentage(game.religion.faithRatio / needNextLevel, 2, true);
                inner += '- Transcend progress: ' + progressPercentage + '%' + _br;
            } else {
                var newRatio = game.religion.faithRatio - needNextLevel;
                var newFaithBonus = game.religion.getTriValueReligion(newRatio);
                inner += '- Transcend new bonus: ' + game.toDisplayPercentage(newFaithBonus, 2, true) + _br;
            }
	        return inner;
        },
        getProductionBonusBlockHtml: function() {
            var inner = '';
            var productionBonus = _Helpers.getProductionBonus();
            var productionBonusMax = _Helpers.getProductionBonus(true);
            var productionBonusAddTranscedence = _Helpers.getProductionBonus(true, 1, 0);
            var productionBonusAddObelisk = _Helpers.getProductionBonus(true, 0, 1);
            inner += '- Bonus: ' + game.toDisplayPercentage(productionBonus / 100, 2, true) + '%' + _br;
            inner += '- Max bonus: ' + game.toDisplayPercentage(productionBonusMax / 100, 2, true) + '%' + _br;
            inner += '- On +1 Transcend: ' + game.toDisplayPercentage(productionBonusAddTranscedence / 100, 2, true) + '%' + _br;
            inner += '- On +1 Obelisk: ' + game.toDisplayPercentage(productionBonusAddObelisk / 100, 2, true) + '%' + _br;
            return inner;
        },
        getTradesBlockHtml: function() {
            var tradeRatio = game.diplomacy.getTradeRatio();
            var spiceMax = 25 + 49 + 49 * tradeRatio;
            var inner = '- Blueprint chance: 10% (fixed)' + _br;
            inner += '- Spice chance: 35% (fixed)' + _br;
            inner += '- Spice amount max: ' + parseFloat(spiceMax).toFixed(2) + _br;
            inner += '- Trade ratio: ' + _Core.chanceToStr(tradeRatio + 1) + _br;
	        return inner;
        },
        getFriendlyTradesBlockHtml: function() {
	        var inner = '';
            var gameStanding = _Helpers.getGameStandingRatio();
            for (var rid in game.diplomacy.races) {
                if (game.diplomacy.races.hasOwnProperty(rid)) {
                    var r = game.diplomacy.races[rid];
                    if (r.attitude === 'friendly' && r.standing) {
                        var rStanding = r.standing * 100 + gameStanding/2;
                        inner += '- ' + r.title +': ' + _Core.percentToStr(rStanding) + _br;
                    }
                }
            }
	        return inner;
        },
        getHostileTradesBlockHtml: function() {
	        var inner = '';
            var gameStanding = _Helpers.getGameStandingRatio();
            for (var rid in game.diplomacy.races) {
                if (game.diplomacy.races.hasOwnProperty(rid)) {
                    var r = game.diplomacy.races[rid];
                    if (r.attitude === 'hostile' && r.standing) {
                        var rStanding = r.standing * 100 + gameStanding;
                        inner += '- ' + r.title +': ' + _Core.percentToStr(rStanding) + _br;
                    }
                }
            }
	        return inner;
        },
        getNeutralTradesBlockHtml: function() {
            var tradeRatio = game.diplomacy.getTradeRatio();
            var dragons = game.diplomacy.get('dragons');
            var dragonsUranium = dragons.sells[0];
            var minDragonsUranium = (1 - dragonsUranium.delta / 2) * (1 + tradeRatio);
            var maxDragonsUranium = (1 + dragonsUranium.delta / 2) * (1 + tradeRatio);
            var inner = '- ' + dragons.title +' uranium: ' + parseFloat(minDragonsUranium).toFixed(2)
                + ' - ' + parseFloat(maxDragonsUranium).toFixed(2) + _br;
            inner += '- ' + dragons.title +' trade chance: 95%' + _br;
	        return inner;
        },
        getZebrasTradeBlockHtml: function() {
            var zebraTitanium = _Helpers.getZebraTitanium();
            var inner = '- Titan chance: ' + _Core.percentToStr(zebraTitanium.percent)+ _br;
            inner += '- Titan amount: ' + parseFloat(zebraTitanium.value).toFixed(2) + _br;
	        return inner;
        },
        getMagnetoBlockHtml: function() {
            var magnetoBonus = _Helpers.getMagnetoBonus();
            var magnetoBonusAddSteam = _Helpers.getMagnetoBonus(1);
            var magnetoBonusAddMagneto = _Helpers.getMagnetoBonus(0, 1);
            var inner = '- Current: ' + _Core.chanceToStr(magnetoBonus) + _br;
            inner += '- On +1 Steam: ' + _Core.chanceToStr(magnetoBonusAddSteam) + _br;
            inner += '- On +1 Magneto: ' + _Core.chanceToStr(magnetoBonusAddMagneto) + _br;
	        return inner;
        },
        getEldersTradeBlockHtml: function() {
	        var inner = '';
            var elders = game.diplomacy.get('leviathans');
            var pyramidVal = game.religion.getZU('blackPyramid').val;
            if (pyramidVal > 0 || elders.unlocked) {
                var items = _Helpers.getDiplomacyTradeValues(elders);
                for (var i in items) {
                    if (items.hasOwnProperty(i)) {
                        var item = items[i];
                        var minStr = _Core.floatToStr(item.min, 2);
                        var maxStr = _Core.floatToStr(item.max, 2);
                        var title = _Helpers.getResourceTitle(i);
                        var chanceStr = _Core.chanceToStr(item.chance);
                        if (minStr !== maxStr) {
                            inner += '- ' + title + ': ' + minStr + ' - ' + maxStr + ' (' + chanceStr + ')' + _br;
                        } else {
                            inner += '- ' + title + ': ' + minStr + ' (' + chanceStr + ')' + _br;
                        }
                    }
                }
            }
	        return inner;
        },
        getBlockHtml: function(blockTitle, blockFunction) {
            if (typeof blockFunction === 'function') {
                var block = blockFunction();
                if (block !== '' && typeof block !== 'undefined' && block !== null) {
                    var inner = _br;
                    inner += blockTitle + ':' + _br;
                    inner += block;
                    return inner;
                }
            }
            return '';
        },
	    initInfoPage: function () {
            var inner = '';

            inner += _BotInfoPage.getButtonsHtml();

            inner += _BotInfoPage.getBlockHtml('Main', _BotInfoPage.getMainBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Void TemporalParadox', _BotInfoPage.getChronoBlockHtml);
            inner += _BotInfoPage.getBlockHtml('AI Cores', _BotInfoPage.getAiCoreBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Energy', _BotInfoPage.getEnergyBlockHtml);
            inner += _BotInfoPage.getBlockHtml('BCoin', _BotInfoPage.getBcoinBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Faith', _BotInfoPage.getFaithBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Production bonus', _BotInfoPage.getProductionBonusBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Trades', _BotInfoPage.getTradesBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Friendly trades (+25% res)', _BotInfoPage.getFriendlyTradesBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Hostile trades chance', _BotInfoPage.getHostileTradesBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Neutral trades', _BotInfoPage.getNeutralTradesBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Zebras trade', _BotInfoPage.getZebrasTradeBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Magneto bonus', _BotInfoPage.getMagnetoBlockHtml);
            inner += _BotInfoPage.getBlockHtml('Elders trade', _BotInfoPage.getEldersTradeBlockHtml);

            $('#IRCChatInner').html(inner);
        }
    };

	var _BotSettingsPage = {
	    getLabelForAutoSetting: function(x) {
	        switch (x) {
                case 'Buy': return $I('btn.build') + ' ' + $I('buildings.tabName');
                case 'BuyReligion': return $I('btn.build') + ' ' + $I('tab.name.religion');
                case 'BuySpace': return $I('btn.build') + ' ' + $I('tab.name.space');
                case 'PromoteKittens': return $I('village.btn.promote') + ' ' + $I('village.msg.kittens');
                case 'TradeBcoins': return $I('trade.buy.ecoin') + '/' + $I('trade.sell.ecoin');
                case 'CollectFaith': return $I('religion.praiseBtn.label');
                case 'SendHunters': return 'Send hunters';
                case 'FeedElders': return $I('trade.msg.elders.feed');
                case 'Trade': return $I('tab.name.trade');
                case 'CreateSteel': return $I('resources.coal.title') + ' → ' + $I('resources.steel.title');
                case 'CreatePlates': return $I('resources.iron.title') + ' → ' + $I('resources.plate.title');
                case 'CreateManuscript': return $I('resources.culture.title') + ' → ' + $I('resources.manuscript.title');
                case 'CreateWood': return $I('resources.catnip.title') + ' → ' + $I('resources.wood.title');
                case 'CreateBeams': return $I('resources.wood.title') + ' → ' + $I('resources.beam.title');
                case 'CreateSlabs': return $I('resources.minerals.title') + ' → ' + $I('resources.slab.title');
                case 'CreateCompedium': return $I('resources.science.title') + ' → ' + $I('resources.compedium.title');
                case 'CreateBlueprints': return $I('resources.science.title') + ' → ' + $I('resources.blueprint.title');
                case 'CreateKerosene': return $I('resources.oil.title') + ' → ' + $I('resources.kerosene.title');
                case 'CreateThorium': return $I('resources.uranium.title') + ' → ' + $I('resources.thorium.title');
                case 'CreateEludium': return $I('resources.unobtainium.title') + ' → ' + $I('resources.eludium.title');
                case 'CreateAlloy': return $I('resources.titanium.title') + ' → ' + $I('resources.alloy.title');
            }
	        return x;
        },
        getSettingsIndexSelectorHtml: function() {
            var inner = '<form><div class="radio-group">';
            for (var i = 0; i < _BotSettings.settings.length; i++) {
                var idx = 'option' + i;
                inner += '<input type="radio" id="' + idx + '" name="selector" '
                    + (i === _BotSettings.settingIdx ? 'checked="checked"' : '') + '>'+
                    '<label for="' + idx + '" onclick="KittenTools.UI.onChangeSettingIdxClick(' + i + ')">' + i + '</label>';
            }
            inner += '</div></form>';
            return inner;
        },
        getLogicBlockHtml: function() {
            var inner = '';
            var currentSettingAuto = _BotSettings.settings[_BotSettings.settingIdx].auto;
            for (var i = 1; i <= 3; i++) {
                for (var x in currentSettingAuto) {
                    if (currentSettingAuto.hasOwnProperty(x)) {
                        var isCreate = x.indexOf('Create') === 0;
                        var isBuy = x.indexOf('Buy') === 0;
                        if ((isBuy && i === 1) || (isCreate && i === 2) || (!isCreate && !isBuy && i === 3)) {
                            var isChecked = currentSettingAuto[x];
                            inner += '<a href="#" onclick="KittenTools.Settings.toggleSetting(\'' + x + '\')">'
                                + _BotSettingsPage.getLabelForAutoSetting(x) + '</a> : ' + (isChecked ? 'ON' : '-') + _br;
                        }
                    }
                }
                inner += _br;
            }
            return inner;
        },
        getBotSpeedBlockHtml: function() {
            var inner = '<form><div class="radio-group">';
            var speeds = [100, 250, 500, 1000, 2000, 5000];
            for (var i in speeds) {
                var label = parseFloat(speeds[i] / 1000).toFixed(speeds[i] < 1000 ? 2 : 0);
                var idx = 'botspeed' + speeds[i];
                inner += '<input type="radio" id="' + idx + '" name="botspeed" '
                    + (speeds[i] === _BotSettings.speed ? 'checked="checked"' : '') + '>' +
                    '<label for="' + idx + '" onclick="KittenTools.UI.onChangeBotSpeed(' + speeds[i] + ')">' + label + '</label>';
            }
            inner += '</div></form>';
            return inner;
        },
        getCraftSpeedBlockHtml: function() {
            var inner = '<form><div class="radio-group">';
            var currentCraftLevel = _BotSettings.getCraftLevel();
            var n1 = 1, n2 = 1;
            for (var i = 0; i < 8; i++) {
                var lvl = n1 + n2; n1 = n2; n2 = lvl; // Fibbonacci
                var idx = 'craftlevel' + lvl;
                inner += '<input type="radio" id="' + idx + '" name="craftlevel" '
                    + (lvl === currentCraftLevel ? 'checked="checked"' : '') + '>'+
                    '<label for="' + idx + '" onclick="KittenTools.UI.onChangeCraftLevel(' + lvl + ')">' + lvl + '</label>';
            }
            inner += '</div></form>';
            return inner;
        },
        getBuildingsBlockHtml: function() {
            var inner = '';
            var bg = game.bld.buildingGroups;
            for (var x in bg) {
                if (bg.hasOwnProperty(x)) {
                    inner += bg[x].title + _br;
                    for (var i in bg[x].buildings) {
                        if (bg[x].buildings.hasOwnProperty(i)) {
                            var b = bg[x].buildings[i];
                            var name = _Helpers.getBuildingTitle(b);
                            var isChecked = _BotSettings.isCheckedBuySetting(b);
                            inner += '<a href="#" onclick="KittenTools.Settings.toggleBuySetting(\'' + b + '\')">'
                                + name + '</a> : ' + (isChecked ? 'ON' : '-') + _br;
                        }
                    }
                    inner += _br;
                }
            }
            return inner;
        },
        getResourcesBlockHtml: function() {
            var inner = '';
            var rm = game.resPool.resourceMap;
            for (var r in rm) {
                if (rm.hasOwnProperty(r)) {
                    if (rm[r].unlocked) {
                        var name = rm[r].title;
                        var isChecked = _BotSettings.isCheckedResSetting(r);
                        inner += '<a href="#" onclick="KittenTools.Settings.toggleResSetting(\'' + r + '\')">'
                            + name + '</a> : ' + (isChecked ? 'ON' : '-') + _br;
                    }
                }
            }
            return inner;
        },
        getSpaceBuildBlockHtml: function() {
            var inner = '';
            var bg = _Helpers.getSpaceBuildings();
            for (var x in bg) {
                if (bg.hasOwnProperty(x)) {
                    var b = bg[x];
                    var isChecked = _BotSettings.isCheckedSpaceSetting(b.name);
                    inner += '<a href="#" onclick="KittenTools.Settings.toggleSpaceSetting(\'' + b.name + '\')">'
                        + b.label + '</a> : ' + (isChecked ? 'ON' : '-') + _br;
                }
            }
            return inner;
        },
        getSettingsBlock: function(title, getterFunction) {
            if (typeof getterFunction === 'function') {
                var result = getterFunction();
                if (result !== '' && result !== null && typeof result !== 'undefined') {
                    return title + '<hr/>' + result + _br;
                }
            }
            return null;
        },
        getHtml: function () {
            var inner = '';
            inner += _BotSettingsPage.getSettingsIndexSelectorHtml();
            inner += _BotSettingsPage.getSettingsBlock('Logic', _BotSettingsPage.getLogicBlockHtml);
            inner += _BotSettingsPage.getSettingsBlock('Bot speed (less = faster)', _BotSettingsPage.getBotSpeedBlockHtml);
            inner += _BotSettingsPage.getSettingsBlock('Craft speed (%)', _BotSettingsPage.getCraftSpeedBlockHtml);
            inner += _BotSettingsPage.getSettingsBlock('Autobuid', _BotSettingsPage.getBuildingsBlockHtml);
            inner += _BotSettingsPage.getSettingsBlock('Space build', _BotSettingsPage.getSpaceBuildBlockHtml);
            inner += _BotSettingsPage.getSettingsBlock('Keep resource', _BotSettingsPage.getResourcesBlockHtml);

            return inner;
        }
    };

	// noinspection JSUnusedGlobalSymbols
    var _BotUI = {
        currentPage: 'log',
        buildingsTranslations: {},
        voidBeforeParadox: undefined,
        lastVoidGain: undefined,
        initTranslations: function() {
            var bg = game.bld.buildingGroups;
            for (var x in bg) {
                if (bg.hasOwnProperty(x)) {
                    for (var i in bg[x].buildings) {
                        if (bg[x].buildings.hasOwnProperty(i)) {
                            var b = bg[x].buildings[i];
                            var name = _Helpers.getBuildingTitle(b);
                            _BotUI.buildingsTranslations[name] = b;
                        }
                    }
                }
            }
        },
        fixFontSize: function() {
            var $midColumn = $('#midColumn');
            var $rightColumn = $('#rightColumn');
            var fnt1 = $('#leftColumn').css('font-size');
            var fnt2 = $midColumn.css('font-size');
            var fnt3 = $rightColumn.css('font-size');
            if (fnt2 !== fnt1 || fnt3 !== fnt1) {
                _Logs.mainLog('Fixing font size');
                $midColumn.css('font-size', fnt1);
                $rightColumn.css('font-size', fnt1);
            }
        },
        fixStyles: function() {
            var style = '<style type="text/css">' +
                '.modern .btnContent, .btn.bldEnabled.modern div.btnContent, .btn.bldlackResConvert.modern div.btnContent {padding: 5px 0 5px 10px;} '+
                '.btn.modern a {padding: 5px 6px 5px 6px !important;margin:-5px 0;} '+
                '#rightTabLog {max-height: 75vh; overflow-y: scroll; padding-right: 4px;} ' +
                '#IRCChatInner {overflow-y: scroll; height: 75vh;} ' +
                '#IRCChatInner a {text-decoration: none; color: darkmagenta} ' +
                '#IRCChatInner a:hover {text-decoration: underline: color: magenta} ' +
                'span.msg:not(.type_date) {display: list-item; margin-left: 1.0em; list-style-type: circle;} ' +
                '.msg {opacity: 1 !important;} ' +
                '.right-tab-header a {padding: 0} ' +
                '#scienceTree, #wikiPopup {z-index:2;position:fixed;left:2vw;top:2vh;width:96vw;height:96vh;background:azure;border:1px solid;} ' +
                '#closeScienceTree, #closeWikiPopup {z-index:3;position:fixed;right:2vw;top:2vh;color:black;font-size:20px;padding:4px;border:1px solid;text-decoration:none;} ' +
                '#closeScienceTree:hover, #closeWikiPopup:hover {color:blue} ' +
                '.legendBlock {margin-bottom: 8px;padding: 0 8px 8px 8px;border-bottom: 1px solid gray;} ' +
                '#scienceLegend {z-index:4;position:fixed;left:3vw;top:50vh;width:47vw;height:47vh;background:whitesmoke;border:1px solid;} ' +
                '#scienceLegendInner {z-index:5;position:fixed;left:4vw;top:51vh;width:45vw;height:45vh;overflow-y:auto;} ' +
                '#scienceLegendTitle {text-align:center;margin-top:8px;font-size:140%;} ' +
                '#scienceLegendPrice td, #scienceLegendUnlock td {padding-right: 16px;vertical-align: top;} ' +
                '#scienceLegendUnlock {font-size: 90%;} ' +
                '.btn.nosel.modern.disabled.nochance {border-color: red;} ' +
                '#wikiPopup .linkBlock {padding: 4px; height: 5vh} ' +
                '#wikiPopup iframe {border: none; width: 100%; height: 90vh;} ' +
                '#wikiPopup a {display: inline-block; padding: 5px 6px; margin: 0 8px 4px 0;border: 1px solid} ' +

                '.radio-group input[type=radio] {position: absolute; visibility: hidden; display: none;}' +
                '.radio-group label {display: inline-block; cursor: pointer; font-weight: bold; padding: 4px 8px;}' +
                '.radio-group input[type=radio]:checked + label{background: #999}' +
                '.radio-group label + input[type=radio] + label {border-left: solid 1px;}' +
                '.radio-group {border: solid 1px; display: inline-block; border-radius: 10px; overflow: hidden;}' +

                '</style>';
            $('body').append($(style));
        },
        addColorsToBuildingsButtons: function() {
            var $buttons = $('.bldGroupContainer .btn.modern');
            if ($buttons.length) {
                var gatherMintCaption = $I('buildings.gatherCatnip.label');
                var pressMintCaption = $I('buildings.refineCatnip.label');
                for (var i = 0; i < $buttons.length; i++) {
                    var $btn = $buttons[i];
                    var isDisabled = $btn.className.indexOf('disabled') >= 0;
                    if (isDisabled) {
                        var btnText = $btn.innerText;
                        var isSpecial = btnText.indexOf(gatherMintCaption) === 0 || btnText.indexOf(pressMintCaption) === 0;
                        if (!isSpecial) {
                            var idx = btnText.indexOf(' (');
                            if (idx <= 0) {
                                var bldName = btnText;
                            } else {
                                bldName = btnText.substr(0, idx);
                            }
                            var building_type = _BotUI.buildingsTranslations[bldName];
                            if (typeof building_type !== 'undefined') {
                                var canBuild = _Helpers.canBuildNow(building_type);
                                if (!canBuild) {
                                    $($btn).addClass('nochance');
                                } else {
                                    $($btn).removeClass('nochance');
                                }
                            }
                        }
                    }
                }
            }
        },
        addColorsToBuildingsButtonsWithTimeout: function() {
            setTimeout(function() {
                KittenTools.UI.addColorsToBuildingsButtons();
            }, 700);
        },
        addBotButton: function () {
            var $a = $('<a href="#" id="botbutton">Bot (' + (_BotLogic.isAutoLogicStarted ? 'on' : 'off') + ')</a>');
            $a.on('click', function() {
                _BotLogic.isAutoLogicStarted = !_BotLogic.isAutoLogicStarted;
                _Logs.mainLog((_BotLogic.isAutoLogicStarted ? 'Started' : 'Stopped') + ' version ' + _Core.getBotVersion());
                $('#botbutton').text(_BotLogic.isAutoLogicStarted ? 'Bot (on)' : 'Bot (off)');
            });
            $('#headerLinks .links-block').append(' | ').append($a);
        },
        initSettingsLink: function () {
            $('a.chatLink').text('Bot settings').attr('onclick', 'KittenTools.UI.onSettingsButtonClick()');
        },
        initSettingsPage: function () {
            if (_BotUI.currentPage !== 'settings') return;

            var inner = _BotSettingsPage.getHtml();
            $('#IRCChatInner').html(inner);
        },
        initInfoButton: function () {
            $('.right-tab-header').append('&nbsp;| <a href="#" id="botinfobtn" onclick="KittenTools.UI.onInfoButtonClick()">Info</a>');
        },
        initCustomLogButton: function(logName, buttonTitle) {
            $('.right-tab-header').append('&nbsp;| <a href="#" id="_log' + logName + '" onclick="KittenTools.UI.onCustomLogButtonClick(\'' + logName + '\')">' + buttonTitle + '</a>');
        },
        initCustomLogPage: function(logName) {
            var inner = '';

            inner += '<a href="#" onclick="KittenTools.UI.onClearCustonLogClick(\'' + logName + '\')">Clear log</a>';
            if (logName === 'trade') {
                inner += ' | <a href="#" onclick="KittenTools.UI.onGetTitaniumClick()">Trade max Titan</a>';
            }
            inner += '<hr/>';

            var lastLogIndex = _Logs.getCustomLogSize(logName) - 1;
            for (var i = lastLogIndex; i >= 0; i--) {
                inner += '<span class="msg">' + _Logs.getCustomLogItem(logName, i) + '</span>';
            }

            $('#IRCChatInner').html(inner);
        },
        init: function () {
            _BotUI.fixStyles();
            _BotUI.addBotButton();
            _BotUI.initInfoButton();
            _BotUI.initCustomLogButton('log2', 'Log2');
            _BotUI.initCustomLogButton('trade', 'Trade');
            _BotUI.initCustomLogButton('hunt', 'Hunts');
            _BotUI.initCustomLogButton('wrkshp', 'Workshop');
            _BotUI.initCustomLogButton('build', 'Build');
            _BotUI.initCustomLogButton('craft', 'Craft');
            _Cytoscape.init();
            _BotUI.initTranslations();
            _BotUI.initLogLink();
            _Wiki.init();
            //_BotUI.addColorsToBuildingsButtonsWithTimeout();
            _BotUI.initInfoUpdateTimer();
        },
        initInfoUpdateTimer: function() {
            setInterval(function () {
                if (_BotUI.currentPage === 'info') {
                    _BotInfoPage.initInfoPage();
                }
            }, 1000);
        },
        onInfoButtonClick: function () {
            _BotUI.currentPage = 'info';
            _BotUI.switchToChatAndMakeLinkActive('botinfobtn');
            _BotInfoPage.initInfoPage();
        },
        switchToChatAndMakeLinkActive: function(linkId) {
            game.ui.loadChat();
            if (typeof linkId !== 'undefined') {
                $('.right-tab-header a.active').removeClass('active');
                $('#' + linkId).addClass('active');
            }
        },
        onSettingsButtonClick: function () {
            _BotUI.currentPage = 'settings';
            _BotUI.switchToChatAndMakeLinkActive();
            _BotUI.initSettingsPage();
        },
        onCustomLogButtonClick: function(logName) {
            _BotUI.currentPage = logName;
            _BotUI.switchToChatAndMakeLinkActive('_log' + logName);
            _BotUI.initCustomLogPage(logName);
        },
        initLogLink: function () {
            $('#logLink').attr('onclick', 'KittenTools.UI.onLogButtonClick()');
        },
        onLogButtonClick: function () {
            _BotUI.currentPage = 'log';
            $('.right-tab-header a.active').removeClass('active');
            game.ui.hideChat();
        },
        onClearCustonLogClick: function(logName) {
            _Logs.clearCustomLog(logName);
            _BotUI.initCustomLogPage(logName);
        },
        onChangeSettingIdxClick: function (newIndex) {
            newIndex = parseInt(newIndex);
            if (_BotSettings.settingIdx !== newIndex) {
                _BotSettings.settingIdx = newIndex;
                _BotSettings.store('settingsIdx', _BotSettings.settingIdx);
                _BotUI.initSettingsPage();
            }
        },
        initStatsPage: function () {
            for (var i in game.stats.statGroups) {
                if (game.stats.statGroups.hasOwnProperty(i)) {
                    var grp = game.stats.statGroups[i];
                    if (grp.title === _Statistics._statTitle) {
                        game.stats.statGroups[i] = _Statistics.getStatsGroup();
                        return;
                    }
                }
            }
            game.stats.statGroups.push(_Statistics.getStatsGroup());
        },
        onChangeCraftLevel: function (newLevel) {
            _BotSettings.setCraftLevel(newLevel);
        },
        onChangeBotSpeed: function (newSpeed) {
            newSpeed = parseInt(newSpeed);
            if (newSpeed !== _BotSettings.speed) {
                clearInterval(_BotLogic.tAutoLogic);
                _BotSettings.speed = newSpeed;
                _BotLogic.tAutoLogic = setInterval(_BotLogic.autoLogic, _BotSettings.speed);
                _BotSettings.store('speed', _BotSettings.speed);

                _Logs.mainLog('New Bot Timeout: ' + _BotSettings.speed);
                _Logs.console('BOT: KittenTools new timeout ' + _BotSettings.speed);
            }
        },
        checkVoidIncome: function () {
            if (game.bld.get('chronosphere').on) {
                if (typeof _BotUI.voidBeforeParadox === 'undefined' && parseInt(game.calendar.futureSeasonTemporalParadox) <= 0 && game.calendar.day > 90) {
                    _BotUI.voidBeforeParadox = game.resPool.get('void').value;
                } else if (typeof _BotUI.voidBeforeParadox !== 'undefined' && game.calendar.day < 10 && game.calendar.day > 0) {
                    var curVoid = game.resPool.get('void').value;
                    _BotUI.lastVoidGain = curVoid - _BotUI.voidBeforeParadox;
                    _Logs.mainLog('Void gained ' + _BotUI.lastVoidGain);
                    _BotUI.voidBeforeParadox = undefined;
                }
            }
        },
        onGetTitaniumClick: function () {
            var titanPerTrade = _Helpers.getZebraTitanium().value;
            if (titanPerTrade > 0) {
                var maxTrades_gold = Math.floor(game.resPool.get('gold').value / 15);
                var maxTrades_slab = Math.floor(game.resPool.get('slab').value / 50);
                var maxTrades_manpower = Math.floor(game.resPool.get('manpower').value / 50);
                var maxTrades = Math.min(maxTrades_gold, maxTrades_manpower, maxTrades_slab);
                var titanium = game.resPool.get('titanium');
                var needTrades = Math.ceil((titanium.maxValue - titanium.value) / titanPerTrade);
                var tradesCanDo = Math.min(needTrades, maxTrades) | 0;
                var zebras = game.diplomacy.get('zebras');

                _Logs.logTrade('[Zebra trade] x ' + tradesCanDo);
                game.diplomacy.tradeMultiple(zebras, tradesCanDo);
                _Statistics.addStatistics('diplomacyCount', 'zebras', tradesCanDo);
            }
            _Logs.console('hello');
        }
    };

	var _BotLogic = {
        tAutoLogic: undefined,
        isAutoLogicStarted: true,
        cryptoPriceOld: undefined,
        autoTradeBcoins: function() {
            if (_BotSettings.isAuto('TradeBcoins') && game.science.get('blackchain').researched) {
                if (game.resPool.get('blackcoin').value > 0) {
                    var bcoinPerSec = _Helpers.getMaxBcoinPriceChangePerSecond();
                    if (game.calendar.cryptoPriceMax - game.calendar.cryptoPrice < 10 * bcoinPerSec) {
                        _BotActions.sellBcoins();
                    }
                }
                if (game.resPool.get('relic').value > 0) {
                    var isPriceDecreased = _BotLogic.cryptoPriceOld > game.calendar.cryptoPrice;
                    var isNewPriceLow = game.calendar.cryptoPrice <= game.calendar.cryptoPriceMax * 0.8;
                    var isOldPriceHigh = _BotLogic.cryptoPriceOld > game.calendar.cryptoPriceMax * 0.9;
                    if (isPriceDecreased && isNewPriceLow && isOldPriceHigh) {
                        _BotActions.buyBcoins();
                    }
                }
                _BotLogic.cryptoPriceOld = game.calendar.cryptoPrice;
            }
        },
        autoBuyItem: function(bldName) {
            var bld = game.bld.get(bldName);
            if (bld.unlocked && _Helpers.canBuyBuilding(bldName)) {
                var itemName = bld.stages && bld.stages.length > 0 ? bld.stages[bld.stage].label : bld.label;
                _BotActions.makeABuy(itemName);
            }
        },
        autoBuyAll: function() {
            if (!_BotSettings.isAuto('Buy')) return;

            var currentSettingBuys = _BotSettings.settings[_BotSettings.settingIdx].buys;
            for (var x in currentSettingBuys) {
                if (currentSettingBuys.hasOwnProperty(x)) {
                    if (currentSettingBuys[x] === true) {
                        _BotLogic.autoBuyItem(x);
                    }
                }
            }
        },
        autoBuySpace: function(bldName) {
            var bld = game.space.getBuilding(bldName);
            if (bld.unlocked) {
                _BotActions.buyModelBuilding(bld, false, true);
            }
        },
        autoBuyAllSpace: function() {
            if (!_BotSettings.isAuto('BuySpace')) return;

            var currentSettingBuysSpace = _BotSettings.settings[_BotSettings.settingIdx].space;
            for (var x in currentSettingBuysSpace) {
                if (currentSettingBuysSpace.hasOwnProperty(x)) {
                    if (currentSettingBuysSpace[x] === true) {
                        _BotLogic.autoBuySpace(x);
                    }
                }
            }
        },
        autoClick: function() {
            var field = game.bld.get('field');
            if (field.on < 5) {
                game.bld.gatherCatnip()
            } else {
                var hut = game.bld.get('hut');
                var wood = game.resPool.get('wood');
                if (!hut.unlocked || wood.value < 10) {
                    var catnip = game.resPool.get('catnip');
                    if (catnip.value >= 100) {
                        game.bld.refineCatnip()
                    }
                }
            }
        },
        autoLogic: function() {
            _BotUI.fixFontSize();
            _BotUI.initStatsPage();
            _BotUI.checkVoidIncome();

            _BotActions.collectAstronomy();
            if (!_BotLogic.isAutoLogicStarted) return;
            _BotLogic.autoClick();
            _BotLogic.autoBuyAll();
            _BotLogic.autoBuyAllSpace();
            _BotLogic.promoteKittens();
            _BotLogic.collectFaith();
            _BotLogic.ironToSteelOrPlates();
            _BotLogic.sendAllHunters();
            _BotLogic.cultureToManuscript();
            _BotLogic.catnipToWood();
            _BotLogic.woodToBeams();
            _BotLogic.mineralsToSlabs();
            _BotLogic.oilToKerosene();
            _BotLogic.unobtainiumToEludium();
            _BotLogic.uraniumToThorium();
            _BotLogic.spendScience();
            _BotLogic.buyReligion();
            _BotLogic.turnOffBuildings();
            _BotLogic.feedElders();
            _BotLogic.autoTradeBcoins();
            _BotLogic.titaniumToAlloy();
            _BotLogic.autoTrade();
        },
        feedElders: function() {
            if (_BotSettings.isAuto('FeedElders')) _BotActions.feedElders();
        },
        turnOffBuildings: function() {
            _BotLogic.turnOffMoonOutpost();
        },
        turnOffMoonOutpost: function() {
            var uranium = game.resPool.get('uranium');
            if (uranium.unlocked && uranium.value <= 0 && game.getResourcePerTick('uranium', true) <= 0) {
                var bld = game.space.getBuilding('moonOutpost');
                if (bld.val > 0 && bld.on > 0) {
                    _BotActions.turnOffMoonOutpost();
                }
            }
        },
        autoTrade: function() {
            if (_BotSettings.isAuto('Trade')) {
                _BotActions.sendZebraCaravan();
                _BotActions.sendDragonsCaravan();
                _BotActions.sendNagasCaravan();
            }
        },
        promoteKittens: function () {
            if (_BotSettings.isAuto('PromoteKittens')) _BotActions.promoteKittens();
        },
        collectFaith: function () {
            if (_BotSettings.isAuto('CollectFaith')) _BotActions.collectFaith();
        },
        ironToSteelOrPlates: function() {
            var iron = game.resPool.get('iron');
            var coal = game.resPool.get('coal');
            if (iron.value >= iron.maxValue || coal.value >= coal.maxValue) {
                var minPlate = _Helpers.getMinCraft('plate');
                if (coal.value >= coal.maxValue && coal.value >= 100 && iron.value >= 100 && _Helpers.isResourceUnlocked('steel')) {
                    _BotLogic.ironToSteel();
                } else if (iron.value >= iron.maxValue && iron.value >= (125 * minPlate) && _Helpers.isResourceUnlocked('plate')) {
                    _BotLogic.ironToPlates();
                }
            }
        },
        oilToKerosene: function () {
            if (_BotSettings.isAuto('CreateKerosene')) _BotActions.oilToKerosene();
        },
        unobtainiumToEludium: function () {
            if (_BotSettings.isAuto('CreateEludium')) {
                if (_Helpers.isResourceUnlocked('eludium')) {
                    var unobtainium = game.resPool.get('unobtainium');
                    if (unobtainium.value >= unobtainium.maxValue && unobtainium.value >= 1000) {
                        _BotActions.craftResource('alloy', 2500);
                    }
                    _BotActions.unobtainiumToEludium();
                }
            }
        },
        uraniumToThorium: function () {
            if (_BotSettings.isAuto('CreateThorium')) _BotActions.uraniumToThorium();
        },
        titaniumToAlloy: function () {
            if (_BotSettings.isAuto('CreateAlloy')) _BotActions.titaniumToAlloy();
        },
        ironToSteel: function () {
            if (_BotSettings.isAuto('CreateSteel')) _BotActions.ironToSteel();
        },
        ironToPlates: function () {
            if (_BotSettings.isAuto('CreatePlates')) _BotActions.ironToPlates();
        },
        sendAllHunters: function () {
            if (_BotSettings.isAuto('SendHunters')) _BotActions.sendAllHunters();
        },
        cultureToManuscript: function () {
            if (_BotSettings.isAuto('CreateManuscript')) {
                var maxScienceResources = _Helpers.getMaximumScienceResourcesToKeep();
                var parchments = game.resPool.get('parchment');
                var minVal = _Helpers.getMinCraft('manuscript');
                if (maxScienceResources.maxParchment === 0) {
                    var maxParchmentsForBuildings = _Helpers.getMaximumScienceResourcesForBuildings();
                    if (maxParchmentsForBuildings.maxParchment === 0) {
                        _BotActions.cultureToManuscript();
                    } else if ((parchments.value - 25 * minVal) >= maxParchmentsForBuildings.maxParchment) {
                        _BotActions.cultureToManuscript();
                    }
                } else {
                    if ((parchments.value - 25 * minVal) >= maxScienceResources.maxParchment) {
                        _BotActions.cultureToManuscript();
                    }
                }
            }
        },
        catnipToWood: function () {
            if (_BotSettings.isAuto('CreateWood')) _BotActions.catnipToWood();
        },
        woodToBeams: function () {
            if (_BotSettings.isAuto('CreateBeams')) _BotActions.woodToBeams();
        },
        mineralsToSlabs: function () {
            if (_BotSettings.isAuto('CreateSlabs')) _BotActions.mineralsToSlabs();
        },
        spendScience: function() {
            var maxScienceResources = _Helpers.getMaximumScienceResourcesToKeep();
            var compediums = game.resPool.get('compedium');
            var minBlueprintCanCraft = _Helpers.getMinCraft('blueprint');
            if ((maxScienceResources.maxCompediums > 0 && compediums.value < maxScienceResources.maxCompediums) || (compediums.value < (25 * minBlueprintCanCraft))) {
                _BotLogic.scienceToCompedium();
            } else {
                _BotLogic.scienceToBlueprints();
                _BotLogic.scienceToCompedium();
            }
        },
        scienceToCompedium: function () {
            if (_BotSettings.isAuto('CreateCompedium')) {
                var maxManuscriptForBuildings = _Helpers.getMaximumScienceResourcesForBuildings();
                if (maxManuscriptForBuildings.maxManuscript === 0) {
                    _BotActions.scienceToCompedium();
                } else {
                    var minVal = _Helpers.getMinCraft('compedium');
                    var manuscripts = game.resPool.get('manuscript');
                    if ((manuscripts.value - 50 * minVal) >= maxManuscriptForBuildings.maxManuscript) {
                        _BotActions.scienceToCompedium();
                    }
                }
            }
        },
        scienceToBlueprints: function () {
            if (_BotSettings.isAuto('CreateBlueprints')) _BotActions.scienceToBlueprints();
        },
        buyReligion: function () {
            if (_BotSettings.isAuto('BuyReligion')) _BotActions.buyReligion();
        }
	};

	var _BotInjections = {
	    game: {
	        _oldMsg: undefined,
	        msg: function (message, type, tag, noBullet) {
                if (tag === 'astronomicalEvent' || tag === 'meteor') {
                    _Logs.log2(message);
                } else if (tag === 'trade') {
                    _Logs.logTrade(message);
                } else if (tag === 'hunt') {
                    _Logs.logCustom('hunt', message);
                } else if (tag === 'craft') {
                    _Logs.logCraft(message);
                } else if (tag === 'workshopAutomation') {
                    _Logs.logCustom('wrkshp', message);
                } else {
                    _BotInjections.game._oldMsg.call(game, message, type, tag, noBullet);
                }
            },
            tabs: {
	            _oldRenderActiveGroup: undefined,
                renderActiveGroup: function (groupContainer) {
                    _BotInjections.game.tabs._oldRenderActiveGroup.call(game.tabs[0], groupContainer);
                    KittenTools.UI.addColorsToBuildingsButtonsWithTimeout();
                }
            }
        },
        init: function () {
            if (game && game.msg) {
                _BotInjections.game._oldMsg = game.msg;
                game.msg = _BotInjections.game.msg;
            }
            /*if (game && game.tabs && game.tabs[0] && game.tabs[0].__proto__.renderActiveGroup) {
                _BotInjections.game.tabs._oldRenderActiveGroup = game.tabs[0].__proto__.renderActiveGroup;
                game.tabs[0].__proto__.renderActiveGroup = _BotInjections.game.tabs.renderActiveGroup;
            }*/
        }
    };

    var KittenTools = {
        Helpers: _Helpers,
        Actions: _BotActions,
        UI: _BotUI,
        CY: _Cytoscape,
        Settings: _BotSettings,
        Logic: _BotLogic,
        Logs: _Logs,
        Wiki: _Wiki
    };

    var _starter = function() {
        if (typeof game.KittenTools === 'undefined') {
            game.KittenTools = KittenTools;
        }
        if (typeof unsafeWindow !== 'undefined') {
            unsafeWindow.KittenTools = KittenTools;
            unsafeWindow.cytoscape = cytoscape;
        }

        _BotUI.init();
        _BotSettings.init();
        _BotInjections.init();

        _BotLogic.tAutoLogic = setInterval(_BotLogic.autoLogic, _BotSettings.speed);

        _Logs.mainLog('Started version ' + _Core.getBotVersion());
        _Logs.console('BOT: KittenTools version ' + _Core.getBotVersion() + ' started');

        _Logs.mainLog('Timeout: ' + _BotSettings.speed);
        _Logs.console('BOT: KittenTools timeout ' + _BotSettings.speed);
    };

    var _waiter = function() {
        if (_Helpers.isGameReady()) {
            _starter();
        } else {
            setTimeout(_waiter, 1000);
        }
    };

    _waiter();
})();