Greasy Fork is available in English.

wsmud_Trigger

武神传说 MUD

// ==UserScript==
// @name            wsmud_Trigger
// @namespace       cqv3
// @version         0.0.46
// @date            03/03/2019
// @modified        08/09/2022
// @homepage        https://greasyfork.org/zh-CN/scripts/378984
// @description     武神传说 MUD
// @author          Bob.cn, 初心, 白三三
// @match           http://*.wsmud.com/*
// @match           http://*.wamud.com/*
// @run-at          document-end
// @require         https://cdn.staticfile.org/vue/2.2.2/vue.min.js
// @grant           unsafeWindow
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_deleteValue
// @grant           GM_listValues
// @grant           GM_setClipboard
// ==/UserScript==

(function () {
    'use strict';

    function CopyObject(obj) {
        return JSON.parse(JSON.stringify(obj));
    }
    function is_match(src, input) {
        if (src.length == 0 && input.length == 0) {
            return true;
        }
        if (src[0] == "*" && src.length == 1) {
            return true;
        }
        if (src.length == 0 || input.length == 0) {
            return false;
        }
        if (src[0] == "?") {
            return is_match(src.substring(1), input.substring(1));
        } else
            if (src[0] == "*") {
                return is_match(src.substring(1), input) || is_match(src.substring(1), input.substring(1)) || is_match(src, input.substring(1));
            } else
                if (src[0] == input[0]) {
                    return is_match(src.substring(1), input.substring(1));
                } else {
                    return false;
                }

    }

    /***********************************************************************************\
        Notification Center
    \***********************************************************************************/

    class Notification {
        constructor(name, params) {
            this.name = name;
            this.params = params;
        }
    }

    class NotificationObserver {
        constructor(targetName, action) {
            this.targetName = targetName;
            this.action = action;
        }
    }

    const NotificationCenter = {
        observe: function (notificationName, action) {
            const index = this._getOberverIndex();
            const observer = new NotificationObserver(notificationName, action);
            this._observers[index] = observer;
            return index;
        },
        removeOberver: function (index) {
            delete this._observers[index];
        },
        /**
         * @param {Notification} notification
         */
        post: function (notification) {
            for (const key in this._observers) {
                if (!this._observers.hasOwnProperty(key)) continue;
                const observer = this._observers[key];
                if (observer.targetName != notification.name) continue;
                observer.action(notification.params);
            }
        },

        _observerCounter: 0,
        _observers: {},
        _getOberverIndex: function () {
            const index = this._observerCounter;
            this._observerCounter += 1;
            return index;
        }
    };

    /***********************************************************************************\
        Monitor Center
    \***********************************************************************************/

    class Monitor {
        constructor(run) {
            this.run = run;
        }
    }

    const MonitorCenter = {
        addMonitor: function (monitor) {
            this._monitors.push(monitor);
        },
        run: function () {
            for (const monitor of this._monitors) {
                monitor.run();
            }
        },

        _monitors: []
    };

    /***********************************************************************************\
        Trigger Template And Trigger
    \***********************************************************************************/

    //---------------------------------------------------------------------------
    //  Trigger Template
    //---------------------------------------------------------------------------

    const EqualAssert = function (lh, rh) {
        return lh == rh;
    };

    const ContainAssert = function (lh, rh) {
        if (/^\s*\*?\s*$/.test(lh)) return true;
        const list = lh.split("|");
        return list.indexOf(rh) != -1;
    };
    const ContainReverseAssert = function (lh, rh) {
        console.log(lh, rh);
        if (/^\s*\*?\s*$/.test(lh)) return true;
        const list = lh.split("|");
        return list.indexOf(rh) == -1;
    };

    const KeyAssert = function (lh, rh) {
        if (/^\s*\*?\s*$/.test(lh)) return true;
        const list = lh.split("|");
        for (const key of list) {
            if (rh.indexOf(key) != -1) return true;
        }
        return false;
    };

    class Filter {
        constructor(name, type, defaultValue, assert) {
            this.name = name;
            this.type = type;
            this.defaultValue = defaultValue;
            this.assert = assert == null ? EqualAssert : assert;
        }
        description(value) {
            if (value != null) {
                this._desc = value;
                return;
            }
            return this._desc == null ? this.name : this._desc;
        }
    }

    class SelectFilter extends Filter {
        constructor(name, options, defaultNumber, assert) {
            const defaultValue = options[defaultNumber];
            super(name, "select", defaultValue, assert);
            this.options = options;
        }
    }

    const InputFilterFormat = {
        number: "数字",
        text: "文本"
    };

    class InputFilter extends Filter {
        /**
         * @param {String} name
         * @param {InputFilterFormat} format
         * @param {*} defaultValue
         */
        constructor(name, format, defaultValue, assert) {
            super(name, "input", defaultValue, assert);
            this.format = format;
        }
    }

    class TriggerTemplate {
        constructor(event, filters, introdution) {
            this.event = event;
            this.filters = filters;
            this.introdution = `${introdution}\n// 如需更多信息,可以到论坛触发器版块发帖。`;
        }
        getFilter(name) {
            for (const filter of this.filters) {
                if (filter.name == name) return filter;
            }
            return null;
        }
    }

    const TriggerTemplateCenter = {
        add: function (template) {
            this._templates[template.event] = template;
        },
        getAll: function () {
            return Object.values(this._templates);
        },
        get: function (event) {
            return this._templates[event];
        },

        _templates: {},
    };

    //---------------------------------------------------------------------------
    //  Trigger
    //---------------------------------------------------------------------------

    class Trigger {
        constructor(name, template, conditions, source) {
            this.name = name;
            this.template = template;
            this.conditions = conditions;
            this.source = source;
            this._action = function (params) {
                let realParams = CopyObject(params);
                for (const key in conditions) {
                    if (!conditions.hasOwnProperty(key)) continue;
                    const filter = template.getFilter(key);
                    const fromUser = conditions[key];
                    const fromGame = params[key];
                    if (!filter.assert(fromUser, fromGame)) return;
                    delete realParams[key];
                }
                let realSource = source;
                for (const key in realParams) {
                    realSource = `($${key}) = ${realParams[key]}\n${realSource}`;
                }
                if (/\/\/\s*~silent\s*\n/.test(source) == false) {
                    realSource = `@print 💡<hio>触发=>${name}</hio>\n${realSource}`;
                }
                ToRaid.perform(realSource, name, false);
            };
            this._observerIndex = null;
        }

        event() { return this.template.event; }
        active() { return this._observerIndex != null; }

        _activate() {
            if (this._observerIndex != null) return;
            if (this.template == null) return;
            this._observerIndex = NotificationCenter.observe(this.template.event, this._action);
        }
        _deactivate() {
            if (this._observerIndex == null) return;
            NotificationCenter.removeOberver(this._observerIndex);
            this._observerIndex = null;
        }
    }

    class TriggerData {
        constructor(name, event, conditions, source, active) {
            this.name = name;
            this.event = event;
            this.conditions = conditions;
            this.source = source;
            this.active = active;
        }
    }

    const TriggerCenter = {
        run: function () {
            const allData = GM_getValue(this._saveKey(), {});
            for (const name in allData) {
                this._loadTrigger(name);
            }
        },
        reload: function () {
            for (const name in this._triggers) {
                if (!this._triggers.hasOwnProperty(name)) continue;
                const trigger = this._triggers[name];
                trigger._deactivate();
                delete this._triggers[name];
            }
            this.run();
        },

        // for upload and download
        getAllData: function () {
            return GM_getValue(this._saveKey(), {});
        },
        corver: function (triggerDatas) {
            for (const old of this.getAll()) {
                this.remove(old.name);
            }
            for (const name in triggerDatas) {
                const trigger = triggerDatas[name];
                this.create(trigger.name, trigger.event, trigger.conditions, trigger.source, trigger.active);
            }
        },

        getAll: function () {
            return Object.values(this._triggers);
        },
        create: function (name, event, conditions, source, active) {
            const checkResult = this._checkName(name);
            if (checkResult != true) return checkResult;

            const theActive = active == null ? false : active;
            const data = new TriggerData(name, event, conditions, source, theActive);
            this._updateData(data);

            this._loadTrigger(name);
            return true;
        },
        modify: function (originalName, name, conditions, source) {
            const trigger = this._triggers[originalName];
            if (trigger == null) return "修改不存在的触发器?";

            const event = trigger.event();
            if (originalName == name) {
                const data = new TriggerData(name, event, conditions, source, trigger.active());
                this._updateData(data);
                this._reloadTrigger(name);
                return true;
            }

            const result = this.create(name, event, conditions, source);
            if (result == true) {
                this.remove(originalName);
                this._loadTrigger(name);
            }
            return result;
        },
        remove: function (name) {
            const trigger = this._triggers[name];
            if (trigger == null) return;

            trigger._deactivate();
            delete this._triggers[name];
            let allData = GM_getValue(this._saveKey(), {});
            delete allData[name];
            GM_setValue(this._saveKey(), allData);
        },

        activate: function (name) {

            for (let x in this._triggers) {
                if (is_match(name, x)) {
                    const trigger = this._triggers[x];
                    if (trigger == null) continue;
                    if (trigger.active()) continue;
                    trigger._activate();
                    let data = this._getData(x);
                    data.active = true;
                    this._updateData(data);
                }

            }

        },
        deactivate: function (name) {
            for (let x in this._triggers) {
                if (is_match(name, x)) {
                    const trigger = this._triggers[x];
                    if (trigger == null) continue;
                    if (!trigger.active()) continue;
                    trigger._deactivate();
                    let data = this._getData(x);
                    data.active = false;
                    this._updateData(data);
                }

            }

        },
        _triggers: {},

        _saveKey: function () {
            return `${Role.id}@triggers`;
        },
        _reloadTrigger: function (name) {
            const oldTrigger = this._triggers[name];
            if (oldTrigger != null) {
                oldTrigger._deactivate();
            }
            this._loadTrigger(name);
        },
        _loadTrigger: function (name) {
            const data = this._getData(name);
            if (data == null) return;
            // patch new trigger
            if (data['event'] === '新聊天信息' && data['conditions']['忽略发言人'] === undefined) {
                data['conditions']['忽略发言人'] = ''
            }
            const trigger = this._toTrigger(data);
            this._triggers[name] = trigger;
            if (data.active) {
                trigger._activate();
            }
        },
        _getData: function (name) {
            let allData = GM_getValue(this._saveKey(), {});
            const data = allData[name];
            return data;
        },
        _updateData: function (data) {
            let allData = GM_getValue(this._saveKey(), {});
            allData[data.name] = data;
            GM_setValue(this._saveKey(), allData);
        },
        _toTrigger: function (data) {
            const template = TriggerTemplateCenter.get(data.event);
            const trigger = new Trigger(data.name, template, data.conditions, data.source);
            return trigger;
        },
        _checkName: function (name) {
            if (this._triggers[name] != null) return "无法修改名称,已经存在同名触发器!";
            if (!/\S+/.test(name)) return "触发器的名称不能为空。";
            if (!/^[_a-zA-Z0-9\u4e00-\u9fa5]+$/.test(name)) return "触发器的名称只能使用中文、英文和数字字符。";
            return true;
        }
    };

    /***********************************************************************************\
        WSMUD
    \***********************************************************************************/

    var WG = null;
    var messageAppend = null;
    var messageClear = null;
    var ToRaid = null;
    var Role = null;


    //---------------------------------------------------------------------------
    //  status
    //---------------------------------------------------------------------------

    (function () {
        const type = new SelectFilter("改变类型", ["新增", "移除", "层数刷新"], 0);
        const value = new InputFilter("BuffId", InputFilterFormat.text, "weapon", ContainAssert);
        const target = new SelectFilter("触发对象", ["自己", "他人"], 0);
        let filters = [type, value, target];
        const intro = `// Buff状态改变触发器
// 触发对象id:(id)
// buff的sid:(sid)
// buff层数:(count)
// duration持续时间:(duration)`;
        const t = new TriggerTemplate("Buff状态改变", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            const post = function (data, sid, type) {
                let params = {
                    "改变类型": type,
                    "BuffId": sid,
                    "触发对象": data.id == Role.id ? "自己" : "他人"
                };
                params["id"] = data.id;
                params["sid"] = sid;
                params["count"] = 0;
                params["duration"] = 0;
                if (data.count != null) params["count"] = data.count;
                if (data.duration != null) params["duration"] = data.duration;
                const n = new Notification("Buff状态改变", params);
                NotificationCenter.post(n);
            };
            WG.add_hook("status", data => {
                if (data.action == null || data.id == null || data.sid == null) return;
                const types = {
                    "add": "新增",
                    "remove": "移除",
                    "refresh": "层数刷新"
                };
                const type = types[data.action];
                if (type == null) return;
                if (data.sid instanceof Array) {
                    for (const s of data.sid) {
                        post(data, s, type);
                    }
                } else {
                    post(data, data.sid, type);
                }
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  msg
    //---------------------------------------------------------------------------

    (function () {
        const channel = new SelectFilter(
            "频道",
            ["全部", "世界", "队伍", "门派", "全区", "帮派", "谣言", "系统"],
            0,
            function (fromUser, fromGame) {
                if (fromUser == "全部") return true;
                return fromUser == fromGame;
            }
        );
        const talker = new InputFilter("发言人", InputFilterFormat.text, "", ContainAssert);
        const pass_talker = new InputFilter("忽略发言人", InputFilterFormat.text, "", ContainReverseAssert);
        const key = new InputFilter("关键字", InputFilterFormat.text, "", KeyAssert);
        let filters = [channel, talker, pass_talker, key];
        const intro = `// 新聊天信息触发器
// 聊天信息内容:(content)
// 发言人:(name)
// 发言人id:(id)
// 频道:(channel)`;
        const t = new TriggerTemplate("新聊天信息", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            WG.add_hook("msg", data => {
                if (data.ch == null || data.content == null) return;
                const types = {
                    "chat": "世界",
                    "tm": "队伍",
                    "fam": "门派",
                    "es": "全区",
                    "pty": "帮派",
                    "rumor": "谣言",
                    "sys": "系统"
                };
                const channel = types[data.ch];
                if (channel == null) return;
                const name = data.name == null ? "无" : data.name;
                const id = data.uid == null ? null : data.uid;
                const datacontent = data.content.replace(/\n/g, "")
                let params = {
                    "频道": channel,
                    "发言人": name,
                    "关键字": data.content,
                    "忽略发言人": name
                };
                params["content"] = datacontent;
                params["name"] = name;
                params["id"] = id;
                params["channel"] = channel;
                const n = new Notification("新聊天信息", params);
                NotificationCenter.post(n);
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  item add
    //---------------------------------------------------------------------------

    (function () {
        const name = new InputFilter("人物名称", InputFilterFormat.text, "", KeyAssert);
        name.description("人名关键字");
        let filters = [name];
        const intro = `// 人物刷新触发器
// 刷新人物id:(id)
// 刷新人物名称:(name)`;
        const t = new TriggerTemplate("人物刷新", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            WG.add_hook("itemadd", data => {
                if (data.name == null || data.id == null) return;
                let params = {
                    "人物名称": data.name,
                };
                params["id"] = data.id;
                params["name"] = data.name;
                const n = new Notification("人物刷新", params);
                NotificationCenter.post(n);
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  dialog pack
    //---------------------------------------------------------------------------

    (function () {
        const name = new InputFilter("名称关键字", InputFilterFormat.text, "", KeyAssert);
        let filters = [name];
        const intro = `// 物品拾取触发器
// 拾取物品id:(id)
// 拾取物品名称:(name)
// 拾取物品数量:(count)
// 物品品质:(quality)  值:白、绿、蓝、黄、紫、橙、红、未知`;
        const t = new TriggerTemplate("物品拾取", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            WG.add_hook("dialog", function (data) {
                if (data.dialog != "pack" || data.id == null || data.name == null || data.count == null || data.remove != null) return;
                let params = {
                    "名称关键字": data.name,
                };
                params["id"] = data.id;
                params["name"] = data.name;
                params["count"] = data.count;
                let quality = "未知";
                const tag = /<\w{3}>/.exec(data.name)[0];
                const tagMap = {
                    "<wht>": "白",
                    "<hig>": "绿",
                    "<hic>": "蓝",
                    "<hiy>": "黄",
                    "<HIZ>": "紫",
                    "<hio>": "橙",
                    "<ord>": "红"
                }
                quality = tagMap[tag];
                params["quality"] = quality;
                const n = new Notification("物品拾取", params);
                NotificationCenter.post(n);
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  text
    //---------------------------------------------------------------------------

    (function () {
        const name = new InputFilter("关键字", InputFilterFormat.text, "", KeyAssert);
        let filters = [name];
        const intro = `// 新提示信息触发器
// 提示信息:(text)`;
        const t = new TriggerTemplate("新提示信息", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            WG.add_hook("text", data => {
                if (data.msg == null) return;
                let params = {
                    "关键字": data.msg,
                };
                params["text"] = data.msg;
                const n = new Notification("新提示信息", params);
                NotificationCenter.post(n);
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  combat
    //---------------------------------------------------------------------------

    (function () {
        const type = new SelectFilter("类型", ["进入战斗", "脱离战斗"], 0);
        let filters = [type];
        const intro = "// 战斗状态切换触发器";
        const t = new TriggerTemplate("战斗状态切换", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            WG.add_hook("combat", data => {
                let params = null;
                if (data.start != null && data.start == 1) {
                    params = { "类型": "进入战斗" };
                } else if (data.end != null && data.end == 1) {
                    params = { "类型": "脱离战斗" };
                }
                const n = new Notification("战斗状态切换", params);
                NotificationCenter.post(n);
            });
            WG.add_hook("text", function (data) {
                if (data.msg == null) return;
                if (data.msg.indexOf('只能在战斗中使用') != -1 || data.msg.indexOf('这里不允许战斗') != -1 || data.msg.indexOf('没时间这么做') != -1) {
                    const params = { "类型": "脱离战斗" };
                    const n = new Notification("战斗状态切换", params);
                    NotificationCenter.post(n);
                }
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  combat
    //---------------------------------------------------------------------------

    (function () {
        const type = new SelectFilter("类型", ["已经死亡", "已经复活"], 0);
        let filters = [type];
        const intro = "// 死亡状态改变触发器";
        const t = new TriggerTemplate("死亡状态改变", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            WG.add_hook("die", data => {
                const value = data.relive == null ? "已经死亡" : "已经复活";
                let params = {
                    "类型": value
                };
                const n = new Notification("死亡状态改变", params);
                NotificationCenter.post(n);
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  time
    //---------------------------------------------------------------------------

    (function () {
        const hours = [
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
            10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
            20, 21, 22, 23
        ];
        const minutes = [
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
            10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
            20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
            30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
            40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
            50, 51, 52, 53, 54, 55, 56, 57, 58, 59
        ];
        const hour = new SelectFilter("时", hours, 0, EqualAssert);
        const minute = new SelectFilter("分", minutes, 0, EqualAssert);
        const second = new SelectFilter("秒", minutes, 0, EqualAssert);
        let filters = [hour, minute, second];
        const intro = "// 时辰已到触发器";
        const t = new TriggerTemplate("时辰已到", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            function timer() {
                const date = new Date();
                const params = {
                    "时": date.getHours(),
                    "分": date.getMinutes(),
                    "秒": date.getSeconds()
                };
                const n = new Notification("时辰已到", params);
                NotificationCenter.post(n);

                const nowTime = Date.now();
                const nextTime = parseInt((nowTime + 1e3) / 1e3) * 1e3 + 1;

                setTimeout(() => {
                    timer();
                }, nextTime - nowTime);
            }
            timer();
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  dispfm
    //---------------------------------------------------------------------------

    (function () {
        const sid = new InputFilter("技能id", InputFilterFormat.text, "", ContainAssert);
        let filters = [sid];
        const intro = `// 技能释放触发器
// 技能id:(id)
// 出招时间:(rtime)
// 冷却时间:(distime)`;
        const t = new TriggerTemplate("技能释放", filters, intro);
        TriggerTemplateCenter.add(t);

        const sid1 = new InputFilter("技能id", InputFilterFormat.text, "", ContainAssert);
        let filters1 = [sid1];
        const intro1 = `// 技能冷却结束触发器
// 技能id:(id)`;
        const t1 = new TriggerTemplate("技能冷却结束", filters1, intro1);
        TriggerTemplateCenter.add(t1);

        const run = function () {
            WG.add_hook("dispfm", data => {
                if (data.id == null || data.distime == null || data.rtime == null) return;
                let params = {
                    "技能id": data.id
                };
                params["id"] = data.id;
                params["rtime"] = data.rtime;
                params["distime"] = data.distime;
                const n = new Notification("技能释放", params);
                NotificationCenter.post(n);

                setTimeout(_ => {
                    let params = {
                        "技能id": data.id
                    };
                    params["id"] = data.id;
                    const n = new Notification("技能冷却结束", params);
                    NotificationCenter.post(n);
                }, data.distime);
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  hp mp
    //---------------------------------------------------------------------------

    var RoomItems = {};

    (function () {
        const name = new InputFilter("人名关键字", InputFilterFormat.text, "", KeyAssert);
        const type = new SelectFilter("类型", ["气血", "内力"], 0, EqualAssert);
        const compare = new SelectFilter("当", ["低于", "高于"], 0, EqualAssert);
        const valueType = new SelectFilter("值类型", ["百分比", "数值"], 0, EqualAssert);
        const value = new InputFilter("值", InputFilterFormat.number, 0, function (fromUser, fromGame) {
            const parts = fromGame.split(";");
            const oldvalue = parseFloat(parts[0]);
            const newvalue = parseFloat(parts[1]);
            if (oldvalue >= fromUser && newvalue < fromUser) return true;
            if (oldvalue <= fromUser && newvalue > fromUser) return true;
            return false;
        });
        let filters = [name, type, compare, valueType, value];
        const intro = `// 气血内力改变触发器
// 人物id:(id)
// 人物当前气血:(hp)
// 人物最大气血:(maxHp)
// 人物当前内力:(mp)
// 人物最大内力:(maxMp)`;
        const t = new TriggerTemplate("气血内力改变", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            WG.add_hook("items", data => {
                if (data.items == null) return;
                RoomItems = {};
                for (const item of data.items) {
                    RoomItems[item.id] = CopyObject(item);
                }
            });
            WG.add_hook("itemadd", data => {
                RoomItems[data.id] = CopyObject(data);
            });
            const decorate = function (params, item) {
                params["id"] = item.id;
                params["hp"] = item.hp;
                params["maxHp"] = item.max_hp;
                params["mp"] = item.mp;
                params["maxMp"] = item.max_mp;
            };
            WG.add_hook("sc", data => {
                if (data.id == null) return;
                let item = RoomItems[data.id];
                if (item == null) return;
                if (data.hp != null) {
                    let compare = "低于";
                    if (data.hp > item.hp) compare = "高于";
                    const oldValue = item.hp;
                    const oldPer = (item.hp / item.max_hp * 100).toFixed(2);
                    item.hp = data.hp;
                    if (item.max_hp < item.hp) item.max_hp = item.hp;
                    if (data.max_hp != null) item.max_hp = data.max_hp;
                    const newValue = item.hp;
                    const newPer = (item.hp / item.max_hp * 100).toFixed(2);
                    let params1 = {
                        "人名关键字": item.name,
                        "类型": "气血",
                        "当": compare,
                        "值类型": "百分比",
                        "值": `${oldPer};${newPer}`
                    };
                    decorate(params1, item);
                    const n1 = new Notification("气血内力改变", params1);
                    NotificationCenter.post(n1);
                    let params2 = {
                        "人名关键字": item.name,
                        "类型": "气血",
                        "当": compare,
                        "值类型": "数值",
                        "值": `${oldValue};${newValue}`
                    };
                    decorate(params2, item);
                    const n2 = new Notification("气血内力改变", params2);
                    NotificationCenter.post(n2);
                }
                if (data.mp != null) {
                    let compare = "低于";
                    if (data.mp > item.mp) compare = "高于";
                    const oldValue = item.mp;
                    const oldPer = (item.mp / item.max_mp * 100).toFixed(2);
                    item.mp = data.mp;
                    if (item.max_mp < item.mp) item.max_mp = item.mp;
                    if (data.max_mp != null) item.max_mp = data.max_mp;
                    const newValue = item.mp;
                    const newPer = (item.mp / item.max_mp * 100).toFixed(2);
                    let params1 = {
                        "人名关键字": item.name,
                        "类型": "内力",
                        "当": compare,
                        "值类型": "百分比",
                        "值": `${oldPer};${newPer}`
                    };
                    decorate(params1, item);
                    const n1 = new Notification("气血内力改变", params1);
                    NotificationCenter.post(n1);
                    let params2 = {
                        "人名关键字": item.name,
                        "类型": "内力",
                        "当": compare,
                        "值类型": "数值",
                        "值": `${oldValue};${newValue}`
                    };
                    decorate(params2, item);
                    const n2 = new Notification("气血内力改变", params2);
                    NotificationCenter.post(n2);
                }
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    //---------------------------------------------------------------------------
    //  damage
    //---------------------------------------------------------------------------

    (function () {
        const name = new InputFilter("人名关键字", InputFilterFormat.text, "", KeyAssert);
        const valueType = new SelectFilter("值类型", ["百分比", "数值"], 0, EqualAssert);
        const value = new InputFilter("值", InputFilterFormat.number, 0, (fromUser, fromGame) => {
            const parts = fromGame.split(";");
            const oldvalue = parseFloat(parts[0]);
            const newvalue = parseFloat(parts[1]);
            if (oldvalue <= fromUser && newvalue > fromUser) return true;
            return false;
        });
        let filters = [name, valueType, value];
        const intro = `// 伤害已满触发器
// 备注:限制条件-值 不支持多条件
// 人物id:(id)
// 人物名称:(name)
// 伤害数值:(value)
// 伤害百分比:(percent)`;
        const t = new TriggerTemplate("伤害已满", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function () {
            const decorate = function (params, item, value, percent) {
                params["id"] = item.id;
                params["name"] = item.name;
                params["value"] = value;
                params["percent"] = percent;
            };
            WG.add_hook("sc", data => {
                if (data.id == null || data.damage == null) return;
                let item = RoomItems[data.id];
                if (item == null || item.id == null || item.name == null || item.max_hp == null) return;
                // 获取之前保存的伤害和伤害百分比
                const oldValue = item._damage == null ? 0 : item._damage;
                const oldPer = item._damagePer == null ? 0 : item._damagePer;
                const value = data.damage;
                const percent = (data.damage / item.max_hp * 100).toFixed(2);
                // 保存伤害和伤害百分比
                item._damage = value;
                item._damagePer = percent;
                let params1 = {
                    "人名关键字": item.name,
                    "值类型": "百分比",
                    "值": `${oldPer};${percent}`
                };
                decorate(params1, item, value, percent);
                const n1 = new Notification("伤害已满", params1);
                NotificationCenter.post(n1);
                let params2 = {
                    "人名关键字": item.name,
                    "值类型": "数值",
                    "值": `${oldValue};${value}`
                };
                decorate(params2, item, value, percent);
                const n2 = new Notification("伤害已满", params2);
                NotificationCenter.post(n2);
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

    /***********************************************************************************\
        UI
    \***********************************************************************************/

    const Message = {
        append: function (msg) {
            messageAppend(msg);
        },
        clean: function () {
            messageClear();
        },
    };

    const UI = {
        triggerHome: function () {
            const content = `
            <style>.breakText {word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}</style>
            <span class="zdy-item" style="width:120px" v-for="t in triggers" :style="activeStyle(t)">
                <div style="width: 30px; float: left; background-color: rgba(255, 255, 255, 0.31); border-radius: 4px;" v-on:click="editTrigger(t)">⚙</div>
                <div class="breakText" style="width: 85px; float: right;" v-on:click="switchStatus(t)">{{ t.name }}</div>
            </span>
            `;
            const rightText = "<span v-on:click='createTrigger()'><wht>新建</wht></span>";
            UI._appendHtml("🍟 <hio>触发器</hio>", content, rightText);
            new Vue({
                el: '#app',
                data: {
                    triggers: TriggerCenter.getAll()
                },
                methods: {
                    switchStatus: function (t) {
                        if (t.active()) {
                            TriggerCenter.deactivate(t.name);
                        } else {
                            TriggerCenter.activate(t.name);
                        }
                        UI.triggerHome();
                    },
                    editTrigger: UI.editTrigger,
                    activeStyle: function (t) {
                        if (t.active()) {
                            return {
                                "background-color": "#a0e6e0",
                                "border": "1px solid #7284ff",
                                "color": "#001bff"
                            };
                        } else {
                            return { };
                        }
                    },
                    createTrigger: UI.selectTriggerTemplate
                }
            });
        },
        selectTriggerTemplate: function () {
            const content = `
            <span class="zdy-item" style="width:120px" v-for="t in templates" v-on:click="select(t)">{{ t.event }}</span>
            `;
            const leftText = "<span v-on:click='back()'>< 返回</span>";
            UI._appendHtml("<wht>选择触发事件</wht>", content, null, leftText);
            new Vue({
                el: '#app',
                data: {
                    templates: TriggerTemplateCenter.getAll()
                },
                methods: {
                    select: UI.createTrigger,
                    back: UI.triggerHome
                }
            });
        },
        createTrigger: function (template) {
            UI._updateTrigger(template);
        },
        editTrigger: function (trigger) {
            UI._updateTrigger(trigger.template, trigger);
        },
        _updateTrigger: function (template, trigger) {
            const content = `
            <div style="margin:0 2em 0 2em">
                <div style="float:left;width:120px">
                    <span class="zdy-item" style="width:90px" v-for="f in filters">
                    <p style="margin:0"><wht>{{ f.description() }}</wht></p>
                    <input v-if="f.type=='input'" style="width:80%" v-model="conditions[f.name]">
                    <select v-if="f.type=='select'" v-model="conditions[f.name]">
                        <option v-for="opt in f.options" :value="opt">{{ opt }}</option>
                    </select>
                    </span>
                </div>
                <div style="float:right;width:calc(100% - 125px)">
                    <textarea class = "settingbox hide" style = "height:10rem;display:inline-block;font-size:0.8em;width:100%" v-model="source"></textarea>
                    <span class="raid-item shareTrigger" v-if="canShared" v-on:click="share()">分享此触发器</span>
                </div>
            </div>
            `;
            const title = `<input style='width:110px' type="text" placeholder="输入触发器名称" v-model="name">`;
            let rightText = "<span v-on:click='save'><wht>保存</wht></span>";
            if (trigger) {
                rightText = "<span v-on:click='remove'>删除</span>"
            }
            let leftText = "<span v-on:click='back'>< 返回</span>";
            if (trigger) {
                leftText = "<span v-on:click='saveback'>< 保存&返回</span>"
            }
            UI._appendHtml(title, content, rightText, leftText);
            let conditions = {};
            if (trigger != null) {
                conditions = trigger.conditions;
            } else {
                for (const f of template.filters) {
                    conditions[f.name] = f.defaultValue;
                }
            }
            let source = template.introdution;
            if (trigger != null) source = trigger.source;
            new Vue({
                el: '#app',
                data: {
                    filters: template.filters,
                    name: trigger ? trigger.name : "",
                    conditions: conditions,
                    source: source,
                    canShared: trigger != null
                },
                methods: {
                    save: function () {
                        const result = TriggerCenter.create(this.name, template.event, this.conditions, this.source);
                        if (result == true) {
                            UI.triggerHome();
                        } else {
                            alert(result);
                        }
                    },
                    remove: function () {
                        const verify = confirm("确认删除此触发器吗?");
                        if (verify) {
                            TriggerCenter.remove(trigger.name);
                            UI.triggerHome();
                        }
                    },
                    back: function () {
                        UI.selectTriggerTemplate();
                    },
                    saveback: function () {
                        const result = TriggerCenter.modify(trigger.name, this.name, this.conditions, this.source);
                        if (result == true) {
                            UI.triggerHome();
                        } else {
                            alert(result);
                        }
                    },

                    share: function () {
                        ToRaid.shareTrigger(TriggerCenter._getData(trigger.name));
                    }
                }
            })
        },

        _appendHtml: function (title, content, rightText, leftText) {
            var realLeftText = leftText == null ? "" : leftText;
            var realRightText = rightText == null ? "" : rightText;
            var html = `
            <div class = "item-commands" style="text-align:center" id="app">
                <div style="margin-top:0.5em">
                    <div style="width:8em;float:left;text-align:left;padding:0px 0px 0px 2em;height:1.23em" id="wsmud_raid_left">${realLeftText}</div>
                    <div style="width:calc(100% - 16em);float:left;height:1.23em">${title}</div>
                    <div style="width:8em;float:left;text-align:right;padding:0px 2em 0px 0px;height:1.23em" id="wsmud_raid_right">${realRightText}</div>
                </div>
                <br><br>
                ${content}
            </div>`;
            Message.clean();
            Message.append(html);
        },
    };

    /***********************************************************************************\
        Trigger Config
    \***********************************************************************************/

    const TriggerConfig = {
        get: function () {
            let all = {};
            let keys = GM_listValues();
            keys.forEach(key => {
                if (key != "roles") {
                    all[key] = GM_getValue(key);
                }
            });
            return all;
        },
        set: function (config) {
            for (const key in config) {
                GM_setValue(key, config[key]);
            }
            TriggerCenter.reload();
        }
    };

    /***********************************************************************************\
        Ready
    \***********************************************************************************/

    let Running = false;

    $(document).ready(function () {
        __init__();
        if (WG == undefined || WG == null || ToRaid == undefined || ToRaid == null) {
            setTimeout(__init__, 300);
        }
    });

    function __init__() {
        WG = unsafeWindow.WG;

        messageAppend = unsafeWindow.messageAppend;
        messageClear = unsafeWindow.messageClear;
        ToRaid = unsafeWindow.ToRaid;

        if (WG == undefined || WG == null || ToRaid == undefined || ToRaid == null) {
            setTimeout(() => { __init__() }, 300);
            return;
        }
        Role = unsafeWindow.Role;

        unsafeWindow.TriggerUI = UI;
        unsafeWindow.TriggerConfig = TriggerConfig;
        unsafeWindow.TriggerCenter = TriggerCenter;

        WG.add_hook("login", function (data) {
            if (Running) return;
            Running = true;

            TriggerCenter.run();
            MonitorCenter.run();
        });
    }
})();