DelugeRPG: Auto-Battle

Auto-battler with battle type hotkey (Normal/Inverse)

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         DelugeRPG: Auto-Battle
// @version      v3.2
// @description  Auto-battler with battle type hotkey (Normal/Inverse)
// @author       Anxarden
// @namespace    https://greasyfork.org/users/789978-anxarden
// @license      MIT
// @match        https://www.delugerpg.com/battle/trainer
// @match        https://www.delugerpg.com/battle/trainer/*
// @match        https://www.delugerpg.com/battle/user
// @match        https://www.delugerpg.com/battle/user/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const CFG = {
        minDelay: 250,
        maxDelay: 650,
        rateLimitWait: 11500,
        sessionMs: 15 * 60 * 1000,
        breakMs: 3 * 60 * 1000,
        observerThrottle: 200
    };

    let state = {
        cooldown: false,
        lastFire: 0,
        sessionStart: Date.now(),
        battleMode: 'normal'  // 'normal' or 'inverse'
    };

    // ── LOAD/SAVE SETTINGS ──
    function loadSettings() {
        const saved = localStorage.getItem('deluge_battle_mode');
        if (saved === 'normal' || saved === 'inverse') {
            state.battleMode = saved;
        }
    }

    function saveSettings() {
        localStorage.setItem('deluge_battle_mode', state.battleMode);
    }

    // ── TOAST NOTIFICATION ──
    function showToast(message, isError = false) {
        const toast = document.createElement('div');
        toast.textContent = message;
        toast.style.position = 'fixed';
        toast.style.bottom = '20px';
        toast.style.right = '20px';
        toast.style.backgroundColor = isError ? '#f44336' : '#4CAF50';
        toast.style.color = 'white';
        toast.style.padding = '8px 16px';
        toast.style.borderRadius = '8px';
        toast.style.zIndex = '9999';
        toast.style.fontSize = '14px';
        toast.style.fontWeight = 'bold';
        toast.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
        document.body.appendChild(toast);
        setTimeout(() => toast.remove(), 2000);
    }

    // ── HOTKEY TOGGLE ──
    document.addEventListener('keydown', (e) => {
        if (e.key.toLowerCase() === 'b') {
            e.preventDefault();
            state.battleMode = state.battleMode === 'normal' ? 'inverse' : 'normal';
            saveSettings();
            const modeName = state.battleMode === 'normal' ? 'Normal Battle' : 'Inverse Battle';
            showToast(`Battle mode: ${modeName}`);
            console.log(`[EB] Battle mode switched to ${state.battleMode}`);
            // Immediately re-check for buttons
            executeAction();
        }
    });

    // ── SKEWED DELAY ──
    function getSkewedDelay() {
        const { minDelay: min, maxDelay: max } = CFG;
        const range = max - min;
        const r = Math.random();
        if (r < 0.70) {
            return min + Math.floor(Math.random() * range * 0.35);
        }
        if (r < 0.90) {
            return min + Math.floor(range * 0.35) + Math.floor(Math.random() * range * 0.40);
        }
        return min + Math.floor(range * 0.75) + Math.floor(Math.random() * range * 0.25);
    }

    // ── CLICK ──
    function clickElement(el) {
        if (!el || el.disabled) return false;
        el.click();
        return true;
    }

    // ── FIND BUTTON BY TEXT ──
    function findButtonByText(label) {
        const lowerLabel = label.toLowerCase();
        const buttons = document.querySelectorAll(`
            button, a, input[type="submit"], input[type="button"],
            .btn, .jconfirm-buttons button, .jconfirm button
        `);
        for (const btn of buttons) {
            const text = (btn.innerText || btn.value || '').trim().toLowerCase();
            if (text.includes(lowerLabel) && btn.offsetParent !== null && !btn.disabled) {
                return btn;
            }
        }
        return null;
    }

    // ── SLOT 1 MOVE ──
    function getSlot1Move() {
        const moveSelectors = [
            '.move-button:first-child',
            '.moves-list .move:first-child button',
            '#battle-moves .move:first-child',
            '.battle-moves button:first-of-type',
            '.moves button:nth-child(1)'
        ];
        for (const sel of moveSelectors) {
            const el = document.querySelector(sel);
            if (el?.offsetParent !== null && !el.disabled) return el;
        }
        const moveArea = document.querySelector('.moves, #moves, .battle-moves, .move-list');
        if (moveArea) {
            return [...moveArea.querySelectorAll('button, .move-button')].find(
                b => b.offsetParent !== null && !b.disabled
            );
        }
        return null;
    }

    // ── SESSION BREAK ──
    function handleSessionBreak() {
        if (Date.now() - state.sessionStart > CFG.sessionMs) {
            console.log('[EB] Session break — 3 minutes pause.');
            state.cooldown = true;
            setTimeout(() => {
                state.cooldown = false;
                state.sessionStart = Date.now();
                console.log('[EB] Break finished, resuming.');
                executeAction();
            }, CFG.breakMs);
            return true;
        }
        return false;
    }

    // ── BLOCKERS (captcha, rate limit) ──
    function isBlockedPage() {
        const bodyText = document.body.innerText;
        if (document.title.includes('Just a moment') || document.querySelector('#cf-wrapper')) {
            console.warn('[EB] Cloudflare challenge – stopping.');
            return true;
        }
        if (bodyText.includes('Security Check') ||
            document.querySelector('img[src*="captcha"], .g-recaptcha')) {
            console.warn('[EB] Captcha – stopping.');
            return true;
        }
        if (/one battle every 10 seconds/i.test(bodyText)) {
            const wait = CFG.rateLimitWait + Math.floor(Math.random() * 2000);
            console.log(`[EB] Rate limit – waiting ${(wait / 1000).toFixed(1)}s`);
            state.cooldown = true;
            setTimeout(() => { state.cooldown = false; }, wait);
            return true;
        }
        return false;
    }

    // ── DYNAMIC ACTIONS (respects battleMode) ──
    function getActions() {
        const baseActions = [
            { name: 'Start Battle',    find: () => findButtonByText('Start Battle') },
            { name: 'Slot 1 Move',     find: getSlot1Move },
            { name: 'Battle Again',    find: () => findButtonByText('Battle Again') },
            { name: 'Same User Again', find: () => findButtonByText('Battle Same User Again') },
            { name: 'Continue',        find: () => findButtonByText('Continue') },
            { name: 'Next',            find: () => findButtonByText('Next') },
            { name: 'Attack',          find: () => findButtonByText('Attack') }
        ];

        // Insert the selected battle type at the beginning (highest priority)
        if (state.battleMode === 'normal') {
            return [
                { name: 'Normal Battle', find: () => findButtonByText('!Normal Battle') },
                ...baseActions
            ];
        } else {
            return [
                { name: 'Inverse Battle', find: () => findButtonByText('Inverse Battle') },
                ...baseActions
            ];
        }
    }

    // ── MAIN ACTION ──
    function executeAction() {
        if (state.cooldown) return;
        if (handleSessionBreak()) return;
        if (isBlockedPage()) return;

        const actions = getActions();
        for (const action of actions) {
            const btn = action.find();
            if (btn) {
                const delay = getSkewedDelay();
                console.log(`[EB] → ${action.name} in ${delay}ms`);
                setTimeout(() => clickElement(btn), delay);
                return;
            }
        }
    }

    // ── OBSERVER ──
    const observer = new MutationObserver(() => {
        const now = Date.now();
        if (now - state.lastFire >= CFG.observerThrottle) {
            state.lastFire = now;
            executeAction();
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    // ── HISTORY SUPPORT ──
    if (window.History && window.History.Adapter) {
        window.History.Adapter.bind(window, 'statechange', () => {
            setTimeout(executeAction, 500);
        });
    }

    // ── INIT ──
    loadSettings();
    executeAction();
    console.log(`[EB] Auto-Battle v3 online – mode: ${state.battleMode} (press B to toggle)`);
    showToast(`Battle mode: ${state.battleMode === 'normal' ? 'Normal' : 'Inverse'} (press B to toggle)`, false);
})();