EquiMissioner

Best OpenSource Hero Zero Utility Userscript

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         EquiMissioner
// @namespace    https://github.com/Equihub/EquiMissioner
// @version      1.7
// @description  Best OpenSource Hero Zero Utility Userscript
// @author       LilyPrism @ Equihub
// @license      AGPL3.0
// @match        https://*.herozerogame.com
// @icon         https://github.com/Equihub/EquiMissioner/blob/main/equimissioner.jpg?raw=true
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js
// @resource     BS5_CSS https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    /**
     * Enum for mission focus types.
     */
    const MISSION_FOCUS = Object.freeze({
        XP: 'XP',
        COINS: 'COINS',
        COMBAT: 'COMBAT',
        TIME: 'TIME',
        MIN_ENERGY: 'MIN_ENERGY',
        HC: 'HC',
        HEROBOOK_ITEM: 'HEROBOOK_ITEM',
        EVENT_ITEM: 'EVENT_ITEM',
        SLOTMACHINE: 'SLOTMACHINE'
    });

    /**
     * Mapping of quest stages to their names.
     */
    const QUEST_STAGES = Object.freeze({
        '1': "At Home in Humphreydale",
        '2': "Dirty Downtown",
        '3': "Center of Granbury",
        '4': "State Capitol",
        '5': "Switzerland",
        '6': "The Big Crumble",
        '7': "Yoyo Island",
        '8': "Gamble City",
        '9': "Sillycon Valley",
        '10': "Paris",
        '11': "Yollywood",
        '12': "Australia",
        '13': "Yokio",
        '14': "The Golden Desert"
    });

    /**
     * Main class for the EquiMissioner script.
     */
    class EquiMissioner {
        constructor() {
            this.currentQuests = [];
            this.currentMissionFocus = GM_getValue("mission-focus", MISSION_FOCUS.XP);
            this.currentFPS = GM_getValue("fps", 30);
            this.maxEnergyPerQuest = GM_getValue("max-energy-quest", 20);
            this.autoStartQuest = GM_getValue("auto-start-quest", false);
            this.autoClaimQuest = GM_getValue("quest-auto-claim", false);
            this.autoNextQuest = GM_getValue("quest-auto-next", false);
            this.autoRedeemVoucherLater = GM_getValue("auto-redeem-voucher-later", false);
            this.autoDismissLevelUp = GM_getValue("auto-dismiss-level-up", false);
            this.autoDismissPetLevelUp = GM_getValue("auto-dismiss-pet-level-up", false);
            this.questSenseBoosterActive = GM_getValue("sense-booster", false);
            this.trainSenseBoosterActive = GM_getValue("train-sense-booster", false);

            this.init();
        }

        /**
         * Initializes the script by applying styles, creating UI, and setting up event listeners.
         */
        init() {
            this.applyStyles();
            this.setupRequestProxy();

            // Event listeners
            document.addEventListener("DOMContentLoaded", this.onDOMContentLoaded.bind(this));
            window.addEventListener("load", this.onWindowLoad.bind(this));
        }

        /**
         * Applies the external Bootstrap CSS styles to the document.
         */
        applyStyles() {
            const bs5Css = GM_getResourceText("BS5_CSS");
            GM_addStyle(bs5Css);
        }

        /**
         * Creates and inserts the user interface elements into the document.
         */
        createUI() {
            // Create the main container div
            const mainDiv = document.createElement('div');
            mainDiv.id = "missioner";
            mainDiv.className = "fixed-top";
            mainDiv.innerHTML = `
                <div style="z-index: 1050">
                    <button class="btn btn-secondary position-absolute d-flex align-items-center" style="z-index: 1050;" type="button" data-bs-toggle="collapse" data-bs-target="#missioner-cont" aria-expanded="false" aria-controls="missioner-cont">
                        <img src="https://github.com/Equihub/EquiMissioner/blob/main/equimissioner.jpg?raw=true" alt="M" style="width: 32px;">
                        <p class="mx-2 my-0">EquiMissioner</p>
                    </button>
                    <div class="position-absolute top-0" style="height: 100vh;background: #262626cc;overflow-y: auto">
                        <div class="collapse collapse-horizontal" id="missioner-cont">
                            <div class="pt-5 px-3" style="width: 300px;">
                                <div class="card text-bg-dark mb-2">
                                    <div class="card-body">
                                        <h5 class="card-title">Best Mission</h5>
                                        <p id="m-city" class="mb-0 card-text text-center">City</p>
                                        <p class="mb-0 card-text"><img src="https://hz-static-2.akamaized.net/assets/emoticons_big/xp.png" alt="XP" style="width: 24px"> <span id="m-xp"></span></p>
                                        <p class="mb-0 card-text"><img src="https://hz-static-2.akamaized.net/assets/emoticons_big/coin.png" alt="Coins" style="width: 24px"> <span id="m-coins"></p>
                                        <p class="mb-0 card-text"><img src="https://hz-static-2.akamaized.net/assets/emoticons_big/energy.png" alt="Coins" style="width: 24px"> <span id="m-cost"></p>
                                        <p class="mb-2 card-text">Duration: <span id="m-duration"></p>
                                        <label class="form-label">Max Energy:</label>
                                        <input type="number" class="form-control mb-2" id="max-energy-quest" min="1" max="50" value="${this.maxEnergyPerQuest}">
                                        <label class="form-label">Quest Focus:</label>
                                        <select class="form-select mb-2" id="quest-focus">
                                            <option value="XP">XP / Energy</option>
                                            <option value="COINS">Coins / Energy</option>
                                            <option value="COMBAT">Fighting Quests</option>
                                            <option value="TIME">Time Quests</option>
                                            <option value="MIN_ENERGY">Minimum Energy</option>
                                            <option value="HC">HeroCon Items</option>
                                            <option value="HEROBOOK_ITEM">Herobook Items</option>
                                            <option value="EVENT_ITEM">Event Items</option>
                                            <option value="SLOTMACHINE">Slotmachine Jetons</option>
                                        </select>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="quest-auto-start">
                                            <label class="form-check-label" for="quest-auto-start">Start Quest on Go</label>
                                        </div>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="quest-auto-claim">
                                            <label class="form-check-label" for="quest-auto-claim">Auto Claim Quest Rewards</label>
                                        </div>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="quest-auto-next">
                                            <label class="form-check-label" for="quest-auto-next">Start Next After Claim</label>
                                        </div>
                                        <button id="mission-go" class="btn btn-success w-100" style="padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x)!important;" type="button">Go</button>
                                    </div>
                                </div>
                                <div class="card text-bg-dark mb-2">
                                    <div class="card-body">
                                        <h5 class="card-title">FPS Unlock</h5>
                                        <label for="fps-input" class="form-label" id="fps-input-label">Current FPS: ${this.currentFPS}</label>
                                        <input type="range" class="form-range" min="30" max="160" step="10" id="fps-input" value="${this.currentFPS}">
                                    </div>
                                </div>
                                <div class="card text-bg-dark mb-2">
                                    <div class="card-body">
                                        <h5 class="card-title">Exploits</h5>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="quest-sense-booster">
                                            <label class="form-check-label" for="quest-sense-booster">Quest Sense Booster</label>
                                        </div>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="train-sense-booster">
                                            <label class="form-check-label" for="train-sense-booster">Train Sense Booster</label>
                                        </div>
                                        <button id="buy-energy" class="btn btn-primary w-100" style="padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x)!important;" type="button">Buy Energy</button>
                                    </div>
                                </div>
                                <div class="card text-bg-dark mb-2">
                                    <div class="card-body">
                                        <h5 class="card-title">Vouchers</h5>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="auto-redeem-voucher-later">
                                            <label class="form-check-label" for="auto-redeem-voucher-later">Auto Redeem-Later</label>
                                        </div>
                                        </div>
                                </div>
                                <div class="card text-bg-dark mb-2">
                                    <div class="card-body">
                                        <h5 class="card-title">Level Up</h5>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="auto-dismiss-level-up">
                                            <label class="form-check-label" for="auto-dismiss-level-up">Auto Dismiss Popup</label>
                                        </div>
                                        <div class="mb-3 form-check">
                                            <input type="checkbox" class="form-check-input" id="auto-dismiss-pet-level-up">
                                            <label class="form-check-label" for="auto-dismiss-pet-level-up">Auto Dismiss Pet Popup</label>
                                        </div>
                                    </div>
                                </div>
                                <div class="d-flex justify-content-center mb-2">
                                    <a href="https://discord.gg/ZEXdQreFxF" target="_blank" class="p-2 bg-dark rounded">
                                        <img src="https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/636e0a6a49cf127bf92de1e2_icon_clyde_blurple_RGB.png" alt="Discord" style="width: 32px">
                                    </a>
                                </div>
                                <div class="d-flex justify-content-center mb-2">
                                    <p>Made with ♥ by LilyPrism</p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            `;
            document.body.appendChild(mainDiv);
            this.initializeUIElements();
        }

        /**
         * Initializes UI elements and sets up event listeners.
         */
        initializeUIElements() {
            // Get references to UI elements
            const questFocusSelect = document.getElementById('quest-focus');
            const questSenseBoosterCheckbox = document.getElementById('quest-sense-booster');
            const trainSenseBoosterCheckbox = document.getElementById('train-sense-booster');
            const questAutoStartCheckbox = document.getElementById('quest-auto-start');
            const questAutoClaimCheckbox = document.getElementById('quest-auto-claim');
            const questAutoNextCheckbox = document.getElementById('quest-auto-next');
            const voucherAutoRedeemLaterCheckbox = document.getElementById('auto-redeem-voucher-later');
            const autoDismissLevelUpCheckbox = document.getElementById('auto-dismiss-level-up');
            const autoDismissPetLevelUpCheckbox = document.getElementById('auto-dismiss-pet-level-up');
            const fpsInput = document.getElementById('fps-input');
            const missionGoButton = document.getElementById('mission-go');
            const buyEnergyButton = document.getElementById('buy-energy');
            const maxEnergyQuestInput = document.getElementById('max-energy-quest');

            // Set initial values
            questFocusSelect.value = this.currentMissionFocus;
            questSenseBoosterCheckbox.checked = this.questSenseBoosterActive;
            trainSenseBoosterCheckbox.checked = this.trainSenseBoosterActive;
            questAutoStartCheckbox.checked = this.autoStartQuest;
            questAutoClaimCheckbox.checked = this.autoClaimQuest;
            questAutoNextCheckbox.checked = this.autoNextQuest;
            voucherAutoRedeemLaterCheckbox.checked = this.autoRedeemVoucherLater;
            autoDismissLevelUpCheckbox.checked = this.autoDismissLevelUp;
            autoDismissPetLevelUpCheckbox.checked = this.autoDismissPetLevelUp;

            // Set up event listeners
            fpsInput.addEventListener('input', this.updateFPS.bind(this));
            missionGoButton.addEventListener('click', this.executeBestMission.bind(this));
            buyEnergyButton.addEventListener('click', this.buyMoreEnergy.bind(this));
            questFocusSelect.addEventListener('change', this.updateMissionFocus.bind(this));
            maxEnergyQuestInput.addEventListener('change', this.updateMaxEnergyQuest.bind(this));
            questSenseBoosterCheckbox.addEventListener('change', this.updateQuestSenseBooster.bind(this));
            trainSenseBoosterCheckbox.addEventListener('change', this.updateTrainSenseBooster.bind(this));
            questAutoStartCheckbox.addEventListener('change', this.updateAutoStartQuest.bind(this));
            questAutoClaimCheckbox.addEventListener('change', this.updateAutoClaimQuest.bind(this));
            questAutoNextCheckbox.addEventListener('change', this.updateAutoNextQuest.bind(this));
            voucherAutoRedeemLaterCheckbox.addEventListener('change', this.updateAutoRedeemVoucherLater.bind(this));
            autoDismissLevelUpCheckbox.addEventListener('change', this.updateAutoDismissLevelUp.bind(this));
            autoDismissPetLevelUpCheckbox.addEventListener('change', this.updateAutoDismissPetLevelUp.bind(this));
        }

        /**
         * Sets up a proxy for XMLHttpRequest to intercept and handle quest data.
         */
        setupRequestProxy() {
            const self = this;
            const originalOpen = XMLHttpRequest.prototype.open;
            const originalSend = XMLHttpRequest.prototype.send;

            XMLHttpRequest.prototype.open = function (method, url) {
                this._method = method;
                this._url = url;
                return originalOpen.apply(this, arguments);
            };

            XMLHttpRequest.prototype.send = function (data) {
                const xhr = this;
                const originalOnReadyStateChange = xhr.onreadystatechange;

                xhr.onreadystatechange = function () {
                    if (xhr.readyState === XMLHttpRequest.DONE && xhr._method === "POST" && xhr._url.includes("request.php")) {
                        const jsonResponse = JSON.parse(xhr.responseText)

                        if (Array.isArray(jsonResponse?.data?.quests))
                            self.handleQuestChange(jsonResponse.data.quests);

                        if (data.includes("action=checkForQuestComplete"))
                            self.handleCheckForQuestComplete();

                        if (data.includes("action=claimQuestRewards"))
                            self.handleClaimQuestRewards();
                    }
                    let reportingError = data && data.includes("gameReportError");
                    if (!reportingError && originalOnReadyStateChange) {
                        originalOnReadyStateChange.apply(xhr, arguments);
                    } else if (originalOnReadyStateChange) {
                        console.error("[Missioner] Game error report prevented", data);
                    }
                }

                return originalSend.apply(this, arguments);
            };
        }

        /**
         * Handles check for claim quest rewards.
         */
        handleClaimQuestRewards() {
            if (!this.autoNextQuest || !document.Missioner.quest_complete)
                return;

            setTimeout(() => {
                this.executeBestMission();
            }, 500);
        }

        /**
         * Handles check for quest complete events.
         */
        handleCheckForQuestComplete() {
            // Auto-claim Quest
            setTimeout(() => {
                if (!this.autoClaimQuest || !document.Missioner.quest_complete || !document.Missioner.quest_complete._btnClose)
                    return;

                document.Missioner.quest_complete.onClickClose();
            }, 500);
        }

        /**
         * Handles changes in quest data and updates the UI.
         * @param {Array} quests - The array of quest data.
         */
        handleQuestChange(quests) {
            this.currentQuests = quests.map(quest => ({...quest, rewards: JSON.parse(quest.rewards)}));
            this.updateUIWithBestQuest(this.getBestQuest());
        }

        /**
         * Updates the user interface with information about the best quest.
         * @param {Object} quest - The best quest object.
         */
        updateUIWithBestQuest(quest) {
            if (!quest) return;
            document.getElementById("m-city").textContent = QUEST_STAGES[quest.stage];
            document.getElementById("m-xp").textContent = numeral(quest.rewards.xp || 0).format('0,0');
            document.getElementById("m-coins").textContent = numeral(quest.rewards.coins || 0).format('0,0');
            document.getElementById("m-cost").textContent = numeral(quest.energy_cost).format('0,0');
            document.getElementById("m-duration").textContent = (quest.duration / 60) + " Minutes";
        }

        /**
         * Determines and returns the best quest based on the current mission focus and settings.
         * @returns {Object|null} The best quest object or null if no quests are available.
         */
        getBestQuest() {
            let availableQuests = [...this.currentQuests];

            if (availableQuests.length === 0) return null;

            // Filter Quests exceeding the max energy per quest
            availableQuests = availableQuests.filter(q => q.energy_cost <= this.maxEnergyPerQuest);

            availableQuests = this.sortQuestsByFocus(availableQuests);

            // If there's no quest meeting the criteria filters, sort all quests by min energy
            if (availableQuests.length <= 0) {
                this.currentQuests.sort((a, b) => a.energy_cost - b.energy_cost);
            }

            return availableQuests[0] || null;
        }

        /**
         * Sorts the quests array based on the current mission focus.
         * @param {Array} quests - The array of quests to sort.
         * @returns {Array} The sorted array of quests.
         */
        sortQuestsByFocus(quests) {
            switch (this.currentMissionFocus) {
                case MISSION_FOCUS.XP:
                    return quests.sort((a, b) => (b.rewards.xp / b.energy_cost) - (a.rewards.xp / a.energy_cost));
                case MISSION_FOCUS.COINS:
                    return quests.sort((a, b) => (b.rewards.coins / b.energy_cost) - (a.rewards.coins / a.energy_cost));
                case MISSION_FOCUS.COMBAT:
                    return quests.filter(q => q.fight_difficulty !== 0).sort((a, b) => a.energy_cost - b.energy_cost);
                case MISSION_FOCUS.TIME:
                    return quests.filter(q => q.fight_difficulty === 0).sort((a, b) => a.energy_cost - b.energy_cost);
                case MISSION_FOCUS.MIN_ENERGY:
                    return quests.sort((a, b) => a.energy_cost - b.energy_cost);
                case MISSION_FOCUS.HEROBOOK_ITEM:
                    return quests.sort((a, b) => b.rewards.hasOwnProperty("herobook_item_epic") - a.rewards.hasOwnProperty("herobook_item_epic") || a.energy_cost - b.energy_cost);
                case MISSION_FOCUS.HC:
                    return quests.sort((a, b) => b.rewards.hasOwnProperty("guild_competition_item") - a.rewards.hasOwnProperty("guild_competition_item") || a.energy_cost - b.energy_cost);
                case MISSION_FOCUS.EVENT_ITEM:
                    return quests.sort((a, b) => b.rewards.hasOwnProperty("event_item") - a.rewards.hasOwnProperty("event_item") || a.energy_cost - b.energy_cost);
                case MISSION_FOCUS.SLOTMACHINE:
                    return quests.sort((a, b) => b.rewards.hasOwnProperty("slotmachine_jetons") - a.rewards.hasOwnProperty("slotmachine_jetons") || a.energy_cost - b.energy_cost);
                default:
                    return quests;
            }
        }

        /**
         * Event handler for changing the mission focus.
         * @param {Event} event
         */
        updateMissionFocus(event) {
            this.currentMissionFocus = event.target.value;
            GM_setValue('mission-focus', this.currentMissionFocus);
            this.updateUIWithBestQuest(this.getBestQuest());
        }

        /**
         * Event handler for changing the auto-redeem voucher later setting.
         * @param {Event} event
         */
        updateAutoRedeemVoucherLater(event) {
            this.autoRedeemVoucherLater = event.target.checked;
            GM_setValue("auto-redeem-voucher-later", this.autoRedeemVoucherLater);
        }

        /**
         * Event handler for changing the auto dismiss pet level-up setting.
         * @param {Event} event
         */
        updateAutoDismissPetLevelUp(event) {
            this.autoDismissPetLevelUp = event.target.checked;
            GM_setValue("auto-dismiss-pet-level-up", this.autoDismissPetLevelUp);
        }

        /**
         * Event handler for changing the auto dismiss level-up setting.
         * @param {Event} event
         */
        updateAutoDismissLevelUp(event) {
            this.autoDismissLevelUp = event.target.checked;
            GM_setValue("auto-dismiss-level-up", this.autoDismissLevelUp);
        }

        /**
         * Event handler for changing the max energy per quest.
         * @param {Event} event
         */
        updateMaxEnergyQuest(event) {
            this.maxEnergyPerQuest = event.target.value;
            GM_setValue("max-energy-quest", this.maxEnergyPerQuest);
            this.updateUIWithBestQuest(this.getBestQuest());
        }

        /**
         * Event handler for updating the quest sense booster setting.
         * @param {Event} event
         */
        updateQuestSenseBooster(event) {
            this.questSenseBoosterActive = event.target.checked;
            GM_setValue("sense-booster", this.questSenseBoosterActive);
        }

        /**
         * Event handler for updating the train sense booster setting.
         * @param {Event} event
         */
        updateTrainSenseBooster(event) {
            this.trainSenseBoosterActive = event.target.checked;
            GM_setValue("train-sense-booster", this.trainSenseBoosterActive);
        }

        /**
         * Event handler for updating the auto-start-quest setting.
         * @param {Event} event
         */
        updateAutoStartQuest(event) {
            this.autoStartQuest = event.target.checked;
            GM_setValue("auto-start-quest", this.autoStartQuest);
        }

        /**
         * Event handler for updating the auto-claim-quest setting.
         * @param {Event} event
         */
        updateAutoClaimQuest(event) {
            this.autoClaimQuest = event.target.checked;
            GM_setValue("quest-auto-claim", this.autoClaimQuest);
        }

        /**
         * Event handler for updating the auto-next-quest setting.
         * @param {Event} event
         */
        updateAutoNextQuest(event) {
            this.autoNextQuest = event.target.checked;
            GM_setValue("quest-auto-next", this.autoNextQuest);
        }

        /**
         * Event handler for updating the FPS value.
         * @param {Event} event
         */
        updateFPS(event) {
            this.currentFPS = parseInt(event.target.value, 10);
            GM_setValue("fps", this.currentFPS);
            document.getElementById("fps-input-label").textContent = "Current FPS: " + this.currentFPS;
            this.setFPS();
        }

        /**
         * Sets the game's FPS (Frames Per Second) to the current FPS value.
         */
        setFPS() {
            if (document.Missioner.app && document.Missioner.app.framePeriod) {
                document.Missioner.app.framePeriod = Math.floor(1000 / this.currentFPS);
            }
        }

        /**
         * Shows the dialog to buy more energy
         */
        buyMoreEnergy() {
            if (document?.Missioner?.quest) {
                document.Missioner.quest.onClickBuyEnergy();
            }
        }

        /**
         * Executes the best quest based on the current settings.
         */
        executeBestMission() {
            if (!document.Missioner.stage)
                return;

            const bestQuest = this.getBestQuest();
            if (!bestQuest) return;

            document.Missioner.stage.setStage(bestQuest.stage);

            setTimeout(() => {
                const questButtons = [
                    document.Missioner.quest._btnQuest1,
                    document.Missioner.quest._btnQuest2,
                    document.Missioner.quest._btnQuest3
                ];

                const targetButton = questButtons.find(button => button.get_tag()._data.id === bestQuest.id);
                if (targetButton) {
                    document.Missioner.quest.clickQuest(targetButton);

                    setTimeout(() => {
                        if (this.autoStartQuest && document.Missioner.dialog_quest) {
                            document.Missioner.dialog_quest.onClickStartQuest();
                        }
                    }, 300);
                } else {
                    console.error("Failed to find quest");
                }
            }, 400);
        }

        /**
         * Handler for the DOMContentLoaded event.
         */
        onDOMContentLoaded() {
            document.Missioner = {};

            // Remove conflicting script
            const embedScript = this.findScriptWithCode("function embedGame()");
            if (embedScript) embedScript.parentNode.removeChild(embedScript);

            // Inject the fixed game code
            this.injectFixedEmbedCode();

            // Create the UI
            this.createUI();
            console.log("[Missioner] Setup complete!");
        }

        /**
         * Handler for the window load event.
         */
        onWindowLoad() {
            this.setFPS();

            // General Loop
            setInterval(() => {

                // Quest sense booster
                if (document.Missioner.quest && document.Missioner.quest._btnSenseBooster)
                    if (document.Missioner.quest._btnSenseBooster.get_visible() === this.questSenseBoosterActive) {
                        document.Missioner.quest._btnSenseBooster.set_visible(!this.questSenseBoosterActive);
                        document.Missioner.quest._btnMostXPQuest.set_visible(this.questSenseBoosterActive);
                        document.Missioner.quest._btnMostGameCurrencyQuest.set_visible(this.questSenseBoosterActive);
                    }

                // Train sense booster
                if (document.Missioner.train && document.Missioner.train._btnTrainingSenseBooster)
                    if (document.Missioner.train._btnTrainingSenseBooster.get_visible() === this.trainSenseBoosterActive) {
                        document.Missioner.train._btnTrainingSenseBooster.set_visible(!this.trainSenseBoosterActive);
                        document.Missioner.train._btnMostGameCurrencyTrainingQuest.set_visible(this.trainSenseBoosterActive);
                        document.Missioner.train._btnMostTrainingProgressTrainingQuest.set_visible(this.trainSenseBoosterActive);
                        document.Missioner.train._btnMostXPTrainingQuest.set_visible(this.trainSenseBoosterActive);
                    }

                // Auto-redeem Voucher Later
                if (this.autoRedeemVoucherLater && document.Missioner.new_voucher && document.Missioner.new_voucher._btnClose) {
                    document.Missioner.new_voucher.onClickClose();
                }

                // Level-up dismiss
                if (this.autoDismissLevelUp && document.Missioner.level_up && document.Missioner.level_up._btnClose) {
                    document.Missioner.level_up.onClickClose();
                    document.Missioner.level_up.dispose();
                }

                // Pet Level-up dismiss
                if (this.autoDismissPetLevelUp && document.Missioner.pet_level_up && document.Missioner.pet_level_up._btnClose) {
                    document.Missioner.pet_level_up.onClickClose();
                    document.Missioner.pet_level_up.dispose();
                }
            }, 300);
        }

        /**
         * Finds a script element that contains the specified code.
         * @param {string} targetCode - The code to search for.
         * @returns {HTMLScriptElement|null} The found script element or null.
         */
        findScriptWithCode(targetCode) {
            return Array.from(document.querySelectorAll("script")).find(script => script.textContent.includes(targetCode)) || null;
        }

        /**
         * Injects the fixed embed code into the document.
         */
        injectFixedEmbedCode() {
            const fixedEmbedCode = `
                var appWidth = 1120;
                var appHeight = 755;
                if (gameLoaded) {
                    embedGame();
                }
                function embedGame() {
                    let script = lime.$scripts["HeroZero.min"].toString();
                    script = script.replace('this.gameDeviceCache', 'document.Missioner.app=this;this.gameDeviceCache');
                    script = script.replace('this._timer=this._tooltipProgressStar1', 'document.Missioner.train=this;this._timer=this._tooltipProgressStar1');
                    script = script.replace('this._btnClose=this._btnStartQuest=this._b', 'document.Missioner.dialog_quest=this;this._btnClose=this._btnStartQuest=this._b');
                    script = script.replace('this._quest.get_isTimeQuest()', 'document.Missioner.quest_complete=this;this._quest.get_isTimeQuest()');
                    script = script.replace('this._btnVideoAdvertisment=this._btnUseResource=this._btnSlotMachine', 'document.Missioner.stage=this;this._btnVideoAdvertisment=this._btnUseResource=this._btnSlotMachine');
                    script = script.replace('{this._leftSideButtons=null;', '{document.Missioner.quest=this;this._leftSideButtons=null;');
                    script = script.replace('this._onLoadedCharacter=this', 'document.Missioner.view_manager=this;this._onLoadedCharacter=this');
                    script = script.replace('this._voucher=a', 'document.Missioner.new_voucher=this;this._voucher=a');
                    script = script.replace('.txtLevelNumber.set_fontName("FontHeadline");', '.txtLevelNumber.set_fontName("FontHeadline");document.Missioner.level_up=this;');
                    script = script.replace('this._btnClose=this._sidekick=null;', 'this._btnClose=this._sidekick=null;document.Missioner.pet_level_up=this;');
                    script = script.replace('this._btnCurrentDungeonQuest=this._btnBack', 'document.Missioner.dungeon=this;this._btnCurrentDungeonQuest=this._btnBack');
                    eval("window.lime_script = " + script);
                    lime.$scripts["HeroZero.min"] = window.lime_script;
                    lime.embed("HeroZero.min", "appClient", appWidth, appHeight, {
                        height: appHeight,
                        rootPath: "https://hz-static-2.akamaized.net/assets/html5",
                        parameters: clientVars
                    });
                    console.log("[Missioner] Game Fix Injected");
                }
            `;
            const fixScript = document.createElement("script");
            fixScript.textContent = fixedEmbedCode;
            document.head.appendChild(fixScript);
        }
    }

    // Instantiate the EquiMissioner class
    new EquiMissioner();
})();