2
\$\begingroup\$

Clickpocalypse 2 is kind of dungeon crawler game but with minor controls. Script's purpose to automate what is left.

To try it out:

  1. Open "Clickpocalypse 2" as separate browser window (not a tab, game have to be in active tab to work without delays)
  2. Select your party members and let them start palying
  3. Paste code in developer tools (gist mirror for faster selection)
  4. Execute turn_on() and auto_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()
    }
}

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.