Idle MMO Battle Automation with GUI and Logs

Automatically clicks "Start Hunt" and "Battle Max" based on game conditions. Includes a GUI for toggling settings, viewing logs, and copying the log, with customizable cooldown and randomization to prevent bans.

目前為 2024-08-24 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Idle MMO Battle Automation with GUI and Logs
// @namespace     https://web.idle-mmo.com/
// @version       1.1
// @description  Automatically clicks "Start Hunt" and "Battle Max" based on game conditions. Includes a GUI for toggling settings, viewing logs, and copying the log, with customizable cooldown and randomization to prevent bans.
// @author       Hidden
// @match        https://web.idle-mmo.com/battle
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // Settings
    let autoBattleEnabled = true;
    let autoHuntEnabled = true;
    let cooldownMin = 2000; // Minimum cooldown (milliseconds)
    let cooldownMax = 5000; // Maximum cooldown (milliseconds)
    let randomizeCooldown = true;

    // Function to click buttons by XPath (enhanced with direct click method)
    function clickButtonByXPath(xpath) {
        setTimeout(() => {
            const button = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            if (button) {
                // Check button state before clicking
                const isButtonDisabled = button.hasAttribute('disabled') || button.classList.contains('disabled');
                logMessage(`Button found with XPath: ${xpath}`);
                logMessage(`Button disabled state: ${isButtonDisabled}`);

                if (!isButtonDisabled) {
                    try {
                        button.click(); // Use click() directly
                        logMessage(`Clicked button with XPath: ${xpath}`);
                    } catch (error) {
                        logMessage(`Error clicking button with XPath: ${xpath} - ${error.message}`);
                    }
                } else {
                    logMessage(`Button with XPath: ${xpath} is disabled`);
                }
            } else {
                logMessage(`Button with XPath: ${xpath} not found`);
            }
        }, randomizeCooldown ? getRandomCooldown() : cooldownMin);
    }

    // Function to get the value of an element by CSS selector
    function getValueBySelector(selector) {
        const element = document.querySelector(selector);
        return element ? element.textContent.trim() : null;
    }

    // Function to get a random cooldown value within the specified range
    function getRandomCooldown() {
        return Math.floor(Math.random() * (cooldownMax - cooldownMin + 1)) + cooldownMin;
    }

    // Function to check if an element exists by XPath
    function elementExistsByXPath(xpath) {
        const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
        return element !== null;
    }

    // Function to check for the "Start Hunt" button
    function checkForStartHunt() {
        const startHuntXPath = '/html/body/div[1]/main/div[1]/div/div[2]/div[2]/div/div[2]/div[1]/div[1]/div[2]/div/div/div/div[1]/button';
        return elementExistsByXPath(startHuntXPath);
    }

    // Logging function
    function logMessage(message) {
        const logArea = document.getElementById('logArea');
        const timestamp = new Date().toLocaleTimeString();
        logArea.value += `[${timestamp}] ${message}\n`;
        logArea.scrollTop = logArea.scrollHeight;
    }

    // Automation logic (removed "Hunt Again" and only focuses on "Start Hunt" and "Battle Max")
    function automateBattle() {
        if (!autoBattleEnabled) return;

        // Updated XPaths and CSS selectors
        const battleMaxXPath = '/html/body/div[1]/main/div[1]/div/div[2]/div[2]/div/div[2]/div[1]/div[1]/div[2]/div/div[2]/div[1]/button';
        const battleMaxValueSelector = 'div.w-5'; // CSS selector for Battle Max Value
        const startHuntXPath = '/html/body/div[1]/main/div[1]/div/div[2]/div[2]/div/div[2]/div[1]/div[1]/div[2]/div/div/div/div[1]/button';
        const enemiesXPath = '/html/body/div[1]/main/div[1]/div/div[2]/div[2]/div/div[2]/div[1]/div[1]/div[2]/div';

        setTimeout(() => {
            // Re-evaluate the XPath to handle potential stale element reference
            const battleMaxButton = document.evaluate(battleMaxXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            const battleMaxValue = getValueBySelector(battleMaxValueSelector);
            const startHuntButton = document.evaluate(startHuntXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

            // Check if there are any enemy buttons within the enemiesXPath container
            const enemiesContainer = document.evaluate(enemiesXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            const enemyButtons = enemiesContainer ? enemiesContainer.querySelectorAll('button') : [];
            const enemiesExist = enemyButtons.length > 0;

            // Enhanced logging for debugging
            console.log("autoBattleEnabled:", autoBattleEnabled);
            console.log("battleMaxButton:", battleMaxButton);
            console.log("battleMaxButton.disabled:", battleMaxButton ? battleMaxButton.disabled : "null");
            console.log("battleMaxValue:", battleMaxValue);
            console.log("parseInt(battleMaxValue) > 0:", parseInt(battleMaxValue) > 0);
            console.log("startHuntButton:", startHuntButton);

            logMessage(`Enemies Exist: ${enemiesExist}`);
            logMessage(`Battle Max Value: ${battleMaxValue}`);

            // Prioritize "Start Hunt", then "Battle Max"
            if (autoBattleEnabled && startHuntButton) {
                logMessage(`Attempting to click "Start Hunt"`);
                clickButtonByXPath(startHuntXPath);
            } else if (autoBattleEnabled &&
                       battleMaxButton &&
                       !battleMaxButton.disabled &&
                       battleMaxValue !== null &&
                       parseInt(battleMaxValue) > 0) {
                logMessage(`Attempting to click "Battle Max"`);
                clickButtonByXPath(battleMaxXPath);
            } else {
                // Log specific reasons for not clicking
                if (!autoBattleEnabled) logMessage("Auto Battle is disabled.");
                if (!battleMaxButton) logMessage("Battle Max button not found.");
                if (battleMaxButton && battleMaxButton.disabled) logMessage("Battle Max button is disabled.");
                if (battleMaxValue === null) logMessage("Battle Max value is null.");
                if (!(parseInt(battleMaxValue) > 0)) logMessage("Battle Max value is not greater than 0.");
                if (!startHuntButton) logMessage("Start Hunt button not found.");
            }
        }, 500);
    }

    // Set up an interval for automation
    setInterval(automateBattle, randomizeCooldown ? getRandomCooldown() : cooldownMin);

    // GUI creation with a toggle button
    function createGUI() {
        // Create GUI container
        const gui = document.createElement('div');
        gui.id = 'battle-automation-gui';
        gui.innerHTML = `
            <div>
                <h3>Battle Automation</h3>
                <label><input type="checkbox" id="toggleAutoBattle" checked> Auto Battle</label><br>
                <label for="cooldownMin">Cooldown Min (ms):</label>
                <input type="number" id="cooldownMin" value="${cooldownMin}" min="0"><br>
                <label for="cooldownMax">Cooldown Max (ms):</label>
                <input type="number" id="cooldownMax" value="${cooldownMax}" min="0"><br>
                <label><input type="checkbox" id="toggleRandomizeCooldown" checked> Randomize Cooldown</label><br>
                <textarea id="logArea" rows="10" cols="40" readonly></textarea><br>
                <button id="copyLogButton">Copy Log</button><br>
            </div>
        `;
        document.body.appendChild(gui);

        // Create Show/Hide GUI button
        const toggleGUIButton = document.createElement('button');
        toggleGUIButton.id = 'toggleGUIButton';
        toggleGUIButton.textContent = 'Hide GUI';
        toggleGUIButton.style.position = 'fixed';
        toggleGUIButton.style.top = '10px';
        toggleGUIButton.style.right = '10px';
        toggleGUIButton.style.zIndex = '1001'; // Above GUI
        document.body.appendChild(toggleGUIButton);

        // Event listeners for toggling settings
        document.getElementById('toggleAutoBattle').addEventListener('change', (e) => {
            autoBattleEnabled = e.target.checked;
            logMessage(`Auto Battle: ${autoBattleEnabled}`);
        });

        // Event listeners for cooldown settings
        document.getElementById('cooldownMin').addEventListener('change', (e) => {
            cooldownMin = parseInt(e.target.value, 10);
            logMessage(`Cooldown Min set to: ${cooldownMin}`);
        });
        document.getElementById('cooldownMax').addEventListener('change', (e) => {
            cooldownMax = parseInt(e.target.value, 10);
            logMessage(`Cooldown Max set to: ${cooldownMax}`);
        });

        // Event listeners for randomizing cooldown
        document.getElementById('toggleRandomizeCooldown').addEventListener('change', (e) => {
            randomizeCooldown = e.target.checked;
            logMessage(`Randomize Cooldown: ${randomizeCooldown}`);
        });

        // Event listener for copying logs
        document.getElementById('copyLogButton').addEventListener('click', () => {
            const logArea = document.getElementById('logArea');
            logArea.select();
            document.execCommand('copy');
            logMessage('Log copied to clipboard.');
        });

        // Event listener for hiding/showing GUI
        document.getElementById('toggleGUIButton').addEventListener('click', () => {
            const gui = document.getElementById('battle-automation-gui');
            if (gui.style.opacity === '0') {
                gui.style.opacity = '1';
                document.getElementById('toggleGUIButton').textContent = 'Hide GUI';
            } else {
                gui.style.opacity = '0';
                document.getElementById('toggleGUIButton').textContent = 'Show GUI';
            }
        });

        // Inject CSS for GUI
        GM_addStyle(`
            #battle-automation-gui {
                position: fixed;
                top: 10px;
                right: 10px;
                background: rgba(255, 255, 255, 0.5); /* Semi-transparent background */
                border: 1px solid #ccc;
                padding: 10px;
                z-index: 1000;
                font-family: Arial, sans-serif;
                opacity: 1; /* Start with GUI visible */
                transition: opacity 0.3s ease; /* Smooth transition for hiding/showing */
            }
            #battle-automation-gui h3 {
                margin: 0;
                font-size: 16px;
                color: black;
            }
            #battle-automation-gui label,
            #battle-automation-gui input,
            #battle-automation-gui textarea,
            #battle-automation-gui button {
                font-size: 14px;
                color: black;
            }
            #logArea {
                color: red; /* Red text for the log area */
            }
        `);
    }

    // Create GUI on page load
    window.addEventListener('load', createGUI);
})();