Greasy Fork is available in English.

wsmud_Raid

武神传说 MUD

目前为 2019-03-12 提交的版本。查看 最新版本

// ==UserScript==
// @name            wsmud_Raid
// @namespace       cqv
// @version         2.3.21
// @date            23/12/2018
// @modified        15/02/2019
// @homepage        https://greasyfork.org/zh-CN/scripts/375851
// @description     武神传说 MUD
// @author          Bob.cn
// @match           http://game.wsmud.com/*
// @match           http://www.wsmud.com/*
// @run-at          document-end
// @require         https://cdn.staticfile.org/vue/2.2.2/vue.min.js
// @grant           unsafeWindow
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_deleteValue
// @grant           GM_listValues
// @grant           GM_setClipboard

// ==/UserScript==

(function () {

    'use strict';

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

    //---------------------------------------------------------------------------
    //  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);
        }
    };

    /**
     * @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);
    })();

    /***********************************************************************************\
        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];
            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: ["380px"],
                    title: "配置参数",
                    content: __ConfigPanelHtml,
                    offset:"auto",
                    shift: 2,
                    move: false,
                    closeBtn: 0,
                    success: function(layero, index) {
                        __ConfigPanelInits.forEach(init => { init(); });
                    },
                    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: "学习",
    };

    /**
     * @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) {
        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)) 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().replace(/[\s]+/,".");
            });
            Role._monitorHpMp();
            Role._monitorStatus();
            Role._monitorState();
            Role._monitorDeath();
            Role._monitorEquipments();
            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) {
            return this.equipments.indexOf(eqId) != -1;
        },

        living: true,

        combating: 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) {
            return FindItem(Object.values(Role.items), itemName, blurry, quality);
        },

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

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

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

        getDressed: function(equipments) {
            for (var i = equipments.length - 1; i >= 0; i--) {
                var e = equipments[i];
                if (e == null) {
                    WG.SendCmd("uneq " + Role.equipments[i]);
                } else {
                    WG.SendCmd("eq " + e);
                }
            }
        },

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

        _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;
                }
            });
        },
        _monitorEquipments: function() {
            WG.add_hook("dialog", function(data) {
                if (data.dialog != "pack") return;
                if (data.eqs != null) {
                    Role.equipments = [];
                    for (var i = 0; i < data.eqs.length; i++) {
                        var eq = data.eqs[i];
                        if (eq != null && eq.id != null) {
                            Role.equipments.push(eq.id);
                        } else {
                            Role.equipments.push(null);
                        }
                    }
                } else if (data.uneq != null) {
                    Role.equipments[data.uneq] = null;
                } else if (data.eq != null) {
                    Role.equipments[data.eq] = data.id;
                } else {
                    return;
                }
            });
        },
        _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.count != null) {
                                item.count -= data.remove;
                            } else {
                                item.count = 0;
                            }
                            if (item.count == 0) 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);
            });
        },
        _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;
                }
            });
        }
    };

    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) {
            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;
                        }
                        return item.id;
                    }
                } else {
                    if (item.name == name) {
                        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) {
            return FindItem(this._list, itemName, blurry, quality);
        },

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

    /***********************************************************************************\
        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,
            ":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.equipments[0],
            ":eq1": Role.equipments[1],
            ":eq2": Role.equipments[2],
            ":eq3": Role.equipments[3],
            ":eq4": Role.equipments[4],
            ":eq5": Role.equipments[5],
            ":eq6": Role.equipments[6],
            ":eq7": Role.equipments[7],
            ":eq8": Role.equipments[8],
            ":eq9": Role.equipments[9],
            ":eq10": Role.equipments[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.coolingSkills().indexOf(sid) != -1;
            },
            ":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 占位符
    //---------------------------------------------------------------------------

    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"
            });
            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);
                    break;
                    case "b": {
                        let item = Role.findItem(p.name, p.blurry, p.quality);
                        if (item) {
                            value = p.type == "id" ? item.id : item.count;
                        }
                        break;
                    }
                    case "d": {
                        let item = DialogList.findItem(p.name, p.blurry, p.quality);
                        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);
    })();

    // var SkillStack = {
    //     perform: function(id) {
    //         if (this._stack[id] == true) return;
    //         //
    //     },
    //     clean: function() {
    //         for (const key in this._stack) {
    //             delete this._stack[key];
    //         }
    //     },

    //     _stack: {}
    // };

    // (function() {
    //     const executor = new AtCmdExecutor("forcePfm", function(performer, param) {
    //         if (performer.log()) Message.cmdLog(`等待 ${(param/1000).toFixed(2)} 秒`);
    //         const parts = param.split(",");
    //         const skill = parts[0];
    //         let timeout = 10000;
    //         if (parts > 1) {
    //             timeout = parts[1];
    //         }
    //         return new Promise(resolve => {
    //             if (Role.coolingSkills().indexOf())
    //         });
    //     });
    //     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 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);
    })();

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

    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);
                    setTimeout(resolve, 1500);
                }, 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);
                setTimeout(resolve, 1500);
            });
        });
        CmdExecuteCenter.addExecutor(executor);
    })();

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

    function GetDungeonSource(name) {
        let source = null;
        for (const d of Dungeons) {
            if (d.name == name) {
                source = d.source;
                break;
            }
        }
        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
<---
($hpPer) = (_DungeonHpThreshold)/100
[if] (:hpPer) < (hpPer)
    @liaoshang
--->
<-recordGains
($_i) = 0
[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 邀月
($deadyaoyue) = true
[if] {邀月的尸体}? == null
    ($deadyaoyue) = false
    [if] (_DungeonWaitSkillCD) == 打开
        @cd
go northwest;go southwest
[if] ($deadyaoyue) == false
    @kill 邀月
look hua
@tip 你数了下大概有($number)朵花
go southeast
look bed;pushstart bed;pushleft bed[(number)]
pushright bed[8]
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 邀月
look hua
@tip 你数了下大概有($number)朵花
go southeast
look bed;pushstart bed;pushleft bed[(number)]
pushright bed[8]
go down;fire;go west
@kill 花无缺
look xia;open xia`
        },
        {
            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: `
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 👑 感谢 .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: "温府(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: `
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);
            });
            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) {
                    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("错误的角色流程获取码!");
            });
        },
        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);
                        }
                    });
                }
            });
        },
        getAllDungeonCmds: function(callback) {
            Server._sync("dungeon", null, callback);
        },
        /**
         * @param {Function} callback function(string[])
         */
        getAllCmdGroup: function(callback) {
            Server._sync("allCmdGroup", null, callback);
        },
        /**
         * @param {string} id 
         * @param {Function} callback function(Object), Object: {name: string, desc: string, author: string, create: Date, update: Date, code: string}
         */
        getCmdGroupInfo: function(id, callback) {
            Server._sync("cmdGroupInfo", {id: id}, callback);
        },

        _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 {
                        if (fail != null) fail(data.code);
                    }
                },
                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) {
            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");
                }
            });
            $("#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 () {
                TDAPP.onEvent("苏轻", "主页", {"count": 1});
                window.open("https://suqing.fun/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 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 () {
                TDAPP.onEvent("捷径", "走出桃花林", {"count": 1});
                WG.SendCmd('stopstate');
                THIsland.outMaze();
            });
            $(".zhoubotong").on('click', function () {
                TDAPP.onEvent("捷径", "找到周伯通", {"count": 1});
                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);
                    });
                });
            });

            $(".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>`;
            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)
        },

        _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.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;
        }
    }

    /***********************************************************************************\
        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);
        }
    };

    $(document).ready(function () {
        var talkingdata = document.createElement("script");
        var talkingdataAppid = "BFAAFE7548C749868F6357426410591D";
        talkingdata.setAttribute("src", `http://sdk.talkingdata.com/app/h5/v1?appid=${talkingdataAppid}&vn=${GM_info.script.version}&vc=${GM_info.script.version}`);
        setTimeout(_ => {
            document.head.appendChild(talkingdata);
        }, 1000);

        WG = unsafeWindow.WG;
        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();
    });
})();