wsmud_Trigger

武神传说 MUD

当前为 2019-03-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            wsmud_Trigger
// @namespace       cqv3
// @version         0.0.3
// @date            03/03/2019
// @modified        04/03/2019
// @homepage        https://greasyfork.org/zh-CN/scripts/378984
// @description     武神传说 MUD
// @author          Bob.cn
// @match           http://game.wsmud.com/*
// @match           http://www.wsmud.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));
    }

    /***********************************************************************************\
        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) {
        const list = lh.split("|");
        return list.indexOf(rh) != -1;
    };

    const KeyAssert = function(lh, rh) {
        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;
        }
    }

    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}`;
                }
                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;
            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);
            }
        },

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

            const data = new TriggerData(name, event, conditions, source, false);
            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);
            } else {
                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) {
            const trigger = this._triggers[name];
            if (trigger == null) return;
            if (trigger.active()) return;
            trigger._activate();
            let data = this._getData(name);
            data.active = true;
            this._updateData(data);
        },
        deactivate: function(name) {
            const trigger = this._triggers[name];
            if (trigger == null) return;
            if (!trigger.active()) return;
            trigger._deactivate();
            let data = this._getData(name);
            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;
            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;

    //---------------------------------------------------------------------------
    //  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 = `
// 触发对象id:(id)
// buff的sid:(sid)
// buff层数:(count)`;
        const t = new TriggerTemplate("Buff状态改变", filters, intro);
        TriggerTemplateCenter.add(t);

        const run = function() {
            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;
                let params = {
                    "改变类型": type,
                    "BuffId": data.sid,
                    "触发对象": data.id == Role.id ? "自己" : "他人"
                };
                params["id"] = data.id;
                params["sid"] = data.sid;
                params["count"] = 0;
                if (data.count != null) params["count"] = data.count;
                const n = new Notification("Buff状态改变", params);
                NotificationCenter.post(n);
            });
        };
        const monitor = new Monitor(run);
        MonitorCenter.addMonitor(monitor);
    })();

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

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

        const run = function() {
            WG.add_hook("msg", data => {
                if (data.ch == null || data.name == null || data.content == null) return;
                const types = {
                    "chat": "世界",
                    "tm": "队伍",
                    "fam": "门派",
                    "es": "全区",
                    "pty": "帮派",
                    "rumor": "谣言",
                    "sys": "系统"
                };
                const chanel = types[data.ch];
                if (chanel == null) return;
                let params = {
                    "频道": chanel,
                    "发言人": data.name,
                    "关键字": data.content
                };
                params["content"] = data.content;
                params["name"] = data.name;
                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, "", ContainAssert);
        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["name"] = 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);
            });
        };
        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);
    })();

    /***********************************************************************************\
        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 { "background-color": "none" };
                        }
                    },
                    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.name }}</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>
                </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
                },
                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);
                        }
                    }
                }
            })
        },

        _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);
        },
    };

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

    $(document).ready(function () {
        WG = unsafeWindow.WG;
        messageAppend  = unsafeWindow.messageAppend;
        messageClear =  unsafeWindow.messageClear;
        ToRaid = unsafeWindow.ToRaid;

        unsafeWindow.TriggerUI = UI;

        WG.add_hook("login", function(data) {
            TriggerCenter.run();
            MonitorCenter.run();
        });
    });
})();