// ==UserScript== // @name 宝可梦点击(Poke Clicker)辅助脚本 脚本核心模块 // @namespace PokeClickerHelper // @version 0.1.5 // @description 核心模块作为基础依赖框架/库提供大量公共方法供其他功能模块调用,【必须安装】,否则功能模块无法生效。 // @author DreamNya、苍猫 // @match https://www.pokeclicker.com // @match https://g8hh.github.io/pokeclicker/ // @match https://pokeclicker.g8hh.com // @match https://yx.g8hh.com/pokeclicker/ // @match https://dreamnya.github.io/pokeclicker/ // @icon data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/319hf99fYX/fX2F/319hf8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////99fYX/fX2F/319hf8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////////wAAAP8AAAD/fX2F/319hf99fYX/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////8AAAD/AAAA/wAAAP99fYX/fX2F/wAAAP8AAAD/AAAA/319hf8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/HBT//xwU//8AAAD//////319hf8AAAD/Dgim/w4Ipv8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAD/HBT//xwU//8cFP//HBT//wAAAP8AAAD/Dgim/w4Ipv8OCKb/Dgim/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/xwU//8cFP//HBT//xwU/44cFP//HBT//xwU//8cFP//Dgim/w4Ipv8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/HBT//xwU/47/////HBT/jhwU//8cFP//HBT//w4Ipv8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xwU//8cFP//HBT/jhwU//8OCKb/Dgim/w4Ipv8OCKb/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/xwU//8OCKb/Dgim/w4Ipv8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAPw/AADwDwAA4AcAAOAHAADAAwAAwAMAAMADAADAAwAA4AcAAOAHAADwDwAA/D8AAP//AAD//wAA//8AAA== // @grant none // @license MIT // @run-at document-body // ==/UserScript== /* eslint-env jquery */ /* global Preload,NotificationConstants */ /* eslint no-implicit-globals:0, no-eval:0*/ const { scriptHandler, version } = GM_info; const [V, v] = version.split('.'); if (scriptHandler == "Tampermonkey") { if (V < 4 || (V == 4 && v < 16)) alert(`Tampermonkey版本过低 可能无法正常运行脚本\n建议更新至4.16+版本\n当前版本:${scriptHandler} ${version}`) } //////////////////// // 脚本公共Object // //////////////////// //所有方法均挂载在PokeClickerHelper对象下便于调用(简写PCH) let PokeClickerHelper = {}; let PCH = PokeClickerHelper; window.PCH = PokeClickerHelper; window.PokeClickerHelper = PokeClickerHelper; //存储或读取变量至localStorage 自动补全前缀PokeClickerHelper_,防止命名冲突 PokeClickerHelper.get = function (key, defaultValue) { let value = JSON.parse(localStorage.getItem('PokeClickerHelper_' + key)) return value ?? defaultValue } PokeClickerHelper.set = function (key, value) { localStorage.setItem('PokeClickerHelper_' + key, JSON.stringify(value)) } //通知函数 //默认设置 标题:'宝可梦点击(Poke Clicker)辅助脚本';样式:Danger;持续:10秒;默认不进行浏览器通知 PokeClickerHelper.Notify = function ({ title = '宝可梦点击(Poke Clicker)辅助脚本', message, timeout = 10000, type = NotificationConstants.NotificationOption.danger, ...args }, alert = false) { //节流 相同通知10秒内最多通知一次 const now = Date.now() if (now - PokeClickerHelper.NotifyThrottle[message] < 10000) return PokeClickerHelper.NotifyThrottle[message] = now //桌面通知 alert && new Notification(message) //游戏内通知 window.Notifier.notify({ title, message, timeout, type, ...args }) } PokeClickerHelper.NotifyThrottle = {} //委托初始化前函数:PokeClickerHelper.initBeforeList.add(fuc) //{fuc}自定义函数 PokeClickerHelper.initBeforeList = new Set() //读取存档前初始化函数 initBefore() function initBefore() { initHook() initGameTickHook() PokeClickerHelper.addHook('Game', forceWebWorker) initWebWorker() initUI() //PokeClickerHelper.hookGameTickList.add(() => console.log('tick测试')) initAfterHook() PokeClickerHelper.initBeforeList.forEach(i => i()) PokeClickerHelper.Notify({ title: '宝可梦点击(Poke Clicker)辅助脚本', message: "加载成功" }); PokeClickerHelper.addHook('Game', initAfterStartHook) } //委托初始化后函数:PokeClickerHelper.initAfterList.add(fuc) //{fuc}自定义函数 PokeClickerHelper.initAfterList = new Set() //读取存档后初始化函数 function initAfter() { appendUI() PokeClickerHelper.initAfterList.forEach(i => i()) } function initAfterHook() { const realHideSplashScreen = Preload.hideSplashScreen Preload.hideSplashScreen = function (fast = false) { initAfter() realHideSplashScreen(fast) } } //强制游戏使用Web Worker计时器,减少延迟 //暂不支持自定义 TODO:Script Setting function forceWebWorker(Game) { const replacement = [["let pageHidden = document.hidden;", "/*let pageHidden = document.hidden;"], ["// Try start our webworker so we can process stuff while the page isn't focused", "*/"], ["let pageHidden = false;\n self.onmessage = function(e) {\n if (e.data.pageHidden != undefined) {\n pageHidden = e.data.pageHidden;\n }\n };", ""], ["if (!pageHidden) return;", ""], ["Settings.getSetting('useWebWorkerForGameTicks').value ? this.gameTick() : null", "this.gameTick()"], ["document.addEventListener('visibilitychange', () => {", "/*document.addEventListener('visibilitychange', () => {"], ["this.worker.postMessage({ 'pageHidden': pageHidden });\n if (this.worker) {", "this.worker.postMessage({ 'pageHidden': pageHidden });*/\n if (this.worker) {"]]; PokeClickerHelper.HookFuc(Game, 'start', replacement, ''); } PokeClickerHelper.initAfterStartList = new Set() //游戏完全加载后初始化函数 function initAfterStartHook(Game) { const realStart = Game.start.bind(Game) Game.start = function () { realStart() PokeClickerHelper.initAfterStartList.forEach(i => i()) } } ////////////////// // 劫持对象方法 // ////////////////// //通过function.toString().replace劫持替换原有函数 //会自动在hook object对象下挂载3个属性{real_prop}原生函数、{hook_prop}劫持函数、{hookStorage}存储prop名方便还原或再次劫持 //调用后直接劫持并应用,如只想劫持不应用,需要额外调用PokeClickerHelper.restoreHook({obj}) //注:暂不支持多模块同时劫持同一个对象并分别还原 //(目前理念是各模块功能明确,互不干涉,暂不存在多模块需要劫持同一个对象情况) //调用方法 //劫持对象属性 PokeClickerHelper.HookFuc({object}, {prop}, {replacement}, {argName}) //{object}劫持对象 {prop}劫持属性 {replacement}替换函数体内容(二维数组) {argName}替换函数传入参数名(字符串 以,分隔) // //还原已劫持对象所有劫持函数 PokeClickerHelper.restoreHook({object}) //{object}已劫持对象 // //重新劫持已还原劫持对象所有劫持函数 PokeClickerHelper.applyHook({object}) //{object}已还原劫持对象 // //额外功能:(通常用于脚本内自定义函数格式化,其结果通常用于与网页劫持函数拼接) //不劫持只返回格式化函数体 PokeClickerHelper.HookFucBody({fucntion}) //{fucntion}需要格式化的函数 PokeClickerHelper.HookFuc = function (obj, prop, replacement, arg) { const fuc = obj[prop] let text = fuc.toString() text = text.slice(text.indexOf('{') + 1, -1) for (let item of replacement) { if (typeof item[0] == 'string') { //string则includes;RegExp则test if (!text.includes(item[0])) { //先检测是否存在 不存在代表脚本过时 停止注入 alert('替换失败 脚本可能需要更新' + item[0]) return false } } else { if (!item[0].test(text)) { alert('替换失败 脚本可能需要更新' + item[0]) return false } } text = text.replace(item[0], item[1]) } const newFuc = new Function(arg, text) obj['real_' + prop] = obj[prop] obj[prop] = newFuc obj['hook_' + prop] = newFuc obj.hookStorage = obj.hookStorage || [] obj.hookStorage.push(prop) } //还原替换hook函数 //prop为指定还原,无prop则为全部还原 PokeClickerHelper.restoreHook = function (obj, prop) { if (prop != void 0) { if (!obj['real_' + prop]) return obj[prop] = obj['real_' + prop] } else { Object.values(obj.hookStorage).forEach(i => { obj[i] = obj['real_' + i] }) } } //重新劫持hook函数 //prop为指定劫持,无prop则为全部劫持 PokeClickerHelper.applyHook = function (obj, prop) { if (prop != void 0) { if (!obj['hook_' + prop]) return obj[prop] = obj['hook_' + prop] } else { Object.values(obj.hookStorage).forEach(i => { obj[i] = obj['hook_' + i] }) } } //格式化函数体 PokeClickerHelper.HookFucBody = function (fuc) { let text = fuc.toString() return text.slice(text.indexOf('{') + 1, -1) } //////////////////// // 委托Web Worker // //////////////////// //Web Worker 用于执行tick间隔低于100ms函数 //每种tick各生成一个Web Worker,相同tick函数共用同一Web Worker //不建议生成过多Web Worker影响效率 //(暂不支持传参,如要传参可以改为读写全局变量/对象上的属性) //调用方法 //创建一个setInterval循环线程 PokeClickerHelper.Worker.setInterval(callback,delay) //创建一个setTimeout循环线程 PokeClickerHelper.Worker.setTimeout(callback,delay) //注:setTimeout循环线程同样不断循环执行,并非只执行一次,循环方法不同(setInterval为执行函数前计算间隔;setTimeout为执行函数后计算间隔) // //删除一个setInterval循环线程 PokeClickerHelper.Worker.clearInterval(callback,delay) //删除一个setTimeout循环线程 PokeClickerHelper.Worker.claerTimeout(callback,delay) //注:删除循环线程需要传入创建时传入的{callback}函数及{delay}间隔 // //{callback}需要不断执行的tick函数 //{delay}间隔毫秒 function initWebWorker() { PokeClickerHelper.Worker = {} PokeClickerHelper.Worker.setInterval = function (callback, delay) { let callbackList = this['intervalList' + delay] if (callbackList == void 0) { callbackList = new Set() let workerURL = URL.createObjectURL(new Blob([`setInterval(()=>{postMessage('')},${delay})`])) this.intervalWorker = new Worker(workerURL) this.intervalWorker.onmessage = () => { callbackList.forEach(i => i()) } URL.revokeObjectURL(workerURL) //垃圾回收 this['intervalList' + delay] = callbackList } callbackList.add(callback) } PokeClickerHelper.Worker.setTimeout = function (callback, delay) { let callbackList = this['timeoutList' + delay] if (callbackList == void 0) { callbackList = new Set() let workerURL = URL.createObjectURL(new Blob([`(function Timeout(){postMessage('');setTimeout(Timeout,${delay})})()`])) this.timeoutWorker = new Worker(workerURL) this.timeoutWorker.onmessage = () => { callbackList.forEach(i => i()) } URL.revokeObjectURL(workerURL) //垃圾回收 this['timeoutList' + delay] = callbackList } callbackList.add(callback) } PokeClickerHelper.Worker.clearInterval = function (callback, delay) { let callbackList = this['intervalList' + delay] if (!callbackList) return callbackList.delete(callback) if (callbackList.size == 0) { this.intervalWorker.terminate() this['intervalList' + delay] = void 0 } } PokeClickerHelper.Worker.clearTimeout = function (callback, delay) { let callbackList = this['timeoutList' + delay] if (!callbackList) return callbackList.delete(callback) if (callbackList.size == 0) { this.intervalWorker.terminate() this['timeoutList' + delay] = void 0 } } } /////////////////////// // 委托劫持class函数 // /////////////////////// //委托劫持class,只委托脚本运行后无法直接访问需要额外new的class,不委托已经new过可直接访问的class,如:Battle、MapHelper //调用方法: //初始化委托劫持class函数:PokeClickerHelper.addHook({className},{fuction(new hookClass)}}) //{className} 委托劫持class函数名称 //{fuction(new hookClass)} 委托劫持的class函数对应的劫持方法,传参:劫持后的new hookClass function initHook() { PokeClickerHelper.hookLists = new Set() PokeClickerHelper.addHook = function (className, hookFuc) { if (PokeClickerHelper.hookLists.has(className)) { PokeClickerHelper['hook' + className + 'List'].add(hookFuc) } else { PokeClickerHelper.hookLists.add(className) PokeClickerHelper['hook' + className + 'List'] = new Set([hookFuc]) //eval may not harmful but useful(=_=!) PokeClickerHelper['real' + className] = eval(className) eval(className + '=' + function (...args) { const hookedFuc = new PokeClickerHelper['real' + className](...args) //劫持class PokeClickerHelper['hook' + className] = hookedFuc PokeClickerHelper['hook' + className + 'List'].forEach(i => i(hookedFuc)) //调用委托劫持函数 传入劫持后的class return hookedFuc }) } } } /////////////////////////////// // 委托劫持Game.gameTick函数 // /////////////////////////////// //利用游戏原生Game.gameTick函数运行脚本定时代码 //(游戏默认100ms执行一次Game.gameTick函数 支持Web Worker较精确定时器) //调用方法: //委托增加gameTick执行函数:PokeClickerHelper.hookGameTickList.add({fuction}) //委托删除gameTick执行函数:PokeClickerHelper.hookGameTickList.delete({fuction}) //{function}委托gameTick执行函数 function initGameTickHook() { PokeClickerHelper.hookGameTickList = new Set() const gameTickHook = function (Game) { Game.realGameTick = Game.gameTick Game.gameTick = function () { PokeClickerHelper.hookGameTickList.forEach(i => i()) //调用委托劫持Game.gameTick函数 Game.realGameTick() } } //初始化劫持Game 并将委托劫持Game.gameTick函数加入到Game class劫持 PokeClickerHelper.addHook('Game', gameTickHook) } //////////////// // 可视化界面 // //////////////// //同时机不同模块UI加载顺序根据油猴等脚本管理器加载顺序决定 // //以油猴为例, //核心模块运行时机为// @run-at document-start //其余所有功能模块运行时机均应为// @run-at document-body //无论脚本管理器中脚本序号,核心模块必定优先于功能模块加载 //而功能模块加载时机相同,因此功能模块UI加载顺序根据脚本管理器中功能模块脚本序号决定,序号越小加载越早 // //若核心模块脚本序号为10,孵蛋模块脚本序号为1,自动地牢/道馆脚本序号为5, // 加载顺序:核心模块→孵蛋模块→自动地牢/道馆模块。面板UI显示顺序:孵蛋模块→自动地牢/道馆模块 //若核心模块脚本序号为10,孵蛋模块脚本序号为4,自动地牢/道馆脚本序号为2, // 加载顺序:核心模块→自动地牢/道馆模块→孵蛋模块。面板UI显示顺序:自动地牢/道馆模块→孵蛋模块 //调用方法: //增加css样式:PokeClickerHelper.UIstyle({styleText}) //{styleText}仅style内css样式文字(不含' + PokeClickerHelper.UIcontainer) $('#settingsModal .nav.nav-tabs').append(PokeClickerHelper.UIScriptTab) //Author:猫猫 $('#settingsModal .tab-content').eq(0).append(PokeClickerHelper.UIScriptTbody) //Author:猫猫 PokeClickerHelper.UIDOM.forEach(i => $('#PokeClickerHelperBody').append(i)) PokeClickerHelper.UIScript.forEach(i => $('#PokeClickerHelperSettingsTbody').append(i)) //Author:猫猫 PokeClickerHelper.UICustomFuc.forEach(i => i()) PokeClickerHelper.UIlistener.forEach(i => i()) PokeClickerHelper.UIsettings() //读取主面板UI DOM元素存储并设置、触发监听事件 }