Greasy Fork is available in English.

wsmud_Raid

武神传说 MUD

Pada tanggal 05 Juni 2020. Lihat %(latest_version_link).

// ==UserScript==
// @name            wsmud_Raid
// @namespace       cqv
// @version         2.4.19
// @date            23/12/2018
// @modified        05/06/2020
// @homepage        https://greasyfork.org/zh-CN/scripts/375851
// @description     武神传说 MUD
// @author          Bob.cn, 初心, 白三三
// @match           http://*.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';

    /***********************************************************************************\
        工具层
    \***********************************************************************************/

    //---------------------------------------------------------------------------
    //  Message Output
    //---------------------------------------------------------------------------

    var Message = {
        append: function(msg) {
            console.log(msg);
        },
        clean: function() { },
        cmdLog: function(title, cmd) {
            let msg = `&nbsp;&nbsp;<hic>${title}</hic>`
            if (cmd != null) {
                msg += `: ${cmd}`;
            }
            this.append(msg);
        }
    };

    function CopyObject(obj) {
        return JSON.parse(JSON.stringify(obj));
    }

    /**
     * @param {Array} list
     * @param {*} value
     * @param {Function} assert function(previous, current)
     */
    const SortInsert = function(list, value, assert) {
        let index = list.length;
        while (index >= 0) {
            if (index == 0) {
                list.splice(index, 0, value);
                break;
            }
            const previous = list[index - 1];
            if (assert(previous, value)) {
                list.splice(index, 0, value);
                break;
            }
            index -= 1;
        }
    };

    //---------------------------------------------------------------------------
    //  Source Split Helper
    //---------------------------------------------------------------------------

    const SourceCodeHelper = {
        split: function(source) {
            var cmds = source.split(/\s*\n+/g);
            var first = cmds[0];
            if (first != null && /\S/.test(first) == false) {
                cmds.splice(0, 1);
            }
            var last = cmds[cmds.length - 1];
            if (last != null && /\S/.test(last) == false) {
                cmds.splice(cmds.length - 1, 1);
            }
            return cmds;
        },
        appendHeader: function(header, text) {
            let result = `\n${text}`;
            result = result.replace(/(\n)/g, `$1${header}`);
            result = result.replace(/\n\s*\n/g, "\n");
            result = result.replace(/^\s*\n/, "");
            return result;
        }
    };

    //---------------------------------------------------------------------------
    //  Persistent Cache Interface
    //---------------------------------------------------------------------------

    class PersistentCache {
        constructor(save, getAll, remove) {
            this._save = save;
            this._getAll = getAll;
            this._remove = remove;
        }
        save(key, value) {
            this._save(key, value);
        }
        get(key) {
            return this.getAll()[key];
        }
        getAll() {
            return this._getAll();
        }
        remove(key) {
            this._remove(key);
        }
    }

    /***********************************************************************************\
        控制逻辑编译层
    \***********************************************************************************/

    //---------------------------------------------------------------------------
    //  Precompiler
    //---------------------------------------------------------------------------

    class PrecompileRule {
        constructor(handle, priority) {
            this._handle = handle;
            this.priority = priority;
        }
        handle(cmds) {
            return this._handle(cmds);
        }
    }

    class PrecompileRuleCenter extends PrecompileRule {
        constructor() {
            const handle = function(cmds) {
                var result = cmds;
                for (const rule of this._rules) {
                    result = rule.handle(result);
                }
                return result;
            };
            super(handle, -1);
            this._rules = [];
            this.instance = this;
        }
        static shared() {
            if(!this.instance) {
                this.instance = new PrecompileRuleCenter();
            }
            return this.instance;
        }
        addRule(rule) {
            SortInsert(this._rules, rule, (p, c) => {
                return p.priority >= c.priority;
            });
        }
    };

    class Precompiler {
        precompile(source) {
            var cmds = SourceCodeHelper.split(source);
            if (cmds.length <= 0) return cmds;

            var result = PrecompileRuleCenter.shared().handle(cmds);

            console.log("<<<============================");
            console.log("预编译最终代码:");
            for (let k = 0; k < result.length; k++) {
                console.log(k + " " + result[k]);
            }
            console.log("============================>>>");
            return result;
        }
    }

    //---------------------------------------------------------------------------
    //  Compiler
    //---------------------------------------------------------------------------

    const ControlKeys = {
        while: "while",
        continue: "continue",
        break: "break",
        if: "if",
        elseif: "elseif",
        else: "else",
        exit: "exit",
    };

    class Compiler {
        constructor() {
            this._cc = "CC";
            this._pc = "PC";
            this._breakStacks = [];
        }

        compile(source) {
            if (source == null) return [];

            var precompiler = new Precompiler();
            var cmds = precompiler.precompile(source);

            var blockCmds = ["[if] true"];
            cmds.forEach(cmd => {
                blockCmds.push("  " + cmd);
            });
            var result = this._handleBlock(blockCmds, 0).cmds;
            result.push("%exit");

            console.log("<<<============================");
            console.log("编译最终代码:");
            for (let k = 0; k < result.length; k++) {
                console.log(k + " " + result[k]);
            }
            console.log("============================>>>");
            return result;
        }

        /**
         * @param {string[]} cmds
         * @param {number} start block 首句在的 index
         * @param {number} loopStart 最邻近的 while 的首句索引
         */
        _handleBlock(cmds, start, loopStart) {
            var realLoopStart = loopStart;

            var result = [];
            var r = this._handleCondition(cmds[0]);
            var callback = function() {};
            var self = this;
            switch (r.type) {
                case ControlKeys.while:
                this._breakStacks.push([]);
                result.push(r.cmd);
                result.push(null);
                callback = function() {
                    result.push(`%${self._pc}=${start}`);
                    var truePC = start + 2;
                    var falsePC = result.length + start;
                    result[1] = `%${self._pc}=${self._cc}?${truePC}:${falsePC}`;
                    var breakStack = self._breakStacks.pop();
                    breakStack.forEach(index => {
                        result[index-start] = `%${self._pc}=${falsePC}`;
                    });
                };
                realLoopStart = start;
                break;
                case ControlKeys.if:
                result.push(r.cmd);
                result.push(null);
                callback = function() {
                    result.push("%pass");
                    var truePC = start + 2;
                    var falsePC = result.length + start;
                    result[1] = `%${self._pc}=${self._cc}?${truePC}:${falsePC}`;
                };
                break;
                case ControlKeys.elseif:
                result.push(r.cmd);
                result.push(null);
                callback = function() {
                    result.push("%pass");
                    var truePC = start + 2;
                    var falsePC = result.length + start;
                    result[1] = `%${self._pc}=${self._cc}?${truePC}:${falsePC}`;
                };
                break;
                case ControlKeys.else:
                result.push(null);
                callback = function() {
                    var truePC = start + 1;
                    var falsePC = result.length + start;
                    result[0] = `%${self._pc}=${self._cc}?${falsePC}:${truePC}`;
                };
                break;
                case ControlKeys.continue:
                result.push(`%${self._pc}=${loopStart}`);
                return {type: "continue", cmds: result};
                case ControlKeys.break:
                result.push(null);
                var breakStack = this._breakStacks[this._breakStacks.length - 1];
                breakStack.push(start);
                return {type: "break", cmds: result};
                case ControlKeys.exit:
                result.push("%exit");
                return {type: "exit", cmds: result};
                default:
                throw "未知的控制关键字: " + r.type;
            }

            var cmdsLength = cmds.length;
            var i = 1;
            while (i < cmdsLength) {
                var cmd = cmds[i];
                var header = /^\s*/g.exec(cmd)[0];
                var headerLength = header.length;
                if (cmd[headerLength] == "[") {
                    var blockCmds = [cmd];
                    var j = i + 1;
                    while (j < cmdsLength) {
                        var next = cmds[j];
                        if (next[headerLength] != " ") break;
                        blockCmds.push(next);
                        j += 1;
                    }
                    var lastCmdIndex = result.length - 1;
                    var blockStart = result.length + start;
                    var k = this._handleBlock(blockCmds, blockStart, realLoopStart);
                    k.cmds.forEach(cmd1 => {
                        result.push(cmd1);
                    });
                    if (k.type == "elseif") {
                        result[lastCmdIndex] = `%${this._pc}=${result.length + start - 1}`;
                    } else if (k.type == "else") {
                        result[lastCmdIndex] = `%${this._pc}=${result.length + start}`;
                    }
                    i = j;
                } else {
                    result.push(cmd.substring(headerLength));
                    i += 1;
                }
            }

            callback();
            return {type: r.type, cmds: result};
        }
        _handleCondition(condition) {
            var type = null;
            var cmd = null;
            var formats = [
                {type: ControlKeys.while, regexp: /^\s*\[while\]/g},
                {type: ControlKeys.if, regexp: /^\s*\[if\]/g},
                {type: ControlKeys.elseif, regexp: /^\s*\[else\s?if\]/g},
                {type: ControlKeys.else, regexp: /^\s*\[else\]/g},
                {type: ControlKeys.continue, regexp: /^\s*\[continue\]/g},
                {type: ControlKeys.break, regexp: /^\s*\[break\]/g},
                {type: ControlKeys.exit, regexp: /^\s*\[exit\]/g},
            ];
            for (const format of formats) {
                var r = format.regexp.exec(condition);
                if (r) {
                    type = format.type;
                    var exp = condition.substring(r[0].length);
                    cmd = `%${this._cc}=${exp}`
                    break;
                }
            }
            if (type == null) {
                throw "编译失败: " + condition;
            }
            return {type: type, cmd: cmd};
        }
    }

    /***********************************************************************************\
        预编译实现层
    \***********************************************************************************/

    const PrecompileRulePriority = {
        subflow: 100,
        call: 90,
        annatition: 80,
        compatible: 70,
        guard: 60,
        emptyLine: 50,

        // 层外使用
        high: 30,
        ordinary: 20,
        low: 10
    };

    //---------------------------------------------------------------------------
    //  Precompile Annatitions
    //---------------------------------------------------------------------------

    (function () {
        const handle = function(cmds) {
            var result = [];
            for (const cmd of cmds) {
                if (/^\s*\/\//.test(cmd)) continue;
                result.push(cmd);
            }
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.annatition);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    (function () {
        const handle = function(cmds) {
            var result = [];
            var ignore = false;
            for (const cmd of cmds) {
                if (/^\s*\/\*/.test(cmd)) {
                    ignore = true;
                    continue;
                }
                if (ignore && /\*\/\s*$/.test(cmd)) {
                    ignore = false;
                    continue;
                }
                if (!ignore) {
                    result.push(cmd);
                }
            }
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.annatition);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    //---------------------------------------------------------------------------
    //  Precompile Subflows
    //---------------------------------------------------------------------------

    (function () {
        const handle = function(cmds) {
            let result = [];
            let collecting = false;
            let subflowCmd = null;
            for (const cmd of cmds) {
                var r = /^(\s*)<===+\s*$/.exec(cmd);
                if (r != null) {
                    collecting = true;
                    subflowCmd = "<===";
                    continue;
                }
                if (collecting) {
                    var r2 = /^\s*=+==>\s*$/.exec(cmd);
                    if (r2 != null) {
                        collecting = false;
                        subflowCmd += "===>";
                        result.push(subflowCmd);
                    } else {
                        subflowCmd += `\n${cmd}`;
                    }
                    continue;
                }
                result.push(cmd);
            }
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.subflow);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    //---------------------------------------------------------------------------
    //  Precompile Guards
    //---------------------------------------------------------------------------

    (function () {
        const handle = function(cmds) {
            var result = [];
            // {headerLength: Number, cmds: [String]}
            var guards = [];
            var guarding = false;
            for (const cmd of cmds) {
                var r = /^(\s*)<---+/.exec(cmd);
                if (r != null) {
                    guarding = true;
                    const guard = {
                        headerLength: r[1].length,
                        cmds: []
                    }
                    guards.push(guard);
                    continue;
                }
                if (guarding) {
                    var r2 = /^\s*-+-->/.exec(cmd);
                    if (r2 == null) {
                        const guard = guards[guards.length - 1];
                        guard.cmds.push(cmd.substring(guard.headerLength));
                    } else {
                        guarding = false;
                    }
                    continue;
                }
                result.push(cmd);
                var r3 = /^(\s*)[^\[\s]/.exec(cmd);
                if (r3 != null) {
                    var header = r3[1];
                    var hasGuard = false;
                    for (let j = guards.length; j > 0; j--) {
                        const guard = guards[j - 1];
                        if (header.length < guard.headerLength) {
                            guards.pop();
                            continue;
                        }
                        if (!hasGuard) {
                            result.push(`${header}%guardStart`);
                            hasGuard = true;
                        }
                        guard.cmds.forEach(cmd1 => {
                            result.push(`${header}${cmd1}`);
                        });
                    }
                    if (hasGuard) result.push(`${header}%guardEnd`);
                }
            }
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.guard);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    //---------------------------------------------------------------------------
    //  Precompile Calls
    //---------------------------------------------------------------------------

    var __CallCounter = 0;
    var FlowStore = null; // PersistentCache

    // TODO: 尚不支持嵌套调用
    // @call 函数名 参数1,参数2,参数3,...
    (function() {
        const handle = function(cmds) {
            let result = [];
            cmds.forEach(cmd => {
                var r = /^(\s*)@call\s(\S+)(\s*(\S.*)+\s*|\s*)$/.exec(cmd);
                if (r == null) {
                    result.push(cmd);
                    return;
                }
                const paramsField = r[4];
                let args = "";
                if (paramsField != null && paramsField.length > 0) {
                    const params = paramsField.split(",");
                    for (let i = 0; i < params.length; i++) {
                        const param = params[i];
                        args += `($arg${i})=${param}\n`;
                    }
                }
                const flowName = r[2];
                let source = FlowStore.get(flowName);
                if (source == null) {
                    throw `未找到调用的流程 ${flowName}`;
                }
                let callSource = `[if] true\n` + SourceCodeHelper.appendHeader("    ", `${args}\n${source}`);
                const callId = __CallCounter; __CallCounter += 1;
                callSource = callSource.replace(/\(\$([_a-z][a-zA-Z0-9_]*?)\)/g, `($__x${callId}_$1)`);
                callSource = callSource.replace(/\(([_a-z][a-zA-Z0-9_]*?)\)/g, `(__x${callId}_$1)`);
                const callCmds = SourceCodeHelper.split(callSource);
                const header = r[1];
                for (const callCmd of callCmds) {
                    if (/^\s*#/.test(callCmd)) continue;
                    result.push(`${header}${callCmd}`);
                }
            });
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.call);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    //---------------------------------------------------------------------------
    //  Precompile Empty Line
    //---------------------------------------------------------------------------

    (function () {
        const handle = function(cmds) {
            var result = [];
            for (const cmd of cmds) {
                if (!/\S+/.test(cmd)) continue;
                result.push(cmd);
            }
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.emptyLine);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    //---------------------------------------------------------------------------
    //  Precompile Raid 1.x.x
    //---------------------------------------------------------------------------

    (function addCompatibleGuardRule() {
        const handle = function(cmds) {
            var result = [];
            cmds.forEach(cmd => {
                var r = /^\s*#(\[.*)$/.exec(cmd);
                if (r == null) {
                    result.push(cmd);
                    return;
                }
                var c1 = `<---`;
                var c2 = r[1];
                var c3 = `--->`;
                result.push(c1, c2, c3);
            });
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.compatible);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    function CompatibleOperator(exp) {
        var result = exp;
        result = result.replace(/([^&])[&]([^&])/g, "$1&&$2");
        result = result.replace(/([^\|])[\|]([^\|])/g, "$1||$2");
        result = result.replace(/([^=<>!])[=]([^=])/g, "$1==$2");
        return result;
    }

    (function addCompatibleUntilRule() {
        const handle = function(cmds) {
            var result = [];
            cmds.forEach(cmd => {
                var r = /^(\s*)\[=(.+?)\](.*)$/.exec(cmd);
                if (r == null) {
                    result.push(cmd);
                    return;
                }
                var header = r[1];
                var condition = r[2];
                condition = CompatibleOperator(condition);
                var command = r[3];
                var c1 = `${header}@until ${condition}`;
                result.push(c1);
                if (!/\S/.test(command)) return;
                var c2 = `${header}${command}`;
                result.push(c2);
            });
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.compatible);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    (function addCompatibleIfRule() {
        const handle = function(cmds) {
            var result = [];
            cmds.forEach(cmd => {
                var r = /^(\s*)\[(.*?[=<>].*?|true|false)\](.*)$/i.exec(cmd);
                if (r == null) {
                    result.push(cmd);
                    return;
                }
                var command = r[3];
                if (!/\S/.test(command)) return;
                var header = r[1];
                var condition = r[2];
                condition = CompatibleOperator(condition);
                var c1 = `${header}[if] ${condition}`;
                var c2 = `${header}    ${command}`;
                result.push(c1, c2);
            });
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.compatible);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    (function addCompatibleNextRule() {
        const handle = function(cmds) {
            var result = [];
            cmds.forEach(cmd => {
                var r = /^(\s*)@next(.*)$/i.exec(cmd);
                if (r == null) {
                    result.push(cmd);
                    return;
                }
                var header = r[1];
                result.push(`${header}[continue]`);
            });
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.compatible);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    (function addCompatibleExitRule() {
        const handle = function(cmds) {
            var result = [];
            cmds.forEach(cmd => {
                var r = /^(\s*)@exit(.*)$/i.exec(cmd);
                if (r == null) {
                    result.push(cmd);
                    return;
                }
                var header = r[1];
                result.push(`${header}[break]`);
            });
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.compatible);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    /***********************************************************************************\
        代码执行器层
    \***********************************************************************************/

    //---------------------------------------------------------------------------
    //  Handle Condition
    //---------------------------------------------------------------------------

    var AssertLeftMarkHandlerCenter = {
        /**
         * @param {Function} handler function(leftMark)->{handle: Bool, value: string}
         */
        addHandler: function(handler) {
            this._leftMarkHandlers.push(handler);
        },
        getValue(leftMark) {
            for (let i = 0; i < this._leftMarkHandlers.length; i++) {
                const handler = this._leftMarkHandlers[i];
                var result = handler.handle(leftMark);
                if (!result.handle) continue;
                return result.value;
            }
            return leftMark;
        },
        _leftMarkHandlers: []
    };

    class AssertWrapper {
        /**
         * @param {Function} assert1 function(string)->Bool
         * @param {string} text
         */
        constructor(assert1) {
            var theSelf = this;
            this.assert = function() {
                return assert1(theSelf.text);
            };
        }
        setText(text) {
            this.text = text;
        }
    }

    class AssertHolder {
        /**
         * @param {Function} match function(expression)->Bool
         * @param {Function} getAssertWrapper function()->AssertWrapper
         */
        constructor(match, getAssertWrapper) {
            this.match = match;
            this._getAssertWrapper = getAssertWrapper;
        }
        getAssertWrapper() {
            return this._getAssertWrapper();
        }
    }

    var AssertHolderCenter = {
        /**
         * @param {AssertHolder} holder
         */
        addAssertHolder: function(holder) {
            this._assertHolders.push(holder);
        },
        /**
         * @param {string} expression
         * @returns {Function} assert: function()
         */
        get: function(expression) {
            var exp = expression.replace(/^\s*|\s*$/g, "");
            var theSelf = this;
            var relationIndex = exp.search(/&&|\|\|/g);
            if (relationIndex != -1) {
                var relation = exp.substring(relationIndex, relationIndex + 2);
                var left = exp.substring(0, relationIndex);
                var right = exp.substring(relationIndex + 2);
                var assert = function() {
                    var leftAssert = theSelf.get(left);
                    var rightAssert = theSelf.get(right);
                    switch (relation) {
                        case "&&":
                            return leftAssert() && rightAssert();
                        case "||":
                            return leftAssert() || rightAssert();
                    }
                };
                return assert;
            }
            var not = exp[0];
            if (not == "!") {
                var assert = function() {
                    return !theSelf.get(exp.substring(1))();
                }
                return assert;
            }
            for (let i = 0; i < this._assertHolders.length; i++) {
                const holder = this._assertHolders[i];
                if (holder.match(exp)) {
                    var wrapper = holder.getAssertWrapper();
                    wrapper.setText(exp);
                    return wrapper.assert;
                }
            }
            return null;
        },
        _assertHolders: []
    };

    (function addTureAssertHolder() {
        var match = function(text) {
            return text == "true";
        };
        var assert = function(text) {
            return true;
        };
        var holder = new AssertHolder(match, function() { return new AssertWrapper(assert); });
        AssertHolderCenter.addAssertHolder(holder);
    })();

    (function addFalseAssertHolder() {
        var match = function(text) {
            return text == "false";
        };
        var assert = function(text) {
            return false;
        };
        var holder = new AssertHolder(match, function() { return new AssertWrapper(assert); });
        AssertHolderCenter.addAssertHolder(holder);
    })();

    (function addPresetConfigAssertHolder() {
        var patt = new RegExp(">=?|<=?|!=|==?");
        var match = function(text) {
            return patt.test(text);
        };
        var assert = function(text) {
            let validText = text;
            validText = validText.replace(/<(\w+)>/g, "「$1」");
            validText = validText.replace(/<(\/\w+)>/g, "「¿$1」");
            var result = patt.exec(validText);
            var opt = result[0];
            var parts = validText.split(opt);
            var left = parts[0].replace(/^\s*|\s*$/g, "");
            var lvalue = AssertLeftMarkHandlerCenter.getValue(left);
            var rvalue = parts[1].replace(/^\s*|\s*$/g, "");;
            var lfloat = parseFloat(lvalue);
            var rfloat = parseFloat(rvalue);
            var byDigit = false;
            if (!isNaN(lfloat) && !isNaN(rfloat)) {
                lvalue = lfloat;
                rvalue = rfloat;
                byDigit = true;
            }
            switch (opt) {
                case "=":
                case "==":
                if (byDigit) {
                    return Math.abs(lvalue - rvalue) < 0.001;
                } else {
                    return lvalue == rvalue;
                }
                case ">":
                return lvalue > rvalue;
                case "<":
                return lvalue < rvalue;
                case ">=":
                return lvalue >= rvalue;
                case "<=":
                return lvalue <= rvalue;
                case "!=":
                if (byDigit) {
                    return Math.abs(lvalue - rvalue) > 0.001;
                } else {
                    return lvalue != rvalue;
                }
                default:
                return false;
            }
        };
        var holder = new AssertHolder(match, function() { return new AssertWrapper(assert); });
        AssertHolderCenter.addAssertHolder(holder);
    })();

    //---------------------------------------------------------------------------
    //  Cmd Prehandler
    //---------------------------------------------------------------------------

    const CmdPrehandlerPriority = {
        ordinary: 50,
    };

    class CmdPrehandler {
        /**
         * @param {Function} handle function(performer: Performer, cmd: String) -> String
         * @param {Number} [priority]
         */
        constructor(handle, priority) {
            this._handle = handle;
            this.priority = priority ? priority : CmdPrehandlerPriority.ordinary;
        }
        handle(performer, cmd) {
            return this._handle(performer, cmd);
        }
    }

    class CmdPrehandleCenter extends CmdPrehandler {
        constructor() {
            const handle = function(performer, cmd) {
                var result = cmd;
                for (const handler of this._handlers) {
                    result = handler.handle(performer, result);
                }
                return result;
            };
            super(handle, -1);
            this._handlers = [];
            this.instance = this;
        }
        static shared() {
            if(!this.instance) {
                this.instance = new CmdPrehandleCenter();
            }
            return this.instance;
        }
        addHandler(handler) {
            SortInsert(this._handlers, handler, (p, c) => {
                return p.priority >= c.priority;
            });
        }
    }

    //---------------------------------------------------------------------------
    //  Cmd Executor
    //---------------------------------------------------------------------------

    const CmdExecutorPriority = {
        compiler: 90,

        // 层外使用
        high: 30,
        ordinary: 20,
        low: 10
    };

    class CmdExecutor {
        /**
         * @param {Function} appropriate function(cmd: String) -> Boolean
         * @param {Function} execute function(performer: Performer, cmd: String)
         * @param {Number} priority
         */
        constructor(appropriate, execute, priority) {
            this._appropriate = appropriate;
            this._execute = execute;
            this.priority = priority ? priority : CmdExecutorPriority.ordinary;
        }
        appropriate(cmd) {
            return this._appropriate(cmd);
        }
        execute(performer, cmd) {
            return this._execute(performer, cmd);
        }
    }

    var CmdExecuteCenter = {
        addExecutor: function(executor) {
            SortInsert(this._executors, executor, (p, c) => {
                return p.priority >= c.priority;
            });
        },
        execute: function(performer, cmd) {
            var valid = null;
            for (const executor of this._executors) {
                if (executor.appropriate(cmd)) {
                    valid = executor;
                    break;
                }
            }
            if (valid == null) {
                throw `无法处理此命令: ${cmd}`;
            }
            return valid.execute(performer, cmd);
        },
        _executors: []
    };

    //---------------------------------------------------------------------------
    //  Performer
    //---------------------------------------------------------------------------

    class Performer {
        /**
         * @param {String} source
         */
        constructor(name, source) {
            this._name = name;
            this._source = source;
            this._log = false;
            this._running = false;
            this._pausing = false;
        }

        name() {
            return this._name;
        }
        runing() {
            return this._running;
        }
        pausing() {
            return this._pausing;
        }
        log(value) {
            if (value == null) return this._log;
            this._log = value;
        }

        start(callback) {
            if (this._running) return;

            try {
                var compiler = new Compiler();
                var start = new Date().getTime();
                this._cmds = compiler.compile(this._source);
                var end = new Date().getTime();
                console.log(`编译总耗时: ${end - start} 毫秒`);
            } catch (err) {
                Message.append(`<ord>编译错误</ord>: ${err}`);
                return;
            }

            if (this._log) Message.append(`<hiy>开始执行,流程: ${this._name}...</hiy>`);
            this._running = true;
            this._pausing = false;

            this._callback = callback;

            this._pc = 0;
            this._cc = true;

            this._guarding = false;
            this._subflows = [];

            this._perform();
        }
        stop() {
            if (!this._running) return;
            this._running = false;
            for (const subflow of this._subflows) {
                subflow.stop();
            }
            if (this._log) Message.append(`<hiy>执行完毕,流程: ${this._name}。</hiy>`);
            if (this._callback) this._callback();
        }
        pause() {
            if (!this._running) return;
            if (this._log) Message.append(`<hiy>暂停执行,流程: ${this._name}...</hiy>`);
            this._pausing = true;
            for (const subflow of this._subflows) {
                subflow.pause();
            }
        }
        resume() {
            if (!this._running || !this._pausing) return;
            if (this._log) Message.append(`<hiy>恢复执行,流程: ${this._name}。</hiy>`);
            this._pausing = false;
            for (const subflow of this._subflows) {
                subflow.resume();
            }
            this._perform();
        }

        guarding() {
            return this._guarding;
        }
        timeSeries(timestamp) {
            if (timestamp != null) {
                if (!this.guarding()) this._systemCmdTimeSeries = timestamp;
                return;
            }
            return this._systemCmdTimeSeries;
        }

        async _perform() {
            if (!this._running || this._pausing) return;
            if (this._doing) return;

            var cmd = this._cmds[this._pc];
            console.log(`>>> ${this._name}, ${this._pc}, ${this._cc}, ${cmd}`);
            this._pc += 1;

            try {
                this._doing = true;
                await CmdExecuteCenter.execute(this, cmd);
            } catch (err) {
                Message.append(`<ord>执行错误</ord>: ${err}`);
                this.stop();
                return;
            } finally {
                this._doing = false;
            }
            this._perform();
        }
    }

    // Compile Cmd Executor

    (function() {
        const appropriate = function(cmd) {
            return cmd == "%exit";
        };
        const execute = function(performer, cmd) {
            performer.stop();
        };
        const executor = new CmdExecutor(appropriate, execute, CmdExecutorPriority.compiler);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return cmd == "%pass";
        };
        const execute = function(performer, cmd) { };
        const executor = new CmdExecutor(appropriate, execute, CmdExecutorPriority.compiler);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return cmd.indexOf("%PC=CC") == 0;
        };
        const execute = function(performer, cmd) {
            performer._pc = eval(`performer._cc${cmd.substring(6)}`);
        };
        const executor = new CmdExecutor(appropriate, execute, CmdExecutorPriority.compiler);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return cmd.indexOf("%PC=") == 0;
        };
        const execute = function(performer, cmd) {
            performer._pc = eval(cmd.substring(4));
        };
        const executor = new CmdExecutor(appropriate, execute, CmdExecutorPriority.compiler);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return cmd.indexOf("%CC=") == 0;
        };
        const execute = function(performer, cmd) {
            const validCmd = CmdPrehandleCenter.shared().handle(performer, cmd);
            var assert = AssertHolderCenter.get(validCmd.substring(4));
            performer._cc = assert();
        };
        const executor = new CmdExecutor(appropriate, execute, CmdExecutorPriority.compiler);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return cmd == "%guardStart";
        };
        const execute = function(performer, cmd) {
            performer._guarding = true;
        };
        const executor = new CmdExecutor(appropriate, execute, CmdExecutorPriority.compiler);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return cmd == "%guardEnd";
        };
        const execute = function(performer, cmd) {
            performer._guarding = false;
        };
        const executor = new CmdExecutor(appropriate, execute, CmdExecutorPriority.compiler);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return /^<===[^爫]*===>$/.test(cmd);
        };
        const execute = function(performer, cmd) {
            const source = cmd.slice(4, -4);
            const p = new Performer("subflow", source);
            performer._subflows.push(p);
            p.start();
        };
        const executor = new CmdExecutor(appropriate, execute, CmdExecutorPriority.compiler);
        CmdExecuteCenter.addExecutor(executor);
    })();

    //---------------------------------------------------------------------------
    //  Variable
    //---------------------------------------------------------------------------

    function TryCalculate(expression) {
        if (/^[0-9\+\-\*\/% ]*$/g.test(expression)) {
            return eval(expression);
        }
        return expression;
    }

    var PersistentVariables = null; // PersistentCache

    function UpdateVariable(performer, name, exp) {
        if (/^_*[A-Z][a-zA-Z0-9_]*$/.test(name)) {
            PersistentVariables.save(name, TryCalculate(exp));
        } else if (/^_*[a-z][a-zA-Z0-9_]*$/.test(name)) {
            if (performer.tempParams == null) {
                performer.tempParams = {};
            }
            performer.tempParams[name] = TryCalculate(exp);
        }
    }

    (function() {
        var patt = /^\(\$([A-Za-z_][a-zA-Z0-9_]*?)\)\s*=\s*(.+)\s*/;
        const appropriate = function(cmd) {
            return patt.test(cmd);
        };
        const execute = function(performer, cmd) {
            const validCmd = CmdPrehandleCenter.shared().handle(performer, cmd);
            var result = patt.exec(validCmd);
            var name = result[1];
            var exp = result[2];
            UpdateVariable(performer, name, exp);
        };
        const executor = new CmdExecutor(appropriate, execute);
        CmdExecuteCenter.addExecutor(executor);
    })();

    var VariableStore = {
        register: function(getAll) {
            this._allGetAll.push(getAll);
        },
        getAll: function() {
            var result = {};
            for (const getAll of this._allGetAll) {
                const all = getAll();
                for (const key in all) {
                    if (!all.hasOwnProperty(key)) continue;
                    result[key] = all[key];
                }
            }
            return result;
        },
        _allGetAll: []
    };

    (function() {
        const _assignVariables = function(expression, params) {
            var placeholders = [];
            var patt = /\([:a-zA-Z0-9_]+?\)/g;
            var result = patt.exec(expression);
            while(result != null) {
                placeholders.push(result[0]);
                result = patt.exec(expression);
            }
            let assignedExp = expression;
            for (let i = 0; i < placeholders.length; i++) {
                const placeholder = placeholders[i];
                var key = placeholder.substring(1, placeholder.length - 1);
                var value = params[key];
                if (value == null) value = "null";
                assignedExp = assignedExp.replace(placeholder, value);
            }

            let placeholders2 = [];
            let patt2 = /\((:[a-zA-Z0-9_]+?)\s+([^\(\)\s][^\(\)]*?)\s*\)/g;
            let r2 = patt2.exec(assignedExp);
            while(r2 != null) {
                placeholders2.push({value: r2[0], key: `${r2[1]} `, params: r2[2]});
                r2 = patt2.exec(assignedExp);
            }
            for (const p of placeholders2) {
                const func = params[p.key];
                let value = "null";
                if (func != null && typeof func == "function") {
                    value = func(p.params);
                }
                assignedExp = assignedExp.replace(p.value, value);
            }

            return assignedExp;
        };
        const assignVariables = function(expression, params) {
            let result = expression;
            while (true) {
                const assigned = _assignVariables(result, params);
                if (assigned == result) return result;
                result = assigned;
            }
        };
        const handle = function(performer, cmd) {
            let allParam = {};
            Object.assign(allParam, VariableStore.getAll(), performer.tempParams);
            const result = assignVariables(cmd, allParam);
            return result;
        };
        const handler = new CmdPrehandler(handle)
        CmdPrehandleCenter.shared().addHandler(handler);
    })();

    /***********************************************************************************\
        System Library
    \***********************************************************************************/

    class AtCmdExecutor extends CmdExecutor {
        constructor(key, execute, priority) {
            const appropriate = function(cmd) {
                return cmd.indexOf(`@${key}`) == 0;
            };
            const superExecute = function(performer, cmd) {
                const validCmd = CmdPrehandleCenter.shared().handle(performer, cmd);
                let param = /^\s*(.*)\s*$/.exec(validCmd.substring(key.length + 1))[1];
                if (param && param.length == 0) param = null;
                return execute(performer, param);
            };
            super(appropriate, superExecute, priority);
        }
    }

    (function() {
        const executor = new AtCmdExecutor("wait", function(performer, param) {
            if (performer.log()) Message.cmdLog(`等待 ${(param/1000).toFixed(2)} 秒`);
            return new Promise(resolve => {
                setTimeout(() => resolve(), param);
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("await", function(performer, param) {
            return new Promise(resolve => {
                setTimeout(() => resolve(), param);
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("debug", function(performer, param) {
            let text = param;
            if (text[0] == ">") {
                text = JSON.stringify(eval(text.substring(1)));
            }
            var message = `&nbsp;&nbsp;[debug]: <hiz>${text}</hiz>`;
            Message.append(message);
            console.log(message);
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("print", function(performer, param) {
            Message.append(param);
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    class UntilAtCmdExecutor extends CmdExecutor {
        constructor(key, assert, priority, tryAgain, timeout) {
            const appropriate = function(cmd) {
                return cmd.indexOf(`@${key}`) == 0;
            };
            const superExecute = function(performer, cmd) {
                const tryExecute = function(callback) {
                    const validCmd = CmdPrehandleCenter.shared().handle(performer, cmd);
                    let param = /^\s*(.*)\s*$/.exec(validCmd.substring(key.length + 1))[1];
                    if (param != null && param.length == 0) param = null;
                    const result = assert(performer, param);
                    if (result == true) {
                        if (timeout != null) {
                            setTimeout(_ => { callback(); }, timeout);
                        } else {
                            callback();
                        }
                    } else {
                        setTimeout(_ => { tryExecute(callback); }, tryAgain != null ? tryAgain : 500);
                    }
                };
                if (performer.log()) Message.cmdLog("等待,直至符合条件", cmd);
                return new Promise(resolve => {
                    tryExecute(resolve);
                });
            };
            super(appropriate, superExecute, priority);
            this._key = key;
            this._assert = assert;
        }
    }

    (function() {
        const executor = new UntilAtCmdExecutor("until", function(performer, param) {
            const assert = AssertHolderCenter.get(param);
            return assert();
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return cmd.indexOf("@js ") == 0;
        };
        const execute = function(performer, cmd) {
            const validCmd = CmdPrehandleCenter.shared().handle(performer, cmd);
            let exp = validCmd.substring(4);
            if (performer.log()) Message.cmdLog("调用 js", exp);
            const result = /^\(\$([A-Za-z_][a-zA-Z0-9_]*?)\)\s*=\s*/.exec(exp);
            if (result == null) {
                eval(exp);
                return;
            }
            const name = result[1];
            exp = exp.substring(result[0].length);
            UpdateVariable(performer, name, eval(exp));
        };
        const executor = new CmdExecutor(appropriate, execute);
        CmdExecuteCenter.addExecutor(executor);
    })();

    /***********************************************************************************\
        Time Variables
    \***********************************************************************************/

    VariableStore.register(_ => {
        return {
            ":date": new Date().getDate(),
            ":day": new Date().getDay(),
            ":hour": new Date().getHours(),
            ":minute": new Date().getMinutes(),
            ":second": new Date().getSeconds()
        }
    });

    /***********************************************************************************\
        Compatible With wsmud_pluginss
    \***********************************************************************************/

    /**
     * @param {String} source
     * @param {Function} callback function(resolve)->void
     */
    function PerformerPromise(source, callback, log) {
        return new Promise(resolve => {
            const p = new Performer("", source);
            if (log) p.log(log);
            p.start(_ => {
                if (callback) {
                    callback(resolve);
                } else {
                    resolve();
                }
            });
        });
    }

    (function() {
        const appropriate = function(cmd) {
            return cmd.indexOf("$wait ") == 0;
        };
        const execute = function(performer, cmd) {
            return PerformerPromise(`@wait ${cmd.substring(6)}`, null, performer.log());
        };
        const executor = new CmdExecutor(appropriate, execute);
        CmdExecuteCenter.addExecutor(executor);
    })();

    /***********************************************************************************\
        User Config Param
    \***********************************************************************************/

    var __ConfigDomIdCounter = 0;
    function GetConfigDomId() {
        const id = __ConfigDomIdCounter;
        __ConfigDomIdCounter += 1;
        return `wsmud_raid_config_dom_id_${id}`;
    }

    var __ConfigPanelHtml = "";
    var __ConfigPanelInits = [];
    var __ConfigPanelActions = [];

    class HashCmdExecutor extends CmdExecutor {
        constructor(key, handle) {
            const appropriate = function(cmd) {
                return cmd.indexOf(`#${key}`) == 0;
            };
            const superHandle = function(performer, cmd) {
                const validCmd = CmdPrehandleCenter.shared().handle(performer, cmd);
                const param = validCmd.substring(this._key.length + 2);
                const result = handle(performer, cmd, param);
                if (result == null) return;
                if (result.html) __ConfigPanelHtml += result.html;
                if (result.init) __ConfigPanelInits.push(result.init);
                if (result.action) __ConfigPanelActions.push(result.action);
            };
            super(appropriate, superHandle);
            this._key = key;
        }
    }

    (function() {
        const executor = new HashCmdExecutor("input", function(performer, cmd, param) {
            const result = /^\(\$([a-zA-Z0-9_]+)\)\s?=\s?([^,]+?),(.*)\s*$/.exec(param);
            if (result == null) {
                throw `错误的格式: ${cmd}`;
            }
            const variableName = result[1];
            const desc = result[2];
            const defaultValue = result[3] == null ? "" : result[3];
            const id = GetConfigDomId();
            const html = `
            <p>
                <label for="${id}">&nbsp;* ${desc}:&nbsp;</label><input style='width:80px' id ="${id}" type="text">
            </p>`;
            const init = function() {
                $(`#${id}`).val(defaultValue);
            };
            const action = function() {
                let result = {};
                result[variableName] = $(`#${id}`).val();
                return result;
            };
            return {html: html, init: init, action: action};
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new HashCmdExecutor("select", function(performer, cmd, param) {
            const result = /^\(\$([a-zA-Z0-9_]+)\)\s?=\s?([^,]+?),([^,]+?),([^,]+?)\s*$/.exec(param);
            if (result == null) {
                throw `错误的格式: ${cmd}`;
            }
            const variableName = result[1];
            const desc = result[2];
            const options = result[3].split("|");
            const defaultValue = result[4];
            const id = GetConfigDomId();
            let optionsHtml = "";
            options.forEach(option => {
                optionsHtml += `<option value="${option}">${option}</option>`;
            });
            const html = `
            <p>
                <label for="${id}">&nbsp;* ${desc}:&nbsp;</label><select style='width:80px' id="${id}">
                    ${optionsHtml}
                </select>
            </p>`;
            const init = function() {
                $(`#${id}`).val(defaultValue);
            };
            const action = function() {
                let result = {};
                result[variableName] = $(`#${id}`).val();
                return result;
            };
            return {html: html, init: init, action: action};
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const appropriate = function(cmd) {
            return /^#config\s*$/.test(cmd);
        };
        const execute = function(performer, cmd) {
            return new Promise(resolve => {
                var index = layer.open({
                    type: 1,
                    skin: "layui-layer-rim", //加上边框
                    area: "350px",
                    title: "配置参数",
                    content: __ConfigPanelHtml,
                    offset:"auto",
                    shift: 2,
                    move: false,
                    closeBtn: 0,
                    success: function(layero, index) {
                        __ConfigPanelInits.forEach(init => { init(); });
                        for (const node of layero[0].children) {
                            if (node.className != "layui-layer-content") continue;
                            node.setAttribute("style", "max-height: 370px");
                        }
                    },
                    end: function() {
                        __ConfigPanelHtml = "";
                        __ConfigPanelInits = [];
                        __ConfigPanelActions = [];
                    },
                    btn: ['运行流程', '取消'],
                    yes: function() {
                        __ConfigPanelActions.forEach(action => {
                            const params = action();
                            for (const key in params) {
                                if (!params.hasOwnProperty(key)) continue;
                                UpdateVariable(performer, key,  params[key]);
                            }
                        });
                        layer.close(index);
                        resolve();
                    },
                    btn2: function() {
                        performer.stop();
                        resolve();
                    }
                });
            });
        };
        const executor = new CmdExecutor(appropriate, execute);
        CmdExecuteCenter.addExecutor(executor);
    })();

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

    var WG = null;
    var messageAppend = null;
    var messageClear = null;
    var T = null;
    var L = null;

    Message.append = function(msg) {
        messageAppend(msg);
    };
    Message.clean = function() {
        messageClear();
    };

    const RoleState = {
        none: "发呆",
        liaoshang: "疗伤",
        dazuo: "打坐",
        wakuang: "挖矿",
        gongzuo: "工作",
        lianxi: "练习",
        xuexi: "学习",
        biguan: "闭关",
        lianyao: "炼药",
        lingwu: "领悟",
        dushu: "读书",
        juhun: "聚魂",
        tuiyan: "推演"
    };

    /**
     * @param {string} itemName
     * @param {Boolean} blurry
     * @param {string} [quality] white(w), green(g), blue(b), yellow(y), purple(p), orange(o), red(r)
     */
    function FindItem(list, itemName, blurry, quality, filterExp) {
        var pattStr = blurry ? itemName : "^" + itemName + "$";
        if (/<[a-zA-Z]{3}>.+<\/[a-zA-Z]{3}>/g.test(itemName)) {
            pattStr = "^" + itemName + "$";
        } else if (quality != null) {
            var map = {
                "white": "wht",
                "w": "wht",
                "green": "hig",
                "g": "hig",
                "blue": "hic",
                "b": "hic",
                "yellow": "hiy",
                "y": "hiy",
                "purple": "HIZ",
                "p": "HIZ",
                "orange": "hio",
                "o": "hio",
                "red": "ord",
                "r": "ord"
            };
            var tag = map[quality];
            if (tag != null) {
                if (blurry) {
                    pattStr = "<" + tag + ">.*" + itemName + ".*</" + tag + ">";
                } else {
                    pattStr = "<" + tag + ">" + itemName + "</" + tag + ">";
                }
            }
        }
        var patt = new RegExp(pattStr);
        for (const item of list) {
            if (patt.test(item.name) && !FilterCenter.filter(filterExp, item)) {
                return item;
            }
        }
        return null;
    }

    var Role = {
        id: null,
        name: null,

        hp: 0,
        maxHp: 0,
        mp: 0,
        maxMp: 0,

        status: {},
        equipments: [],
        items: {}, // {id: object}
        stores: {}, // {id: object}

        kongfu: {
            quan: null,
            nei: null,
            zhao: null,
            qing: null,
            jian: null,
            dao: null,
            gun: null,
            zhang: null,
            bian: null,
            an: null
        },

        init: function() {
            WG.add_hook("login", function(data) {
                Role.id = data.id;
                Role.status = [];
                setTimeout(function() {
                    $("span[command=skills]").click();
                    setTimeout(_ => { $(".glyphicon-remove-circle").click(); }, 500);
                }, 2000); // 查看装备技能
                // if (GM_getValue(`###CodeTranslator@${Role.id}`, null) != "did") {
                //     CodeTranslator.run();
                //     GM_setValue(`###CodeTranslator@${Role.id}`, "did");
                // }
                UI.showToolbar();
                setTimeout(_ => { Server.getNotice(); }, 3000);
            });
            $("li[command=SelectRole]").on("click", function () {
                Role.name = $('.role-list .select').text().split(/\s+/).pop();
            });
            Role._monitorHpMp();
            Role._monitorStatus();
            Role._monitorState();
            Role._monitorDeath();
            Role._monitorSkillCD();
            Role._monitorSkills();
            Role._monitorGains();
            Role._monitorItems();
            Role._monitorCombat();
        },

        hasStatus: function(s) {
            var stamp = Role.status[s];
            if (stamp == null) return false;
            if (stamp < new Date().getTime()) return false;
            return true;
        },
        isFree: function() {
            return !Role.hasStatus("busy") && !Role.hasStatus("faint") && !Role.hasStatus("rash");
        },

        gains(from, to) {
            var theGains = Role._gains.slice();
            var start = -1;
            var end = -1;
            for (let i = 0; i < theGains.length; i++) {
                const gain = theGains[i];
                if (gain.timestamp >= from) { start = i; break; }
            }
            for (let j = theGains.length - 1; j >= 0; j--) {
                const gain = theGains[j];
                if (gain.timestamp <= to) { end = j; break; }
            }
            if (start == -1 || end == -1) return [];
            return theGains.slice(start, end + 1);
        },

        state: RoleState.none,

        wearing: function(eqId) {
            for (const eq of this.equipments) {
                if (eq != null && eq.id == eqId) return true;
            }
            return false;
        },

        getEqId: function(index) {
            const eq = this.equipments[index];
            if (eq == null) return null;
            return eq.id;
        },

        living: true,

        combating: false,

        rtime: false,

        shimen: function(callback) {
            var timestamp = new Date().getTime();
            Role._shimen(0, timestamp, callback);
        },

        atPath: function(p) {
            switch (arguments.length) {
            case 0:
                return Room.path;
            case 1:
                return p == Room.path;
            }
        },
        inRoom: function(n) {
            switch (arguments.length) {
            case 0:
                return Room.name;
            case 1:
                return n == Room.name;
            }
        },

        findItem: function(itemName, blurry, quality, filterExp) {
            return FindItem(Object.values(Role.items), itemName, blurry, quality, filterExp);
        },

        renew: function(callback) {
            const source = `
            stopstate;$to 扬州城-武庙
            @liaoshang
            [if] (:mpPer)<0.8
                @dazuo
                stopstate
            `;
            const p = new Performer("", source);
            p.start(callback);
        },

        cleanBag: function(callback) {
            WG.clean_all();
            if (callback) callback();
        },

        tidyBag: function(callback) {
            Role._tidyBag(0, callback);
        },

        hasCoolingSkill: function() {
            return Role._coolingSkills.length > 0;
        },
        coolingSkills: function() {
            var result = [];
            for (const mark of Role._coolingSkills) {
                result.push(mark.split("_")[0]);
            }
            return result;
        },
        coolingSkill: function(skill) {
            return this.coolingSkills().indexOf(skill) != -1
        },

        _renewHookIndex: null,
        _renewStatus: "resting",

        _coolingSkills: [],
        _gains: [], // [{timestamp: number, name: string, count: number, unit: string}]

        _shimen: function(counter, timestamp, callback) {
            if (counter == 0) {
                WG.SendCmd("stopstate");
                WG.sm_button();
            }
            var result = SystemTips.search("你先去休息|和本门毫无瓜葛|你没有", timestamp);
            if (result != null) { callback(); return; }
            setTimeout(function() { Role._shimen(counter + 1, timestamp, callback) }, 1000);
        },

        _tidyBag: function(counter, callback) {
            if (counter == 0) WG.sell_all();

            if (!WG.packup_listener) {
                window.setTimeout(callback, 1000);
                return;
            }
            if (counter > 10) {
                if (WG.packup_listener) WG.sell_all();
                callback();
                return;
            }
            window.setTimeout(function() { Role._tidyBag(counter + 1, callback); }, 1000);
        },

        _monitorHpMp: function() {
            WG.add_hook(["items", "sc", "itemadd"], function(data) {
                switch (data.type) {
                case "items":
                    if (data.items == null) break;
                    for (var i = data.items.length - 1; i >= 0; i--) {
                        var item = data.items[i];
                        if (item.id == Role.id) {
                            Role.hp = item.hp;
                            Role.maxHp = item.max_hp;
                            Role.mp = item.mp;
                            Role.maxMp = item.max_mp;
                            break;
                        }
                    }
                    break;
                case "itemadd":
                case "sc":
                    if (data.id != Role.id) break;
                    if (data.hp != null) Role.hp = data.hp;
                    if (data.max_hp != null) Role.maxHp = data.max_hp;
                    if (data.mp != null) Role.mp = data.mp;
                    if (data.max_mp != null) Role.maxMp = data.max_mp;
                    break;
                }
            });
        },
        _monitorStatus: function() {
            WG.add_hook(["items", "status", "itemadd"], function(data) {
                switch (data.type) {
                case "items":
                    if (data.items == null) break;
                    for (var i = data.items.length - 1; i >= 0; i--) {
                        var item = data.items[i];
                        if (item.id != Role.id) continue;
                        if (item.status == null) break;
                        Role.status = {};
                        var timestamp = new Date().getTime();
                        for (var j = item.status.length - 1; j >= 0; j--) {
                            var s = item.status[j];
                            Role.status[s.sid] = timestamp + s.duration - s.overtime;
                        }
                        break;
                    }
                    break;
                case "status":
                    if (data.id != Role.id) break;
                    var timestamp1 = new Date().getTime();
                    if (data.action == "add") {
                        Role.status[data.sid] = timestamp1 + data.duration;
                    } else if (data.action == "remove") {
                        delete Role.status[data.sid];
                    }
                    break;
                case "itemadd":
                    if (data.id != Role.id) break;
                    if (data.status == null) break;
                    Role.status = {};
                    var timestamp2 = new Date().getTime();
                    for (var k = data.status.length - 1; k >= 0; k--) {
                        var s1 = data.status[k];
                        Role.status[s1.sid] = timestamp2 + s1.duration - s1.overtime;
                    }
                    break;
                }
            });
        },
        _monitorState: function() {
            WG.add_hook("state", function(data) {
                var text = data.state;
                if (text == null) {
                    Role.state = RoleState.none;
                    return;
                }
                for (const key in RoleState) {
                    if (!RoleState.hasOwnProperty(key)) continue;
                    const keyword = RoleState[key];
                    if (text.indexOf(keyword) != -1) {
                        Role.state = keyword;
                        return;
                    }
                }
                Role.state = RoleState.none;
            });
        },
        _monitorDeath: function() {
            WG.add_hook("die", function(data) {
                if (data.relive == true) {
                    Role.living = true;
                } else {
                    Role.living = false;
                }
            });
        },
        _monitorItems: function() {
            WG.add_hook("dialog", function(data) {
                if (data.dialog == null) return;
                if (data.dialog == "pack") {
                    if (data.items != null) {
                        Role.items = {};
                        for (const item of data.items) {
                            if (item.id) Role.items[item.id] = item;
                        }
                    } else if (data.id != null) {
                        if (data.remove == null && data.count != null) {
                            Role.items[data.id] = data;
                            return;
                        } else if (data.remove != null) {
                            var item = Role.items[data.id];
                            if (item == null) return; // 从随从那里那回东西
                            if (item.count != null) {
                                item.count -= data.remove;
                            } else {
                                item.count = 0;
                            }
                            if (item.count == 0) delete Role.items[data.id];
                        }
                    }
                    if (data.eqs != null) {
                        Role.equipments = CopyObject(data.eqs);
                    } else if (data.uneq != null && data.id != null) {
                        let item = Role.equipments[data.uneq];
                        item.count = 1;
                        item.id = data.id;
                        Role.items[item.id] = item;
                        Role.equipments[data.uneq] = null;
                    } else if (data.eq != null && data.id != null) {
                        let item = Role.items[data.id];
                        Role.equipments[data.eq] = item;
                        delete Role.items[data.id];
                    }
                }
                if (data.dialog == "list") {
                    if (data.stores != null) {
                        Role.stores = {};
                        for (const item of data.stores) {
                            if (item.id) Role.stores[item.id] = item;
                        }
                    } else if (data.id != null && data.storeid != null && data.store != null) {
                        var item = Role.items[data.id];
                        var store = Role.stores[data.storeid];
                        if (item == null) {
                            item = Object.assign({}, store, {count: 0});
                            item.id = data.id;
                            Role.items[item.id] = item;
                        }
                        if (store == null) {
                            store = Object.assign({}, item, {count: 0});
                            Role.stores[store.id] = store;
                        }
                        item.count -= data.store;
                        store.count += data.store;
                        if (item.count <= 0) delete Role.items[data.id];
                        if (store.count <= 0) delete Role.stores[data.storeid];
                    }
                }
            });
        },
        _monitorGains: function() {
            WG.add_hook("dialog", function(data) {
                if (data.dialog != "pack" || data.id == null || data.name == null || data.unit == null || data.count == null || data.remove != null) return;
                var timestamp = new Date().getTime();
                // [{timestamp: number, name: string, count: number, unit: string}]
                var old = Role.items[data.id];
                var count = data.count;
                if (old != null && old.count != null) {
                    count -= old.count;
                }
                var gain = {timestamp: timestamp, name: data.name, count: count, unit: data.unit};
                Role._gains.push(gain);
            });
        },
        _monitorSkillCD: function() {
            WG.add_hook("dispfm", function(data) {
                var timestamp = Date.parse(new Date());
                var mark = data.id + "_" + timestamp;
                Role._coolingSkills.push(mark);
                window.setTimeout(function() {
                    var index = Role._coolingSkills.indexOf(mark);
                    if (index != -1) Role._coolingSkills.splice(index, 1);
                }, data.distime);
                if (data.rtime != null && data.rtime != 0) {
                    if (Role._rtimer != null) clearTimeout(Role._rtimer);
                    Role.rtime = true;
                    Role._rtimer = setTimeout(_ => {
                        Role.rtime = false;
                    }, data.rtime);
                }
            });
        },
        _monitorSkills: function() {
            var action = function(id, value) {
                switch (id) {
                    case "unarmed":
                    Role.kongfu.quan = value; break;
                    case "force":
                    Role.kongfu.nei = value; break;
                    case "parry":
                    Role.kongfu.zhao = value; break;
                    case "dodge":
                    Role.kongfu.qing = value; break;
                    case "sword":
                    Role.kongfu.jian = value; break;
                    case "blade":
                    Role.kongfu.dao = value; break;
                    case "club":
                    Role.kongfu.gun = value; break;
                    case "staff":
                    Role.kongfu.zhang = value; break;
                    case "whip":
                    Role.kongfu.bian = value; break;
                    case "throwing":
                    Role.kongfu.an = value; break;
                    default:
                    break;
                }
            };
            WG.add_hook("dialog", function(data) {
                if (data.dialog == null || data.dialog != "skills") return;
                if (data.items != null) {
                    for (const item of data.items) {
                        var value = item.enable_skill ? item.enable_skill : null;
                        action(item.id, value);
                    }
                }
                if (data.id != null && data.enable != null) {
                    var value = data.enable;
                    if (value == false) value = "none";
                    action(data.id, value);
                }
            });
        },
        _monitorCombat: function() {
            WG.add_hook("combat", function(data) {
                if (data.start != null && data.start == 1) {
                    Role.combating = true;
                } else if (data.end != null && data.end == 1) {
                    Role.combating = false;
                }
            });
            WG.add_hook("text", function(data) {
                if (data.msg == null) return;
                if (data.msg.indexOf('只能在战斗中使用') != -1 || data.msg.indexOf('这里不允许战斗') != -1 || data.msg.indexOf('没时间这么做') != -1) {
                    Role.combating = false;
                }
            });
        }
    };

    var Room = {
        name: null,
        path: null,

        updateTimestamp: null,

        init: function() {
            this._monitorLocation();
            this._monitorItemsInRoom();
            this._monitorDeath();
        },
        getItem: function(id) {
            return this._itemsInRoom[id];
        },
        getItemId: function(name, blurry, living, filterExp) {
            for (const item of Object.values(this._itemsInRoom)) {
                if (blurry == true) {
                    if (item.name.indexOf(name) != -1) {
                        if (living == true && item.name.indexOf("的尸体") != -1) {
                            continue;
                        }
                        if (FilterCenter.filter(filterExp, item)) {
                            continue;
                        }
                        return item.id;
                    }
                } else {
                    if (item.name == name && !FilterCenter.filter(filterExp, item)) {
                        return item.id;
                    }
                }
            }
            return null;
        },
        /**
         * @param {{name: string, blurry: Boolean}[]} itemNameInfos
         * @returns {Boolean}
         */
        didKillItemsInRoom: function(itemNameInfos) {
            var deadItems = this._deadItemsInRoom.slice();
            for (const info of itemNameInfos) {
                var found = false;
                for (let j = 0; j < deadItems.length; j++) {
                    const deadItem = deadItems[j];
                    if (info.blurry == true) {
                        if (deadItem.name.indexOf(info.name) != -1) found = true;
                    } else {
                        if (deadItem.name == info.name) found = true;
                    }
                    if (found) {
                        deadItems.splice(j, 1);
                        break;
                    }
                }
                if (!found) return false;
            }
            return true;
        },

        _itemsInRoom: {},
        _deadItemsInRoom: [],

        _monitorLocation: function() {
            WG.add_hook("room", function(data) {
                Room.name = data.name;
                Room.path = data.path;
                Room.updateTimestamp = new Date().getTime();
                Room._itemsInRoom = {};
                Room._deadItemsInRoom = [];
            });
        },
        _monitorItemsInRoom: function() {
            WG.add_hook(["items", "itemadd", "itemremove", "sc", "status"], function(data) {
                switch (data.type) {
                case "items":
                    if (data.items == null) break;
                    for (const item of data.items) {
                        if (item.name == null || item.id == null) continue;
                        Room._itemsInRoom[item.id] = item;
                    }
                    break;
                case "itemadd":
                    if (data.name == null || data.id == null) break;
                    Room._itemsInRoom[data.id] = data;
                    break;
                case "itemremove":
                    if (data.id == null) break;
                    delete Room._itemsInRoom[data.id];
                    break;
                case "sc": {
                    if (data.id == null) break;
                    const item = Room._itemsInRoom[data.id];
                    if (item == null) break;
                    if (data.hp != null) item.hp = data.hp;
                    if (data.max_hp != null) item.max_hp = data.max_hp;
                    if (data.mp != null) item.mp = data.mp;
                    if (data.max_mp != null) item.max_mp = data.max_mp;
                    break;}
                case "status": {
                    if (data.action == null || data.id == null || data.sid == null) return;
                    const item = Room._itemsInRoom[data.id];
                    if (item == null) break;
                    if (data.action == "add") {
                        if (item.status == null) item.status = [];
                        item.status.push({sid: data.sid, name: data.name, duration: data.duration, overtime: 0});
                    } else if (data.action == "remove") {
                        for (let i = 0; i < item.status.length; i++) {
                            const s = item.status[i];
                            if (s.sid == data.sid) {
                                item.status.splice(i, 1);
                                break;
                            }
                        }
                    }
                    break;}
                }
            });
        },
        _monitorDeath: function() {
            WG.add_hook("sc", function(data) {
                if (data.id == null|| data.hp == null || data.hp != 0) return;
                for (const item of Object.values(Room._itemsInRoom)) {
                    if (item.id == data.id) {
                        Room._deadItemsInRoom.push(item);
                        return;
                    }
                }
            });
        }
    };

    class SystemTip {
        constructor(text) {
            this.timestamp = new Date().getTime();
            this.text = text;
        }
    }

    var SystemTips = {
        init: function() {
            this._monitorSystemTips();
        },
        search: function(regex, from) {
            var patt = new RegExp(regex);
            var tips = this._tips.slice();
            for (let index = tips.length - 1; index >= 0; index--) {
                const tip = tips[index];
                if (tip.timestamp < from) break;
                var result = patt.exec(tip.text);
                if (result) return result;
            }
            return null;
        },
        clean: function(to) {
            while(true) {
                if (this._tips.length <= 0) break;
                var tip = this._tips[0];
                if (tip.timestamp > to) break;
                this._tips.shift();
            }
        },
        rejectTimestamp: null,

        _monitorSystemTips: function() {
            var theSelf = this;
            WG.add_hook("text", function(data) {
                var tip = new SystemTip(data.msg);
                theSelf._push(tip);

                if (data.msg == "不要急,慢慢来。") {
                    theSelf.rejectTimestamp = new Date().getTime();
                }
            });
            WG.add_hook("item", function(data) {
                var desc = data.desc;
                if (desc == null) return;
                var tip = new SystemTip(desc);
                theSelf._push(tip);
            });
        },
        _push: function(tip) {
            if (this._tips.length >= this._maxCapacity) {
                this._tips.shift();
            }
            this._tips.push(tip);
        },
        _tips: [],
        _maxCapacity: 100,
    };

    var DialogList = {
        init: function() {
            this._monitorDialogList();
        },
        timestamp: null,
        findItem: function(itemName, blurry, quality, filterExp) {
            return FindItem(this._list, itemName, blurry, quality, filterExp);
        },

        _list: [],
        _monitorDialogList: function() {
            const self = this;
            WG.add_hook("dialog", function(data) {
                let list = null;
                if (data.selllist != null) {
                    list = data.selllist;
                } else if (data.stores != null) {
                    list = data.stores;
                } else if (data.dialog == "pack2" && data.items != null) {
                    list = data.items;
                }
                if (list == null) return;
                self.timestamp = new Date().getTime();
                self._list = list;
            });
        },
    };

    var TaskList = {
        init: function() {
            this._monitorTasksList();
        },
        search: function(regex, from) {
            if (this._timestamp < from) return null;
            var patt = new RegExp(regex);
            for (const task of this._list) {
                const result = patt.exec(task);
                if (result) return result;
            }
            return null;
        },

        _timestamp: null,
        _list: [],
        _monitorTasksList: function() {
            const self = this;
            WG.add_hook("dialog", function(data) {
                if (data.dialog == null || data.dialog != "tasks" || data.items == null) return;
                let list = [];
                for (const item of data.items) {
                    list.push(item.desc);
                }
                self._timestamp = new Date().getTime();
                self._list = list;
            });
        }
    };

    var Xiangyang = {
        init: function() {
            this._monitorXiangyang();
        },
        search: function(regex, from) {
            if (this._timestamp < from) return null;
            var patt = new RegExp(regex);
            const result = patt.exec(this._desc);
            if (result) return result;
            return null;
        },

        _timestamp: null,
        _desc: '',
        _monitorXiangyang: function() {
            const self = this;
            WG.add_hook('dialog', function(data) {
                if (data.dialog == null || data.t != 'fam' || data.index != 8 || data.desc == null) return;
                self._timestamp = new Date().getTime();
                self._desc = data.desc;
            });
        }
    };

    /***********************************************************************************\
        Persistent Cache
    \***********************************************************************************/

    (function() {
        const FlowStoreKey = function() { return `flow_store@${Role.id}`; };
        const getMap = function() {
            let map = GM_getValue(FlowStoreKey(), null);
            if (map == null) {
                // 之前 FlowStoreKey 会错误地一只返回 flow_store@null
                map = GM_getValue("flow_store@null", {});
            }
            return map;
        };
        FlowStore = new PersistentCache((key, value) => {
            let map = getMap();
            map[key] = value;
            GM_setValue(FlowStoreKey(), map);
        }, _ => {
            return getMap();
        }, key => {
            let map = getMap();
            delete map[key];
            GM_setValue(FlowStoreKey(), map);
        });
        FlowStore.corver = function(value) {
            GM_setValue(FlowStoreKey(), value);
        };
    })();

    (function() {
        const PersistentVariablesKey = function() { return `global_params@${Role.id}`; };
        const getMap = function() {
            let map = GM_getValue(PersistentVariablesKey(), null);
            if (map == null) {
                // 之前 PersistentVariablesKey 会错误地一只返回 global_params@null
                map = GM_getValue("global_params@null", {});
            }
            return map;
        };
        PersistentVariables = new PersistentCache((key, value) => {
            let map = getMap();
            map[key] = value;
            GM_setValue(PersistentVariablesKey(), map);
        }, _ => {
            return getMap();
        }, key => {
            let map = getMap();
            delete map[key];
            GM_setValue(PersistentVariablesKey(), map);
        });
        VariableStore.register(_ => { return PersistentVariables.getAll(); });
    })();

    VariableStore.register(_ => {
        return {
            ":id": Role.id,
            ":name": Role.name,
            ":hp": Role.hp,
            ":maxHp": Role.maxHp,
            ":hpPer": Role.hp/Role.maxHp,    // 0-1
            ":mp": Role.mp,
            ":maxMp": Role.maxMp,
            ":mpPer": Role.mp/Role.maxMp,    // 0-1
            ":living": Role.living,          // true/false
            ":state": Role.state,            // RoleState
            ":combating": Role.combating,    // true/false
            ":free": Role.isFree,

            ":room": Room.name,
            ":path": Room.path,

            ":eq0": Role.getEqId(0),
            ":eq1": Role.getEqId(1),
            ":eq2": Role.getEqId(2),
            ":eq3": Role.getEqId(3),
            ":eq4": Role.getEqId(4),
            ":eq5": Role.getEqId(5),
            ":eq6": Role.getEqId(6),
            ":eq7": Role.getEqId(7),
            ":eq8": Role.getEqId(8),
            ":eq9": Role.getEqId(9),
            ":eq10": Role.getEqId(10),

            ":kf_quan": Role.kongfu.quan,
            ":kf_nei": Role.kongfu.nei,
            ":kf_zhao": Role.kongfu.zhao,
            ":kf_qing": Role.kongfu.qing,
            ":kf_jian": Role.kongfu.jian,
            ":kf_dao": Role.kongfu.dao,
            ":kf_gun": Role.kongfu.gun,
            ":kf_zhang": Role.kongfu.zhang,
            ":kf_bian": Role.kongfu.bian,
            ":kf_an": Role.kongfu.an
        };
    });

    VariableStore.register(_ => {
        return {
            ":cd ": function(sid) {
                return Role.coolingSkill(sid);
            },
            ":status ": function(param) {
                const parts = param.split(",");
                if (parts.length > 1) {
                    const status = parts[0];
                    const id = parts[1];
                    const item = Room.getItem(id);
                    if (item == null || item.status == null) return false;
                    for (const s of item.status) {
                        if (s.sid == status) return true;
                    }
                    return false;
                }
                return Role.hasStatus(param);
            },
            ":hp ": function(id) {
                const item = Room.getItem(id);
                if (item != null) return item.hp;
                return -1;
            },
            ":maxHp ": function(id) {
                const item = Room.getItem(id);
                if (item != null) return item.max_hp;
                return -1;
            },
            ":mp ": function(id) {
                const item = Room.getItem(id);
                if (item != null) return item.mp;
                return -1;
            },
            ":maxMp ": function(id) {
                const item = Room.getItem(id);
                if (item != null) return item.max_mp;
                return -1;
            },
            ":exist ": function(id) {
                if (id == null) return false;
                const item = Room.getItem(id);
                return item != null;
            }
        };
    });

    /***********************************************************************************\
        WSMUD Cmd Prehandler And Handler
    \***********************************************************************************/

    //---------------------------------------------------------------------------
    //  WSMUD Raid 占位符
    //---------------------------------------------------------------------------

    const FilterCenter = {
        filter: function(filterExp, obj) {
            if (filterExp == null) {
                return false;
            }
            const exp = filterExp.substring(1, filterExp.length - 2);
            const yes = eval(`${exp}`);
            return yes;
        }
    }

    function ReplacePlaceholder(exp) {
        var patt = /\{([a-z]?)([^a-z%#]+?|<\w+>[^a-z%#]+?<\/\w+>)([a-z]?)(%?)(#?)\}\??(#[^#{}]*#)?/g;
        var placeholders = [];
        var result = patt.exec(exp);
        while(result != null) {
            placeholders.push({
                text: result[0],
                location: result[1] == "" ? null : result[1],
                name: result[2],
                blurry: result[4] != "%",
                quality: result[3] == "" ? null : result[3],
                type: result[5] != "#" ? "id" : "amount",
                filterExp: result[6]
            });
            result = patt.exec(exp);
        }
        const getValue = function(p) {
            let locationOrder = [];
            if (p.location == null) {
                locationOrder = p.quality == null ? ["r", "b", "d"] : ["b", "d"];
            } else {
                locationOrder = [p.location];
            }
            for (const location of locationOrder) {
                let value = null;
                switch (location) {
                    case "r":
                    value = Room.getItemId(p.name, p.blurry, false, p.filterExp);
                    break;
                    case "b": {
                        let item = Role.findItem(p.name, p.blurry, p.quality, p.filterExp);
                        if (item) {
                            value = p.type == "id" ? item.id : item.count;
                        }
                        break;
                    }
                    case "d": {
                        let item = DialogList.findItem(p.name, p.blurry, p.quality, p.filterExp);
                        if (item) {
                            value = p.type == "id" ? item.id : item.count;
                        }
                        break;
                    }
                }
                if (value != null) return value;
            }
            return null;
        };
        let realExp = exp;
        for (const p of placeholders) {
            let value = getValue(p);
            realExp = realExp.replace(p.text, value);
        }
        return realExp;
    }

    (function() {
        const handle = function(performer, cmd) {
            return ReplacePlaceholder(cmd);
        };
        const handler = new CmdPrehandler(handle)
        CmdPrehandleCenter.shared().addHandler(handler);
    })();

    (function () {
        const handle = function(cmds) {
            var result = [];
            for (const cmd of cmds) {
                const header = /^\s*/.exec(cmd)[0];
                let patt = /(\{[^\}]+\})([^\?]|$)/g;
                let r = patt.exec(cmd);
                while(r != null) {
                    result.push(`${header}@until ${r[1]}? != null`);
                    r = patt.exec(cmd);
                }
                result.push(cmd);
            }
            return result;
        };
        const rule = new PrecompileRule(handle, PrecompileRulePriority.low);
        PrecompileRuleCenter.shared().addRule(rule);
    })();

    //---------------------------------------------------------------------------
    //  WSMUD Raid 命令
    //---------------------------------------------------------------------------

    (function() {
        const executor = new CmdExecutor(cmd => {
            return cmd.indexOf("<-stopSSAuto") == 0 || cmd.indexOf("@stopSSAuto") == 0;
        }, (performer, _) => {
            if (performer.log()) Message.cmdLog("暂停自动婚宴和自动Boss", "目前手动终止流程不会自动恢复");
            WG.stopAllAuto();
        })
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new CmdExecutor(cmd => {
            return cmd.indexOf("stopSSAuto->") == 0 || cmd.indexOf("@recoverSSAuto") == 0;
        }, (performer, _) => {
            if (performer.log()) Message.cmdLog("恢复自动婚宴和自动Boss设置");
            WG.reSetAllAuto();
        })
        CmdExecuteCenter.addExecutor(executor);
    })();

    var __RecordGainsFrom = null;
    (function() {
        const executor = new CmdExecutor(cmd => {
            return cmd.indexOf("<-recordGains") == 0;
        }, (performer, _) => {
            if (performer.log()) Message.cmdLog("开始记录物品获取");
            __RecordGainsFrom = new Date().getTime();
        })
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new CmdExecutor(cmd => {
            return cmd.indexOf("recordGains->") == 0;
        }, _ => {
            const gains = Role.gains(__RecordGainsFrom, new Date().getTime());
            var result = {};
            gains.forEach(gain => {
                var oldCount = 0;
                var old = result[gain.name];
                if (old) oldCount = old.count;
                result[gain.name] = {count: oldCount + gain.count, unit: gain.unit};
            });
            var content = "";
            Message.clean();
            Message.append("&nbsp;&nbsp;> 战利品列表如下:");
            for (const name in result) {
                if (!result.hasOwnProperty(name)) continue;
                const gain = result[name];
                Message.append("&nbsp;&nbsp;* " + name + " <wht>" + gain.count + gain.unit + "</wht>");
                content += `&nbsp;&nbsp;* ${name} <wht>${gain.count}${gain.unit}</wht><br>`;
            }
            layer.open({
                type: 1,
                area: ["380px", "300px"],
                title: "战利品列表",
                content: content,
                offset: 'auto',
                shift: 2
            });
        })
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("toolbar", function(performer, param) {
            performer.timeSeries(new Date().getTime());
            $(`span[command=${param}]`).click();
            return new Promise(resolve => {
                setTimeout(_ => {
                    $(".glyphicon-remove-circle").click();
                    resolve();
                }, 500);
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new UntilAtCmdExecutor("liaoshang", function(performer, param) {
            if (Role.hp/Role.maxHp >= 1) {
                WG.SendCmd("stopstate");
                return true;
            }
            if (Role.state != RoleState.liaoshang) {
                WG.SendCmd("stopstate;liaoshang");
            }
            return false;
        }, null, 1000);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new UntilAtCmdExecutor("dazuo", function(performer, param) {
            if (Role.mp/Role.maxMp > 0.99) {
                WG.SendCmd("stopstate");
                return true;
            }
            if (Role.state != RoleState.dazuo) {
                WG.SendCmd("stopstate;dazuo");
            }
            return false;
        }, null, 1000);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new UntilAtCmdExecutor("eq", function(performer, param) {
            const eqIds = param.split(",");
            let cmds = [];
            eqIds.forEach(eqId => {
                if (!Role.wearing(eqId)) cmds.push(`eq ${eqId}`);
            });
            if (cmds.length > 0) {
                WG.SendCmd("stopstate;" + cmds.join(";"));
                return false;
            }
            return true;
        }, null, 1000);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new UntilAtCmdExecutor("cd", function(performer, param) {
            if (param == null) {
                return !Role.hasCoolingSkill();
            }

            let validParam = param;
            let isBlack = false;
            if (validParam[0] == "^") {
                validParam = validParam.substring(1);
                isBlack = true;
            }
            const skills = validParam.split(",");
            if (isBlack) {
                for (const cooling of Role.coolingSkills()) {
                    if (skills.indexOf(cooling) == -1) {
                        return false;
                    }
                }
            } else {
                let coolings = Role.coolingSkills();
                for (const skill of skills) {
                    if (coolings.indexOf(skill) != -1) {
                        return false;
                    }
                }
            }
            return true;
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    class UntilSearchedAtCmdExecutor extends UntilAtCmdExecutor {
        constructor(key, search) {
            const assert = function(performer, param) {
                let placeholders = [];
                let patt = /\(\$[a-zA-Z0-9_]+?\)/g;
                let result = patt.exec(param);
                while(result != null) {
                    placeholders.push(result[0]);
                    result = patt.exec(param);
                }
                let regex = param;
                for (let i = 0; i < placeholders.length; i++) {
                    const placeholder = placeholders[i];
                    regex = regex.replace(placeholder, "(.+?)");
                }
                let result2 = search(regex, performer.timeSeries());
                if (result2 == null) {
                    return false;
                }
                for (let j = 0; j < placeholders.length; j++) {
                    const placeholder = placeholders[j];
                    let key = placeholder.substring(2, placeholder.length - 1);
                    let value = result2[j + 1];
                    if (value != null) {
                        UpdateVariable(performer, key, value);
                    }
                }
                return true;
            };
            super(key, assert);
        }
    }

    (function() {
        const executor = new UntilSearchedAtCmdExecutor("tip", (regex, from) => {
            return SystemTips.search(regex, from);
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new UntilSearchedAtCmdExecutor("task", (regex, from) => {
            return TaskList.search(regex, from);
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new UntilSearchedAtCmdExecutor("xy", (regex, from) => {
            return Xiangyang.search(regex, from);
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new UntilAtCmdExecutor("kill", function(performer, param) {
            const parts = param.split(",");
            let infos = [];
            for (let i = 0; i < parts.length; i++) {
                const name = parts[i];
                let blurry = true;
                if (name.substring(name.length - 1) == "%") {
                    name = name.substring(0, name.length - 1);
                    blurry = false;
                }
                infos.push({name: name, blurry: blurry});
            }
            const finish = Room.didKillItemsInRoom(infos);
            if (finish) {
                return true;
            } else {
                let cmd = "";
                infos.forEach(info => {
                    const itemId = Room.getItemId(info.name, info.blurry, true);
                    if (itemId != null) {
                        cmd += "kill " + itemId + ";";
                    }
                });
                WG.SendCmd(cmd);
                return false;
            }
        }, null, 1000, 1000);
        CmdExecuteCenter.addExecutor(executor);
    })();

    /* 等待,直到 dialog 打开,在打开 dialog 后调用,便于后续使用占位符 */
    (function() {
        const executor = new UntilAtCmdExecutor("dialog", function(performer, param) {
            return DialogList.timestamp > performer.timeSeries();
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    function UntilRoleFreePerformerPromise(callback, log) {
        return PerformerPromise("@until (:free) == true", callback, log);
    }

    (function() {
        const executor = new AtCmdExecutor("cleanBag", function(performer, param) {
            if (performer.log()) Message.cmdLog("清理包裹");
            return UntilRoleFreePerformerPromise(resolve => {
                WG.SendCmd("$cleanall");
                setTimeout(resolve, 1000);
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("tidyBag", function(performer, param) {
            if (performer.log()) Message.cmdLog("整理包裹");
            return UntilRoleFreePerformerPromise(resolve => {
                Role.tidyBag(_ => { setTimeout(resolve, 1000); });
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("shimen", function(performer, param) {
            if (performer.log()) Message.cmdLog("自动完成允许放弃的放弃师门");
            return UntilRoleFreePerformerPromise(resolve => {
                Role.shimen(_ => { setTimeout(resolve, 1000); });
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("renew", function(performer, param) {
            if (performer.log()) Message.cmdLog("恢复角色气血和内力");
            return UntilRoleFreePerformerPromise(resolve => {
                Role.renew(_ => { setTimeout(resolve, 1000); });
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    //---------------------------------------------------------------------------
    //  Skill State Machine
    //---------------------------------------------------------------------------

    var SkillStateMachine = {
        perform: function(skill, force) {
            const timestamp = new Date().getTime();
            this._perform(skill, force, timestamp);
        },
        _perform: function(skill, force, timestamp) {
            if (this._skillStack[skill] != null && this._skillStack[skill] > timestamp) return;
            const self = this;
            if ((!Role.isFree() && !force) || Role.coolingSkill(skill) || Role.rtime) {
                setTimeout(_ => {
                    self._perform(skill, force, timestamp);
                }, 200);
                return;
            }
            this._skillStack[skill] = timestamp;
            WG.SendCmd(`perform ${skill}`);
            const timer = setInterval(_ => {
                if (Role.coolingSkill(skill) || Role.combating == false) {
                    clearInterval(timer);
                    if (self._skillStack[skill] != null && self._skillStack[skill] == timestamp) {
                        delete self._skillStack[skill];
                    }
                    return;
                }
                if (!Role.isFree() || Role.rtime) return;
                WG.SendCmd(`perform ${skill}`);
            }, 1000);
        },
        reset: function() {
            this._skillStack = {};
        },
        _skillStack: {}
    }

    //---------------------------------------------------------------------------
    //  Send System Cmd
    //---------------------------------------------------------------------------

    var __systemCmdDelay = 1500;

    (function() {
        const executor = new AtCmdExecutor("cmdDelay", function(performer, param) {
            performer._cmdDelay = parseInt(param);
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    const UnpackSystemCmd = function(cmd) {
        let result = cmd;
        let patt = /([^;]+)\[(\d+?)\]/g;
        let r = patt.exec(cmd);
        while (r != null) {
            const packedCmd = r[1];
            const count = parseInt(r[2]);
            const temp = (new Array(count)).fill(packedCmd);
            const unpackedCmd = temp.join(";");
            result = result.replace(r[0], unpackedCmd);
            r = patt.exec(cmd);
        }
        return result;
    };

    (function() {
        const executor = new CmdExecutor(_ => {
            return true;
        }, (performer, cmd) => {
            let validCmd = CmdPrehandleCenter.shared().handle(performer, cmd);
            validCmd = UnpackSystemCmd(validCmd);
            return UntilRoleFreePerformerPromise(resolve => {
                const timestamp = new Date().getTime();
                let delay = 0;
                const fromReject = timestamp - SystemTips.rejectTimestamp;
                if (fromReject < 1500) {
                    console.log(fromReject);
                    delay = fromReject;
                }
                setTimeout(_ => {
                    if (performer.log()) Message.cmdLog("执行系统命令", validCmd);
                    performer.timeSeries(timestamp);
                    performer.systemCmdTimestamp = timestamp;
                    WG.SendCmd(validCmd);
                    const cmdDelay = performer._cmdDelay == null ? __systemCmdDelay : performer._cmdDelay;
                    setTimeout(resolve, cmdDelay);
                }, delay);
            });
        }, CmdExecutorPriority.low);
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("force", function(performer, param) {
            return new Promise(resolve => {
                if (performer.log()) Message.cmdLog("强行执行系统命令", param);
                WG.SendCmd(param);
                const cmdDelay = performer._cmdDelay == null ? __systemCmdDelay : performer._cmdDelay;
                setTimeout(resolve, cmdDelay);
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("perform", function(performer, param) {
            const skills = param.split(",");
            for (const skill of skills) {
                SkillStateMachine.perform(skill, false);
            }
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    //---------------------------------------------------------------------------
    //  Manage Trigger
    //---------------------------------------------------------------------------

    (function() {
        const executor = new AtCmdExecutor("on", function(performer, param) {
            unsafeWindow.TriggerCenter.activate(param);
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    (function() {
        const executor = new AtCmdExecutor("off", function(performer, param) {
            unsafeWindow.TriggerCenter.deactivate(param);
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    /***********************************************************************************\
        Dungeons
    \***********************************************************************************/

    const GetDungeonFlow = function(name) {
        for (const d of Dungeons) {
            if (d.name == name) {
                return d.source;
            }
        }
        return null;
    };

    // params: name subtype
    const AutoDungeonName = function(params) {
        const parts = params.split(' ');
        const name = parts[0];
        const type = parts[1];
        let totalName = '';
        switch (type) {
            case '0':
            if (GetDungeonFlow(name)) {
                return name;
            }
            totalName = `${name}(简单)`;
            if (GetDungeonFlow(totalName)) {
                return totalName;
            }
            break;
            case '1':
            totalName = `${name}(困难)`;
            if (GetDungeonFlow(totalName) != null) {
                return totalName;
            };
            break;
            case '2':
            totalName = `${name}(组队)`;
            if (GetDungeonFlow(totalName) != null) {
                return totalName;
            };
            break;
            default:
            break;
        }
        return null;
    };

    (function() {
        const executor = new AtCmdExecutor("fb", function(performer, param) {
            const name = AutoDungeonName(param);
            if (name == null) {
                Message.append('暂不支持次副本哦,欢迎到论坛分享此副本流程。');
            } else {
                const source = GetDungeonSource(name);
                return new Promise(resolve => {
                    const p = new Performer(`自动副本-${name}`, source);
                    p.log(true);
                    p.start(_ => {
                        resolve();
                    });
                });
            }
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    function GetDungeonSource(name) {
        let source = GetDungeonFlow(name);
        if (source == null) {
            return null;
        }
        const result = `
[if] (_DungeonHpThreshold) == null
    ($_DungeonHpThreshold) = 50
[if] (_DungeonWaitSkillCD) == null
    ($_DungeonWaitSkillCD) = 打开
[if] (_DungeonBagCleanWay) == null
    ($_DungeonBagCleanWay) = 存仓及售卖
#select ($_DungeonHpThreshold) = 副本内疗伤,当气血低于百分比,100|90|80|70|60|50|40|30|20|10,(_DungeonHpThreshold)
#select ($_DungeonWaitSkillCD) = Boss战前等待技能冷却,打开|关闭,(_DungeonWaitSkillCD)
#select ($_DungeonBagCleanWay) = 背包清理方案,不清理|售卖|存仓及售卖,(_DungeonBagCleanWay)
#input ($_repeat) = 重复次数,1
#config
[if] (arg0) != null
    ($_DungeonHpThreshold) = (arg0)
[if] (arg1) != null
    ($_DungeonWaitSkillCD) = (arg1)
[if] (arg2) != null
    ($_DungeonBagCleanWay) = (arg2)
[if] (arg3) != null
    ($_repeat) = (arg3)
<-stopSSAuto
stopstate
<---
[if] (_DungeonHpThreshold) == null
    ($_DungeonHpThreshold) = 50
($hpPer) = (_DungeonHpThreshold)/100
[if] (:hpPer) < (hpPer)
    @liaoshang
--->
<-recordGains
($_i) = 0
[if] (_repeat) == null
    ($_repeat) = 1
[while] (_i) < (_repeat)
    @renew
    [if] (_DungeonBagCleanWay) == 售卖
        @cleanBag
    [else if] (_DungeonBagCleanWay) == 存仓及售卖
        @tidyBag
${SourceCodeHelper.appendHeader("    ", source)}
    cr;cr over
    ($_i) = (_i) + 1
[if] (_DungeonBagCleanWay) == 售卖
    @cleanBag
[else if] (_DungeonBagCleanWay) == 存仓及售卖
    @tidyBag
$to 住房-练功房;dazuo
recordGains->
stopSSAuto->`
        return result;
    }

    const Dungeons = [
        {
            name: "华山论剑",
            source: `
@print 👑 感谢 koyodakla、freesunny 对此副本代码提供的帮助。
jh fb 30 start1;cr huashan/lunjian/leitaixia
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go up
@tip 恭喜你战胜了五绝
@wait 1000
jump bi
get all from {r五绝宝箱}`
        },
        {
            name: "光明顶(组队)",
            source: `
@print 👑 感谢 dtooboss 分享此副本代码。
jh fb 26 start3;cr mj/shanmen 2 0
go north;go west;go northwest
@kill 冷谦
go north
@kill 张中
go north
@kill 周颠
go north;go north
@kill 颜垣
go east
@kill 唐洋
go north
@kill 辛然
go west;go west
@kill 庄铮
go south
@kill 闻苍松
go east;go south
@kill 说不得,彭莹玉
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north[2]
@kill 韦一笑,殷天正
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north[2]
@kill 张无忌,杨逍,范遥`
        },
        {
            name: "光明顶",
            source: `
@print 👑 感谢 dtooboss 分享此副本代码。
jh fb 26 start1;cr mj/shanmen
go north;go west;go northwest
@kill 冷谦
go north
@kill 张中
go north
@kill 周颠
go north;go north
@kill 颜垣
go east
@kill 唐洋
go north
@kill 辛然
go west;go west
@kill 庄铮
go south
@kill 闻苍松
go east;go south
@kill 说不得,彭莹玉
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north[2]
@kill 韦一笑,殷天正
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north[2]
@kill 张无忌,杨逍,范遥`
        },
        {
            name: "燕子坞(困难)",
            source: `
jh fb 23 start2;cr murong/anbian 1 0
go east;go east
@kill 包不同
go east;go south;go east;go south;go south
@kill 王夫人
go north;go north;go west;go north
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go east;go east;go east
@kill 慕容复
go west;go north
look pai;bai pai[3]
go north;search
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go south
@kill 慕容博
go east
@kill 阿朱`
        },
        {
            name: "燕子坞(简单)",
            source: `
jh fb 23 start1;cr murong/anbian
go east;go east
@kill 包不同
go east;go south;go east;go south;go south
@kill 王夫人
go north;go north;go west;go north
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go east;go east;go east
@kill 慕容复
go west;go north
look pai;bai pai[3]
go north;search
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go south
@kill 慕容博
go east
@kill 阿朱`
        },
        {
            name: "燕子坞(偷书)",
            source: `
@print 👑 感谢 Airson 分享此副本代码。
jh fb 23 start1;cr murong/anbian
go east;go east
@kill 包不同
go east;go east;go east;go north
look pai;bai pai[3]
go north;search`
        },
        {
            name: "移花宫(困难)",
            source: `
jh fb 22 start2;cr huashan/yihua/shandao 1 0
go south[5]
go south[5]
go south[5]
@kill 花月奴
go south;go south
@kill 移花宫女弟子,移花宫女弟子
go south
@kill 移花宫女弟子,移花宫女弟子
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go southeast
@kill 涟星
[if] {r邀月}? != null
    @kill 邀月
[if] {邀月的尸体}? == null
    [if] (_DungeonWaitSkillCD) == 打开
        @cd
go northwest;go southwest
[if] {r邀月}? != null
    @kill 邀月
[if] {b火折子g}? != null
    look hua
    @tip 你数了下大概有($number)朵花
    go southeast
    look bed;pushstart bed
    pushleft bed[(number)]
    @await 1000
    pushright bed[8]
    @await 1000
    go down;fire;go west
    @kill 花无缺
    look xia;open xia`
        },
        {
            name: "移花宫(简单)",
            source: `
jh fb 22 start1;cr huashan/yihua/shandao
go south[5]
go south[5]
go south[5]
@kill 花月奴
go south;go south
@kill 移花宫女弟子,移花宫女弟子
go south
@kill 移花宫女弟子,移花宫女弟子
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go southeast
@kill 涟星
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go northwest;go southwest
@kill 邀月
[if] {b火折子g}? != null
    look hua
    @tip 你数了下大概有($number)朵花
    go southeast
    look bed;pushstart bed
    pushleft bed[(number)]
    @await 1000
    pushright bed[8]
    @await 1000
    go down;fire;go west
    @kill 花无缺
    look xia;open xia`
        },
        {
            name: "冰火岛(困难)",
            source: `
@print 👑 感谢 WanJiaQi 分享此副本代码。
jh fb 21 start2;cr mj/bhd/haibian 1 0
go west
@kill 炎龙
go west
@kill 炎龙
go west
@kill 炎龙王
@liaoshang
go west;search
@tip 你找到了
go east[4];go north
@kill 白熊
go north
@kill 白熊
@liaoshang
go north;go west;zuan dong
[if] (_DungeonWaitSkillCD) == 打开
    @cd
@kill 张翠山
@kill 谢逊`
        },
        {
            name: "冰火岛(简单)",
            source: `
@print 👑 感谢 WanJiaQi 分享此副本代码。
jh fb 21 start1;cr mj/bhd/haibian 0 0
go west
@kill 炎龙
go west
@kill 炎龙
go west
@kill 炎龙王
@liaoshang
go west;search
@tip 你找到了
go east[4];go north
@kill 白熊
go north
@kill 白熊
@liaoshang
go north;go west;zuan dong
[if] (_DungeonWaitSkillCD) == 打开
    @cd
@kill 谢逊`
        },
        {
            name: "星宿海",
            source: `
jh fb 20 start1;cr xingxiu/xxh6
go northeast
@kill 星宿派
go north
@kill 星宿派
go northwest
@kill 星宿派
go southwest
@kill 星宿派
go south
@kill 星宿派
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north;go northeast;go north
@kill 丁春秋`
        },
        {
            name: "白驼山(组队)",
            source: `
jh fb 19 start3;cr baituo/damen 2 0
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north[4]
@kill 欧阳锋
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go south
@kill 欧阳克,白衣少女
go south[2];go west[3]
@kill 毒蛇
go north
@kill 毒蛇
go north;go north
@kill 蟒蛇`
        },
        {
            name: "桃花岛(困难)",
            source: `
jh fb 18 start2;cr taohua/haitan 1 0
@until (:path) == taohua/haitan
@taohualin
@wait 1000
go south
@kill 陆乘风
go east;go east
@kill 曲灵风
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go east;go east
@kill 黄药师
go west;go north
@kill 黄蓉`
        },
        {
            name: "桃花岛(简单)",
            source: `
jh fb 18 start1;cr taohua/haitan
@until (:path) == taohua/haitan
@taohualin
@wait 1000
go south
@kill 陆乘风
go east;go east
@kill 曲灵风
go east;go north
ok {黄蓉}
@zhoubotong
@kill 周伯通
look xia;search xia
go east[2]
go northwest;go southeast;go southeast;go northwest
go southwest;go northeast;go northeast;go southwest
@until (:path) == taohua/haitan
@taohualin
@wait 2000
go south;go east
go east;go east;go north
select {黄蓉};give1 {黄蓉}
@kill 黄蓉`
        },
        {
            name: "云梦沼泽(组队)",
            source: `
@print 👑 感谢 leiyulin 分享此副本代码。
jh fb 17 start3;cr cd/yunmeng/senlin 2 0
$wait 500
go east
@kill 巨鳄
go north
@kill 巨鳄,巨鳄
go east
@kill 巨鳄,巨鳄
go west;go north
@kill 巨鳄,巨鳄
look lu;kan lu;go north
@kill 火龙
go north
@kill 火龙
go north
@kill 火龙
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 火龙王`
        },
        {
            name: "云梦沼泽",
            source: `
@print 👑 感谢 leiyulin 分享此副本代码。
jh fb 17 start1;cr cd/yunmeng/senlin
$wait 500
go east
@kill 巨鳄
go north
@kill 巨鳄,巨鳄
go east
@kill 巨鳄,巨鳄
go west;go north
@kill 巨鳄,巨鳄
look lu;kan lu;go north
@kill 火龙
go north
@kill 火龙
go north
@kill 火龙
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 火龙王`
        },
        {
            name: "嵩山",
            source: `
jh fb 16 start1;cr wuyue/songshan/taishi
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north[2]
@kill 十三太保
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go northup;go northeast;go northup[2]
@kill 十三太保,十三太保
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go northup;go north
@kill 十三太保,十三太保,十三太保
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 十三太保,十三太保,十三太保,十三太保
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 左冷禅`
        },
        {
            name: "衡山",
            source: `
jh fb 14 start1;cr wuyue/henshan/hengyang
go west;go north
@kill 嵩山弟子,嵩山弟子
go north;go north
@kill 费彬
@kill 史登达,丁勉
@kill 刘正风
go south[3];go west[2]
@kill 曲洋,曲非烟
go east[4];go southeast;go south;go east;go south
@kill 莫大`
        },
        {
            name: "青城山",
            source: `
@print 👑 感谢 矮大瓜 分享此副本代码。
jh fb 13 start1;cr wuyue/qingcheng/shanlu
go westup
@kill 青城派弟子,青城派弟子
go north
go northup
go eastup
@kill 青城派弟子,青城派弟子
go northup
@kill 洪人雄
go north[3]
@kill 于人豪
go north
@kill 侯人英,罗人杰
go south
go east
@kill 余人彦
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 余沧海`
        },
        {
            name: "恒山",
            source: `
@print 👑 感谢 .min-A 分享此副本代码。
jh fb 12 start1;cr wuyue/hengshan/daziling
go northup;go northwest;go northwest;go northup;go northup
@kill 不戒和尚,仪琳,哑婆婆
go north;go north
@kill 定静师太,定闲师太,定仪师太

($path)=(:path)
[while] true
    <---
    @until (path)!=(:path)
    ($path)=(:path)
    ($guy) = {r采花大盗 田伯光}?
    [if] (guy) != null
        @kill 采花大盗 田伯光
    [if] {田伯光的尸体}? != null
        [break]
    --->
    go south
    go west
    go north
    go south
    go east
    go east
    go north
    go south
    go west
    go south
    go southdown
    go east
    go southeast
    go northup
    go southdown
    go southeast
    go southdown

    go northup
    go northwest
    go northup
    go southdown
    go northwest
    go northup
    go northup
    go north
    go north`
        },
        {
            name: "五毒教(组队)",
            source: `
@print 👑 感谢 矮大瓜 分享此副本代码。
jh fb 11 start3;cr cd/wudu/damen 2 0
@kill 五毒教徒,五毒教徒,五毒教徒,五毒教徒
go east
@kill 沙千里
go south
@kill 藏獒
go west
@kill 白髯老者
go east
go south
@kill 毒郎中
go north
go north
[if](_DungeonWaitSkillCD) == 打开
    @cd
go east
@kill 潘秀达,岑其斯,齐云敖
[if](_DungeonWaitSkillCD) == 打开
    @cd
go east
@kill 何红药
[if](_DungeonWaitSkillCD) == 打开
    @cd
go east
@kill 何铁手`
        },
        {
            name: "五毒教",
            source: `
@print 👑 感谢 矮大瓜 分享此副本代码。
jh fb 11 start1;cr cd/wudu/damen
@kill 五毒教徒,五毒教徒,五毒教徒,五毒教徒
go east
@kill 沙千里
go south
@kill 藏獒
go west
@kill 白髯老者
go east
go south
@kill 毒郎中
go north
go north
[if](_DungeonWaitSkillCD) == 打开
    @cd
go east
@kill 潘秀达,岑其斯,齐云敖
[if](_DungeonWaitSkillCD) == 打开
    @cd
go east
@kill 何红药
[if](_DungeonWaitSkillCD) == 打开
    @cd
go east
@kill 何铁手`
        },
        {
            name: "温府",
            desc: "温府(2k+闪避)",
            source: `
@print 👑 感谢 JiaQi Wan 分享此副本代码。
jh fb 10 start1;cr cd/wen/damen
look tree;climb tree;go north;go northeast
[while] true
    [if] (:path) != cd/wen/zoulang4
        go northeast
    [else]
        [break]
go north[2];go northwest;go north
look zhuang;tiao zhuang
@kill 温方义,温方山,温方施,温方南
[if] {r温家老大 温方达%}? != null
    @kill 温方达
@wait 2000
[if] (_DungeonWaitSkillCD) == 打开
    @cd
look zhuang;tiao zhuang
@until (:path) == cd/wen/xiaoyuan
@wait 500
[if] {r夏雪宜}? != null
    @kill 夏雪宜
go north
@kill 温仪`
        },
        {
            name: "关外",
            source: `
@print 👑 感谢 老实人 分享此副本代码。
jh fb 9 start1;cr bj/guanwai/damen
go northeast
@kill 金雕
go east
@kill 金雕
go southeast
@kill 金雕
go east
@kill 平四
go north
select {r胡斐}
ask {r胡斐} about 阎基
@tip 胡斐说道:阎基是我的杀父仇人($chat)
[if] (chat) == ,
        give {r胡斐} {b阎基的头颅}
        ask {r胡斐} about 胡家刀谱
[if] (_DungeonWaitSkillCD) == 打开
    @cd
@kill 胡斐
go south;go east
@kill 东北虎
go eastup
@kill 东北虎
go southup
@kill 东北虎
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go eastup
@kill 黑熊
go westdown;go northdown;go west[2];go northwest
go west;go southwest;go west
give {r船夫} 10000 money
@until (:room)==关外-船厂(副本区域)
@wait 500
@kill 船夫
go south;go west[5];go north
@kill 江湖医生 阎基`
        },
        {
            name: "神龙教(组队)",
            source: `
jh fb 8 start3;cr bj/shenlong/haitan 2 0;go north
@kill 毒蛇,竹叶青
look bush;kan bush;go north
@kill 毒蛇,竹叶青
go north
@kill 神龙教弟子,神龙教弟子
go north
@kill 神龙教军师 陆高轩
go south;go east
@kill 神龙教青龙使 许雪亭
go east
@kill 神龙教女弟子,神龙教女弟子
go north[2]
@kill 神龙教弟子,神龙教弟子
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 洪安通,张淡月,无根道长`
        },
        {
            name: "神龙教",
            source: `
jh fb 8 start1;cr bj/shenlong/haitan;go north
@kill 毒蛇,竹叶青
look bush;kan bush;go north
@kill 毒蛇,竹叶青
go north
@kill 神龙教弟子,神龙教弟子
go north
@kill 神龙教军师 陆高轩
go south;go east
@kill 神龙教青龙使 许雪亭
go east
@kill 神龙教女弟子,神龙教女弟子
go north[2]
@kill 神龙教弟子,神龙教弟子
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 洪安通,张淡月,无根道长`
        },
        {
            name: "天地会",
            source: `
@print 👑 感谢 jicki 分享此副本代码,感谢 andyfos、mma1996 协助完成。
jh fb 7 start1;cr bj/tdh/hct
@kill 药铺伙计
@kill 天地会青木堂护法 徐天川
go west
@kill 关夫子 关安基
knock;knock;knock
go down
@until {r尸体}? != null
go west[5]
@until {r尸体}? != null
go north
@liaoshang
[if] (_DungeonWaitSkillCD) == 打开
    @cd
@kill 陈近南
go east
get {r一}?
@wait 500
get {r一}?
go west
go north
go east
@tip 拔刀相助,贫尼感激不尽。
@wait 1000
select {r神尼};cha {r神尼}
@wait 1000
@kill 独臂神尼`
        },
        {
            name: "鳌拜府",
            source: `
@print 👑 感谢 Jeaepan 分享此副本代码。
jh fb 6 start1;cr bj/ao/damen
@kill 官兵,官兵
go west
@kill 吴之荣
go north
@kill 厨师
go south;go west
@kill 家将,家将,女管家
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go west
@kill 满洲第一勇士 鳌拜
go east;go north
look shu;open shu
@tip 发现扉页的($pos)下角被鳌拜写了一个大大的杀字
look hua
$wait 500
[if] (pos) == 左
    tleft hua
[if] (pos) == 右
    $wait 500
    tright hua
go north
select {r四十二章经g}?
get {r四十二章经g}?
go south;go south
($open) = 没开
look men;unlock men
@tip 你用一把钥匙($open)了牢房门|你不会撬锁
[if] (open) == 打开
    go south
    select {r庄允城}?
    ask {r庄允城}? about 吴之荣
    @kill 庄允城`
        },
        {
            name: "庄府",
            source: `
@print 👑 感谢 qwer68588 分享此副本代码。
jh fb 5 start1;cr bj/zhuang/xiaolu
go north
@kill 土匪
go north
look men;break men
go north
@kill 神龙教弟子,神龙教弟子
go north
@kill 神龙教弟子
@kill 神龙教小头目 章老三
go west
@kill 神龙教弟子
go east;go east
@kill 神龙教弟子`
        },
        {
            name: "兵营",
            source: `
@print 👑 感谢 qwer68588 分享此副本代码。
jh fb 4 start1;cr yz/by/damen
@kill 官兵,官兵
$wait 1000
@liaoshang
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go south
@kill 官兵,官兵,官兵,武将,武将,史青山
$wait 1000
look men;open men
go south;search`
        },
        {
            name: "流氓巷(组队)",
            source: `
jh fb 2 start3;cr yz/lmw/xiangzi1 2 0
@kill 小流氓,小流氓
go east
@kill 流氓,流氓
go north
@kill 流氓头,流氓,流氓
go south;go east
@kill 流氓,流氓
go east
@kill 流氓头领`
        },
        {
            name: "流氓巷",
            source: `
jh fb 2 start1;cr yz/lmw/xiangzi1
@kill 小流氓,小流氓
go east
@kill 流氓,流氓
go north
@kill 流氓头,流氓,流氓
go south;go east
@kill 流氓,流氓
go east
@kill 流氓头领`
        },
        {
            name: "丽春院",
            source: `
jh fb 3 start1;cr yz/lcy/dating
@kill 韦春芳
go up
@kill 龟公
go west
@kill 史松
look tai;tui tai;go enter
@kill 茅十八`
        },
        {
            name: "财主家(困难)",
            source: `
jh fb 1 start2;cr yz/cuifu/caizhu 1 0
@kill 大狼狗,大狼狗
go north
@kill 管家,家丁,家丁
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 财主 崔员外
($open) = 没开
look men;open men
@tip 你不会撬锁|钥匙($open)了秘门
[if] (open) == 打开
    go east
    ok {丫鬟}
    go west;go south;go south
    go north;go north;go west
    select {财主女儿 崔莺莺};ask {财主女儿 崔莺莺} about 东厢
[else]
    go west
@kill 财主女儿 崔莺莺
[if] (open) == 打开
    go east;go east;look gui;search gui`
        },
        {
            name: "财主家(简单)",
            source: `
jh fb 1 start1;cr yz/cuifu/caizhu
@kill 大狼狗,大狼狗
go north
@kill 管家,家丁,家丁
[if] (_DungeonWaitSkillCD) == 打开
    @cd
go north
@kill 财主 崔员外
($open) = 没开
look men;open men
@tip 你不会撬锁|钥匙($open)了秘门
[if] (open) == 打开
    go east
    ok {丫鬟}
    go west;go south;go south
    go north;go north;go west
    select {财主女儿 崔莺莺};ask {财主女儿 崔莺莺} about 东厢
[else]
    go west
@kill 财主女儿 崔莺莺
[if] (open) == 打开
    go east;go east;look gui;search gui` }
    ];

    /***********************************************************************************\
        Server
    \***********************************************************************************/

    const Server = {
        uploadConfig: function() {
            let all = {};
            let keys = GM_listValues();
            keys.forEach(key => {
                all[key] = GM_getValue(key);
            });
            if (unsafeWindow.TriggerConfig != null) {
                const tConfig = unsafeWindow.TriggerConfig.get();
                all["@@@trigger"] = tConfig;
            }
            let value = JSON.stringify(all);
            Server._sync("uploadConfig", {id: Role.id, value: value}, _ => {
                alert("wsmud_Raid 配置上传成功,该角色配置会在服务器保存 24 小时。");
            }, _ => {
                alert("wsmud_Raid 配置上传失败!");
            });
        },
        downloadConfig: function() {
            Server._sync("downloadConfig", {id: Role.id}, data => {
                let config = JSON.parse(data);
                for (const key in config) {
                    if (key == "@@@trigger") {
                        if (unsafeWindow.TriggerConfig != null) {
                            unsafeWindow.TriggerConfig.set(config[key]);
                        }
                        continue;
                    }
                    GM_setValue(key, config[key]);
                }
                alert("wsmud_Raid 配置下载成功!");
            }, _ => {
                alert("wsmud_Raid 配置下载失败!");
            });
        },
        uploadFlows: function() {
            const flows = FlowStore.getAll();
            const map = WorkflowConfig._rootList();
            const data = {map: map, flows: flows};
            const value = JSON.stringify(data);
            Server._sync("uploadFlows", {id: Role.id, value: value}, pass => {
                GM_setClipboard(pass);
                alert(`角色流程上传成功,该角色流程会在服务器保存 24 小时。\n角色流程获取码:${pass},已复制到系统剪切板。`);
            }, _ => {
                alert("角色流程上传失败!");
            });
        },
        downloadFlows: function(pass) {
            Server._sync("downloadFlows", {pass: pass}, value => {
                let data = JSON.parse(value);
                FlowStore.corver(data.flows);
                WorkflowConfig._rootList(data.map);
                console.log(data);
                alert("拷贝角色流程成功!");
            }, _ => {
                alert("错误的角色流程获取码!");
            });
        },
        uploadTriggers: function() {
            const triggers = unsafeWindow.TriggerCenter.getAllData();
            const value = JSON.stringify(triggers);
            Server._sync("uploadTriggers", {id: Role.id, value: value}, pass => {
                GM_setClipboard(pass);
                alert(`角色触发器上传成功,该角色流程会在服务器保存 24 小时。\n角色触发器获取码:${pass},已复制到系统剪切板。`);
            }, _ => {
                alert("角色触发器上传失败!");
            });
        },
        downloadTriggers: function(pass) {
            Server._sync("downloadTriggers", {pass: pass}, value => {
                let data = JSON.parse(value);
                unsafeWindow.TriggerCenter.corver(data);
                console.log(data);
                alert("拷贝角色触发器成功!");
            }, _ => {
                alert("错误的角色触发器获取码!");
            });
        },
        getNotice: function() {
            const noticeDataKey = "NoticeDataKey";
            const oldData = GM_getValue(noticeDataKey, {version: "0.0.0", type: "0", value: "欢迎使用 wsmud_Raid"});
            Server._async("notice", {version: oldData.version}, data => {
                let validData = oldData;
                if (data.version > oldData.version) {
                    GM_setValue(noticeDataKey, data);
                    validData = data;
                }
                if (validData.type == "0") {
                    L.msg(`
                    <div>
                    <p><hig>Raid:</hig>${validData.value}</p>
                    <p style="text-align:center">(v-${GM_info.script.version})</p>
                    </div>`);
                } else {
                    const HideVersionNoticeKey = "HideVersionNoticeKey";
                    if (GM_getValue(HideVersionNoticeKey, null) == validData.version) {
                        return;
                    }
                    layer.open({
                        type: 1,
                        skin: "layui-layer-rim", //加上边框
                        area: ["380px"],
                        title: `wsmud_Raid 提示`,
                        content: validData.value,
                        offset:"auto",
                        shift: 2,
                        move: false,
                        closeBtn: 0,
                        btn: ['确认', '不再显示'],
                        yes: function(index) {
                            layer.close(index);
                        },
                        btn2: function() {
                            GM_setValue(HideVersionNoticeKey, validData.version);
                        }
                    });
                }
            });
        },

        shareFlowTrigger: function (username, password, type, data) {
            let value = data;
            value["author"] = username;
            const params = {
                username: username,
                password: password,
                name: data.name,
                type: type,
                value: JSON.stringify(value)
            };
            console.log(params);
            Server._sync("uploadSingle", params, token => {
                GM_setClipboard(token);
                alert(`${type}分享成功,该${type}会在服务器保存 30 天\n每次下载会延长保存 始于下载时刻的 30 天\n分享码:${token}\n已复制到系统剪切板。`);
            }, error => {
                alert(error);
            });
        },
        importFlow: function (token, target) {
            if (token.indexOf("·流程") == -1) {
                alert("错误的流程分享码!");
                return;
            }
            const params = { token: token };
            Server._sync("downloadSingle", params, data => {
                const flow = JSON.parse(data);
                const result = WorkflowConfig.createWorkflow(flow.name, flow.source, target);
                if (result == true) {
                    alert(`导入流程 ${flow.name} 成功!`);
                } else {
                    alert(result);
                }
            }, _ => {
                alert("错误的流程分享码!");
            });
        },
        importTrigger: function (token) {
            if (token.indexOf("·触发") == -1) {
                alert("错误的触发器分享码!");
                return;
            }
            const params = { token: token };
            Server._sync("downloadSingle", params, data => {
                const trigger = JSON.parse(data);
                const result = unsafeWindow.TriggerCenter.create(trigger.name, trigger.event, trigger.conditions, trigger.source, trigger.active);
                if (result == true) {
                    alert(`导入触发器 ${trigger.name} 成功!`);
                } else {
                    alert(result);
                }
            }, _ => {
                alert("错误的触发器分享码!");
            });
        },

        _address: "wsmud.bobcn.me:8080/wsmud",
        _async(uri, params, success, fail) {
            this._get(true, uri, params, success, fail);
        },
        _sync(uri, params, success, fail) {
            this._get(false, uri, params, success, fail);
        },
        _get(async, uri, params, success, fail) {
            $.ajax({
                type: "post",
                url: `http://${Server._address}/${uri}`,
                data: params,
                async: async,
                success: function(data) {
                    if (data.code == 200) {
                        if (success != null) success(data.data);
                    } else {
                        let error = data.code;
                        if (data.data != null) error = data.data;
                        if (fail != null) fail(error);
                    }
                },
                dataType: "json"
            });
        }
    };

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

    //---------------------------------------------------------------------------
    //  兼容 1.x.x
    //---------------------------------------------------------------------------

    var CmdGroupManager = {
        /**
         * @returns {{ id: number, name: string }[]}
         */
        getAll: function() {
            var result = [];
            GM_listValues().map(function (key) {
                if (key.indexOf(CmdGroupManager._prefix) == 0) {
                    var id = CmdGroupManager._id(key);
                    var name = CmdGroupManager.getName(id);
                    result.push({ id: id, name: name });
                }
            });
            return result;
        },
        getName: function(id) {
            var value = GM_getValue(this._key(id));
            if (value == null) return null;
            var obj = JSON.parse(value);
            return obj.name;
        },
        getCmdsText: function(id) {
            var value = GM_getValue(this._key(id));
            if (value == null) return "";
            var obj = JSON.parse(value);
            var cmdsStr = obj.cmdsStr;
            return cmdsStr;
        },
        /**
         * @returns {string[]}
         */
        getCmds: function(id) {
            var text = this.getCmdsText(id);
            var cmds = text.split(/^\s*|\s*\n+\s*/g);
            var first = cmds[0];
            if (first != null && first.length == 0) {
                cmds.splice(0, 1);
            }
            var last = cmds[cmds.length - 1];
            if (last != null && last.length == 0) {
                cmds.splice(cmds.length - 1, 1);
            }
            return cmds;
        },
        createCmdGroup: function(name, cmdsStr) {
            var id = new Date().getTime();
            return this.updateCmdGroup(id, name, cmdsStr);
        },
        updateCmdGroup: function(id, name, cmdsStr) {
            if (name == null || !/\S+/g.test(name)) {
                alert("命令组想要一个名字...");
                return false;
            }
            if (cmdsStr == null || !/\S+/g.test(cmdsStr)) {
                alert("命令组不想没有任何内容...");
                return false;
            }
            // 存储格式
            var value = {
                name: name,
                cmdsStr: cmdsStr
            };
            GM_setValue(this._key(id), JSON.stringify(value));
            return true;
        },
        removeCmdGroup: function(id) {
            GM_deleteValue(this._key(id));
        },

        _prefix: "@cmdgroup",
        _key: function(id) {
            return this._prefix + id;
        },
        _id: function(key) {
            return parseInt(key.substring(this._prefix.length));
        }
    };

    var WorkflowConfigManager = {
        /**
         * @returns {{ id: number, name: string }[]}
         */
        getAll: function() {
            var result = [];
            GM_listValues().map(function (key) {
                if (WorkflowConfigManager._isMyKey(key)) {
                    var id = WorkflowConfigManager._id(key);
                    var name = WorkflowConfigManager.getName(id);
                    result.push({ id: id, name: name });
                }
            });
            return result;
        },
        getName: function(id) {
            var value = GM_getValue(this._key(id));
            if (value == null) return null;
            var obj = JSON.parse(value);
            return obj.name;
        },
        /**
         * @returns {{ id: number, repeat: number }[]}
         */
        getCmdGroupInfos: function(id) {
            var value = GM_getValue(this._key(id));
            if (value == null) return null;
            var obj = JSON.parse(value);
            return obj.infos;
        },
        /**
         * @returns {Workflow}
         */
        getWorkflow: function(id) {
            var cmdGroupInfos = this.getCmdGroupInfos(id);
            var items = [];
            for (const info of cmdGroupInfos) {
                var name = CmdGroupManager.getName(info.id);
                var cmds = CmdGroupManager.getCmds(info.id);
                var commandWorkflow = new CommandWorkflow(name, cmds, info.repeat);
                items.push(commandWorkflow);
            }
            var workflow = new Workflow(this.getName(id), items, 1);
            return workflow;
        },
        /**
         * @param {string} name
         * @param {{ id: string, repeat: number }[]} cmdGroupInfos
         */
        createWorkflowConfig: function(name, cmdGroupInfos) {
            var id = new Date().getTime();
            return this.updateWorkflowConfig(id, name, cmdGroupInfos);
        },
        /**
         * @param {number} id
         * @param {string} name
         * @param {{ id: string, repeat: number }[]} cmdGroupInfos
         */
        updateWorkflowConfig: function(id, name, cmdGroupInfos) {
            if (name == null || !/\S+/g.test(name)) {
                alert("工作流想要一个名字...");
                return false;
            }
            if (cmdGroupInfos == null || cmdGroupInfos.length <= 0) {
                alert("工作流不想没有任何内容...");
                return false;
            }
            // 存储格式
            var value = {
                name: name,
                infos: cmdGroupInfos
            };
            GM_setValue(this._key(id), JSON.stringify(value));
            return true;
        },
        removeWorkflowConfig: function(id) {
            GM_deleteValue(this._key(id));
        },

        _prefix: "workflow@",
        _isMyKey: function(key) {
            return key.indexOf(this._prefix + Role.id) == 0;
        },
        _key: function(id) {
            return this._prefix + Role.id + id;
        },
        _id: function(key) {
            return parseInt(key.substring((this._prefix + Role.id).length));
        }
    };

    const CodeTranslator = {
        run: function() {
            const oldFinder1 = this._getFinder("原命令组");
            if (oldFinder1) {
                WorkflowConfig.removeFinder(oldFinder1);
            }
            WorkflowConfig.createFinder("原命令组");
            const oldFinder2 = this._getFinder("原工作流程");
            if (oldFinder2) {
                WorkflowConfig.removeFinder(oldFinder2);
            }
            WorkflowConfig.createFinder("原工作流程");

            let allCmdGroup = CmdGroupManager.getAll();
            let allWorkflow = WorkflowConfigManager.getAll();
            const result = this._newSingleName(allCmdGroup, allWorkflow);
            allCmdGroup = result.group;
            allWorkflow = result.flow;

            allCmdGroup.forEach(g => {
                const cmdsText = CmdGroupManager.getCmdsText(g.id);
                const header = "    ";
                const cmdsTextHasHeader = this._appendHeader(header, cmdsText);
                const source = `($_i) = 0\n[while] (_i) < (arg0)\n${cmdsTextHasHeader}\n${header}($_i) = (_i) + 1`;
                WorkflowConfig.createWorkflow(g.name, source, "原命令组");
            });

            allWorkflow.forEach(f => {
                const infos = WorkflowConfigManager.getCmdGroupInfos(f.id);
                let source = "";
                infos.forEach(info => {
                    let cmdGroupName = null;
                    for (const cmdGroup of allCmdGroup) {
                        if (cmdGroup.id == info.id) {
                            cmdGroupName = cmdGroup.name;
                            break;
                        }
                    }
                    source += `@call ${cmdGroupName} ${info.repeat}\n`;
                });
                WorkflowConfig.createWorkflow(f.name, source, "原工作流程");
            });
        },
        _newSingleName: function(cmdGroups, workflows) {
            let allCmdGroup = this._singleName(cmdGroups);
            let allWorkflow = this._singleName(workflows);
            allCmdGroup.forEach(cmdGroup => {
                const name = cmdGroup.name;
                for (const flow of allWorkflow) {
                    if (flow.name == name) {
                        cmdGroup.name = `芫${name}`;
                        break;
                    }
                }
            });
            return {group: allCmdGroup, flow: allWorkflow};
        },
        _singleName: function(list) {
            for (const item of list) {
                item.name = item.name.replace(/[^_a-zA-Z0-9\u4e00-\u9fa5]/g, "");
            }
            for (let i = 0; i < list.length; i++) {
                const item = list[i];
                let name = item.name;
                let number = 1;
                for (let j = i + 1; j < list.length; j++) {
                    const item1 = list[j];
                    if (item1.name == name) {
                        item1.name = `${name}_${number}`;
                        number += 1;
                    }
                }
            }
            return list;
        },
        _getFinder: function(name) {
            let list = WorkflowConfig._rootList();
            const index = WorkflowConfig._findFinder(name, list);
            if (index == null) return null;
            return list[index];
        },
        _appendHeader: function(header, text) {
            let result = `\n${text}`;
            result = result.replace(/(\n)/g, `$1${header}`);
            result = result.replace(/\n\s*\n/g, "\n");
            result = result.replace(/^\s*\n/, "");
            console.log(result);
            return result;
        }
    };

    //---------------------------------------------------------------------------
    //  2.1.x UI
    //---------------------------------------------------------------------------

    var WorkflowConfig = {
        rootFinderName: "根文件夹",
        rootFinderSortWay: function(value) {
            const key = "__WorkflowRootFinderSortWay";
            if (value == null) {
                return GM_getValue(key, "nameAsc");
            }
            GM_setValue(key, value);
        },
        finderList: function(finderName) {
            let result = [];
            if (finderName == this.rootFinderName) {
                result = this._rootList();
            } else {
                const list = this._rootList();
                const index = this._findFinder(finderName, list);
                if (index != null) {
                    const finder = list[index];
                    result = finder.flows;
                }
            }
            result.forEach(item => {
                if (item.type == "flow") {
                    item.finder = finderName;
                }
            });
            switch (this.rootFinderSortWay()) {
                case "updateDesc":
                result.reverse();
                break;
                case "nameAsc":
                result.sort(function(a, b) {
                    return a.name.localeCompare(b.name);
                });
                break;
                case "nameDesc":
                result.sort(function(a, b) {
                    return b.name.localeCompare(a.name);
                });
                break;
                case "updateAsc":
                default:
                break;
            }
            return result;
        },
        createFinder: function(name, flows) {
            const result = this._checkName(null, name, true);
            if (result != true) return result;

            let list = this._rootList();
            const finder = {name: name, type: "finder", flows: flows ? flows : []};
            list.push(finder);
            this._rootList(list);
            return true;
        },
        modifyFinder: function(finder, newName) {
            const result = this._checkName(finder.name, newName, true);
            if (result != true) return result;

            if (finder.name == newName) return true;

            this.removeFinder(finder);
            return this.createFinder(newName, finder.flows);
        },
        removeFinder: function(finder) {
            let list = this._rootList();
            const index = this._findFinder(finder.name, list);
            if (index == null) return;

            list.splice(index, 1);
            this._rootList(list);

            for (const flow of finder.flows) {
                FlowStore.remove(flow.name);
            }
        },
        createWorkflow: function(name, source, finderName) {
            const result = this._checkName(null, name, false);
            if (result != true) return result;

            const flow = {name: name, type: "flow"};
            let list = this._rootList();
            let success = false;
            if (finderName == this.rootFinderName) {
                list.push(flow);
                success = true;
            } else {
                const index = this._findFinder(finderName, list);
                if (index != null) {
                    const finder = list[index];
                    finder.flows.push(flow);
                    success = true;
                }
            }
            if (success) {
                FlowStore.save(name, source);
                this._rootList(list);
                return true;
            } else {
                return `未找到名为"${finderName}"的文件夹。`;
            }
        },
        modifyWorkflow: function(flow, newName, newSource, newFinderName) {
            const result = this._checkName(flow.name, newName, false);
            if (result != true) return result;

            if (flow.name != newName || flow.finder != newFinderName) {
                this.removeWorkflow(flow);
                return this.createWorkflow(newName, newSource, newFinderName);
            } else if (FlowStore.get(flow.name) != newSource) {
                FlowStore.save(flow.name, newSource);
            }
            return true;
        },
        removeWorkflow: function(flow) {
            let list = this._rootList();
            if (flow.finder == this.rootFinderName) {
                for (let i = 0; i < list.length; i++) {
                    const item = list[i];
                    if (item.type == "flow" && item.name == flow.name) {
                        list.splice(i, 1);
                        break;
                    }
                }
            } else {
                const index = this._findFinder(flow.finder, list);
                if (index != null) {
                    const finder = list[index];
                    const flows = finder.flows;
                    for (let k = 0; k < flows.length; k++) {
                        const flow1 = flows[k];
                        if (flow1.name == flow.name) {
                            flows.splice(k, 1);
                            break;
                        }
                    }
                }
            }
            this._rootList(list);

            FlowStore.remove(flow.name);
        },
        getFinderNames: function() {
            let result = [this.rootFinderName];
            let list = this._rootList();
            list.forEach(item => {
                if (item.type == "finder") {
                    result.push(item.name);
                }
            });
            return result;
        },
        _rootList: function(list) {
            const key = `WorkflowConfig_${Role.id}`;
            if (list != null) {
                GM_setValue(key, list);
            }
            return GM_getValue(key, []);
        },
        _checkName: function(oldName, name, isFinder) {
            if (name == oldName) return true;
            const itemName = isFinder ? "文件夹" : "工作流程";
            if (!/\S+/.test(name)) return `${itemName}的名称不能为空。`;
            if (name.indexOf(this.rootFinderName) != -1) return `${itemName}的名称中不能包含"${this.rootFinderName}"。`;
            if (!/^[_a-zA-Z0-9\u4e00-\u9fa5]+$/.test(name)) return `${itemName}的名称只能使用中文、英文和数字字符。`;
            let list = this._rootList();
            const type = isFinder ? "finder" : "flow";
            for (const item of list) {
                if (item.type == type && item.name == name) {
                    return `已经存在此名称的${itemName}。`;
                }
                if (item.type == "finder" && !isFinder) {
                    for (const flow of item.flows) {
                        if (flow.name == name) {
                            return `已经存在此名称的${itemName}。`;
                        }
                    }
                }
            }
            return true;
        },
        _findFinder: function(name, list) {
            for (let i = 0; i < list.length; i++) {
                const item = list[i];
                if (item.type == "finder" && item.name == name) {
                    return i;
                }
            }
            return null;
        }
    };

    var ManagedPerformerCenter = {
        start: function(name, source, log, callback) {
            const p = new Performer(name, source);
            p.log(log != null ? log : true);
            const key = `key${this._counter}`;
            this._counter += 1;
            this._performers[key] = p;
            p.start(_ => {
                delete ManagedPerformerCenter._performers[key];
                if (ManagedPerformerCenter.getAll().length == 0) {
                    $("#workflows-button").css("border-color", "inherit");
                }
                if (callback) callback();
            });
            $("#workflows-button").css("border-color", "#00FF00");
        },
        getAll: function() {
            return Object.values(this._performers);
        },
        _counter: 0,
        _performers: {}
    };

    const UI = {
        showToolbar: function() {
            if (!UI._toolbarHidden) return;
            UI._toolbarHidden = false;
            var raidToolbar = `
            <style>
                .raid-item{
                    display: inline-block;
                    border: solid 1px gray;
                    color: gray;
                    background-color: black;
                    text-align: center;
                    cursor: pointer;
                    border-radius: 0.25em;
                    //min-width: 2.5em;
                    margin-right: 0em;
                    //margin-left: 0.4em;
                    position: relative;
                    padding-left: 0.3em;
                    padding-right: 0.3em;
                    line-height: 28px;
                }
            </style>
            <div id="raidToolbar">
                <div class="raidToolbar" style="width:calc(100% - 40px);margin:5px 0 5px 0">
                    <span class="raid-item hideRaidToolbar" style="width:10px">\<</span>
                    <span class="raid-item forum">🍥 <hiy>链接</hiy></span>
                    <span class="raid-item shortcut">🍯 <hiz>捷径</hiz></span>
                    <span class="raid-item trigger">🍟 <hio>触发</hio></span>
                    <span class="raid-item customFlow" id="workflows-button">🥗 <hig>流程</hig></span>
                    <span class="raid-item moreRaid">🍺 <hic>副本</hic></span>
                    <!--<span class="raid-item hideRaidToolbar" style="float:right;"><wht>测试</wht></span>-->
                </div>
            </div>`
            $(".WG_log").before(raidToolbar);
            $(".customFlow").on('click', UI.workflows);
            $(".trigger").on('click', UI.trigger);
            $(".forum").on('click', UI.forum);
            $(".shortcut").on('click', UI.shortcut);
            $(".moreRaid").on('click', UI.dungeons);
            $(".hideRaidToolbar").on('click', UI.hideToolbar);
        },
        hideToolbar: function() {
            var toolbar = document.getElementById("raidToolbar");
            if (toolbar != null) {
                toolbar.parentNode.removeChild(toolbar);
                L.msg("单击右键,选择流程菜单可恢复显示。");
            }
            UI._toolbarHidden = true;
        },

        trigger: function() {
            if (unsafeWindow.TriggerUI == null) {
                const content = `
                <span class = "zdy-item install-trigger" style="width:120px"> 前往安装 </span>
                `;
                UI._appendHtml("🍟 <hio>触发器</hio>", content);
                $(".install-trigger").on('click', function () {
                    window.open("https://greasyfork.org/zh-CN/scripts/378984", '_blank').location;
                });
            } else {
                unsafeWindow.TriggerUI.triggerHome();
            }
        },
        forum: function() {
            var content = `
            <span class = "zdy-item about-something" style="width:120px"> 综合讨论 </span>
            <span class = "zdy-item about-flow" style="width:120px"> <wht>流程讨论</wht> </span>
            <span class = "zdy-item about-trigger" style="width:120px"> <wht>触发器讨论</wht> </span>
            <span class = "zdy-item about-bug" style="width:120px"> <wht>Bug 提交</wht> </span>
            <br><br>
            <hr style="background-color: gray; height: 1px; width: calc(100% - 4em); border: none;"><br>
            <span class = "zdy-item suqingHome" style="width:120px"> 🍿 <hig>苏</hig><hio>轻</hio><hiy>工</hiy><wht>具</wht><hic>包</hic> </span>`;
            UI._appendHtml("🍱 <hiy>江湖客栈</hiy>", content);

            $(".about-something").on('click', function () {
                window.open("http://wsmud.bobcn.me/category/1", '_blank').location;
            });
            $(".about-flow").on('click', function () {
                window.open("http://wsmud.bobcn.me/category/2", '_blank').location;
            });
            $(".about-trigger").on('click', function () {
                window.open("http://wsmud.bobcn.me/category/9", '_blank').location;
            });
            $(".about-bug").on('click', function () {
                window.open("http://wsmud.bobcn.me/category/4", '_blank').location;
            });
            $(".suqingHome").on('click', function () {
                window.open("https://emeisuqing.github.io/wsmud/", '_blank').location;
            });
        },
        shortcut: function() {
            var content = `
            <span class = "zdy-item outMaze" style="width:120px"> 走出桃花林 </span>
            <span class = "zdy-item zhoubotong" style="width:120px"> 找到周伯通 </span>
            <span class = "zdy-item uploadConfig" style="width:120px"> 上传本地配置 </span>
            <span class = "zdy-item downloadConfig" style="width:120px"> 下载云端配置 </span>
            <span class = "zdy-item uploadFlows" style="width:120px"> 分享角色流程 </span>
            <span class = "zdy-item downloadFlows" style="width:120px"> 拷贝角色流程 </span>
            <span class = "zdy-item uploadTriggers" style="width:120px"> 分享角色触发 </span>
            <span class = "zdy-item downloadTriggers" style="width:120px"> 拷贝角色触发 </span>
            <span class = "zdy-item importFlow" style="width:120px"> 导入流程 </span>
            <span class = "zdy-item importTrigger" style="width:120px"> 导入触发器 </span>
            <!--<span class = "zdy-item translateCode" style="width:120px"> 流程转换修复 </span>-->
            <span class = "zdy-item raidVersion" style="width:120px"> 🏹 ${GM_info.script.version} </span>`
            UI._appendHtml("🍯 <hiz>捷径</hiz>", content);

            $(".outMaze").on('click', function () {
                WG.SendCmd('stopstate');
                THIsland.outMaze();
            });
            $(".zhoubotong").on('click', function () {
                WG.SendCmd('stopstate');
                THIsland.zhoubotong();
            });
            $(".uploadConfig").on('click', _ => {
                Server.uploadConfig();
            });
            $(".downloadConfig").on('click', _ => {
                Server.downloadConfig();
            });
            $(".uploadFlows").on('click', _ => {
                Server.uploadFlows();
            });
            $(".downloadFlows").on('click', _ => {
                layer.confirm('拷贝成功将会完全覆盖原有角色流程!', {
                    title: "<red>! 警告</red>",
                    btn: ['那还是算了','好的继续'],
                    shift: 2,
                }, function(index){
                    layer.close(index);
                }, function(){
                    layer.prompt({ title: '输入角色流程获取码', formType: 1, shift: 2 }, function(pass, index){
                        layer.close(index);
                        Server.downloadFlows(pass);
                    });
                });
            });
            $(".uploadTriggers").on('click', _ => {
                Server.uploadTriggers();
            });
            $(".downloadTriggers").on('click', _ => {
                layer.confirm('拷贝成功将会完全覆盖原有角色触发器!', {
                    title: "<red>! 警告</red>",
                    btn: ['那还是算了','好的继续'],
                    shift: 2,
                }, function(index){
                    layer.close(index);
                }, function(){
                    layer.prompt({ title: '输入角色流程获取码', formType: 1, shift: 2 }, function(pass, index){
                        layer.close(index);
                        Server.downloadTriggers(pass);
                    });
                });
            });

            $(".importFlow").on('click', _ => {
                let allFinder = WorkflowConfig.getFinderNames().join("|");
                let source = `
                #input ($token)=分享码,
                #select ($target)=目标文件夹,${allFinder},${WorkflowConfig.rootFinderName}
                #config
                @js Server.importFlow("(token)", "(target)");
                `
                const p = new Performer("导入流程", source);
                p.log(false);
                p.start();
            });

            $(".importTrigger").on('click', _ => {
                let source = `
                #input ($token)=分享码,
                #config
                @js Server.importTrigger("(token)");
                `
                const p = new Performer("导入触发器", source);
                p.log(false);
                p.start();
            });

            $(".translateCode").on('click', _ => {
                layer.prompt({ title: '客栈->流程讨论,阅读使用说明后操作', formType: 0, shift: 2 }, function(pass, index){
                    if (pass == "我确认开始转换") {
                        layer.close(index);
                        CodeTranslator.run();
                    }
                });
            });

            $(".raidVersion").on('click', _ => {
                Server.getNotice();
            });
        },
        dungeons: function() {
            UI._appendHtml("🍺 <hic>自动副本</hic>", "");
            const model = UI._dungeonsContentModel();
            UI._mountableDiv().appendChild(model.$el);
        },

        workflows: function() {
            if (ManagedPerformerCenter.getAll().length == 0) {
                UI.workflowsHome();
            } else {
                UI.runningFlows();
            }
        },
        workflowsHome: function() {
            // const leftText = `
            // <select style='width:80px' id="workflows-sort">
            //     <option value="updateAsc">更新时间升序</option>
            //     <option value="updateDesc">更新时间降序</option>
            //     <option value="nameAsc">名称升序</option>
            //     <option value="nameDesc">名称降序</option>
            // </select>
            // `
            const leftText = `<wht>运行中</wht>`;
            const rightText = `
            <select style='width:80px' id="workflows-opts">
                <option value="none">选择操作</option>
                <option value="createFinder">新建文件夹</option>
                <option value="createFlow">新建流程</option>
            </select>`
            // const getMoreFlows = function() {
            //     window.open("http://wsmud.bobcn.me:4567/category/2", '_blank').location;
            // };
            UI._appendHtml("🥗 <hig>工作流程</hig>", "", rightText, null, leftText, UI.runningFlows);
            $('#workflows-opts').val("none");
            $("#workflows-opts").change(function () {
                switch($('#workflows-opts').val()) {
                    case "createFinder":
                    UI.createFinder();
                    break;
                    case "createFlow":
                    UI.createWorkflow(WorkflowConfig.rootFinderName);
                    break;
                    case "none":
                    default:
                    break;
                };
            });
            // $('#workflows-sort').val(WorkflowConfig.rootFinderSortWay());
            // $("#workflows-sort").change(function () {
            //     WorkflowConfig.rootFinderSortWay($('#workflows-sort').val());
            //     UI.workflows();
            // });
            const model = UI._workflowContentModel(WorkflowConfig.finderList(WorkflowConfig.rootFinderName));
            UI._mountableDiv().appendChild(model.$el);
        },
        runningFlows: function() {
            UI._appendHtml("🥗 <hig>运行中流程</hig>", "", null, null, UI._backTitle, UI.workflowsHome);
            const model = UI._runningFlowsContentModel();
            UI._mountableDiv().appendChild(model.$el);
        },
        createFinder: function() {
            const content = `
            <div style="margin: 0 2em 5px 2em;text-align:center;width:calc(100% - 4em)">
                <label for="create-finder-name"> 名称:</label><input id ="create-finder-name" style='width:120px' type="text"  name="create-finder-name" value="">
            </div>`;
            const save = function() {
                const name = $("#create-finder-name").val();
                const result = WorkflowConfig.createFinder(name);
                if (result == true) {
                    UI.workflowsHome();
                } else {
                    alert(result);
                }
            };
            UI._appendHtml("🥗 <hig>新建文件夹</hig>", content, "<wht>保存</wht>", save, UI._backTitle, UI.workflowsHome);
        },
        modifyFinder: function(finder) {
            const content = `
            <div style="margin: 0 2em 5px 2em;text-align:center;width:calc(100% - 4em)">
                <label for="modify-finder-name"> 名称:</label><input id ="modify-finder-name" style='width:120px' type="text"  name="modify-finder-name" value="">
            </div>`;
            const remove = function() {
                var verify = confirm("删除文件夹将删除其中的所有流程,确认删除吗?");
                if (verify) {
                    WorkflowConfig.removeFinder(finder);
                    UI.workflowsHome();
                }
            };
            const back = function() {
                const name = $("#modify-finder-name").val();
                const result = WorkflowConfig.modifyFinder(finder, name);
                if (result != true) {
                    alert(result);
                    return;
                }
                UI.workflowsHome();
            };
            UI._appendHtml("🥗 <hig>修改文件夹</hig>", content, "删除", remove, UI._backSaveTitle, back);
            $('#modify-finder-name').val(finder.name);
        },
        openFinder: function(finderName) {
            if (finderName == WorkflowConfig.rootFinderName) {
                UI.workflowsHome();
                return;
            }
            const list = WorkflowConfig.finderList(finderName);
            UI._appendHtml(`<wht>📂 ${finderName}</wht>`, "", null, null, UI._backTitle, UI.workflowsHome);
            const model = UI._workflowContentModel(list);
            UI._mountableDiv().appendChild(model.$el);
        },
        createWorkflow: function(finderName) {
            const content = `
            <div style="margin: 0 2em 5px 2em;text-align:left;width:calc(100% - 4em)">
                <label for="create-flow-name"> 名称:</label><input id ="create-flow-name" style='width:120px' type="text"  name="create-flow-name" value="">
            </div>
            <textarea class = "settingbox hide" style = "height:5rem;display:inline-block;font-size:0.8em;width:calc(100% - 4em)" id = "create-flow-source"></textarea>`;
            const save = function() {
                const name = $("#create-flow-name").val();
                const source = $("#create-flow-source").val();
                const result = WorkflowConfig.createWorkflow(name, source, finderName);
                if (result == true) {
                    UI.workflowsHome();
                } else {
                    alert(result);
                }
            };
            UI._appendHtml("🥗 <hig>新建流程</hig>", content, "<wht>保存</wht>", save, UI._backTitle, UI.workflowsHome);
        },
        modifyWorkflow: function(flow) {
            let options = "";
            WorkflowConfig.getFinderNames().forEach(finderName => {
                options += `<option value="${finderName}">${finderName}</option>`;
            });
            const content = `
            <div style="margin: 0 2em 5px 2em;text-align:left;width:calc(100% - 4em)">
                <label for="modify-flow-name"> 名称:</label><input id ="modify-flow-name" style='width:120px' type="text"  name="modify-flow-name" value="">
                <label for="modify-flow-finder">移动至</label><select id="modify-flow-finder">
                    ${options}
                </select>
            </div>
            <textarea class = "settingbox hide" style = "height:5rem;display:inline-block;font-size:0.8em;width:calc(100% - 4em)" id = "modify-flow-source"></textarea>
            <span class="raid-item shareFlow">分享此流程</span>`;
            const remove = function() {
                var verify = confirm("确认删除此工作流程吗?");
                if (verify) {
                    WorkflowConfig.removeWorkflow(flow);
                    UI.workflowsHome();
                }
            };
            const back = function() {
                const name = $("#modify-flow-name").val();
                const source = $("#modify-flow-source").val();
                const finderName = $("#modify-flow-finder").val();
                const result = WorkflowConfig.modifyWorkflow(flow, name, source, finderName);
                if (result != true) {
                    alert(result);
                    return;
                }
                UI.openFinder(finderName);
            };
            UI._appendHtml("🥗 <hig>修改流程</hig>", content, "删除", remove, UI._backSaveTitle, back);
            $("#modify-flow-name").val(flow.name);
            $("#modify-flow-source").val(FlowStore.get(flow.name));
            $("#modify-flow-finder").val(flow.finder);
            $(".shareFlow").on('click', function () {
                const data = {
                    name: $("#modify-flow-name").val(),
                    source: $("#modify-flow-source").val()
                };
                UI._share("流程", data);
            });
        },

        _toolbarHidden: true,
        _backTitle: "<wht>< 返回</wht>",
        _backSaveTitle: "<wht>< 保存&返回</wht>",

        _appendHtml(title, content, rightText, rightAction, leftText, leftAction) {
            var realLeftText = leftText == null ? "" : leftText;
            var realRightText = rightText == null ? "" : rightText;
            var html = `
            <div class = "item-commands" style="text-align:center">
                <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);
            $("#wsmud_raid_left").on('click', function () {
                if (leftAction) leftAction();
            });
            $("#wsmud_raid_right").on('click', function () {
                if (rightAction) rightAction();
            });
        },
        _mountableDiv: function() {
            var wg_log = document.getElementsByClassName("WG_log")[0];
            var pre = wg_log.getElementsByTagName("pre")[0];
            var div = pre.getElementsByTagName("div")[0];
            return div;
        },
        _workflowContentModel: function(items) {
            const contentModel = new Vue({
                el: '#WorkflowsContentModel',
                methods: {
                    createSpan: function(createElement, item) {
                        let style = {
                            width: "120px",
                            "background-color": "#12e4a0",
                            border: "solid 1px rgb(107, 255, 70)",
                            color: "#000dd4"
                        };
                        if (item.type == "finder") {
                            style = {
                                width: "120px",
                                "background-color": "#0359c3",
                                border: "solid 1px rgb(107, 203, 255)",
                                color: "white"
                            };
                        }
                        var properties = {
                            attrs: { class: "zdy-item" },
                            style: style
                        };
                        var play = function() {
                            if (item.type == "finder") {
                                UI.openFinder(item.name);
                            } else {
                                ManagedPerformerCenter.start(item.name, FlowStore.get(item.name));
                            }
                        };
                        var edit = function() {
                            if (item.type == "finder") {
                                UI.modifyFinder(item);
                            } else {
                                UI.modifyWorkflow(item);
                            }
                        };
                        const leftProperties = {
                            style: {
                                width: "30px",
                                float: "left",
                                "background-color": "#ffffff4f",
                                "border-radius": "4px"
                            },
                            on: { click: edit }
                        };
                        var leftNode = createElement("div", leftProperties, "⚙");
                        var mainProperties = {
                            attrs: { class: "breakText" },
                            style: { width: "85px", float: "right"},
                            on: { click: play }
                        };
                        const title = item.type == "finder" ? item.name : `▶️${item.name}`;
                        const mainNode = createElement("div", mainProperties, title);
                        return createElement("span", properties, [leftNode, mainNode]);
                    },
                },
                render: function(createElement) {
                    var self = this;
                    let flows = [];
                    let finders = [];
                    items.forEach(item => {
                        if (item.type == "finder") finders.push(self.createSpan(createElement, item));
                        if (item.type == "flow") flows.push(self.createSpan(createElement, item));
                    });
                    let nodes = [];
                    if (flows.length > 0) nodes.push(flows);
                    if (finders.length > 0) {
                        nodes.push(createElement("hr", { style: { "background-color": "gray", height: "1px", width: "calc(100% - 4em)", border: "none" } }));
                        nodes.push(finders);
                    }
                    const style = createElement("style", ".breakText {word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}");
                    nodes.push(style);
                    return createElement(
                        "div",
                        { attrs: { class: "item-commands" } },
                        nodes
                    );
                }
            });
            return contentModel;
        },
        _dungeonsContentModel: function() {
            const contentModel = new Vue({
                el: '#DungeonsContentModel',
                methods: {
                    getItems: function() {
                        return Dungeons;
                    },
                    createSpan: function(createElement, item) {
                        var properties = {
                            attrs: { class: "zdy-item" },
                            style: { width: "120px" },
                            on: { click: function() {
                                ManagedPerformerCenter.start(`自动副本-${item.name}`, GetDungeonSource(item.name));
                            }},
                        };
                        return createElement('span', properties, item.desc != null ? item.desc : item.name);
                    },
                },
                render: function(createElement) {
                    var items = this.getItems();
                    var theSelf = this;
                    var spans = items.map(function(item) {
                        return theSelf.createSpan(createElement, item);
                    });
                    return createElement(
                        "div",
                        { attrs: { class: "item-commands" } },
                        spans
                    );
                }
            });
            return contentModel;
        },
        _runningFlowsContentModel: function() {
            const contentModel = new Vue({
                el: '#WorkflowsContentModel',
                methods: {
                    createSpan: function(createElement, flow) {
                        let style = {
                            width: "120px",
                            "background-color": "#05b77d",
                            border: "solid 1px rgb(107, 255, 70)",
                            color: "white"
                        };
                        var properties = {
                            attrs: { class: "zdy-item" },
                            style: style
                        };
                        var stop = function() {
                            flow.stop();
                        };
                        var pause = function() {
                            if (flow.pausing()) {
                                flow.resume();
                            } else {
                                flow.pause();
                            }
                            UI.runningFlows();
                            if (flow.pausing()) {
                                Message.append(`<hiy>暂停执行,流程: ${flow.name()}...</hiy>`);
                            } else {
                                Message.append(`<hiy>恢复执行,流程: ${flow.name()}。</hiy>`);
                            }
                        };
                        const leftProperties = {
                            style: {
                                width: "30px",
                                float: "left",
                                "background-color": "#ffffff4f",
                                "border-radius": "4px"
                            },
                            on: { click: pause }
                        };
                        var leftNode = createElement("div", leftProperties, flow.pausing() ? "▶️" : "⏸");
                        var mainProperties = {
                            attrs: { class: "breakText" },
                            style: { width: "85px", float: "right"},
                            on: { click: stop }
                        };
                        const mainNode = createElement("div", mainProperties, `⏹${flow.name()}`);
                        return createElement("span", properties, [leftNode, mainNode]);
                    },
                },
                render: function(createElement) {
                    var items = ManagedPerformerCenter.getAll();
                    var theSelf = this;
                    var spans = items.map(function(item) {
                        return theSelf.createSpan(createElement, item);
                    });
                    const style = createElement("style", ".breakText {word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}");
                    spans.push(style);
                    return createElement(
                        "div",
                        { attrs: { class: "item-commands" } },
                        spans
                    );
                }
            });
            return contentModel;
        },

        _shareData: null,
        /**
         * @param {String} type 流程  触发
         * @param {Object} value
         */
        _share: function(type, value) {
            UI._shareData = value;
            let source = `
            [if] (__FormUserName) == null
                (__FormUserName) = 论坛账号
            #input ($__FormUserName)=论坛账号,(__FormUserName)
            #input ($password)=论坛密码,
            #config
            @js Server.shareFlowTrigger("(__FormUserName)", "(password)", "${type}", UI._shareData);
            `
            const p = new Performer(`分享${type}`, source);
            p.log(false);
            p.start();
        }
    }

    /***********************************************************************************\
        TaoHua Island
    \***********************************************************************************/

    // 暂时保留给桃花岛解密用
    class AncientCmdExecuter {
        constructor(cmds, willStartExecute, didFinishExecute, willPerformCmd, didPerformCmd, interval) {
            this.cmds = cmds;
            this.willStartExecute = willStartExecute;
            this.didFinishExecute = didFinishExecute;
            this.willPerformCmd = willPerformCmd;
            this.didPerformCmd = didPerformCmd;
            this.interval = interval ? interval : 1000;
        }
        execute() {
            if (this.isWorking) return;
            this.isWorking = true;
            if (this.willStartExecute) this.willStartExecute();
            this._performCmd(0);
        }
        _performCmd(index) {
            if (index >= this.cmds.length) { this._finishExecute(); return; }
            if (!Role.isFree()) { this._delayPerformCmd(index); return; }
            var cmd = this.cmds[index];
            if (this.willPerformCmd) {
                var lastCmd = null;
                if (index > 0) lastCmd = this.cmds[index - 1];
                var valid = this.willPerformCmd(lastCmd, cmd);
                if (!valid) { this._delayPerformCmd(index); return; }
                cmd = valid;
            }
            // @开头,虚命令,不真正执行
            if (cmd.indexOf("@") == -1 && cmd.indexOf("kill?") == -1) WG.SendCmd(cmd);
            if (this.didPerformCmd) this.didPerformCmd(cmd);
            // [exit] 保留命令,立即退出执行器
            if (cmd.indexOf("[exit]") != -1) {
                this._finishExecute();
                return;
            }
            this._delayPerformCmd(index + 1);
        }
        _delayPerformCmd(index) {
            var executer = this;
            window.setTimeout(function () {
                executer._performCmd(index);
            }, executer.interval);
        }
        _finishExecute() {
            this.isWorking = false;
            WG.remove_hook(AncientCmdExecuter._hookIndex);
            if (this.didFinishExecute) this.didFinishExecute();
        }
    }

    const THIsland = {
        outMaze: function(callback) {
            if (!Role.atPath("taohua/haitan")) {
                Message.append("只有在 桃花岛的海滩 才能使用此虫洞。");
                return;
            }

            var cmds = [
                "go south",
                "@look 1",
                "@look 5"
            ];
            var willStartExecute = function() {
                THIsland._monitorMaze();
            };
            var didFinishExecute = function() {
                THIsland._cancelMonitorMaze();
                if (callback) callback();
            };
            var willPerformCmd = function(lastCmd, cmd) {
                if (cmd == "@look 1") {
                    if (THIsland._goCenterCmd) {
                       return THIsland._goCenterCmd;
                    } else {
                        return null;
                    }
                }
                if (cmd == "@look 5") {
                    if (THIsland._decodedMaze) {
                        return THIsland._outMazeCmd();
                    } else {
                        return null;
                    }
                }
                return cmd;
            };
            var executer = new AncientCmdExecuter(
                cmds,
                willStartExecute,
                didFinishExecute,
                willPerformCmd,
                undefined,
                1000
            );
            executer.execute();
        },
        zhoubotong: function(callback) {
            if (!Role.atPath("taohua/wofang")) {
                Message.append("只有在 蓉儿的卧室 才能使用此虫洞。");
                return;
            }

            var cmds = [
                "go south;go west;go west;go west;go north;go north;go north",
                "go west;go east;go west;go east;go west",
                "go south",
                "@look 1",
                "@look 5",
                "@go 2",
                "@go 3",
                "@go 4",
                "@go 6",
                "@go 7",
                "@go 8",
                "@end"
            ];
            var willStartExecute = function() {
                THIsland._monitorMaze();
                THIsland._exitsHookIndex = WG.add_hook("exits", function(data) {
                    if (THIsland._lastCoord == undefined || THIsland._lastCoord == [0, 0]) return;
                    if (Object.keys(data.items).length != 4) return;
                    for(var key in data.items) {
                        if (data.items[key] != "桃花林") return;
                    }
                    var normalExistMap = [
                        [["north", "northeast", "east"], ["east", "north", "south"], ["east", "south", "southeast"],],
                        [["east", "north", "west"], [], ["west", "east", "south"],],
                        [["west", "northwest", "north"], ["west", "south", "north"], ["west", "southwest", "south"],]
                    ];
                    var x = THIsland._lastCoord[0] + 1;
                    var y = THIsland._lastCoord[1] + 1;
                    var normalExists = normalExistMap[x][y];
                    for(var key2 in data.items) {
                        if (normalExists.indexOf(key2) != -1) continue;
                        THIsland._goCave = "go " + key2;
                        return;
                    }
                });
            };
            var didFinishExecute = function() {
                THIsland._lastCoord = undefined;
                THIsland._lastGo = undefined;
                THIsland._goCave = undefined;
                THIsland._cancelMonitorMaze();
                WG.remove_hook(THIsland._exitsHookIndex);
                if (callback) callback();
            };
            var willPerformCmd = function(lastCmd, cmd) {
                if (THIsland._goCave) return THIsland._goCave + ";go west;[exit]";

                var number = 0;
                switch (cmd) {
                case "@look 1":
                    if (THIsland._goCenterCmd) {
                       return THIsland._goCenterCmd;
                    } else {
                        return null;
                    }
                    break;
                case "@look 5":
                    if (!THIsland._decodedMaze) return null;
                    break;
                case "@go 2":
                    THIsland._lastCoord = THIsland._mazeCoords[2];
                    THIsland._lastGo = THIsland._mazePath(THIsland._lastCoord);
                    return THIsland._lastGo;
                case "@go 3": number = 3; break;
                case "@go 4": number = 4; break;
                case "@go 6": number = 6; break;
                case "@go 7": number = 7; break;
                case "@go 8": number = 8; break;
                }
                if (number != 0) {
                    var back = THIsland._mazeBackPath(THIsland._lastGo);
                    THIsland._lastCoord = THIsland._mazeCoords[number];
                    THIsland._lastGo = THIsland._mazePath(THIsland._lastCoord);
                    return back + ";" + THIsland._lastGo;
                }
                return cmd;
            };
            var executer = new AncientCmdExecuter(
                cmds,
                willStartExecute,
                didFinishExecute,
                willPerformCmd,
                undefined,
                1000
            );
            executer.execute();
        },

        _outMazeCmd: function() {
            var cmd = "";
            for (var i = 2; i <= 9; i++) {
                var coord = THIsland._mazeCoords[i];
                var go = THIsland._mazePath(coord);
                if (i == 9) {
                    cmd += go + ";" + go;
                } else {
                    cmd += go + ";" + THIsland._mazeBackPath(go) + ";";
                }
            }
            cmd += ";go south";
            return cmd;
        },
        _mazePath: function(coord) {
            var pathMap = [
                ["go southwest", "go west", "go northwest"],
                ["go south", "", "go north"],
                ["go southeast", "go east", "go northeast"]
            ];
            var x = coord[0] + 1;
            var y = coord[1] + 1;
            return pathMap[x][y];
        },
        _mazeBackPath: function(path) {
            var backMap = {
                "": "",
                "go southwest": "go northeast",
                "go west": "go east",
                "go northwest": "go southeast",
                "go south": "go north",
                "go north": "go south",
                "go southeast": "go northwest",
                "go east": "go west",
                "go northeast": "go southwest"
            };
            return backMap[path];
        },
        _monitorMaze: function() {
            THIsland._mazeCoords = [
                [2, 2], // unused
                [2, 2],
                [2, 2],
                [2, 2],
                [2, 2],
                [0, 0],
                [2, 2],
                [2, 2],
                [2, 2],
                [2, 2]
            ];
            THIsland._atFirst = false;
            THIsland._goCenterCmd = undefined;
            THIsland._decodedMaze = false;

            var index1 = WG.add_hook(["room", "exits"], function(data) {
                if (THIsland._goCenterCmd != undefined) return;

                if (data.type == "room") {
                    if (data.desc == undefined) return;
                    var patt = new RegExp("四周栽了大概有一棵桃树");
                    var result = patt.exec(data.desc);
                    if (result) THIsland._atFirst = true;
                } else if (data.type == "exits") {
                    if (data.items == undefined) return;
                    if (THIsland._atFirst) {
                        if (data.items.north && data.items.south) {
                            if (data.items.west) {
                                THIsland._mazeCoords[1] = [1, 0];
                                THIsland._goCenterCmd = "go west";
                            } else {
                                THIsland._mazeCoords[1] = [-1, 0];
                                THIsland._goCenterCmd = "go east";
                            }
                        } else if (data.items.west && data.items.east) {
                            if (data.items.north) {
                                THIsland._mazeCoords[1] = [0, -1];
                                THIsland._goCenterCmd = "go north";
                            } else {
                                THIsland._mazeCoords[1] = [0, 1];
                                THIsland._goCenterCmd = "go south";
                            }
                        }
                    }
                }
            });
            var index2 = WG.add_hook("room", function(data) {
                if (THIsland._decodedMaze) return;

                if (data.desc == undefined) return;
                var patt = new RegExp("能看到东南方向大概有.(?=棵桃树)");
                var count = patt.exec(data.desc);
                if (!count) return;
                var text = count.toString();
                switch (text.substring(text.length - 1)) {
                    case "二": THIsland._mazeCoords[2] = [1, -1]; break;
                    case "四": THIsland._mazeCoords[4] = [1, -1]; break;
                    case "六": THIsland._mazeCoords[6] = [1, -1]; break;
                    case "八": THIsland._mazeCoords[8] = [1, -1]; break;
                }

                THIsland._mazeCoords[9] = [-THIsland._mazeCoords[1][0], -THIsland._mazeCoords[1][1]];
                while (true) {
                    if (THIsland._mazeCoords[2][0] != 2) {
                        THIsland._mazeCoords[8] = [-THIsland._mazeCoords[2][0], -THIsland._mazeCoords[2][1]];
                    }
                    if (THIsland._mazeCoords[8][0] != 2) {
                        if (THIsland._mazeCoords[8][0] == THIsland._mazeCoords[1][0]) {
                            THIsland._mazeCoords[6] = [THIsland._mazeCoords[8][0], -THIsland._mazeCoords[8][1]];
                        } else {
                            THIsland._mazeCoords[6] = [-THIsland._mazeCoords[8][0], THIsland._mazeCoords[8][1]];
                        }
                    }
                    if (THIsland._mazeCoords[6][0] != 2) {
                        THIsland._mazeCoords[4] = [-THIsland._mazeCoords[6][0], -THIsland._mazeCoords[6][1]];
                    }
                    if (THIsland._mazeCoords[4][0] != 2) {
                        if (THIsland._mazeCoords[4][0] == THIsland._mazeCoords[9][0]) {
                            THIsland._mazeCoords[2] = [THIsland._mazeCoords[4][0], -THIsland._mazeCoords[4][1]];
                        } else {
                            THIsland._mazeCoords[2] = [-THIsland._mazeCoords[4][0], THIsland._mazeCoords[4][1]];
                        }
                    }
                    if (THIsland._mazeCoords[2][0] != 2 &&
                        THIsland._mazeCoords[4][0] != 2 &&
                        THIsland._mazeCoords[6][0] != 2 &&
                        THIsland._mazeCoords[8][0] != 2) {
                        break;
                    }
                }
                if (THIsland._mazeCoords[8][0] == THIsland._mazeCoords[4][0]) {
                    THIsland._mazeCoords[3] = [THIsland._mazeCoords[8][0], 0];
                } else {
                    THIsland._mazeCoords[3] = [0, THIsland._mazeCoords[8][1]];
                }
                THIsland._mazeCoords[7] = [-THIsland._mazeCoords[3][0], -THIsland._mazeCoords[3][1]];

                THIsland._decodedMaze = true;
            });
            THIsland._mazeHookIndexes = [index1, index2];
        },
        _cancelMonitorMaze: function() {
            for (var i = THIsland._mazeHookIndexes.length - 1; i >= 0; i--) {
                var index = THIsland._mazeHookIndexes[i];
                WG.remove_hook(index);
            }
        },
    };

    //---------------------------------------------------------------------------

    /* @taohualin 走出桃花林 */
    (function() {
        const executor = new AtCmdExecutor("taohualin", function(performer, param) {
            return new Promise(resolve => {
                THIsland.outMaze(resolve);
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

    /* @zhoubotong 找到周伯通 */
    (function() {
        const executor = new AtCmdExecutor("zhoubotong", function(performer, param) {
            return new Promise(resolve => {
                THIsland.zhoubotong(resolve);
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

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

    const ToRaid = {
        menu :UI.showToolbar,

        perform: function(content, name, log) {
            const realName = name ? name : "第三方调用";
            ManagedPerformerCenter.start(realName, content, log);
        },

        existAutoDungeon: function(params) {
            return AutoDungeonName(params) != null;
        },

        shareTrigger: function(triggerData) {
            UI._share("触发", triggerData);
        }
    };

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

    function __init__(){
        WG = unsafeWindow.WG;
        if(WG == undefined || WG == null){
            setTimeout(()=>{__init__()}, 300);
            return;
        }
        messageAppend = unsafeWindow.messageAppend;
        messageClear = unsafeWindow.messageClear;
        T = unsafeWindow.T;
        L = unsafeWindow.L;

        unsafeWindow.ToRaid = ToRaid;
        unsafeWindow.Role = Role;

        Role.init();
        Room.init();
        SystemTips.init();
        DialogList.init();
        TaskList.init();
        Xiangyang.init();
    }
})();