Greasy Fork is available in English.

DH3 KindaSafe Cheat

The cheat script for DH3

// ==UserScript==
// @name         DH3 KindaSafe Cheat
// @namespace    http://tampermonkey.net/
// @version      1.5.1
// @description  The cheat script for DH3
// @author       Lasse Brustad
// @match        dh3.diamondhunt.co
// @grant        none
// @license      MIT
// ==/UserScript==

/* jshint esversion:8 */

(function() {
    'use strict';

    // config for cheats - "true" = the cheat is on, "!true" or "false" = it's off
    const cheats = {
        // auto fight
        autoCombat: !true,

        // auto smelt ores
        autoSmelt: !true,

        // auto chop trees, it's fully automated with a tiny issue that doesn't matter
        autoChop: !true,

        // auto consume potions you have
        autoDrinkPotions: !true,

        // auto send/collect the Oxygen Tank, you can't get any fish at all with this on
        autoOxy: !true,

        // auto send/collect the Row Boat, this is forced off if autoOxy is on
        autoRowBoat: !true,

        // auto send/collect the Canoe Boat, this is forced off if autoOxy or autoRowBoat is on
        autoCanoeBoat: !true
    };

    const combatConfig = {

        /**
         * >>> Areas - START
         */

        // info about the areas, better leave this as it is if you don't know what you're doing
        areas: {
            // areaName: {
            //   energy: cost, (1e3 = 1000, 1e4 = 10000, 1_000 = 1000)
            //   points: cost
            // },

            // Fields
            fields: {
                energy: 50,
                points: 900
            },

            // Forest
            forest: {
                energy: 200,
                points: 1800
            },

            // Caves
            caves: {
                energy: 500,
                points: 3600
            },

            // Lava Dungeon
            lavaDungeon: {
                energy: 2e3,
                points: 5400
            },

            // Northern Fields
            northernFields: {
                energy: 5e3,
                points: 7200
            },

            // Cemetery
            cemetery: {
                energy: 10e3,
                points: 9e3
            },

            // Ocean
            ocean: {
                energy: 16e3,
                points: 10800
            },

            // Castle Dungeon
            dungeon: {
                energy: 30e3,
                points: 13200
            },

            // Dungeon Hole - !!! MUST BE VERIFIED !!!
            dungeonHole: {
                energy: 60e3,
                points: 25e3
            }
        },

        /**
         * <<< Areas - ENDS
         */

        // replace "fields" with the "areaName" from the list above
        area: 'fields',

        // don't teleport when you meet a shiny entity, no matter if the list chosen below says something else
        // basically, as far as the entity is shiny, fight it
        forceFightShiny: !true,

        // kill entity with disintegrate when it's possible?
        disintegrateMonster: !true,

        // want to disable any spell? just comment it out with double '//' in front of the line
        spellOrder: [
            //'disabledSpell',
            'ghostScan',
            'invisibility',
            'freeze',
            'reflect',
            'poison',
            'heal',
            'fire'
        ],

        // replace "fight" with the name of any of the combat list names, only one will be active
        listName: 'fight', // 'none' here will disable this cheat

        /**
         * >>> Combat Lists - START
         */

        // fight: when you meet any of the entities in this list, teleport won't be used
        fight: [
            // example fields
            'exampleChicken', // enabled
            'exampleRat', //     enabled
            //'exampleBee', //   disabled

            // fields
            'chicken',
            'rat',
            'bee',

            // forest
            'snake',
            'ent',
            //'thief',

            // caves
            'spider',
            'bear',
            'skeleton',

            // lava dungeon
            //'lavaAlien',
            'bat',
            'fireMage',

            // NF
            'boneHead',
            //'babyPolarBear',
            //'mammaPolarBear',
            //'yeti',

            // cemetery
            //'ghost',
            //'skeletonGhost',
            'reaper',

            // ocean
            'pufferFish',
            'shark',
            'tridentSoldier',

            /* I'm just guessing on the names below here, so I can't make
             * sure anything below this point will work at all! */

            // castle dungeon
            'skeletonMonks',
            'dungeonSpider',
            'stoneWomen',

            // dungeon hole
            'babyRedDragon',
            'babyBlueDragon',
            'babyYellowDragon',
        ],

        // skip: if you meet entities in this list, teleport will be used
        skip: [
            //'spider'
            'skeleton'
        ],

        // you can add as many custom lists as you want, but they will work just like "fight"

        // bonesMode: this is a custom list. when you're fighting in caves, you'll only fight Skeleton
        bonesMode: [
            'skeleton'
        ],

        // magicMode: another custom list. this is a mode for getting magic lvl as fast as
        // possible, so I recommend fighting fields or forest when using this list
        magicMode: [],

        /**
         * <<< Combat Lists - ENDS
         */
    }

    // additional config for autoSmelt
    const smeltConfig = {

        /**
         * >>> Intelligent Auto Smelt - START
         */

        // when this is on, the script will use the "ores" list below as the order to smelt ores
        // if you don't have the minimum amount of the ore, the script will smelt the next ore instead
        smeltAllAvaliable: true,

        /**
         * <<< Intelligent Auto Smelt - ENDS
         */

        /**
         * >>> Auto Smelt Ore Limits - START
         */

        // this is the list of ores that can be smelted, the order matters for "smeltAllAvaliable",
        // but the limits will be used all the time, so set it up while thinking about oil/charcoal and time
        ores: {
            titanium: {
                // minimum amount before smelting, higher than capacity will be reduced to maximum capacity
                min: 5,
                // maximum amount to autosmelt each time, 0 = no limit/furnace capacity
                max: 10
            },
            sand: {
                min: 100,
                max: 0
            },
            promethium: {
                min: 5,
                max: 50
            },
            gold: {
                min: 20,
                max: 100
            },
            silver: {
                min: 50,
                max: 250
            },
            iron: {
                min: 100,
                max: 500
            },
            copper: {
                min: 200,
                max: 1000
            }
        }

        /**
         * <<< Auto Smelt Ore Limits - ENDS
         */
    }

    // additional settings for auto drink potions
    const potionConfig = {
        /**
         * >>> Auto Drink Potions - START
         */
        potions: {
            // what is this, and how to modify it?
            //namePotion: {
            //    use: true, // you know how this works now, I guess?
            //    time: 300, // the base time for a potion without any bonus time, and in seconds
            //    more: 0    // whenever there's more settings, it might be named well
            //},

            // SD potion
            stardustPotion: {
                use: !true,
                time: 5 * 60 // 5 min
            },

            // Sand potion
            sandPotion: {
                use: !true,
                time: 1 * 60 * 60 // 1 hour
            },

            // FP potion - This name isn't really modified in the source code of DH3,
            // it's actually combatCooldownPotion, but my code will translate it for ya :D
            fightPointPotion: {
                use: !true,
                time: 10 * 60, // 10 min
                // use even when you can reset the timer
                useWhenResetIsReady: !true,
                // stop using when you have x fight points
                stopUsingAt: 4000 // max - 4000 FP
            },

            // Compost potion
            compostPotion: {
                use: !true,
                time: 30 * 60 // 30 min
            },

            // Oil potion
            oilPotion: {
                use: !true,
                time: 30 * 60 // 30 min
            },

            // Bone potion
            bonePotion: {
                use: !true,
                time: 2 * 60 * 60 // 2 hours
            },

            // Tree starter potion
            treeStarterPotion: {
                use: !true,
                time: 10 * 60, // 10 min
                // require at least x empty patches before use
                emptyTreePatches: 3
            },

            // Bar potion
            barPotion: {
                use: !true,
                time: 10 * 60 // 10 min
            },

            // Large SD potion
            largeStardustPotion: {
                use: !true,
                time: 5 * 60 // 5 min
            },

            // Furnace potion
            largeFurnacePotion: {
                use: !true,
                time: 30 * 60 // 30 min
            },

            // Pirate potion
            largePiratePotion: {
                use: !true,
                time: 3 * 60 * 60 // 3 hours
            },

            // Rocket speed potion
            largeRocketSpeedPotion: {
                use: !true,
                time: 2 * 60 * 60 // 2 hours
            },

            // Large bar potion
            largeBarPotion: {
                use: !true,
                time: 10 * 60 // 10 min
            },

            // Large oil factory potion
            largeOilFactoryPotion: {
                use: !true,
                time: 30 * 60 // 30 min
            },

            // hugeStardustPotion
            hugeStardustPotion: {
                use: !true,
                time: 5 * 60 // 5 min
            }
        }
        /**
         * <<< Auto Drink Potions - ENDS
         */
    }

    // config for legal mods
    const mods = {
        // right click the axe to chop down all fully grown trees
        chopTrees: true,

        // right click the rake to harvest all the ready farms
        harvestCrops: true
    };

    /**
     * >>> Advanced Config Section!
     *
     * This is an advanced section for complicated features which requires JS knowledge!
     */

    // just don't touch this, at least, do not remove or edit existing code, it's
    // critically used in parts of the script this is just to keep active data from
    // cheat features, like, if stuff is currently enabled or not
    const cheatData = {
        combat: {
            fightSoon: false,
            error: false,
            useSpells: false,
            // I know, the "key" here is kept as it is because of
            // the history of DH3 KindaSafe Cheat's development, lol
            disabled: JSON.parse(localStorage.getItem('lbdata-combatError')) || false
        },
        smelting: {
            queue: false
        },
        brewing: {
            autoPotions: {}
        }
    };

    // advanced auto drink potions configs
    const advancedPotionsExtraCheckFn = {
        // any potion that requires additional testing should have it's test code here!
        // DO NOT FORGET: the function should return `true` if the potion is ready for use
        // there's no need for testing time or if there's any left of the potion

        // Example with all valid values from "data", but this
        // example will never run, so it can be left here :)
        examplePotion(data) {
            // "name" and "amount" is the same value, but it's easier
            // to read one over the other depending on the use case
            const { name, amount, timer } = data.strings;

            // test if there's any potions left of this potion.
            // here I would use "amount" instead of "name"
            if (window.getItem(amount) === 0) {
                // auto brew a new one if you're empty? ask for help if it's wanted (:

                // return false because you know you're out of this potion anyway here
                return false;
            }

            // test the time left. using 60 sec in this example
            if (window.getItem(timer) < 60) {
                // ignore the built-in time left check?
                data.ignoreTime();

                // return true to not stop the chance for the potion to be used
                return true;
            }

            // I really don't recommend it, but you can access these as well:

            // WARNING! calling this will result in a crash loop!
            data.loop(); // it's the main loop that is running every 1 sec

            // WARNING! calling this might put your account in a high
            // risk of being detected for cheats which can lead to ban!
            data.getStrings('examplePotion'); // modifies "data.strings"

            // this is useless here, just saying
            data.getRealPotName('examplePotion'); // returns the actual string used to drink the potion

            // this one is the handler that will drink the potion,
            // and using it here is without any built-in security testing
            data.handler(); // want to use it? then you're on your own when developing the security!
        },

        // FP potion
        fightPointPotion(data) {
            // if auto combat is turned off for some reason, don't use FP potion
            if (cheatData.combat.disabled || !cheats.autoCombat) return false;

            // get additional settings
            const { useWhenResetIsReady, stopUsingAt } = potionConfig.potions.fightPointPotion;
            const stopAt = window.getItem('maxFightPoints') - Math.abs(stopUsingAt);

            // if you're less than 4k FP away from full FP, don't use FP potion
            if (stopAt <= 0 && window.getItem('fightPoints') >= stopAt) return false;

            // check if FP reset is ready
            if (window.getItem('heroCooldownResetTimer') == 0) return useWhenResetIsReady;

            // otherwise, return true, that's it
            return true;
        },

        // Tree starter potion
        treeStarterPotion(data) {
            const { amount, timer } = data.strings;

            // don't use more than 1
            if (window.getItem(timer) != 0) return false;

            // count unlocked patches
            let unlockedPatches = [];
            for (let i = 1; i <= 6; i++) {
                const treePatch = window.getItem(`treeUnlocked${i}`);
                if (treePatch) unlockedPatches.push(`tree${i}`);
            }

            // require 3 woodcutting patches to be empty
            let emptyPatches = 0;
            for (let key of unlockedPatches) {
                emptyPatches += window.getItem(key) === 'none';

                // check if it's x empty patches now
                if (emptyPatches === potionConfig.potions.treeStarterPotion.emptyTreePatches) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * <<< Advanced Config Section!
     */


    /**
     * >>> INFO
     *
     * Code below here is functional code, but you can enable/disable everything in the configs above
     *
     * Remember, cheats is safe to use as it is, but if you mess with the code, don't
     * blame me if you get banned! Cheats is ofc not allowed, but who cares? I don't ;)
     *
     * <<< INFO
     */

    // logging / debugging
    const debugLevels = [
        'silent',
        'normal logging',
        'additional logging',
        'debugging'
    ];

    let debugLevel = parseInt(localStorage.getItem('lbdata-debug')) || 1;

    function debug(lvl, ...msg) {
        if (lvl > debugLevel) {
            return;
        }

        window.lastMessage = msg.join(' - ');
        switch (lvl) {
            case 0: // silent
                return;
            case 1: // standard logging
            case 2: // additional logging
                return console.log('DH3 KindaSafe:', ...msg);
            case 3: // debugging
                return console.log('DH3 KindaSafe Debugging:', ...msg);
        }
    }

    window.setDebugLevel = (lvl = debugLevel) => {
        if (typeof lvl !== 'number' || (lvl < 0 && lvl >= debugLevels.length)) {
            console.log(`Didn't change the logging level, it has to be a number ` +
                        `between 0 and ${debugLevels.length - 1}.`);
            return;
        }
        const previous = debugLevel;
        debugLevel = lvl;

        if (previous === debugLevel) {
            console.log(`Current debug level is: ${lvl} ~ ${debugLevels[lvl]}`);
        } else {
            console.log(`Debug level is now set to: ${lvl} ~ ${debugLevels[lvl]}`);
        }
    }

    async function wait(ms) {
        await new Promise(resolve => {
            setTimeout(resolve, ms);
        });
    }

    // auto combat
    function castSpell(spell) {
        // stole the spell safety check from DH3 Fixed #yolo - need improvements!
        if(
            document.getElementById("navigation-right-combat-fighting").style.display != "none" &&
            document.getElementById(`combat-spell-${spell}`).style.display != "none"
        ){
            document.getElementById(`combat-spell-${spell}`).click();
            return true;
        }
    }

    async function inCombat() {
        // avoid spamming
        if (!cheatData.combat.useSpells) return;
        cheatData.combat.fightSoon = false;
        cheatData.combat.useSpells = false;

        // get the entity name
        const entity = window.getItem('monsterName');

        // stop here if it's in castle!
        if (entity.startsWith('knight')) return;

        // use all spells in choosen order
        for (let spell of combatConfig.spellOrder) {
            await wait(Math.floor(Math.random() * 150) + 100);
            castSpell(spell);
        }

        // if the entity isn't in the list, cast teleport!
        const { listName, forceFightShiny } = combatConfig;
        const noTeleport = combatConfig[listName];
        const isCustomList = !['fight', 'skip', 'none'].includes(listName);
        if (forceFightShiny && !!window.getItem('shinyMonster')) {
            await wait(Math.floor(Math.random() * 150) + 100);
            castSpell('disintegrate');

            // no, no, NO! don't teleport if the entity is shiny!
            return;
        }

        // teleport?
        if (
            (!noTeleport.includes(entity) && listName === 'fight') ||
            (noTeleport.includes(entity) && listName === 'skip') ||
            (noTeleport.includes(entity) && isCustomList)
        ) {
            await wait(Math.floor(Math.random() * 150) + 350);
            castSpell('teleport');
        }

        // disintegrate?
        if (combatConfig.disintegrateMonster) {
            await wait(Math.floor(Math.random() * 150) + 100);
            castSpell('disintegrate');
        }
    }

    function autoCombat() {
        const { fightSoon, error, disabled } = cheatData.combat;
        // damn, didn't expect this function to require that much anti-fail testing
        if (error || disabled) return;
        if (window.getItem('monsterName') != 'none' && window.getItem('combatCountDown') == 0) {
            inCombat();
            return;
        }
        if (fightSoon || window.getItem('teleportCooldown') != 0) {
            return;
        }
        if (!Object.keys(combatConfig.areas).includes(combatConfig.area)) {
            cheatData.combat.error = true;
            debug(2, `The script has no area called ${combatConfig.area} registered!`);
            return;
        }
        const { points, energy } = combatConfig.areas[combatConfig.area];
        if (window.getAreaTimer(points) > window.getItem('fightPoints')) {
            return;
        }
        if (window.getEnergyReduction(energy) > window.getItem('energy')) {
            return;
        }

        // FIGHT!!!
        cheatData.combat.fightSoon = true;
        cheatData.combat.useSpells = true;
        safeDelay(() => {
            const { points, energy } = combatConfig.areas[combatConfig.area];
            if (
                window.getItem('monsterName') != 'none' ||
                window.getItem('teleportCooldown') >= 1 ||
                window.getAreaTimer(points) > window.getItem('fightPoints') ||
                window.getEnergyReduction(energy) > window.getItem('energy')
            ) {
                cheatData.combat.fightSoon = false;
                cheatData.combat.useSpells = false;
                return;
            }
            window.sendBytes(`FIGHT=${combatConfig.area}`);
        })
    }

    // settings for autoCombat while playing
    window.setCombatStatus = (state = null) => {
        if (typeof state === 'boolean') {
            cheatData.combat.disabled = !state;
            console.log(`Set the state to ${state ? 'active' : 'inactive'}`);
        } else if (typeof state === 'string' && typeof combatConfig.areas[state] === 'object') {
            combatConfig.area = state;
            console.log(`Set the area to ${state}`);
        } else if (state === null) {
            console.log(`Current state: Fighting ${combatConfig.area} is ` +
                        `${!cheatData.combat.disabled ? 'active' : 'inactive'}`);
        }
    }

    unload(e => {
        localStorage.setItem('lbdata-combatError', JSON.stringify(cheatData.combat.disabled));
        localStorage.setItem('lbdata-combatArea', combatConfig.area);
    });

    // auto smelt ores
    window.smeltOre = localStorage.getItem('lbdata-smeltOre') || 'copper'; // the ore to smelt
    if (cheats.autoSmelt) {
        unload(e => {
            localStorage.setItem('lbdata-smeltOre', window.smeltOre);
        });
    }

    // cheatSmelt
    function burningResource(resource) {
        const oilCost = window.getOilCost(resource) || 0;
        const charcoalCost = window.getCharcoalCost(resource) || 0;
        if (oilCost == 0 && charcoalCost == 0) {
            debug(2, `Can't smelt "${resource}"`);
            return null;
        }
        return {
            burnable: ['oil', 'charcoal'][+(oilCost == 0)],
            amount: Math.max(oilCost, charcoalCost)
        };
    }

    function getFirstSmeltable() {
        let data = null,
            usable = {
                oil: window.getItem('oil'),
                charcoal: window.getItem('charcoal')
            };

        for (let ore in smeltConfig.ores) {
            const owned = window.getItem(ore);
            let { min } = smeltConfig.ores[ore];
            if (owned < min) continue;

            const burningInfo = burningResource(ore);
            if (burningInfo === null) continue;

            const minCost = burningInfo.amount * min;
            if (minCost > usable[burningInfo.burnable]) continue;

            data = {
                ore,
                type: burningInfo.burnable,
                cost: burningInfo.amount
            }
            break;
        }
        // done!
        return data;
    }

    class Smeltable {
        constructor(ore, cost = null, type = null) {
            // type of ore
            this.ore = ore;
            if (type !== null && cost !== null) {
                // oil / charcoal
                this.type = type;
                this.cost = cost;
            } else {
                const { burnable, amount } = burningResource(ore);
                this.type = burnable;
                this.cost = amount;
            }
        }

        test() {
            // don't try, just don't - security first!
            if (window.isSmelting()) return false;

            // prepare needed data
            const burn = window.getItem(this.type);
            const capacity = window.getItem('furnaceCapacity');
            const owned = window.getItem(this.ore);
            const data = smeltConfig.ores[this.ore];
            let limit = data.max;

            if (limit < data.min) {
                limit = capacity;
            }

            // prepare variables to be set
            let amount = Math.min(
                owned,
                limit,
                capacity,
                Math.floor(burn / this.cost)
            );

            // last test to pass
            if (amount < data.min) {
                debug(3, 'Failed the last test, crap!', {
                    amount,
                    limit,
                    min: data.min,
                    max: data.max
                });
                return false;
            }

            return amount
        }

        smelt() {
            // run the test
            const amount = this.test();

            if (typeof amount !== 'number') {
                return amount;
            }

            // smelt it!
            window.smelt(this.ore, amount);
        }
    }

    function cheatSmelt() {
        if (window.isSmelting() || cheatData.smelting.queue) return;

        let smeltable;

        if (smeltConfig.smeltAllAvaliable) {
            const { ore, cost, type } = getFirstSmeltable();
            smeltable = new Smeltable(ore, cost, type);
        } else {
            smeltable = new Smeltable(window.smeltOre)
        }

        cheatData.smelting.queue = smeltable.test() != false;
        safeDelay(() => {
            smeltable.smelt();
            cheatData.smelting.queue = !true;
        });
    }

    const clicksItem = window.clicksItem;
    function clicksItemMod(modified = true) {
        if (modified) {
            window.clicksItem = (item, ...args) => {
                if (typeof smeltConfig.ores[item] === 'object') {
                    window.smeltOre = item;
                }
                clicksItem(item, ...args);
            };
        } else {
            window.clicksItem = clicksItem;
        }
    }
    window.clicksItemMod = clicksItemMod;

    // auto chop trees
    function cheatChop() {
        const treeData = {
            click(n) {
                debug(2, `Auto Chop - Chopping patch ${n}`);
                document.getElementById(`tree-section-${n}`).click();
                setTimeout(() => window.closeDialogue('dialogue-confirm'), 1e3);
            },
            timer(n) {
                return window[`var_treeTimer${n}`] * 1;
            },
            isActive(n) {
                return window[`var_tree${n}`] !== 'none';
            }
        };

        const tasks = [null, false, false, false, false, false, false];
        function chop(num, waitMinSec = 0) {
            if (tasks[num]) return;
            tasks[num] = true;
            safeDelay(() => {
                tasks[num] = false;
                if (!treeData.isActive(num)) return;
                treeData.click(num);
            }, waitMinSec * 1e3);
        }

        function loop() {
            for (let i = 1; i < tasks.length; i++) {
                const timeLeft = treeData.timer(i);
                if (timeLeft < 10) chop(i, timeLeft);
            }
        }
        setInterval(loop, 1e4);
    }

    // auto drink potions
    class AutoDrinkPotion {
        constructor(potionName, potionTime, extraCheckFn = () => false) {
            this.getStrings = this.getStrings.bind(this);
            this.getRealPotName = this.getRealPotName.bind(this);
            this.handler = this.handler.bind(this);
            this.getPotionTime = this.getPotionTime.bind(this);
            this.loop = this.loop.bind(this);

            this.extraCheck = extraCheckFn;
            this.potionTime = potionTime;
            this.willDrink = false;

            const realName = this.getRealPotName(potionName);
            this.strings = this.getStrings(realName);
        }

        getStrings(str) {
            str = this.getRealPotName(str);
            const strings = {
                name: str,
                amount: str,
                timer: `${str}Timer`
            };
            return strings;
        }

        getRealPotName(name) {
            const potionsWithWrongName = {
                fightPointPotion: 'combatCooldownPotion'
            };

            if (typeof potionsWithWrongName[name] !== 'string') {
                return name;
            } else {
                return potionsWithWrongName[name];
            }
        }

        async handler() {
            this.willDrink = true;
            const { name, timer } = this.strings;

            await wait(Math.floor(Math.random() * 4e3) + 1e3);
            window.sendBytes(`DRINK=${name}`);

            await wait(5e3);
            this.willDrink = false;
        }

        getPotionTime() {
            const modifierStr = window.getBrewingKitDataTypes()[3];
            if (modifierStr === '0%') {
                return this.potionTime
            }
            return this.potionTime * (parseInt(modifierStr) / 100 + 1);
        }

        loop() {
            if (this.willDrink) return;

            let ignoreTime = false;
            const self = Object.assign(this, {
                ignoreTime: () => {
                    ignoreTime = true;
                }
            });
            if (!this.extraCheck(self)) return;

            const { amount, timer } = this.strings;
            if (window.getItem(amount) === 0) return;

            const potionTimeLeft = window.getItem(timer);
            const potionTimeModified = this.getPotionTime();
            if (potionTimeLeft < potionTimeModified || ignoreTime) {
                this.handler();
            }
        }
    }

    for (let potion in potionConfig.potions) {
        const { use, time } = potionConfig.potions[potion];
        const extraCheckFn = advancedPotionsExtraCheckFn[potion] || (fn => true);

        // will not use
        if (!use) continue;

        // will use
        const obj = new AutoDrinkPotion(potion, time, extraCheckFn);
        cheatData.brewing.autoPotions[potion] = obj;
    }

    // auto boat
    let willUseBoat = false;
    function cheatAnyBoat(str = 'oxygenTank') {
        if (willUseBoat) return;
        const tLeft = window.getItem(str + "Timer");
        if (tLeft > 2) return;
        let task = () => {};
        switch (tLeft) {
            case 0:
                willUseBoat = true;
                task = () => window.sendBytes("SEND_BOAT=" + str);
                break;
            case 1:
                willUseBoat = true;
                task = () => window.sendBytes("COLLECT_BOAT=" + str);
                break;
        }
        safeDelay(() => {
            const timeLeft = window.getItem(str + "Timer");
            if (timeLeft === tLeft) task();
            willUseBoat = false;
        });
    }

    function initCheats() {
        // combat
        if (cheats.autoCombat) {
            setInterval(autoCombat, 1e3);
            debug(1, 'Cheat - Initialized Auto Combat');
        }

        // mining

        // crafting
        if (cheats.autoSmelt) {
            setInterval(cheatSmelt, 1e3);
            clicksItemMod(smeltConfig.smeltAllAvaliable);
            debug(1, 'Cheat - Initialized Auto Smelt');
        }

        // woodcutting
        if (cheats.autoChop) {
            cheatChop();
            debug(1, 'Cheat - Initialized Auto Chop');
        }

        // farming

        // brewing
        if (cheats.autoDrinkPotions) {
            for (let autoPotion in cheatData.brewing.autoPotions) {
                debug(3, `Cheat - ${autoPotion} should be enabled`)
                setInterval(cheatData.brewing.autoPotions[autoPotion].loop, 1e3);
            }
            debug(1, 'Cheat - Initialized Auto Drink Potions');
        }

        // fishing
        if (cheats.autoOxy)
        {
            setInterval(cheatAnyBoat, 1e3);
            debug(1, 'Cheat - Initialized Auto Oxygen Tank');
        }
        else if (cheats.autoRowBoat)
        {
            setInterval(cheatAnyBoat, 1e3, 'rowBoat');
            debug(1, 'Cheat - Initialized Auto Row Boat');
        }
        else if (cheats.autoCanoeBoat)
        {
            setInterval(cheatAnyBoat, 1e3, 'canoeBoat');
            debug(1, 'Cheat - Initialized Auto Canoe Boat');
        }

        // cooking
    }

    // mod 1 - RightClick axe to chop all trees
    function chopTrees(e) {
        const trees = getEls('#tree-section-woodcutting > center > div');
        e.preventDefault();

        trees.each(el => {
            const timeEl = el.querySelector('.tree-secton-timer');
            const time = timeEl !== null ? timeEl.innerText : '';
            if (time === 'READY') el.click();
        });

        debug(2, 'Chopped all trees');
    }

    // mod 2 - RightClick rake to harvest the crops
    function harvestCrops(e) {
        const trees = getEls('#plot-section-farming > center > div');
        e.preventDefault();

        trees.each(el => {
            const timeEl = el.querySelector('.tree-secton-timer');
            const time = timeEl !== null ? timeEl.innerText : '';
            if (time === 'READY') el.click();
        });

        debug(2, 'Harvested all crops');
    }

    function initMods() {
        if (mods.chopTrees) {
            // init mod 1
            getEls('#item-section-woodcutting-1 [data-tooltip-id=tooltip-axe]')
                .each(el => el.addEventListener('contextmenu', chopTrees));
            debug(1, 'Mod - Initialized Right Click Axe');
        }

        if (mods.harvestCrops) {
            // init mod 2
            getEls('#item-section-farming-1 [data-tooltip-id=tooltip-rake]')
                .each(el => el.addEventListener('contextmenu', harvestCrops));
            debug(1, 'Mod - Initialized Right Click Rake');
        }
    }

    function makeSureUserGetDetected() {
        if (!['anarox'].includes(window.getItem('username'))) {
            return;
        }

        // try to get users market banned! usernames are specified
        setInterval(window.sendBytes, 100, 'REFRESH_TRADABLES');
        debug(2, `You'll be market banned with this!`);
    }

    function init() {
        if (typeof window.var_username === "string" && window.var_username.length >= 3) {
            initMods();
            debug(1, 'Initialized Mods');

            initCheats();
            debug(1, 'Initialized Cheats');

            makeSureUserGetDetected();
            debug(2, 'TEST');
        } else {
            setTimeout(init, 2e3);
        }
    }

    setTimeout(init, 2e3);

    /**
     * minor functions for this script to work well with clean code
     */

    function getEls(qSel) {
        let els = document.querySelectorAll(qSel);

        return {
            getRaw() {
                return els;
            },
            getNum(x) {
                return els[x];
            },
            each(fn) {
                els.forEach(fn);
            },
            ids() {
                let res = [];
                for (let key of els) res.push(key.id || 'none');
                return res;
            }
        }
    }

    function unload(fn) {
        window.addEventListener('unload', fn);
    }

    // A safe cheat delayer
    function safeDelay(fn, minTime = 0) {
        setTimeout(fn, Math.floor(Math.random() * 4e3) + 1e3 + minTime);
    }
})();