DelugeRPG: Auto-Walker

Automatically walks on the DelugeRPG map with randomized human-like movement, detection pause, and safety features.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         DelugeRPG: Auto-Walker
// @namespace    https://greasyfork.org/users/Codex9
// @version      1
// @description  Automatically walks on the DelugeRPG map with randomized human-like movement, detection pause, and safety features.
// @author       Codex9
// @license      MIT
// @match        https://m.delugerpg.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // ═══════════════════════════════════════════════════════════════
    // TARGETS — edit to customise
    // ═══════════════════════════════════════════════════════════════
    const TARGET_POKEMON = [
        'Meowth', 'Munchlax', 'Eevee', 'Snorlax', 'Charmander', 'Kangaskhan'
    ];
    const SHINY_TYPES = [
        'Dark', 'Chrome', 'Retro', 'Shiny'
    ];
    const ALWAYS_STOP = [
        'Mythical', 'Legendary'
    ];
    // ═══════════════════════════════════════════════════════════════

    const CFG = {
        moveMin: 850,
        moveMax: 1600,
        boundTiles: 4,
        softZone: 2,
        distractChance: 0.012,
        sessionMs: 20 * 60 * 1000,
        breakMs: 4 * 60 * 1000,
        heartbeatMs: 3000,
        startDelay: 1800
    };

    const CARDINALS = [
        { dir: 'n', dx: 0, dy: -1 },
        { dir: 's', dx: 0, dy: 1 },
        { dir: 'e', dx: 1, dy: 0 },
        { dir: 'w', dx: -1, dy: 0 }
    ];

    const DIAGONALS = [
        { dir: 'ne', dx: 1, dy: -1 },
        { dir: 'nw', dx: -1, dy: -1 },
        { dir: 'se', dx: 1, dy: 1 },
        { dir: 'sw', dx: -1, dy: 1 }
    ];

    const ALL_MOVES = [...CARDINALS, ...DIAGONALS];

    // ── STATE ─────────────────────────────────────────────────────────
    const S = {
        x: 0,
        y: 0,
        active: false,
        paused: false,
        waiting: false,
        sessionStart: Date.now(),
        lastMove: Date.now(),
        lastFire: 0,
        moves: 0,
        lastURL: location.href,
        observer: null,
        observedEl: null,
        heartbeat: null
    };

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

    // ── BOX-MULLER GAUSSIAN ───────────────────────────────────────────
    // Kept for walker — direction buttons are small, center bias prevents misses
    function gaussian() {
        let u = 0, v = 0;
        while (u === 0) u = Math.random();
        while (v === 0) v = Math.random();
        const n = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
        return Math.min(0.85, Math.max(0.15, 0.5 + n * 0.15));
    }

    // ── PHANTOM CLICK ─────────────────────────────────────────────────
    function phantomClick(el) {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const cx = rect.left + rect.width * gaussian();
        const cy = rect.top + rect.height * gaussian();
        const base = { bubbles: true, view: window, clientX: cx, clientY: cy };

        el.dispatchEvent(new PointerEvent('pointerdown', base));
        el.dispatchEvent(new MouseEvent('mousedown', base));

        setTimeout(() => {
            el.dispatchEvent(new MouseEvent('mouseup', base));
            el.dispatchEvent(new PointerEvent('pointerup', base));
            el.click();
        }, 40 + Math.floor(Math.random() * 60));

        return true;
    }

    // ── RUBBER BAND MOVE PICKER ───────────────────────────────────────
    function pickMove() {
        const dist = Math.max(Math.abs(S.x), Math.abs(S.y));

        // Hard wall — only inward moves allowed at boundary
        if (dist >= CFG.boundTiles) {
            const inward = ALL_MOVES.filter(m => {
                const nx = S.x + m.dx;
                const ny = S.y + m.dy;
                return Math.max(Math.abs(nx), Math.abs(ny)) < dist;
            });
            if (inward.length > 0) {
                const pick = inward[Math.floor(Math.random() * inward.length)];
                console.log(`[PW] Hard wall (${S.x},${S.y}) → ${pick.dir}`);
                return pick;
            }
        }

        const pool = [];

        for (const m of ALL_MOVES) {
            const nx = S.x + m.dx;
            const ny = S.y + m.dy;
            const newDist = Math.max(Math.abs(nx), Math.abs(ny));

            // Never step outside hard boundary
            if (newDist > CFG.boundTiles) continue;

            let weight = 10;

            if (dist > CFG.softZone) {
                const op = (dist - CFG.softZone) / (CFG.boundTiles - CFG.softZone);
                if (newDist < dist) {
                    weight = Math.floor(10 + op * 30);
                } else if (newDist === dist) {
                    weight = Math.floor(10 * (1 - op * 0.7));
                } else {
                    weight = Math.floor(10 * (1 - op * 0.9));
                }
            }

            weight = Math.max(1, weight);
            for (let i = 0; i < weight; i++) pool.push(m);
        }

        if (pool.length === 0) {
            const safe = ALL_MOVES.filter(m =>
                Math.max(Math.abs(S.x + m.dx), Math.abs(S.y + m.dy)) <= CFG.boundTiles
            );
            return safe.length > 0
                ? safe[Math.floor(Math.random() * safe.length)]
                : CARDINALS[0];
        }

        return pool[Math.floor(Math.random() * pool.length)];
    }

    // ── PRIORITY DETECTOR ─────────────────────────────────────────────
    function checkPriority() {
        const body = document.body.innerText;
        if (!body.includes('Appeared!')) return false;

        for (const word of ALWAYS_STOP) {
            if (body.includes(word)) {
                stopWalker(`!!! ${word.toUpperCase()} DETECTED !!!`);
                return true;
            }
        }

        const hasType = SHINY_TYPES.some(t => body.includes(t));
        const hasName = TARGET_POKEMON.some(n => body.includes(n));

        if (hasType && hasName) {
            const lines = body.split('Appeared!')[0].trim().split('\n');
            const name = lines[lines.length - 1].trim();
            stopWalker(`!!! TARGET: ${name} !!!`);
            return true;
        }

        return false;
    }

    function stopWalker(msg) {
        S.paused = true;
        console.warn(`[PW] ${msg}`);
        if (navigator.vibrate) navigator.vibrate([500, 200, 500, 200, 500]);
        try {
            const ctx = new (window.AudioContext || window.webkitAudioContext)();
            const osc = ctx.createOscillator();
            osc.connect(ctx.destination);
            osc.frequency.value = 880;
            osc.start();
            setTimeout(() => osc.stop(), 800);
        } catch (e) {}
    }

    // ── FIND DIRECTION BUTTON ─────────────────────────────────────────
    function findDirBtn(dir) {
        const fn = window.mapMove || window.move;
        if (typeof fn === 'function') return { native: fn, dir };

        return (
            document.querySelector(`a[onclick*="'${dir}'"]`) ||
            document.querySelector(`[id$='_${dir}']`) ||
            document.querySelector(`[data-dir="${dir}"]`) ||
            [...document.querySelectorAll('a, button')].find(
                el => (el.innerText || '').trim().toLowerCase() === dir
            ) ||
            null
        );
    }

    // ── EXECUTE MOVE ──────────────────────────────────────────────────
    function doMove() {
        if (S.paused || S.waiting || !S.active) return;
        if (checkPriority()) return;

        // Distraction pause — 1.2% chance
        if (Math.random() < CFG.distractChance) {
            const idle = 5000 + Math.floor(Math.random() * 8000);
            S.paused = true;
            setTimeout(() => {
                S.paused = false;
                scheduleMove();
            }, idle);
            return;
        }

        const chosen = pickMove();
        const btn = findDirBtn(chosen.dir);

        if (!btn) {
            setTimeout(doMove, 600);
            return;
        }

        if (btn.native) {
            btn.native(btn.dir);
        } else {
            phantomClick(btn);
        }

        S.x += chosen.dx;
        S.y += chosen.dy;
        S.waiting = true;
        S.lastMove = Date.now();
        S.moves++;

        console.log(
            `[PW] #${S.moves} ${chosen.dir.toUpperCase()} (${S.x},${S.y}) d:${Math.max(Math.abs(S.x), Math.abs(S.y))}`
        );
    }

    // ── SCHEDULE MOVE ─────────────────────────────────────────────────
    function scheduleMove() {
        if (!S.active || S.paused) return;
        setTimeout(doMove, skew(CFG.moveMin, CFG.moveMax));
    }

    // ── SESSION BREAK ─────────────────────────────────────────────────
    function checkSession() {
        if (Date.now() - S.sessionStart > CFG.sessionMs) {
            S.paused = true;
            console.log('[PW] Session break.');
            setTimeout(() => {
                S.paused = false;
                S.sessionStart = Date.now();
                S.moves = 0;
                scheduleMove();
            }, CFG.breakMs);
            return true;
        }
        return false;
    }

    // ── OBSERVER ──────────────────────────────────────────────────────
    function setupObserver() {
        const target = document.getElementById('map-grid') || document.body;
        if (S.observer && S.observedEl === target) return;
        if (S.observer) S.observer.disconnect();

        S.observer = new MutationObserver(() => {
            if (!S.waiting) return;
            const now = Date.now();
            if (now - S.lastFire < 350) return;
            S.lastFire = now;
            S.waiting = false;
            if (checkSession()) return;
            scheduleMove();
        });

        S.observer.observe(target, { childList: true, subtree: true });
        S.observedEl = target;
        console.log(`[PW] Observer: ${target.id || 'body'}`);
    }

    // ── URL WATCHER ───────────────────────────────────────────────────
    function onURL() {
        const cur = location.href;
        if (cur === S.lastURL) return;
        S.lastURL = cur;
        console.log(`[PW] URL: ${cur}`);

        if (cur.includes('/map/')) {
            resetAndInit();
        } else {
            S.active = false;
            S.paused = true;
            if (S.observer) {
                S.observer.disconnect();
                S.observer = null;
                S.observedEl = null;
            }
        }
    }

    // Native pushState
    const _push = history.pushState;
    history.pushState = function() {
        _push.apply(history, arguments);
        setTimeout(onURL, 500);
    };

    // Native popstate
    window.addEventListener('popstate', () => setTimeout(onURL, 500));

    // history.js library (DelugeRPG uses this)
    if (window.History && window.History.Adapter) {
        window.History.Adapter.bind(window, 'statechange', () => {
            setTimeout(onURL, 500);
        });
    }

    // Fallback poll
    setInterval(onURL, 1000);

    // ── HEARTBEAT ─────────────────────────────────────────────────────
    function startHeartbeat() {
        if (S.heartbeat) clearInterval(S.heartbeat);
        S.heartbeat = setInterval(() => {
            if (!S.active || S.paused) return;
            setupObserver();
            if (S.waiting && Date.now() - S.lastMove > 6000) {
                console.log('[PW] Stuck — resetting.');
                S.waiting = false;
                scheduleMove();
            }
        }, CFG.heartbeatMs);
    }

    // ── RESET + INIT ──────────────────────────────────────────────────
    function resetAndInit() {
        S.x = 0;
        S.y = 0;
        S.active = false;
        S.paused = false;
        S.waiting = false;
        S.lastFire = 0;
        S.moves = 0;
        S.lastMove = Date.now();
        S.sessionStart = Date.now();
        if (S.observer) {
            S.observer.disconnect();
            S.observer = null;
            S.observedEl = null;
        }
        init();
    }

    function init() {
        if (!location.href.includes('/map/')) return;
        S.active = true;
        setupObserver();
        startHeartbeat();
        setTimeout(scheduleMove, CFG.startDelay);
        console.log(
            `[PW] Phantom Walker 30.5 online | ±${CFG.boundTiles} tiles | soft ±${CFG.softZone}`
        );
    }

    init();

})();