Last active
March 4, 2024 06:38
-
-
Save koutoftimer/c497259d270640c00f4bd637b82eeb3a to your computer and use it in GitHub Desktop.
Clickpocalypse 2 automation script
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
// Usage: run this script in your browser. | |
// | |
// By default it doesn't starts automatically. You have to run `turn_on()` function | |
// or start each module indivitually `auto_<name>.start()`. | |
// `turn_off()` and `auto_<name>.stop()` does the oposite. | |
// `modules_preset` defines set of modules to be handled by `turn_*` functions. | |
// | |
// ------------------------------------------ | |
// -- Utility | |
// ------------------------------------------ | |
/** @param {number} msec */ | |
async function sleep(msec) { | |
return new Promise(resolve => setTimeout(resolve, msec)) | |
} | |
class Mutex { | |
constructor() { | |
this.current = Promise.resolve() | |
} | |
async lock() { | |
/** @type {() => void} */ | |
let unlock | |
const prev = this.current | |
this.current = new Promise(resolve => { unlock = () => resolve(undefined) }) | |
return prev.then(() => unlock) | |
} | |
} | |
const mutex = new Mutex() | |
/** | |
* Manages event queue to let Clickpocalypse 2 engine handle them properly. | |
* @param {Element} button | |
*/ | |
async function button_click(button) { | |
const unlock = await mutex.lock() | |
button.dispatchEvent(new MouseEvent('mouseup')) | |
await sleep(150) | |
unlock() | |
} | |
// Decorator class that allows to turn on/off separate modules | |
class WorkerDecorator { | |
#job | |
#interval | |
#timer | |
/** | |
* @param {number} interval | |
* @param {() => void} job | |
*/ | |
constructor(job, interval) { | |
this.#job = job | |
this.#interval = interval | |
this.#timer = 0 | |
} | |
start() { | |
this.#timer = setInterval(this.#job, this.#interval) | |
} | |
stop() { | |
clearInterval(this.#timer) | |
} | |
} | |
// ------------------------------------------ | |
// -- Set auto looter (1 second interval) | |
// ------------------------------------------ | |
// Presses loot button. | |
const auto_loot = new WorkerDecorator( | |
async function () { | |
const loot_button = document.querySelector('#treasureChestLootButtonPanel.lootButton') | |
if (loot_button) button_click(loot_button) | |
} | |
, 1000 | |
) | |
// ------------------------------------------ | |
// -- Set auto upgrader (30 seconds interval) | |
// ------------------------------------------ | |
// Uses gold, kills and experience to buy upgrades. | |
const auto_upgrader = new WorkerDecorator( | |
async function () { | |
Array.prototype.filter.call( | |
document.querySelectorAll('#upgradeButtonContainer .upgradeButton'), | |
/** @param {HTMLElement} e */ | |
e => e.style.display == 'block' | |
).forEach(/** @param {Element} e */ async (e) => await button_click(e)) | |
} | |
, 30 * 1000 | |
) | |
// ------------------------------------------ | |
// -- Set auto potion user (30 seconds interval) | |
// ------------------------------------------ | |
// Waits for all avaliable potion slots to fill up | |
// before activation. | |
const auto_potions = new WorkerDecorator( | |
async function () { | |
const potions = document.querySelectorAll('#potionButtonContainer .potionButton') | |
const locked_potions = document.querySelectorAll('.potionButtonLocked').length | |
if (potions.length === 8 - locked_potions) { | |
potions.forEach(async (e) => { | |
if (e.parentElement) { | |
await button_click(e.parentElement) | |
} | |
}) | |
} | |
} | |
, 30 * 1000 | |
) | |
// ------------------------------------------ | |
// -- Set auto skill unlocker (30 seconds interval) | |
// ------------------------------------------ | |
// Unlocks skills for each hero in order specified | |
// by strategies. | |
var auto_skills = function() { | |
// Character names - Role: | |
// * Hugo - Fighter | |
// * Drago - Druid | |
// * Meiji - Ninja | |
// * Lord Volaille - King | |
// * Casey - Rogue | |
/** @param {number} id */ | |
function get_character_name(id) { | |
return document.querySelector(`#characterLevelUpButtonContainer${id}_0 span`) | |
?.textContent | |
?.replace('Level Up ', '') | |
} | |
// List of all skills, top to bottom, left to right. | |
const full_skillset = [...Array(4).keys()].map(col => [...Array(9).keys()].map(row => [row, col])).flat() | |
// List of skill learning strategies per hero | |
// TODO: add more strategies for ommited heroes | |
const strategies = new Map([ | |
[ | |
'Hugo', | |
[ | |
// taunt | |
[...Array(9).keys()].map(row => [row, 0]), | |
// health regen | |
[...Array(9).keys()].map(row => [row, 2]), | |
// AOE | |
[...Array(9).keys()].map(row => [row, 3]), | |
// last skills | |
[...Array(9).keys()].map(row => [row, 1]), | |
].flat(), | |
], | |
[ | |
'Drago', | |
[ | |
// Minor Heal | |
[...Array(9).keys()].map(row => [row, 0]), | |
// Guard Dog | |
[...Array(9).keys()].map(row => [row, 3]), | |
// max wolf pack | |
[...Array(9).keys()].map(row => [row, 2]), | |
// Sleep | |
[...Array(9).keys()].map(row => [row, 1]), | |
].flat(), | |
], | |
[ | |
'Meiji', | |
[ | |
// Swift Strike | |
[...Array(9).keys()].map(row => [row, 0]), | |
// HP regen | |
[...Array(9).keys()].map(row => [row, 2]), | |
// Damage | |
[...Array(9).keys()].map(row => [row, 1]), | |
// Attack speed | |
[...Array(9).keys()].map(row => [row, 3]), | |
].flat(), | |
], | |
[ | |
'Lord Volaille', | |
[ | |
// Very first chicken | |
[[0, 0]], | |
// Guard chicken | |
[...Array(9).keys()].map(row => [row, 1]), | |
// Chicken chance: Rouge | |
[...Array(9).keys()].map(row => [row, 0]).slice(1), | |
// Chicken chance: Ninja | |
[...Array(9).keys()].map(row => [row, 2]), | |
// Chicken chance: Barbarian | |
[...Array(9).keys()].map(row => [row, 3]), | |
].flat(), | |
], | |
[ | |
'Casey', | |
[ | |
// Detect Treasure Chest | |
[...Array(9).keys()].map(row => [row, 3]), | |
// Loot instantly | |
[...Array(9).keys()].map(row => [row, 2]), | |
// Improve health | |
[...Array(9).keys()].map(row => [row, 1]), | |
// Skip stealth | |
[...Array(4).keys()].map(row => [row, 0]), | |
].flat(), | |
], | |
]) | |
async function worker() { | |
// `id` is a positional index of the hero in the party composition | |
const locked_hero_slots = document.querySelectorAll('.gameTabLockedAdventurerInfo').length | |
for (let id of Array(5 - locked_hero_slots).keys()) { | |
// skip hero if he has no free skill points | |
if (!document.querySelector(`#gameTabMenu li:nth-child(${id + 4}).tabHighlighted`)) { | |
continue | |
} | |
// select skillset tab for current hero | |
/** @type {HTMLElement|null} */ | |
const link = document.querySelector(`#gameTabMenu li:nth-child(${id + 4}) > a`) | |
link?.click() | |
// let Clickpocalypse 2 engine update skillset DOM | |
await sleep(200) | |
// TODO: cache this value over id | |
let strategy | |
const name = get_character_name(id) | |
if (name === undefined || !strategies.has(name)) { | |
console.warn(`Custom strategy for [${name}] not found. Using default one.`) | |
} else { | |
strategy = strategies.get(name) | |
} | |
for (let [row, col] of strategy || full_skillset) { | |
const skill = document.getElementById(`characterSkillsContainer${id}_${row}_${col}_${row}`) | |
// some characters have incompleate skill table | |
// ensure we have met requirements to learn this skill | |
if (skill?.classList.contains('upgradeButton')) { | |
await button_click(skill) // dead-lock | |
break | |
} | |
} | |
} | |
// go back to Game screen | |
/** @type {HTMLElement|null} */ | |
const link = document.querySelector('#gameTabMenu li:nth-child(3) > a') | |
link?.click() | |
} | |
return new WorkerDecorator(worker, 30 * 1000) | |
}() | |
// auto_loot helpfull for early game or if you have no Rouge in the party | |
const modules_preset = [auto_skills, auto_potions, auto_upgrader] | |
// Bot (automation) switch handlers | |
function turn_on() { | |
for (const worker of modules_preset) { | |
worker.start() | |
} | |
} | |
function turn_off() { | |
for (const worker of modules_preset) { | |
worker.stop() | |
} | |
} |
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
Show hidden characters
{ | |
"compilerOptions": { | |
/* Visit https://aka.ms/tsconfig.json to read more about this file */ | |
/* Language and Environment */ | |
"target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ | |
"lib": ["es2019", "dom"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ | |
"useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ | |
/* JavaScript Support */ | |
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ | |
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ | |
/* Interop Constraints */ | |
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ | |
/* Type Checking */ | |
"strict": true, /* Enable all strict type-checking options. */ | |
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ | |
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment