// ==UserScript== // @name pokechill助手 // @version 3.7 // @description 游戏变速器 + 自动重开:按钮优化(1/5/200/500)、跳过时间、拖拽修复、面板折叠 // @author 黄黄 // @match https://play-pokechill.github.io/* // @match https://g1tyx.github.io/play-pokechill/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; const CONFIG = { MIN_SPEED: 0.1, MAX_SPEED: 500.0, DEFAULT_SPEED: 1.0, STEP_SIZE: 0.5, UI_ZINDEX: 2147483647 }; const STORAGE = { enabled: 'msg_autoRejoinEnabled', count: 'msg_autoRejoinCount' }; const state = { speed: CONFIG.DEFAULT_SPEED, isActive: false, isMuted: false, autoRejoin: { enabled: localStorage.getItem(STORAGE.enabled) === '1', count: parseInt(localStorage.getItem(STORAGE.count) || '0', 10), clickedThisCycle: false, lastVisible: false }, startTime: { real: 0, virtual: 0 }, originals: { raf: null, date: null, dateNow: null, perfNow: null, setTimeout: null, setInterval: null }, ui: null }; function getVirtualTime(realTimeNow) { if (!state.isActive) return realTimeNow; const realDelta = realTimeNow - state.startTime.real; return state.startTime.virtual + (realDelta * state.speed); } function getRealNow() { if (state.originals.perfNow) { return state.originals.perfNow.call(window.performance); } return state.originals.dateNow.call(state.originals.date); } function updateTimeAnchor() { const realNow = getRealNow(); const currentVirtual = state.isActive ? getVirtualTime(realNow) : realNow; state.startTime.real = realNow; state.startTime.virtual = currentVirtual; state.isActive = true; } function skipTime(hours) { updateTimeAnchor(); const msToAdd = hours * 60 * 60 * 1000; state.startTime.virtual += msToAdd; const label = hours < 1 ? `${hours * 60}分钟` : `${hours}小时`; console.log(`[Pokechill助手] ⏳ 已跳过 ${label}`); const btnId = hours === 1.5 ? 'msg-skip-90m' : 'msg-skip-12h'; const btn = document.getElementById(btnId); if(btn) { const originalColor = btn.style.background; btn.style.background = "#27ae60"; setTimeout(() => { if(btn) btn.style.background = originalColor; }, 500); } } function isActuallyVisible(el) { return !!(el && el.offsetParent !== null && el.getClientRects().length > 0); } function checkAutoRejoin() { if (!state.autoRejoin.enabled) return; const btn = document.getElementById('area-rejoin'); if (!btn) return; const visible = isActuallyVisible(btn); if (!visible && state.autoRejoin.lastVisible) { state.autoRejoin.clickedThisCycle = false; } if (visible && !state.autoRejoin.lastVisible && !state.autoRejoin.clickedThisCycle) { state.autoRejoin.clickedThisCycle = true; btn.click(); state.autoRejoin.count++; localStorage.setItem(STORAGE.count, state.autoRejoin.count); updateUI(); console.log(`[Pokechill助手] 自动重开触发 (总次数: ${state.autoRejoin.count})`); } state.autoRejoin.lastVisible = visible; } function toggleAutoRejoin() { state.autoRejoin.enabled = !state.autoRejoin.enabled; localStorage.setItem(STORAGE.enabled, state.autoRejoin.enabled ? '1' : '0'); if (!state.autoRejoin.enabled) { state.autoRejoin.count = 0; localStorage.setItem(STORAGE.count, '0'); state.autoRejoin.clickedThisCycle = false; state.autoRejoin.lastVisible = false; } updateUI(); } // ================= 函数劫持 (Hooks) ================= function saveOriginals() { if (state.originals.date) return; const rafName = window.requestAnimationFrame ? 'requestAnimationFrame' : window.webkitRequestAnimationFrame ? 'webkitRequestAnimationFrame' : null; if (rafName) state.originals.raf = window[rafName]; state.originals.date = window.Date; state.originals.dateNow = Date.now; if (window.performance && window.performance.now) { state.originals.perfNow = window.performance.now; } state.originals.setTimeout = window.setTimeout; state.originals.setInterval = window.setInterval; } function hijackRAF() { if (!state.originals.raf) return; const rafPolyfill = (callback) => { return state.originals.raf.call(window, (realTimestamp) => { const virtualTimestamp = state.isActive ? getVirtualTime(realTimestamp) : realTimestamp; callback(virtualTimestamp); }); }; if (window.requestAnimationFrame) window.requestAnimationFrame = rafPolyfill; if (window.webkitRequestAnimationFrame) window.webkitRequestAnimationFrame = rafPolyfill; } function hijackPerformance() { if (!state.originals.perfNow) return; window.performance.now = () => { const realNow = state.originals.perfNow.call(window.performance); return state.isActive ? getVirtualTime(realNow) : realNow; }; } function hijackDate() { const OriginalDate = state.originals.date; const MockDate = function(...args) { if (args.length === 0 && state.isActive) { const realNow = state.originals.dateNow.call(OriginalDate); const offset = getVirtualTime(getRealNow()) - getRealNow(); return new OriginalDate(realNow + offset); } return new OriginalDate(...args); }; MockDate.prototype = OriginalDate.prototype; MockDate.UTC = OriginalDate.UTC; MockDate.parse = OriginalDate.parse; MockDate.now = () => { const realNow = state.originals.dateNow.call(OriginalDate); if (!state.isActive) return realNow; const offset = getVirtualTime(getRealNow()) - getRealNow(); return realNow + offset; }; window.Date = MockDate; } function hijackTimers() { window.setTimeout = (cb, delay, ...args) => { const scaledDelay = state.isActive ? (delay / state.speed) : delay; return state.originals.setTimeout.call(window, cb, scaledDelay, ...args); }; window.setInterval = (cb, delay, ...args) => { const scaledDelay = state.isActive ? (delay / state.speed) : delay; return state.originals.setInterval.call(window, cb, scaledDelay, ...args); }; } function setSpeed(targetSpeed) { targetSpeed = Math.max(CONFIG.MIN_SPEED, Math.min(CONFIG.MAX_SPEED, targetSpeed)); if (state.speed === targetSpeed && state.isActive) return; updateTimeAnchor(); state.speed = targetSpeed; updateUI(); } function toggleMute() { state.isMuted = !state.isMuted; document.querySelectorAll('audio, video').forEach(el => el.muted = state.isMuted); updateUI(); } function createUI() { if (state.ui) return; const ui = document.createElement('div'); ui.id = 'pokechill-helper-ui'; ui.style.cssText = ` position: fixed; top: 10px; left: 10px; width: 210px; background: rgba(16, 20, 25, 0.95); color: #fff; padding: 10px; border-radius: 8px; font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.6); z-index: ${CONFIG.UI_ZINDEX}; backdrop-filter: blur(5px); user-select: none; border: 1px solid rgba(255,255,255,0.1); box-sizing: border-box; `; ui.innerHTML = `