Greasy Fork is available in English.

【脚本停止维护 有问题自行解决】宝可梦点击(Poke Clicker)辅助脚本 脚本核心模块

核心模块作为基础依赖框架/库提供大量公共方法供其他功能模块调用,【必须安装】,否则功能模块无法生效。

// ==UserScript==
// @name         【脚本停止维护 有问题自行解决】宝可梦点击(Poke Clicker)辅助脚本 脚本核心模块
// @namespace    PokeClickerHelper
// @version      1.0
// @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样式文字(不含<style>节点)
//
//增加#PokeClickerHelperBody内DOM元素:PokeClickerHelper.UIDOM({DOMText})
//{DOMText} DOM元素文字(需含子元素节点<div>等)
//
//增加 开始菜单=>设置=>Script标签页内DOM元素:PokeClickerHelper.UIScript({DOMText})
//{DOMText} DOM元素文字(需含子元素节点<tr>等)
//
//增加DOM元素监听事件:PokeClickerHelper.UIlistener.push({DOMlistener})
//{DOMlistener} DOM监听事件函数
//
//如果上述静态方法均无法满足需求,可尝试以下动态自定义方法
//增加动态自定义UI函数:PokeClickerHelper.UICustomFuc.push({UICustomFun})
//{UICustomFun} 自定义UI函数
//
//增加非#PokeClickerHelperBody UI面板:PokeClickerHelper.UIContainerID.push({UIContainerID})
//{UIContainerID} 自定义UI面板ID(增加后与#PokeClickerHelperBody适用相同方法)

function initUI() {
    PokeClickerHelper.UIstyle = []
    PokeClickerHelper.UIDOM = []
    PokeClickerHelper.UIScript = [] //Author:猫猫
    PokeClickerHelper.UIlistener = []
    PokeClickerHelper.UICustomFuc = []
    PokeClickerHelper.UIContainerID = ['#PokeClickerHelperContainer']
    //TODO 先这样凑合用吧 摆烂 看着CSS就头疼
    const style = `
        #PokeClickerHelperContainer{z-index:1;font-family:"Open Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";position:absolute;top:38px;right:0;width:375px;background-color:white;opacity:.9;padding:10px;font-size:12px}
        #PokeClickerHelperContainer button{margin-left:5px}
        #PokeClickerHelperContainer .custom-row{margin-top:10px;display:flex}
        #PokeClickerHelperContainer .form-group{margin-bottom:5px}
        .labelContainer{width:93px;display:inline-block}
        .contentContainer{flex:1;padding-left:0px;padding-right:0px}
        .opacity-25{opacity: 0.25}
        #PokeClickerHelperContainer input[type=checkbox]{position: absolute;margin-top: 0.24rem;margin-left: -1rem;}
    `
    PokeClickerHelper.UIcontainer = `
        <div id="PokeClickerHelperContainer" class="shadow bg-white border border-primary" style="width: 375px; top: 50px; right: 10px;">
            <div>
                <div class="d-inline">
                    <label>完全隐藏(Shift+F12):</label>
                </div>
                <button id="PokeClickerHelperToggle" class="btn btn-sm btn-primary">隐藏</button>
            </div>
            <div class="mt-2 mb-1 border-top border-secondary"></div>
            <div id="PokeClickerHelperBody">
            </div>
        </div>
    `

    //Author:猫猫
    PokeClickerHelper.UIScriptTab = `<li class="nav-item"><a class="nav-link" href="#PokeClickerHelperSettings-Script" data-toggle="tab">Script</a></li>`
    PokeClickerHelper.UIScriptTbody = `
        <div class="tab-pane" id = "PokeClickerHelperSettings-Script">
        <table class="table table-striped table-hover m-0">
            <tbody id="PokeClickerHelperSettingsTbody">
            </tbody>
        </table>
        </div>
    `

    const listener = function () {
        //DOM元素监听事件 触发监听事件后自动存储DOM.value(button、input、select均以value存储)
        const DOMlistener = function () {
            if (this.dataset.save == 'false') return
            if (this.nodeName == 'INPUT' && this.type == 'checkbox') this.value = this.checked
            PokeClickerHelper.set(PokeClickerHelper.formatId(this), this.value)
        }
        PokeClickerHelper.UIContainerID.forEach(i => {
            $(`${i} button`).on('click', DOMlistener)
            $(`${i} input,${i} select`).on('change', DOMlistener)
        })

        $('#PokeClickerHelperToggle').on('click', function () {
            if (this.value == '隐藏') {
                $('#PokeClickerHelperContainer *:not(#PokeClickerHelperToggle):not(:has(#PokeClickerHelperToggle))').addClass('d-none')
                $('#PokeClickerHelperToggle').text(this.value = '显示')
                $('#PokeClickerHelperContainer').css('width', '75px')
            } else {
                $('#PokeClickerHelperContainer *:not(#PokeClickerHelperToggle):not(:has(#PokeClickerHelperToggle))').removeClass('d-none')
                $('#PokeClickerHelperToggle').text(this.value = '隐藏')
                $('#PokeClickerHelperContainer').css('width', '375px')
            }
        })

        //拖拽 多重解构语法糖
        document.querySelector("#PokeClickerHelperContainer").addEventListener('mousedown', function ({ x: Gx, y: Gy, which, target: { nodeName } }) {
            if (which != 1 || nodeName == 'BUTTON' || nodeName == 'INPUT' || nodeName == 'SELECT') return

            const that = this
            const top = that.style.top.replace('px', '') * 1 - Gy
            const right = that.style.right.replace('px', '') * 1 + Gx

            const mousemove = function ({ x, y }) {
                that.style.top = top + y + 'px'
                that.style.right = right - x + 'px'
            }
            const mouseup = function ({ x, y }) {
                this.removeEventListener('mousemove', mousemove)
                this.removeEventListener('mouseup', mouseup)
                x != Gx && PokeClickerHelper.set('top', that.style.top)
                y != Gy && PokeClickerHelper.set('right', that.style.right)
            }
            document.addEventListener('mousemove', mousemove)
            document.addEventListener('mouseup', mouseup)
        })
        //Shift+F12隐藏
        document.addEventListener('keydown', ({ key, shiftKey }) => {
            if (key == 'F12' && shiftKey) $("#PokeClickerHelperContainer").toggleClass('d-none')
        })
    }
    PokeClickerHelper.UIstyle.push(style)
    PokeClickerHelper.UIlistener.push(listener)
}

//读取DOM.value(button、input、select均以value存储)
PokeClickerHelper.UIsettings = function () {
    const clickEvent = new Event('click')
    const changeEvent = new Event('change')
    PokeClickerHelper.UIContainerID.forEach(i => {
        $(`${i} button,${i} input,${i} select`).each(UIsettings)
    })
    $("#PokeClickerHelperContainer").css({ 'top': PokeClickerHelper.set('top', '50px'), 'right': PokeClickerHelper.get('right', '10px') })
    function UIsettings() {
        let value = PokeClickerHelper.get(PokeClickerHelper.formatId(this))
        if (value == void 0) return
        this.value = value
        switch (this.nodeName) {
            case 'BUTTON': this.dispatchEvent(clickEvent); break
            case "INPUT": {
                if (this.type == "checkbox") {
                    this.checked = JSON.parse(value);
                }
                break
            }
            case 'SELECT': this.dispatchEvent(changeEvent); break
            default: alert('UIsettings Error')
        }
    }
}

//去除DOM元素ID前缀(DOM元素ID应以PokeClickerHelper开头)
PokeClickerHelper.formatId = function (e) {
    let id = e.id
    if (!id.startsWith('PokeClickerHelper')) {
        console.log(e, 'id规则错误,应以PokeClickerHelper开头\nlocalStorage读写失败')
        alert(e.nodeName + ' id规则错误,应以PokeClickerHelper开头,详见控制台。\nlocalStorage读写失败')
        throw new Error('id规则错误,应以PokeClickerHelper开头\nlocalStorage读写失败')
    }
    return id.replace(/^PokeClickerHelper/, '')
}

//加载UI
function appendUI() {
    $('body').append('<style>' + PokeClickerHelper.UIstyle.join('\n') + '</style>' + 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元素存储并设置、触发监听事件
}