DelugeRPG: Auto-Walker

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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();

})();