EquiMissioner

Best OpenSource Hero Zero Utility Userscript

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