Clickpocalypse 2 is kind of dungeon crawler game but with minor controls. Script's purpose to automate what is left.
To try it out:
- Open "Clickpocalypse 2" as separate browser window (not a tab, game have to be in active tab to work without delays)
- Select your party members and let them start palying
- Paste code in developer tools (gist mirror for faster selection)
- Execute
turn_on()
andauto_loot.start()
// 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
// ------------------------------------------
async function sleep(msec) {
return new Promise(resolve => setTimeout(resolve, msec))
}
class Mutex {
constructor() {
this.current = Promise.resolve()
}
async lock() {
let unlock
const prev = this.current
this.current = new Promise(resolve => unlock = () => resolve())
return prev.then(() => unlock)
}
}
const mutex = new Mutex()
// Manages event queue to let Clickpocalypse 2 engine handle them properly.
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
constructor(job, interval) {
this.#job = job
this.#interval = interval
this.#timer = null
}
start() {
this.#timer = setInterval(this.#job, this.#interval)
}
stop() {
clearInterval(this.#timer)
}
}
// ------------------------------------------
// -- Set auto looter (1 second interval)
// ------------------------------------------
// Presses loot button.
async function auto_loot() {
const loot_button = document.querySelector('#treasureChestLootButtonPanel.lootButton')
if (loot_button) button_click(loot_button)
}
auto_loot = new WorkerDecorator(auto_loot, 1000)
// ------------------------------------------
// -- Set auto upgrader (30 seconds interval)
// ------------------------------------------
// Uses gold, kills and experience to buy upgrades.
async function auto_upgrader() {
Array.prototype.filter.call(
document.querySelectorAll('#upgradeButtonContainer .upgradeButton'),
e => e.style.display == 'block'
).forEach(async (e) => await button_click(e))
}
auto_upgrader = new WorkerDecorator(auto_upgrader, 30 * 1000)
// ------------------------------------------
// -- Set auto potion user (30 seconds interval)
// ------------------------------------------
// Waits for all avaliable potion slots to fill up
// before activation.
function auto_potions() {
const potions = document.querySelectorAll('#potionButtonContainer .potionButton')
const locked_potions = document.querySelectorAll('.potionButtonLocked').length
if (potions.length === 8 - locked_potions) {
potions.forEach(async (e) => await button_click(e.parentNode))
}
}
auto_potions = new WorkerDecorator(auto_potions, 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
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 = {
'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(),
}
return async function() {
// `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
document.querySelector(`#gameTabMenu li:nth-child(${id + 4}) > a`).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 (!Object.hasOwn(strategies, name)) {
strategy = full_skillset
console.warn(`Custom strategy for [${name}] not found. Using default one.`)
} else {
strategy = strategies[name]
}
for (let [row, col] of strategy) {
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
document.querySelector('#gameTabMenu li:nth-child(3) > a').click()
}
}()
auto_skills = new WorkerDecorator(auto_skills, 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()
}
}