DelugeRPG: Auto-Battle

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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