Last active
November 29, 2021 06:49
-
-
Save irisfofs/a6aa37f1e9bf2994a03ea7dace959e70 to your computer and use it in GitHub Desktop.
Quick scripts to help play kittens game because life is short
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SECONDS = 1e3; | |
MINUTES = 60 * SECONDS; | |
HOURS = 60 * MINUTES; | |
VERBOSE_AUTO = true; | |
autocraftNames = ['steel', 'plate']; | |
mediumCraftNames = ['kerosene', 'thorium', 'eludium']; | |
slowCraftNames = ['beam', 'slab', 'parchment', 'manuscript']; | |
scienceCraftNames = ['parchment', 'manuscript', 'compendium']; | |
religionBuyNames = ['Black Pyramid', 'Black Nexus', 'Black Core']; | |
partialCraftNames = ['concrete', 'alloy', 'gear']; | |
autobuyNames = [ | |
'Workshop', | |
'Hut', | |
'Log House', | |
// 'Unic. Pasture', | |
// 'Observatory', | |
'Lumber Mill', | |
'Mine', | |
'Library', | |
'Academy', | |
'Temple', | |
'Tradepost', | |
'Smelter', | |
'Reactor', | |
'Factory', | |
'Accelerator', | |
'Calciner', | |
'Mansion', | |
'Chapel', | |
'Broadcast Tower', | |
'Solar Farm', | |
'Hydro Plant', | |
// 'Harbour', | |
'Mint', | |
'Barn', | |
// 'Magneto', | |
// 'Steamworks', | |
]; | |
spaceBuyNames = [ | |
'Space Elevator', 'Lunar Outpost', 'Orbital Array', 'Research Vessel', | |
'Sunlifter', 'Space Beacon' | |
]; | |
autoTradeNames = ['Leviathans']; | |
function getOrCreateAutomation() { | |
try { | |
return Automation; | |
} catch (e) { | |
return class Automation { | |
constructor(timeout, runFn) { | |
this.timeout = timeout; | |
this.lastRun = 0; | |
this.fn = runFn; | |
this.enabled = true; | |
this.switchBackTo = ''; | |
this.switchTo = ''; | |
} | |
run() { | |
this.lastRun = Date.now(); | |
if (this.switchTo && this.switchBackTo) { | |
switchTab(this.switchTo); | |
setTimeout(() => { | |
this.fn(); | |
setTimeout(() => switchTab(this.switchBackTo), 100); | |
}, 100); | |
} else { | |
this.fn(); | |
} | |
} | |
timeLeft() { | |
return this.timeout - (Date.now() - this.lastRun); | |
} | |
disable() { | |
this.enabled = false; | |
} | |
enable() { | |
this.enabled = true; | |
} | |
}; | |
} | |
} | |
Automation = getOrCreateAutomation(); | |
function getOrCreateCraftAutomation() { | |
try { | |
return CraftAutomation; | |
} catch (e) { | |
return class CraftAutomation extends Automation { | |
constructor(timeout, craftNames) { | |
super(timeout, () => craftResourcesByName(window[this.craftNames])); | |
this.craftNames = craftNames; | |
} | |
toString() { | |
const enabledStr = this.enabled ? '[E]' : '[ ]'; | |
const timeout = `[${this.timeout / SECONDS}s]`; | |
return `${enabledStr}${timeout} ${this.craftNames}:\n${ | |
prettyPrintArr(window[this.craftNames])}`; | |
} | |
} | |
} | |
} | |
CraftAutomation = getOrCreateCraftAutomation(); | |
try { | |
console.log(`Clearing runAllTimeout ${runAllTimeout}`); | |
clearTimeout(runAllTimeout); | |
} catch (e) { | |
} | |
automations = new Map(); | |
automations.set('observe', new Automation(10 * SECONDS, () => { | |
const observeBtn = document.getElementById('observeBtn'); | |
if (observeBtn) observeBtn.click(); | |
})); | |
automations.set( | |
'trade', new Automation(100 * SECONDS, () => tradeByName(autoTradeNames))); | |
automations.set( | |
'buyBuilding', | |
new Automation(2 * SECONDS, () => buyBuildingsByName(autobuyNames))); | |
automations.set( | |
'buySpace', | |
new Automation(51 * SECONDS, () => buySpaceByName(spaceBuyNames))); | |
automations.get('buySpace').switchTo = 'Space'; | |
automations.get('buySpace').switchBackTo = 'Trade'; | |
automations.set( | |
'buyReligion', | |
new Automation(31 * SECONDS, () => buyReligionByName(religionBuyNames))); | |
automations.get('buyReligion').switchTo = 'Religion'; | |
automations.get('buyReligion').switchBackTo = 'Trade'; | |
automations.set( | |
'titaniumTrade', | |
new Automation(11 * SECONDS, () => tradeByName(['Zebras']))); | |
automations.get('titaniumTrade').switchTo = 'Trade'; | |
automations.get('titaniumTrade').switchBackTo = 'Bonfire'; | |
automations.set('feedElders', new Automation(1 * MINUTES, () => { | |
if (game.resPool.get('necrocorn').value > 0) { | |
const feedBtn = | |
Array | |
.from(document.querySelectorAll('.trade-race .btn')) | |
.find((el) => el.innerText.includes('Feed elders')); | |
if (feedBtn) feedBtn.click(); | |
} | |
})); | |
automations.set('buildCraftCombo', new Automation(5 * SECONDS, () => { | |
buyBuildingsByName(autobuyNames).then(() => { | |
automations.get('slowCraft').run(); | |
}); | |
})); | |
automations.set('craft', new CraftAutomation(15 * SECONDS, 'autocraftNames')); | |
automations.set( | |
'mediumCraft', new CraftAutomation(3 * MINUTES, 'mediumCraftNames')); | |
automations.set( | |
'slowCraft', new CraftAutomation(7 * SECONDS, 'slowCraftNames')); | |
automations.set( | |
'scienceCraft', new CraftAutomation(15 * SECONDS, 'scienceCraftNames')); | |
automations.set( | |
'partialCraft', new CraftAutomation(30 * SECONDS, 'partialCraftNames')); | |
automations.get('partialCraft').fn = function() { | |
craftResourcesByNameTenPercent(window[this.craftNames]); | |
}; | |
automations.set( | |
'hunt', | |
new Automation( | |
2 * SECONDS, | |
() => document.querySelector('#fastHuntContainer > a').click())); | |
automations.set( | |
'praise', | |
new Automation( | |
1 * MINUTES, | |
() => document.querySelector('#fastPraiseContainer > a').click())); | |
function craftResourcesByName(names) { | |
const resourceRows = | |
Array.from(document.querySelectorAll('.craftTable .resourceRow')); | |
names.forEach((name) => { | |
const row = resourceRows.find((row) => row.innerText.includes(name)); | |
const target = row && row.querySelector('td:last-child a'); | |
if (target) target.click(); | |
}); | |
} | |
function craftResourcesByNameTenPercent(names) { | |
const resourceRows = | |
Array.from(document.querySelectorAll('.craftTable .resourceRow')); | |
names.forEach((name) => { | |
const row = resourceRows.find((row) => row.innerText.includes(name)); | |
const target = row && row.querySelector('td:nth-child(5) a'); | |
if (target) target.click(); | |
}); | |
} | |
// Returns a Promise for when it's done trying to buy every building. | |
function buyBuildingsByName(names) { | |
const itr = names.entries(); | |
const buildingButtonContainer = document.querySelector('.bldGroupContainer'); | |
if (!buildingButtonContainer) return Promise.resolve(); | |
let resolve; | |
const boughtAllPromise = new Promise((res, rej) => { | |
resolve = res; | |
}); | |
const helper = () => { | |
const val = itr.next(); | |
if (val.done) { | |
resolve(); | |
return; | |
} | |
buyBuildingByName(val.value[1], buildingButtonContainer); | |
setTimeout(helper, 0); | |
}; | |
helper(); | |
return boughtAllPromise; | |
} | |
function buyBuildingByName(name, buttonContainer) { | |
const buildingButtons = Array.from( | |
buttonContainer.querySelectorAll('.btn:not(.disabled) .btnContent')); | |
const button = buildingButtons.find((btn) => btn.innerText.includes(name)); | |
if (button) { | |
console.log(`Trying to buy ${name}`); | |
button.click(); | |
} | |
} | |
function buyReligionByName(names) { | |
const religionButtons = Array.from(document.querySelectorAll( | |
'.panelContainer .btn:not(.disabled) .btnContent')); | |
names.forEach((name) => { | |
const button = religionButtons.find((btn) => btn.innerText.includes(name)); | |
if (button) { | |
console.log(`Trying to buy ${name}`); | |
button.click(); | |
} | |
}); | |
} | |
function buySpaceByName(names) { | |
const spaceButtons = Array.from(document.querySelectorAll( | |
'.panelContainer .btn:not(.disabled) .btnContent')); | |
names.forEach((name) => { | |
const button = spaceButtons.find((btn) => btn.innerText.includes(name)); | |
if (button) { | |
console.log(`Trying to buy ${name}`); | |
button.click(); | |
} | |
}); | |
} | |
function tradeByName(names) { | |
const tradeButtons = Array.from(document.querySelectorAll('.panelContainer')); | |
names.forEach((name) => { | |
const race = tradeButtons.find((btn) => btn.innerText.includes(name)); | |
if (race) race.querySelector('.trade a:nth-child(2)').click(); | |
}); | |
} | |
runAllAutos = () => { | |
const now = Date.now(); | |
for ([name, auto] of automations) { | |
// Only run if enabled | |
if (!auto.enabled) continue; | |
if (now - auto.lastRun > auto.timeout) { | |
if (VERBOSE_AUTO) console.log(`Running ${name}`); | |
try { | |
auto.run(); | |
} catch (e) { | |
console.warn(`${name} failed`); | |
console.error(e); | |
} | |
} | |
} | |
runAllTimeout = setTimeout(runAllAutos, 500); | |
}; | |
function hotkeys(e) { | |
switch (e.key) { | |
case 'B': | |
switchTab('Bonfire'); | |
break; | |
case 'T': | |
switchTab('Trade'); | |
break; | |
case 'R': | |
switchTab('Religion'); | |
break; | |
case 'W': | |
switchTab('Workshop'); | |
break; | |
case 'K': // Kittens | |
case 'V': // Village | |
document.querySelector('.tabsContainer a.tab:nth-of-type(2)').click(); | |
break; | |
case 'S': | |
switchTab('Space'); | |
break; | |
case 'C': | |
switchTab('Science'); | |
break; | |
case 'E': // timE | |
switchTab('Time'); | |
break; | |
} | |
} | |
document.addEventListener('keypress', (e) => hotkeys(e)); | |
function switchTab(tabName) { | |
const tabs = Array.from(document.querySelectorAll('.tabsContainer a.tab')); | |
const target = tabs.find((x) => x.innerText.includes(tabName)); | |
if (target) target.click(); | |
} | |
function status() { | |
const enabled = []; | |
const disabled = []; | |
for ([name, auto] of automations) { | |
if (auto.enabled) { | |
enabled.push(name); | |
} else { | |
disabled.push(name); | |
} | |
} | |
console.log(`Enabled automations\n${prettyPrintArr(enabled)}`); | |
console.log(`Disabled automations\n${prettyPrintArr(disabled)}`); | |
const buyTimeout = automations.get('buyBuilding').timeout / SECONDS; | |
const buildingOn = automations.get('buyBuilding').enabled ? 'E' : ' '; | |
console.log(`[${buildingOn}][${buyTimeout}s] buyBuilding - autobuyNames:\n${ | |
prettyPrintArr(autobuyNames)}`); | |
const spaceTimeout = automations.get('buySpace').timeout / SECONDS; | |
const spaceOn = automations.get('buySpace').enabled ? 'E' : ' '; | |
console.log(`[${spaceOn}][${spaceTimeout}s] buySpace - spaceBuyNames:\n${ | |
prettyPrintArr(spaceBuyNames)}`); | |
console.log(automations.get('craft').toString()); | |
console.log(automations.get('mediumCraft').toString()); | |
console.log(automations.get('slowCraft').toString()); | |
console.log(automations.get('scienceCraft').toString()); | |
console.log(automations.get('partialCraft').toString()); | |
} | |
function prettyPrintArr(arr) { | |
return `[${arr.join(', ')}]`; | |
} | |
function disable(name) { | |
automations.get(name).disable(); | |
} | |
function enable(name) { | |
automations.get(name).enable(); | |
} | |
function removeByName(arr, name) { | |
let i; | |
while ((i = arr.indexOf(name)) >= 0) { | |
arr.splice(i, 1); | |
} | |
return arr; | |
} | |
function setLategameTimeouts() { | |
automations.get('hunt').timeout = 1 * SECONDS; | |
automations.get('craft').timeout = 9 * SECONDS; | |
// automations.get('slowCraft').timeout = 5 * SECONDS; | |
automations.get('scienceCraft').timeout = 30 * SECONDS; | |
automations.get('mediumCraft').timeout = 10 * MINUTES; | |
automations.get('praise').timeout = 30 * SECONDS; | |
disable('slowCraft'); | |
disable('buyBuilding'); | |
disable('observe'); | |
enable('mediumCraft'); | |
enable('buildCraftCombo'); | |
automations.get('buildCraftCombo').timeout = 5 * SECONDS; | |
} | |
function prepareForReset() { | |
disable('buyBuilding'); | |
disable('buildCraftCombo'); | |
disable('slowCraft'); | |
disable('craft'); | |
disable('mediumCraft'); | |
disable('praise'); | |
disable('scienceCraft'); | |
disable('buySpace'); | |
disable('buyReligion'); | |
disable('trade'); | |
} | |
function recalcTimeouts() { | |
// Praise | |
setTimeoutByResPercent('praise', 'faith', 0.45); | |
setTimeoutByResPercent('hunt', 'manpower', 0.45); | |
// Craft | |
setTimeoutForCraft('craft'); | |
setTimeoutForCraft('slowCraft', 1); | |
} | |
function setTimeoutByResPercent(automation, res, waitFraction = .8) { | |
const newTimeout = waitFraction * secondsToCap(res); | |
const percentStr = `[${(waitFraction * 100).toFixed(1)}% ${res}]`; | |
console.log( | |
`Set ${automation} timeout to ${newTimeout.toFixed(1)}s ${percentStr}`); | |
automations.get(automation).timeout = newTimeout * SECONDS; | |
} | |
function setTimeoutForCraft(craftAutomation, waitFraction = .8) { | |
const craftNames = window[automations.get(craftAutomation).craftNames]; | |
// Fastest capped resource of raw materials | |
const fastestOfMaterials = craftNames.map((craftTarget) => { | |
const mats = game.workshop.getCraft(craftTarget).prices.map((p) => p.name); | |
return fastestCapped(mats); | |
}); | |
// Fastest of those | |
const fastest = fastestCapped(fastestOfMaterials); | |
setTimeoutByResPercent(craftAutomation, fastest, waitFraction); | |
} | |
function secondsToCap(resName) { | |
const res = game.resPool.get(resName); | |
const perSecond = 5 * game.getResourcePerTick(res.name, true); | |
return res.maxValue / perSecond; | |
} | |
function fastestCapped(resNames) { | |
if (resNames.length === 1) return resNames[0]; | |
return resNames.map((r) => [r, secondsToCap(r)]) | |
.filter(([name, capSecs]) => capSecs > 0) | |
.reduce((best, elem) => { | |
return best[1] < elem[1] ? best : elem; | |
})[0]; | |
} | |
automations.get('buildCraftCombo').disable(); | |
automations.get('buyBuilding').disable(); | |
automations.get('observe').disable(); | |
automations.get('partialCraft').disable(); | |
automations.get('titaniumTrade').disable(); | |
automations.get('mediumCraft').enable(); | |
automations.get('slowCraft').enable(); | |
automations.get('praise').enable(); | |
automations.get('trade').enable(); | |
automations.get('buySpace').enable(); | |
automations.get('feedElders').enable(); | |
automations.get('buyReligion').enable(); | |
console.log( | |
`Loaded automations [${Array.from(automations.keys()).join(', ')}]`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment