// ==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(10), false); furball.set("joe", "amt", 5); const races = game.diplomacy.races; let idx = 0; setInterval(() => { if(should_run("joe")) { const race = races[idx]; if(idx === races.length - 1) { idx = 0; } else { idx += 1; } if(race.unlocked) { let amt = game.diplomacy.getMaxTradeAmt(race); if(amt > 0) { if(amt > furball.conf.joe.amt) { amt = furball.conf.joe.amt; } game.diplomacy.tradeMultiple(race, amt); log_msg(`did a trade with ${race.name} ${amt} times`); game.village.huntAll(); log_msg("hunted all the things on the way back"); } } } }, 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)); })();