DH3 KindaSafe Cheat

The cheat script for DH3

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
    }
})();