// ==UserScript==
// @name Kitten Scientists
// @namespace http://www.reddit.com/r/kittensgame/comments/34gb2u/kitten_scientists_automation_script/
// @description Launch Kitten Scientists
// @include *bloodrizer.ru/games/kittens/*
// @include file:///*kitten-game*
// @include *kittensgame.com/web/*
// @include *kittensgame.com/beta/*
// @include *kittensgame.com/alpha/*
// @version 1.5.0
// @grant none
// @copyright 2015, cameroncondry
// ==/UserScript==
// ==========================================
// Begin Kitten Scientist's Automation Engine
// ==========================================
var kg_version = 'Kitten Scientists version 1.5.0';
var address = '1HDV6VEnXH9m8PJuT4eQD7v8jRnucbneaq';
// Game will be referenced in loadTest function
var game = null;
var i18ng = null;
var lang = 'en';
var run = function() {
var i18nData = {
'en': {
'option.observe': 'Observe Astro Events',
'option.festival': 'Hold Festivals',
'option.praise': 'Auto Praise',
'option.shipOverride': 'Force Ships to 243',
'option.autofeed': 'Feed Leviathans',
'option.hunt': 'Hunt',
'option.crypto': 'Trade Blackcoin',
'option.embassies': 'Build Embassies (Beta)',
'option.style': 'View Full Width',
'option.steamworks': 'Turn on Production buildings',
'filter.build': 'Building',
'filter.craft': 'Crafting',
'filter.upgrade': 'Upgrading',
'filter.research': 'Researching',
'filter.trade': 'Trading',
'filter.hunt': 'Hunting',
'filter.praise': 'Praising',
'filter.faith': 'Order of the Sun',
'filter.festival': 'Festivals',
'filter.star': 'Astronomical Events',
'filter.misc': 'Miscellaneous',
'dispose.necrocorn': 'Kittens disposed of inefficient necrocorns',
'blackcoin.buy': 'Kittens sold your Relics and bought {0} Blackcoins',
'blackcoin.sell': 'Kittens sold your Blackcoins and bought {0} Relics',
'act.feed': 'Kittens fed the Elders. The elders are pleased',
'act.observe': 'Kitten Scientists have observed a star',
'act.hunt': 'Sent kittens on {0} hunts',
'act.build': 'Kittens have built a new {0}',
'act.builds': 'Kittens have built a new {0} {1} times.',
'act.craft': 'Kittens have crafted {0} {1}',
'act.trade': 'Kittens have traded {0}x with {1}',
'upgrade.space.mission': 'Kittens conducted a mission to {0}',
'upgrade.space': 'Kittens conducted a {0}',
'upgrade.race': 'Kittens met the {0}',
'upgrade.building.pasture': 'Upgraded pastures to solar farms!',
'upgrade.building.aqueduct': 'Upgraded aqueducts to hydro plants!',
'upgrade.building.library': 'Upgraded libraries to data centers!',
'upgrade.building.amphitheatre': 'Upgraded amphitheatres to broadcast towers!',
'upgrade.upgrade': 'Kittens have bought the upgrade {0}',
'upgrade.limited': 'Optimize {0}',
'upgrade.unlimited': 'All {0}',
'upgrade.tech': 'Kittens have bought the tech {0}',
'upgrade.policy': 'Kittens have passed {0}',
'festival.hold': 'Kittens begin holding a festival',
'festival.extend': 'Kittens extend the festival',
'build.embassy': 'Built {0} embassy for {1}',
'build.embassies': 'Built {0} embassies for {1}',
'act.praise': 'Praised the sun! Accumulated {0} faith to {1} worship',
'act.sun.discover': 'Kittens have discovered {0}',
'act.sun.discovers': 'Kittens have discovered {0} {1} times.',
'ui.items': 'items',
'ui.disable.all': 'disable all',
'ui.enable.all': 'enable all',
'ui.craft.resources': 'resources',
'ui.trigger': 'trigger',
'ui.trigger.set': 'Enter a new trigger value for {0}. Should be in the range of 0 to 1.',
'ui.limit': 'Limited',
'ui.trigger.missions.set': 'Enter a new trigger value for missions. Should be in the range of 0 to 13. Corresponds to each planet sort',
'ui.trigger.crypto.set': 'Enter a new trigger value for {0}. Corresponds to the amount of Relics needed before the exchange is made.',
'ui.engine': 'Enable Scientists',
'ui.build': 'Bonfire',
'ui.space': 'Space',
'ui.craft': 'Crafting',
'ui.upgrade': 'Unlocking',
'ui.trade': 'Trading',
'ui.faith': 'Religion',
'ui.time': 'Time',
'ui.options': 'Options',
'ui.filter': 'Filters',
'ui.distribute': 'Kitten Resources',
'ui.max': 'Max: {0}',
'ui.upgrade.upgrades': 'Upgrades',
'ui.upgrade.techs': 'Techs',
'ui.upgrade.races': 'Races',
'ui.upgrade.missions': 'Missions',
'ui.upgrade.buildings': 'Buildings',
'ui.upgrade.policies': 'Policies',
'ui.upgrade.policies.load': 'Load',
'ui.upgrade.policies.show': 'Show',
'ui.faith.addtion': 'addition',
'option.faith.best.unicorn': 'Build Best Unicorn Building First',
'option.faith.best.unicorn.desc': 'Include auto Sacrifice Unicorns if tears are not enough to build the best unicorn building',
'option.faith.transcend': 'Auto Transcend',
'act.transcend': 'Spend {0} epiphany, Transcend to T-level: {1}',
'summary.transcend': 'Transcend {0} times',
'filter.transcend': 'Transcend',
'option.faith.adore': 'Auto Adore the Galaxy',
'act.adore': 'Adore the galaxy! Accumulated {0} worship to {1} epiphany',
'summary.adore': 'Accumulated {0} epiphany by adore the galaxy',
'filter.adore': 'Adoring',
'adore.trigger.set': 'Enter a new trigger value for AutoAdore. Should be in the range of 0 to 1.\nKS will AutoAdore if the Solor Revolutuin Bonus brought by praising the sun once after adore can reach the trigger of maximum.\n\nNote: The solar revolution bonus will diminish after reaching 75% of the maximum.',
'resources.add': 'add resources',
'resources.clear.unused': 'clear unused',
'resources.stock': 'Stock: {0}',
'resources.consume': 'Comsume: {0}',
'resources.del': 'del',
'resources.stock.set': 'Stock for {0}',
'resources.consume.set': 'Consume rate for {0}',
'resources.del.confirm': 'Delete resource controls for {0}?',
'status.ks.enable': 'Enabling the kitten scientists!',
'status.ks.disable': 'Disabling the kitten scientists!',
'status.sub.enable': 'Enabled {0}',
'status.auto.enable': 'Enable Auto {0}',
'status.sub.disable': 'Disabled {0}',
'status.auto.disable': 'Disable Auto {0}',
'trade.limited': 'Trading with {0}: limited to only occur when profitable based off relative production time',
'trade.unlimited': 'Trading with {0}: unlimited',
'trade.seasons': 'seasons',
'trade.season.enable': 'Enabled trading with {0} in the {1}',
'trade.season.disable': 'Disabled trading with {0} in the {1}',
'filter.enable': 'Enable {0} Filter',
'filter.disable': 'Disabled {0} Filter',
'craft.limited': 'Crafting {0}: limited to be proportional to cost ratio',
'craft.unlimited': 'Crafting {0}: unlimited',
'distribute.limited': 'Distribute to {0}: stop when reach max',
'distribute.unlimited': 'Distribute to {0}: unlimited',
'distribute.leaderJob': 'Leader Job: {0} ',
'distribute.leaderTrait': 'Choose {0} Leader',
'distribute.makeLeader': 'Make Leader',
'act.distribute': 'Distribute a kitten to {0}',
'act.distributeLeader': 'Make a {0} kitten leader',
'ui.max.set': 'Maximum for {0}',
'summary.distribute': 'Help {0} kittens to find job',
'filter.distribute': 'Distribute',
'option.promote': 'Promote Leader',
'act.promote': 'Kittens\' leader has been promoted to rank {0}',
'filter.promote': 'Promote leader',
'summary.promote': 'Promoted leader {0} times',
'ui.timeCtrl': 'Time Control',
'option.accelerate': 'Tempus Fugit',
'act.accelerate': 'Accelerate time!',
'filter.accelerate': 'Tempus Fugit',
'summary.accelerate': 'Accelerate time {0} times',
'option.time.skip': 'Time Skip',
'act.time.skip': 'Kittens combuste Time crystal, {0} years skiped!',
'ui.cycles': 'cycles',
'ui.maximum': 'Maximum',
'time.skip.cycle.enable': 'Enable time skip in cycle {0} and allow skip over this cycle',
'time.skip.cycle.disable': 'Disable time skip in cycle {0} and disallow skip over this cycle',
'time.skip.season.enable': 'Enable time skip in the {0}',
'time.skip.season.disable': 'Disable time skip in the {0}',
'time.skip.trigger.set': 'Enter a new trigger value for Time Skip (Combust time crystal). Should be a positive integer.',
'summary.time.skip': 'Skip {0} years',
'filter.time.skip': 'Time Skip',
'option.time.reset': 'Reset Timeline (Danger!)',
'status.reset.check.enable': 'Enable check {0} before Reset Timeline',
'status.reset.check.disable': 'Disable check {0} before Reset Timeline',
'ui.min': 'Min: {0}',
'reset.check.trigger.set': 'Enter a new trigger value for {0}.\n-1 meaning must build this building until exceeding resource limit.',
'reset.check': 'Trigger for {0} : {1}, you have {2}',
'reset.checked': 'All conditions are met, the timeline will restart in next few seconds!',
'reset.tip': 'You can cancel this reset by disable "Kitten Scientists" or "Time Control" or "Reset Timeline"',
'reset.countdown.10': '10 - Harvesting catnip',
'reset.countdown.9': ' 9 - Sacrificing Unicorns',
'reset.countdown.8': ' 8 - Releasing lizards',
'reset.countdown.7': ' 7 - Disassembling railguns',
'reset.countdown.6': ' 6 - Starting time engines',
'reset.countdown.5': ' 5 - Melting blackcoins',
'reset.countdown.4': ' 4 - Turning off satellite',
'reset.countdown.3': ' 3 - Opening temporal rifts',
'reset.countdown.2': ' 2 - Boosting the chronoforge',
'reset.countdown.1': ' 1 - Time engine start',
'reset.countdown.0': ' 0 - Temporal rifts opened!',
'reset.last.message': 'See you next poincaré recurrence',
'reset.after': 'Nice to meet you, the cute Kittens Scientists will serve you',
'reset.cancel.message': 'Timeline Reset canceled.',
'reset.cancel.activity': 'Meoston, We Have a Problem.',
'summary.time.reset.title': 'Summary of the last {0} timelines',
'summary.time.reset.content': 'Gain {0} Karma.
Gain {1} Paragon.',
'ui.close': 'close',
'option.fix.cry': 'Fix Cryochamber',
'act.fix.cry': 'Kittens fix {0} Cryochambers',
'summary.fix.cry': 'Fix {0} Cryochambers',
'summary.festival': 'Held {0} festivals',
'summary.stars': 'Observed {0} stars',
'summary.praise': 'Accumulated {0} worship by praising the sun',
'summary.hunt': 'Sent adorable kitten hunters on {0} hunts',
'summary.embassy': 'Built {0} embassies',
'summary.feed': 'Fed the elders {0} necrocorns',
'summary.tech': 'Researched: {0}',
'summary.upgrade': 'Upgraded: {0}',
'summary.building': 'Built: +{0} {1}',
'summary.sun': 'Discovered: +{0} {1}',
'summary.craft': 'Crafted: +{0} {1}',
'summary.trade': 'Traded: {0}x {1}',
'summary.year': 'year',
'summary.years': 'years',
'summary.separator': ' and ',
'summary.day': 'day',
'summary.days': 'days',
'summary.head': 'Summary of the last {0}',
'summary.show': 'Show activity',
},
'zh': {
'option.observe': '观测天文事件',
'option.festival': '举办节日',
'option.praise': '赞美太阳',
'option.shipOverride': '强制243船',
'option.autofeed': '献祭上古神',
'option.hunt': '狩猎',
'option.crypto': '黑币交易',
'option.embassies': '建造大使馆 (Beta)',
'option.style': '占满屏幕',
'option.steamworks': '启动蒸汽工房',
'filter.build': '建筑',
'filter.craft': '工艺',
'filter.upgrade': '升级',
'filter.research': '研究',
'filter.trade': '贸易',
'filter.hunt': '狩猎',
'filter.praise': '赞美太阳',
'filter.faith': '太阳秩序',
'filter.festival': '节日',
'filter.star': '天文事件',
'filter.misc': '杂项',
'dispose.necrocorn': '小猫处理掉了影响效率的多余死灵兽',
'blackcoin.buy': '小猫出售遗物并买入 {0} 黑币',
'blackcoin.sell': '小猫出售黑币并买入了 {0} 遗物',
'act.feed': '小猫向上古神献上祭品。上古神很高兴',
'act.observe': '小猫珂学家观测到一颗流星',
'act.hunt': '派出 {0} 波小猫去打猎',
'act.build': '小猫建造了一个 {0}',
'act.builds': '小猫建造了 {1} 个新的 {0}',
'act.craft': '小猫制作了 {0} {1}',
'act.trade': '小猫与 {1} 交易 {0} 次',
'upgrade.space.mission': '小猫执行了 {0} 的任务',
'upgrade.space': '小猫执行了 {0}',
'upgrade.race': '小猫遇到了 {0}',
'upgrade.building.pasture': '牧场 升级为 太阳能发电站 !',
'upgrade.building.aqueduct': '水渠 升级为 水电站 !',
'upgrade.building.library': '图书馆 升级为 数据中心!',
'upgrade.building.amphitheatre': '剧场 升级为 广播塔!',
'upgrade.upgrade': '小猫发明了 {0}',
'upgrade.limited': '优化 {0}',
'upgrade.unlimited': '全部 {0}',
'upgrade.tech': '小猫掌握了 {0}',
'upgrade.policy': '小猫通过了 {0} 法案',
'festival.hold': '小猫开始举办节日',
'festival.extend': '小猫延长了节日',
'build.embassy': '在 {1} 设立了 {0} 个大使馆',
'build.embassies': '在 {1} 设立了 {0} 个大使馆',
'act.praise': '赞美太阳! 转化 {0} 信仰为 {1} 虔诚',
'act.sun.discover': '小猫在 {0} 方面获得顿悟',
'act.sun.discovers': '小猫在 {0} 方面获得 {1} 次顿悟',
'ui.items': '项目',
'ui.disable.all': '全部禁用',
'ui.enable.all': '全部启用',
'ui.craft.resources': '资源',
'ui.trigger': '触发条件',
'ui.trigger.set': '输入新的 {0} 触发值,取值范围为 0 到 1 的小数。',
'ui.limit': '限制',
'ui.trigger.missions.set': '输入一个新的 探索星球 触发值,取值范围为 0 到 13 的整数。\n分别对应13颗星球。',
'ui.trigger.crypto.set': '输入一个新的 {0} 触发值,\n遗物数量达到触发值时会进行黑笔交易。',
'ui.engine': '启用小猫珂学家',
'ui.build': '营火',
'ui.space': '太空',
'ui.craft': '工艺',
'ui.upgrade': '升级',
'ui.trade': '贸易',
'ui.faith': '宗教',
'ui.time': '时间',
'ui.options': '选项',
'ui.filter': '日志过滤',
'ui.distribute': '猫力资源',
'ui.max': 'Max: {0}',
'ui.upgrade.upgrades': '升级',
'ui.upgrade.techs': '科技',
'ui.upgrade.races': '探险队出发!',
'ui.upgrade.missions': '探索星球',
'ui.upgrade.buildings': '建筑',
'ui.upgrade.policies': '政策',
'ui.upgrade.policies.load': '读取',
'ui.upgrade.policies.show': '列表',
'ui.faith.addtion': '附加',
'option.faith.best.unicorn': '优先最佳独角兽建筑',
'option.faith.best.unicorn.desc': '当眼泪不够建造最佳独角兽建筑时也会自动献祭独角兽',
'option.faith.transcend': '自动超越',
'act.transcend': '消耗 {0} 顿悟,达到超越 {1}',
'summary.transcend': '超越了 {0} 次',
'filter.transcend': '超越',
'option.faith.adore': '赞美群星',
'act.adore': '赞美群星! 转化 {0} 虔诚为 {1} 顿悟',
'summary.adore': '通过赞美群星积累了 {0} 顿悟',
'filter.adore': '赞美群星',
'adore.trigger.set': '为自动赞美群星设定一个新触发值,取值范围为 0 到 1 的小数。\n如果赞美群星后第一次赞美太阳可将太阳革命加成恢复到(触发值*太阳革命太阳革命极限加成),那么珂学家将自动赞美群星。\n\n注意:太阳革命加成在到达上限的75%后便会收益递减。',
'resources.add': '添加资源',
'resources.clear.unused': '清除未使用',
'resources.stock': '库存: {0}',
'resources.consume': '消耗率: {0}',
'resources.del': '删除',
'resources.stock.set': '设置 {0} 的库存',
'resources.consume.set': '设置 {0} 的消耗率',
'resources.del.confirm': '确定要取消 {0} 的库存控制?',
'status.ks.enable': '神说,要有猫猫珂学家!',
'status.ks.disable': '太敬业了,该歇了',
'status.sub.enable': '启用 {0}',
'status.auto.enable': '启用自动化 {0}',
'status.sub.disable': '禁用 {0}',
'status.auto.disable': '禁用自动化 {0}',
'trade.limited': '与 {0} 的交易限制为比产量更优时才会触发',
'trade.unlimited': '取消与 {0} 交易的限制',
'trade.seasons': '季节',
'trade.season.enable': '启用在 {1} 与 {0} 的交易',
'trade.season.disable': '停止在 {1} 与 {0} 的交易',
'filter.enable': '过滤 {0}',
'filter.disable': '取消过滤 {0}',
'craft.limited': '制作 {0} 受库存消耗比率的限制',
'craft.unlimited': '制作 {0} 不受限制',
'distribute.limited': '分配 {0} 受限于最大值',
'distribute.leaderJob': '领袖工作为 {0} ',
'distribute.leaderTrait': '领袖的特质为 {0} ',
'distribute.unlimited': '分配 {0} 不受限',
'distribute.makeLeader': '分配领袖',
'act.distribute': '分配一只猫猫成为 {0}',
'act.distributeLeader': '分配一只 {0} 猫猫领袖',
'ui.max.set': '设置 {0} 的最大值',
'summary.distribute': '帮助 {0} 只猫猫找到工作',
'filter.distribute': '猫口分配',
'option.promote': '提拔领袖',
'act.promote': '领袖被提拔到 {0} 级',
'filter.promote': '提拔领袖',
'summary.promote': '提拔领袖 {0} 次',
'ui.timeCtrl': '时间操纵',
'option.accelerate': '时间加速',
'act.accelerate': '固有时制御,二倍速!',
'filter.accelerate': '时间加速',
'summary.accelerate': '加速时间 {0} 次',
'option.time.skip': '时间跳转',
'act.time.skip': '燃烧时间水晶, 跳过接下来的 {0} 年!',
'ui.cycles': '周期',
'ui.maximum': '上限',
'time.skip.cycle.enable': '启用在 {0} 跳转时间并允许跳过该周期',
'time.skip.cycle.disable': '停止在 {0} 跳转时间并禁止跳过该周期',
'time.skip.season.enable': '启用在 {0} 跳转时间',
'time.skip.season.disable': '停止在 {0} 跳转时间',
'time.skip.trigger.set': '为跳转时间(燃烧时间水晶)设定一个新触发值,取值范围为正整数',
'summary.time.skip': '跳过 {0} 年',
'filter.time.skip': '时间跳转',
'option.time.reset': '重启时间线 (危险!)',
'status.reset.check.enable': '在重启时间线前检查 {0}',
'status.reset.check.disable': '在重启时间线前不检查 {0}',
'ui.min': 'Min: {0}',
'reset.check.trigger.set': '为 {0} 设置新的触发值.\n-1 表示必须将此建筑建造至超过资源上限为止',
'reset.check': '{0} 的触发值: {1}, 现在共有 {2}',
'reset.checked': '所有条件都已满足,时间线将在几秒后重启!',
'reset.tip': '你可以通过取消 "启用小猫珂学家" 或 "时间操控" 或 "重启时间线" 以取消此次重启',
'reset.countdown.10': '10 - 正在收获猫薄荷',
'reset.countdown.9': ' 9 - 正在献祭独角兽',
'reset.countdown.8': ' 8 - 正在放生蜥蜴',
'reset.countdown.7': ' 7 - 正在拆解电磁炮',
'reset.countdown.6': ' 6 - 正在启动时间引擎',
'reset.countdown.5': ' 5 - 正在融化黑币',
'reset.countdown.4': ' 4 - 正在关闭卫星',
'reset.countdown.3': ' 3 - 正在打开时空裂隙',
'reset.countdown.2': ' 2 - 正在启动时间锻造',
'reset.countdown.1': ' 1 - 时间引擎已启动!',
'reset.countdown.0': ' 0 - 时空裂缝已打开!',
'reset.last.message': '我们下个庞加莱回归再见',
'reset.after': '初次见面,可爱的猫猫科学家为您服务',
'reset.cancel.message': '重启时间线计划取消.',
'reset.cancel.activity': '喵斯顿,我们有麻烦了.',
'summary.time.reset.title': '过去 {0} 个时间线的总结',
'summary.time.reset.content': '获得 {0} 业.
获得 {1} 领导力.',
'ui.close': '关闭',
'option.fix.cry': '修复冷冻仓',
'act.fix.cry': '小猫修复了 {0} 个冷冻仓',
'summary.fix.cry': '修复了 {0} 个冷冻仓',
'summary.festival': '举办了 {0} 次节日',
'summary.stars': '观测了 {0} 颗流星',
'summary.praise': '通过赞美太阳积累了 {0} 虔诚',
'summary.hunt': '派出了 {0} 批可爱的小猫猎人',
'summary.embassy': '设立了 {0} 个大使馆',
'summary.feed': '向上古神献祭 {0} 只死灵兽',
'summary.tech': '掌握了 {0}',
'summary.upgrade': '发明了 {0}',
'summary.building': '建造了 {0} 个 {1}',
'summary.sun': '在 {1} 方面顿悟 {0} 次',
'summary.craft': '制作了 {0} 个 {1}',
'summary.trade': '与 {1} 贸易了 {0} 次',
'summary.year': '年',
'summary.years': '年',
'summary.separator': ' ',
'summary.day': '天',
'summary.days': '天',
'summary.head': '过去 {0} 的总结',
'summary.show': '总结',
},
};
if (!i18nData[lang]) {
console.error(lang + ' not found')
i18nData[lang] = i18nData['en'];
}
var i18n = function(key, args) {
// i18n('$xx') mean load string from game
// i18n('xx') mean load string from ks
if (key[0] == "$")
return i18ng(key.slice(1));
value = i18nData[lang][key];
if (typeof value === 'undefined') {
value = i18nData['en'][key];
if (!value) {
console.error('key "' + key + '" not found')
return '$' + key;
}
console.error('Key "' + key + '" not found in ' + lang)
}
if (args)
for (var i = 0; i < args.length; i++)
value = value.replace('{' + i + '}', args[i])
return value;
}
var options = {
// When debug is enabled, messages that go to the game log are also logged using window.console.
debug: false,
// The interval at which the internal processing loop is run, in milliseconds.
interval: 2000,
// The default color for KS messages in the game log (like enabling and disabling items).
msgcolor: '#aa50fe', // dark purple
// The color for activity summaries.
summarycolor: '#009933', // light green
// The color for log messages that are about activities (like festivals and star observations).
activitycolor: '#E65C00', // orange
// The color for resources with stock counts higher than current resource max
stockwarncolor: '#DD1E00',
// The default consume rate.
consume: 0.6,
// The default settings for game automation.
auto: {
// Settings related to KS itself.
engine: {
// Should any automation run at all?
enabled: false
},
// split form faith to make "Best Unicorn Building" easily
unicorn: {
items: {
unicornPasture: {require: false, enabled: true, variant: 'zp', label: i18n('$buildings.unicornPasture.label'), checkForReset: true, triggerForReset: -1},
unicornTomb: {require: false, enabled: false, variant: 'z', label: i18n('$religion.zu.unicornTomb.label'), checkForReset: true, triggerForReset: -1},
ivoryTower: {require: false, enabled: false, variant: 'z', label: i18n('$religion.zu.ivoryTower.label'), checkForReset: true, triggerForReset: -1},
ivoryCitadel: {require: false, enabled: false, variant: 'z', label: i18n('$religion.zu.ivoryCitadel.label'), checkForReset: true, triggerForReset: -1},
skyPalace: {require: false, enabled: false, variant: 'z', label: i18n('$religion.zu.skyPalace.label'), checkForReset: true, triggerForReset: -1},
unicornUtopia: {require: 'gold', enabled: false, variant: 'z', label: i18n('$religion.zu.unicornUtopia.label'), checkForReset: true, triggerForReset: -1},
sunspire: {require: 'gold', enabled: false, variant: 'z', label: i18n('$religion.zu.sunspire.label'), checkForReset: true, triggerForReset: -1},
}
},
faith: {
// Should religion building be automated?
enabled: true,
// At what percentage of the storage capacity should KS build faith buildings?
trigger: 0,
// Additional options
addition: {
bestUnicornBuilding: {enabled: true, misc: true, label: i18n('option.faith.best.unicorn')},
autoPraise: {enabled: true, misc: true, label: i18n('option.praise'), subTrigger: 0.98},
// Former [Faith Reset]
adore: {enabled: false, misc: true, label: i18n('option.faith.adore'), subTrigger: 0.75},
transcend: {enabled: false, misc: true, label: i18n('option.faith.transcend')},
},
// Which religious upgrades should be researched?
items: {
// Variant denotes which category the building or upgrade falls within in the Religion tab.
// Ziggurats are variant z.
// UNICORN BUILDING START
// unicornPasture: {require: false, enabled: true, variant: 'zp', label: i18n('$buildings.unicornPasture.label')},
// unicornTomb: {require: false, enabled: false, variant: 'z'},
// ivoryTower: {require: false, enabled: false, variant: 'z'},
// ivoryCitadel: {require: false, enabled: false, variant: 'z'},
// skyPalace: {require: false, enabled: false, variant: 'z'},
// unicornUtopia: {require: 'gold', enabled: false, variant: 'z'},
// sunspire: {require: 'gold', enabled: false, variant: 'z'},
// UNICORN BUILDING END
marker: {require: 'unobtainium', enabled: false, variant: 'z', checkForReset: true, triggerForReset: -1},
unicornGraveyard: {require: false, enabled: false, variant: 'z', checkForReset: true, triggerForReset: -1},
unicornNecropolis: {require: false, enabled: false, variant: 'z', checkForReset: true, triggerForReset: -1},
blackPyramid: {require: 'unobtainium', enabled: false, variant: 'z', checkForReset: true, triggerForReset: -1},
// Order of the Sun is variant s.
solarchant: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
scholasticism: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
goldenSpire: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
sunAltar: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
stainedGlass: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
solarRevolution: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
basilica: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
templars: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
apocripha: {require: 'faith', enabled: false, variant: 's', checkForReset: true, triggerForReset: -1},
transcendence: {require: 'faith', enabled: true, variant: 's', checkForReset: true, triggerForReset: -1},
// Cryptotheology is variant c.
blackObelisk: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
blackNexus: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
blackCore: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
singularity: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
blackLibrary: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
blackRadiance: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
blazar: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
darkNova: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
holyGenocide: {require: false, enabled: false, variant: 'c', checkForReset: true, triggerForReset: -1},
}
},
build: {
// Should buildings be built automatically?
enabled: true,
// When a building requires a certain resource (this is what their *require* property refers to), then
// this is the percentage of the storage capacity of that resource, that has to be met for the building
// to be built.
trigger: 0,
// The items that be automatically built.
// Every item can define a required resource. This resource has to be available at a certain capacity for
// the building to be built. The capacity requirement is defined by the trigger value set for the section.
//
// Additionally, for upgradeable buildings, the item can define which upgrade stage it refers to.
// For upgraded buildings, the ID (or internal name) of the building can be controlled through the *name*
// property. For other buildings, the key of the item itself is used.
items: {
// housing
hut: {require: 'wood', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
logHouse: {require: 'minerals', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
mansion: {require: 'titanium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// craft bonuses
workshop: {require: 'minerals', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
factory: {require: 'titanium', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
// production
field: {require: 'catnip', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
pasture: {require: 'catnip', enabled: true, max:-1, stage: 0, checkForReset: true, triggerForReset: -1},
solarFarm: {require: 'titanium', enabled: true, max:-1, stage: 1, name: 'pasture', checkForReset: true, triggerForReset: -1},
mine: {require: 'wood', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
lumberMill: {require: 'minerals', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
aqueduct: {require: 'minerals', enabled: true, max:-1, stage: 0, checkForReset: true, triggerForReset: -1},
hydroPlant: {require: 'titanium', enabled: true, max:-1, stage: 1, name: 'aqueduct', checkForReset: true, triggerForReset: -1},
oilWell: {require: 'coal', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
quarry: {require: 'coal', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
// conversion
smelter: {require: 'minerals', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
biolab: {require: 'science', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
calciner: {require: 'titanium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
reactor: {require: 'titanium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
accelerator: {require: 'titanium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
steamworks: {require: false, enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
magneto: {require: false, enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// science
library: {require: 'wood', enabled: true, max:-1, stage: 0, checkForReset: true, triggerForReset: -1},
dataCenter: {require: false, enabled: true, max:-1, stage: 1, name: 'library', checkForReset: true, triggerForReset: -1},
academy: {require: 'wood', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
observatory: {require: 'iron', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
// other
amphitheatre: {require: 'minerals', enabled: true, max:-1, stage: 0, checkForReset: true, triggerForReset: -1},
broadcastTower: {require: 'titanium', enabled: true, max:-1, stage: 1, name: 'amphitheatre', checkForReset: true, triggerForReset: -1},
tradepost: {require: 'gold', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
chapel: {require: 'minerals', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
temple: {require: 'gold', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
mint: {require: false, enabled: false,max:-1, checkForReset: true, triggerForReset: -1},
// unicornPasture: {require: false, enabled: true},
ziggurat: {require: false, enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
chronosphere: {require: 'unobtainium', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
aiCore: {require: false, enabled: false,max:-1, checkForReset: true, triggerForReset: -1},
brewery: {require: false, enabled: false,max:-1, checkForReset: true, triggerForReset: -1},
// storage
barn: {require: 'wood', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
harbor: {require: false, enabled: false,max:-1, checkForReset: true, triggerForReset: -1},
warehouse: {require: false, enabled: false,max:-1, checkForReset: true, triggerForReset: -1},
// zebras
zebraOutpost: {require: 'bloodstone', enabled: true, max:-1, checkForReset: true, triggerForReset: -1},
zebraWorkshop: {require: 'bloodstone', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
zebraForge: {require: 'bloodstone', enabled: false, max:-1, checkForReset: true, triggerForReset: -1}
}
},
space: {
// Should space buildings be built automatically?
enabled: false,
// The functionality of the space section is identical to the build section. It just needs to be treated
// seperately, because the game internals are slightly different.
trigger: 0,
items: {
// Cath
spaceElevator: {require: 'unobtainium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
sattelite: {require: 'titanium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
spaceStation: {require: 'oil', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Moon
moonOutpost: {require: 'uranium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
moonBase: {require: 'unobtainium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Dune
planetCracker: {require: 'science', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
hydrofracturer: {require: 'science', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
spiceRefinery: {require: 'science', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Piscine
researchVessel: {require: 'titanium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
orbitalArray: {require: 'eludium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Helios
sunlifter: {require: 'eludium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
containmentChamber: {require: 'science', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
heatsink: {require: 'thorium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
sunforge: {require: false, enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// T-Minus
cryostation: {require: 'eludium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Kairo
spaceBeacon: {require: 'antimatter', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Yarn
terraformingStation: {require: 'antimatter', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
hydroponics: {require: 'kerosene', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Umbra
hrHarvester: {require: 'antimatter', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Charon
entangler: {require: 'antimatter', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
// Centaurus
tectonic: {require: 'antimatter', enabled: false, max:-1, checkForReset: true, triggerForReset: -1},
moltenCore: {require: 'uranium', enabled: false, max:-1, checkForReset: true, triggerForReset: -1}
}
},
time: {
// Should time upgrades be built automatically?
enabled: false,
trigger: 0,
items: {
// Variants denote whether these buildings fall within the Chronoforge or Void categories.
// Chronoforge has variant chrono.
temporalBattery: {require: false, enabled: false, variant: 'chrono', checkForReset: true, triggerForReset: -1},
blastFurnace: {require: false, enabled: false, variant: 'chrono', checkForReset: true, triggerForReset: -1},
timeBoiler: {require: false, enabled: false, variant: 'chrono', checkForReset: true, triggerForReset: -1},
temporalAccelerator: {require: false, enabled: false, variant: 'chrono', checkForReset: true, triggerForReset: -1},
temporalImpedance: {require: false, enabled: false, variant: 'chrono', checkForReset: true, triggerForReset: -1},
ressourceRetrieval: {require: false, enabled: false, variant: 'chrono', checkForReset: true, triggerForReset: -1},
// Void Space has variant void.
cryochambers: {require: false, enabled: false, variant: 'void', checkForReset: true, triggerForReset: -1},
voidHoover: {require: 'antimatter', enabled: false, variant: 'void', checkForReset: true, triggerForReset: -1},
voidRift: {require: false, enabled: false, variant: 'void', checkForReset: true, triggerForReset: -1},
chronocontrol: {require: 'temporalFlux', enabled: false, variant: 'void', checkForReset: true, triggerForReset: -1},
voidResonator: {require: false, enabled: false, variant: 'void', checkForReset: true, triggerForReset: -1}
}
},
timeCtrl: {
enabled: true,
items: {
accelerateTime: {enabled: true, subTrigger: 1, misc: true, label: i18n('option.accelerate')},
timeSkip: {enabled: false, subTrigger: 5, misc: true, label: i18n('option.time.skip'), maximum: 50,
0: false, 1: false, 2: false, 3: false, 4: false, 5: false, 6: false, 7: false, 8: false, 9: false,
spring: true, summer: false, autumn: false, winter: false},
reset: {enabled: false, subTrigger: 99999, misc: true, label: i18n('option.time.reset')}
}
},
craft: {
// Should resources be crafted automatically?
enabled: true,
// Every item can define a required resource with the *require* property.
// At what percentage of the storage capacity of that required resource should the listed resource be crafted?
trigger: 0.95,
// The items that can be crafted.
// In addition to the *require* property, which is explained above, items can also define a *max*. If they
// do, no more than that resource will be automatically produced. This feature can not be controlled through
// the UI and is not used for any resource by default.
// The *limited* property tells KS to craft resources whenever the ratio of the component cost of the stored resources
// to the number of stored components is greater than the limit ratio "limRat".
// This means that if limRat is 0.5, then if you have 1000 beams and 500 beams worth of scaffolds, 250 of the beams
// will be crafted into scaffolds. If instead limRat is 0.75, 625 of the beams will be crafted into scaffolds for a final result
// of 1125 beams-worth of scaffolds and 375 remaining beams.
// Currently, limRat is not modifiable through the UI, though if there is demand, perhaps this will be added in the future.
// Limited has a few other effects like balancing plates and steel while minimizing iron waste
// TLDR: The purpose of the limited property is to proportionally distribute raw materials
// across all crafted resources without wasting raw materials.
items: {
wood: {require: 'catnip', max: 0, limited: true, limRat: 0.5, enabled: true},
beam: {require: 'wood', max: 0, limited: true, limRat: 0.5, enabled: true},
slab: {require: 'minerals', max: 0, limited: true, limRat: 0.5, enabled: true},
steel: {require: 'coal', max: 0, limited: true, limRat: 0.5, enabled: true},
plate: {require: 'iron', max: 0, limited: true, limRat: 0.5, enabled: true},
alloy: {require: 'titanium', max: 0, limited: true, limRat: 0.5, enabled: true},
concrate: {require: false, max: 0, limited: true, limRat: 0.5, enabled: true},
gear: {require: false, max: 0, limited: true, limRat: 0.5, enabled: true},
scaffold: {require: false, max: 0, limited: true, limRat: 0.5, enabled: true},
ship: {require: false, max: 0, limited: true, limRat: 0.5, enabled: true},
tanker: {require: false, max: 0, limited: true, limRat: 0.5, enabled: true},
parchment: {require: false, max: 0, limited: false, limRat: 0.5, enabled: true},
manuscript: {require: 'culture', max: 0, limited: true, limRat: 0.5, enabled: true},
compedium: {require: 'science', max: 0, limited: true, limRat: 0.5, enabled: true},
blueprint: {require: 'science', max: 0, limited: true, limRat: 0.5, enabled: true},
kerosene: {require: 'oil', max: 0, limited: true, limRat: 0.5, enabled: true},
megalith: {require: false, max: 0, limited: true, limRat: 0.5, enabled: true},
eludium: {require: 'unobtainium', max: 0, limited: true, limRat: 0.5, enabled: true},
thorium: {require: 'uranium', max: 0, limited: true, limRat: 0.5, enabled: true}
}
},
trade: {
// Should KS automatically trade?
enabled: true,
// Every trade can define a required resource with the *require* property.
// At what percentage of the storage capacity of that required resource should the trade happen?
trigger: 0.98,
// Trades can be limited to only happen during specific seasons. This is because trades with certain races
// are more effective during specific seasons.
// The *allowcapped* property allows us to trade even if the sold resources are at their cap.
items: {
dragons: {enabled: true, require: 'titanium', allowcapped: false, limited: true,
summer: true, autumn: true, winter: true, spring: true},
zebras: {enabled: true, require: false, allowcapped: false, limited: true,
summer: true, autumn: true, winter: true, spring: true},
lizards: {enabled: true, require: 'minerals', allowcapped: false, limited: true,
summer: true, autumn: false, winter: false, spring: false},
sharks: {enabled: true, require: 'iron', allowcapped: false, limited: true,
summer: false, autumn: false, winter: true, spring: false},
griffins: {enabled: true, require: 'wood', allowcapped: false, limited: true,
summer: false, autumn: true, winter: false, spring: false},
nagas: {enabled: true, require: false, allowcapped: false, limited: true,
summer: false, autumn: false, winter: false, spring: true},
spiders: {enabled: true, require: false, allowcapped: false, limited: true,
summer: false, autumn: true, winter: false, spring: false},
leviathans: {enabled: true, require: 'unobtainium', allowcapped: true, limited: true,
summer: true, autumn: true, winter: true, spring: true}
}
},
upgrade: {
//Should KS automatically upgrade?
enabled: false,
items: {
upgrades: {enabled: true, limited: true},
techs: {enabled: true},
policies: {enabled: false},
races: {enabled: true},
missions: {enabled: true, subTrigger: 12},
buildings: {enabled: true},
}
},
options: {
//Which misc options should be enabled?
enabled: true,
items: {
observe: {enabled: true, misc: true, label: i18n('option.observe')},
festival: {enabled: true, misc: true, label: i18n('option.festival')},
shipOverride: {enabled: true, misc: true, label: i18n('option.shipOverride')},
autofeed: {enabled: true, misc: true, label: i18n('option.autofeed')},
hunt: {enabled: true, subTrigger: 0.98, misc: true, label: i18n('option.hunt')},
promote: {enabled: true, misc: true, label: i18n('option.promote')},
crypto: {enabled: true, subTrigger: 10000, misc: true, label: i18n('option.crypto')},
fixCry: {enabled: false, misc: true, label: i18n('option.fix.cry')},
buildEmbassies: {enabled: true, subTrigger: 0.9, misc: true, label: i18n('option.embassies')},
style: {enabled: true, misc: true, label: i18n('option.style')},
_steamworks: {enabled: false, misc: true, label: i18n('option.steamworks')}
}
},
distribute: {
// Should KS automatically distribute free kittens
enabled: false,
items: {
// dynamic priority. distribute to the job which have lowest (job.val / job.max) value.
// if all jobs reach the max, then distribute kittens to unlimited job.
woodcutter: {enabled: true, max: 1, limited: false},
farmer: {enabled: true, max: 1, limited: false},
scholar: {enabled: true, max: 1, limited: false},
hunter: {enabled: true, max: 1, limited: false},
miner: {enabled: true, max: 1, limited: false},
priest: {enabled: true, max: 1, limited: false},
geologist: {enabled: true, max: 1, limited: false},
engineer: {enabled: true, max: 1, limited: false},
leader: {enabled: true, leaderJob: 'farmer', leaderTrait: 'manager'},
}
},
filter: {
//What log messages should be filtered?
enabled: false,
items: {
buildFilter: {enabled: false, filter: true, label: i18n('filter.build'), variant: "ks-activity type_ks-build"},
craftFilter: {enabled: false, filter: true, label: i18n('filter.craft'), variant: "ks-activity type_ks-craft"},
upgradeFilter: {enabled: false, filter: true, label: i18n('filter.upgrade'), variant: "ks-activity type_ks-upgrade"},
researchFilter: {enabled: false, filter: true, label: i18n('filter.research'), variant: "ks-activity type_ks-research"},
tradeFilter: {enabled: false, filter: true, label: i18n('filter.trade'), variant: "ks-activity type_ks-trade"},
huntFilter: {enabled: false, filter: true, label: i18n('filter.hunt'), variant: "ks-activity type_ks-hunt"},
praiseFilter: {enabled: false, filter: true, label: i18n('filter.praise'), variant: "ks-activity type_ks-praise"},
adoreFilter: {enabled: false, filter: true, label: i18n('filter.adore'), variant: "ks-activity type_ks-adore"},
transcendFilter: {enabled: false, filter: true, label: i18n('filter.transcend'), variant: "ks-activity type_ks-transcend"},
faithFilter: {enabled: false, filter: true, label: i18n('filter.faith'), variant: "ks-activity type_ks-faith"},
accelerateFilter:{enabled: false, filter: true, label: i18n('filter.accelerate'), variant: "ks-activity type_ks-accelerate"},
timeSkipFilter: {enabled: false, filter: true, label: i18n('filter.time.skip'), variant: "ks-activity type_ks-timeSkip"},
festivalFilter: {enabled: false, filter: true, label: i18n('filter.festival'), variant: "ks-activity type_ks-festival"},
starFilter: {enabled: false, filter: true, label: i18n('filter.star'), variant: "ks-activity type_ks-star"},
distributeFilter:{enabled: false, filter: true, label: i18n('filter.distribute'), variant: "ks-activity type_ks-distribute"},
promoteFilter: {enabled: false, filter: true, label: i18n('filter.promote'), variant: "ks-activity type_ks-promote"},
miscFilter: {enabled: false, filter: true, label: i18n('filter.misc'), variant: "ks-activity"}
}
},
resources: {
furs: {enabled: true, stock: 1000, checkForReset: false, stockForReset: Infinity},
timeCrystal: {enabled: false, stock: 0, checkForReset: true, stockForReset: 500000}
},
policies: [],
cache: {
cache: [],
cacheSum: {}
}
}
};
// GameLog Modification
// ====================
// Increase messages displayed in log
game.console.maxMessages = 1000;
var printoutput = function (args) {
if (options.auto.filter.enabled) {
for (var filt in options.auto.filter.items) {
var filter = options.auto.filter.items[filt];
if (filter.enabled && filter.variant === args[1]) {return;}
}
}
var color = args.pop();
args[1] = args[1] || 'ks-default';
// update the color of the message immediately after adding
var msg = game.msg.apply(game, args);
$(msg.span).css('color', color);
if (options.debug && console) console.log(args);
};
// Used for option change messages and other special notifications
var message = function () {
var args = Array.prototype.slice.call(arguments);
args.push('ks-default');
args.push(options.msgcolor);
printoutput(args);
};
var activity = function () {
var args = Array.prototype.slice.call(arguments);
var activityClass = args.length > 1 ? ' type_' + args.pop() : '';
args.push('ks-activity' + activityClass);
args.push(options.activitycolor);
printoutput(args);
};
var summary = function () {
var args = Array.prototype.slice.call(arguments);
args.push('ks-summary');
args.push(options.summarycolor);
printoutput(args);
};
var warning = function () {
var args = Array.prototype.slice.call(arguments);
args.unshift('Warning!');
if (console) console.log(args);
};
// i18n support
var imessage = function(key, args, t) { message(i18n(key, args), t); }
var iactivity = function(key, args, t) { activity(i18n(key, args), t); }
var isummary = function(key, args, t) { summary(i18n(key, args), t); }
var iwarning = function(key, args, t) { warning(i18n(key, args), t); }
// Core Engine for Kitten Scientists
// =================================
var Engine = function () {
this.upgradeManager = new UpgradeManager();
this.buildManager = new BuildManager();
this.spaceManager = new SpaceManager();
this.craftManager = new CraftManager();
this.bulkManager = new BulkManager();
this.tradeManager = new TradeManager();
this.religionManager = new ReligionManager();
this.timeManager = new TimeManager();
this.villageManager = new TabManager('Village');
this.cacheManager = new CacheManager();
};
Engine.prototype = {
upgradeManager: undefined,
buildManager: undefined,
spaceManager: undefined,
craftManager: undefined,
bulkManager: undefined,
tradeManager: undefined,
religionManager: undefined,
timeManager: undefined,
villageManager: undefined,
cacheManager: undefined,
loop: undefined,
start: function (msg=true) {
if (this.loop) return;
this.loop = setInterval(this.iterate.bind(this), options.interval);
if(msg) imessage('status.ks.enable');
},
stop: function (msg=true) {
if (!this.loop) return;
clearInterval(this.loop);
this.loop = undefined;
if (msg) imessage('status.ks.disable');
},
iterate: async function () {
var subOptions = options.auto.options;
if (subOptions.enabled && subOptions.items.observe.enabled) {this.observeStars()};
if (options.auto.upgrade.enabled) {this.upgrade()};
if (subOptions.enabled && subOptions.items.festival.enabled) {this.holdFestival()};
if (options.auto.build.enabled) {this.build()};
if (options.auto.space.enabled) {this.space()};
if (options.auto.craft.enabled) {this.craft()};
if (subOptions.enabled && subOptions.items.hunt.enabled) {this.setHunt()};
if (options.auto.trade.enabled) {this.trade()};
if (options.auto.faith.enabled) {this.worship()};
if (options.auto.time.enabled) {this.chrono()};
if (subOptions.enabled && subOptions.items.crypto.enabled) {this.crypto()};
if (subOptions.enabled && subOptions.items.autofeed.enabled) {this.autofeed()};
if (subOptions.enabled && subOptions.items.promote.enabled) {this.promote()};
if (options.auto.distribute.enabled) {this.distribute()};
if (options.auto.timeCtrl.enabled) {this.timeCtrl()};
if (subOptions.enabled) {this.miscOptions()};
if (options.auto.timeCtrl.enabled && options.auto.timeCtrl.items.reset.enabled) {await this.reset()};
},
halfInterval: async function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
this.hunt();
}, Math.floor(options.interval / 2))
})
},
setHunt: async function() {
await this.halfInterval();
},
reset: async function () {
// check challenge
if (game.challenges.anyChallengeActive())
return;
var checkedList = [];
var checkList = [];
var check = function (buttons) {
if (checkList.length != 0) {
for (var i in buttons) {
if (!buttons[i].model.metadata)
continue;
var name = buttons[i].model.metadata.name;
var index = checkList.indexOf(name)
if (index != -1) {
checkList.splice(index, 1)
if (game.resPool.hasRes(buttons[i].model.prices))
return true;
}
}
}
return false;
}
// check building
var opt = options.auto.build.items;
for (var name in opt)
if (opt[name].checkForReset) {
var bld = game.bld.get(name);
checkedList.push({name: bld.label, trigger: opt[name].triggerForReset, val: bld.val})
if (opt[name].triggerForReset > 0) {
if (opt[name].triggerForReset > bld.val)
return;
} else {
checkList.push(name);
}
}
// unicornPasture
opt = options.auto.unicorn.items.unicornPasture
if (opt.checkForReset) {
var bld = game.bld.get('unicornPasture');
checkedList.push({name: bld.label, trigger: opt.triggerForReset, val: bld.val})
if (opt.triggerForReset > 0) {
if (opt.triggerForReset > bld.val)
return;
} else {
checkList.push('unicornPasture');
}
}
if (check(this.buildManager.manager.tab.buttons) || checkList.length)
return;
// check space
opt = options.auto.space.items;
for (var name in opt)
if (opt[name].checkForReset) {
var bld = game.space.getBuilding(name)
checkedList.push({name: bld.label, trigger: opt[name].triggerForReset, val: bld.val})
if (opt[name].triggerForReset > 0) {
if (opt[name].triggerForReset > bld.val)
return;
} else {
checkList.push(name);
}
}
if (checkList.length != 0) {
var panels = this.spaceManager.manager.tab.planetPanels;
for (var i in panels) {
for (var j in panels[i].children) {
var model = panels[i].children[j].model;
var name = model.metadata.name;
var index = checkList.indexOf(name)
if (index != -1) {
checkList.splice(index, 1)
if (game.resPool.hasRes(model.prices))
this.return;
}
}
}
}
if (checkList.length)
return;
// check religion
opt = options.auto.faith.items;
for (var name in opt)
if (opt[name].checkForReset) {
var bld = this.religionManager.getBuild(name, opt[name].variant);
checkedList.push({name: bld.label, trigger: opt[name].triggerForReset, val: bld.val})
if (opt[name].triggerForReset > 0) {
if (opt[name].triggerForReset > bld.val)
return;
} else {
checkList.push(name);
}
}
opt = options.auto.unicorn.items
for (var name in opt)
if (opt[name].checkForReset && opt[name].variant == 'z') {
var bld = this.religionManager.getBuild(name, 'z')
checkedList.push({name: bld.label, trigger: opt[name].triggerForReset, val: bld.val})
if (opt[name].triggerForReset > 0) {
if (opt[name].triggerForReset > bld.val)
return;
} else {
checkList.push(name);
}
}
if (check(this.religionManager.manager.tab.zgUpgradeButtons) ||
check(this.religionManager.manager.tab.rUpgradeButtons) ||
check(this.religionManager.manager.tab.children[0].children[0].children) ||
checkList.length)
return;
// check time
opt = options.auto.time.items;
for (var name in opt)
if (opt[name].checkForReset) {
var bld = this.timeManager.getBuild(name, opt[name].variant);
checkedList.push({name: bld.label, trigger: opt[name].triggerForReset, val: bld.val})
if (opt[name].triggerForReset > 0) {
if (opt[name].triggerForReset > bld.val)
return;
} else {
checkList.push(name);
}
}
if (check(this.timeManager.manager.tab.children[2].children[0].children) ||
check(this.timeManager.manager.tab.children[3].children[0].children) ||
checkList.length)
return;
// check resources
opt = options.auto.resources;
for (var name in opt)
if (opt[name].checkForReset) {
var res = game.resPool.get(name);
checkedList.push({name: res.title, trigger: opt[name].stockForReset, val: res.value})
if (opt[name].stockForReset > res.value)
return;
}
// stop!
this.stop(false);
var sleep = function (time=1500) {
return new Promise(resolve => {
if (!(options.auto.engine.enabled && options.auto.timeCtrl.enabled && options.auto.timeCtrl.items.reset.enabled))
throw 'canceled by player'
setTimeout(resolve, time)
})
}
try {
for (var i in checkedList) {
await sleep(500);
var checked = checkedList[i];
imessage('reset.check', [checked.name, game.getDisplayValueExt(checked.trigger), game.getDisplayValueExt(checked.val)]);
}
await sleep(0).then(() => {
imessage('reset.checked');
return sleep();
}).then(() => {
iactivity('reset.tip');
return sleep();
}).then(() => {
imessage('reset.countdown.10');
return sleep(2000);
}).then(() => {
imessage('reset.countdown.9');
return sleep();
}).then(() => {
imessage('reset.countdown.8');
return sleep();
}).then(() => {
imessage('reset.countdown.7');
return sleep();
}).then(() => {
imessage('reset.countdown.6');
return sleep();
}).then(() => {
imessage('reset.countdown.5');
return sleep();
}).then(() => {
imessage('reset.countdown.4');
return sleep();
}).then(() => {
imessage('reset.countdown.3');
return sleep();
}).then(() => {
imessage('reset.countdown.2');
return sleep();
}).then(() => {
imessage('reset.countdown.1');
return sleep();
}).then(() => {
imessage('reset.countdown.0');
return sleep();
}).then(() => {
iactivity('reset.last.message');
return sleep();
});
} catch (error) {
imessage('reset.cancel.message');
iactivity('reset.cancel.activity');
return;
}
if (typeof kittenStorage.reset === 'undefined')
kittenStorage.reset = {};
kittenStorage.reset.karmaLastTime = game.resPool.get('karma').value;
kittenStorage.reset.paragonLastTime = game.resPool.get('paragon').value;
kittenStorage.reset.times += 1;
kittenStorage.reset.reset = true;
saveToKittenStorage();
//=============================================================
for (var i = 0; i < game.challenges.challenges.length; i++){
game.challenges.challenges[i].pending = false;
}
game.resetAutomatic();
//=============================================================
},
timeCtrl: function () {
var optionVals = options.auto.timeCtrl.items;
// Tempus Fugit
if (optionVals.accelerateTime.enabled && !game.time.isAccelerated && game.science.get("calendar").researched) {
var tf = game.resPool.get('temporalFlux')
if (tf.value >= tf.maxValue * optionVals.accelerateTime.subTrigger) {
game.time.isAccelerated = true;
iactivity('act.accelerate', [], 'ks-accelerate');
storeForSummary('accelerate', 1);
}
}
// Combust time crystal
TimeSkip:
if (optionVals.timeSkip.enabled && game.workshop.get('chronoforge').researched) {
if (game.calendar.day < 0)
break TimeSkip;
var shatter = game.timeTab.cfPanel.children[0].children[0]; // check?
var timeCrystal = game.resPool.get('timeCrystal');
if (timeCrystal.value < optionVals.timeSkip.subTrigger && !shatter.controller.hasResources(shatter.model))
break TimeSkip;
var season = game.calendar.season;
if (!optionVals.timeSkip[game.calendar.seasons[season].name])
break TimeSkip;
var currentCycle = game.calendar.cycle;
if (!optionVals.timeSkip[currentCycle])
break TimeSkip;
var heatMax = game.getEffect('heatMax');
var heatNow = game.time.heat;
if (heatNow >= heatMax)
break TimeSkip;
var yearsPerCycle = game.calendar.yearsPerCycle;
var remainingYearsCurrentCycle = yearsPerCycle - game.calendar.cycleYear;
var cyclesPerEra = game.calendar.cyclesPerEra;
var factor = game.challenges.getChallenge("1000Years").researched ? 5 : 10;
var canSkip = Math.min(Math.floor((heatMax - heatNow) / factor), optionVals.timeSkip.maximum);
var willSkip = 0;
if (canSkip < remainingYearsCurrentCycle){
willSkip = canSkip;
} else {
willSkip += remainingYearsCurrentCycle;
canSkip -= remainingYearsCurrentCycle;
var skipCycles = 1;
while (canSkip > yearsPerCycle && optionVals.timeSkip[(currentCycle + skipCycles) % cyclesPerEra]) {
willSkip += yearsPerCycle;
canSkip -= yearsPerCycle;
skipCycles += 1;
}
if (optionVals.timeSkip[(currentCycle + skipCycles) % cyclesPerEra] && canSkip > 0)
willSkip += canSkip;
}
if (willSkip > 0) {
iactivity('act.time.skip', [willSkip], 'ks-timeSkip');
shatter.controller.doShatterAmt(shatter.model, willSkip);
storeForSummary('time.skip', willSkip);
}
}
},
promote: function () {
if (game.science.get('civil').researched && game.village.leader != null) {
var leader = game.village.leader;
var rank = leader.rank;
var gold = this.craftManager.getResource('gold');
var goldStock = this.craftManager.getStock('gold');
// game.village.sim.goldToPromote will check gold
// game.village.sim.promote check both gold and exp
if (game.village.sim.goldToPromote(rank, rank+1, gold-goldStock)[0] && game.village.sim.promote(leader, rank+1) == 1) {
iactivity('act.promote', [rank+1], 'ks-promote');
gamePage.tabs[1].censusPanel.census.renderGovernment(gamePage.tabs[1].censusPanel.census);
gamePage.tabs[1].censusPanel.census.update();
storeForSummary('promote', 1);
}
}
},
distribute: function () {
var refreshRequired = false;
var distributeItem = options.auto.distribute.items;
var leaderVals = distributeItem.leader;
if (leaderVals.enabled && game.science.get('civil').researched && !game.challenges.isActive("anarchy")) {
var leaderJobName = leaderVals.leaderJob;
var traitName = leaderVals.leaderTrait;
var optionsTheocracy = false;
if (options.auto.upgrade.items.policies.enabled) {
optionsTheocracy = (options.policies === undefined) ? false : options.policies.some(obj => obj === 'theocracy');
}
if (optionsTheocracy || game.science.getPolicy('theocracy').researched) {leaderJobName = "priest";}
var distributeJob = game.village.getJob(leaderJobName);
if (game.village.leader == null || !(game.village.leader.job == leaderJobName && game.village.leader.trait.name == traitName)) {
var traitKittens = game.village.sim.kittens.filter(kitten => kitten.trait.name == traitName);
if (traitKittens.length != 0) {
if (distributeJob.unlocked && distributeJob.value < game.village.getJobLimit(leaderJobName)) {
var correctLeaderKitten = traitKittens.sort(function(a, b) {return b.rank - a.rank != 0 ? b.rank - a.rank : b.exp - a.exp;})[0];
if (distributeJob.value >= distributeItem[leaderJobName].max && distributeItem[leaderJobName].limited && distributeJob.value) {
game.village.sim.removeJob(leaderJobName, 1);
}
game.village.unassignJob(correctLeaderKitten);
game.village.getJob(leaderJobName).value++;
correctLeaderKitten.job= leaderJobName;
game.villageTab.censusPanel.census.makeLeader(correctLeaderKitten);
game.workshopTab.updateTab();
refreshRequired = true;
iactivity('act.distributeLeader', [i18n('$village.trait.' + traitName)], 'ks-distribute');
storeForSummary('distribute', 1);
}
}
}
}
var freeKittens = game.village.getFreeKittens();
if (!freeKittens)
return;
var jobName = '';
var minRatio = Infinity;
var currentRatio = 0;
for (var i in game.village.jobs) {
var name = game.village.jobs[i].name;
var unlocked = game.village.jobs[i].unlocked;
var enabled = options.auto.distribute.items[name].enabled;
var maxGame = game.village.getJobLimit(name);
var maxKS = options.auto.distribute.items[name].max;
var val = game.village.jobs[i].value
var limited = options.auto.distribute.items[name].limited;
if (unlocked && enabled && val < maxGame && (!limited || val < maxKS)) {
currentRatio = val/maxKS;
if (currentRatio < minRatio) {
minRatio = currentRatio;
jobName = name;
}
}
}
if (jobName) {
game.village.assignJob(game.village.getJob(jobName), 1);
refreshRequired = true;
iactivity('act.distribute', [i18n('$village.job.' + jobName)], 'ks-distribute');
storeForSummary('distribute', 1);
}
if (refreshRequired) {this.villageManager.render();}
},
autofeed: function () {
var levi = game.diplomacy.get("leviathans");
var nCorn = game.resPool.get("necrocorn");
if (!(levi.unlocked && nCorn.value > 0)) {return;}
if (nCorn.value >= 1) {
if (levi.energy < game.diplomacy.getMarkerCap()) {
game.diplomacy.feedElders();
iactivity('act.feed');
storeForSummary('feed', 1);
}
} else {
if (0.25 * (1 + game.getEffect("corruptionBoostRatio")) < 1) {
storeForSummary('feed', nCorn.value);
game.diplomacy.feedElders();
iactivity('dispose.necrocorn');
}
}
},
crypto: function () {
var coinPrice = game.calendar.cryptoPrice;
var previousRelic = game.resPool.get('relic').value;
var previousCoin = game.resPool.get('blackcoin').value;
if((!game.science.get("blackchain").researched && !previousCoin > 0) || !game.diplomacy.get("leviathans").unlocked) {return;}
var exchangedCoin = 0.0;
var exchangedRelic = 0.0;
var waitForBestPrice = false;
// Waits for coin price to drop below a certain treshold before starting the exchange process
if (waitForBestPrice == true && coinPrice < 860.0) { waitForBestPrice = false; }
// Exchanges up to a certain threshold, in order to keep a good exchange rate, then waits for a higher treshold before exchanging for relics.
if (waitForBestPrice == false && coinPrice < 950.0 && previousRelic > options.auto.options.items.crypto.subTrigger) {
var currentCoin;
// function name changed in v1.4.8.0
if (typeof game.diplomacy.buyEcoin === 'function') {
game.diplomacy.buyEcoin();
} else {
game.diplomacy.buyBcoin();
}
currentCoin = game.resPool.get('blackcoin').value;
exchangedCoin = Math.round(currentCoin - previousCoin);
iactivity('blackcoin.buy', [exchangedCoin]);
}
else if (coinPrice > 1050.0 && game.resPool.get('blackcoin').value > 0) {
var currentRelic;
waitForBestPrice = true;
// function name changed in v1.4.8.0
if (typeof game.diplomacy.sellEcoin === 'function') {
game.diplomacy.sellEcoin();
} else {
game.diplomacy.sellBcoin();
}
currentRelic = game.resPool.get('relic').value;
exchangedRelic = Math.round(currentRelic - previousRelic);
iactivity('blackcoin.sell', [exchangedRelic]);
}
},
worship: function () {
var builds = options.auto.faith.items;
var manager = this.religionManager;
var buildManager = this.buildManager;
var craftManager = this.craftManager;
var option = options.auto.faith.addition;
if (option.bestUnicornBuilding.enabled) {
var bestUnicornBuilding = this.getBestUnicornBuilding();
if (bestUnicornBuilding) {
if (bestUnicornBuilding == 'unicornPasture')
buildManager.build(bestUnicornBuilding, undefined, 1);
else
{
var btn = manager.getBuildButton(bestUnicornBuilding, 'z');
for (var i in btn.model.prices)
if (btn.model.prices[i].name=='tears')
var tearNeed = btn.model.prices[i].val;
var tearHave = craftManager.getValue('tears') - craftManager.getStock('tears');
if (tearNeed > tearHave)
{
// if no ziggurat, getBestUnicornBuilding will return unicornPasture
var maxSacrifice = Math.floor((craftManager.getValue('unicorns') - craftManager.getStock('unicorns')) / 2500);
var needSacrifice = Math.ceil((tearNeed-tearHave) / game.bld.getBuildingExt('ziggurat').meta.on);
if (needSacrifice < maxSacrifice)
game.religionTab.sacrificeBtn.controller._transform(game.religionTab.sacrificeBtn.model, needSacrifice);
// iactivity?
}
religionManager.build(bestUnicornBuilding, 'z', 1);
}
}
} else {
builds = Object.assign({}, builds, Object.fromEntries(Object.entries(options.auto.unicorn.items).filter(([k,v]) => v.variant!='zp')));
if (options.auto.unicorn.items.unicornPasture.enabled)
this.build({unicornPasture: {require: false, enabled: true}});
}
// religion build
this._worship(builds);
var faith = craftManager.getResource('faith');
var rate = faith.value / faith.maxValue;
// enough faith, and then TAP
if (0.98 <= rate) {
var worship = game.religion.faith;
var epiphany = game.religion.faithRatio;
var transcendenceReached = game.religion.getRU("transcendence").on;
var tt = transcendenceReached ? game.religion.transcendenceTier : 0;
// Transcend
if (option.transcend.enabled && transcendenceReached)
{
var adoreIncreaceRatio = Math.pow((tt + 2) / (tt + 1), 2);
var needNextLevel = game.religion._getTranscendTotalPrice(tt + 1) - game.religion._getTranscendTotalPrice(tt);
var x = needNextLevel;
var k = adoreIncreaceRatio;
var epiphanyRecommend = (1-k+Math.sqrt(80*(k*k-1)*x+(k-1)*(k-1)))*k/(40*(k+1)*(k+1)*(k-1))+x+x/(k*k-1);
if(epiphany > epiphanyRecommend) {
// code copy from kittens game's religion.js: game.religion.transcend()
// game.religion.transcend() need confirm by player
// game version: 1.4.8.1
// ========================================================================================================
// DO TRANSCEND START
// ========================================================================================================
game.religion.faithRatio -= needNextLevel;
game.religion.tcratio += needNextLevel;
game.religion.transcendenceTier += 1;
var atheism = game.challenges.getChallenge("atheism");
atheism.calculateEffects(atheism, game);
var blackObelisk = game.religion.getTU("blackObelisk");
blackObelisk.calculateEffects(blackObelisk, game);
game.msg($I("religion.transcend.msg.success", [game.religion.transcendenceTier]));
// ========================================================================================================
// DO TRANSCEND END
// ========================================================================================================
epiphany = game.religion.faithRatio;
tt = game.religion.transcendenceTier;
iactivity('act.transcend', [game.getDisplayValueExt(needNextLevel), tt], 'ks-transcend');
storeForSummary('transcend', 1);
}
}
// Adore
if (option.adore.enabled && game.religion.getRU('apocripha').on) {
// game version: 1.4.8.1
var maxSolarRevolution = 10 + game.getEffect("solarRevolutionLimit")
var triggerSolarRevolution = maxSolarRevolution*option.adore.subTrigger;
var epiphanyInc = worship / 1000000 * (tt + 1) * (tt + 1) * 1.01;
var epiphanyAfterAdore = epiphany + epiphanyInc;
var worshipAfterAdore = 0.01 + faith.value*(1 + game.getUnlimitedDR(epiphanyAfterAdore, 0.1)*0.1);
var solarRevolutionAdterAdore = game.getLimitedDR(game.getUnlimitedDR(worshipAfterAdore, 1000)/100, maxSolarRevolution);
if (solarRevolutionAdterAdore >= triggerSolarRevolution) {
game.religion._resetFaithInternal(1.01);
iactivity('act.adore', [game.getDisplayValueExt(worship), game.getDisplayValueExt(epiphanyInc)], 'ks-adore');
storeForSummary('adore', epiphanyInc);
epiphany = game.religion.faithRatio;
worship = game.religion.faith;
}
}
}
// Praise
if (option.autoPraise.enabled && rate >= option.autoPraise.subTrigger) {
if (!game.religion.getFaithBonus) {
var apocryphaBonus = game.religion.getApocryphaBonus();
} else {
var apocryphaBonus = game.religion.getFaithBonus();
}
var worshipInc = faith.value * (1 + apocryphaBonus);
storeForSummary('praise', worshipInc);
iactivity('act.praise', [game.getDisplayValueExt(faith.value), game.getDisplayValueExt(worshipInc)], 'ks-praise');
game.religion.praise();
}
},
_worship: function (builds) {
var builds = builds || options.auto.faith.items;
var buildManager = this.religionManager;
var craftManager = this.craftManager;
var bulkManager = this.bulkManager;
var trigger = options.auto.faith.trigger;
// Render the tab to make sure that the buttons actually exist in the DOM. Otherwise we can't click them.
buildManager.manager.render();
var metaData = {};
for (var name in builds) {
var build = builds[name]
metaData[name] = buildManager.getBuild(name, build.variant);
if (!buildManager.getBuildButton(name, build.variant)) {
metaData[name].rHidden = true;
} else {
var model = buildManager.getBuildButton(name, build.variant).model;
var panel = (build.variant === 'c') ? game.science.get('cryptotheology').researched : true;
metaData[name].rHidden = !(model.visible && model.enabled && panel);
}
}
var buildList = bulkManager.bulk(builds, metaData, trigger);
var refreshRequired = false;
for (var entry in buildList) {
if (buildList[entry].count > 0) {
buildManager.build(buildList[entry].id, buildList[entry].variant, buildList[entry].count);
refreshRequired = true;
}
}
if (refreshRequired) {game.ui.render();}
},
chrono: function () {
if (!game.timeTab.visible) {return;}
var builds = options.auto.time.items;
var buildManager = this.timeManager;
var craftManager = this.craftManager;
var bulkManager = this.bulkManager;
var trigger = options.auto.time.trigger;
// Render the tab to make sure that the buttons actually exist in the DOM. Otherwise we can't click them.
buildManager.manager.render();
var metaData = {};
for (var name in builds) {
var build = builds[name]
metaData[name] = buildManager.getBuild(name, build.variant);
var model = buildManager.getBuildButton(name, build.variant).model;
var panel = (build.variant === 'chrono') ? buildManager.manager.tab.cfPanel : buildManager.manager.tab.vsPanel;
metaData[name].tHidden = (!model.visible || !model.enabled || !panel.visible);
}
var buildList = bulkManager.bulk(builds, metaData, trigger);
var refreshRequired = false;
for (var entry in buildList) {
if (buildList[entry].count > 0) {
buildManager.build(buildList[entry].id, buildList[entry].variant, buildList[entry].count);
refreshRequired = true;
}
}
if (refreshRequired) {game.ui.render();}
},
upgrade: function () {
var upgrades = options.auto.upgrade.items;
var upgradeManager = this.upgradeManager;
var craftManager = this.craftManager;
var bulkManager = this.bulkManager;
var buildManager = this.buildManager;
upgradeManager.sciManager.render();
if (upgrades.upgrades.enabled && gamePage.workshopTab.visible) {
upgradeManager.workManager.render();
var work = game.workshop.upgrades;
var noup = ["factoryOptimization","factoryRobotics","spaceEngineers","aiEngineers","chronoEngineers","steelPlants","amFission","biofuel","gmo","factoryAutomation","advancedAutomation","invisibleBlackHand"];
workLoop:
for (var upg in work) {
if (work[upg].researched || !work[upg].unlocked) {continue;}
var prices = dojo.clone(work[upg].prices); // game.village.getEffectLeader will override its argument
prices = game.village.getEffectLeader("scientist", prices);
for (var resource in prices) {
if (craftManager.getValueAvailable(prices[resource].name, true) < prices[resource].val) {continue workLoop;}
}
if (upgrades.upgrades.limited){
for (var name in noup) {
if (work[upg].name == noup[name]) {continue workLoop;}
}
}
upgradeManager.build(work[upg], 'workshop');
}
}
if (upgrades.techs.enabled && gamePage.libraryTab.visible) {
var tech = game.science.techs;
techLoop:
for (var upg in tech) {
if (tech[upg].researched || !tech[upg].unlocked) {continue;}
var prices = dojo.clone(tech[upg].prices);
prices = game.village.getEffectLeader("scientist", prices);
for (var resource in prices) {
if (craftManager.getValueAvailable(prices[resource].name, true) < prices[resource].val) {continue techLoop;}
}
upgradeManager.build(tech[upg], 'science');
}
}
if (upgrades.policies.enabled && gamePage.libraryTab.visible) {
// write a function to make breaking big loop easier
(function (){
var policies = game.science.policies;
var lastIndex = 0;
var length = policies.length;
var toResearch = [];
// A **little** more efficient than game.science.getPolicy if options.policies is right order
for (var i in options.policies) {
targetName = options.policies[i];
for (var j in policies) {
j = parseInt(j); // fuck js
policy = policies[(j+lastIndex) % length];
if (policy.name == targetName) {
lastIndex = j+lastIndex+1;
if (!policy.researched) {
if (policy.blocked) {
continue;
}
if (policy.unlocked) {
if (policy.requiredLeaderJob == undefined ||
(game.village.leader != null && game.village.leader.job == policy.requiredLeaderJob)
){
toResearch.push(policy);
}
}
}
break;
}
}
}
for (var i = 0; i < toResearch.length; i++) {
for (var resource of toResearch[i].prices) {
if (craftManager.getValueAvailable(resource.name, true) < resource.val) {continue;}
}
upgradeManager.build(toResearch[i], 'policy');
}
})();
}
if (upgrades.missions.enabled && gamePage.spaceTab.visible) {
upgradeManager.spaManager.render();
var missionsLength = Math.min(game.space.meta[0].meta.length, upgrades.missions.subTrigger);
var missions = game.space.meta[0].meta;
missionLoop:
for (var i = 0; i < missionsLength ; i++) {
if (!(missions[i].unlocked && missions[i].val < 1)) {continue;}
var model = this.spaceManager.manager.tab.GCPanel.children[i];
var prices = model.model.prices;
for (var resource in prices) {
if (craftManager.getValueAvailable(prices[resource].name, true) < prices[resource].val) {continue missionLoop;}
}
model.domNode.click();
if (i === 7 || i === 12) {
iactivity('upgrade.space.mission', [missions[i].label], 'ks-upgrade');
} else {
iactivity('upgrade.space', [missions[i].label], 'ks-upgrade');
}
}
}
if (upgrades.races.enabled && gamePage.diplomacyTab.visible) {
var maxRaces = (game.diplomacy.get('leviathans').unlocked) ? 8 : 7;
if (game.diplomacyTab.racePanels.length < maxRaces) {
var manpower = craftManager.getValueAvailable('manpower', true);
if (!game.diplomacy.get('lizards').unlocked) {
if (manpower >= 1000) {
game.resPool.get('manpower').value -= 1000;
iactivity('upgrade.race', [game.diplomacy.unlockRandomRace().title], 'ks-upgrade');
manpower -= 1000;
game.ui.render();
}
}
if (!game.diplomacy.get('sharks').unlocked) {
if (manpower >= 1000) {
game.resPool.get('manpower').value -= 1000;
iactivity('upgrade.race', [game.diplomacy.unlockRandomRace().title], 'ks-upgrade');
manpower -= 1000;
game.ui.render();
}
}
if (!game.diplomacy.get('griffins').unlocked) {
if (manpower >= 1000) {
game.resPool.get('manpower').value -= 1000;
iactivity('upgrade.race', [game.diplomacy.unlockRandomRace().title], 'ks-upgrade');
manpower -= 1000;
game.ui.render();
}
}
if (!game.diplomacy.get('nagas').unlocked && game.resPool.get("culture").value >= 1500) {
if (manpower >= 1000) {
game.resPool.get('manpower').value -= 1000;
iactivity('upgrade.race', [game.diplomacy.unlockRandomRace().title], 'ks-upgrade');
manpower -= 1000;
game.ui.render();
}
}
if (!game.diplomacy.get('zebras').unlocked && game.resPool.get("ship").value >= 1) {
if (manpower >= 1000) {
game.resPool.get('manpower').value -= 1000;
iactivity('upgrade.race', [game.diplomacy.unlockRandomRace().title], 'ks-upgrade');
manpower -= 1000;
game.ui.render();
}
}
if (!game.diplomacy.get('spiders').unlocked && game.resPool.get("ship").value >= 100 && game.resPool.get("science").maxValue > 125000) {
if (manpower >= 1000) {
game.resPool.get('manpower').value -= 1000;
iactivity('upgrade.race', [game.diplomacy.unlockRandomRace().title], 'ks-upgrade');
manpower -= 1000;
game.ui.render();
}
}
if (!game.diplomacy.get('dragons').unlocked && game.science.get("nuclearFission").researched) {
if (manpower >= 1000) {
game.resPool.get('manpower').value -= 1000;
iactivity('upgrade.race', [ game.diplomacy.unlockRandomRace().title], 'ks-upgrade');
manpower -= 1000;
game.ui.render();
}
}
}
}
if (upgrades.buildings.enabled) {
var pastures = (game.bld.getBuildingExt('pasture').meta.stage === 0) ? game.bld.getBuildingExt('pasture').meta.val: 0;
var aqueducts = (game.bld.getBuildingExt('aqueduct').meta.stage === 0) ? game.bld.getBuildingExt('aqueduct').meta.val: 0;
var pastureMeta = game.bld.getBuildingExt('pasture').meta;
if (pastureMeta.stage === 0) {
if (pastureMeta.stages[1].stageUnlocked) {
if (craftManager.getPotentialCatnip(true, 0, aqueducts) > 0) {
var prices = pastureMeta.stages[1].prices;
var priceRatio = bulkManager.getPriceRatio(pastureMeta, true);
if (bulkManager.singleBuildPossible(pastureMeta, prices, 1)) {
var button = buildManager.getBuildButton('pasture', 0);
button.controller.sellInternal(button.model, 0);
pastureMeta.on = 0;
pastureMeta.val = 0;
pastureMeta.stage = 1;
iactivity('upgrade.building.pasture', [], 'ks-upgrade');
game.ui.render();
buildManager.build('pasture', 1, 1);
game.ui.render();
return;
}
}
}
}
var aqueductMeta = game.bld.getBuildingExt('aqueduct').meta;
if (aqueductMeta.stage === 0) {
if (aqueductMeta.stages[1].stageUnlocked) {
if (craftManager.getPotentialCatnip(true, pastures, 0) > 0) {
var prices = aqueductMeta.stages[1].prices;
var priceRatio = bulkManager.getPriceRatio(aqueductMeta, true);
if (bulkManager.singleBuildPossible(aqueductMeta, prices, 1)) {
var button = buildManager.getBuildButton('aqueduct', 0);
button.controller.sellInternal(button.model, 0);
aqueductMeta.on = 0
aqueductMeta.val = 0
aqueductMeta.stage = 1
aqueductMeta.calculateEffects(aqueductMeta, game)
iactivity('upgrade.building.aqueduct', [], 'ks-upgrade');
game.ui.render();
buildManager.build('aqueduct', 1, 1);
game.ui.render();
return;
}
}
}
}
var libraryMeta = game.bld.getBuildingExt('library').meta;
if (libraryMeta.stage === 0) {
if (libraryMeta.stages[1].stageUnlocked) {
var enCon = (game.workshop.get('cryocomputing').researched) ? 1 : 2;
if (game.challenges.isActive('energy')) {enCon *= 2 * (1 + game.getEffect("energyConsumptionIncrease"));}
var libToDat = 3;
if (game.workshop.get('uplink').researched) {
libToDat *= (1 + game.bld.get('biolab').val * game.getEffect('uplinkDCRatio'));
}
if (game.workshop.get('machineLearning').researched) {
libToDat *= (1 + game.bld.get('aiCore').on * game.getEffect('dataCenterAIRatio'));
}
var scienceBldMax = game.bld.getEffect("scienceMax");
if (game.resPool.get('compedium').value > scienceBldMax) {
if (game.resPool.energyProd >= game.resPool.energyCons + enCon * libraryMeta.val / libToDat) {
var prices = libraryMeta.stages[1].prices;
var priceRatio = bulkManager.getPriceRatio(libraryMeta, true);
if (bulkManager.singleBuildPossible(libraryMeta, prices, 1)) {
var button = buildManager.getBuildButton('library', 0);
button.controller.sellInternal(button.model, 0);
libraryMeta.on = 0
libraryMeta.val = 0
libraryMeta.stage = 1
libraryMeta.calculateEffects(libraryMeta, game)
iactivity('upgrade.building.library', [], 'ks-upgrade');
game.ui.render();
buildManager.build('library', 1, 1);
game.ui.render();
return;
}
}
}
}
}
var amphitheatreMeta = game.bld.getBuildingExt('amphitheatre').meta;
if (amphitheatreMeta.stage === 0) {
if (amphitheatreMeta.stages[1].stageUnlocked) {
var prices = amphitheatreMeta.stages[1].prices;
var priceRatio = bulkManager.getPriceRatio(amphitheatreMeta, true);
if (game.getResourcePerTick('titanium', true) > 0) {
if (bulkManager.singleBuildPossible(amphitheatreMeta, prices, 1)) {
var button = buildManager.getBuildButton('amphitheatre', 0);
button.controller.sellInternal(button.model, 0);
amphitheatreMeta.on = 0
amphitheatreMeta.val = 0
amphitheatreMeta.stage = 1
iactivity('upgrade.building.amphitheatre', [], 'ks-upgrade');
game.ui.render();
buildManager.build('amphitheatre', 1, 1);
game.ui.render();
return;
}
}
}
}
}
},
build: function (builds) {
var builds = builds || options.auto.build.items;
var buildManager = this.buildManager;
var craftManager = this.craftManager;
var bulkManager = this.bulkManager;
var trigger = options.auto.build.trigger;
// Render the tab to make sure that the buttons actually exist in the DOM. Otherwise we can't click them.
buildManager.manager.render();
var metaData = {};
for (var name in builds) {
var build = builds[name]
metaData[name] = buildManager.getBuild(build.name || name).meta;
}
var buildList = bulkManager.bulk(builds, metaData, trigger, 'bonfire');
var refreshRequired = false;
for (var entry in buildList) {
if (buildList[entry].count > 0) {
buildManager.build(buildList[entry].name || buildList[entry].id, buildList[entry].stage, buildList[entry].count);
refreshRequired = true;
}
}
if (refreshRequired) {game.ui.render();}
},
space: function () {
var builds = options.auto.space.items;
var buildManager = this.spaceManager;
var craftManager = this.craftManager;
var bulkManager = this.bulkManager;
var trigger = options.auto.space.trigger;
// Render the tab to make sure that the buttons actually exist in the DOM. Otherwise we can't click them.
buildManager.manager.render();
var metaData = {};
for (var name in builds) {
var build = builds[name]
metaData[name] = buildManager.getBuild(name);
}
var buildList = bulkManager.bulk(builds, metaData, trigger, 'space');
var refreshRequired = false;
for (var entry in buildList) {
if (buildList[entry].count > 0) {
buildManager.build(buildList[entry].id, buildList[entry].count);
refreshRequired = true;
}
}
if (refreshRequired) {game.ui.render();}
},
craft: function () {
var crafts = options.auto.craft.items;
var manager = this.craftManager;
var trigger = options.auto.craft.trigger;
for (var name in crafts) {
var craft = crafts[name];
var current = !craft.max ? false : manager.getResource(name);
var require = !craft.require ? false : manager.getResource(craft.require);
var season = game.calendar.season;
var amount = 0;
// Ensure that we have reached our cap
if (current && current.value > craft.max) continue;
if (!manager.singleCraftPossible(name)) {continue;}
// Craft the resource if we meet the trigger requirement
if (!require || trigger <= require.value / require.maxValue) {
amount = manager.getLowestCraftAmount(name, craft.limited, craft.limRat, true);
} else if (craft.limited) {
amount = manager.getLowestCraftAmount(name, craft.limited, craft.limRat, false);
}
if (amount > 0) {
manager.craft(name, amount);
}
}
},
holdFestival: function () {
if (!(game.science.get('drama').researched && game.calendar.festivalDays < 400)) {return;}
if (!game.prestige.getPerk('carnivals').researched && game.calendar.festivalDays > 0) {return;}
var craftManager = this.craftManager;
if (craftManager.getValueAvailable('manpower', true) < 1500 || craftManager.getValueAvailable('culture', true) < 5000
|| craftManager.getValueAvailable('parchment', true) < 2500) {return;}
var catpowProf = 4000 * craftManager.getTickVal(craftManager.getResource('manpower'), true) > 1500;
var cultureProf = 4000 * craftManager.getTickVal(craftManager.getResource('culture'), true) > 5000;
var parchProf = 4000 * craftManager.getTickVal(craftManager.getResource('parchment'), true) > 2500;
if (!(catpowProf && cultureProf && parchProf)) {return;}
// Render the tab to make sure that the buttons actually exist in the DOM. Otherwise we can't click them.
this.villageManager.render();
if (game.villageTab.festivalBtn.model.enabled) {
var beforeDays = game.calendar.festivalDays;
game.villageTab.festivalBtn.onClick();
storeForSummary('festival');
if (beforeDays > 0) {
iactivity('festival.extend', [], 'ks-festival');
} else {
iactivity('festival.hold', [], 'ks-festival');
}
}
},
observeStars: function () {
if (game.calendar.observeBtn != null){
game.calendar.observeHandler();
iactivity('act.observe', [], 'ks-star');
storeForSummary('stars', 1);
}
},
hunt: function () {
var manpower = this.craftManager.getResource('manpower');
if (manpower.value < 100 || game.challenges.isActive("pacifism")) {return;}
if (options.auto.options.items.hunt.subTrigger <= manpower.value / manpower.maxValue) {
// No way to send only some hunters. Thus, we hunt with everything
var huntCount = Math.floor(manpower.value/100);
storeForSummary('hunt', huntCount);
iactivity('act.hunt', [huntCount], 'ks-hunt');
var huntCount = Math.floor(manpower.value/100);
var aveOutput = this.craftManager.getAverageHunt();
var trueOutput = {};
for (var out in aveOutput) {
var res = this.craftManager.getResource(out);
trueOutput[out] = (res.maxValue > 0) ? Math.min(aveOutput[out] * huntCount, Math.max(res.maxValue - res.value, 0)) : aveOutput[out] * huntCount;
}
this.cacheManager.pushToCache({'materials': trueOutput, 'timeStamp': game.timer.ticksTotal});
game.village.huntAll();
}
},
trade: function () {
var craftManager = this.craftManager;
var tradeManager = this.tradeManager;
var cacheManager = this.cacheManager;
var gold = craftManager.getResource('gold');
var trades = [];
var requireTrigger = options.auto.trade.trigger;
tradeManager.manager.render();
if (!tradeManager.singleTradePossible(undefined)) {return;}
var season = game.calendar.getCurSeason().name;
// Determine how many races we will trade this cycle
for (var name in options.auto.trade.items) {
var trade = options.auto.trade.items[name];
// Check if the race is in season, enabled, unlocked, and can actually afford it
if (!trade.enabled) continue;
if (!trade[season]) continue;
var race = tradeManager.getRace(name);
if (!race.unlocked) {continue;}
var button = tradeManager.getTradeButton(race.name);
if (!button.model.enabled) {continue;}
if (!tradeManager.singleTradePossible(name)) {continue;}
var require = !trade.require ? false : craftManager.getResource(trade.require);
// If we have enough to trigger the check, then attempt to trade
var prof = tradeManager.getProfitability(name);
if (trade.limited && prof) {
trades.push(name);
} else if ((!require || requireTrigger <= require.value / require.maxValue) && requireTrigger <= gold.value / gold.maxValue) {
trades.push(name);
}
}
if (trades.length === 0) {return;}
// Figure out how much we can currently trade
var maxTrades = tradeManager.getLowestTradeAmount(undefined, true, false);
// Distribute max trades without starving any race
if (maxTrades < 1) {return;}
var maxByRace = [];
for (var i = 0; i < trades.length; i++) {
var name = trades[i];
var trade = options.auto.trade.items[name];
var require = !trade.require ? false : craftManager.getResource(trade.require);
var trigConditions = ((!require || requireTrigger <= require.value / require.maxValue) && requireTrigger <= gold.value / gold.maxValue);
var tradePos = tradeManager.getLowestTradeAmount(name, trade.limited, trigConditions);
if (tradePos < 1) {
trades.splice(i, 1);
i--;
continue;
}
maxByRace[i] = tradePos;
}
if (trades.length === 0) {return;}
var tradesDone = {};
while (trades.length > 0 && maxTrades >= 1) {
if (maxTrades < trades.length) {
var j = Math.floor(Math.random() * trades.length);
if (!tradesDone[trades[j]]) {tradesDone[trades[j]] = 0;}
tradesDone[trades[j]] += 1;
maxTrades -= 1;
trades.splice(j, 1);
maxByRace.splice(j, 1);
continue;
}
var minTrades = Math.floor(maxTrades / trades.length);
var minTradePos = 0;
for (var i = 0; i < trades.length; i++) {
if (maxByRace[i] < minTrades) {
minTrades = maxByRace[i];
minTradePos = i;
}
}
if (!tradesDone[trades[minTradePos]]) {tradesDone[trades[minTradePos]] = 0;}
tradesDone[trades[minTradePos]] += minTrades;
maxTrades -= minTrades;
trades.splice(minTradePos, 1);
maxByRace.splice(minTradePos, 1);
}
if (tradesDone.length === 0) {return;}
var tradeNet = {};
for (var name in tradesDone) {
var race = tradeManager.getRace(name);
var materials = tradeManager.getMaterials(name);
for (var mat in materials) {
if (!tradeNet[mat]) {tradeNet[mat] = 0;}
tradeNet[mat] -= materials[mat] * tradesDone[name];
}
var meanOutput = tradeManager.getAverageTrade(race);
for (var out in meanOutput) {
var res = craftManager.getResource(out);
if (!tradeNet[out]) {tradeNet[out] = 0;}
tradeNet[out] += (res.maxValue > 0) ? Math.min(meanOutput[out] * tradesDone[name], Math.max(res.maxValue - res.value, 0)) : meanOutput[out] * tradesDone[name];
}
}
cacheManager.pushToCache({'materials': tradeNet, 'timeStamp': game.timer.ticksTotal});
for (var name in tradesDone) {
if (tradesDone[name] > 0) {
tradeManager.trade(name, tradesDone[name]);
}
}
},
miscOptions: function () {
var craftManager = this.craftManager;
var buildManager = this.buildManager;
var optionVals = options.auto.options.items;
AutoEmbassy:
if (optionVals.buildEmbassies.enabled && !!game.diplomacy.races[0].embassyPrices) {
var culture = craftManager.getResource('culture');
if (optionVals.buildEmbassies.subTrigger <= culture.value / culture.maxValue) {
var racePanels = game.diplomacyTab.racePanels;
var cultureVal = craftManager.getValueAvailable('culture', true);
var embassyBulk = {};
var bulkTracker = [];
for (var i = 0; i < racePanels.length; i++) {
if (!racePanels[i].embassyButton) {continue;}
var name = racePanels[i].race.name;
var race = game.diplomacy.get(name);
var priceCoeficient = 1 - game.getEffect("embassyCostReduction");
embassyBulk[name] = {'val': 0, 'basePrice': race.embassyPrices[0].val * priceCoeficient, 'currentEm': race.embassyLevel, 'priceSum': 0, 'race': race};
bulkTracker.push(name);
}
if (bulkTracker.length === 0) {break AutoEmbassy;}
var refreshRequired = false;
while (bulkTracker.length > 0) {
for (var i=0; i < bulkTracker.length; i++) {
var name = bulkTracker[i];
var emBulk = embassyBulk[name];
var nextPrice = emBulk.basePrice * Math.pow(1.15, emBulk.currentEm + emBulk.val + game.getEffect("embassyFakeBought"));
if (nextPrice <= cultureVal) {
cultureVal -= nextPrice;
emBulk.priceSum += nextPrice;
emBulk.val += 1;
refreshRequired = true;
} else {
bulkTracker.splice(i, 1);
i--;
}
}
}
for (var name in embassyBulk) {
var emBulk = embassyBulk[name];
if (emBulk.val === 0) {continue;}
var cultureVal = craftManager.getValueAvailable('culture', true);
if (emBulk.priceSum > cultureVal) {warning('Something has gone horribly wrong.' + [emBulk.priceSum, cultureVal]);}
game.resPool.resources[13].value -= emBulk.priceSum;
emBulk.race.embassyLevel += emBulk.val;
storeForSummary('embassy', emBulk.val);
if (emBulk.val !== 1) {
iactivity('build.embassies', [emBulk.val, emBulk.race.title], 'ks-trade');
} else {
iactivity('build.embassy', [emBulk.val, emBulk.race.title], 'ks-trade');
}
}
if (refreshRequired) {game.ui.render();}
}
}
// fix Cryochamber
if (optionVals.fixCry.enabled && game.time.getVSU("usedCryochambers").val > 0) {
var fixed = 0;
var btn = this.timeManager.manager.tab.vsPanel.children[0].children[0]; //check?
// buyItem will check resources
while (btn.controller.buyItem(btn.model, {}, function(callback) {return callback;})) {
fixed += 1;
}
if (fixed > 0) {
iactivity('act.fix.cry', [fixed], 'ks-fixCry');
storeForSummary('fix.cry', fixed);
}
}
// auto turn on steamworks
if (optionVals._steamworks.enabled) {
var st = game.bld.getBuildingExt('steamworks').meta;
if (st.val && st.on == 0) {
var stButton = buildManager.getBuildButton('steamworks');
stButton.controller.onAll(stButton.model);
}
var re = game.bld.getBuildingExt('reactor').meta;
var ur = game.getResourcePerTick("uranium",true);
if (re.val && re.on == 0 && ur > 0) {
var reButton = buildManager.getBuildButton('reactor');
reButton.controller.onAll(reButton.model);
}
var ma = game.bld.getBuildingExt('magneto').meta;
var oil = game.getResourcePerTick("oil",true);
if (ma.val && ma.on == 0 && oil > 0) {
var maButton = buildManager.getBuildButton('magneto');
maButton.controller.onAll(maButton.model);
}
}
},
// ref: https://github.com/Bioniclegenius/NummonCalc/blob/112f716e2fde9956dfe520021b0400cba7b7113e/NummonCalc.js#L490
getBestUnicornBuilding: function () {
var unicornPasture = 'unicornPasture';
var pastureButton = buildManager.getBuildButton('unicornPasture');
if(typeof pastureButton === 'undefined')
return;
var validBuildings = ['unicornTomb','ivoryTower','ivoryCitadel','skyPalace','unicornUtopia','sunspire'];
var unicornsPerSecond = game.getEffect('unicornsPerTickBase') * game.getTicksPerSecondUI();
var globalRatio = game.getEffect('unicornsGlobalRatio') + 1;
var religionRatio = game.getEffect('unicornsRatioReligion') + 1;
var paragonRatio = game.prestige.getParagonProductionRatio() + 1;
var faithBonus = game.religion.getSolarRevolutionRatio() + 1;
var cycle = 1;
if(game.calendar.cycles[game.calendar.cycle].festivalEffects['unicorns'] != undefined)
if(game.prestige.getPerk('numeromancy').researched && game.calendar.festivalDays)
cycle = game.calendar.cycles[game.calendar.cycle].festivalEffects['unicorns'];
var onZig = Math.max(game.bld.getBuildingExt('ziggurat').meta.on, 1);
var total = unicornsPerSecond * globalRatio * religionRatio * paragonRatio * faithBonus * cycle;
var baseUnicornsPerRift = 500 * (1 + game.getEffect('unicornsRatioReligion') * 0.1);
var riftChanceRatio = 1;
if(game.prestige.getPerk('unicornmancy').researched)
riftChanceRatio *= 1.1;
var baseRift = game.getEffect('riftChance') * riftChanceRatio / (10000 * 2) * baseUnicornsPerRift;
var bestAmoritization = Infinity;
var bestBuilding = '';
var pastureAmor = game.bld.getBuildingExt('unicornPasture').meta.effects['unicornsPerTickBase'] * game.getTicksPerSecondUI();
pastureAmor = pastureAmor * globalRatio * religionRatio * paragonRatio * faithBonus * cycle;
pastureAmor = pastureButton.model.prices[0].val / pastureAmor;
if(pastureAmor < bestAmoritization){
bestAmoritization = pastureAmor;
bestBuilding = unicornPasture;
}
for(var i in this.religionManager.manager.tab.zgUpgradeButtons){
var btn = this.religionManager.manager.tab.zgUpgradeButtons[i];
if(validBuildings.indexOf(btn.id)!=-1){
if(btn.model.visible){
unicornPrice = 0;
for(var j in btn.model.prices){
if(btn.model.prices[j].name=='unicorns')
unicornPrice += btn.model.prices[j].val;
if(btn.model.prices[j].name=='tears')
unicornPrice += btn.model.prices[j].val * 2500 / onZig;
}
var bld = game.religion.getZU(btn.id);
var relBonus = religionRatio;
var riftChance = game.getEffect('riftChance');
for(var j in bld.effects){
if(j == 'unicornsRatioReligion')
relBonus += bld.effects[j]
if(j == 'riftChance')
riftChance += bld.effects[j];
}
var unicornsPerRift = 500 * ((relBonus -1) * 0.1 +1);
var riftBonus = riftChance * riftChanceRatio / (10000 * 2) * unicornsPerRift;
riftBonus -= baseRift;
var amor = unicornsPerSecond * globalRatio * relBonus * paragonRatio * faithBonus * cycle;
amor -= total;
amor = amor + riftBonus;
amor = unicornPrice / amor;
if(amor < bestAmoritization)
if(riftBonus > 0 || relBonus > religionRatio && unicornPrice > 0){
bestAmoritization = amor;
bestBuilding = btn.id;
}
}
}
}
return bestBuilding;
}
};
// Tab Manager
// ===========
var TabManager = function (name) {
this.setTab(name);
};
TabManager.prototype = {
tab: undefined,
render: function () {
if (this.tab && game.ui.activeTabId !== this.tab.tabId) this.tab.render();
return this;
},
setTab: function (name) {
for (var tab in game.tabs) {
if (game.tabs[tab].tabId === name) {
this.tab = game.tabs[tab];
break;
}
}
this.tab ? this.render() : warning('unable to find tab ' + name);
}
};
// Religion manager
// ================
var ReligionManager = function () {
this.manager = new TabManager('Religion');
this.crafts = new CraftManager();
this.bulkManager = new BulkManager();
};
ReligionManager.prototype = {
manager: undefined,
crafts: undefined,
bulkManager: undefined,
build: function (name, variant, amount) {
var build = this.getBuild(name, variant);
var button = this.getBuildButton(name, variant);
if (!button || !button.model.enabled) return;
var amountTemp = amount;
var label = build.label;
amount=this.bulkManager.construct(button.model, button, amount);
if (amount !== amountTemp) {warning(label + ' Amount ordered: '+amountTemp+' Amount Constructed: '+amount);}
if (variant === "s") {
storeForSummary(label, amount, 'faith');
if (amount === 1) {
iactivity('act.sun.discover', [label], 'ks-faith');
} else {
iactivity('act.sun.discovers', [label, amount], 'ks-faith');
}
} else {
storeForSummary(label, amount, 'build');
if (amount === 1) {
iactivity('act.build', [label], 'ks-build');
} else {
iactivity('act.builds', [label, amount], 'ks-build');
}
}
},
getBuild: function (name, variant) {
switch (variant) {
case 'z':
return game.religion.getZU(name);
case 's':
return game.religion.getRU(name);
case 'c':
return game.religion.getTU(name);
}
},
getBuildButton: function (name, variant) {
switch (variant) {
case 'z':
var buttons = this.manager.tab.zgUpgradeButtons;
break;
case 's':
var buttons = this.manager.tab.rUpgradeButtons;
break;
case 'c':
var buttons = this.manager.tab.children[0].children[0].children;
}
var build = this.getBuild(name, variant);
for (var i in buttons) {
var haystack = buttons[i].model.name;
if (haystack.indexOf(build.label) !== -1) {
return buttons[i];
}
}
}
};
// Time manager
// ============
var TimeManager = function () {
this.manager = new TabManager('Time');
this.crafts = new CraftManager();
this.bulkManager = new BulkManager();
};
TimeManager.prototype = {
manager: undefined,
crafts: undefined,
bulkManager: undefined,
build: function (name, variant, amount) {
var build = this.getBuild(name, variant);
var button = this.getBuildButton(name, variant);
if (!button || !button.model.enabled) return;
var amountTemp = amount;
var label = build.label;
amount=this.bulkManager.construct(button.model, button, amount);
if (amount !== amountTemp) {warning(label + ' Amount ordered: '+amountTemp+' Amount Constructed: '+amount);}
storeForSummary(label, amount, 'build');
if (amount === 1) {
iactivity('act.build', [label], 'ks-build');
} else {
iactivity('act.builds', [label, amount], 'ks-build');
}
},
getBuild: function (name, variant) {
if (variant === 'chrono') {
return game.time.getCFU(name);
} else {
return game.time.getVSU(name);
}
},
getBuildButton: function (name, variant) {
if (variant === 'chrono') {
var buttons = this.manager.tab.children[2].children[0].children;
} else {
var buttons = this.manager.tab.children[3].children[0].children;
}
var build = this.getBuild(name, variant);
for (var i in buttons) {
var haystack = buttons[i].model.name;
if (haystack.indexOf(build.label) !== -1) {
return buttons[i];
}
}
}
};
// Upgrade manager
// ============
var UpgradeManager = function () {
this.workManager = new TabManager('Workshop');
this.sciManager = new TabManager('Science');
this.spaManager = new TabManager('Space');
this.crafts = new CraftManager();
};
UpgradeManager.prototype = {
manager: undefined,
crafts: undefined,
build: function (upgrade, variant) {
var button = this.getBuildButton(upgrade, variant);
if (!button || !button.model.enabled) return;
if (variant === 'policy') {
if (game.village.leader != null && button.model.metadata.requiredLeaderJob && game.village.leader.job != button.model.metadata.requiredLeaderJob) {
var jobTitle = game.village.getJob(button.model.metadata.requiredLeaderJob).title;
game.msg($I("msg.policy.wrongLeaderJobForResearch", [button.model.metadata.label, jobTitle]), "important");
return;
} else if (button.model.metadata.name == "transkittenism" && game.bld.getBuildingExt("aiCore").meta.effects["aiLevel"] >= 15) {
game.msg($I("msg.policy.aiNotMerges"),"alert", "ai");
return;
} else if (button.model.metadata.blocked === false) {
for (var i = 0; i < button.model.metadata.blocks.length; i++) {
if (game.science.getPolicy(button.model.metadata.blocks[i]).researched) {
button.model.metadata.blocked = true;
return;
}
}
}
}
//need to simulate a click so the game updates everything properly
button.controller.payPrice(button.model, {}, function() {});
button.controller.onPurchase(button.model, {}, function() {});
game.stats.getStat("totalClicks").val += 1;
var label = upgrade.label;
if (variant === 'workshop') {
storeForSummary(label, 1, 'upgrade');
iactivity('upgrade.upgrade', [label], 'ks-upgrade');
} else if (variant === 'science') {
storeForSummary(label, 1, 'research');
iactivity('upgrade.tech', [label], 'ks-research');
} else if (variant === 'policy') {
iactivity('upgrade.policy', [label]);
}
},
getBuildButton: function (upgrade, variant) {
if (variant === 'workshop') {
var buttons = this.workManager.tab.buttons;
} else if (variant === 'science') {
var buttons = this.sciManager.tab.buttons;
} else if (variant === 'policy') {
var buttons = this.sciManager.tab.policyPanel.children;
}
for (var i in buttons) {
var haystack = buttons[i].model.name;
if (haystack === upgrade.label) {
return buttons[i];
}
}
}
};
// Building manager
// ================
var BuildManager = function () {
this.manager = new TabManager('Bonfire');
this.crafts = new CraftManager();
this.bulkManager = new BulkManager();
};
BuildManager.prototype = {
manager: undefined,
crafts: undefined,
bulkManager: undefined,
build: function (name, stage, amount) {
var build = this.getBuild(name);
var button = this.getBuildButton(name, stage);
if (!button || !button.model.enabled) return;
var amountTemp = amount;
var label = build.meta.label ? build.meta.label : build.meta.stages[stage].label;
amount=this.bulkManager.construct(button.model, button, amount);
if (amount !== amountTemp) {warning(label + ' Amount ordered: '+amountTemp+' Amount Constructed: '+amount);}
storeForSummary(label, amount, 'build');
if (amount === 0) {
// the few build 0 unicornPasture, should render for bldTab when getBestUnicornBuilding return unicornPasture
return;
} else if (amount === 1) {
iactivity('act.build', [label], 'ks-build');
} else {
iactivity('act.builds', [label, amount], 'ks-build');
}
},
getBuild: function (name) {
return game.bld.getBuildingExt(name);
},
getBuildButton: function (name, stage) {
var buttons = this.manager.tab.children;
var build = this.getBuild(name);
var label = typeof stage !== 'undefined' ? build.meta.stages[stage].label : build.meta.label;
for (var i in buttons) {
var haystack = buttons[i].model.name;
if (haystack.indexOf(label) !== -1){
return buttons[i];
}
}
}
};
// Space manager
// ================
var SpaceManager = function () {
this.manager = new TabManager('Space');
this.crafts = new CraftManager();
this.bulkManager = new BulkManager();
};
SpaceManager.prototype = {
manager: undefined,
crafts: undefined,
bulkManager: undefined,
build: function (name, amount) {
var build = this.getBuild(name);
var button = this.getBuildButton(name);
if (!build.unlocked || !button || !button.model.enabled || !options.auto.space.items[name].enabled) return;
var amountTemp = amount;
var label = build.label;
amount=this.bulkManager.construct(button.model, button, amount);
if (amount !== amountTemp) {
warning(label + ' Amount ordered: '+amountTemp+' Amount Constructed: '+amount);
}
storeForSummary(label, amount, 'build');
if (amount === 1) {
iactivity('act.build', [label], 'ks-build');
} else {
iactivity('act.builds', [label, amount], 'ks-build');
}
},
getBuild: function (name) {
return game.space.getBuilding(name);
},
getBuildButton: function (name) {
var panels = this.manager.tab.planetPanels;
for (var panel in panels) {
for (var child in panels[panel].children) {
if (panels[panel].children[child].id === name) return panels[panel].children[child];
}
}
}
};
// Crafting Manager
// ================
var CraftManager = function () {
this.cacheManager = new CacheManager();
};
CraftManager.prototype = {
craft: function (name, amount) {
amount = Math.floor(amount);
if (!name || 1 > amount) return;
if (!this.canCraft(name, amount)) return;
var craft = this.getCraft(name);
var ratio = game.getResCraftRatio(craft.name);
game.craft(craft.name, amount);
var iname = game.resPool.get(name).title;
// determine actual amount after crafting upgrades
amount = (amount * (1 + ratio)).toFixed(2);
storeForSummary(iname, amount, 'craft');
iactivity('act.craft', [game.getDisplayValueExt(amount), iname], 'ks-craft');
},
canCraft: function (name, amount) {
var craft = this.getCraft(name);
var enabled = options.auto.craft.items[name].enabled;
var result = false;
if (craft.unlocked && enabled) {
result = true;
var prices = game.workshop.getCraftPrice(craft);
for (var i in prices) {
var price = prices[i];
var value = this.getValueAvailable(price.name);
if (value < price.val * amount) {
result = false;
}
}
}
return result;
},
getCraft: function (name) {
return game.workshop.getCraft(name);
},
singleCraftPossible: function (name) {
var materials = this.getMaterials(name);
for (var mat in materials) {
if (this.getValueAvailable(mat, true) < materials[mat]) {return false;}
}
return true;
},
getLowestCraftAmount: function (name, limited, limRat, aboveTrigger) {
var amount = Number.MAX_VALUE;
var plateMax = Number.MAX_VALUE;
var materials = this.getMaterials(name);
var craft = this.getCraft(name);
var ratio = game.getResCraftRatio(craft.name);
var trigger = options.auto.craft.trigger;
var optionVal = options.auto.options.enabled && options.auto.options.items.shipOverride.enabled;
// Safeguard if materials for craft cannot be determined.
if (!materials) return 0;
if (name==='steel' && limited && options.auto.craft.items['plate'].enabled) {
var plateRatio=game.getResCraftRatio("plate");
if (this.getValueAvailable('plate')/this.getValueAvailable('steel') < ((plateRatio+1)/125)/((ratio+1)/100)) {
return 0;
}
} else if (name==='plate' && limited && options.auto.craft.items['steel'].enabled) {
var steelRatio=game.getResCraftRatio("steel");
if (game.getResourcePerTick('coal', true) > 0) {
if (this.getValueAvailable('plate')/this.getValueAvailable('steel') > ((ratio+1)/125)/((steelRatio+1)/100)) {
var ironInTime = ((this.getResource('coal').maxValue*trigger - this.getValue('coal'))/game.getResourcePerTick('coal', true))*Math.max(game.getResourcePerTick('iron', true), 0);
plateMax = (this.getValueAvailable('iron') - Math.max(this.getResource('coal').maxValue*trigger - ironInTime,0))/125;
}
}
}
var res = this.getResource(name);
for (var i in materials) {
var delta = undefined;
if (! limited || (this.getResource(i).maxValue > 0 && aboveTrigger) || (name === 'ship' && optionVal && (this.getResource('ship').value < 243)) ) {
// If there is a storage limit, we can just use everything returned by getValueAvailable, since the regulation happens there
delta = this.getValueAvailable(i) / materials[i];
} else {
// Take the currently present amount of material to craft into account
// Currently this determines the amount of resources that can be crafted such that base materials are proportionally distributed across limited resources.
// This base material distribution is governed by limRat "limited ratio" which defaults to 0.5, corresponding to half of the possible components being further crafted.
// If this were another value, such as 0.75, then if you had 10000 beams and 0 scaffolds, 7500 of the beams would be crafted into scaffolds.
delta = limRat * ((this.getValueAvailable(i, true) + (materials[i] / (1 + ratio)) * this.getValueAvailable(res.name, true)) / materials[i]) - (this.getValueAvailable(res.name, true) / (1 + ratio));
}
amount = Math.min(delta,amount,plateMax);
}
// If we have a maximum value, ensure that we don't produce more than
// this value. This should currently only impact wood crafting, but is
// written generically to ensure it works for any craft that produces a
// good with a maximum value.
if (res.maxValue > 0 && amount > (res.maxValue - res.value))
amount = res.maxValue - res.value;
return Math.floor(amount);
},
getMaterials: function (name) {
var materials = {};
var craft = this.getCraft(name);
// Safeguard against craft items that aren't actually available yet.
if (!craft) return;
var prices = game.workshop.getCraftPrice(craft);
for (var i in prices) {
var price = prices[i];
materials[price.name] = price.val;
}
return materials;
},
getTickVal: function (res, preTrade) {
var prod = game.getResourcePerTick(res.name, true);
if (res.craftable) {
var minProd=Number.MAX_VALUE;
var materials = this.getMaterials(res.name);
for (var mat in materials) {
var rat = (1+game.getResCraftRatio(res.name))/materials[mat];
//Currently preTrade is only true for the festival stuff, so including furs from hunting is ideal.
var addProd = this.getTickVal(this.getResource(mat));
minProd = Math.min(addProd * rat, minProd);
}
prod += (minProd!==Number.MAX_VALUE) ? minProd : 0;
}
if (prod <= 0 && (res.name === 'spice' || res.name === 'blueprint')) {return 'ignore';}
if (!preTrade) {prod += this.cacheManager.getResValue(res.name)};
return prod;
},
getAverageHunt: function() {
var output = {};
var hunterRatio = game.getEffect('hunterRatio') + game.village.getEffectLeader('manager', 0);
output['furs'] = 40 + 32.5 * hunterRatio;
output['ivory'] = 50 * Math.min(0.225 + 0.01 * hunterRatio, 0.5) + 40 * hunterRatio * Math.min(0.225 + 0.01 * hunterRatio, 0.5);
output['unicorns'] = 0.05;
if (this.getValue('zebras') >= 10) {
output['bloodstone'] = (this.getValue('bloodstone') === 0) ? 0.05 : 0.0005;
}
if (game.ironWill && game.workshop.get('goldOre').researched) {
output['gold'] = 0.625 + 0.625 * hunterRatio;
}
return output;
},
getResource: function (name) {
if (name === 'slabs') {name = 'slab';} //KG BETA BUGFIX
// for (var i in game.resPool.resources) {
// var res = game.resPool.resources[i];
// if (res.name === name) return res;
// }
var res = game.resPool.get(name);
if (res) return res
warning('unable to find resource ' + name);
return null;
},
getValue: function (name) {
return this.getResource(name).value;
},
getStock: function (name) {
var res = options.auto.resources[name];
var stock = (res && res.enabled) ? res.stock : 0;
return !stock ? 0 : stock;
},
getValueAvailable: function (name, all, typeTrigger) {
var value = this.getValue(name);
var stock = this.getStock(name);
if (!typeTrigger && typeTrigger !== 0) {
var trigger = options.auto.craft.trigger;
}
else {
var trigger = typeTrigger;
}
if ('catnip' === name) {
var pastures = (game.bld.getBuildingExt('pasture').meta.stage === 0) ? game.bld.getBuildingExt('pasture').meta.val: 0;
var aqueducts = (game.bld.getBuildingExt('aqueduct').meta.stage === 0) ? game.bld.getBuildingExt('aqueduct').meta.val: 0;
var resPerTick = this.getPotentialCatnip(true, pastures, aqueducts)
if (resPerTick < 0) stock -= resPerTick * 202 * 5;
}
value = Math.max(value - stock, 0);
// If we have a maxValue, and user hasn't requested all, check
// consumption rate
if (!all && this.getResource(name).maxValue > 0) {
var res = options.auto.resources[name];
var consume = res && res.enabled && (res.consume != undefined) ? res.consume : options.consume;
value -= Math.min(this.getResource(name).maxValue * trigger, value) * (1 - consume);
if ('unobtainium' === name) {
if (value < 1000 && this.getResource(name).value == this.getResource(name).maxValue && this.getResource(name).value>= 1000) {
value = this.getResource(name).value;// fix unobtainium carfting to eludium
}
}
}
return value;
},
getPotentialCatnip: function (worstWeather, pastures, aqueducts) {
var fieldProd = game.getEffect('catnipPerTickBase');
if (worstWeather) {
fieldProd *= 0.1;
fieldProd *= 1 + game.getLimitedDR(game.getEffect("coldHarshness"),1);
if (game.science.getPolicy("communism").researched) {fieldProd = 0;}
} else {
fieldProd *= game.calendar.getWeatherMod({name: "catnip"});
}
var vilProd = (game.village.getResProduction().catnip) ? game.village.getResProduction().catnip * (1 + game.getEffect('catnipJobRatio')) : 0;
var baseProd = fieldProd + vilProd;
var hydroponics = game.space.getBuilding('hydroponics');
var hydroponicsEffect = hydroponics.effects['catnipRatio'];
baseProd *= 1 + game.bld.getBuildingExt('aqueduct').meta.stages[0].effects['catnipRatio'] * aqueducts + hydroponicsEffect * hydroponics.val;
var paragonBonus = (game.challenges.isActive("winterIsComing")) ? 0 : game.prestige.getParagonProductionRatio();
baseProd *= 1 + paragonBonus;
baseProd *= 1 + game.religion.getSolarRevolutionRatio() * (1 + game.bld.pollutionEffects["solarRevolutionPollution"]);
//if (!game.opts.disableCMBR) {baseProd *= (1 + game.getCMBRBonus());}
baseProd *= 1 + (game.getEffect('blsProductionBonus') * game.resPool.get('sorrow').value);
baseProd = game.calendar.cycleEffectsFestival({catnip: baseProd})['catnip'];
baseProd *= 1 + game.bld.pollutionEffects["catnipPollutionRatio"];
var baseDemand = game.village.getResConsumption()['catnip'];
var uniPastures = game.bld.getBuildingExt('unicornPasture').meta.val;
baseDemand *= 1 + (game.getLimitedDR(pastures * -0.005 + uniPastures * -0.0015, 1.0));
if (game.village.sim.kittens.length > 0 && game.village.happiness > 1) {
var happyCon = Math.max(game.village.happiness * (1 + game.getEffect("hapinnessConsumptionRatio")) - 1, 0);
if (game.challenges.isActive("anarchy")) {
baseDemand *= 1 + happyCon * (1 + game.getEffect("catnipDemandWorkerRatioGlobal"));
} else {
baseDemand *= 1 + happyCon * (1 + game.getEffect("catnipDemandWorkerRatioGlobal")) * (1 - game.village.getFreeKittens() / game.village.sim.kittens.length);
}
}
baseProd += baseDemand;
baseProd += game.getResourcePerTickConvertion('catnip');
//Might need to eventually factor in time acceleration using game.timeAccelerationRatio().
return baseProd;
}
};
// Bulk Manager
// ============
var BulkManager = function () {
this.craftManager = new CraftManager();
};
BulkManager.prototype = {
craftManager: undefined,
bulk: function (builds, metaData, trigger, source) {
var bList = [];
var countList = [];
var counter = 0;
for (var name in builds) {
var build = builds[name];
var data = metaData[name];
if (!build.enabled) {continue;}
if (data.tHidden === true) {continue;}
if (data.rHidden === true) {continue;}
if ((data.rHidden === undefined) && !data.unlocked) {continue;}
if (name === 'cryochambers' && (game.time.getVSU('usedCryochambers').val > 0
|| game.bld.getBuildingExt('chronosphere').meta.val <= data.val)) {continue;}
if (name === 'ressourceRetrieval' && data.val >= 100) {continue;}
var prices = (data.stages) ? data.stages[data.stage].prices : data.prices;
if (build.variant === 's') {prices = game.village.getEffectLeader("wise", dojo.clone(data.prices));}
var priceRatio = this.getPriceRatio(data, source);
if (!this.singleBuildPossible(data, prices, priceRatio, source)) {continue;}
var require = !build.require ? false : this.craftManager.getResource(build.require);
if (!require || trigger <= require.value / require.maxValue) {
if (typeof(build.stage) !== 'undefined' && build.stage !== data.stage) {
continue;
}
bList.push(new Object());
bList[counter].id = name;
bList[counter].label = build.label;
bList[counter].name = build.name;
bList[counter].stage = build.stage;
bList[counter].variant = build.variant;
countList.push(new Object());
countList[counter].id = name;
countList[counter].name = build.name;
countList[counter].count = 0;
countList[counter].spot = counter;
// countList[counter].prices = prices;
countList[counter].prices = [];
var pricesDiscount = game.getLimitedDR(game.getEffect(name + "CostReduction"), 1);
var priceModifier = 1 - pricesDiscount;
for (var i in prices) {
var resPriceDiscount = game.getLimitedDR(game.getEffect(prices[i].name+"CostReduction"), 1);
var resPriceModifier = 1 - resPriceDiscount;
countList[counter].prices.push({
val: prices[i].val * priceModifier * resPriceModifier,
name: prices[i].name
});
}
countList[counter].priceRatio = priceRatio;
countList[counter].source = source;
countList[counter].limit = build.max || 0;
countList[counter].val = data.val;
counter++;
}
}
if (countList.length === 0) {return;}
var tempPool = new Object();
for (var res in game.resPool.resources) {
tempPool[game.resPool.resources[res].name]=game.resPool.resources[res].value;
}
for (var res in tempPool) {tempPool[res] = this.craftManager.getValueAvailable(res, true);}
var k = 0;
while (countList.length !== 0) {
bulkLoop:
for (var j = 0; j < countList.length; j++) {
var build = countList[j];
var data = metaData[build.id];
var prices = build.prices;
var priceRatio = build.priceRatio;
var source = build.source;
for (var p = 0; p < prices.length; p++) {
var spaceOil = false;
var cryoKarma = false;
if (source && source === 'space' && prices[p].name === 'oil') {
spaceOil = true;
var oilPrice = prices[p].val * (1 - game.getLimitedDR(game.getEffect('oilReductionRatio'), 0.75));
} else if (build.id === 'cryochambers' && prices[p].name === 'karma') {
cryoKarma = true;
var karmaPrice = prices[p].val * (1 - game.getLimitedDR(0.01 * game.prestige.getBurnedParagonRatio(), 1.0));
}
if (spaceOil) {
var nextPriceCheck = (tempPool['oil'] < oilPrice * Math.pow(1.05, k + data.val));
} else if (cryoKarma) {
var nextPriceCheck = (tempPool['karma'] < karmaPrice * Math.pow(priceRatio, k + data.val));
} else {
var nextPriceCheck = (tempPool[prices[p].name] < prices[p].val * Math.pow(priceRatio, k + data.val));
}
if (nextPriceCheck || (data.noStackable && (k + data.val)>=1) || (build.id === 'ressourceRetrieval' && k + data.val >= 100)
|| (build.id === 'cryochambers' && game.bld.getBuildingExt('chronosphere').meta.val <= k + data.val)) {
for (var p2 = 0; p2 < p; p2++) {
if (source && source === 'space' && prices[p2].name === 'oil') {
var oilPriceRefund = prices[p2].val * (1 - game.getLimitedDR(game.getEffect('oilReductionRatio'), 0.75));
tempPool['oil'] += oilPriceRefund * Math.pow(1.05, k + data.val);
} else if (build.id === 'cryochambers' && prices[p2].name === 'karma') {
var karmaPriceRefund = prices[p2].val * (1 - game.getLimitedDR(0.01 * game.prestige.getBurnedParagonRatio(), 1.0));
tempPool['karma'] += karmaPriceRefund * Math.pow(priceRatio, k + data.val);
} else {
var refundVal = prices[p2].val * Math.pow(priceRatio, k + data.val);
tempPool[prices[p2].name] += (prices[p2].name === 'void') ? Math.ceil(refundVal) : refundVal;
}
}
if (build.limit && build.limit != -1) {
build.count = Math.max(0, Math.min(build.count, (build.limit - build.val)));
}
bList[countList[j].spot].count = countList[j].count;
countList.splice(j, 1);
j--;
continue bulkLoop;
}
if (spaceOil) {
tempPool['oil'] -= oilPrice * Math.pow(1.05, k + data.val);
} else if (cryoKarma) {
tempPool['karma'] -= karmaPrice * Math.pow(priceRatio, k + data.val);
} else {
var pVal = prices[p].val * Math.pow(priceRatio, k + data.val);
tempPool[prices[p].name] -= (prices[p].name === 'void') ? Math.ceil(pVal) : pVal;
}
}
countList[j].count++;
}
k++;
}
return bList;
},
construct: function (model, button, amount) {
var meta = model.metadata;
var counter = 0;
if (typeof meta.limitBuild == "number" && meta.limitBuild - meta.val < amount) {
amount = meta.limitBuild - meta.val;
}
if (model.enabled && button.controller.hasResources(model) || game.devMode ) {
while (button.controller.hasResources(model) && amount > 0) {
model.prices=button.controller.getPrices(model);
button.controller.payPrice(model);
button.controller.incrementValue(model);
counter++;
amount--;
}
if (meta.breakIronWill) {game.ironWill = false;}
if (meta.unlocks) {game.unlock(meta.unlocks);}
if (meta.upgrades) {game.upgrade(meta.upgrades);}
}
return counter;
},
getPriceRatio: function (data, source) {
var ratio = (!data.stages) ? data.priceRatio : (data.priceRatio || data.stages[data.stage].priceRatio);
var ratioDiff = 0;
if (source && source === 'bonfire') {
ratioDiff = game.getEffect(data.name + "PriceRatio") +
game.getEffect("priceRatio") +
game.getEffect("mapPriceReduction");
ratioDiff = game.getLimitedDR(ratioDiff, ratio - 1);
}
return ratio + ratioDiff;
},
singleBuildPossible: function (data, prices, priceRatio, source) {
var pricesDiscount = game.getLimitedDR(game.getEffect(data.name + "CostReduction"), 1);
var priceModifier = 1 - pricesDiscount;
for (var price in prices) {
var resPriceDiscount = game.getLimitedDR(game.getEffect(prices[price].name+"CostReduction"), 1);
var resPriceModifier = 1 - resPriceDiscount;
var rightPrice = prices[price].val * priceModifier * resPriceModifier;
if (source && source === 'space' && prices[price].name === 'oil') {
var oilPrice = rightPrice * (1 - game.getLimitedDR(game.getEffect('oilReductionRatio'), 0.75));
if (this.craftManager.getValueAvailable('oil', true) < oilPrice * Math.pow(1.05, data.val)) {return false;}
} else if (data.name === 'cryochambers' && prices[price].name === 'karma') {
var karmaPrice = rightPrice * (1 - game.getLimitedDR(0.01 * game.prestige.getBurnedParagonRatio(), 1.0));
if (this.craftManager.getValueAvailable('karma', true) < karmaPrice * Math.pow(priceRatio, data.val)) {return false;}
} else {
if (this.craftManager.getValueAvailable(prices[price].name, true) < rightPrice * Math.pow(priceRatio, data.val)) {return false;}
}
}
return true;
}
};
// Trading Manager
// ===============
var TradeManager = function () {
this.craftManager = new CraftManager();
this.manager = new TabManager('Trade');
};
TradeManager.prototype = {
craftManager: undefined,
manager: undefined,
trade: function (name, amount) {
if (!name || 1 > amount) {warning('KS trade checks are not functioning properly, please create an issue on the github page.');}
var race = this.getRace(name);
var button = this.getTradeButton(race.name);
if (!button.model.enabled || !options.auto.trade.items[name].enabled) {warning('KS trade checks are not functioning properly, please create an issue on the github page.');}
game.diplomacy.tradeMultiple(race, amount);
storeForSummary(race.title, amount, 'trade');
iactivity('act.trade', [amount, ucfirst(race.title)], 'ks-trade');
},
getProfitability: function (name) {
var race = this.getRace(name);
var materials = this.getMaterials(name);
var cost = 0;
for (var mat in materials) {
var tick = this.craftManager.getTickVal(this.craftManager.getResource(mat));
if (tick <= 0) {return false;}
cost += materials[mat]/tick;
}
var output = this.getAverageTrade(race);
var profit = 0;
for (var prod in output) {
var res = this.craftManager.getResource(prod);
var tick = this.craftManager.getTickVal(res);
if (tick === 'ignore') {continue;}
if (tick <= 0) {return true;}
profit += (res.maxValue > 0) ? Math.min(output[prod], Math.max(res.maxValue - res.value, 0))/tick : output[prod]/tick;
}
return (cost <= profit);
},
getAverageTrade: function (race) {
// standingRatio
// var standRat = game.getEffect("standingRatio");
var standRat = game.getEffect("standingRatio") + game.diplomacy.calculateStandingFromPolicies(race.name, game);
// standRat += (game.prestige.getPerk("diplomacy").researched) ? 10 : 0;
// raceRatio
var rRatio = 1 + race.energy * 0.02;
// tradeRatio
// var tRatio = 1 + game.diplomacy.getTradeRatio();
var tRatio = 1 + game.diplomacy.getTradeRatio() + game.diplomacy.calculateTradeBonusFromPolicies(race.name, game) + game.challenges.getChallenge("pacifism").getTradeBonusEffect(game);
// var successRat = (race.attitude === "hostile") ? Math.min(race.standing + standRat/100, 1) : 1;
// var bonusRat = (race.attitude === "friendly") ? Math.min(race.standing + standRat/200, 1) : 0;
// ref: var failedTradeAmount = race.standing < 0 ? this.game.math.binominalRandomInteger(totalTradeAmount, -(race.standing + standingRatio)) : 0;
// ref: var successfullTradeAmount = totalTradeAmount - failedTradeAmount;
var failedRat = (race.standing < 0) ? (race.standing + standRat) : 0;
var successRat = (failedRat < 0) ? (1 + failedRat) : 1;
var bonusRat = (race.standing > 0) ? Math.min(race.standing + standRat / 2, 1) : 0;
var output = {};
for (var s in race.sells) {
var item = race.sells[s];
if (!this.isValidTrade(item, race)) { continue; }
var resource = this.craftManager.getResource(item.name);
var mean = 0;
var tradeChance = (race.embassyPrices) ? item.chance * (1 + game.getLimitedDR(0.01 * race.embassyLevel, 0.75)) : item.chance;
var sRatio = (!item.seasons) ? 1 : 1 + item.seasons[game.calendar.getCurSeason().name];
var normBought = (successRat - bonusRat) * Math.min(tradeChance / 100, 1);
var normBonus = bonusRat * Math.min(tradeChance / 100, 1);
mean = (normBought + 1.25 * normBonus) * item.value * rRatio * sRatio * tRatio;
output[item.name] = mean;
}
if (race.name == "zebras") {
var shipCount = game.resPool.get("ship").value;
var titanProb = Math.min(0.15 + shipCount * 0.0035, 1);
var titanRat = 1.5 + shipCount * 0.03;
var zebraRelationModifierTitanium = game.getEffect("zebraRelationModifier") * game.bld.getBuildingExt("tradepost").meta.effects["tradeRatio"];
mean = titanRat * successRat * titanProb * (1 + zebraRelationModifierTitanium);
output['titanium'] = mean;
}
var spiceChance = (race.embassyPrices) ? 0.35 * (1 + 0.01 * race.embassyLevel) : 0.35;
var spiceTradeAmount = successRat * Math.min(spiceChance, 1);
output['spice'] = 25 * spiceTradeAmount + 50 * spiceTradeAmount * tRatio / 2;
output['blueprint'] = 0.1 * successRat;
return output;
},
isValidTrade: function (item, race) {
return (!(item.minLevel && race.embassyLevel < item.minLevel) && (game.resPool.get(item.name).unlocked || item.name === 'titanium' || item.name === 'uranium' || race.name === 'leviathans'));
},
getLowestTradeAmount: function (name, limited, trigConditions) {
var amount = undefined;
var highestCapacity = undefined;
var materials = this.getMaterials(name);
var race = this.getRace(name);
for (var i in materials) {
if (i === "manpower") {
var manpowerValue = Math.max(this.craftManager.getValueAvailable(i, true) - 100, 0);
var total = manpowerValue / materials[i];
} else {
var total = this.craftManager.getValueAvailable(i, limited, options.auto.trade.trigger) / materials[i];
}
amount = (amount === undefined || total < amount) ? total : amount;
}
amount = Math.floor(amount);
if (amount === 0) {return 0;}
if (race === null || options.auto.trade.items[name].allowcapped) return amount;
// Loop through the items obtained by the race, and determine
// which good has the most space left. Once we've determined this,
// reduce the amount by this capacity. This ensures that we continue to trade
// as long as at least one resource has capacity, and we never over-trade.
var tradeOutput = this.getAverageTrade(race);
for (var s in race.sells) {
var item = race.sells[s];
var resource = this.craftManager.getResource(item.name);
var max = 0;
// No need to process resources that don't cap
if (!resource.maxValue) continue;
max = tradeOutput[item.name];
var capacity = Math.max((resource.maxValue - resource.value) / max, 0);
highestCapacity = (capacity < highestCapacity) ? highestCapacity : capacity;
}
// We must take the ceiling of capacity so that we will trade as long
// as there is any room, even if it doesn't have exact space. Otherwise
// we seem to starve trading altogether.
highestCapacity = Math.ceil(highestCapacity);
if (highestCapacity === 0) {return 0;}
// Now that we know the most we *should* trade for, check to ensure that
// we trade for our max cost, or our max capacity, whichever is lower.
// This helps us prevent trading for resources we can't store. Note that we
// essentially ignore blueprints here. In addition, if highestCapacity was never set,
// then we just
amount = (highestCapacity < amount) ? Math.max(highestCapacity - 1, 1) : amount;
return Math.floor(amount);
},
getMaterials: function (name) {
var materials = {
manpower: 50 - game.getEffect("tradeCatpowerDiscount"),
gold: 15 - game.getEffect("tradeGoldDiscount")
};
if (name === undefined)
return materials;
var prices = this.getRace(name).buys;
for (var i in prices) {
var price = prices[i];
materials[price.name] = price.val;
}
return materials;
},
getRace: function (name) {
if (name === undefined)
return null;
else
return game.diplomacy.get(name);
},
getTradeButton: function (race) {
for (var i in this.manager.tab.racePanels) {
var panel = this.manager.tab.racePanels[i];
if (panel.race.name === race) return panel.tradeBtn;
}
},
singleTradePossible: function (name) {
var materials = this.getMaterials(name);
for (var mat in materials) {
if (this.craftManager.getValueAvailable(mat, true) < materials[mat]) {return false;}
}
return true;
}
};
// Cache Manager
// ===============
var CacheManager = function () {};
CacheManager.prototype = {
pushToCache: function (data) {
var cache = options.auto.cache.cache;
var cacheSum = options.auto.cache.cacheSum;
var materials = data['materials'];
var currentTick = game.timer.ticksTotal;
cache.push(data);
for (var mat in materials) {
if (!cacheSum[mat]) {cacheSum[mat] = 0;}
cacheSum[mat] += materials[mat];
}
for (var i = 0; i < cache.length; i++) {
var oldData = cache[i];
if (cache.length > 10000) {
var oldMaterials = oldData['materials'];
for (var mat in oldMaterials) {
if (!cacheSum[mat]) {cacheSum[mat] = 0;}
cacheSum[mat] -= oldMaterials[mat];
}
cache.shift();
i--;
} else {
return;
}
}
},
getResValue: function (res) {
var cache = options.auto.cache.cache;
if (cache.length === 0) {return 0;}
var cacheSum = options.auto.cache.cacheSum;
if (!cacheSum[res]) {return 0;}
var currentTick = game.timer.ticksTotal;
var startingTick = cache[0].timeStamp;
return (cacheSum[res] / (currentTick - startingTick));
}
};
// ==============================
// Configure overall page display
// ==============================
var right = $('#rightColumn');
var addRule = function (rule) {
var sheets = document.styleSheets;
sheets[0].insertRule(rule, 0);
};
var defaultSelector = 'body[data-ks-style]:not(.scheme_sleek)';
addRule('body {' // low priority. make sure it can be covered by the theme
+ 'font-family: monospace;'
+ 'font-size: 12px;'
+ '}');
addRule(defaultSelector + ' #game {'
// + 'font-family: monospace;'
// + 'font-size: 12px;'
+ 'min-width: 1300px;'
+ 'top: 32px;'
+ '}');
// addRule(defaultSelector + ' {'
// + 'font-family: monospace;'
// + 'font-size: 12px;'
// + '}');
addRule(defaultSelector + ' .column {'
+ 'min-height: inherit;'
+ 'max-width: inherit !important;'
+ 'padding: 1%;'
+ 'margin: 0;'
+ 'overflow-y: auto;'
+ '}');
addRule(defaultSelector + ' #leftColumn {'
+ 'height: 92%;'
+ 'width: 26%;'
+ '}');
addRule(defaultSelector + ' #midColumn {'
+ 'margin-top: 1% !important;'
+ 'height: 90%;'
+ 'width: 48%;'
+ '}');
addRule(defaultSelector + ' #rightColumn {'
+ 'overflow-y: auto;'
+ 'height: 92%;'
+ 'width: 19%;'
+ '}');
addRule('body #gamePageContainer #game #rightColumn {'
+ 'overflow-y: auto;'
+ '}');
// addRule(defaultSelector + ' #gameLog .msg {'
// + 'display: block;'
// + '}');
addRule(defaultSelector + ' #gameLog {'
+ 'overflow-y: hidden !important;'
+ 'width: 100% !important;'
+ 'padding-top: 5px !important;'
+ '}');
addRule(defaultSelector + ' #resContainer .maxRes {'
+ 'color: #676766;'
+ '}');
addRule(defaultSelector + ' #game .btn {'
+ 'border-radius: 0px;'
+ 'font-family: monospace;'
+ 'font-size: 12px !important;'
+ 'margin: 0 5px 7px 0;'
+ 'width: 290px;'
+ '}');
addRule(defaultSelector + ' #game .map-viewport {'
+ 'height: 340px;'
+ 'max-width: 500px;'
+ 'overflow: visible;'
+ '}');
addRule(' #game .map-dashboard {'
+ 'height: 120px;'
+ 'width: 292px;'
+ '}');
addRule('#ks-options ul {'
+ 'list-style: none;'
+ 'margin: 0 0 5px;'
+ 'padding: 0;'
+ '}');
addRule('#ks-options ul:after {'
+ 'clear: both;'
+ 'content: " ";'
+ 'display: block;'
+ 'height: 0;'
+ '}');
addRule('#ks-options ul li {'
+ 'display: block;'
+ 'float: left;'
+ 'width: 100%;'
+ '}');
addRule('#ks-options #toggle-list-resources .stockWarn *,'
+ '#ks-options #toggle-reset-list-resources .stockWarn * {'
+ 'color: ' + options.stockwarncolor + ';'
+ '}');
addRule('.right-tab {'
+ 'height: unset !important;'
+ '}');
// Local Storage
// =============
var kittenStorageVersion = 3;
var kittenStorage = {
version: kittenStorageVersion,
toggles: {},
items: {},
resources: {},
triggers: {},
reset: {
reset: false,
times: 0,
paragonLastTime: 0,
pargonTotal: 0,
karmaLastTime: 0,
karmaTotal: 0
},
policies: []
};
var initializeKittenStorage = function () {
$("#items-list-build, #items-list-craft, #items-list-trade").find("input[id^='toggle-']").each(function () {
kittenStorage.items[$(this).attr("id")] = $(this).prop("checked");
});
saveToKittenStorage();
};
var saveToKittenStorage = function () {
kittenStorage.toggles = {
build: options.auto.build.enabled,
space: options.auto.space.enabled,
craft: options.auto.craft.enabled,
upgrade: options.auto.upgrade.enabled,
trade: options.auto.trade.enabled,
faith: options.auto.faith.enabled,
time: options.auto.time.enabled,
timeCtrl: options.auto.timeCtrl.enabled,
distribute: options.auto.distribute.enabled,
options: options.auto.options.enabled,
filter: options.auto.filter.enabled
};
kittenStorage.resources = options.auto.resources;
kittenStorage.triggers = {
faith: options.auto.faith.trigger,
time: options.auto.time.trigger,
build: options.auto.build.trigger,
space: options.auto.space.trigger,
craft: options.auto.craft.trigger,
trade: options.auto.trade.trigger
};
kittenStorage.policies = options.policies;
localStorage['cbc.kitten-scientists'] = JSON.stringify(kittenStorage);
};
var loadFromKittenStorage = function () {
var saved = JSON.parse(localStorage['cbc.kitten-scientists'] || 'null');
if (!saved || saved.version > kittenStorageVersion) {
initializeKittenStorage();
return;
}
if (saved.version == 1) {
saved.version = 2;
saved.reset = {
reset: false,
times: 0,
paragonLastTime: 0,
pargonTotal: 0,
karmaLastTime: 0,
karmaTotal: 0
};
}
if (saved.version == 2) {
saved.version = 3;
saved.policies = [];
}
if (saved.version == kittenStorageVersion) {
kittenStorage = saved;
if (saved.toggles) {
for (var toggle in saved.toggles) {
if (toggle !== 'engine' && options.auto[toggle]) {
options.auto[toggle].enabled = !!saved.toggles[toggle];
var el = $('#toggle-' + toggle);
el.prop('checked', options.auto[toggle].enabled);
}
}
}
for (var item in kittenStorage.items) {
var value = kittenStorage.items[item];
var el = $('#' + item);
var option = el.data('option');
var name = item.split('-');
if (option === undefined) {
delete kittenStorage.items[item];
continue;
}
if (name[0] == 'set') {
el[0].title = value;
if (name[name.length -1] == 'max') {
el.text(i18n('ui.max', [value]));
} else if (name[name.length -1] == 'min') {
el.text(i18n('ui.min', [value]));
}
} else {
el.prop('checked', value);
}
if (name.length == 2) {
option.enabled = value;
} else if (name[1] == 'reset' && name.length >= 4) {
var type = name[2];
var itemName = name[3];
switch (name[0]) {
case 'toggle':
options.auto[type].items[itemName].checkForReset = value;
break;
case 'set':
options.auto[type].items[itemName].triggerForReset = value;
break;
}
} else {
if (name[1] == 'limited') {
option.limited = value;
} else if (name[1] == 'leaderJob') {
option[name[1]] = name[2];
} else if (name[1] == 'leaderTrait') {
option[name[1]] = name[2];
} else {
option[name[2]] = value;
}
}
}
var resourcesList = $("#toggle-list-resources");
var resetList = $("#toggle-reset-list-resources");
for (var resource in kittenStorage.resources) {
var res = kittenStorage.resources[resource];
if (res.enabled) {
if ($('#resource-' + resource).length === 0)
resourcesList.append(addNewResourceOption(resource));
if ('stock' in res) setStockValue(resource, res.stock);
if ('consume' in res) setConsumeRate(resource, res.consume);
}
if (res.checkForReset) {
if ($('#resource-reset-' + resource).length === 0)
resetList.append(addNewResourceOption(resource, undefined, true));
if ('stockForReset' in res) setStockValue(resource, res.stockForReset ? res.stockForReset : Infinity, true);
}
}
if (saved.triggers) {
options.auto.faith.trigger = saved.triggers.faith;
options.auto.time.trigger = saved.triggers.time;
options.auto.build.trigger = saved.triggers.build;
options.auto.space.trigger = saved.triggers.space;
options.auto.craft.trigger = saved.triggers.craft;
options.auto.trade.trigger = saved.triggers.trade;
$('#trigger-faith')[0].title = options.auto.faith.trigger;
$('#trigger-time')[0].title = options.auto.time.trigger;
$('#trigger-build')[0].title = options.auto.build.trigger;
$('#trigger-space')[0].title = options.auto.space.trigger;
$('#trigger-craft')[0].title = options.auto.craft.trigger;
$('#trigger-trade')[0].title = options.auto.trade.trigger;
}
options.policies = saved.policies;
}
};
// Add options element
// ===================
var ucfirst = function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
var roundToTwo = function (n) {
return +(Math.round(n + "e+2") + "e-2")
};
var setStockWarning = function(name, value, forReset=false) {
// simplest way to ensure it doesn't stick around too often; always do
// a remove first then re-add only if needed
var path = forReset ? '#resource-reset-'+name : '#resource-'+name;
$(path).removeClass("stockWarn");
var maxValue = game.resPool.resources.filter(i => i.name == name)[0].maxValue;
if ((value > maxValue && !(maxValue === 0)) || value === Infinity) $(path).addClass("stockWarn");
}
var setStockValue = function (name, value, forReset=false) {
var n = Number(value);
if (isNaN(n) || n < 0) {
warning('ignoring non-numeric or invalid stock value ' + value);
return;
}
if (!options.auto.resources[name]) options.auto.resources[name] = {};
if (forReset) {
var path = '#resource-reset-' + name + ' #stock-value-' + name;
n = n<0 ? Infinity : n;
options.auto.resources[name].checkForReset = true;
options.auto.resources[name].stockForReset = n;
} else {
var path = '#resource-' + name + ' #stock-value-' + name;
options.auto.resources[name].enabled = true;
options.auto.resources[name].stock = n;
}
$(path).text(i18n('resources.stock', [n === Infinity ? '∞' : game.getDisplayValueExt(n)]));
setStockWarning(name, n, forReset);
};
var setConsumeRate = function (name, value) {
var n = parseFloat(value);
if (isNaN(n) || n < 0.0 || n > 1.0) {
warning('ignoring non-numeric or invalid consume rate ' + value);
return;
}
if (!options.auto.resources[name]) options.auto.resources[name] = {};
options.auto.resources[name].consume = n;
$('#consume-rate-' + name).text(i18n('resources.consume', [n.toFixed(2)]));
};
var removeResourceControl = function (name, forReset=false) {
var opt = options.auto.resources[name];
if (forReset)
opt.checkForReset = false;
else
opt.enabled = false;
if (!opt.enabled && !opt.checkForReset)
delete options.auto.resources[name];
};
var addNewResourceOption = function (name, title, forReset=false) {
title = title || game.resPool.get(name).title || ucfirst(name);
var res = options.auto.resources[name];
if (forReset && res && res.stockForReset)
var stock = res.stockForReset;
else if (!forReset && res && res.stock)
var stock = res.stock;
else
var stock = 0;
var consume = res && (res.consume != undefined) ? res.consume : options.consume;
var container = $('