diff --git a/www/furball.js b/www/furball.js new file mode 100644 index 0000000..09137ea --- /dev/null +++ b/www/furball.js @@ -0,0 +1,221 @@ +// ==UserScript== +// @name Furball +// @namespace https://spookyinternet.com/ +// @version 0.1 +// @description Kitten Game Automation. Sometimes clicking is hard. +// @author uplime +// @match https://kittensgame.com/web/* +// @grant unsafeWindow +// @grant GM.setValue +// @grant GM.getValue +// @grant GM.listValues +// ==/UserScript== + +/* + * There comes an end to all things; the most capacious measure is filled at + * last; and this brief condescension to evil finally destroyed the balance of + * my soul. + * + * - Dr. Jekyll + */ + +(async function() { + "use strict"; + + /* + * Public API stub. + */ + + const furball = { conf: { } }; + unsafeWindow.furball = furball; + + /* + * Boilerplate stubs for automating the game. + */ + + let game = undefined; + const defer = { }; + + /* + * Utility methods. + */ + + const seconds = (amt) => amt * 1000; + const minutes = (amt) => seconds(amt * 60); + const hours = (amt) => minutes(amt * 60); + const days = (amt) => hours(amt * 24); + + const log_msg = (msg) => { + const node = game.msg(`✨furball✨ ${msg}`); + node.span.style.color = "rgb(66, 227, 93)"; + }; + + const should_run = (feat) => furball.conf[feat].enable && !game.isPaused; + + const total_crafted = (res, amt) => amt * (1 + game.getResCraftRatio(res)); + + /* + * Public API for controlling automation. + */ + + furball.set = (feat, key, val, ow=true) => { + if(furball.conf[feat] === undefined) { + furball.conf[feat] = { }; + } + + if(ow || furball.conf[feat][key] === undefined) { + furball.conf[feat][key] = val; + GM.setValue(`furball.${feat}.${key}`, val); + } + }; + + furball.get = (feat, key, val=undefined) => { + if(furball.conf[feat] === undefined || furball.conf[feat][key] === undefined) { + return val; + } else { + return furball.conf[feat][key]; + } + }; + + furball.toggle = (feat, enable=true, ow=true) => { + furball.set(feat, "enable", enable, ow); + }; + + /* + * Neighborhood Skywatch. + */ + + defer.skywatch = () => { + furball.toggle("skywatch", true, false); + + const skywatcher = new MutationObserver((muts, watcher) => { + if(should_run("skywatch") && muts[0].addedNodes.length > 0) { + muts[0].addedNodes[0].click(); + log_msg("did a watch on the sky"); + } + }); + + const sky = document.getElementById("observeButton"); + + if(sky !== null) { + skywatcher.observe(sky, { + childList: true, attributes: true, subtree: true + }); + } + }; + + /* + * Craft simple resources. + */ + + defer.easybake = () => { + furball.toggle("easybake", true, false); + furball.set("easybake", "time", minutes(3), false); + furball.set("easybake", "amt", 10, false); + + [ "beam", "slab", "plate", "steel" ].forEach((name) => { + const res = game.resPool.get(name); + + setInterval(() => { + if(should_run("easybake") && res.unlocked) { + game.craft(name, furball.conf.easybake.amt); + const total = total_crafted(name, furball.conf.easybake.amt); + log_msg(`crafted ${game.getDisplayValueExt(total, true)} ${name}`); + } + }, furball.conf.easybake.time); + }); + }; + + /* + * Craft advanced resources. + */ + + // make sure parchment is handled + // manuscripts, compendium + + /* + * Download saves automagically. + */ + + defer.lifesaver = () => { + furball.toggle("lifesaver", true, false); + furball.set("lifesaver", "time", hours(1), false); + + setInterval(() => { + if(should_run("lifesaver")) { + game.saveToFile(true); + log_msg("saved right before the boss fight"); + } + }, furball.conf.lifesaver.time); + }; + + /* + * Praise the sun! \o/ + */ + + defer.faithfull = () => { + furball.toggle("faithfull", true, false); + furball.set("faithfull", "time", hours(2) + minutes(30), false); + + setInterval(() => { + if(should_run("faithfull")) { + game.religion.praise(); + log_msg("Praise the sun \\o/"); + } + }, furball.conf.faithfull.time); + }; + + /* + * Trader Joe has the best deals around. + */ + + defer.joe = () => { + furball.toggle("joe", true, false); + furball.set("joe", "time", minutes(5), false); + furball.set("joe", "amt", 5); + + const races = game.diplomacy.races; + let idx = 0; + + setInterval(() => { + const race = races[idx]; + + if(should_run("joe") && race.unlocked) { + game.diplomacy.tradeMultiple(race, furball.conf.joe.amt); + log_msg(`did a trade with ${race.name} ${furball.conf.joe.amt} times`); + game.village.huntAll(); + log_msg("hunted all the things on the way back"); + + if(idx === races.length - 1) { + idx = 0; + } else { + idx += 1; + } + } + }, furball.conf.joe.time); + } + + /* + * Driver for loading furball on ready. + */ + + const gm_keys = await GM.listValues(); + + await gm_keys.forEach(async (gm_key) => { + const val = await GM.getValue(gm_key); + const keys = gm_key.split("."); + furball.conf[keys[1]] = { }; + furball.conf[keys[1]][keys[2]] = val; + }); + + const game_tmr = setInterval(() => { + if(unsafeWindow.gamePage !== undefined) { + clearInterval(game_tmr); + game = unsafeWindow.gamePage; + + Object.keys(defer).forEach((entry) => + defer[entry]() + ); + } + }, seconds(1)); +})();