wsmud_Raid

武神传说 MUD

目前為 2018-12-30 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         wsmud_Raid
// @namespace    cqv
// @version      0.5.0
// @date        23/12/2018
// @modified     28/12/2018
// @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

// ==/UserScript==

(function () {
    'use strict';

    var WG = unsafeWindow.WG;
    var messageAppend = undefined;
    var messageClear = undefined;

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

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

        status: [],

        equipments: [],

        init: function() {
            WG.add_hook("login", function(data) {
                Role.id = data.id;
                Role.status = [];
            });

            $("li[command=SelectRole]").on("click", function () {
                Role.name = $('.role-list .select').text().replace(/[\s]+/,".");
            });
            
            Role._monitorHpMp();
            Role._monitorStatus();
            Role._monitorEquipments();
            Role._monitorSkillCD();
            Role._monitorLocation();
            Role._monitorItemsInRoom();
        },

        hasStatus: function(s) {
            return Role.status.indexOf(s) != -1;
        },
        isFree: function() {
            return !Role.hasStatus("busy") && !Role.hasStatus("faint") && !Role.hasStatus("rash");
        },

        atPath: function(p) {
            switch (arguments.length) {
            case 0:
                return Role._roomPath;
            case 1:
                return p == Role._roomPath;
            }
        },
        inRoom: function(n) {
            switch (arguments.length) {
            case 0:
                return Role._roomName;
            case 1:
                return n == Role._roomName;
            }
        },

        renew: function(callback) {
            if (!Role.isFree) {
                window.setTimeout(function() { Role.renew(callback) }, 2000);
                return;
            }

            switch (Role._renewStatus) {
            case "resting":
                WG.go("扬州城-武庙");

                if (Role._renewHookIndex) WG.remove_hook(Role._renewHookIndex);
                Role._renewHookIndex = WG.add_hook("text", function(data) {
                    let patt1 = new RegExp("你运功完毕,深深吸了口气,站了起来。");
                    let count1 = patt1.exec(data.msg);
                    if (count1) {
                        Role._renewStatus = "dazuo finish"; return;
                    }
                    let patt2 = new RegExp("你目前气血充沛,没有受到任何伤害。|你疗伤完毕,深深吸了口气,脸色看起来好了很多。");
                    let count2 = patt2.exec(data.msg);
                    if (count2) {
                        Role._renewStatus = "liaoshang finish"; return;
                    }
                });

                Role._renewStatus = "liaoshang doing";
                WG.Send("stopstate;liaoshang");
                break;
            case "liaoshang finish":
                if (Role.mp/Role.maxMp < 0.7) {
                    Role._lastWeapon = Role.equipments[0];
                    Role._renewStatus = "dazuo doing";
                    WG.Send("stopstate;dazuo");
                }
                break;
            case "liaoshang doing":
            case "dazuo doing":
            case "dazuo finish":
                break;
            }

            if (Role._renewStatus == "liaoshang finish" || Role._renewStatus == "dazuo finish") {
                if (Role._renewStatus == "liaoshang finish") {
                    if (callback) callback();
                } else if (Role._renewStatus == "dazuo finish") {
                    window.setTimeout(function() {
                        WG.Send("stopstate");
                        Role.getDressed([Role._lastWeapon]);
                        if (callback) callback();
                    }, 4500);
                }
                WG.remove_hook(Role._renewHookIndex);
                Role._renewHookIndex = undefined;
                Role._renewStatus = "resting";
                return;
            }
            
            window.setTimeout(function() { Role.renew(callback); }, 2000);
        },

        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--) {
                let e = equipments[i];
                if (e == null) {
                    WG.Send("uneq " + Role.equipments[i]);
                } else {
                    WG.Send("eq " + e);
                }
            }
        },

        hasCoolingSkill: function() {
            return Role._coolingSkills.length > 0;
        },

        findItem: function(name) {
            for (var i = 0; i < Role._itemsInRoom.length; i++) {
                let item = Role._itemsInRoom[i];
                if (item.name == name) {
                    return item.id;
                }
            }
            return null;
        },

        _renewHookIndex: undefined,
        _renewStatus: "resting",

        _coolingSkills: [],

        _itemsInRoom: {},

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

            if (!WG.packup_listener) {
                window.setTimeout(callback, 2000);
                return;
            }
            if (counter > 5) {
                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 == undefined) break;
                    for (var i = data.items.length - 1; i >= 0; i--) {
                        let 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 != undefined) Role.hp = data.hp;
                    if (data.max_hp != undefined) Role.maxHp = data.max_hp;
                    if (data.mp != undefined) Role.mp = data.mp;
                    if (data.max_mp != undefined) Role.maxMp = data.max_mp;
                    break;
                }
            });
        },
        _monitorStatus: function() {
            WG.add_hook(["items", "status", "itemadd"], function(data) {
                switch (data.type) {
                case "items":
                    if (data.items == undefined) break;
                    for (var i = data.items.length - 1; i >= 0; i--) {
                        let item = data.items[i];
                        if (item.id != Role.id) continue;
                        if (item.status == undefined) break;
                        Role.status = [];
                        for (var j = item.status.length - 1; j >= 0; j--) {
                            let s = item.status[j];
                            Role.status.push(s.sid);
                        }
                        break;
                    }
                    break;
                case "status":
                    if (data.id != Role.id) break;
                    if (data.action == "add") {
                        Role.status.push(data.sid);
                    } else if (data.action == "remove") {
                        let index = Role.status.indexOf(data.sid);
                        if (index == -1) return;
                        Role.status.splice(index,1);
                    }
                    break;
                case "itemadd":
                    if (data.id != Role.id) break;
                    if (data.status == undefined) break;
                    Role.status = [];
                    for (var k = data.status.length - 1; k >= 0; k--) {
                        let s = data.status[k];
                        Role.status.push(s.sid);
                    }
                    break;
                }
            });
        },
        _monitorEquipments: function() {
            WG.add_hook("dialog", function(data) {
                if (data.dialog != "pack") return;
                if (data.eqs != undefined) {
                    for (var i = 0; i < data.eqs.length; i++) {
                        let eq = data.eqs[i];
                        if (eq != null && eq.id != null) {
                            Role.equipments.push(eq.id);
                        } else {
                            Role.equipments.push(null);
                        }
                    }
                } else if (data.uneq != undefined) {
                    Role.equipments[data.uneq] = null;
                } else if (data.eq != undefined) {
                    Role.equipments[data.eq] = data.id;
                } else {
                    return;
                }
            });
        },
        _monitorSkillCD: function() {
            WG.add_hook("dispfm", function(data) {
                let timestamp = Date.parse(new Date());
                let mark = data.id + "_" + timestamp;
                Role._coolingSkills.push(mark);
                window.setTimeout(function() {
                    let index = Role._coolingSkills.indexOf(mark);
                    if (index != -1) Role._coolingSkills.splice(index, 1);
                }, data.distime);
            });
        },
        _monitorLocation: function() {
            WG.add_hook("room", function(data) {
                Role._roomName = data.name;
                Role._roomPath = data.path;
            });
        },
        _monitorItemsInRoom: function() {
            WG.add_hook(["items", "itemadd", "itemremove"], function(data) {
                switch (data.type) {
                case "items":
                    if (data.items == undefined) break;
                    Role._itemsInRoom = [];
                    for (var i = 0; i < data.items.length; i++) {
                        let item = data.items[i];
                        if (item.name == undefined || item.id == undefined) continue;
                        Role._itemsInRoom.push(item);
                    }
                    break;
                case "itemadd":
                    if (data.name == undefined || data.id == undefined) break;
                    Role._itemsInRoom.push(data);
                    break;
                case "itemremove":
                    for (var i = 0; i < Role._itemsInRoom.length; i++) {
                        let item = Role._itemsInRoom[i];
                        if (item.id == data.id) {
                            Role._itemsInRoom.splice(i, 1);
                        }
                    }
                    break;
                }
            });
        },
    };

    var Config = {
        hpThresholdInDungeon: function() {
            return GM_getValue(Role.id + "@hpThresholdInRaid", "50");
        },
        setHpThresholdInDungeon: function(value) {
            GM_setValue(Role.id + "@hpThresholdInRaid", value);
        },

        waitSkillCD: function() {
            return GM_getValue(Role.id + "@waitSkillCD", "no");
        },
        setWaitSkillCD: function(value) {
            GM_setValue(Role.id + "@waitSkillCD", value);
        },

        // none, clean, tidy
        bagCleanWay: function() {
            return GM_getValue(Role.id + "@bagCleanWay", "clean");
        },
        setBagCleanWay: function(value) {
            GM_setValue(Role.id + "@bagCleanWay", value);
        },

        cmdInterval: function() {
            return GM_getValue(Role.id + "@cmdInterval", 1000);
        },

        setCmdInterval: function(value) {
            GM_setValue(Role.id + "@cmdInterval", value);
        },

        /*
        hpThresholdInRaid: function(raid) {
            return GM_getValue(Role.id + "@hpThresholdInRaid@" + raid, "50");
        },
        setHpThresholdInRaid: function(raid, value) {
            GM_setValue(Role.id + "@hpThresholdInRaid@" + raid, value);
        },

        waitSkillCD: function(raid) {
            return GM_getValue(Role.id + "@waitSkillCD@" + raid, "no");
        },
        setWaitSkillCD: function(raid, value) {
            GM_setValue(Role.id + "@waitSkillCD@" + raid, value);
        },
         */

        wudaota: {
            autoToFloor: function() {
                return GM_getValue(Role.id + "@wudao.autoToFloor", 0);
            },
            setAutoToFloor: function(value) {
                GM_setValue(Role.id + "@wudao.autoToFloor", value);
            },

            fastCombatOpening: function() {
                return GM_getValue(Role.id + "@wudao.fastCombatOpening", "no");
            },
            setFastCombatOpening: function(value) {
                GM_setValue(Role.id + "@wudao.fastCombatOpening", value);
            },

            hpThresholdInRaid: function() {
                return GM_getValue(Role.id + "@wudao.hpThresholdInRaid", "50");
            },
            setHpThresholdInRaid: function(value) {
                GM_setValue(Role.id + "@wudao.hpThresholdInRaid", value);
            },

            waitSkillCDFrom: function() {
                return GM_getValue(Role.id + "@wudao.waitSkillCDFrom", 100);
            },
            setWaitSkillCDFrom: function(value) {
                GM_setValue(Role.id + "@wudao.waitSkillCDFrom", value);
            },
        },
    };

    /* ------------------------ CmdExecuter ------------------------ *\
    \* ------------------------------------------------------------- */

    function CmdExecuter(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;
    }
    CmdExecuter.prototype.execute = function() {
        if (this.isWorking) return;
        this.isWorking = true;

        if (this.willStartExecute) this.willStartExecute();

        this._monitorItemsInRoom();

        this._performCmd(0);
    };
    CmdExecuter.prototype._performCmd = function(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];
            let valid = this.willPerformCmd(lastCmd, cmd);
            if (!valid) { this._delayPerformCmd(index); return; }
            cmd = valid;
        }

        // kill?开头,询问击杀是否完成命令
        if (cmd.indexOf("kill?") != -1) {
            let items = cmd.substring(5).split(";");
            if (CmdExecuter._removedItems == undefined) {
                this._delayPerformCmd(index); 
                return;
            }
            var removedItems = CmdExecuter._removedItems.slice();
            for (var i = 0; i < items.length; i++) {
                let item = items[i];
                let k = removedItems.indexOf(item);
                if (k == -1) { // 尚存在未击杀对象
                    WG.Send("kill " + Role.findItem(item));
                    this._delayPerformCmd(index); 
                    return;
                }
                removedItems.splice(k, 1);
            }
            CmdExecuter._removedItems = [];
        }

        // {item.name} 会被该 item 的 id 替换
        let patt = /\{.*?\}/g;
        var placeholders = [];
        var result = patt.exec(cmd);
        while(result != null) {
            placeholders.push(result[0]);
            result = patt.exec(cmd);
        }
        var realCmd = cmd;
        for (var j = 0; j < placeholders.length; j++) {
            let placeholder = placeholders[j];
            let item = Role.findItem(placeholder.substring(1, placeholder.length - 1));
            if (item == null) {
                this._delayPerformCmd(index); 
                return;
            }
            realCmd = realCmd.replace(placeholder, item);
        }
        cmd = realCmd;

        // @开头,虚命令,不真正执行
        if (cmd.indexOf("@") == -1 && cmd.indexOf("kill?") == -1) {
            console.log("执行命令:" + cmd);
            WG.Send(cmd);
        }
        if (this.didPerformCmd) { this.didPerformCmd(cmd); }

        // [exit] 保留命令,立即退出执行器
        if (cmd.indexOf("[exit]") != -1) {
            this._finishExecute();
            return;
        } else {

            this._delayPerformCmd(index + 1);
        }
    };
    CmdExecuter.prototype._delayPerformCmd = function(index) {
        let executer = this;
        window.setTimeout(function() { 
            executer._performCmd(index); 
        }, executer.interval);
    };
    CmdExecuter.prototype._monitorItemsInRoom = function() {
        CmdExecuter._hookIndex = WG.add_hook(["items", "itemadd", "itemremove"], function(data) {
            switch (data.type) {
            case "items":
                if (data.items == undefined) break;
                CmdExecuter._itemsInRoom = [];
                CmdExecuter._removedItems = [];
                for (var i = 0; i < data.items.length; i++) {
                    let item = data.items[i];
                    if (item.name == undefined || item.id == undefined) continue;
                    CmdExecuter._itemsInRoom.push(item);
                }
                break;
            case "itemadd":
                if (CmdExecuter._itemsInRoom == undefined) break;
                if (data.name == undefined || data.id == undefined) break;
                CmdExecuter._itemsInRoom.push(data);
                break;
            case "itemremove":
                if (CmdExecuter._itemsInRoom == undefined) break;
                for (var i = 0; i < CmdExecuter._itemsInRoom.length; i++) {
                    let item = CmdExecuter._itemsInRoom[i];
                    if (item.id == data.id) {
                        CmdExecuter._itemsInRoom.splice(i, 1);
                        CmdExecuter._removedItems.push(item.name);
                    }
                }
                break;
            }
        });
    };
    CmdExecuter.prototype._finishExecute = function() {
        this.isWorking = false;
        WG.remove_hook(CmdExecuter._hookIndex);
        if (this.didFinishExecute) this.didFinishExecute();
    };

    /* ------------------------ Workflow ------------------------ *\
    \* ---------------------------------------------------------- */

    function WorkItem(name, run, defaultParams) {
        this.name = name;
        this.run = run;
        this._params = defaultParams;
        this.description = function() {
            return this.name;
        };
    }
    WorkItem.prototype.didFinish = function() {
        this._didFinish();
    };
    // 可以为 WorkItem 设置 assert (function()),若返回 false 则该 WorkItem 不会被执行
    WorkItem.prototype.setAssert = function(assert) {
        this._assert = assert;
    };
    // 可以为 WorkItem 设置 params,如果该 WorkItem 需要的话
    WorkItem.prototype.setParams = function(params) {
        this._params = params;
    };

    var WorkItemCenter = {
        register: function(value, key) {
            if (typeof value == "function") {
                WorkItemCenter._items[key] = value;
            } else {
                WorkItemCenter._items[value.name] = value;
            }
        },
        take: function(key) {
            return WorkItemCenter._items[key];
        },
        allKey: function() {
            return Object.keys(WorkItemCenter._items);
        },
        _items: {},
    };

    WorkItemCenter.register(new WorkItem("回满状态", function() {
        let item = this;
        Role.renew(function() {
            item.didFinish();
        });
    }));
    WorkItemCenter.register(new WorkItem("清理背包", function() {
        let item = this;
        Role.cleanBag(function() {
            item.didFinish();
        });
    }));
    WorkItemCenter.register(new WorkItem("整理背包", function() {
        let item = this;
        Role.tidyBag(function() {
            item.didFinish();
        });
    }));
    WorkItemCenter.register(new WorkItem("通用结束动作", function() {
        WG.go("练功房");
        let item = this;
        window.setTimeout(function() {
            WG.Send("stopstate;dazuo");
            item.didFinish();
        }, 1000);
    }));
    WorkItemCenter.register(new WorkItem("等待技能冷却", function() {
        var timer;
        let item = this;
        timer = window.setInterval(function() {
            if (!Role.hasCoolingSkill()) {
                window.clearInterval(timer);
                item.didFinish();
            }
        }, 1000);
    }));
    WorkItemCenter.register(new WorkItem("等待时间", function() {
        let item = this;
        window.setTimeout(function() {
            item.didFinish();
        }, item._params);
    }), 1000);

    var Workflow = {
        name: undefined,
        items: [],

        willStartWorkflow: undefined,
        didFinishWorkflow: undefined,

        willStartWorkItem: undefined,       // optional: function(item.name)
        didFinishWorkItem: undefined,       // optional: function(item.name)

        start: function() {
            WG.Send('stopstate');
            WG.stopAllAuto();
            messageClear()
            messageAppend("正在运行工作流 " + Workflow.name + "...");
            messageAppend("* 运行工作流期间会暂时关闭 自动Boss 和 自动婚宴。");
            messageAppend("* 如需要立即中断,请刷新网页。");

            if (Workflow.willStartWorkflow) Workflow.willStartWorkflow();
            Workflow._start(0);
        },

        _start: function(number) {
            if (number >= Workflow.items.length) {
                Workflow._finish();
                return;
            }

            let item = Workflow.items[number];

            if (item._assert && item._assert() == false) {
                Workflow._start(number + 1);
                return;
            }

            item._didFinish = function() {
                if (Workflow.didFinishWorkItem) Workflow.didFinishWorkItem(item);
                messageAppend("▶ " + item.description() + "执行完毕。");
                window.setTimeout(function() {
                    Workflow._start(number + 1);
                }, 2000);
            };

            if (Workflow.willStartWorkItem) Workflow.willStartWorkItem(item);
            messageAppend("▷ 正在运行 " + item.description() + "...");
            item.run(item._params);
        },
        _finish: function() {
            let name = Workflow.name;
            Workflow.name = undefined;
            Workflow.items = [];
            Workflow.willStart = undefined;
            let didFinishWorkflow = Workflow.didFinishWorkflow;
            Workflow.didFinish = undefined;
            Workflow.willStartWorkItem = undefined;
            Workflow.didFinishWorkItem = undefined;

            messageAppend("顺利完成工作流 " + name + "。");
            WG.reSetAllAuto();
            if (didFinishWorkflow) didFinishWorkflow();
        }
    };

    /* ------------------------ Dungeon ------------------------ *\
    \* --------------------------------------------------------- */

    function Dungeon(name, description, cmds, willStartDungeon, didFinishDungeon, willPerformCmd, didPerformCmd) {
        this.name = name;
        this.description = description;
        this.cmds = cmds;
        this.willStartDungeon = willStartDungeon;
        this.didFinishDungeon = didFinishDungeon;
        this.willPerformCmd = willPerformCmd;

        var totalCmds = this.cmds.slice(); totalCmds.push("cr;cr over");
        let dungeon = this;
        let didFinishExecute = function() {
            if (dungeon.didFinishDungeon) dungeon.didFinishDungeon();
        };
        let executerWillPerformCmd = function(lastCmd, cmd) {
            return dungeon._willPerformCmd(lastCmd, cmd);
        };
        this.executer = new CmdExecuter(
            totalCmds,
            undefined,
            didFinishExecute,
            executerWillPerformCmd,
            didPerformCmd,
            Config.cmdInterval()
        );
    }
    Dungeon.prototype.enter = function() {
        this._reset();

        if (this.willStartDungeon) { this.willStartDungeon(); }

        this.executer.execute();
    };
    Dungeon.prototype._reset = function() {
        this._liaoshangDoing = false;
        this._waitingSkillCD = false;
        this._noneEnemy = false;
    };
    Dungeon.prototype._willPerformCmd = function(lastCmd, cmd) {
        if (this._liaoshangDoing) {
            if (Role.hp/Role.maxHp < 0.99) {
                return null;
            } else {
                this._liaoshangDoing = false;
                WG.Send("stopstate");
            }
        }
        if (Role.hp/Role.maxHp < Config.hpThresholdInDungeon()/100 && !this._noneEnemy) {
            WG.Send("liaoshang");
            this._liaoshangDoing = true;
            return null;
        }

        // #结尾,表明执行完此命令将会遇到 Boss
        if (cmd.indexOf("#") != -1) {
            if (Config.waitSkillCD() == "yes" && Role.hasCoolingSkill()) {
                if (!this._waitingSkillCD) {
                    messageAppend("! 前方高能,等待技能冷却再战...");
                    this._waitingSkillCD = true;
                }
                return null;
            }
            cmd = cmd.substring(0, cmd.length - 1);
        }
        this._waitingSkillCD = false;

        // $结尾,表明之后的命令不再会遇敌
        if (cmd.indexOf("$") != -1) {
            this._noneEnemy = true;
            cmd = cmd.substring(0, cmd.length - 1);
        }

        if (this.willPerformCmd) cmd = this.willPerformCmd(lastCmd, cmd);
        return cmd;
    };

    // Dungeon Config

    let DungeonConfig = {

        "移花宫": {
            "shared": {
                willStartDungeon: function() {
                    let index1 = WG.add_hook("item", function(data) {
                        if (data.desc == undefined) return;
                        let patt = new RegExp("你数了下大概有\\d(?=朵花。)");
                        let result = patt.exec(data.desc);
                        if (result) {
                            let text = result.toString();
                            let count = text[text.length - 1];
                            ToRaid.leftPush = count;
                        }
                    });
                    let index2 = WG.add_hook("exits", function(data) {
                        if (data.items == undefined || data.items.down == undefined) return;
                        if (data.items.down == "密道") {
                            ToRaid.didFindSecretPath = true;
                        }
                    });
                    ToRaid.indexes = [index1, index2];

                    ToRaid.leftPush = 0;
                    ToRaid.didFindSecretPath = false;
                },
                didFinishDungeon: function() {
                    for (var i = ToRaid.indexes.length - 1; i >= 0; i--) {
                        let index = ToRaid.indexes[i];
                        WG.remove_hook(index);
                    }
                },
                willPerformCmd: function(lastCmd, cmd) {
                    if (lastCmd == "look hua" && (ToRaid.leftPush == undefined || ToRaid.leftPush == 0)) {
                        return null;
                    }
                    if (lastCmd == "look bed" && !ToRaid.didFindSecretPath) {
                        WG.Send("pushstart bed");
                        for (var i = ToRaid.leftPush - 1; i >= 0; i--) {
                            WG.Send("pushleft bed");
                        }
                        for (var j = 7; j >= 0; j--) {
                            WG.Send("pushright bed");
                        }
                        return null;
                    }
                    return cmd;
                },
            },
            "困难": {
                number: 222,
                cmds: [
                    "jh fb 22 start2;cr huashan/yihua/shandao 1 0",
                    "go south;go south;go south;go south;go south;go south;go south",
                    "go south;go south;go south;go south;go south;go south;go south",
                    "kill?花月奴",
                    "go south;go south",
                    "kill?移花宫女弟子;移花宫女弟子",
                    "go south",
                    "kill?移花宫女弟子;移花宫女弟子",
                    "go southeast#",
                    "kill?移花宫二宫主 涟星;移花宫大宫主 邀月",
                    "go northwest;go southwest",
                    "look hua",
                    "go southeast",
                    "look bed",
                    "go down;fire;go west",
                    "kill?移花宫少宫主 花无缺",
                    "look xia;open xia",
                    "@wait"
                ],
            },
            "简单": {
                number: 221,
                cmds: [
                    "jh fb 22 start1;cr huashan/yihua/shandao",
                    "go south;go south;go south;go south;go south;go south;go south",
                    "go south;go south;go south;go south;go south;go south;go south",
                    "kill?花月奴",
                    "go south;go south",
                    "kill?移花宫女弟子;移花宫女弟子",
                    "go south",
                    "kill?移花宫女弟子;移花宫女弟子",
                    "go southeast#",
                    "kill?移花宫二宫主 涟星",
                    "go northwest;go southwest",
                    "kill?移花宫大宫主 邀月",
                    "look hua",
                    "go southeast",
                    "look bed",
                    "go down;fire;go west",
                    "kill?移花宫少宫主 花无缺",
                    "look xia;open xia",
                    "@wait"
                ],
            },
        },

        "白驼山": {
            "shared": {
                cmds: [
                    "jh fb 19 start3;cr baituo/damen 2 0",
                    "go north;go north;go north",
                    "kill?白驼山少庄主 欧阳克;白衣少女",
                    "go north#",
                    "kill?<hiy>西毒</hiy> 欧阳锋",
                    "go south;go south;go south;go west;go west;go west",
                    "kill?毒蛇",
                    "go north",
                    "kill?毒蛇",
                    "go north;go north",
                    "kill?蟒蛇$"
                ],
            },
            "组队": {
                number: 193,
            }
        },

        "燕子坞": {
            "简单": {
                number: 231,
                cmds: [
                    "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",
                    "go east;go east;go east#",
                    "kill?姑苏慕容公子 慕容复",
                    "go west;go north",
                    "look pai;bai pai;bai pai;bai pai",
                    "go north;search",
                    "go south#",
                    "kill?慕容博$"
                ],
            },
            "困难": {
                number: 232,
                cmds: [
                    "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",
                    "go east;go east;go east#",
                    "kill?姑苏慕容公子 慕容复",
                    "go west;go north",
                    "look pai;bai pai;bai pai;bai pai",
                    "go north;search",
                    "go south#",
                    "kill?慕容博$"
                ],
            },
            "偷书": {
                number: 233,
                description: "👏 <hiy>感谢 Airson 提供本副本代码。</hiy>",
                cmds: [
                    "jh fb 23 start1;cr murong/anbian",
                    "go east;go east",
                    "kill?金凤庄庄主 包不同$",
                    "go east;go east;go east;go north",
                    "look pai;bai pai;bai pai;bai pai",
                    "go north;search",
                ],
            },
        },

        "华山论剑": {
            "shared": {
                number: 300,
                description: "👏 <hiy>感谢 koyodakla、freesunny 对本副本代码提供的帮助。</hiy>",
                cmds: [
                    "jh fb 30 start1;cr huashan/lunjian/leitaixia",
                    "go up#",
                    "kill?<hiy>东邪</hiy> 黄药师;<hiy>南帝</hiy> 一灯大师;<hiy>西毒</hiy> 欧阳锋;<hiy>北丐</hiy> 洪七公;<hiy>中神通</hiy> 王重阳$",
                    "jump bi",
                    "@get box",
                    "@wait"
                ],
                willPerformCmd: function(lastCmd, cmd) {
                    if (cmd == "@get box") WG.get_all();
                    return cmd;
                },
            },
        },

        "温府": {
            ">2k闪避": {
                number: 103,
                description: "👏 <hiy>感谢 JiaQi Wan 提供本副本代码。</hiy>",
                cmds: [
                    "jh fb 23 start2;cr cd/wen/damen",
                    "look tree;climb tree;go north;go northeast;go north;go north;go northwest;go north",
                    "look zhuang;tiao zhuang",
                    "kill?温家老二 温方义;温家老三 温方山;温家老四 温方施;温家老五 温方南;温家老大 温方达",
                    "look zhuang;tiao zhuang#",
                    "kill?<hiy>金蛇郎君</hiy> 夏雪宜",
                    "go north",
                    "kill?温家小姐 温仪$"
                ],
            }
        },

        "桃花岛": {
            "困难": {
                number: 182,
                cmds: [
                    "jh fb 18 start2;cr taohua/haitan 1 0",
                    "go south",
                    "@look 1",
                    "@look 5",
                    "go south;go south",
                    "kill?桃花岛四弟子 陆乘风",
                    "go east;go east",
                    "kill?桃花岛大弟子 曲灵风",
                    "go east;go north#",
                    "kill?<hiy>东邪</hiy> 黄药师$",
                ],
                willStartDungeon: function() {
                    TaohuaIsland._monitorMaze();
                },
                didFinishDungeon: function() {
                    TaohuaIsland._cancelMonitorMaze();
                },
                willPerformCmd: function(lastCmd, cmd) {
                    if (cmd == "@look 1") {
                        if (TaohuaIsland._goCenterCmd) {
                           return TaohuaIsland._goCenterCmd;
                        } else {
                            return null;
                        }
                    }
                    if (cmd == "@look 5") {
                        if (TaohuaIsland._decodedMaze) {
                            return TaohuaIsland._outMazeCmd();
                        } else {
                            return null;
                        }
                    }
                    return cmd;
                }
            }
        },

        "财主家": {
            "简单": {
                number: 21,
                cmds: [
                    "jh fb 1 start1;cr yz/cuifu/caizhu",
                    "kill?大狼狗;大狼狗",
                    "go north",
                    "kill?管家;家丁;家丁",
                    "go north#",
                    "kill?财主 崔员外",
                    "look men;open men;go east",
                    "ok {丫鬟}",
                    "go west;go south;go south",
                    "go north;go north;go west",
                    "select {财主女儿 崔莺莺};ask {财主女儿 崔莺莺} about 东厢",
                    "kill?财主女儿 崔莺莺$",
                    "go east;go east;look gui;search gui"
                ],
            },
            "困难": {
                number: 22,
                cmds: [
                    "jh fb 1 start2;cr yz/cuifu/caizhu 1 0",
                    "kill?大狼狗;大狼狗",
                    "go north",
                    "kill?管家;家丁;家丁",
                    "go north#",
                    "kill?财主 崔员外",
                    "look men;open men;go east",
                    "ok {丫鬟}",
                    "go west;go south;go south",
                    "go north;go north;go west",
                    "select {财主女儿 崔莺莺};ask {财主女儿 崔莺莺} about 东厢",
                    "kill?财主女儿 崔莺莺$",
                    "go east;go east;look gui;search gui"
                ],
            }
        },

        "星宿海": {
            "shared": {
                number: 200,
                cmds: [
                    "jh fb 20 start1;cr xingxiu/xxh6",
                    "go northeast",
                    "kill?星宿派八师兄 出尘子",
                    "go north",
                    "kill?星宿派小师妹 阿紫",
                    "go northwest",
                    "kill?星宿派二师兄 狮吼子",
                    "go southwest",
                    "kill?星宿派大师兄 摘星子",
                    "go south",
                    "kill?星宿派三师兄 天狼子",
                    "#go north;go northeast;go north$",
                    "kill?星宿老怪 丁春秋"
                ],
            },
        }
    };

    function parseDungeonConfig() {
        var result = [];
        for (var key in DungeonConfig) {
            let value = DungeonConfig[key];
            let count = Object.keys(value).length;
            var shared = value["shared"];
            if (shared && count > 1) {
                for (var subkey in value) {
                    if (subkey == "shared") continue;
                    var item = { };
                    for (var sharedKey in shared) {
                        item[sharedKey] = shared[sharedKey];
                    }
                    let subvalue = value[subkey];
                    for (var itemKey in subvalue) {
                        item[itemKey] = subvalue[itemKey];
                    }
                    item.name = key + "(" + subkey + ")";
                    result.push(item);
                }
            } else if (shared && count == 1) {
                shared.name = key;
                result.push(shared);
            } else if (count > 0) {
                for (var subkey in value) {
                    var item = { };
                    let subvalue = value[subkey];
                    for (var itemKey in subvalue) {
                        item[itemKey] = subvalue[itemKey];
                    }
                    item.name = key + "(" + subkey + ")";
                    result.push(item);
                }
            }
        }
        result.sort(function(a, b) {
            return b.number - a.number;
        });
        return result;
    }

    let ParsedDungeonConfig = parseDungeonConfig();

    (function registerDungeonWorkItems() {
        let item = new WorkItem("扫荡副本", function(dungeonName) {
            var config;
            for (var i = 0; i < ParsedDungeonConfig.length; i++) {
                let c = ParsedDungeonConfig[i];
                if (c.name == dungeonName) {
                    config = c; break;
                }
            }
            let dungeon = new Dungeon(
                config.name,
                config.description,
                config.cmds,
                config.willStartDungeon,
                undefined,
                config.willPerformCmd,
                config.didPerformCmd
            );
            let didFinishDungeon = config.didFinishDungeon;
            let item = this;
            dungeon.didFinishDungeon = function() {
                if (didFinishDungeon) didFinishDungeon();
                item.didFinish();
            };
            dungeon.enter();
        }, "副本名称");
        item.description = function() {
            return "扫荡" + this._params;
        };
        WorkItemCenter.register(item);
    })();

    /* ------------------------ Raid ------------------------ *\
    \* ------------------------------------------------------ */

    var Raid = {
        repeatRun: function(name) {
            let num = prompt("输入自动【" + name + "】副本次数,例如:\"1\"", '1');
            if (num > 0) {
                Raid._repeatTotal = num;
                Raid._repeatCounter = 0;
            } else {
                return;
            }

            Workflow.name = "自动扫荡副本 <" + name + "> " + num + "次";
            var items = [];
            let toDungeon = WorkItemCenter.take("扫荡副本");
            toDungeon.setParams(name);
            let cleanBagItem = Raid._cleanBagItem();
            for (var i = 0; i < num; i++) {
                items.push(WorkItemCenter.take("回满状态"));
                if (cleanBagItem) items.push(cleanBagItem);
                items.push(toDungeon);
            }
            if (cleanBagItem) items.push(cleanBagItem);
            items.push(WorkItemCenter.take("通用结束动作"));
            Workflow.items = items;
            Workflow.willStartWorkItem = function(item) {
                if (item.name == "扫荡副本") {
                    Raid._repeatCounter += 1;
                    messageAppend("- 自动扫荡进度:正在进行 " + Raid._repeatCounter + "/" + Raid._repeatTotal + "...");
                }
            };
            Workflow.start();
        },

        _cleanBagItem: function() {
            switch (Config.bagCleanWay()) {
            case "clean":
                return WorkItemCenter.take("清理背包");
            case "tidy":
                return WorkItemCenter.take("整理背包");
            default:
                return null;
            }
        },

        _repeatTotal: 1,
        _repeatCounter: 0,
    };

    var Wudaota2 = {
        run: function() {
            Workflow.name = "自动扫荡武道塔";
            var items = [];
            items.push(WorkItemCenter.take("清理背包"));
            items.push(Wudaota2._reset);

            items.push(Wudaota2._fastFight);
            items.push(WorkItemCenter.take("通用结束动作"));
            Workflow.items = items;

            Workflow.start();
        },

        _reset: new WorkItem("重置武道塔", function() {
            let cmds = [
                "jh fam 0 start;jh fam 8 start",
                "@reset",
            ];
            let item = this;
            let didFinishExecute = function() {
                item.didFinish();
            };
            let willPerformCmd = function(lastCmd, cmd) {
                if (cmd == "@reset") {
                    let butler = Role.findItem("守护人");
                    return "select " + butler + ";ask1 " + butler;
                }
                return cmd;
            };
            let executer = new CmdExecuter(cmds, willStartExecute, didFinishExecute, willPerformCmd);
            executer.execute();
        }),
        _fight: new WorkItem("挑战守护者", function() {

        }),
        _fastFight: new WorkItem("快速挑战守护者(扫荡符)", function() {

        }),
    };

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

    let HighlightedRaids = ["移花宫(简单)", "移花宫(困难)", "华山论剑"];

    var MoreRaid = new Vue({
        el: '#MoreRaid',
        data: {
            items: [],
        },
        methods: {
            getItems: function() {
                var result = [{name: "< 返回"}];
                for (var i = 0; i < ParsedDungeonConfig.length; i++) {
                    let config = ParsedDungeonConfig[i];
                    if (HighlightedRaids.indexOf(config.name) != -1) continue;
                    result.push(config);
                }
                return result;
            },
            createSpan: function(createElement, item) {
                return createElement(
                    'span',
                    {
                        attrs: { class: "zdy-item" },
                        style: { width: "120px" },
                        on: {
                            click: function() {
                                if (item.name == "< 返回") {
                                    UI.home();
                                } else {
                                    Raid.repeatRun(item.name);
                                }
                            },
                        },
                    },
                    item.name,
                );
            },
        },
        render: function(createElement, item) {
            let theSelf = this;
            var subNodes = [
                createElement("br"),
                "⛩ ",
                createElement("hic", ["更多副本"]),
                createElement("br"),
                createElement("br"),
            ];
            let spans = this.items.map(function(item) {
                return theSelf.createSpan(createElement, item);
            });
            subNodes.push(spans);
            return createElement(
                "div",
                {
                    attrs: { class: "item-commands" },
                    style: { "text-align": "center" },
                },
                subNodes,
            );
        }
    });
    MoreRaid.items = MoreRaid.getItems();

    var UI = {
        home: function() {
            messageClear();
            var html = `
            <div class = "item-commands" style="text-align:center">
                </br>🏮 <ord>恭祝 2019(己亥年) 新春大吉、万事如意!</ord>🏮</br></br>
                <span class = "zdy-item yihua" style="width:120px"> 移花宫(简单) </span>
                <span class = "zdy-item yihuaH" style="width:120px"> 移花宫(困难) </span>
                <span class = "zdy-item lunjian" style="width:120px"> 华山论剑(简单) </span>
                <span class = "zdy-item raidSetting" style="width:120px"> ⚙ 副本设置 </span>
                <span class = "zdy-item wudaota" style="width:120px"> 🏯 <hio>武道塔</hio> </span>
                <span class = "zdy-item xiangyang" style="width:120px"> ⚔ <hiy>守卫襄阳</hiy> </span>
                <span class = "zdy-item shortcut" style="width:120px"> 🚀 <hig>捷径</hig> </span>
                <span class = "zdy-item moreRaid" style="width:120px"> ⛩ <hic>更多副本</hic> </span>
                <div>
                    </br><a href="https://greasyfork.org/zh-CN/scripts/375851-wsmud-raid/feedback" style="font-size:1em">(版本: ${GM_info.script.version})</a>
                </div>
            </div>`;
            messageAppend(html);

            $(".yihua").on('click',function(){
                Raid.repeatRun("移花宫(简单)");
            });
            $(".yihuaH").on('click', function () {
                Raid.repeatRun("移花宫(困难)");
            });
            $(".lunjian").on('click', function () {
                Raid.repeatRun("华山论剑");
            });
            $(".raidSetting").on('click', function () {
                UI.setting();
            });
            $(".wudaota").on('click', function () {
                UI.wudaota();
            });
            $(".xiangyang").on('click', function () {
                UI.xiangyang();
            });
            $(".shortcut").on('click', function () {
                UI.shortcut();
            });
            $(".moreRaid").on('click', function () {
                UI.moreRaid();
            });
        },
        setting: function() {
            messageClear();
            var html = `
            <div class = "item-commands" style="text-align:center">
                </br>⚙ 副本设置</br></br>
                <span style='border:solid 0px gray'>
                    <label for="liaoshangInRaid">◆ 副本内疗伤,当气血低于: </label><select style='width:80px' id="liaoshangInRaid">
                        <option value="100">100%</option>
                        <option value="90">90%</option>
                        <option value="80">80%</option>
                        <option value="70">70%</option>
                        <option value="60">60%</option>
                        <option value="50">50%</option>
                        <option value="40">40%</option>
                        <option value="30">30%</option>
                        <option value="20">20%</option>
                        <option value="10">10%</option>
                    </select>
                </span>
                <span style='border:solid 0px gray'>
                    <label for="waitSkillCD">◆ Boss战前等待技能冷却: </label><select style='width:80px' id = "waitSkillCD">
                        <option value="no">关闭</option>
                        <option value="yes">开启</option>
                    </select>
                </span>
                <span style='border:solid 0px gray'>
                    <label for="bagCleanWay">◆ 背包清理方案: </label><select style='width:80px' id = "bagCleanWay">
                        <option value="none">不清理</option>
                        <option value="clean">售卖</option>
                        <option value="tidy">存仓&售卖</option>
                    </select>
                </span>
                <span style='border:solid 0px gray'>
                    <label for="cmdInterval">◆ 命令间隔时间: </label><select style='width:80px' id = "cmdInterval">
                        <option value=1000>1秒(推荐)</option>
                        <option value=2000>2秒</option>
                        <option value=3000>3秒</option>
                        <option value=4000>4秒</option>
                    </select>
                </span>
                <!--
                <span style='border:solid 0px gray'>
                    <label for="autoArm">自动换装备&技能: </label><select style='width:80px' id = "autoArm">
                        <option value="no">关闭</option>
                        <option value="yes">开启</option>
                    </select><button>更新为当前</button>
                </span>
                -->
                </br>
                <span class = "zdy-item settingBack" style="width:120px"> < 返回 </span>
            </div>`;
            messageAppend(html);

            $('#liaoshangInRaid').val(Config.hpThresholdInDungeon());
            $("#liaoshangInRaid").change(function () {
                Config.setHpThresholdInDungeon($("#liaoshangInRaid").val());
            });
            $('#waitSkillCD').val(Config.waitSkillCD());
            $("#waitSkillCD").change(function () {
                Config.setWaitSkillCD($("#waitSkillCD").val());
            });
            $('#bagCleanWay').val(Config.bagCleanWay());
            $("#bagCleanWay").change(function () {
                Config.setBagCleanWay($("#bagCleanWay").val());
            });
            $('#cmdInterval').val(Config.cmdInterval());
            $("#cmdInterval").change(function () {
                Config.setCmdInterval($("#cmdInterval").val());
            });

            $(".settingBack").on('click', function () {
                UI.home();
            });
        },
        wudaota: function() {
            messageClear();
            var html = `
            <div class = "item-commands" style="text-align:center">
                </br>🏯 <hio>武道塔</hio></br></br>
                <!--
                <span style='border:solid 0px gray;width:100%'>
                    <label>◆ 自动战斗到第 </label><input type="number" id="wudaotaAutoToFloor" style="text-align:center;width:60px"><label> 层,</label><label for="wudaotaFastCombatOpening">剩余层扫荡符处理 </label><select style='width:60px' id = "wudaotaFastCombatOpening">
                        <option value="no">关闭</option>
                        <option value="yes">开启</option>
                    </select><label>。</label>
                </span>
                <span style='border:solid 0px gray;width:100%'>
                    <label for="wudaotaHpThreshold">◆ 疗伤,当气血小于 </label><select style='width:60px' id="wudaotaHpThreshold">
                        <option value="100">100%</option>
                        <option value="90">90%</option>
                        <option value="80">80%</option>
                        <option value="70">70%</option>
                        <option value="60">60%</option>
                        <option value="50">50%</option>
                        <option value="40">40%</option>
                        <option value="30">30%</option>
                        <option value="20">20%</option>
                        <option value="10">10%</option>
                    </select><label>;从第 </label><input type="number" id="wudaotaWaitSkillCDFrom" style="text-align:center;width:60px"><label for="waitSkillCD"> 层开始,战前等待技能冷却。</label>
                </span>
                -->
                即将开放...
                </br>
                <span class = "zdy-item wudaoBack" style="width:120px"> < 返回 </span>
                <!--<span class = "zdy-item wudaoStart" style="width:120px"> 开始爬塔 </span>-->
            </div>`;
            messageAppend(html);

            $('#wudaotaAutoToFloor').val(Config.wudaota.autoToFloor());
            $('#wudaotaAutoToFloor').focusout(function () {
                let autoToFloor = $('#wudaotaAutoToFloor').val();
                if (autoToFloor >= 0 && autoToFloor <= 100) {
                    Config.wudaota.setAutoToFloor(autoToFloor);
                } else {
                    $('#wudaotaAutoToFloor').val(Config.wudaota.autoToFloor());
                }
            });
            $('#wudaotaFastCombatOpening').val(Config.wudaota.fastCombatOpening());
            $("#wudaotaFastCombatOpening").change(function () {
                Config.wudaota.setFastCombatOpening($("#wudaotaFastCombatOpening").val());
            });
            $('#wudaotaHpThreshold').val(Config.wudaota.hpThresholdInRaid());
            $("#wudaotaHpThreshold").change(function () {
                Config.wudaota.setHpThresholdInRaid($("#wudaotaHpThreshold").val());
            });
            $('#wudaotaWaitSkillCDFrom').val(Config.wudaota.waitSkillCDFrom());
            $('#wudaotaWaitSkillCDFrom').focusout(function () {
                let from = $('#wudaotaWaitSkillCDFrom').val();
                if (from >= 0 && from <= 100) {
                    Config.wudaota.setWaitSkillCDFrom(from);
                } else {
                    $('#wudaotaWaitSkillCDFrom').val(Config.wudaota.waitSkillCDFrom());
                }
            });

            $(".wudaoBack").on('click', function () {
                UI.home();
            });
            $(".wudaoStart").on('click', function () {
                Wudaota.run();
            });
        },
        xiangyang: function() {
            messageClear();
            var html = `
            <div class = "item-commands" style="text-align:center">
                </br>⚔ <hiy>守卫襄阳</hiy></br></br>
                <span class = "zdy-item xiangyangBack" style="width:120px"> < 返回 </span>
                <span class = "zdy-item xiangyangStart" style="width:120px"> <hiy>提开发建议=></hiy> </span>
            </div>`;
            messageAppend(html);

            $(".xiangyangBack").on('click', function () {
                UI.home();
            });
            $(".xiangyangStart").on('click', function () {
                window.open("https://greasyfork.org/zh-CN/forum/discussion/48858/%E5%AE%88%E5%8D%AB%E8%A5%84%E9%98%B3%E5%BC%80%E5%8F%91%E5%BB%BA%E8%AE%AE/p1?new=1", '_blank').location;
            });
        },
        moreRaid: function() {
            messageClear();

            let wg_log = document.getElementsByClassName("WG_log")[0];
            let pre = wg_log.getElementsByTagName("pre")[0];
            pre.appendChild(MoreRaid.$el);
        },
        shortcut: function() {
            messageClear();
            var html = `
            <div class = "item-commands" style="text-align:center">
                </br>🚀 <hig>捷径</hig></br></br>
                <span class = "zdy-item shortcutBack" style="width:120px"> < 返回 </span>
                <span class = "zdy-item outMaze" style="width:120px"> 走出桃花林 </span>
                <span class = "zdy-item zhoubotong" style="width:120px"> 找到周伯通 </span>
            </div>`;
            messageAppend(html);

            $(".shortcutBack").on('click', function () {
                UI.home();
            });
            $(".outMaze").on('click', function () {
                WG.Send('stopstate');
                TaohuaIsland.outMaze();
            });
            $(".zhoubotong").on('click', function () {
                WG.Send('stopstate');
                TaohuaIsland.zhoubotong();
            });
        }
    };

    var ToRaid = { menu :UI.home };

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

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

            let 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",
            ];
            let willStartExecute = function() {
                TaohuaIsland._monitorMaze();
                TaohuaIsland._exitsHookIndex = WG.add_hook("exits", function(data) {
                    if (TaohuaIsland._lastCoord == undefined || TaohuaIsland._lastCoord == [0, 0]) return;
                    if (Object.keys(data.items).length != 4) return;
                    for(var key in data.items) {
                        if (data.items[key] != "桃花林") return;
                    }
                    let 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"],]
                    ];
                    let x = TaohuaIsland._lastCoord[0] + 1;
                    let y = TaohuaIsland._lastCoord[1] + 1;
                    let normalExists = normalExistMap[x][y];
                    for(var key2 in data.items) {
                        if (normalExists.indexOf(key2) != -1) continue;
                        TaohuaIsland._goCave = "go " + key2;
                        return;
                    }
                });
            };
            let didFinishExecute = function() {
                TaohuaIsland._lastCoord = undefined;
                TaohuaIsland._lastGo = undefined;
                TaohuaIsland._goCave = undefined;
                TaohuaIsland._cancelMonitorMaze();
                WG.remove_hook(TaohuaIsland._exitsHookIndex);
            };
            let willPerformCmd = function(lastCmd, cmd) {
                if (TaohuaIsland._goCave) return TaohuaIsland._goCave + ";go west;[exit]";

                var number = 0;
                switch (cmd) {
                case "@look 1":
                    if (TaohuaIsland._goCenterCmd) {
                       return TaohuaIsland._goCenterCmd;
                    } else {
                        return null;
                    }
                    break;
                case "@look 5":
                    if (!TaohuaIsland._decodedMaze) return null;
                    break;
                case "@go 2":
                    TaohuaIsland._lastCoord = TaohuaIsland._mazeCoords[2];
                    TaohuaIsland._lastGo = TaohuaIsland._mazePath(TaohuaIsland._lastCoord);
                    return TaohuaIsland._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) {
                    let back = TaohuaIsland._mazeBackPath(TaohuaIsland._lastGo);
                    TaohuaIsland._lastCoord = TaohuaIsland._mazeCoords[number];
                    TaohuaIsland._lastGo = TaohuaIsland._mazePath(TaohuaIsland._lastCoord);
                    return back + ";" + TaohuaIsland._lastGo;
                }
                return cmd;
            };
            let executer = new CmdExecuter(
                cmds,
                willStartExecute,
                didFinishExecute,
                willPerformCmd,
                undefined,
                1000
            );
            executer.execute();
        },

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

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

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

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

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

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

    let ChineseToNumber = {
        run: function(text) {
            var rtn = 0;
            var section = 0;
            var number = 0;
            var secUnit = false;
            var str = text.split('');

            for (var i = 0; i < str.length; i++) {
                var num = ChineseToNumber.chnNumChar[str[i]];
                if (typeof num !== 'undefined') {
                    number = num;
                    if (i === str.length - 1){
                        section += number;
                    }
                }else{
                    var unit = ChineseToNumber.chnNameValue[str[i]].value;
                    secUnit = ChineseToNumber.chnNameValue[str[i]].secUnit;
                    if (secUnit){
                        section = (section + number) * unit;
                        rtn += section;
                        section = 0;
                    } else {
                        section += (number * unit);
                    }
                    number = 0;
                }
            }
            return rtn + section;
        },
        chnNumChar: {
            零:0,
            一:1,
            二:2,
            三:3,
            四:4,
            五:5,
            六:6,
            七:7,
            八:8,
            九:9
        },
        chnNameValue: {
            十:{value:10, secUnit:false},
            百:{value:100, secUnit:false},
            千:{value:1000, secUnit:false},
            万:{value:10000, secUnit:true},
            亿:{value:100000000, secUnit:true}
        }
    };

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