Greasy Fork is available in English.
Automatically walks on the DelugeRPG map with randomized human-like movement, detection pause, and safety features.
// ==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();
})();