var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 
{
    "init": function () {
	this._afterLoadResources = function () {
		// 本函数将在所有资源加载完毕后，游戏开启前被执行
	};

	//抄的轮回3，看不懂

	core.plugin.isEnemy = function (x, y, floorId) {
		var res = core.getBlockCls(x, y, floorId);
		return res == "enemys" || res == "enemy48";
	}

	var origin_func_checkNextPoint = core.maps._canMoveDirectly_checkNextPoint;
	core.maps._canMoveDirectly_checkNextPoint = function (blocksObj, x, y) {
		var index = x + "," + y;
		// 是否会触发追猎
		if (core.status.checkBlock.chase[index]) return false;
		return origin_func_checkNextPoint.call(this, blocksObj, x, y);
	};

	events.prototype._startGame_afterStart = function (callback) {
		core.ui.closePanel();
		if (core.status.floorId) { // 后续重开
			core.insertAction([]);
			if (callback) callback();
		} else {
			core.changeFloor(core.firstData.floorId, null, core.firstData.hero.loc, null, function () {
				// 插入一个空事件避免直接回放录像出错
				core.insertAction([]);
				if (callback) callback();
			});
		}
		this._startGame_upload();
	}

},
    "drawLight": function () {
        // 绘制灯光/漆黑层效果。调用方式 core.plugin.drawLight(...)
        // 【参数说明】
        // name：必填，要绘制到的画布名；可以是一个系统画布，或者是个自定义画布；如果不存在则创建
        // color：可选，只能是一个0~1之间的数，为不透明度的值。不填则默认为0.9。
        // lights：可选，一个数组，定义了每个独立的灯光。
        //        其中每一项是三元组 [x,y,r] x和y分别为该灯光的横纵坐标，r为该灯光的半径。
        // lightDec：可选，0到1之间，光从多少百分比才开始衰减（在此范围内保持全亮），不设置默认为0。
        //        比如lightDec为0.5代表，每个灯光部分内圈50%的范围全亮，50%以后才开始快速衰减。
        // 【调用样例】
        // core.plugin.drawLight('curtain'); // 在curtain层绘制全图不透明度0.9，等价于更改画面色调为[0,0,0,0.9]。
        // core.plugin.drawLight('ui', 0.95, [[25,11,46]]); // 在ui层绘制全图不透明度0.95，其中在(25,11)点存在一个半径为46的灯光效果。
        // core.plugin.drawLight('test', 0.2, [[25,11,46,0.1]]); // 创建一个test图层，不透明度0.2，其中在(25,11)点存在一个半径为46的灯光效果，灯光中心不透明度0.1。
        // core.plugin.drawLight('test2', 0.9, [[25,11,46],[105,121,88],[301,221,106]]); // 创建test2图层，且存在三个灯光效果，分别是中心(25,11)半径46，中心(105,121)半径88，中心(301,221)半径106。
        // core.plugin.drawLight('xxx', 0.3, [[25,11,46],[105,121,88,0.2]], 0.4); // 存在两个灯光效果，它们在内圈40%范围内保持全亮，40%后才开始衰减。
        this.drawLight = function (name, color, lights, lightDec) {
            // 清空色调层；也可以修改成其它层比如animate/weather层，或者用自己创建的canvas
            var ctx = core.getContextByName(name);
            if (ctx == null) {
                if (typeof name == 'string')
                    ctx = core.createCanvas(
                        name,
                        0,
                        0,
                        core._PX_ || core.__PIXELS__,
                        core._PY_ || core.__PIXELS__,
                        98
                    );
                else return;
            }

            ctx.mozImageSmoothingEnabled = false;
            ctx.webkitImageSmoothingEnabled = false;
            ctx.msImageSmoothingEnabled = false;
            ctx.imageSmoothingEnabled = false;

            core.clearMap(name);
            // 绘制色调层，默认不透明度
            if (color == null) color = 0.9;
            ctx.fillStyle = 'rgba(0,0,0,' + color + ')';
            ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

            lightDec = core.clamp(lightDec, 0, 1);

            // 绘制每个灯光效果
            ctx.globalCompositeOperation = 'destination-out';
            lights.forEach(function (light) {
                // 坐标，半径，中心不透明度
                var x = light[0],
                    y = light[1],
                    r = light[2];
                // 计算衰减距离
                var decDistance = parseInt(r * lightDec);
                // 正方形区域的直径和左上角坐标
                var grd = ctx.createRadialGradient(x, y, decDistance, x, y, r);
                grd.addColorStop(0, 'rgba(0,0,0,1)');
                grd.addColorStop(1, 'rgba(0,0,0,0)');
                ctx.beginPath();
                ctx.fillStyle = grd;
                ctx.arc(x, y, r, 0, 2 * Math.PI);
                ctx.fill();
            });
            ctx.globalCompositeOperation = 'source-over';
            // 可以在任何地方（如afterXXX或自定义脚本事件）调用函数，方法为  core.plugin.xxx();
        };
    },
    "shop": function () {
        // 【全局商店】相关的功能
        //
        // 打开一个全局商店
        // shopId：要打开的商店id；noRoute：是否不计入录像
        this.openShop = function (shopId, noRoute) {
            var shop = core.status.shops[shopId];
            // Step 1: 检查能否打开此商店
            if (!this.canOpenShop(shopId)) {
                core.drawTip('该商店尚未开启');
                return false;
            }

            // Step 2: （如有必要）记录打开商店的脚本事件
            if (!noRoute) {
                core.status.route.push('shop:' + shopId);
            }

            // Step 3: 检查道具商店 or 公共事件
            if (shop.item) {
                if (core.openItemShop) {
                    core.openItemShop(shopId);
                } else {
                    core.playSound('操作失败');
                    core.insertAction(
                        '道具商店插件不存在！请检查是否存在该插件！'
                    );
                }
                return;
            }
            if (shop.commonEvent) {
                core.insertCommonEvent(shop.commonEvent, shop.args);
                return;
            }

            _shouldProcessKeyUp = true;

            // Step 4: 执行标准公共商店
            core.insertAction(this._convertShop(shop));
            return true;
        };

        ////// 将一个全局商店转变成可预览的公共事件 //////
        this._convertShop = function (shop) {
            return [
                {
                    type: 'function',
                    function: "function() {core.addFlag('@temp@shop', 1);}"
                },
                {
                    type: 'while',
                    condition: 'true',
                    data: [
                        // 检测能否访问该商店
                        {
                            type: 'if',
                            condition: "core.isShopVisited('" + shop.id + "')",
                            true: [
                                // 可以访问，直接插入执行效果
                                {
                                    type: 'function',
                                    function:
                                        "function() { core.plugin._convertShop_replaceChoices('" +
                                        shop.id +
                                        "', false) }"
                                }
                            ],
                            false: [
                                // 不能访问的情况下：检测能否预览
                                {
                                    type: 'if',
                                    condition: shop.disablePreview,
                                    true: [
                                        // 不可预览，提示并退出
                                        { type: 'playSound', name: '操作失败' },
                                        '当前无法访问该商店！',
                                        { type: 'break' }
                                    ],
                                    false: [
                                        // 可以预览：将商店全部内容进行替换
                                        {
                                            type: 'tip',
                                            text: '当前处于预览模式，不可购买'
                                        },
                                        {
                                            type: 'function',
                                            function:
                                                "function() { core.plugin._convertShop_replaceChoices('" +
                                                shop.id +
                                                "', true) }"
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                },
                {
                    type: 'function',
                    function: "function() {core.addFlag('@temp@shop', -1);}"
                }
            ];
        };

        this._convertShop_replaceChoices = function (shopId, previewMode) {
            var shop = core.status.shops[shopId];
            var choices = (shop.choices || [])
                .filter(function (choice) {
                    if (choice.condition == null || choice.condition == '')
                        return true;
                    try {
                        return core.calValue(choice.condition);
                    } catch (e) {
                        return true;
                    }
                })
                .map(function (choice) {
                    var ableToBuy = core.calValue(choice.need);
                    return {
                        text: choice.text,
                        icon: choice.icon,
                        color:
                            ableToBuy && !previewMode
                                ? choice.color
                                : [153, 153, 153, 1],
                        action:
                            ableToBuy && !previewMode
                                ? [{ type: 'playSound', name: '商店' }].concat(
                                      choice.action
                                  )
                                : [
                                      { type: 'playSound', name: '操作失败' },
                                      {
                                          type: 'tip',
                                          text: previewMode
                                              ? '预览模式下不可购买'
                                              : '购买条件不足'
                                      }
                                  ]
                    };
                })
                .concat({
                    text: '离开',
                    action: [
                        { type: 'playSound', name: '取消' },
                        { type: 'break' }
                    ]
                });
            core.insertAction({
                type: 'choices',
                text: shop.text,
                choices: choices
            });
        };

        /// 是否访问过某个快捷商店
        this.isShopVisited = function (id) {
            if (!core.hasFlag('__shops__')) core.setFlag('__shops__', {});
            var shops = core.getFlag('__shops__');
            if (!shops[id]) shops[id] = {};
            return shops[id].visited;
        };

        /// 当前应当显示的快捷商店列表
        this.listShopIds = function () {
            return Object.keys(core.status.shops).filter(function (id) {
                return (
                    core.isShopVisited(id) || !core.status.shops[id].mustEnable
                );
            });
        };

        /// 是否能够打开某个商店
        this.canOpenShop = function (id) {
            if (this.isShopVisited(id)) return true;
            var shop = core.status.shops[id];
            if (shop.item || shop.commonEvent || shop.mustEnable) return false;
            return true;
        };

        /// 启用或禁用某个快捷商店
        this.setShopVisited = function (id, visited) {
            if (!core.hasFlag('__shops__')) core.setFlag('__shops__', {});
            var shops = core.getFlag('__shops__');
            if (!shops[id]) shops[id] = {};
            if (visited) shops[id].visited = true;
            else delete shops[id].visited;
        };

        /// 能否使用快捷商店
        this.canUseQuickShop = function (id) {
            // 如果返回一个字符串，表示不能，字符串为不能使用的提示
            // 返回null代表可以使用

            // 检查当前楼层的canUseQuickShop选项是否为false
            if (core.status.thisMap.canUseQuickShop === false)
                return '当前楼层不能使用快捷商店。';
            return null;
        };

        var _shouldProcessKeyUp = true;

        /// 允许商店X键退出
        core.registerAction(
            'keyUp',
            'shops',
            function (keycode) {
                if (
                    !core.status.lockControl ||
                    core.status.event.id != 'action'
                )
                    return false;
                if ((keycode == 13 || keycode == 32) && !_shouldProcessKeyUp) {
                    _shouldProcessKeyUp = true;
                    return true;
                }

                if (
                    !core.hasFlag('@temp@shop') ||
                    core.status.event.data.type != 'choices'
                )
                    return false;
                var data = core.status.event.data.current;
                var choices = data.choices;
                var topIndex = core.actions._getChoicesTopIndex(choices.length);
                if (keycode == 88 || keycode == 27) {
                    // X, ESC
                    core.actions._clickAction(
                        core._HALF_WIDTH_ || core.__HALF_SIZE__,
                        topIndex + choices.length - 1
                    );
                    return true;
                }
                return false;
            },
            60
        );

        /// 允许长按空格或回车连续执行操作
        core.registerAction(
            'keyDown',
            'shops',
            function (keycode) {
                if (
                    !core.status.lockControl ||
                    !core.hasFlag('@temp@shop') ||
                    core.status.event.id != 'action'
                )
                    return false;
                if (core.status.event.data.type != 'choices') return false;
                core.status.onShopLongDown = true;
                var data = core.status.event.data.current;
                var choices = data.choices;
                var topIndex = core.actions._getChoicesTopIndex(choices.length);
                if (keycode == 13 || keycode == 32) {
                    // Space, Enter
                    core.actions._clickAction(
                        core._HALF_WIDTH_ || core.__HALF_SIZE__,
                        topIndex + core.status.event.selection
                    );
                    _shouldProcessKeyUp = false;
                    return true;
                }
                return false;
            },
            60
        );

        // 允许长按屏幕连续执行操作
        core.registerAction(
            'longClick',
            'shops',
            function (x, y, px, py) {
                if (
                    !core.status.lockControl ||
                    !core.hasFlag('@temp@shop') ||
                    core.status.event.id != 'action'
                )
                    return false;
                if (core.status.event.data.type != 'choices') return false;
                var data = core.status.event.data.current;
                var choices = data.choices;
                var topIndex = core.actions._getChoicesTopIndex(choices.length);
                if (
                    Math.abs(x - (core._HALF_WIDTH_ || core.__HALF_SIZE__)) <=
                        2 &&
                    y >= topIndex &&
                    y < topIndex + choices.length
                ) {
                    core.actions._clickAction(x, y);
                    return true;
                }
                return false;
            },
            60
        );
    },
    "removeMap": function () {
        // 高层塔砍层插件，删除后不会存入存档，不可浏览地图也不可飞到。
        // 推荐用法：
        // 对于超高层或分区域塔，当在1区时将2区以后的地图删除；1区结束时恢复2区，进二区时删除1区地图，以此类推
        // 这样可以大幅减少存档空间，以及加快存读档速度

        // 删除楼层
        // core.removeMaps("MT1", "MT300") 删除MT1~MT300之间的全部层
        // core.removeMaps("MT10") 只删除MT10层
        this.removeMaps = function (fromId, toId) {
            toId = toId || fromId;
            var fromIndex = core.floorIds.indexOf(fromId),
                toIndex = core.floorIds.indexOf(toId);
            if (toIndex < 0) toIndex = core.floorIds.length - 1;
            flags.__visited__ = flags.__visited__ || {};
            flags.__removed__ = flags.__removed__ || [];
            flags.__disabled__ = flags.__disabled__ || {};
            flags.__leaveLoc__ = flags.__leaveLoc__ || {};
            for (var i = fromIndex; i <= toIndex; ++i) {
                var floorId = core.floorIds[i];
                if (core.status.maps[floorId].deleted) continue;
                delete flags.__visited__[floorId];
                flags.__removed__.push(floorId);
                delete flags.__disabled__[floorId];
                delete flags.__leaveLoc__[floorId];
                (core.status.autoEvents || []).forEach(function (event) {
                    if (event.floorId == floorId && event.currentFloor) {
                        core.autoEventExecuting(event.symbol, false);
                        core.autoEventExecuted(event.symbol, false);
                    }
                });
                core.status.maps[floorId].deleted = true;
                core.status.maps[floorId].canFlyTo = false;
                core.status.maps[floorId].canFlyFrom = false;
                core.status.maps[floorId].cannotViewMap = true;
            }
        };

        // 恢复楼层
        // core.resumeMaps("MT1", "MT300") 恢复MT1~MT300之间的全部层
        // core.resumeMaps("MT10") 只恢复MT10层
        this.resumeMaps = function (fromId, toId) {
            toId = toId || fromId;
            var fromIndex = core.floorIds.indexOf(fromId),
                toIndex = core.floorIds.indexOf(toId);
            if (toIndex < 0) toIndex = core.floorIds.length - 1;
            flags.__removed__ = flags.__removed__ || [];
            for (var i = fromIndex; i <= toIndex; ++i) {
                var floorId = core.floorIds[i];
                if (!core.status.maps[floorId].deleted) continue;
                flags.__removed__ = flags.__removed__.filter(function (f) {
                    return f != floorId;
                });
                core.status.maps[floorId] = core.loadFloor(floorId);
            }
        };

        // 分区砍层相关
        var inAnyPartition = function (floorId) {
            var inPartition = false;
            (core.floorPartitions || []).forEach(function (floor) {
                var fromIndex = core.floorIds.indexOf(floor[0]);
                var toIndex = core.floorIds.indexOf(floor[1]);
                var index = core.floorIds.indexOf(floorId);
                if (fromIndex < 0 || index < 0) return;
                if (toIndex < 0) toIndex = core.floorIds.length - 1;
                if (index >= fromIndex && index <= toIndex) inPartition = true;
            });
            return inPartition;
        };

        // 分区砍层
        this.autoRemoveMaps = function (floorId) {
            if (main.mode != 'play' || !inAnyPartition(floorId)) return;
            // 根据分区信息自动砍层与恢复
            (core.floorPartitions || []).forEach(function (floor) {
                var fromIndex = core.floorIds.indexOf(floor[0]);
                var toIndex = core.floorIds.indexOf(floor[1]);
                var index = core.floorIds.indexOf(floorId);
                if (fromIndex < 0 || index < 0) return;
                if (toIndex < 0) toIndex = core.floorIds.length - 1;
                if (index >= fromIndex && index <= toIndex) {
                    core.resumeMaps(
                        core.floorIds[fromIndex],
                        core.floorIds[toIndex]
                    );
                } else {
                    core.removeMaps(
                        core.floorIds[fromIndex],
                        core.floorIds[toIndex]
                    );
                }
            });
        };
    },
    "fiveLayers": function () {
        // 是否启用五图层（增加背景2层和前景2层） 将__enable置为true即会启用；启用后请保存后刷新编辑器
        // 背景层2将会覆盖背景层 被事件层覆盖 前景层2将会覆盖前景层
        // 另外 请注意加入两个新图层 会让大地图的性能降低一些
        // 插件作者：ad
        var __enable = false;
        if (!__enable) return;

        // 创建新图层
        function createCanvas(name, zIndex) {
            if (!name) return;
            var canvas = document.createElement('canvas');
            canvas.id = name;
            canvas.className = 'gameCanvas';
            // 编辑器模式下设置zIndex会导致加入的图层覆盖优先级过高
            if (main.mode != 'editor') canvas.style.zIndex = zIndex || 0;
            // 将图层插入进游戏内容
            document.getElementById('gameDraw').appendChild(canvas);
            var ctx = canvas.getContext('2d');
            core.canvas[name] = ctx;
            canvas.width = core._PX_ || core.__PIXELS__;
            canvas.height = core._PY_ || core.__PIXELS__;
            return canvas;
        }

        var bg2Canvas = createCanvas('bg2', 20);
        var fg2Canvas = createCanvas('fg2', 63);
        // 大地图适配
        core.bigmap.canvas = [
            'bg2',
            'fg2',
            'bg',
            'event',
            'event2',
            'fg',
            'damage'
        ];
        core.initStatus.bg2maps = {};
        core.initStatus.fg2maps = {};

        if (main.mode == 'editor') {
            /*插入编辑器的图层 不做此步新增图层无法在编辑器显示*/
            // 编辑器图层覆盖优先级 eui > efg > fg(前景层) > event2(48*32图块的事件层) > event(事件层) > bg(背景层)
            // 背景层2(bg2) 插入事件层(event)之前(即bg与event之间)
            document
                .getElementById('mapEdit')
                .insertBefore(bg2Canvas, document.getElementById('event'));
            // 前景层2(fg2) 插入编辑器前景(efg)之前(即fg之后)
            document
                .getElementById('mapEdit')
                .insertBefore(fg2Canvas, document.getElementById('ebm'));
            // 原本有三个图层 从4开始添加
            var num = 4;
            // 新增图层存入editor.dom中
            editor.dom.bg2c = core.canvas.bg2.canvas;
            editor.dom.bg2Ctx = core.canvas.bg2;
            editor.dom.fg2c = core.canvas.fg2.canvas;
            editor.dom.fg2Ctx = core.canvas.fg2;
            editor.dom.maps.push('bg2map', 'fg2map');
            editor.dom.canvas.push('bg2', 'fg2');

            // 创建编辑器上的按钮
            var createCanvasBtn = function (name) {
                // 电脑端创建按钮
                var input = document.createElement('input');
                // layerMod4/layerMod5
                var id = 'layerMod' + num++;
                // bg2map/fg2map
                var value = name + 'map';
                input.type = 'radio';
                input.name = 'layerMod';
                input.id = id;
                input.value = value;
                editor.dom[id] = input;
                input.onchange = function () {
                    editor.uifunctions.setLayerMod(value);
                };
                return input;
            };

            var createCanvasBtn_mobile = function (name) {
                // 手机端往选择列表中添加子选项
                var input = document.createElement('option');
                var id = 'layerMod' + num++;
                var value = name + 'map';
                input.name = 'layerMod';
                input.value = value;
                editor.dom[id] = input;
                return input;
            };
            if (!editor.isMobile) {
                var input = createCanvasBtn('bg2');
                var input2 = createCanvasBtn('fg2');
                // 获取事件层及其父节点
                var child = document.getElementById('layerMod'),
                    parent = child.parentNode;
                // 背景层2插入事件层前
                parent.insertBefore(input, child);
                // 不能直接更改背景层2的innerText 所以创建文本节点
                var txt = document.createTextNode('bg2');
                // 插入事件层前(即新插入的背景层2前)
                parent.insertBefore(txt, child);
                // 向最后插入前景层2(即插入前景层后)
                parent.appendChild(input2);
                var txt2 = document.createTextNode('fg2');
                parent.appendChild(txt2);
                parent.childNodes[2].replaceWith('bg');
                parent.childNodes[6].replaceWith('事件');
                parent.childNodes[8].replaceWith('fg');
            } else {
                var input = createCanvasBtn_mobile('bg2');
                var input2 = createCanvasBtn_mobile('fg2');
                // 手机端因为是选项 所以可以直接改innerText
                input.innerText = '背景层2';
                input2.innerText = '前景层2';
                var parent = document.getElementById('layerMod');
                parent.insertBefore(input, parent.children[1]);
                parent.appendChild(input2);
            }
        }

        var _loadFloor_doNotCopy = core.maps._loadFloor_doNotCopy;
        core.maps._loadFloor_doNotCopy = function () {
            return ['bg2map', 'fg2map'].concat(_loadFloor_doNotCopy());
        };
        ////// 绘制背景和前景层 //////
        core.maps._drawBg_draw = function (
            floorId,
            toDrawCtx,
            cacheCtx,
            config
        ) {
            config.ctx = cacheCtx;
            core.maps._drawBg_drawBackground(floorId, config);
            // ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块；后绘制的覆盖先绘制的。
            core.maps._drawFloorImages(
                floorId,
                config.ctx,
                'bg',
                null,
                null,
                config.onMap
            );
            core.maps._drawBgFgMap(floorId, 'bg', config);
            if (config.onMap) {
                core.drawImage(
                    toDrawCtx,
                    cacheCtx.canvas,
                    core.bigmap.v2 ? -32 : 0,
                    core.bigmap.v2 ? -32 : 0
                );
                core.clearMap('bg2');
                core.clearMap(cacheCtx);
            }
            core.maps._drawBgFgMap(floorId, 'bg2', config);
            if (config.onMap)
                core.drawImage(
                    'bg2',
                    cacheCtx.canvas,
                    core.bigmap.v2 ? -32 : 0,
                    core.bigmap.v2 ? -32 : 0
                );
            config.ctx = toDrawCtx;
        };
        core.maps._drawFg_draw = function (
            floorId,
            toDrawCtx,
            cacheCtx,
            config
        ) {
            config.ctx = cacheCtx;
            // ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制前景图块；后绘制的覆盖先绘制的。
            core.maps._drawFloorImages(
                floorId,
                config.ctx,
                'fg',
                null,
                null,
                config.onMap
            );
            core.maps._drawBgFgMap(floorId, 'fg', config);
            if (config.onMap) {
                core.drawImage(
                    toDrawCtx,
                    cacheCtx.canvas,
                    core.bigmap.v2 ? -32 : 0,
                    core.bigmap.v2 ? -32 : 0
                );
                core.clearMap('fg2');
                core.clearMap(cacheCtx);
            }
            core.maps._drawBgFgMap(floorId, 'fg2', config);
            if (config.onMap)
                core.drawImage(
                    'fg2',
                    cacheCtx.canvas,
                    core.bigmap.v2 ? -32 : 0,
                    core.bigmap.v2 ? -32 : 0
                );
            config.ctx = toDrawCtx;
        };
        ////// 移动判定 //////
        core.maps._generateMovableArray_arrays = function (floorId) {
            return {
                bgArray: this.getBgMapArray(floorId),
                fgArray: this.getFgMapArray(floorId),
                eventArray: this.getMapArray(floorId),
                bg2Array: this._getBgFgMapArray('bg2', floorId),
                fg2Array: this._getBgFgMapArray('fg2', floorId)
            };
        };
    },
    "itemShop": function () {
        // 道具商店相关的插件
        // 可在全塔属性-全局商店中使用「道具商店」事件块进行编辑（如果找不到可以在入口方块中找）

        var shopId = null; // 当前商店ID
        var type = 0; // 当前正在选中的类型，0买入1卖出
        var selectItem = 0; // 当前正在选中的道具
        var selectCount = 0; // 当前已经选中的数量
        var page = 0;
        var totalPage = 0;
        var totalMoney = 0;
        var list = [];
        var shopInfo = null; // 商店信息
        var choices = []; // 商店选项
        var use = 'money';
        var useText = '金币';

        var bigFont = core.ui._buildFont(20, false),
            middleFont = core.ui._buildFont(18, false);

        this._drawItemShop = function () {
            // 绘制道具商店

            // Step 1: 背景和固定的几个文字
            core.ui._createUIEvent();
            core.clearMap('uievent');
            core.ui.clearUIEventSelector();
            core.setTextAlign('uievent', 'left');
            core.setTextBaseline('uievent', 'top');
            core.fillRect('uievent', 0, 0, 416, 416, 'black');
            core.drawWindowSkin('winskin.png', 'uievent', 0, 0, 416, 56);
            core.drawWindowSkin('winskin.png', 'uievent', 0, 56, 312, 56);
            core.drawWindowSkin('winskin.png', 'uievent', 0, 112, 312, 304);
            core.drawWindowSkin('winskin.png', 'uievent', 312, 56, 104, 56);
            core.drawWindowSkin('winskin.png', 'uievent', 312, 112, 104, 304);
            core.setFillStyle('uievent', 'white');
            core.setStrokeStyle('uievent', 'white');
            core.fillText('uievent', '购买', 32, 74, 'white', bigFont);
            core.fillText('uievent', '卖出', 132, 74);
            core.fillText('uievent', '离开', 232, 74);
            core.fillText(
                'uievent',
                '当前' + useText,
                324,
                66,
                null,
                middleFont
            );
            core.setTextAlign('uievent', 'right');
            core.fillText(
                'uievent',
                core.formatBigNumber(core.status.hero[use]),
                405,
                89
            );
            core.setTextAlign('uievent', 'left');
            core.ui.drawUIEventSelector(
                1,
                'winskin.png',
                22 + 100 * type,
                66,
                60,
                33
            );
            if (selectItem != null) {
                core.setTextAlign('uievent', 'center');
                core.fillText(
                    'uievent',
                    type == 0 ? '买入个数' : '卖出个数',
                    364,
                    320,
                    null,
                    bigFont
                );
                core.fillText(
                    'uievent',
                    '<   ' + selectCount + '   >',
                    364,
                    350
                );
                core.fillText('uievent', '确定', 364, 380);
            }

            // Step 2：获得列表并展示
            list = choices.filter(function (one) {
                if (one.condition != null && one.condition != '') {
                    try {
                        if (!core.calValue(one.condition)) return false;
                    } catch (e) {}
                }
                return (
                    (type == 0 && one.money != null) ||
                    (type == 1 && one.sell != null)
                );
            });
            var per_page = 6;
            totalPage = Math.ceil(list.length / per_page);
            page = Math.floor((selectItem || 0) / per_page) + 1;

            // 绘制分页
            if (totalPage > 1) {
                var half = 156;
                core.setTextAlign('uievent', 'center');
                core.fillText(
                    'uievent',
                    page + ' / ' + totalPage,
                    half,
                    388,
                    null,
                    middleFont
                );
                if (page > 1)
                    core.fillText('uievent', '上一页', half - 80, 388);
                if (page < totalPage)
                    core.fillText('uievent', '下一页', half + 80, 388);
            }
            core.setTextAlign('uievent', 'left');

            // 绘制每一项
            var start = (page - 1) * per_page;
            for (var i = 0; i < per_page; ++i) {
                var curr = start + i;
                if (curr >= list.length) break;
                var item = list[curr];
                core.drawIcon('uievent', item.id, 10, 125 + i * 40);
                core.setTextAlign('uievent', 'left');
                core.fillText(
                    'uievent',
                    core.material.items[item.id].name,
                    50,
                    132 + i * 40,
                    null,
                    bigFont
                );
                core.setTextAlign('uievent', 'right');
                core.fillText(
                    'uievent',
                    (type == 0
                        ? core.calValue(item.money)
                        : core.calValue(item.sell)) +
                        useText +
                        '/个',
                    300,
                    133 + i * 40,
                    null,
                    middleFont
                );
                core.setTextAlign('uievent', 'left');
                if (curr == selectItem) {
                    // 绘制描述，文字自动放缩
                    var text =
                        core.material.items[item.id].text || '该道具暂无描述';
                    try {
                        text = core.replaceText(text);
                    } catch (e) {}
                    for (var fontSize = 20; fontSize >= 8; fontSize -= 2) {
                        var config = {
                            left: 10,
                            fontSize: fontSize,
                            maxWidth: 403
                        };
                        var height = core.getTextContentHeight(text, config);
                        if (height <= 50) {
                            config.top = (56 - height) / 2;
                            core.drawTextContent('uievent', text, config);
                            break;
                        }
                    }
                    core.ui.drawUIEventSelector(
                        2,
                        'winskin.png',
                        8,
                        120 + i * 40,
                        295,
                        40
                    );
                    if (type == 0 && item.number != null) {
                        core.fillText(
                            'uievent',
                            '存货',
                            324,
                            132,
                            null,
                            bigFont
                        );
                        core.setTextAlign('uievent', 'right');
                        core.fillText(
                            'uievent',
                            item.number,
                            406,
                            132,
                            null,
                            null,
                            40
                        );
                    } else if (type == 1) {
                        core.fillText(
                            'uievent',
                            '数量',
                            324,
                            132,
                            null,
                            bigFont
                        );
                        core.setTextAlign('uievent', 'right');
                        core.fillText(
                            'uievent',
                            core.itemCount(item.id),
                            406,
                            132,
                            null,
                            null,
                            40
                        );
                    }
                    core.setTextAlign('uievent', 'left');
                    core.fillText('uievent', '预计' + useText, 324, 250);
                    core.setTextAlign('uievent', 'right');
                    totalMoney =
                        selectCount *
                        (type == 0
                            ? core.calValue(item.money)
                            : core.calValue(item.sell));
                    core.fillText(
                        'uievent',
                        core.formatBigNumber(totalMoney),
                        405,
                        280
                    );

                    core.setTextAlign('uievent', 'left');
                    core.fillText(
                        'uievent',
                        type == 0 ? '已购次数' : '已卖次数',
                        324,
                        170
                    );
                    core.setTextAlign('uievent', 'right');
                    core.fillText(
                        'uievent',
                        (type == 0 ? item.money_count : item.sell_count) || 0,
                        405,
                        200
                    );
                }
            }

            core.setTextAlign('uievent', 'left');
            core.setTextBaseline('uievent', 'alphabetic');
        };

        var _add = function (item, delta) {
            if (item == null) return;
            selectCount = core.clamp(
                selectCount + delta,
                0,
                Math.min(
                    type == 0
                        ? Math.floor(
                              core.status.hero[use] / core.calValue(item.money)
                          )
                        : core.itemCount(item.id),
                    type == 0 && item.number != null
                        ? item.number
                        : Number.MAX_SAFE_INTEGER
                )
            );
        };

        var _confirm = function (item) {
            if (item == null || selectCount == 0) return;
            if (type == 0) {
                core.status.hero[use] -= totalMoney;
                core.getItem(item.id, selectCount);
                core.stopSound();
                core.playSound('确定');
                if (item.number != null) item.number -= selectCount;
                item.money_count = (item.money_count || 0) + selectCount;
            } else {
                core.status.hero[use] += totalMoney;
                core.removeItem(item.id, selectCount);
                core.playSound('确定');
                core.drawTip(
                    '成功卖出' +
                        selectCount +
                        '个' +
                        core.material.items[item.id].name,
                    item.id
                );
                if (item.number != null) item.number += selectCount;
                item.sell_count = (item.sell_count || 0) + selectCount;
            }
            selectCount = 0;
        };

        this._performItemShopKeyBoard = function (keycode) {
            var item = list[selectItem] || null;
            // 键盘操作
            switch (keycode) {
                case 38: // up
                    if (selectItem == null) break;
                    if (selectItem == 0) selectItem = null;
                    else selectItem--;
                    selectCount = 0;
                    break;
                case 37: // left
                    if (selectItem == null) {
                        if (type > 0) type--;
                        break;
                    }
                    _add(item, -1);
                    break;
                case 39: // right
                    if (selectItem == null) {
                        if (type < 2) type++;
                        break;
                    }
                    _add(item, 1);
                    break;
                case 40: // down
                    if (selectItem == null) {
                        if (list.length > 0) selectItem = 0;
                        break;
                    }
                    if (list.length == 0) break;
                    selectItem = Math.min(selectItem + 1, list.length - 1);
                    selectCount = 0;
                    break;
                case 13:
                case 32: // Enter/Space
                    if (selectItem == null) {
                        if (type == 2) core.insertAction({ type: 'break' });
                        else if (list.length > 0) selectItem = 0;
                        break;
                    }
                    _confirm(item);
                    break;
                case 27: // ESC
                    if (selectItem == null) {
                        core.insertAction({ type: 'break' });
                        break;
                    }
                    selectItem = null;
                    break;
            }
        };

        this._performItemShopClick = function (px, py) {
            var item = list[selectItem] || null;
            // 鼠标操作
            if (px >= 22 && px <= 82 && py >= 71 && py <= 102) {
                // 买
                if (type != 0) {
                    type = 0;
                    selectItem = null;
                    selectCount = 0;
                }
                return;
            }
            if (px >= 122 && px <= 182 && py >= 71 && py <= 102) {
                // 卖
                if (type != 1) {
                    type = 1;
                    selectItem = null;
                    selectCount = 0;
                }
                return;
            }
            if (px >= 222 && px <= 282 && py >= 71 && py <= 102)
                // 离开
                return core.insertAction({ type: 'break' });
            // < >
            if (px >= 318 && px <= 341 && py >= 348 && py <= 376)
                return _add(item, -1);
            if (px >= 388 && px <= 416 && py >= 348 && py <= 376)
                return _add(item, 1);
            // 确定
            if (px >= 341 && px <= 387 && py >= 380 && py <= 407)
                return _confirm(item);

            // 上一页/下一页
            if (px >= 45 && px <= 105 && py >= 388) {
                if (page > 1) {
                    selectItem -= 6;
                    selectCount = 0;
                }
                return;
            }
            if (px >= 208 && px <= 268 && py >= 388) {
                if (page < totalPage) {
                    selectItem = Math.min(selectItem + 6, list.length - 1);
                    selectCount = 0;
                }
                return;
            }

            // 实际区域
            if (px >= 9 && px <= 300 && py >= 120 && py < 360) {
                if (list.length == 0) return;
                var index = parseInt((py - 120) / 40);
                var newItem = 6 * (page - 1) + index;
                if (newItem >= list.length) newItem = list.length - 1;
                if (newItem != selectItem) {
                    selectItem = newItem;
                    selectCount = 0;
                }
                return;
            }
        };

        this._performItemShopAction = function () {
            if (flags.type == 0)
                return this._performItemShopKeyBoard(flags.keycode);
            else return this._performItemShopClick(flags.px, flags.py);
        };

        this.openItemShop = function (itemShopId) {
            shopId = itemShopId;
            type = 0;
            page = 0;
            selectItem = null;
            selectCount = 0;
            core.isShopVisited(itemShopId);
            shopInfo = flags.__shops__[shopId];
            if (shopInfo.choices == null)
                shopInfo.choices = core.clone(
                    core.status.shops[shopId].choices
                );
            choices = shopInfo.choices;
            use = core.status.shops[shopId].use;
            if (use != 'exp') use = 'money';
            useText = use == 'money' ? '金币' : '经验';

            core.insertAction([
                {
                    type: 'while',
                    condition: 'true',
                    data: [
                        {
                            type: 'function',
                            function:
                                'function () { core.plugin._drawItemShop(); }'
                        },
                        { type: 'wait' },
                        {
                            type: 'function',
                            function:
                                'function() { core.plugin._performItemShopAction(); }'
                        }
                    ]
                },
                {
                    type: 'function',
                    function:
                        "function () { core.deleteCanvas('uievent'); core.ui.clearUIEventSelector(); }"
                }
            ]);
        };
    },
    "enemyLevel": function () {
        // 此插件将提供怪物手册中的怪物境界显示
        // 使用此插件需要先给每个怪物定义境界，方法如下：
        // 点击怪物的【配置表格】，找到“【怪物】相关的表格配置”，然后在【名称】仿照增加境界定义：
        /*
		 "level": {
			  "_leaf": true,
			  "_type": "textarea",
			  "_string": true,
			  "_data": "境界"
		 },
		 */
        // 然后保存刷新，可以看到怪物的属性定义中出现了【境界】。再开启本插件即可。

        // 是否开启本插件，默认禁用；将此改成 true 将启用本插件。
        var __enable = false;
        if (!__enable) return;

        // 这里定义每个境界的显示颜色；可以写'red', '#RRGGBB' 或者[r,g,b,a]四元数组
        var levelToColors = {
            萌新一阶: 'red',
            萌新二阶: '#FF0000',
            萌新三阶: [255, 0, 0, 1]
        };

        // 复写 _drawBook_drawName
        var originDrawBook = core.ui._drawBook_drawName;
        core.ui._drawBook_drawName = function (index, enemy, top, left, width) {
            // 如果没有境界，则直接调用原始代码绘制
            if (!enemy.level)
                return originDrawBook.call(
                    core.ui,
                    index,
                    enemy,
                    top,
                    left,
                    width
                );
            // 存在境界，则额外进行绘制
            core.setTextAlign('ui', 'center');
            if (enemy.specialText.length == 0) {
                core.fillText(
                    'ui',
                    enemy.name,
                    left + width / 2,
                    top + 27,
                    '#DDDDDD',
                    this._buildFont(17, true)
                );
                core.fillText(
                    'ui',
                    enemy.level,
                    left + width / 2,
                    top + 51,
                    core.arrayToRGBA(levelToColors[enemy.level] || '#DDDDDD'),
                    this._buildFont(14, true)
                );
            } else {
                core.fillText(
                    'ui',
                    enemy.name,
                    left + width / 2,
                    top + 20,
                    '#DDDDDD',
                    this._buildFont(17, true),
                    width
                );
                switch (enemy.specialText.length) {
                    case 1:
                        core.fillText(
                            'ui',
                            enemy.specialText[0],
                            left + width / 2,
                            top + 38,
                            core.arrayToRGBA(
                                (enemy.specialColor || [])[0] || '#FF6A6A'
                            ),
                            this._buildFont(14, true),
                            width
                        );
                        break;
                    case 2:
                        // Step 1: 计算字体
                        var text =
                            enemy.specialText[0] + '  ' + enemy.specialText[1];
                        core.setFontForMaxWidth(
                            'ui',
                            text,
                            width,
                            this._buildFont(14, true)
                        );
                        // Step 2: 计算总宽度
                        var totalWidth = core.calWidth('ui', text);
                        var leftWidth = core.calWidth(
                            'ui',
                            enemy.specialText[0]
                        );
                        var rightWidth = core.calWidth(
                            'ui',
                            enemy.specialText[1]
                        );
                        // Step 3: 绘制
                        core.fillText(
                            'ui',
                            enemy.specialText[0],
                            left + (width + leftWidth - totalWidth) / 2,
                            top + 38,
                            core.arrayToRGBA(
                                (enemy.specialColor || [])[0] || '#FF6A6A'
                            )
                        );
                        core.fillText(
                            'ui',
                            enemy.specialText[1],
                            left + (width + totalWidth - rightWidth) / 2,
                            top + 38,
                            core.arrayToRGBA(
                                (enemy.specialColor || [])[1] || '#FF6A6A'
                            )
                        );
                        break;
                    default:
                        core.fillText(
                            'ui',
                            '多属性...',
                            left + width / 2,
                            top + 38,
                            '#FF6A6A',
                            this._buildFont(14, true),
                            width
                        );
                }
                core.fillText(
                    'ui',
                    enemy.level,
                    left + width / 2,
                    top + 56,
                    core.arrayToRGBA(levelToColors[enemy.level] || '#DDDDDD'),
                    this._buildFont(14, true)
                );
            }
        };

        // 也可以复写其他的属性颜色如怪物攻防等，具体参见下面的例子的注释部分
        core.ui._drawBook_drawRow1 = function (
            index,
            enemy,
            top,
            left,
            width,
            position
        ) {
            // 绘制第一行
            core.setTextAlign('ui', 'left');
            var b13 = this._buildFont(13, true),
                f13 = this._buildFont(13, false);
            var col1 = left,
                col2 = left + (width * 9) / 25,
                col3 = left + (width * 17) / 25;
            core.fillText('ui', '生命', col1, position, '#DDDDDD', f13);
            core.fillText(
                'ui',
                core.formatBigNumber(enemy.hp || 0),
                col1 + 30,
                position,
                /*'red' */ null,
                b13
            );
            core.fillText('ui', '攻击', col2, position, null, f13);
            core.fillText(
                'ui',
                core.formatBigNumber(enemy.atk || 0),
                col2 + 30,
                position,
                /* '#FF0000' */ null,
                b13
            );
            core.fillText('ui', '防御', col3, position, null, f13);
            core.fillText(
                'ui',
                core.formatBigNumber(enemy.def || 0),
                col3 + 30,
                position,
                /* [255, 0, 0, 1] */ null,
                b13
            );
        };
    },
    "multiHeros": function () {
        // 多角色插件
        // Step 1: 启用本插件
        // Step 2: 定义每个新的角色各项初始数据（参见下方注释）
        // Step 3: 在游戏中的任何地方都可以调用 `core.changeHero()` 进行切换；也可以 `core.changeHero(1)` 来切换到某个具体的角色上

        // 是否开启本插件，默认禁用；将此改成 true 将启用本插件。
        var __enable = false;
        if (!__enable) return;

        // 在这里定义全部的新角色属性
        // 请注意，在这里定义的内容不会多角色共用，在切换时会进行恢复。
        // 你也可以自行新增或删除，比如不共用金币则可以加上"money"的初始化，不共用道具则可以加上"items"的初始化，
        // 多角色共用hp的话则删除hp，等等。总之，不共用的属性都在这里进行定义就好。
        var hero1 = {
            floorId: 'MT0', // 该角色初始楼层ID；如果共用楼层可以注释此项
            image: 'brave.png', // 角色的行走图名称；此项必填不然会报错
            name: '1号角色',
            lv: 1,
            hp: 10000, // 如果HP共用可注释此项
            atk: 1000,
            def: 1000,
            mdef: 0,
            // "money": 0, // 如果要不共用金币则取消此项注释
            // "exp": 0, // 如果要不共用经验则取消此项注释
            loc: { x: 0, y: 0, direction: 'up' }, // 该角色初始位置；如果共用位置可注释此项
            items: {
                tools: {}, // 如果共用消耗道具（含钥匙）则可注释此项
                // "constants": {}, // 如果不共用永久道具（如手册）可取消注释此项
                equips: {} // 如果共用在背包的装备可注释此项
            },
            equipment: [] // 如果共用装备可注释此项；此项和上面的「共用在背包的装备」需要拥有相同状态，不然可能出现问题
        };
        // 也可以类似新增其他角色
        // 新增的角色，各项属性共用与不共用的选择必须和上面完全相同，否则可能出现问题。
        // var hero2 = { ...

        var heroCount = 2; // 包含默认角色在内总共多少个角色，该值需手动修改。

        this.initHeros = function () {
            core.setFlag('hero1', core.clone(hero1)); // 将属性值存到变量中
            // core.setFlag("hero2", core.clone(hero2)); // 更多的角色也存入变量中；每个定义的角色都需要新增一行

            // 检测是否存在装备
            if (hero1.equipment) {
                if (!hero1.items || !hero1.items.equips) {
                    alert(
                        '多角色插件的equipment和道具中的equips必须拥有相同状态！'
                    );
                }
                // 存99号套装为全空
                var saveEquips = core.getFlag('saveEquips', []);
                saveEquips[99] = [];
                core.setFlag('saveEquips', saveEquips);
            } else {
                if (hero1.items && hero1.items.equips) {
                    alert(
                        '多角色插件的equipment和道具中的equips必须拥有相同状态！'
                    );
                }
            }
        };

        // 在游戏开始注入initHeros
        var _startGame_setHard = core.events._startGame_setHard;
        core.events._startGame_setHard = function () {
            _startGame_setHard.call(core.events);
            core.initHeros();
        };

        // 切换角色
        // 可以使用 core.changeHero() 来切换到下一个角色
        // 也可以 core.changeHero(1) 来切换到某个角色（默认角色为0）
        this.changeHero = function (toHeroId) {
            var currHeroId = core.getFlag('heroId', 0); // 获得当前角色ID
            if (toHeroId == null) {
                toHeroId = (currHeroId + 1) % heroCount;
            }
            if (currHeroId == toHeroId) return;

            var saveList = Object.keys(hero1);

            // 保存当前内容
            var toSave = {};
            // 暂时干掉 drawTip 和 音效，避免切装时的提示
            var _drawTip = core.ui.drawTip;
            core.ui.drawTip = function () {};
            var _playSound = core.control.playSound;
            core.control.playSound = function () {};
            // 记录当前录像，因为可能存在换装问题
            core.clearRouteFolding();
            var routeLength = core.status.route.length;
            // 优先判定装备
            if (hero1.equipment) {
                core.items.quickSaveEquip(100 + currHeroId);
                core.items.quickLoadEquip(99);
            }

            saveList.forEach(function (name) {
                if (name == 'floorId')
                    toSave[name] = core.status.floorId; // 楼层单独设置
                else if (name == 'items') {
                    toSave.items = core.clone(core.status.hero.items);
                    Object.keys(toSave.items).forEach(function (one) {
                        if (!hero1.items[one]) delete toSave.items[one];
                    });
                } else toSave[name] = core.clone(core.status.hero[name]); // 使用core.clone()来创建新对象
            });

            core.setFlag('hero' + currHeroId, toSave); // 将当前角色信息进行保存
            var data = core.getFlag('hero' + toHeroId); // 获得要切换的角色保存内容

            // 设置角色的属性值
            saveList.forEach(function (name) {
                if (name == 'floorId');
                else if (name == 'items') {
                    Object.keys(core.status.hero.items).forEach(function (one) {
                        if (data.items[one])
                            core.status.hero.items[one] = core.clone(
                                data.items[one]
                            );
                    });
                } else {
                    core.status.hero[name] = core.clone(data[name]);
                }
            });
            // 最后装上装备
            if (hero1.equipment) {
                core.items.quickLoadEquip(100 + toHeroId);
            }

            core.ui.drawTip = _drawTip;
            core.control.playSound = _playSound;
            core.status.route = core.status.route.slice(0, routeLength);
            core.control._bindRoutePush();

            // 插入事件：改变角色行走图并进行楼层切换
            var toFloorId = data.floorId || core.status.floorId;
            var toLoc = data.loc || core.status.hero.loc;
            core.insertAction([
                { type: 'setHeroIcon', name: data.image || 'hero.png' }, // 改变行走图
                // 同层则用changePos，不同层则用changeFloor；这是为了避免共用楼层造成触发eachArrive
                toFloorId != core.status.floorId
                    ? {
                          type: 'changeFloor',
                          floorId: toFloorId,
                          loc: [toLoc.x, toLoc.y],
                          direction: toLoc.direction,
                          time: 0 // 可以在这里设置切换时间
                      }
                    : {
                          type: 'changePos',
                          loc: [toLoc.x, toLoc.y],
                          direction: toLoc.direction
                      }
                // 你还可以在这里执行其他事件，比如增加或取消跟随效果
            ]);
            core.setFlag('heroId', toHeroId); // 保存切换到的角色ID
        };
    },
    "heroFourFrames": function () {
        // 样板的勇士/跟随者移动时只使用2、4两帧，观感较差。本插件可以将四帧全用上。

        // 是否启用本插件
        var __enable = true;
        if (!__enable) return;

        ['up', 'down', 'left', 'right'].forEach(function (one) {
            // 指定中间帧动画
            core.material.icons.hero[one].midFoot = 2;
        });

        var heroMoving = function (timestamp) {
            if (core.status.heroMoving <= 0) return;
            if (
                timestamp - core.animateFrame.moveTime >
                core.values.moveSpeed
            ) {
                core.animateFrame.leftLeg++;
                core.animateFrame.moveTime = timestamp;
            }
            core.drawHero(
                ['stop', 'leftFoot', 'midFoot', 'rightFoot'][
                    core.animateFrame.leftLeg % 4
                ],
                4 * core.status.heroMoving
            );
        };
        core.registerAnimationFrame('heroMoving', true, heroMoving);

        core.events._eventMoveHero_moving = function (step, moveSteps) {
            var curr = moveSteps[0];
            var direction = curr[0],
                x = core.getHeroLoc('x'),
                y = core.getHeroLoc('y');
            // ------ 前进/后退
            var o = direction == 'backward' ? -1 : 1;
            if (direction == 'forward' || direction == 'backward')
                direction = core.getHeroLoc('direction');
            var faceDirection = direction;
            if (direction == 'leftup' || direction == 'leftdown')
                faceDirection = 'left';
            if (direction == 'rightup' || direction == 'rightdown')
                faceDirection = 'right';
            core.setHeroLoc('direction', direction);
            if (curr[1] <= 0) {
                core.setHeroLoc('direction', faceDirection);
                moveSteps.shift();
                return true;
            }
            if (step <= 4) core.drawHero('stop', 4 * o * step);
            else if (step <= 8) core.drawHero('leftFoot', 4 * o * step);
            else if (step <= 12) core.drawHero('midFoot', 4 * o * (step - 8));
            else if (step <= 16) core.drawHero('rightFoot', 4 * o * (step - 8)); // if (step == 8) {
            if (step == 8 || step == 16) {
                core.setHeroLoc(
                    'x',
                    x + o * core.utils.scan2[direction].x,
                    true
                );
                core.setHeroLoc(
                    'y',
                    y + o * core.utils.scan2[direction].y,
                    true
                );
                core.updateFollowers();
                curr[1]--;
                if (curr[1] <= 0) moveSteps.shift();
                core.setHeroLoc('direction', faceDirection);
                return step == 16;
            }
            return false;
        };
    },
    "routeFixing": function () {
        // 是否开启本插件，true 表示启用，false 表示禁用。
        var __enable = true;
        if (!__enable) return;
        /*
		 使用说明：启用本插件后，录像回放时您可以用数字键1或6分别切换到原速或24倍速，
		 暂停播放时按数字键7（电脑按N）可以单步播放。（手机端可以点击难度单词切换出数字键）
		 数字键2-5可以进行录像自助精修，具体描述见下（实际弹窗请求您输入时不要带有任何空格）：
		 
		 up down left right 勇士向某个方向「行走一步或撞击」
		 item:ID 使用某件道具，如 item:bomb 表示使用炸弹
		 unEquip:n 卸掉身上第(n+1)件装备（n从0开始），如 unEquip:1 默认表示卸掉盾牌
		 equip:ID 穿上某件装备，如 equip:sword1 表示装上铁剑
		 saveEquip:n 将身上的当前套装保存到第n套快捷套装（n从0开始）
		 loadEquip:n 快捷换上之前保存好的第n套套装
		 fly:ID 使用楼传飞到某一层，如 fly:MT10 表示飞到主塔10层
		 choices:none 确认框/选择项「超时」（作者未设置超时时间则此项视为缺失）
		 choices:n 确认框/选择项选择第(n+1)项（选择项n从0开始，确认框n为0表示「确定」，1表示「取消」）
		 选择项n为负数时表示选择倒数第 -n 项，如 -1 表示最后一项（V2.8.2起标准全局商店的「离开」项）
		 此项缺失的话，确认框将选择作者指定的默认项（初始光标位置），选择项将弹窗请求补选（后台录像验证中选最后一项，可以复写函数来修改）
		 shop:ID 打开某个全局商店，如 shop:itemShop 表示打开道具商店。因此连载塔千万不要中途修改商店ID！
		 turn 单击勇士（Z键）转身，core.turnHero() 会产生此项，因此通过事件等方式强制让勇士转向应该用 core.setHeroLoc()
		 turn:dir 勇士转向某个方向，dir 可以为 up down left right（此项一般是读取自动存档产生的，属于样板的不良特性，请勿滥用）
		 getNext 轻按获得身边道具，优先获得面前的（面前没有则按上下左右顺序依次获得），身边如果没有道具则此项会被跳过
		 input:none “等待用户操作事件”中超时（作者未设置超时时间则此项会导致报错）
		 input:xxx 可能表示“等待用户操作事件”的一个操作（如按键操作将直接记录 input:keycode ），
		 也可能表示一个“接受用户输入数字”的输入，后者的情况下 xxx 为输入的整数。此项缺失的话前者将直接报错，后者将用0代替（后者现在支持负数了）
		 input2:xxx 可能表示“读取全局存储（core.getGlobal）”读取到的值，也可能表示一个“接受用户输入文本”的输入，
		 两种情况下 xxx 都为 base64 编码。此项缺失的话前者将重新现场读取，后者将用空字符串代替
		 no 走到可穿透的楼梯上不触发楼层切换事件，通过本插件可以让勇士停在旁边没有障碍物的楼梯上哦～
		 move:x:y 尝试瞬移到 [x,y] 点（不改变朝向），该点甚至可以和勇士相邻或者位于视野外
		 key:n 松开键值为n的键，如 key:49 表示松开大键盘数字键1，默认会触发使用破墙镐
		 click:n:px:py 点击自绘状态栏，n为0表示横屏1表示竖屏，[px,py] 为点击的像素坐标
		 random:n 生成了随机数n，即 core.rand2(num) 的返回结果，n必须在 [0,num-1] 范围，num必须为正整数。此项缺失将导致现场重新随机生成数值，可能导致回放结果不一致！
		 作者自定义的新项（一般为js对象，可以先JSON.stringify()再core.encodeBase64()得到纯英文数字的内容）需要用(半角圆括弧)括起来。
		 
		 当您使用数字键5将一些项追加到即将播放内容的开头时，请注意要逆序逐项追加，或者每追加一项就按下数字键7或字母键N单步播放一步。
		 但是【input input2 random choices】是被动读取的，单步播放如果触发了相应的事件就会连续读取，这时候只能提前逐项追加好。
		 电脑端熟练以后推荐直接在控制台操作 core.status.route 和 core.status.replay.toReplay（后者录像回放时才有），配合 core.push() 和 core.unshift() 更加灵活自由哦！
		 */
        core.actions.registerAction(
            'onkeyUp',
            '_sys_onkeyUp_replay',
            function (e) {
                if (this._checkReplaying()) {
                    if (e.keyCode == 27)
                        // ESCAPE
                        core.stopReplay();
                    else if (e.keyCode == 90)
                        // Z
                        core.speedDownReplay();
                    else if (e.keyCode == 67)
                        // C
                        core.speedUpReplay();
                    else if (e.keyCode == 32)
                        // SPACE
                        core.triggerReplay();
                    else if (e.keyCode == 65)
                        // A
                        core.rewindReplay();
                    else if (e.keyCode == 83)
                        // S
                        core.control._replay_SL();
                    else if (e.keyCode == 88)
                        // X
                        core.control._replay_book();
                    else if (e.keyCode == 33 || e.keyCode == 34)
                        // PgUp/PgDn
                        core.control._replay_viewMap();
                    else if (e.keyCode == 78)
                        // N
                        core.stepReplay();
                    else if (e.keyCode == 84)
                        // T
                        core.control._replay_toolbox();
                    else if (e.keyCode == 81)
                        // Q
                        core.control._replay_equipbox();
                    else if (e.keyCode == 66)
                        // B
                        core.ui._drawStatistics();
                    else if (e.keyCode == 49 || e.keyCode == 54)
                        // 1/6，原速/24倍速播放
                        core.setReplaySpeed(e.keyCode == 49 ? 1 : 24);
                    else if (e.keyCode > 49 && e.keyCode < 54) {
                        // 2-5，录像精修
                        switch (e.keyCode - 48) {
                            case 2: // pop
                                alert(
                                    '您已移除已录制内容的最后一项：' +
                                        core.status.route.pop()
                                );
                                break;
                            case 3: // push
                                core.utils.myprompt(
                                    '请输入您要追加到已录制内容末尾的项：',
                                    '',
                                    function (value) {
                                        if (value != null)
                                            core.status.route.push(value);
                                    }
                                );
                                break;
                            case 4: // shift
                                alert(
                                    '您已移除即将播放内容的第一项：' +
                                        core.status.replay.toReplay.shift()
                                );
                                break;
                            case 5: // unshift
                                core.utils.myprompt(
                                    '请输入您要追加到即将播放内容开头的项：',
                                    '',
                                    function (value) {
                                        if (value != null)
                                            core.status.replay.toReplay.unshift(
                                                value
                                            );
                                    }
                                );
                        }
                    }
                    return true;
                }
            },
            100
        );
    },
    "numpad": function () {
        // 样板自带的整数输入事件为白屏弹窗且可以误输入任意非法内容但不支持负整数，观感较差。本插件可以将其美化成仿RM样式，使其支持负整数同时带有音效
        // 另一方面，4399等第三方平台不允许使用包括 core.myprompt() 和 core.myconfirm() 在内的弹窗，因此也需要此插件来替代，不然类似生命魔杖的道具就不好实现了
        // 关于负整数输入，V2.8.2原生支持其录像的压缩和解压，只是默认的 core.events._action_input() 函数将负数取了绝对值，可以只复写下面的 core.isReplaying() 部分来取消

        // 是否启用本插件，false表示禁用，true表示启用
        var __enable = true;
        if (!__enable) return;

        core.events._action_input = function (data, x, y, prefix) {
            // 复写整数输入事件
            if (core.isReplaying()) {
                // 录像回放时，处理方式不变，但增加负整数支持
                core.events.__action_getInput(
                    core.replaceText(data.text, prefix),
                    false,
                    function (value) {
                        value = parseInt(value) || 0; // 去掉了取绝对值的步骤
                        core.status.route.push('input:' + value);
                        core.setFlag('input', value);
                        core.doAction();
                    }
                );
            } else {
                // 正常游戏中，采用暂停录制的方式然后用事件流循环“绘制-等待-变量操作”三板斧实现（按照13*13适配的）。
                // 您可以自行修改循环内的内容来适配15*15或其他需求，或干脆作为公共事件编辑。
                core.insertAction(
                    [
                        // 记录当前录像长度，下面的循环结束后裁剪。达到“暂停录制”的效果
                        {
                            type: 'function',
                            function:
                                "function(){flags['@temp@length']=core.status.route.length}"
                        },
                        { type: 'setValue', name: 'flag:input', value: '0' },
                        {
                            type: 'while',
                            condition: 'true',
                            data: [
                                {
                                    type: 'drawBackground',
                                    background: 'winskin.png',
                                    x: 16,
                                    y: 16,
                                    width: 384,
                                    height: 384
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10181',
                                    x: 32,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10185',
                                    x: 64,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10186',
                                    x: 96,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10187',
                                    x: 128,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10188',
                                    x: 160,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10189',
                                    x: 192,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10193',
                                    x: 224,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10194',
                                    x: 256,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10195',
                                    x: 288,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10196',
                                    x: 320,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10197',
                                    x: 352,
                                    y: 288
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10286',
                                    x: 32,
                                    y: 352
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10169',
                                    x: 96,
                                    y: 352
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10232',
                                    x: 128,
                                    y: 352
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10185',
                                    x: 320,
                                    y: 352
                                },
                                {
                                    type: 'drawIcon',
                                    id: 'X10242',
                                    x: 352,
                                    y: 352
                                },
                                {
                                    type: 'fillBoldText',
                                    x: 48,
                                    y: 256,
                                    style: [255, 255, 255, 1],
                                    font: 'bold 32px Consolas',
                                    text: '${flag:input}'
                                },
                                {
                                    type: 'fillBoldText',
                                    x: 32,
                                    y: 48,
                                    style: [255, 255, 255, 1],
                                    font: '16px Consolas',
                                    text: core.replaceText(data.text, prefix)
                                },
                                {
                                    type: 'wait',
                                    forceChild: true,
                                    data: [
                                        {
                                            case: 'keyboard',
                                            keycode:
                                                '48,49,50,51,52,53,54,55,56,57',
                                            action: [
                                                // 按下数字键，追加到已输入内容的末尾，但禁止越界。变量：keycode-48就是末位数字
                                                {
                                                    type: 'playSound',
                                                    name: '光标移动'
                                                },
                                                {
                                                    type: 'if',
                                                    condition: '(flag:input<0)',
                                                    true: [
                                                        {
                                                            type: 'setValue',
                                                            name: 'flag:input',
                                                            value: '10*flag:input-(flag:keycode-48)'
                                                        }
                                                    ],
                                                    false: [
                                                        {
                                                            type: 'setValue',
                                                            name: 'flag:input',
                                                            value: '10*flag:input+(flag:keycode-48)'
                                                        }
                                                    ]
                                                },
                                                {
                                                    type: 'setValue',
                                                    name: 'flag:input',
                                                    value: 'core.clamp(flag:input,-9e15,9e15)'
                                                }
                                            ]
                                        },
                                        {
                                            case: 'keyboard',
                                            keycode: '189',
                                            action: [
                                                // 按下减号键，变更已输入内容的符号
                                                {
                                                    type: 'playSound',
                                                    name: '跳跃'
                                                },
                                                {
                                                    type: 'setValue',
                                                    name: 'flag:input',
                                                    value: '-flag:input'
                                                }
                                            ]
                                        },
                                        {
                                            case: 'keyboard',
                                            keycode: '8',
                                            action: [
                                                // 按下退格键，从已输入内容的末尾删除一位
                                                {
                                                    type: 'playSound',
                                                    name: '取消'
                                                },
                                                {
                                                    type: 'setValue',
                                                    name: 'flag:input',
                                                    operator: '//=',
                                                    value: '10'
                                                }
                                            ]
                                        },
                                        {
                                            case: 'keyboard',
                                            keycode: '27',
                                            action: [
                                                // 按下ESC键，清空已输入内容
                                                {
                                                    type: 'playSound',
                                                    name: '读档'
                                                },
                                                {
                                                    type: 'setValue',
                                                    name: 'flag:input',
                                                    value: '0'
                                                }
                                            ]
                                        },
                                        {
                                            case: 'keyboard',
                                            keycode: '13',
                                            action: [
                                                // 按下回车键，确定
                                                { type: 'break', n: 1 }
                                            ]
                                        },
                                        {
                                            case: 'mouse',
                                            px: [32, 63],
                                            py: [288, 320],
                                            action: [
                                                // 点击减号，变号。右边界写63防止和下面重叠
                                                {
                                                    type: 'playSound',
                                                    name: '跳跃'
                                                },
                                                {
                                                    type: 'setValue',
                                                    name: 'flag:input',
                                                    value: '-flag:input'
                                                }
                                            ]
                                        },
                                        {
                                            case: 'mouse',
                                            px: [64, 384],
                                            py: [288, 320],
                                            action: [
                                                // 点击数字，追加到已输入内容的末尾，但禁止越界。变量：x-2就是末位数字
                                                {
                                                    type: 'playSound',
                                                    name: '光标移动'
                                                },
                                                {
                                                    type: 'if',
                                                    condition: '(flag:input<0)',
                                                    true: [
                                                        {
                                                            type: 'setValue',
                                                            name: 'flag:input',
                                                            value: '10*flag:input-(flag:x-2)'
                                                        }
                                                    ],
                                                    false: [
                                                        {
                                                            type: 'setValue',
                                                            name: 'flag:input',
                                                            value: '10*flag:input+(flag:x-2)'
                                                        }
                                                    ]
                                                },
                                                {
                                                    type: 'setValue',
                                                    name: 'flag:input',
                                                    value: 'core.clamp(flag:input,-9e15,9e15)'
                                                }
                                            ]
                                        },
                                        {
                                            case: 'mouse',
                                            px: [32, 64],
                                            py: [352, 384],
                                            action: [
                                                // 点击左箭头，退格
                                                {
                                                    type: 'playSound',
                                                    name: '取消'
                                                },
                                                {
                                                    type: 'setValue',
                                                    name: 'flag:input',
                                                    operator: '//=',
                                                    value: '10'
                                                }
                                            ]
                                        },
                                        {
                                            case: 'mouse',
                                            px: [96, 160],
                                            py: [352, 384],
                                            action: [
                                                // 点击CE，清空
                                                {
                                                    type: 'playSound',
                                                    name: '读档'
                                                },
                                                {
                                                    type: 'setValue',
                                                    name: 'flag:input',
                                                    value: '0'
                                                }
                                            ]
                                        },
                                        {
                                            case: 'mouse',
                                            px: [320, 384],
                                            py: [352, 384],
                                            action: [
                                                // 点击OK，确定
                                                { type: 'break', n: 1 }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        },
                        { type: 'clearMap' },
                        // 裁剪录像，只保留'input:n'，然后继续录制
                        {
                            type: 'function',
                            function:
                                "function(){core.status.route.splice(flags['@temp@length']);core.status.route.push('input:'+core.getFlag('input',0))}"
                        }
                    ],
                    x,
                    y
                );
                core.events.doAction();
            }
        };
    },
    "sprites": function () {
        // 基于canvas的sprite化，摘编整理自万宁魔塔
        //
        // ----------------------------------- 第一部分 必装 js代码 ----------------------------------- //
        //
        // 关于新增的on方法说明：
        // 该方法类似与样板的registerAction，它允许你能够使用与样板类似的api对sprite进行操作
        // on(type, handler)
        // 其中type为操作类型，常见的有click mousedown mouseup mousemove mouseenter mouseleave wheel keydown keyup touchstart touchend touchmove
        // 这些操作类型分为四类，第一类是鼠标事件，上述的操作中wheel之前的事件均为鼠标事件，它的监听形式为
        // sprite.on(type, (px, py) => { code });  其中px, py为参数，为点击的横纵坐标（相对sprite左上角）
        // 第二类是滚轮事件，只有一个事件wheel，它的监听形式为
        // sprite.on('wheel', (dy, dx, dz) => { code });  dy为滚轮的纵向滑动量，dx为滚轮的横向滑动量，dz为滚轮在垂直屏幕方向上的滑动量（？很奇怪，但是html确实提供了这个东西
        // 第三类是键盘事件，keydown和keyup都是键盘事件，它的监听形式为
        // sprite.on(type, (key, keyCode, altKey, ctrlKey, shiftKey) => { code });
        // 其中key为按键名称，可以通过console.log自行尝试，需要注意的是这个东西是区分大小写的，keyCode是与样板相同的keycode，可自行在网上查询
        // 后面三个都是布尔值（true or false）分别为alt键、ctrl键、shift键是否被按下
        // 第四类是触摸屏操作（注意click可能不会被触屏触发），包括touchstart touchend touchmove，有两种使用方法
        // 第一种：sprite.on(type, ([px, py]) => { code }); px py是点击的坐标，相对于sprite左上角
        // 第二种：sprite.on(type, (...locs) => { code }); locs是由[px, py]组成的数组，比如locs[0][0]就是第一个点击点的横坐标，locs[2][1]就是第三个点击点的纵坐标

        /* ---------------- 用法说明 ---------------- *
         * 1. 创建sprite: const sprite = new Sprite(x, y, w, h, z, reference, name);
         *   其中x y w h为画布的横纵坐标及长宽，reference为参考系，只能填game（相对于游戏画面）和window（相对于窗口）
         *   且当为相对游戏画面时，长宽与坐标将会乘以放缩比例（相当于用createCanvas创建）
         *   z为纵深，表示不同元素之间的覆盖关系，大的覆盖小的
         *   name为自定义名称，可以不填
         * 2. 删除: sprite.destroy();
         * 3. 设置css特效: sprite.setCss(css);
         *   其中css直接填 box-shadow: 0px 0px 10px black;的形式即可，与style标签与css文件内写法相同
         *   对于已设置的特效，如果之后不需要再次设置，可以不填
         * 4. 添加事件监听器: sprite.addEventListener(); 用法与html元素的addEventListener完全一致
         * 5. 移除事件监听器: sprite.removeEventListener(); 用法与html元素的removeEventListener完全一致
         * 6. 属性列表
         *   (1) sprite.x | sprite.y | sprite.width | sprite.height | sprite.zIndex | sprite.reference 顾名思义
         *   (2) sprite.canvas 该sprite的画布
         *   (3) sprite.context 该画布的CanvasRenderingContext2d对象，即样板中常见的ctx
         *   (4) sprite.count 不要改这个玩意
         * 7. 使用样板api进行绘制
         *   示例：
         *   var ctx = sprite.context;
         *   core.fillText(ctx, 'xxx', 100, 100);
         *   core.fillRect(ctx, 0, 0, 50, 50);
         *   当然也可以使用原生js
         *   ctx.moveTo(0, 0);
         *   ctx.bezierCurveTo(50, 50, 100, 0, 100, 50);
         *   ctx.stroke();
         * ---------------- 用法说明 ---------------- */

        const sprites = {};

        // 终于能用es6了（恼
        class Sprite {
            constructor(x, y, w, h, z, reference, name) {
                this.x = x;
                this.y = y;
                this.width = w;
                this.height = h;
                this.zIndex = z;
                this.reference = reference;
                /** @type {HTMLCanvasElement} */
                this.canvas = null;
                /** @type {CanvasRenderingContext2D} */
                this.context = null;
                this.count = 0;
                this.name = name;
                this.key = [];
                this.init();
            }

            init() {
                const name = this.name || `_sprite_${Sprite.count}`;
                this.name = name;
                if (this.reference === 'window') {
                    const canvas = document.createElement('canvas');
                    this.canvas = canvas;
                    this.context = canvas.getContext('2d');
                    canvas.width = this.width;
                    canvas.height = this.height;
                    canvas.style.width = this.width + 'px';
                    canvas.style.height = this.height + 'px';
                    canvas.style.position = 'absolute';
                    canvas.style.top = this.y + 'px';
                    canvas.style.left = this.x + 'px';
                    canvas.style.zIndex = this.zIndex.toString();
                    document.body.appendChild(canvas);
                } else {
                    this.context = core.createCanvas(
                        name,
                        this.x,
                        this.y,
                        this.width,
                        this.height,
                        this.zIndex
                    );
                    this.canvas = this.context.canvas;
                    this.count = Sprite.count;
                    this.canvas.style.pointerEvents = 'auto';
                }
                Sprite.count++;
                sprites[this.name] = this;
            }

            setCss(css) {
                css = css.replace('\n', ';').replace(';;', ';');
                const effects = css.split(';');
                const canvas = this.canvas;
                effects.forEach(v => {
                    const content = v.split(':');
                    let name = content[0];
                    let value = content[1];
                    name = name
                        .trim()
                        .split('-')
                        .reduce((pre, curr, i, a) => {
                            if (i === 0 && curr !== '') return curr;
                            if (a[0] === '' && i === 1) return curr;
                            return pre + curr.toUpperCase()[0] + curr.slice(1);
                        }, '');
                    if (name in canvas.style) canvas.style[name] = value;
                });
                return this;
            }

            move(x, y, isDelta) {
                if (x !== undefined && x !== null) this.x = x;
                if (y !== undefined && y !== null) this.y = y;
                if (this.reference === 'window') {
                    var ele = this.canvas;
                    ele.style.left =
                        x + (isDelta ? parseFloat(ele.style.left) : 0) + 'px';
                    ele.style.top =
                        y + (isDelta ? parseFloat(ele.style.top) : 0) + 'px';
                } else core.relocateCanvas(this.context, x, y, isDelta);
                return this;
            }

            resize(w, h, styleOnly) {
                if (w !== undefined && w !== null) this.width = w;
                if (h !== undefined && h !== null) this.height = h;
                if (this.reference === 'window') {
                    const ele = this.canvas;
                    ele.style.width = w + 'px';
                    ele.style.height = h + 'px';
                    if (!styleOnly) {
                        ele.width = w;
                        ele.height = h;
                    }
                } else core.resizeCanvas(this.context, w, h, styleOnly);
                return this;
            }

            rotate(angle, cx, cy) {
                if (this.reference === 'window') {
                    const left = this.x;
                    const top = this.y;
                    this.canvas.style.transformOrigin =
                        cx - left + 'px ' + (cy - top) + 'px';
                    if (angle === 0) {
                        canvas.style.transform = '';
                    } else {
                        canvas.style.transform = 'rotate(' + angle + 'deg)';
                    }
                } else {
                    core.rotateCanvas(this.context, angle, cx, cy);
                }
                return this;
            }

            destroy() {
                if (this.reference === 'window') {
                    if (this.canvas) document.body.removeChild(this.canvas);
                } else {
                    core.deleteCanvas(this.name);
                }
                this.key.forEach(v => document.removeEventListener(v[0], v[1]));
                sprites[this.name] = void 0;
            }

            /**
             * 类似样板registerAction接口，但是是以该sprite的左上角为(0,0)计算的
             * @param {keyof HTMLElementEventMap} type
             * @param {(...param: any[]) => void} handler
             */
            on(type, handler) {
                if (this.reference !== 'game')
                    throw new ReferenceError(
                        `当sprite的reference为window时，不可使用该函数`
                    );
                const mouse = [
                    'auxclick',
                    'click',
                    'contextmenu',
                    'dblclick',
                    'mousedown',
                    'mouseup',
                    'mouseenter',
                    'mouseleave',
                    'mousemove',
                    'mouseout',
                    'mouseover'
                ];
                const key = ['keydown', 'keypress', 'keyup'];
                const touch = [
                    'touchstart',
                    'touchend',
                    'touchcancel',
                    'touchmove'
                ];
                if (mouse.includes(type)) {
                    this.addEventListener(type, e => {
                        const px = e.offsetX / core.domStyle.scale,
                            py = e.offsetY / core.domStyle.scale;
                        handler(px, py);
                    });
                } else if (type === 'wheel') {
                    this.addEventListener('wheel', e => {
                        handler(e.deltaY, e.deltaX, e.deltaZ);
                    });
                } else if (key.includes(type)) {
                    // 键盘事件只能加到document上
                    const listener = e => {
                        handler(
                            e.key,
                            e.keyCode,
                            e.altKey,
                            e.ctrlKey,
                            e.shiftKey
                        );
                    };
                    this.key.push([type, listener]);
                    document.addEventListener(type, listener);
                } else if (touch.includes(type)) {
                    this.addEventListener(type, e => {
                        /** @type {TouchList} */
                        const touches = e.touches;
                        const locs = [];
                        for (let i = 0; i < touches.length; i++) {
                            const t = touches[i];
                            const { x, y } = core.actions._getClickLoc(
                                t.clientX,
                                t.clientY
                            );
                            const px = x / core.domStyle.scale,
                                py = y / core.domStyle.scale;
                            locs.push([px, py]);
                        }
                        handler(...locs);
                    });
                }
            }

            addEventListener() {
                this.canvas.addEventListener.apply(this.canvas, arguments);
            }

            removeEventListener() {
                this.canvas.removeEventListener.apply(this.canvas, arguments);
            }
        }

        this.getSprite = function (name) {
            const s = sprites[name];
            if (!s) throw new ReferenceError(`不能获得不存在的sprite`);
            return sprites[name];
        };

        Sprite.count = 0;

        window.Sprite = Sprite;
    },
    "hotReload": function () {
	/* ---------- 功能说明 ---------- *

	1. 当 libs/ main.js index.html 中的任意一个文件被更改后，会自动刷新塔的页面
	2. 修改楼层文件后自动在塔的页面上显示出来，不需要刷新
	3. 修改脚本编辑或插件编写后也能自动更新更改的插件或脚本，但不保证不会出问题（一般都不会有问题的
	4. 修改图块属性、怪物属性等后会自动更新
	5. 当全塔属性被修改时，会自动刷新塔的页面
	6. 样板的 styles.css 被修改后也可以直接显示，不需要刷新
	7. 其余内容修改后不会自动更新也不会刷新

	/* ---------- 使用方式 ---------- *

	1. 前往 https://nodejs.org/en/ 下载node.js的LTS版本（点左边那个绿色按钮）并安装
	2. 将该插件复制到插件编写中
	3. 在造塔群的群文件-魔塔样板·改中找到server.js，下载并放到塔的根目录（与启动服务同一级）
	4. 在该目录下按下shift+鼠标右键（win11只按右键即可），选择在终端打开或在powershell打开
	5. 运行node server.js即可

	*/

	if (main.mode !== 'play' || main.replayChecking) return;

	/**
	 * 发送请求
	 * @param {string} url
	 * @param {string} type
	 * @param {string} data
	 * @returns {Promise<string>}
	 */
	async function post(url, type, data) {
		const xhr = new XMLHttpRequest();
		xhr.open(type, url);
		xhr.send(data);
		const res = await new Promise(res => {
			xhr.onload = e => {
				if (xhr.status !== 200) {
					console.error(`hot reload: http ${xhr.status}`);
					res('@error');
				} else res('success');
			};
			xhr.onerror = e => {
				res('@error');
				console.error(`hot reload: error on connection`);
			};
		});
		if (res === 'success') return xhr.response;
		else return '@error';
	}

	/**
	 * 热重载css
	 * @param {string} data
	 */
	function reloadCss(data) {
		const all = Array.from(document.getElementsByTagName('link'));
		all.forEach(v => {
			if (v.rel !== 'stylesheet') return;
			if (v.href === `http://127.0.0.1:3000/${data}`) {
				v.remove();
				const link = document.createElement('link');
				link.rel = 'stylesheet';
				link.type = 'text/css';
				link.href = data;
				document.head.appendChild(link);
				console.log(`css hot reload: ${data}`);
			}
		});
	}

	/**
	 * 热重载楼层
	 * @param {string} data
	 */
	async function reloadFloor(data) {
		// 首先重新加载main.floors对应的楼层
		await import(`/project/floors/${data}.js?v=${Date.now()}`);
		// 然后写入core.floors并解析
		core.floors[data] = main.floors[data];
		const floor = core.loadFloor(data);
		if (core.isPlaying()) {
			core.status.maps[data] = floor;
			delete core.status.mapBlockObjs[data];
			core.extractBlocks(data);
			if (data === core.status.floorId) {
				core.drawMap(data);
				core.setWeather(
					core.animateFrame.weather.type,
					core.animateFrame.weather.level
				);
			}
			core.updateStatusBar(true, true);
		}
		console.log(`floor hot reload: ${data}`);
	}

	/**
	 * 热重载脚本编辑及插件编写
	 * @param {string} data
	 */
	async function reloadScript(data) {
		if (data === 'plugins') {
			// 插件编写比较好办
			const before = plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1;
			// 这里不能用动态导入，因为动态导入会变成模块，变量就不是全局的了
			const script = document.createElement('script');
			script.src = `/project/plugins.js?v=${Date.now()}`;
			document.body.appendChild(script);
			await new Promise(res => {
				script.onload = () => res('success');
			});
			const after = plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1;
			// 找到差异的函数
			for (const id in before) {
				const fn = before[id];
				if (typeof fn !== 'function') continue;
				if (fn.toString() !== after[id]?.toString()) {
					try {
						core.plugin[id] = after[id];
						core.plugin[id].call(core.plugin);
						core.updateStatusBar(true, true);
						console.log(`plugin hot reload: ${id}`);
					} catch (e) {
						console.error(e);
					}
				}
			}
		} else if (data === 'functions') {
			// 脚本编辑略微麻烦点
			const before = functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a;
			// 这里不能用动态导入，因为动态导入会变成模块，变量就不是全局的了
			const script = document.createElement('script');
			script.src = `/project/functions.js?v=${Date.now()}`;
			document.body.appendChild(script);
			await new Promise(res => {
				script.onload = () => res('success');
			});
			const after = functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a;
			// 找到差异的函数
			for (const mod in before) {
				const fns = before[mod];
				for (const id in fns) {
					const fn = fns[id];
					if (typeof fn !== 'function' || id === 'hasSpecial')
						continue;
					const now = after[mod][id];
					if (fn.toString() !== now.toString()) {
						try {
							if (mod === 'events') {
								core.events.eventdata[id] = now;
							} else if (mod === 'enemys') {
								core.enemys.enemydata[id] = now;
							} else if (mod === 'actions') {
								core.actions.actionsdata[id] = now;
							} else if (mod === 'control') {
								core.control.controldata[id] = now;
							} else if (mod === 'ui') {
								core.ui.uidata[id] = now;
							}
							core.updateStatusBar(true, true);
							console.log(
								`function hot reload: ${mod}.${id}`
							);
						} catch (e) {
							console.error(e);
						}
					}
				}
			}
		}
	}

	/**
	 * 属性热重载，包括全塔属性等
	 * @param {string} data
	 */
	async function reloadData(data) {
		const script = document.createElement('script');
		script.src = `/project/${data}.js?v=${Date.now()}`;
		document.body.appendChild(script);
		await new Promise(res => {
			script.onload = () => res('success');
		});

		let after;
		if (data === 'data')
			after = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
		if (data === 'enemys')
			after = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80;
		if (data === 'icons')
			after = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1;
		if (data === 'items')
			after = items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a;
		if (data === 'maps')
			after = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
		if (data === 'events')
			after = events_c12a15a8_c380_4b28_8144_256cba95f760;

		if (data === 'enemys') {
			core.enemys.enemys = after;
			for (var enemyId in after) {
				core.enemys.enemys[enemyId].id = enemyId;
			}
			core.material.enemys = core.getEnemys();
		} else if (data === 'icons') {
			core.icons.icons = after;
			core.material.icons = core.getIcons();
		} else if (data === 'items') {
			core.items.items = after;
			for (var itemId in after) {
				core.items.items[itemId].id = itemId;
			}
			core.material.items = core.getItems();
		} else if (data === 'maps') {
			core.maps.blocksInfo = after;
			core.status.mapBlockObjs = {};
			core.status.number2block = {};
			Object.values(core.status.maps).forEach(v => delete v.blocks);
			core.extractBlocks();
			core.setWeather(
				core.animateFrame.weather.type,
				core.animateFrame.weather.level
			);
			core.drawMap();
		} else if (data === 'events') {
			core.events.commonEvent = after.commonEvent;
		} else if (data === 'data') {
			location.reload();
		}
		core.updateStatusBar(true, true);
		console.log(`data hot reload: ${data}`);
	}

	// 初始化
	(async function () {
		const data = await post('/reload', 'POST', 'test');
		if (data === '@error') {
			console.log(`未检测到node服务，热重载插件将无法使用`);
		} else {
			console.log(`热重载插件加载成功`);
			// reload
			setInterval(async () => {
				const res = await post('/reload', 'POST');
				if (res === '@error') return;
				if (res === 'true') location.reload();
				else return;
			}, 1000);

			// hot reload
			setInterval(async () => {
				const res = await post('/hotReload', 'POST');
				const data = res.split('@@');
				data.forEach(v => {
					if (v === '') return;
					const [type, file] = v.split(':');
					if (type === 'css') reloadCss(file);
					if (type === 'data') reloadData(file);
					if (type === 'floor') reloadFloor(file);
					if (type === 'script') reloadScript(file);
				});
			}, 1000);
		}
	})();
},
    "replayDebug": function () {
        if (main.mode !== 'play' || main.replayChecking) return;

        /** 服务器是否有录像调试模块 */
        let hasReplay = false;

        /** 回放时当前cnt */
        let nowCnt = 0;

        /** 是否在录像调试中 */
        let inDebug = false;

        /** 录像记录队列 */
        const queue = new Promise(res => {
            res('ready');
        });

        /** 录像存档队列 */
        let replaySaves = [];

        /**
         * 发送请求
         * @param {string} url
         * @param {string} type
         * @param {string} data
         * @returns {Promise<string>}
         */
        async function post(url, type, data) {
            const xhr = new XMLHttpRequest();
            xhr.open(type, url);
            xhr.send(data);
            const res = await new Promise(res => {
                xhr.onload = e => {
                    if (xhr.status !== 200) {
                        console.error(`replay debugger: http ${xhr.status}`);
                        res('@error');
                    } else res('success');
                };
                xhr.onerror = e => {
                    res('@error');
                    console.error(`replay debugger: error on connection`);
                };
            });
            if (res === 'success') return xhr.response;
            else return '@error';
        }

        // 初始化
        (async function () {
            const data = await post('/replay', 'POST', 'test');
            if (data === '@error')
                console.log(
                    `未检测到node服务的录像调试模块，录像调试插件将无法使用`
                );
            else {
                hasReplay = true;
                console.log(`录像调试插件加载成功`);
            }
        })();

        /**
         * 传输数据，并写入本地文件
         */
        async function postStatus(status) {
            const res = `${JSON.stringify(status)}`;
            const n = Number(await post('/replayWrite', 'POST', res));
            return n;
        }

        /**
         * 对比勇士属性
         * @param {string} status
         * @param {string} n
         */
        async function check(status, n) {
            if (isNaN(Number(n))) return;
            const data = `${n}@-|-@${JSON.stringify(status)}`;
            const res = await post('/replayCheck', 'POST', data);
            if (res === 'false') pause();
        }

        /**
         * 暂停录像的进行
         */
        function pause() {
            inDebug = true;
            core.pauseReplay();
            core.drawTip(`检测到属性不同，已进入录像调试界面`);
        }

        /**
         * 打印差异信息
         */
        async function printStatus() {
            const n = nowCnt;
            const rep = core.clone(core.status.hero);
            const local = JSON.parse(await post('/replayGet', 'POST', n));
            console.clear();
            console.log(
                `%c ---------- 录像调试模式 ---------- `,
                `
				background-color: #f55;
				font-size: 22px;
				font-family: 微软雅黑;
				color: black;
			`
            );
            const style = `
				background-color: #3de9ff;
				color: black;
				font-size: 15px;
				font-family: 微软雅黑;
			`;
            console.log(`%c 录像调试模式下：`, style);
            console.log(`%c 点击播放按钮会单步前进 `, style);
            console.log(`%c 点击停止按钮退出调试模式 `, style);
            console.log(`%c 点击回退按钮会单步回退 `, style);
            console.log(`%c 回放速度不能超过1倍速 `, style);
            console.log(
                `%c 录像中的属性：`,
                `
				background-color: #9aff24; 
				color: black; 
				font-size: 16px;
				font-family: 微软雅黑;
				`
            );
            console.log(rep);
            console.log(
                `%c 游玩过程中的属性：`,
                `
				background-color: #ffb45c; 
				color: black; 
				font-size: 16px;
				font-family: 微软雅黑;
				`
            );
            console.log(local);
            console.log(
                `%c 差异属性：`,
                `
				background-color: #f770ff; 
				color: black; 
				font-size: 16px;
				font-family: 微软雅黑;
				`
            );
            printDiff(rep, local);
        }

        /**
         * 输出差异信息
         */
        function printDiff(rep, local) {
            const diff = (a, b) => {
                const da = {};
                const db = {};
                for (const key in a) {
                    if (key === 'statistics' || key === 'timeout') continue;
                    const aa = a[key];
                    const bb = b[key];
                    if (typeof aa === 'object' && aa !== null) {
                        const d = diff(aa, bb);
                        if (Object.keys(d.da).length > 0) {
                            da[key] = d.da;
                            db[key] = d.db;
                        }
                    }
                    if (
                        typeof aa === 'boolean' ||
                        typeof aa === 'number' ||
                        typeof aa === 'string' ||
                        typeof aa === 'symbol' ||
                        typeof aa === 'undefined' ||
                        typeof aa === 'bigint' ||
                        aa === null
                    ) {
                        if (aa !== bb) {
                            da[key] = aa;
                            db[key] = bb;
                        }
                    }
                }
                return { da, db };
            };

            const group = (a, b) => {
                for (const key in a) {
                    const aa = a[key];
                    const bb = b[key];
                    console.group(`差异属性：${key}`);
                    if (typeof aa === 'object' && aa !== null) group(aa, bb);
                    else
                        console.log(
                            `%c 录像：${aa}    游戏：${bb} `,
                            `
						background-color: #9aff24; 
						color: black; 
						font-size: 14px;
						font-family: 微软雅黑;
					`
                        );
                    console.groupEnd();
                }
            };

            const { da, db } = diff(rep, local);
            group(da, db);
        }

        /**
         * 回放时保存当前状态
         * @param {string} n
         */
        async function save(n, action) {
            replaySaves.push(Number(n));
            const data = {
                data: core.saveData(),
                replay: {
                    totalList: core.status.replay.totalList.slice(),
                    toReplay: [`${action}@---@${n}`].concat(
                        core.status.replay.toReplay.slice()
                    ),
                    steps: core.status.replay.steps
                }
            };

            const res = await post(
                '/replaySave',
                'POST',
                `${n}@-|-@${JSON.stringify(data)}`
            );
            if (res.startsWith('@error')) return console.error(res.slice(1));
        }

        /**
         * 录像单步回退
         */
        async function rewind() {
            /** @type {number} */
            const n = replaySaves.pop();
            if (!core.isPlaying() || !core.isReplaying()) return;
            if (!core.status.replay.pausing) {
                core.playSound('操作失败');
                return core.drawTip('请先暂停录像');
            }
            if (
                core.isMoving() ||
                core.status.replay.animate ||
                core.status.event.id
            ) {
                core.playSound('操作失败');
                return core.drawTip('请等待当前事件的处理结束');
            }
            const res = await post('/replayGetSave', 'POST', n.toString());
            const data = JSON.parse(res);
            nowCnt = n.toString();
            core.loadData(data.data, function () {
                printStatus();
                core.removeFlag('__fromLoad__');
                core.status.replay = {
                    replaying: true,
                    pausing: true,
                    animate: false,
                    toReplay: data.replay.toReplay,
                    totalList: data.replay.totalList,
                    speed: core.status.replay.speed,
                    steps: data.replay.steps,
                    save: []
                };
                core.createCanvas(
                    'replay',
                    0,
                    core._PY_ - 40,
                    core._PX_,
                    40,
                    199
                );
                core.setOpacity('replay', 0.6);
                core.control._replay_drawProgress();
                core.updateStatusBar(false, true);
                core.drawTip('成功回退到上一步');
            });
        }

        ////// 点击状态栏中的工具箱时 //////
        const toolboxClick = main.statusBar.image.toolbox.onclick;
        main.statusBar.image.toolbox.onclick = function (e) {
            if (!inDebug)
                return toolboxClick.call(main.statusBar.image.toolbox, e);
            e.stopPropagation();
            rewind();
        };

        ////// 点击状态栏中的怪物手册时 //////
        const bookClick = main.statusBar.image.book.onclick;
        main.statusBar.image.book.onclick = function (e) {
            if (!inDebug) return bookClick.call(main.statusBar.image.book, e);
            e.stopPropagation();
            core.stepReplay();
        };

        ////// 点击状态栏中的读档按钮时 //////
        const loadClick = main.statusBar.image.load.onclick;
        main.statusBar.image.load.onclick = function (e) {
            if (!inDebug) return loadClick.call(main.statusBar.image.load, e);
            e.stopPropagation();
            if (core.status.replay.speed < 1) return core.speedUpReplay();
            core.drawTip('调试模式下速度不能超过1倍速');
        };

        ////// 点击状态栏中的楼层传送器/装备栏时 //////
        const flyClick = main.statusBar.image.fly.onclick;
        main.statusBar.image.fly.onclick = function (e) {
            if (!inDebug) return flyClick.call(main.statusBar.image.fly, e);
            e.stopPropagation();
            inDebug = false;
            console.clear();
            console.log(
                '%c 已退出录像调试，当再次检测到属性不同时会再次进入',
                `
				background-color: #f55;
				font-size: 19px;
				font-family: 微软雅黑;
				color: black;
			`
            );
        };

        // rewrite start game 需要在游戏开始的时候设置是否开启录像调试模式
        events.prototype._startGame_start = function (
            hard,
            seed,
            route,
            callback
        ) {
            core.resetGame(
                core.firstData.hero,
                hard,
                null,
                core.cloneArray(core.initStatus.maps)
            );
            core.setHeroLoc('x', -1);
            core.setHeroLoc('y', -1);

            if (seed != null && seed > 0) {
                core.setFlag('__seed__', seed);
                core.setFlag('__rand__', seed);
            } else core.utils.__init_seed();
            core.clearStatusBar();

            var todo = [];
            if (hasReplay) {
                todo.push({
                    type: 'confirm',
                    text: '要开启录像调试模式吗',
                    yes: [
                        '在录像调试模式下，每一步操作后都会记录勇士属性、变量等，当回放该录像时，会自动进入调试模式，每一步后自动比对录像状态下与游玩状态下的差异，如有差异，会自动暂停并进入调试状态',
                        {
                            type: 'function',
                            function:
                                'function(){\nflags[`__replayDebug__`] = true;\n}'
                        }
                    ],
                    no: []
                });
            }
            if (core.flags.startUsingCanvas) {
                core.hideStatusBar();
                core.dom.musicBtn.style.display = 'block';
                core.push(todo, core.firstData.startCanvas);
            }
            core.push(todo, {
                type: 'function',
                function: 'function() { core.events._startGame_setHard(); }'
            });
            core.push(todo, core.firstData.startText);
            this.insertAction(todo, null, null, function () {
                core.events._startGame_afterStart(callback);
            });

            if (route != null) core.startReplay(route);
        };

        // 需要复写core.status.route，在这个函数里面
        control.prototype._bindRoutePush = function () {
            const target = core.status.route.slice();
            /** @type {ProxyHandler<string[]>} */
            const handler = {
                get(target, key) {
                    // proxy大显身手的地方（
                    if (typeof key === 'symbol') return target[key];
                    const n = Number(key);
                    if (!isNaN(n)) {
                        if (n < 0) return target[target.length + n];
                        return target[n];
                    }
                    if (key === 'push') {
                        if (core.isReplaying()) {
                            return (...ele) => {
                                if (core.isReplaying()) {
                                    const status = core.clone(core.status.hero);
                                    if (inDebug) {
                                        // 需要输出对比信息
                                        printStatus();
                                    } else {
                                        if (
                                            typeof nowCnt === 'string' &&
                                            target.length !== 0
                                        )
                                            check(status, nowCnt);
                                    }
                                    return target.push(...ele);
                                }
                            };
                        } else
                            return (...ele) => {
                                if (!window.flags.__replayDebug__)
                                    return target.push(...ele);
                                const status = core.clone(core.status.hero);
                                ele.forEach(v => {
                                    queue.then(async () => {
                                        const n = await postStatus(status);
                                        target.push(`${v}@---@${n}`);
                                    });
                                });
                            };
                    }
                    return target[key];
                }
            };
            core.status.route = new Proxy(target, handler);
        };

        ////// 开始播放 //////
        control.prototype.startReplay = function (list) {
            if (inDebug) return core.stepReplay();
            if (!core.isPlaying()) return;
            core.status.replay.replaying = true;
            core.status.replay.pausing = true;
            core.status.replay.failed = false;
            core.status.replay.speed = 1.0;
            core.status.replay.toReplay = core.cloneArray(list);
            core.status.replay.totalList = core.status.route.concat(list);
            core.status.replay.steps = 0;
            core.status.replay.save = [];
            core.createCanvas('replay', 0, core._PY_ - 40, core._PX_, 40, 199);
            core.setOpacity('replay', 0.6);
            this._replay_drawProgress();
            core.updateStatusBar(false, true);
            core.drawTip('开始播放');
            this.replay();
            if (flags.__replayDebug__) core.drawTip(`进入录像调试模式`);
        };

        const origin = core.control.replay;
        control.prototype.replay = function (force) {
            if (!flags.__replayDebug__) return origin.call(core.control, force);
            if (
                !core.isPlaying() ||
                !core.isReplaying() ||
                core.status.replay.animate ||
                core.status.event.id ||
                core.status.replay.failed
            )
                return;
            if (core.status.replay.pausing && !force) return;

            if (inDebug && core.status.replay.speed > 1)
                core.status.replay.speed = 1;
            this._replay_drawProgress();
            if (core.status.replay.toReplay.length == 0)
                return this._replay_finished();

            var action = core.status.replay.toReplay.shift();
            const [act, n] = action.split('@---@');
            nowCnt = n;
            core.status.replay.steps++;
            save(n, act);
            if (this._doReplayAction(act)) {
                return;
            }
            this._replay_error(action);
        };

        ////// 解密路线 //////
        utils.prototype.decodeRoute = function (route) {
            if (!route) return route;

            // 解压缩
            try {
                var v = LZString.decompressFromBase64(route);
                if (v != null && /^[-_a-zA-Z0-9+\/=:()@]*$/.test(v)) {
                    if (v != '' || route.length < 8) route = v;
                }
            } catch (e) {}

            var decodeObj = { route: route, index: 0, ans: [] };
            while (decodeObj.index < decodeObj.route.length) {
                this._decodeRoute_decodeOne(
                    decodeObj,
                    decodeObj.route.charAt(decodeObj.index++)
                );
            }
            return decodeObj.ans;
        };

        control.prototype._moveAction_moving = function (callback) {
            core.setHeroMoveInterval(function () {
                var direction = core.getHeroLoc('direction');
                core.status.route.push(direction);
                core.setHeroLoc('x', core.nextX(), true);
                core.setHeroLoc('y', core.nextY(), true);

                core.control._moveAction_popAutomaticRoute();

                core.moveOneStep();
                core.checkRouteFolding();
                if (callback) callback();
            });
        };

        ////// 检查录像折叠 //////
        control.prototype.checkRouteFolding = function () {
            // 不支持该功能
        };
    },
    "小地图楼传": function () {
	// 在此增加新插件
	// 该插件可自定义空间很大，自定义内容请看注释

	// ------------------------- 安装说明 ------------------------- //
	// 先安装基于canvas的sprite化插件（2.10.0以上自带）
	// 确保自己的编辑器已安装造塔群内的编辑器升级压缩包（在HTML5魔塔样板文件夹内，2.10.1以上样板自带）
	// 再将以下代码复制进插件中
	// 提供的api请看以this.xxx = function开头的函数，函数前会有函数说明及参数说明，调用时只需core.plugin.xxx(参数)即可

	// ------------------------- 使用说明 ------------------------- //
	/*
	 * 直接复制进插件中，然后添加一个快捷键或道具效果为core.plugin.drawFlyMap()即可使用，不需额外设置
	 * 楼层id中不要出现下划线
	 * 该插件具体功能有：
	 * 1.绘制区域内的地图
	 * 2.可以拖动地图
	 * 3.点击地图可直接传送至目标地图，同时降低背景的不透明度，方便观察
	 * 4.滚轮或双指可以放缩绘制内容
	 * 5.放缩较大时，绘制地图的缩略图，可能会比较卡，但移动不会卡
	 * 6.整合漏怪检测，如果想忽略怪物，请在下方改动或用脚本修改core.plugin.ignoreEnemies，类型为数组
	 * 7.整合区域显示，所有单独或连在一起的地图会被视为一个区域
	 * 8.键盘操作，上下左右移动
	 */

	// ------------------------- 插件说明 ------------------------- //
	/*
	 * 该插件注释极其详细，可以帮助那些想要提升代码力，但实力有不足的作者
	 * 注意！！！该插件难度极大，没有代码底力的不建议研究
	 * 该插件涉及部分较为高级的算法，如bfs
	 */

	// 录像验证直接干掉这个插件
	if (main.replayChecking || main.mode === 'editor') return;

	// ----- 不可自定义 杂七杂八的变量
	/** @type {{[x: string]: BFSResult}} */
	let mapCache = {}; // 地图缓存
	let drawCache = {}; // 绘制信息缓存
	let status = 'none'; // 当前的绘制状态
	/** @type {{[x: string]: Sprite}} */
	let sprites = {}; // 当前所有的sprite
	/** @type {{[x: string]: Sprite}} */
	let canDrag = {}; // 可以拖拽的sprite
	/** @type {{[x: string]: Button}} */
	let areaSprite = {}; // 区域列表对应的sprite
	let clicking = false; // 是否正在点击，用于拖拽判定
	let drawingMap = ''; // 正在绘制的中心楼层
	let nowScale = 0; // 当前绘制的放缩比例
	let lastTouch = {}; // 上一次的单点点击信息
	let lastLength = 0; // 手机端缩放时上一次的两指间距离
	let nowDepth = 0; // 当前的遍历深度
	let drawedThumbnail = {}; // 已经绘制过的缩略图
	let moved = false; // 鼠标按下后是否移动了
	let noBorder = false; // 是否是无边框拼接模式
	let lastScale = 0; // 上一次缩放，用于优化缩略图绘制
	let showEnemy = false; // 是否显示漏怪
	let areaPage = 0; // 区域显示的当前页数
	let nowArea = 0; // 当前区域index
	let selecting = ''; // 选择时当前正在选择的地图

	// ---- 不可自定义，常量
	/** @type {Area} */
	let areas = []; // 区域信息
	const perPage = Math.floor((core._PY_ - 60) / 30); // 区域的每页显示数量

	// ---- 可自定义，默认的切换地图的图块id
	const defaultChange = {
		left: 'leftPortal', // 左箭头
		up: 'upPortal', // 上箭头
		right: 'rightPortal', // 右箭头
		down: 'downPortal', // 下箭头
		upFloor: 'upFloor', // 上楼
		downFloor: 'downFloor' // 下楼
	};
	// ---- 可自定义，默认数值
	const defaultValue = {
		font: 'Verdana', // 默认字体
		scale: 3, // 默认地图缩放比例
		depth: Infinity, // 默认的遍历深度
	};

	// ---- 不可自定义，计算数据
	const dirData = {
		up: [1, 0],
		down: [-1, 0],
		left: [0, 1],
		right: [0, -1],
		upFloor: [0, 0],
		downFloor: [0, 0]
	}

	let ignoreEnemies = this.ignoreEnemies = [];

	let allChangeEntries = Object.entries(defaultChange);

	const reset = core.events.resetGame;
	core.events.resetGame = function () {
		reset.apply(core.events, arguments);
		areas = [];
		// 获取所有分区，使用异步函数，保证不会卡顿
		// 原理是用bfs扫，将所有连在一起的地图合并成一个区域
		(async function () {
			let all = core.floorIds.slice();
			const scanned = {
				[all[0]]: true
			};
			while (all.length > 0) {
				let now = all.shift();
				if (core.status.maps[now].deleted) continue;
				if (!now) return;
				await new Promise(res => {
					const result = bfsSearch(now, Infinity, true);
					mapCache[`${now}_Infinity_false`] = result;
					areas.push({ name: core.floors[now].title, maps: result.order });
					for (const map of result.order) {
						scanned[map] = true;
						all = all.filter(v => !result.order.includes(v));
					}
					res('success');
				});
			}
		})();
	}

	/** 工具按钮 */
	class Button extends Sprite {
		constructor(name, x, y, w, h, text, fontSize = '20px', transition = true) {
			const btn = super(x, y, w, h, 1050, 'game', name);
			this.css(transition);
			setTimeout(() => btn.setCss(`opacity: 1;`), 50);
			const ctx = btn.context;
			ctx.textAlign = 'center';
			ctx.textBaseline = 'middle';
			core.fillText(ctx, text, w / 2, h / 2, '#fff', `${fontSize} normal`, w - 10);
			sprites[name] = btn;
		}

		css(transition) {
			this.setCss(
				'transition: opacity 0.6s linear, transform 0.2s linear;' +
				'background-color: #aaa;' +
				'box-shadow: 0px 0px 0px black;' +
				(transition ? 'opacity: 0;' : '') +
				'filter: drop-shadow(1px 1px 2px black);' +
				'box-shadow: 0px 0px 4px black;' +
				'cursor: pointer;'
			);
		}
	}

	/** 背景 */
	class Back extends Sprite {
		constructor(name, x, y, w, h, z, color) {
			const sprite = super(x, y, w, h, z, 'game', name);
			sprites[name] = sprite;
			this.setCss(`transition: all 0.6s linear;`);
			setTimeout(() => {
				this.setCss(`background-color: ${color};`);
			}, 50);
		}
	}

	/** 
	 * 获取绘制信息
	 * @param {string?} center 中心地图id
	 * @param {number?} depth 搜索深度
	 * @param {boolean?} noCache 是否不使用缓存
	 * @returns {MapDrawInfo}
	 */
	this.getMapDrawInfo = function (center = core.status.floorId, depth = defaultValue.depth, noCache = false) {
		nowDepth = depth;
		drawingMap = center;
		const id = `${center}_${depth}_${noBorder}`;
		// 检查缓存
		if (drawCache[id] && !noCache) return drawCache[id];
		const map = bfsSearch(center, depth, noCache);
		mapCache[id] = map;
		const res = getDrawInfo(map.res, center, map.order);
		res.upOrDown = map.upOrDown;
		drawCache[id] = res;
		return res;
	}

	/** 
	 * 绘制大地图，可拖动、滚轮缩放、点击对应位置可以楼传等
	 * @param {string} floorId 中心地图的id
	 * @param {number} depth 遍历深度
	 * @param {boolean} noCache 是否不使用缓存
	 * @param {number} scale 绘制的缩放比例
	 */
	this.drawFlyMap = function (floorId = core.status.floorId,
		depth = defaultValue.depth, noCache = false, scale = defaultValue.scale) {

		if (core.isReplaying()) return;

		// 把区域页码归零
		nowArea = areas.findIndex(v => v.maps.includes(core.status.floorId));
		areaPage = 0;
		nowScale = scale;
		selecting = floorId;
		const info = this.getMapDrawInfo(floorId, depth, noCache);
		if (status !== 'scale' && status !== 'border') {
			drawBack();
			drawTools();
		}
		drawMap(info, scale);
		status = 'flyMap';
		core.lockControl();
		core.canvas.data.canvas.style.zIndex = '990';
	}

	/**
	 * 获得某个区域的剩余怪物
	 * @param {string} floorId 区域包含的地图或要扫描的地图
	 * @param {boolean} area 是否扫描整个区域
	 * @returns {RemainEnemy} 怪物总数、所在地图、位置
	 * 返回值格式：{
	 *  rough: 每种怪物的数量及所有怪物的总数，为字符串，每个怪物独占一行
	 *  detail: 每个怪物的所在位置，每个怪物独占一行，以每20个整合成字符串，为字符串数组形式
	 *  data: 怪物数量的原始信息，格式为{ 楼层id: { 'x,y': 怪物id } }
	 * }
	 */
	this.getRemainEnemy = function (floorId = core.status.floorId, area = false) {
		const res = bfsSearch(floorId, Infinity, true);
		// 整合怪物总数
		/** @type {{[x: string]: number}} */
		const category = {};
		const toShow = area ? res.order : [floorId];
		const strArr = [];
		const add = (...num) => num.reduce((pre, cur) => pre + cur, 0);
		const name = (id) => core.material.enemys[id].name;
		const title = (id) => core.status.maps[id].title;
		for (const id of toShow) {
			const enemies = res.enemies[id];
			Object.values(enemies).forEach(v => {
				// 编辑器不支持 ??=，悲
				category[v] = category[v] ?? 0;
				category[v]++;
			})
			// 每个怪物的信息
			strArr.push(...Object.entries(enemies)
				.map(v => `${name(v[1])}    楼层:${title(id)},楼层id:${id},坐标:${v[0]}`));
		}
		// 输出字符串
		const all = `当前${area ? '区域' : '地图'}中剩余怪物数量：${add(...Object.values(category))}`;
		const classified = Object.entries(category).map(v => `${name(v[0])} × ${v[1]}`).join`\n`;
		const detail = [];
		while (strArr.length > 0) {
			detail.push(strArr.splice(0, 20).join`\n`);
		}
		return { rough: `${all}\n${classified}`, detail, data: res.enemies };
	}

	/** 
	 * 广度优先搜索搜索地图路径
	 * @param {string} center 中心地图的id
	 * @param {number} depth 搜索深度
	 * @param {boolean} noCache 是否不使用缓存
	 * @returns {BFSResult} 格式：floorId_x_y_dir: floorId_x_y
	 */
	function bfsSearch(center, depth, noCache) {
		// 检查缓存
		const id = `${center}_${depth}_${noBorder}`;
		if (mapCache[id] && !noCache) return mapCache[id];
		const used = {
			[center]: true
		}; // 搜索过的楼层
		let queue = [];
		let stack = [center]; // 当前栈
		let nowDepth = -1;
		const mapOrder = [center]; // 遍历顺序，顺便还能记录遍历了哪些楼层

		const res = {}; // 输出结果，格式：floorId_x_y_dir: floorId_x_y
		const enemies = {};
		const upOrDown = {};

		// 开始循环搜索
		while (nowDepth < depth && stack.length > 0) {
			const now = stack.shift(); // 当前id
			if (core.status.maps[now].deleted) continue;
			const blocks = core.getMapBlocksObj(now); // 获取当前地图的每点的事件
			enemies[now] = {};
			// 遍历，获取可以传送的点，只检测绿点事件，因此可用红点事件进行传送来实现分区功能
			for (const i in blocks) {
				const block = blocks[i];
				// 整合漏怪检测，所以要检测怪物
				if (block.event.trigger === 'battle') {
					const id = block.event.id;
					if (ignoreEnemies.includes(id)) continue;
					else enemies[now][i] = block.event.id;
					continue;
				}
				// 检测触发器是否为切换楼层，不是则直接跳过
				if (block.event.trigger !== 'changeFloor') continue;
				const dirEntries = allChangeEntries.find(v => v[1] === block.event.id);
				// 如果不是那六种传送门，直接忽略
				if (!dirEntries) continue;
				const data = block.event.data;
				const dir = dirEntries[0];
				const route = `${now}_${i.replace(',', '_')}_${dir}`;
				const target = `${data.floorId}_${data.loc.join('_')}`;
				if (!used[data.floorId]) {
					if (dir === 'upFloor' || dir === 'downFloor') {
						upOrDown[now] = upOrDown[id] ?? [];
						upOrDown[now].push(dir);
					}
					queue.push(data.floorId); // 没有搜索过，则加入栈中
					mapOrder.push(data.floorId);
					used[data.floorId] = true;
				}
				res[route] = target;
			}
			if (stack.length === 0) {
				stack = queue;
				queue = [];
				nowDepth++;
			}
			if (stack.length === 0 && queue.length === 0) break;
		}
		return { res, order: mapOrder, enemies, upOrDown };
	}

	/**
	 * 提供地图的绘制信息
	 * @param {{[x: string]: string}} map 要绘制的地图，格式：floorId_x_y_dir: floorId_x_y
	 * @param {string} center 中心地图的id
	 * @param {string[]} order 遍历顺序
	 * @returns {MapDrawInfo} 地图的绘制信息
	 */
	function getDrawInfo(map, center, order) {
		// 先根据地图id分类，从而确定每个地图连接哪些地图，同时方便处理
		const links = {};
		for (const i in map) {
			const splitted = i.split('_');
			const id = splitted[0]
			if (!links[id]) links[id] = {};
			links[id][i] = map[i];
		}
		// 分类完毕，然后根据连接点先计算出各个地图的坐标，然后再进行判断
		const centerFloor = core.status.maps[center];
		const visitedCenter = core.hasVisitedFloor(center);
		const locs = { // 格式：[中心x, 中心y, 宽, 高, 是否到达过]
			[center]: [0, 0, centerFloor.width, centerFloor.height, visitedCenter]
		};
		const lines = {}; // 地图间的连线
		// 可以上楼下楼的地图
		const upOrDown = {};
		for (const id of order) {
			const now = links[id];
			// 遍历每一个地图的连接情况
			for (const from in now) {
				const to = now[from];
				// 先根据from to计算物理位置
				const fromData = from.split('_'),
					toData = to.split('_');
				const dir = fromData[3];
				if (dir === 'upFloor' || dir === 'downFloor') continue;
				if (!defaultChange[dir]) continue;
				const v = dirData[dir][1], // 竖直数值
					h = dirData[dir][0], // 水平数值
					ha = Math.abs(h),
					va = Math.abs(v);
				const fx = parseInt(fromData[1]), // fromX
					fy = parseInt(fromData[2]), // fromY
					tx = parseInt(toData[1]), // toX
					ty = parseInt(toData[2]), // toY
					ff = id, // fromFloorId
					tf = toData[0]; // toFloorId
				const fromFloor = core.status.maps[ff],
					toFloor = core.status.maps[tf];
				const fhw = Math.floor(fromFloor.width / 2), // fromFloorHalfWidth
					fhh = Math.floor(fromFloor.height / 2),
					thw = Math.floor(toFloor.width / 2),
					thh = Math.floor(toFloor.height / 2);
				const fLoc = locs[id] ?? [0, 0];
				if (!locs[ff]) continue;
				let x, y;
				const dis = noBorder ? 1 : 5;
				if (locs && locs[tf]) {
					x = locs[tf][0];
					y = locs[tf][1];
				} else {
					// 计算坐标，公式可以通过画图推断出
					x = fLoc[0] - ha * (fhw - fx + tx - thw) - v * (fhw + thw + dis);
					y = fLoc[1] - va * (fhh - fy + ty - thh) - h * (fhh + thh + dis);
				};
				locs[tf] = locs[tf] ?? [x, y, toFloor.width, toFloor.height, core.hasVisitedFloor(tf)];
				// 添加连线
				lines[`${from}_${to}`] = [
					[
						fx - fhw + locs[ff][0],
						fy - fhh + locs[ff][1],
						x + tx - thw, y + ty - thh
					]
				];
			}
		}
		// 获取地图绘制需要的长宽
		let width = 0,
			height = 0;
		let left, right, up, down;
		for (const id in locs) {
			const [x, y, w, h] = locs[id];
			if (left === void 0) {
				left = right = x;
				up = down = y;
			}
			left = Math.min(x - w / 2 - 1, left);
			right = Math.max(x + w / 2 + 1, right);
			up = Math.min(y - h / 2 - 1, up);
			down = Math.max(y + h / 2 + 1, down);
		}
		width = right - left;
		height = down - up;
		// 所有地图和连线向右下移动，避免绘制出现问题
		for (const id in locs) {
			const loc = locs[id];
			loc[0] -= left; // 这时候left和up是负值，所以要减
			loc[1] -= up;
		}
		for (const route in lines) {
			const line = lines[route];
			for (const node of line) {
				node[0] -= left;
				node[1] -= up;
				node[2] -= left;
				node[3] -= up;
			}
		}

		return { locs, lines, width, height, layer: upOrDown };
	}

	/** 绘制背景 */
	function drawBack() {
		if (status !== 'none') return;
		new Back('__map_back__', 0, 0, core._PX_, core._PY_, 175, 'rgba(0, 0, 0, 0.9)');
		const listen = new Sprite(0, 0, core._PX_, core._PY_, 1000, 'game', '__map_listen__');
		addDrag(listen);
		const exit = new Button('__map_exit__', core._PX_ - 64, core._PY_ - 26, 60, 22, '退出');
		exit.addEventListener('click', close);
		sprites.listen = listen;
	}

	/** 绘制工具栏 */
	function drawTools() {
		new Back('__map_toolback__', 0, core._PY_ - 30, core._PX_, 30, 600, 'rgba(200, 200, 200, 0.9)');
		// 无边框
		const border = new Button('__map_border__', core._PX_ - 150, core._PY_ - 26, 60, 22, '边框');
		border.addEventListener('click', changeBorder);
		// 怪物数量
		const enemy = new Button('__map_enemy__', core._PX_ - 240, core._PY_ - 26, 60, 22, '怪物');
		enemy.addEventListener('click', triggerEnemy);
		// 区域显示
		const area = new Back('__map_areasback__', core._PX_ - 80, 0, 80, core._PY_ - 30, 550, 'rgba(200, 200, 200, 0.9)');
		drawAreaList();
		core.drawLine(area.context, 0, core._PY_ - 30, 80, core._PY_ - 30, '#222', 2);
	}

	/** 绘制区域列表 */
	function drawAreaList(transition = true) {
		const start = perPage * areaPage;
		Object.values(areaSprite).forEach(v => v.destroy());
		areaSprite = {};
		for (let i = start; i < start + perPage && areas[i]; i++) {
			const n = i % perPage;
			const { name, maps } = areas[i];
			const btn = new Button(`_area_${maps[0]}`, core._PX_ - 75, 4 + 30 * n, 70, 22, name, '16px', transition);
			areaSprite[maps[0]] = btn;
			if (i === nowArea) btn.setCss(`border: 2px solid gold;`);
			btn.addEventListener('click', e => {
				if (i === nowArea) return;
				changeArea(i);
			})
		}
		// 上一页下一页
		if (areaPage > 0) {
			const last = new Button('_area_last_', core._PX_ - 75, core._PY_ - 50, 30, 16, '上一页', '14px', transition);
			areaSprite._area_last_ = last;
			last.addEventListener('click', e => {
				areaPage--;
				drawAreaList(false);
			});
		}
		if (areaPage < Math.floor(areas.length / perPage)) {
			const next = new Button('_area_next_', core._PX_ - 35, core._PY_ - 50, 30, 16, '下一页', '14px', transition);
			areaSprite._area_next_ = next;
			next.addEventListener('click', e => {
				areaPage++;
				drawAreaList(false);
			});
		}
	}

	/** 
	 * 绘制大地图
	 * @param {MapDrawInfo} info 地图绘制信息
	 * @param {number} scale 地图的绘制比例
	 */
	function drawMap(info, scale = defaultValue.scale) {
		if (status === 'flyMap') return;
		const PX = core._PX_,
			PY = core._PY_;
		const w = info.width * scale,
			h = info.height * scale;
		const id = `__flyMap__`;
		const cx = PX / 2 - w / 2,
			cy = PY / 2 - h / 2;
		const map = new Sprite(cx, cy, w, h, 500, 'game', id);
		sprites[id] = map;
		canDrag[id] = map;
		map.canvas.className = 'fly-map';
		const ctx = map.context;
		core.clearMap(ctx);
		if (!noBorder) {
			const drawed = {}; // 绘制过的线
			// 先绘制连线
			const lines = info.lines;
			for (const route in lines) {
				const line = lines[route];
				for (const node of line) {
					const from = `${node[0]},${node[1]}`,
						to = `${node[2]},${node[3]}`;
					if (drawed[`${from}-${to}`] || drawed[`${to}-${from}`]) continue;
					drawed[`${from}-${to}`] = true;
					let lineWidth = scale / 2;
					core.drawLine(ctx, node[0] * scale, node[1] * scale, node[2] * scale, node[3] * scale, '#fff', lineWidth);
				}
			}
			// 再绘制楼层
			const locs = info.locs;
			for (const id in locs) {
				const loc = locs[id];
				let color = '#000';
				if (!loc[4]) color = '#f0f';
				const [x, y, w, h] = loc.map(v => typeof v === 'number' && v * scale);
				let dx = 0,
					dy = 0; // 避免绘图误差
				if (loc[2] % 2 === 0) dx = 0.5 * scale;
				if (loc[3] % 2 === 0) dy = 0.5 * scale;
				const fx = x - w / 2 - dx,
					fy = y - h / 2 - dy;
				core.fillRect(ctx, fx, fy, w, h, color);
				if (id === selecting) core.strokeRect(ctx, fx, fy, w, h, 'gold', scale / 2);
				else core.strokeRect(ctx, fx, fy, w, h, '#fff', scale / 2);
				const layer = info.upOrDown[id];
				const min = Math.min(w, h);
				if (layer?.includes('upFloor'))
					core.drawIcon(ctx, defaultChange.upFloor, fx, fy, min / 3, min / 3);
				if (layer?.includes('downFloor'))
					core.drawIcon(ctx, defaultChange.downFloor, fx + w - min / 3, fy + h - min / 3, min / 3, min / 3);
				// 显示漏怪数量
				if (showEnemy) {
					ctx.textAlign = 'center';
					ctx.textBaseline = 'middle';
					const c = `${drawingMap}_${nowDepth}_${noBorder}`;
					const n = Object.keys(mapCache[c].enemies[id]).length;
					color = '#3f3';
					if (n > 0) color = '#fff';
					if (n > 10) color = '#fc3';
					if (n > 20) color = '#f22';
					ctx.shadowBlur = 0.6 * nowScale;
					ctx.shadowColor = '#000';
					core.fillText(ctx, `怪物数量：${n}`, x, y, color, `${2 * nowScale}px normal`);
					ctx.shadowBlur = 0;
				}
			}
		}
		checkThumbnail();
	}

	/** 
	 * 重新绘制缩略图
	 * @param {Sprite} sprite
	 * @param {string} floor
	 */
	function drawThumbnail(sprite, floor, x, y, w, h) {
		const ctx = sprite.context;
		const scale = nowScale;
		core.drawThumbnail(floor, void 0, {
			ctx: ctx,
			x: x - w / 2,
			y: y - h / 2,
			damage: true,
			all: true,
			size: Math.max(w, h) / Math.max(core._PX_, core._PY_),
			fromMap: true
		});
		const color = floor === core.status.floorId ? 'gold' : '#fff'
		if (!noBorder)
			core.strokeRect(ctx, x - w / 2, y - h / 2, w, h, color, scale / 2);
	}

	/** 检查是否需要绘制缩略图 */
	function checkThumbnail() {
		const id = `${drawingMap}_${nowDepth}_${noBorder}`;
		const locs = drawCache[id].locs;
		const map = canDrag[`__flyMap__`];
		for (const id in locs) {
			const loc = locs[id];
			const scale = nowScale;
			const [x, y, w, h] = loc.map(v => typeof v === 'number' && v * scale);
			let dx = 0,
				dy = 0; // 避免绘图误差
			if (loc[2] % 2 === 0) dx = 0.5 * scale;
			if (loc[3] % 2 === 0) dy = 0.5 * scale;
			if (!drawedThumbnail[id] && x + map.x > 0 && x + map.x < core._PX_ &&
				y + map.y > 0 && y + map.y < core._PY_) {
				if (!noBorder && core.hasVisitedFloor(id) && scale > 5) {
					drawThumbnail(map, id, x - dx, y - dy, w, h);
					drawedThumbnail[id] = true;
				}
				if (noBorder) {
					drawThumbnail(map, id, x - dx, y - dy, w, h)
					drawedThumbnail[id] = true;
					if (!core.hasVisitedFloor(id))
						core.fillRect(map.context, x - dx - w / 2, y - dy - h / 2, w, h, 'rgba(255,0,255,0.2)');
				}
			}
		}
		// 如果是无边框模式，那就只绘制当前地图的边框
		if (noBorder) {
			const loc = locs[selecting];
			const scale = nowScale;
			if (loc) {
				const [x, y, w, h] = loc.map(v => typeof v === 'number' && v * scale);
				core.strokeRect(map.context, x - w / 2, y - h / 2, w, h, 'gold', scale / 2);
			}
		}
	}

	/** 检查点击点是否在以x,y为中心的某一矩形中 */
	function inRect(x, y, w, h, px, py) {
		x -= w / 2;
		y -= h / 2;
		return px > x && px < x + w && py > y && py < y + h;
	}

	/** 测试画布是否超过上限，摘自https://github.com/jhildenbiddle/canvas-size */
	function canvasTest(size) {
		const width = Math.max(Math.ceil(size[0]), 1);
		const height = Math.max(Math.ceil(size[1]), 1);
		if (width === 0 || height === 0) return true;
		const fill = [width - 1, height - 1, 1, 1];
		let cropCvs, testCvs;
		cropCvs = document.createElement("canvas");
		cropCvs.width = 1;
		cropCvs.height = 1;
		testCvs = document.createElement("canvas");
		testCvs.width = width;
		testCvs.height = height;
		const cropCtx = cropCvs.getContext("2d");
		const testCtx = testCvs.getContext("2d");
		if (testCtx) {
			testCtx.fillRect.apply(testCtx, fill);
			cropCtx.drawImage(testCvs, width - 1, height - 1, 1, 1, 0, 0, 1, 1);
		}
		const isTestPass = cropCtx && cropCtx.getImageData(0, 0, 1, 1).data[3] !== 0;
		return isTestPass;
	}

	/** 检查浏览器限制 */
	function checkMaximum(before, scale) {
		for (const id in canDrag) {
			const sprite = canDrag[id];
			const rate = scale / before;
			const w = sprite.width * rate * core.domStyle.scale,
				h = sprite.height * rate * core.domStyle.scale;
			const valid = canvasTest([w, h]);
			if (!valid) {
				core.drawTip('画布大小将超过浏览器限制！请勿继续放大！');
				return true;
			}
		}
		return false;
	}

	/** 关闭事件 */
	function close() {
		document.body.removeEventListener('keyup', keyboard);
		Object.values(sprites).forEach((v) => {
			v.setCss('transition: opacity 0.6s linear;');
		});
		setTimeout(() => {
			Object.values(sprites).forEach((v) => {
				v.setCss('opacity: 0;');
			});
		}, 50);
		setTimeout(() => {
			core.unlockControl();
			Object.values(sprites).forEach((v) => {
				v.destroy();
			});
			drawedThumbnail = {};
			sprites = {};
			canDrag = {};
			status = 'none';
			core.canvas.data.canvas.style.zIndex = '170';
		}, 650);
	}

	/** 
	 * 点击地图事件，尝试楼层传送
	 * @param {MouseEvent} e
	 */
	function clickMap(e) {
		if (moved) return moved = false;
		const { x, y } = core.actions._getClickLoc(e.clientX, e.clientY);
		let px = x / core.domStyle.scale,
			py = y / core.domStyle.scale;
		const scale = nowScale;
		const id = `${drawingMap}_${nowDepth}_${noBorder}`;
		const locs = drawCache[id].locs;
		const sprite = canDrag.__flyMap__;
		px -= sprite.x;
		py -= sprite.y;
		for (const id in locs) {
			const loc = locs[id];
			const [x, y, w, h] = loc.map(v => typeof v === 'number' && v * scale);
			if (inRect(x, y, w, h, px, py)) {
				return flyTo(id);
			}
		}
	}

	/** 飞向某个楼层 */
	function flyTo(id) {
		if (!core.hasItem('fly')) return core.drawTip('你没有楼层传送器');
		sprites.__map_back__.setCss('opacity: 0.2;');
		return core.flyTo(id, () => setTimeout(() => {
			if (sprites.__map_back__) core.lockControl();
		}, 100));
	}

	/** 
	 * 拖拽事件
	 * @param {MouseEvent} e
	 */
	function drag(e) {
		if (!clicking) return;
		const scale = core.domStyle.scale
		moveEle(e.movementX / scale, e.movementY / scale);
	}

	/**
	 * 手机端点击拖动事件
	 * @param {TouchEvent} e
	 * @this {HTMLCanvasElement}
	 */
	function touchDrag(e) {
		moved = true;
		const scale = core.domStyle.scale;
		if (e.touches.length === 1) { // 拖拽
			const info = e.touches[0];
			if (!lastTouch[this.id]) {
				lastTouch[this.id] = [info.clientX, info.clientY];
				return;
			}
			const { clientX: x, clientY: y } = info;
			const dx = x - lastTouch[this.id][0],
				dy = y - lastTouch[this.id][1];
			moveEle(dx / scale, dy / scale);
			lastTouch[this.id] = [info.clientX, info.clientY];
		} else if (e.touches.length >= 2) { // 双指放缩
			const first = e.touches[0],
				second = e.touches[1];
			const dx = first.clientX - second.clientX,
				dy = first.clientY - second.clientY;
			if (lastLength === 0) {
				lastLength = Math.sqrt(dx * dx + dy * dy);
				return;
			}
			let cx = (first.clientX + second.clientX) / 2,
				cy = (first.clientY + second.clientY) / 2;
			const { x, y } = core.actions._getClickLoc(cx, cy);
			const mx = x / scale;
			const my = y / scale;
			const length = Math.sqrt(dx * dx + dy * dy);
			const delta = (lastLength / length) ** (1 / 3);
			const info = {};
			for (const id in canDrag) {
				const sprite = canDrag[id];
				const sx = sprite.x + sprite.width / 2,
					sy = sprite.y + sprite.height / 2;
				const dx = sx - mx,
					dy = sy - my;
				info[id] = [mx + dx * delta, my + dy * delta];
			}
			scaleMap(delta * nowScale, info);
		}
	}

	/** 
	 * 滚轮缩放
	 * @param {WheelEvent} e
	 */
	function wheel(e) {
		const delta = 1 - Math.sign(e.deltaY) / 10;
		const { x, y } = core.actions._getClickLoc(e.clientX, e.clientY);
		const scale = core.domStyle.scale;
		const mx = x / scale,
			my = y / scale;
		const info = {};
		for (const id in canDrag) {
			const sprite = canDrag[id];
			const cx = sprite.x + sprite.width / 2,
				cy = sprite.y + sprite.height / 2;
			const dx = cx - mx,
				dy = cy - my;
			info[id] = [mx + dx * delta, my + dy * delta];
		}
		scaleMap(delta * nowScale, info);
	}

	/** 切换边框 */
	function changeBorder() {
		noBorder = !noBorder;
		redraw('border');
	}

	/** 切换是否显示漏怪数量 */
	function triggerEnemy() {
		showEnemy = !showEnemy;
		redraw('enemy');
	}

	/** 改变区域 */
	function changeArea(index) {
		nowArea = index;
		drawAreaList(false);
		drawedThumbnail = {};
		status = 'area';
		nowScale = defaultValue.scale;
		drawMap(core.plugin.getMapDrawInfo(areas[index].maps[0]));
	}

	/** 重绘 */
	function redraw(id, px, py, move = true) {
		const { x, y } = canDrag.__flyMap__;
		status = id;
		drawedThumbnail = {};
		drawMap(core.plugin.getMapDrawInfo(drawingMap, nowDepth, true), nowScale);
		if (move) canDrag.__flyMap__.move(px ?? x, py ?? y);
		checkThumbnail();
	}

	/** 
	 * 拖拽时移动需要元素
	 * @param {string} dx
	 * @param {string} dy
	 */
	function moveEle(dx, dy) {
		moved = true;
		for (const id in canDrag) {
			const sprite = canDrag[id];
			const ctx = sprite.context;
			sprite.x += dx;
			sprite.y += dy;
			core.relocateCanvas(ctx, dx, dy, true);
		}
		checkThumbnail();
	}

	/** 
	 * 缩放绘制地图
	 * @param {number} target 目标缩放比例
	 * @param {{[x: string]: [number, number]}} info 缩放后的sprite位置数据
	 */
	function scaleMap(target, info) {
		// 检查浏览器限制
		if (checkMaximum(nowScale, target)) return;
		clearTimeout(lastScale);
		const [x, y] = info.__flyMap__;
		// 先直接修改style，延迟200ms再绘制，进行性能优化
		const sprite = canDrag.__flyMap__;
		const rate = target / nowScale;
		nowScale = target;
		sprite.resize(sprite.width * rate, sprite.height * rate, true);
		sprite.move(x - sprite.width / 2, y - sprite.height / 2);
		lastScale = setTimeout(() => {
			redraw('scale', x - sprite.width / 2, y - sprite.height / 2);
		}, 200)
	}

	/** 键盘操作
	 * @param {KeyboardEvent} e
	 */
	function keyboard(e) {
		if (e.key === 'Enter' || e.key === 'C' || e.key === 'c' || e.key === ' ') {
			return flyTo(selecting);
		} else if (e.key === 'Escape' || e.key === 'x' || e.key === 'X') {
			return close();
		} else if (e.key.startsWith('Arrow')) {
			const dir = e.key.slice(5).toLowerCase();
			// 获取目标楼层
			const res = mapCache[`${drawingMap}_${nowDepth}_${noBorder}`].res;
			const key = Object.keys(res)
				.find(v => {
					const [floorId, x, y, d] = v.split('_');
					return floorId === selecting && d === dir;
				});
			if (!key) return;
			const target = res[key].split('_')[0];
			selecting = target;
			redraw('key');
		}
	}

	/**
	 * 给需要的元素添加拖拽等事件
	 * @param {HTMLCanvasElement} ele
	 */
	function addDrag(ele) {
		ele.addEventListener('wheel', wheel);
		ele.addEventListener('mousemove', drag);
		ele.addEventListener('touchmove', touchDrag);
		ele.addEventListener('click', clickMap);
		ele.addEventListener('mousedown', () => { clicking = true; });
		ele.addEventListener('mouseup', () => { clicking = false; });
		ele.addEventListener('touchend', () => {
			lastTouch = {};
			lastLength = 0;
		});
		document.body.addEventListener('keyup', keyboard);
	}

	maps.prototype._drawThumbnail_drawToTarget = function (floorId, options) {
		const ctx = core.getContextByName(options.ctx);
		if (ctx == null) return;
		const x = options.x || 0,
			y = options.y || 0,
			size = options.size || 1;
		// size的含义改为(0,1]范围的系数以适配长方形，默认为1，楼传为3/4，SL界面为0.3
		let w = Math.ceil(size * core._PX_),
			h = Math.ceil(size * core._PY_);
		// 特判是否为编辑器，编辑器中长宽均采用core.js的遗留正方形像素边长，以保证下面的绘制正常
		if (main.mode == 'editor') w = h = size * core.__PIXELS__;
		const width = core.floors[floorId].width,
			height = core.floors[floorId].height;
		let centerX = options.centerX,
			centerY = options.centerY;
		if (centerX == null) centerX = Math.floor(width / 2);
		if (centerY == null) centerY = Math.floor(height / 2);
		const tempCanvas = core.bigmap.tempCanvas;

		if (options.all) {
			const tempWidth = tempCanvas.canvas.width,
				tempHeight = tempCanvas.canvas.height;
			// 绘制全景图
			if (tempWidth <= tempHeight) {
				const realHeight = h,
					realWidth = realHeight * tempWidth / tempHeight;
				const side = (w - realWidth) / 2;
				if (options.fromMap) {
					return core.drawImage(ctx, tempCanvas.canvas, 0, 0, tempWidth, tempHeight, x, y, realWidth, realHeight);
				}
				core.fillRect(ctx, x, y, side, realHeight, '#000000');
				core.fillRect(ctx, x + w - side, y, side, realHeight);
				core.drawImage(ctx, tempCanvas.canvas, 0, 0, tempWidth, tempHeight, x + side, y, realWidth, realHeight);
			} else {
				const realWidth = w,
					realHeight = realWidth * tempHeight / tempWidth;
				const side = (h - realHeight) / 2;
				if (options.fromMap) {
					return core.drawImage(ctx, tempCanvas.canvas, 0, 0, tempWidth, tempHeight, x, y, realWidth, realHeight);
				}
				core.fillRect(ctx, x, y, realWidth, side, '#000000');
				core.fillRect(ctx, x, y + h - side, realWidth, side);
				core.drawImage(ctx, tempCanvas.canvas, 0, 0, tempWidth, tempHeight, x, y + side, realWidth, realHeight);
			}
		} else {
			// 只绘制可见窗口
			let pw = core._PX_,
				ph = core._PY_,
				hw = core._HALF_WIDTH_,
				hh = core._HALF_HEIGHT_,
				W = core._WIDTH_,
				H = core._HEIGHT_;
			const ratio = core.domStyle.isVertical ? core.domStyle.ratio : core.domStyle.scale;
			if (main.mode == 'editor') {
				pw = ph = core.__PIXELS__;
				hw = hh = core.__HALF_SIZE__;
				W = H = core.__SIZE__;
			}
			if (options.v2) {
				core.drawImage(ctx, tempCanvas.canvas, 0, 0, pw * ratio, ph * ratio, x, y, w, h);
			} else {
				const offsetX = core.clamp(centerX - hw, 0, width - W),
					offsetY = core.clamp(centerY - hh, 0, height - H);
				if (options.noHD) {
					core.drawImage(ctx, tempCanvas.canvas, offsetX * 32, offsetY * 32, pw, ph, x, y, w, h);
					return;
				}
				core.drawImage(ctx, tempCanvas.canvas, offsetX * 32 * ratio, offsetY * 32 * ratio, pw * ratio, ph * ratio, x, y, w, h);
			}
		}
	}
},
    "漏怪检测": function () {
	// 检查漏怪
	this.checkEnemy = function (floorIds) {
		var enemys = [],
			result = "当前剩余记忆碎片：\n",
			have = false,
			skipEnemy = [], // 略过怪物
			whetherSkip = false;

		// 是否为略过怪物
		function skip(enemy) {
			for (var a = 0; a < skipEnemy.length; a++) {
				if (enemy.id == skipEnemy[a]) {
					whetherSkip = true;
				}
			}
			return whetherSkip;
		}
		// 检查漏怪
		for (var i = 1; i < floorIds.length; i++) {
			var floorId = floorIds[i];
			if (core.enemys.getCurrentEnemys(floorId).length > 0) {
				// 若当前地图的怪物数量大于0，则遍历每个图块
				core.status.maps[floorId].blocks.forEach(function (block) {
					if (core.isset(block.event) && !block.disable) {
						var id = block.event.id,
							enemy = core.material.enemys[id];
						if (enemy) { // 是否是怪物 是否略过
							if (!skip(enemy)) {
								if (enemys.length == 0) {
									enemys.push([enemy.id, enemy.name, 1, floorId]);
								} else {
									for (var j = 0; j < enemys.length; j++) { // 是否重复
										if (enemys[j][0] == enemy.id && enemys[j][3] == floorId) {
											enemys[j][2]++; // 重复则数量+1
											have = true;
											break;
										}
									}
									if (!have) { // 不重复新增一项
										enemys.push([enemy.id, enemy.name, 1, floorId]);
									}
									have = false; // 重置
								}
							}
							whetherSkip = false; // 重置
						}
					}
				});
			}
		}
		if (enemys.length > 0) { // 楼层名
			for (var i = 0; i < enemys.length; i++) {
				enemys[i][3] = core.floors[enemys[i][3]].title + "（" + enemys[i][3] + "）";
			}
		}
		if (enemys.length > 0 && enemys.length < 15) { // 输出结果 个数较少
			for (var i = 0; i < enemys.length; i++) {
				result += enemys[i][3] + ":" + enemys[i][1] + " × " + enemys[i][2];
				if (i != enemys.length - 1) result += "\n";
			}
		} else if (enemys.length == 0) { // 没有怪物
			result = "当前无剩余记忆碎片"
		} else { // 怪物较多
			var number = 0;
			for (var i = 0; i < enemys.length; i++) {
				number += enemys[i][2]; // 总个数
			}
			result = "当前剩余记忆碎片较多，请手动检查。当前剩余" + number + "个\n分布楼层：\n";
			for (var j = 0; j < 100; j++) { // 除去重复楼层
				for (var i = 0; i < enemys.length - 1; i++) {
					if (enemys[i][3] == enemys[i + 1][3]) {
						enemys.splice(i, 1);
					}
				}
			}
			for (var i = 0; i < enemys.length; i++) { // 结果
				result += enemys[i][3];
				if (i != enemys.length - 1) result += "\n";
			}
		}
		core.setFlag("remainEnemy", result); // 赋给flag
		core.setFlag("enemyNumber", enemys.length);
		core.insertAction([{
			"type": "if",
			"condition": "(flag:enemyNumber == 0)",
			"true": [
				"${flag:remainEnemy}",
				"可以继续前进了",
				{ "type": "hide", "remove": true },
			],
			"false": [
				"${flag:remainEnemy}",
			]
		}, ]);
		return;
	}
},
    "手册2": function () {
	// 在此增加新插件
	this.getStatusLabelSecond =
		function (name) {
			return {
				name: "名称",
				lv: "等级",
				hpmax: "生命上限",
				hp: "印象",
				manamax: "魔力上限",
				mana: "魔力",
				atk: "伤痛",
				def: "深刻",
				mdef: "护盾",
				money: "金币",
				exp: "经验",
				point: "加点",
				steps: "行程",
			} [name] || name;
		}
},
    "复写手册": function () {
	// 在此增加新插件
	////// 绘制怪物手册 //////
	ui.prototype.drawBook = function (index) {
		var floorId = core.floorIds[(core.status.event.ui || {}).index] || core.status.floorId;
		// 清除浏览地图时的光环缓存
		if (floorId != core.status.floorId && core.status.checkBlock) {
			core.status.checkBlock.cache = {};
		}
		var enemys = core.enemys.getCurrentEnemys(floorId);
		core.clearUI();
		core.clearMap('data');
		// 生成groundPattern
		core.maps.generateGroundPattern(floorId);
		this._drawBook_drawBackground();
		core.setAlpha('ui', 1);

		if (enemys.length == 0) {
			return this._drawBook_drawEmpty();
		}

		index = core.clamp(index, 0, enemys.length - 1);
		core.status.event.data = index;
		var pageinfo = this._drawBook_pageinfo();
		var perpage = pageinfo.per_page,
			page = parseInt(index / perpage) + 1,
			totalPage = Math.ceil(enemys.length / perpage);

		var start = (page - 1) * perpage;
		enemys = enemys.slice(start, page * perpage);

		for (var i = 0; i < enemys.length; i++)
			this._drawBook_drawOne(floorId, i, enemys[i], pageinfo, index == start + i);

		core.drawBoxAnimate();
		this.drawPagination(page, totalPage);
		core.setTextAlign('ui', 'center');
		core.fillText('ui', '返回游戏', core._PX_ - 46, core._PY_ - 13, '#DDDDDD', this._buildFont(15, true));
	}

	ui.prototype._drawBook_pageinfo = function () {
		var per_page = core._HALF_HEIGHT_;
		var padding_top = 12; // 距离顶端像素
		var per_height = (core._PY_ - 32 - padding_top) / per_page;
		return { per_page: per_page, padding_top: padding_top, per_height: per_height };
	}

	ui.prototype._drawBook_drawBackground = function () {
		core.setAlpha('ui', 1);
		core.setFillStyle('ui', core.material.groundPattern);
		core.fillRect('ui', 0, 0, core._PX_, core._PY_);

		core.setAlpha('ui', 0.6);
		core.setFillStyle('ui', '#000000');
		core.fillRect('ui', 0, 0, core._PX_, core._PY_);
	}

	ui.prototype._drawBook_drawEmpty = function () {
		core.setTextAlign('ui', 'center');
		core.fillText('ui', "本层无怪物", core._PX_ / 2, core._PY_ / 2 + 14, '#999999', this._buildFont(50, true));
		core.fillText('ui', '返回游戏', core._PX_ - 46, core._PY_ - 13, '#DDDDDD', this._buildFont(15, true));
	}

	ui.prototype._drawBook_drawOne = function (floorId, index, enemy, pageinfo, selected) {
		var top = pageinfo.per_height * index + pageinfo.padding_top; // 最上面margin默认是12px
		enemy.floorId = floorId;
		// 横向规划：
		// 22 + 42 = 64 是头像框
		this._drawBook_drawBox(index, enemy, top, pageinfo);
		var left = 64,
			total_width = core._PX_ - left;
		var name_width = total_width * 10 / 35;
		this._drawBook_drawName(index, enemy, top, left, name_width);
		this._drawBook_drawContent(index, enemy, top, left + name_width);
		if (selected)
			core.strokeRoundRect('ui', 10, top + 1, core._PX_ - 10 * 2, pageinfo.per_height, 10, core.status.globalAttribute.selectColor);
	}

	ui.prototype._drawBook_is32x32 = function (blockInfo) {
		// 判定48的怪物上半部分是否是全透明
		var height = blockInfo.height - 32;
		var canvas = document.createElement('canvas');
		canvas.width = 32;
		canvas.height = height;
		var ctx = canvas.getContext("2d");
		core.drawImage(ctx, blockInfo.image, 0, blockInfo.posY * blockInfo.height, 32, height, 0, 0, 32, height);
		var url = canvas.toDataURL();
		core.clearMap(ctx);
		return url == canvas.toDataURL();
	}

	ui.prototype._drawBook_drawBox = function (index, enemy, top, pageinfo) {
		// 横向：22+42；纵向：10 + 42 + 10（正好居中）；内部图像 32x32
		var border_top = top + (pageinfo.per_height - 42) / 2,
			border_left = 22;
		var img_top = border_top + 5,
			img_left = border_left + 5;
		core.strokeRect('ui', 22, border_top, 42, 42, '#DDDDDD', 2);
		var blockInfo = core.getBlockInfo(enemy.id);

		// 检查大怪物
		if (blockInfo.bigImage) {
			core.status.boxAnimateObjs.push({
				bigImage: blockInfo.bigImage,
				face: blockInfo.face,
				centerX: border_left + 21,
				centerY: border_top + 21,
				max_width: 60
			});
		} else if (blockInfo.height >= 42) {
			var originEnemy = core.material.enemys[enemy.id] || {};
			// 检查上半部分是不是纯透明的；取用原始值避免重复计算
			if (originEnemy.is32x32 == null) {
				originEnemy.is32x32 = this._drawBook_is32x32(blockInfo);
			}
			if (originEnemy.is32x32) {
				core.status.boxAnimateObjs.push({
					'bgx': border_left,
					'bgy': border_top,
					'bgWidth': 42,
					'bgHeight': 42,
					'x': img_left,
					'y': img_top,
					'height': 32,
					'animate': blockInfo.animate,
					'image': blockInfo.image,
					'pos': blockInfo.posY * blockInfo.height + blockInfo.height - 32
				});
			} else {
				var drawWidth = 42 * 32 / blockInfo.height;
				core.status.boxAnimateObjs.push({
					'bgx': border_left,
					'bgy': border_top,
					'bgWidth': 42,
					'bgHeight': 42,
					'x': img_left - 5 + (42 - drawWidth) / 2,
					'y': img_top - 5,
					'dw': drawWidth,
					'dh': 42,
					'height': blockInfo.height,
					'animate': blockInfo.animate,
					'image': blockInfo.image,
					'pos': blockInfo.posY * blockInfo.height
				});
			}
		} else {
			core.status.boxAnimateObjs.push({
				'bgx': border_left,
				'bgy': border_top,
				'bgWidth': 42,
				'bgHeight': 42,
				'x': img_left,
				'y': img_top,
				'height': 32,
				'animate': blockInfo.animate,
				'image': blockInfo.image,
				'pos': blockInfo.posY * blockInfo.height
			});
		}
	}

	ui.prototype._drawBook_drawName = function (index, enemy, top, left, width) {
		// 绘制第零列（名称和特殊属性）
		// 如果需要添加自己的比如怪物的称号等，也可以在这里绘制
		core.setTextAlign('ui', 'center');
		if (enemy.specialText.length == 0) {
			core.fillText('ui', enemy.name, left + width / 2,
				top + 35, '#DDDDDD', this._buildFont(17, true), width);
		} else {
			core.fillText('ui', enemy.name, left + width / 2,
				top + 28, '#DDDDDD', this._buildFont(17, true), width);
			switch (enemy.specialText.length) {
			case 1:
				core.fillText('ui', enemy.specialText[0], left + width / 2,
					top + 50, core.arrayToRGBA((enemy.specialColor || [])[0] || '#FF6A6A'),
					this._buildFont(15, true), width);
				break;
			case 2:
				// Step 1: 计算字体
				var text = enemy.specialText[0] + "  " + enemy.specialText[1];
				core.setFontForMaxWidth('ui', text, width, this._buildFont(15, true));
				// Step 2: 计算总宽度
				var totalWidth = core.calWidth('ui', text);
				var leftWidth = core.calWidth('ui', enemy.specialText[0]);
				var rightWidth = core.calWidth('ui', enemy.specialText[1]);
				// Step 3: 绘制
				core.fillText('ui', enemy.specialText[0], left + (width + leftWidth - totalWidth) / 2,
					top + 50, core.arrayToRGBA((enemy.specialColor || [])[0] || '#FF6A6A'));
				core.fillText('ui', enemy.specialText[1], left + (width + totalWidth - rightWidth) / 2,
					top + 50, core.arrayToRGBA((enemy.specialColor || [])[1] || '#FF6A6A'));
				break;
			default:
				core.fillText('ui', '多属性...', left + width / 2,
					top + 50, '#FF6A6A', this._buildFont(15, true), width);
			}
		}
	}

	ui.prototype._drawBook_drawContent = function (index, enemy, top, left) {
		var width = core._PX_ - left; // 9 : 8 : 8 划分三列
		this._drawBook_drawRow1(index, enemy, top, left, width, top + 20);
		this._drawBook_drawRow2(index, enemy, top, left, width, top + 38);
		this._drawBook_drawRow3(index, enemy, top, left, width, top + 56);
	}

	ui.prototype._drawBook_drawRow1 = function (index, enemy, top, left, width, position) {
		// 绘制第一行
		core.setTextAlign('ui', 'left');
		var b13 = this._buildFont(13, true),
			f13 = this._buildFont(13, false);
		var col1 = left,
			col2 = left + width * 9 / 25,
			col3 = left + width * 17 / 25;
		core.fillText('ui', core.getStatusLabelSecond('hp'), col1, position, '#DDDDDD', f13);
		core.fillText('ui', core.formatBigNumber(enemy.hp || 0), col1 + 30, position, null, b13);
		core.fillText('ui', core.getStatusLabelSecond('atk'), col2, position, null, f13);
		core.fillText('ui', core.formatBigNumber(enemy.atk || 0), col2 + 30, position, null, b13);
		core.fillText('ui', core.getStatusLabelSecond('def'), col3, position, null, f13);
		core.fillText('ui', core.formatBigNumber(enemy.def || 0), col3 + 30, position, null, b13);
	}

	ui.prototype._drawBook_drawRow2 = function (index, enemy, top, left, width, position) {
		// 绘制第二行
		core.setTextAlign('ui', 'left');
		var b13 = this._buildFont(13, true),
			f13 = this._buildFont(13, false);
		var col1 = left,
			col2 = left + width * 9 / 25,
			col3 = left + width * 17 / 25;
		// 获得第二行绘制的内容
		var second_line = [];
		if (core.flags.statusBarItems.indexOf('enableMoney') >= 0) second_line.push([core.getStatusLabelSecond('money'), core.formatBigNumber(enemy.money || 0)]);
		if (core.flags.enableAddPoint) second_line.push([core.getStatusLabelSecond('point'), core.formatBigNumber(enemy.point || 0)]);
		if (core.flags.statusBarItems.indexOf('enableExp') >= 0) second_line.push([core.getStatusLabelSecond('exp'), core.formatBigNumber(enemy.exp || 0)]);

		var damage_offset = col1 + (core._PX_ - col1) / 2 - 12;
		// 第一列
		if (second_line.length > 0) {
			var one = second_line.shift();
			core.fillText('ui', one[0], col1, position, '#DDDDDD', f13);
			core.fillText('ui', one[1], col1 + 30, position, null, b13);
			damage_offset = col2 + (core._PX_ - col2) / 2 - 12;
		}
		// 第二列
		if (second_line.length > 0) {
			var one = second_line.shift();
			core.fillText('ui', one[0], col2, position, '#DDDDDD', f13);
			core.fillText('ui', one[1], col2 + 30, position, null, b13);
			damage_offset = col3 + (core._PX_ - col3) / 2 - 12;
		}
		// 忽略第三列，直接绘制伤害
		this._drawBook_drawDamage(index, enemy, damage_offset, position);
	}

	ui.prototype._drawBook_drawRow3 = function (index, enemy, top, left, width, position) {
		// 绘制第三行
		core.setTextAlign('ui', 'left');
		var b13 = this._buildFont(13, true),
			f13 = this._buildFont(13, false);
		var col1 = left,
			col2 = left + width * 9 / 25,
			col3 = left + width * 17 / 25;
		core.fillText('ui', '临界', col1, position, '#DDDDDD', f13);
		core.fillText('ui', core.formatBigNumber(enemy.critical || 0), col1 + 30, position, null, b13);
		core.fillText('ui', '减伤', col2, position, null, f13);
		core.fillText('ui', core.formatBigNumber(enemy.criticalDamage || 0), col2 + 30, position, null, b13);
		core.fillText('ui', '承受', col3, position, null, f13);
		core.fillText('ui', core.formatBigNumber(enemy.defDamage || 0), col3 + 30, position, null, b13);
	}

	ui.prototype._drawBook_drawDamage = function (index, enemy, offset, position) {
		core.setTextAlign('ui', 'center');
		var damage = enemy.damage,
			color = '#FFFF00';
		if (damage == null) {
			damage = '无法战斗';
			color = '#FF2222';
		} else {
			if (damage >= core.status.hero.hp) color = '#FF2222';
			else if (damage >= core.status.hero.hp * 2 / 3) color = '#FF9933';
			else if (damage <= 0) color = '#11FF11';
			damage = core.formatBigNumber(damage);
			if (core.enemys.hasSpecial(enemy, 19)) damage += "+";
			if (core.enemys.hasSpecial(enemy, 21)) damage += "-";
			if (core.enemys.hasSpecial(enemy, 11)) damage += "^";
		}
		if (enemy.notBomb) damage += "[b]";
		core.fillText('ui', damage, offset, position, color, this._buildFont(13, true));
	}

	////// 绘制怪物属性的详细信息 //////
	ui.prototype._drawBookDetail = function (index) {
		var info = this._drawBookDetail_getInfo(index),
			enemy = info[0];
		if (!enemy) return;
		var content = info[1].join("\n");
		core.status.event.id = 'book-detail';
		core.animateFrame.tip = null;
		core.clearMap('data');

		var left = 10,
			width = core._PX_ - 2 * left,
			right = left + width;
		var content_left = left + 25,
			validWidth = right - content_left - 13;
		var height = Math.max(this.getTextContentHeight(content, { fontSize: 16, lineHeight: 24, maxWidth: validWidth }) + 58, 80),
			top = (core._PY_ - height) / 2,
			bottom = top + height;

		core.setAlpha('data', 0.9);
		core.fillRect('data', left, top, width, height, '#000000');
		core.setAlpha('data', 1);
		core.strokeRect('data', left - 1, top - 1, width + 1, height + 1,
			core.arrayToRGBA(core.status.globalAttribute.borderColor), 2);
		core.playSound('确定');
		this._drawBookDetail_drawContent(enemy, content, { top: top, content_left: content_left, bottom: bottom, validWidth: validWidth });
	}

	ui.prototype._drawBookDetail_getInfo = function (index) {
		var floorId = core.floorIds[(core.status.event.ui || {}).index] || core.status.floorId;
		// 清除浏览地图时的光环缓存
		if (floorId != core.status.floorId && core.status.checkBlock) {
			core.status.checkBlock.cache = {};
		}
		var enemys = core.enemys.getCurrentEnemys(floorId);
		if (enemys.length == 0) return [];
		index = core.clamp(index, 0, enemys.length - 1);
		var enemy = enemys[index],
			enemyId = enemy.id;
		var texts = core.enemys.getSpecialHint(enemyId);
		if (texts.length == 0) texts.push("该记忆碎片无特殊属性。");
		if (enemy.description) texts.push(enemy.description + "\r");
		texts.push("");
		this._drawBookDetail_getTexts(enemy, floorId, texts);
		return [enemy, texts];
	}

	ui.prototype._drawBookDetail_getTexts = function (enemy, floorId, texts) {
		// --- 原始数值
		this._drawBookDetail_origin(enemy, texts);
		// --- 模仿临界计算器
		this._drawBookDetail_mofang(enemy, texts);
		// --- 吸血怪最低生命值
		this._drawBookDetail_vampire(enemy, floorId, texts);
		// --- 仇恨伤害
		this._drawBookDetail_hatred(enemy, texts);
		// --- 战斗回合数，临界表
		this._drawBookDetail_turnAndCriticals(enemy, floorId, texts);
	}

	ui.prototype._drawBookDetail_origin = function (enemy, texts) {
		// 怪物数值和原始值不一样时，在详细信息页显示原始数值
		var originEnemy = core.enemys._getCurrentEnemys_getEnemy(enemy.id);
		var content = [];
		if (enemy.locs != null && enemy.locs.length >= 0) {
			texts.push("\r[#FF6A6A]\\d记忆碎片坐标：\\d\r[]" + JSON.stringify(enemy.locs));
		}
		["hp", "atk", "def", "point", "money", "exp"].forEach(function (one) {
			if (enemy[one] == null || originEnemy[one] == null) return;
			if (enemy[one] != originEnemy[one]) {
				content.push(core.getStatusLabelSecond(one) + " " + originEnemy[one]);
			}
		});
		if (content.length > 0) {
			texts.push("\r[#FF6A6A]\\d原始数值：\\d\r[]" + content.join("；"));
		}
	}

	ui.prototype._drawBookDetail_mofang = function (enemy, texts) {
		// 模仿临界计算器
		if (core.enemys.hasSpecial(enemy.special, 10)) {
			var hp = enemy.hp;
			var delta = core.status.hero.atk - core.status.hero.def;
			if (delta < hp && hp <= 10000 && hp > 0) {
				texts.push("\r[#FF6A6A]\\d模仿临界计算器：\\d\r[]（当前攻防差" + core.formatBigNumber(delta) + "）");
				var u = [];
				this._drawBookDetail_mofang_getArray(hp).forEach(function (t) {
					if (u.length < 20) u.push(t);
					else if (Math.abs(t[0] - delta) < Math.abs(u[0][0] - delta)) {
						u.shift();
						u.push(t);
					}
				});
				texts.push(JSON.stringify(u.map(function (v) {
					return core.formatBigNumber(v[0]) + ":" + v[1];
				})));
			}
		}
	}

	ui.prototype._drawBookDetail_mofang_getArray = function (hp) {
		var arr = [];
		var last = 0,
			start = 0;
		for (var i = 1; i < hp; i++) {
			var now = parseInt((hp - 1) / i);
			if (now != last) {
				if (last != 0) {
					arr.push([start, last + "x"]);
				}
				last = now;
				start = i;
			}
		}
		if (last != 0) {
			arr.push([start, "1x"]);
			arr.push([hp, "0"]);
		}
		return arr;
	}

	ui.prototype._drawBookDetail_vampire = function (enemy, floorId, texts) {
		if (core.enemys.hasSpecial(enemy.special, 11)) {
			var damage = core.getDamage(enemy.id);
			if (damage != null) {
				// 二分HP
				var start = 1,
					end = 100 * damage;
				var nowHp = core.status.hero.hp;
				while (start < end) {
					var mid = Math.floor((start + end) / 2);
					core.status.hero.hp = mid;
					if (core.canBattle(enemy.id, enemy.x, enemy.y, floorId)) end = mid;
					else start = mid + 1;
				}
				core.status.hero.hp = start;
				if (core.canBattle(enemy.id)) {
					texts.push("\r[#FF6A6A]\\d遗忘该记忆碎片最低需要意志：\\d\r[]" + core.formatBigNumber(start));
				}
				core.status.hero.hp = nowHp;
			}
		}
	}

	ui.prototype._drawBookDetail_hatred = function (enemy, texts) {
		if (core.enemys.hasSpecial(enemy.special, 17)) {
			texts.push("\r[#FF6A6A]\\d当前仇恨伤害值：\\d\r[]" + core.getFlag('hatred', 0));
		}
	}

	ui.prototype._drawBookDetail_turnAndCriticals = function (enemy, floorId, texts) {
		var damageInfo = core.getDamageInfo(enemy.id, null, enemy.x, enemy.y, floorId);
		texts.push("\r[#FF6A6A]\\d忘记回合数：\\d\r[]" + ((damageInfo || {}).turn || 0));
		// 临界表
		var criticals = core.enemys.nextCriticals(enemy.id, 8, enemy.x, enemy.y, floorId).map(function (v) {
			return core.formatBigNumber(v[0]) + ":" + core.formatBigNumber(v[1]);
		});
		while (criticals[0] == '0:0') criticals.shift();
		texts.push("\r[#FF6A6A]\\d临界表：\\d\r[]" + JSON.stringify(criticals));
	}

	ui.prototype._drawBookDetail_drawContent = function (enemy, content, pos) {
		// 名称
		core.setTextAlign('data', 'left');
		core.fillText('data', enemy.name, pos.content_left, pos.top + 30, core.status.globalAttribute.selectColor, this._buildFont(22, true));
		var content_top = pos.top + 44;

		this.drawTextContent('data', content, {
			left: pos.content_left,
			top: content_top,
			maxWidth: pos.validWidth,
			fontSize: 16,
			lineHeight: 24
		});
	}
},
    "checkBlocketc": function () {
	// 在此增加新插件

	// 追猎
	// chase == [[x1,y1,id1,direct1],[x2,y2,id2,direct2],...]
	this._checkBlock_chase = function (chase) {
		//if (!chase || chase.length == 0) return;

		if (!chase || chase.length == 0) return;
		var actions = [];
		chase.forEach(function (t) {
			// 再次检查怪物是否有追猎，可能已经被换位换走
			//if (!core.hasSpecial(core.getBlockId(t[0], t[1]), 32)) return;
			var hero_x = core.status.hero.loc.x,
				hero_y = core.status.hero.loc.y;
			var mon_x = t[0],
				mon_y = t[1];
			var should_battle = false; // 移动后与主角相邻
			var reverse_dir;
			switch (t[3]) {
			case "right":
				mon_x += 1;
				reverse_dir = "left";
				if (hero_x == mon_x + 1 && hero_y == mon_y) should_battle = true;
				break;
			case "left":
				mon_x -= 1;
				reverse_dir = "right";
				if (hero_x == mon_x - 1 && hero_y == mon_y) should_battle = true;
				break;
			case "up":
				mon_y -= 1;
				reverse_dir = "down";
				if (hero_x == mon_x && hero_y == mon_y - 1) should_battle = true;
				break;
			case "down":
				reverse_dir = "up";
				mon_y += 1;
				if (hero_x == mon_x && hero_y == mon_y + 1) should_battle = true;
				break;
				// 8方阻擊
			case "leftup":
				nextx -= 1
				nexty -= 1
				if (nextx == hero_x && nexty == hero_y) should_battle = true;
				break;
			case "leftdown":
				nextx -= 1
				nexty += 1
				if (nextx == hero_x && nexty == hero_y) should_battle = true;
				break;
			case "rightup":
				nextx += 1
				nexty -= 1
				if (nextx == hero_x && nexty == hero_y) should_battle = true;
				break;
			case "rightdown":
				nextx += 1
				nexty += 1
				if (nextx == hero_x && nexty == hero_y) should_battle = true;
				break;
			}
			if (hero_x == mon_x && hero_y == mon_y) should_battle = true;
			// 交换位置
			actions.push({
				"type": "if",
				"condition": "!core.getBlock(" + mon_x + "," + mon_y + ") || core.getBlockCls(" + mon_x + "," + mon_y + ") == \"items\"",
				"true": [
					{ "type": "move", "loc": [t[0], t[1]], "steps": [t[3]], "time": 150, "keep": true, "async": true },
					{ "type": "move", "loc": [mon_x, mon_y], "steps": [reverse_dir], "time": 150, "keep": true },
					{ "type": "waitAsync", "excludeAnimates": true }
				]
			});
			if (should_battle) { // 强制战斗
				// 这里的 if 是因为如果存在捕捉+追猎，捕捉战斗后怪物已经消失，要防止追猎再战斗一次
				actions.push({
					"type": "if",
					"condition": "core.getBlockId(" + mon_x + ", " + mon_y + ") ==\'" + t[2] + "\'",
					"true": [{
						"type": "function",
						"function": "function() { " +
							"core.battle('" + t[2] + "', " + mon_x + "," + mon_y + ", true, core.doAction); " +
							"}",
						"async": true
					}, ]
				});
			}
		});
		actions.push({ "type": "waitAsync", "excludeAnimates": true });
		core.insertAction(actions);

	}

	////// 检查并执行领域、夹击、阻击事件 //////
	control.prototype.checkBlock = function () {
		var x = core.getHeroLoc('x'),
			y = core.getHeroLoc('y'),
			loc = x + "," + y;
		var damage = core.status.checkBlock.damage[loc];
		if (damage) {
			core.status.hero.hp -= damage;
			var text = (Object.keys(core.status.checkBlock.type[loc] || {}).join("，")) || "伤害";
			core.drawTip("受到" + text + damage + "点");
			core.drawHeroAnimate("zone");
			this._checkBlock_disableQuickShop();
			core.status.hero.statistics.extraDamage += damage;
			if (core.status.hero.hp <= 0) {
				core.status.hero.hp = 0;
				core.updateStatusBar(false, true);
				core.events.lose();
				return;
			} else {
				core.updateStatusBar(false, true);
			}
		}
		this._checkBlock_ambush(core.status.checkBlock.ambush[loc]);
		this._checkBlock_repulse(core.status.checkBlock.repulse[loc]);
		core.plugin._checkBlock_chase(core.status.checkBlock.chase[loc]);
	}
},
    "标题": function () {
	// 横屏下，使标题画面可以覆盖状态栏 554*422
	// 竖屏下，标题画面依然和游戏画面大小相同 422*422
	// 可能存在bug。遇到bug的话，可以在技术群内，找“理派四阵”反馈

	//本插件需要开启“标题事件化”才能使用，且同时具有“标题”和“难度分歧”2个功能

	if (!core.flags.startUsingCanvas) return;

	var hard = ["imagination", "dream", "reality"], //请按照“低-高”的顺序，填写各个难度的英文名；默认的flags.hard依次为1、2、3……以此类推
		hardColor = ["#00FF00", "#00FFFF", "#FFFF00"]; //和hard同样的顺序，填写各个难度的颜色
	//请将上述2个变量，按照自己的需求修改好

	//非常重要！！！
	//非常重要！！！
	//请完成下面1-3的步骤，不然本插件无法正常运行！

	//1、请在“脚本编辑”-“重置游戏”的末尾，加入下面这句话
	//if (document.getElementById("title")) document.getElementById("title").style.display = 'none';

	//2、请将“标题事件”和“难度分歧”全部清空
	//3、请在“开场剧情”的最开头，加上下面这段内容
	/*
      {"type": "if", "condition": "(!core.isReplaying())",
      "true": [
        {"type": "function", "function": "function(){core.plugin.title();}"},
        {"type": "setValue", "name": "flag:start", "value": "true"},
        {"type": "while", "condition": "flag:start",
        "data": [
          {"type": "sleep", "time": 1, "noSkip": true},
        ],
        },
      ],
      "false": [
        {"type": "function", "function": "function(){core.plugin.replayHardSelect(core.status.replay.toReplay[0], true);}"},
      ]},
	 */

	//以下部分，需要自行编写具体内容

	this.drawTitle = function () { //绘制部分：需要横竖屏分别绘制
		var ctx = document.getElementById("title").getContext('2d');
		core.clearMap(ctx); //每次绘制之前，先清空画布

		if (core.domStyle.isVertical) { //竖屏和横屏需要分别绘制
			switch (core.getFlag("title", 0)) {
			case "start":
				core.drawImage(ctx, "start_vertical.png", 0, 0);
				break;
			case "hard":
				core.drawImage(ctx, "hard_vertical.png", 0, 0);
				break;
			}
		} else { //flags.title是预先内设的变量，为"start"表示当前处于开始界面，为"hard"表示当前处于难度选择界面，你也可以自行设置更多的
			switch (core.getFlag("title", 0)) {
			case "start":
				core.drawImage(ctx, "start.png", 0, 0);
				break;
			case "hard":
				core.drawImage(ctx, "hard.png", 0, 0);
				break;
			}
		}
	}

	this.performTitle = function (e) { //处理玩家的点击操作，比如点击开始游戏、点击难度选项等等
		var px = parseInt(e.offsetX / core.domStyle.scale),
			py = parseInt(e.offsetY / core.domStyle.scale);
		//你可以console.log(px, py)来查看点击的坐标

		//if (core.domStyle.isVertical) { //竖屏和横屏，需要分别处理
		//	
		//} else { //根据当前的页面，进行不同的处理
		switch (core.getFlag("title", 0)) {
		case "start":
			core.plugin.performTitle_startPage(px, py);
			break;
		case "hard":
			core.plugin.performTitle_hardSelect(px, py);
			break;
		}
		//}  ////这里横竖屏的坐标一样，就没有单独适配
	}

	this.performTitle_startPage = function (px, py) { //横屏下，开始页面的点击处理
		if (px >= 20 && px <= 180 && py >= 211 && py <= 253) core.setFlag("title", "hard"); //点击“开始游戏”,则进入“难度分歧”
		if (px >= 73 && px <= 235 && py >= 270 && py <= 314) core.plugin.openSL(); //点击“读取存档”
		if (px >= 140 && px <= 304 && py >= 331 && py <= 374) core.chooseReplayFile(); //点击“录像回放”
		//你还可以自行添加其他的功能
	}
	this.performTitle_hardSelect = function (px, py) { //横屏下，难度分歧页面的点击处理
		if (px >= 20 && px <= 100 && py >= 211 && py <= 253) core.plugin.selectHard(1);
		if (px >= 73 && px <= 153 && py >= 270 && py <= 314) core.plugin.selectHard(2);
		if (px >= 140 && px <= 222 && py >= 331 && py <= 374) core.plugin.selectHard(3); //这里的1、2、3，就是你希望的flags.hard取几。强烈建议按照1、2、3……的顺序，否则需要修改很多关联内容
		//你可以设置更多的难度
	}

	////之后的部分请勿修改
	////之后的部分请勿修改
	////之后的部分请勿修改

	this.title = function () { //主程序
		//resize后，数值初始化
		var width = parseFloat(document.getElementById('statusBar').style.width.slice(0, -2));
		if (document.getElementById("title")) { //重启游戏时
			document.getElementById("title").style.display = "block";
			core.setFlag("title", "start");
			core.plugin.drawTitle();
			return;
		}
		//第一次打开页面时
		var newCanvas = document.createElement("canvas");
		newCanvas.id = "title";
		newCanvas.style.display = 'block';
		newCanvas.style.left = '0px';
		newCanvas.style.top = '0px';
		newCanvas.style.left = '0px';
		newCanvas.style.top = '0px';
		newCanvas.style.zIndex = 210;
		newCanvas.style.position = 'absolute';
		if (core.domStyle.isVertical) {
			newCanvas.style.top = core.dom.gameDraw.style.top;
			newCanvas.width = newCanvas.height = 422;
			newCanvas.style.width = newCanvas.style.height = width + 'px';
			core.maps._setHDCanvasSize(newCanvas.getContext('2d'), 422, 422);
		} else {
			newCanvas.width = 554;
			newCanvas.height = 422;
			newCanvas.style.width = '554px';
			newCanvas.style.height = '422px';
			core.maps._setHDCanvasSize(newCanvas.getContext('2d'), 554, 422);
		}
		newCanvas.onclick = function (e) { //点击-处理-重绘，如此循环。和自绘ui一个原理
			e.stopPropagation();
			core.plugin.performTitle(e);
			core.plugin.drawTitle();
		}

		core.dom.gameGroup.appendChild(newCanvas); //注册该画布
		core.control.resize(); //调整分辨率

		core.setFlag("title", "start");
		core.plugin.drawTitle();
	}

	this.selectHard = function (num) { //选中了某难度时，num表示flags.hard应设为num
		core.setFlag("hard", num);
		core.status.hard = hard[num - 1];
		core.setFlag('__hardColor__', hardColor[num - 1]);
		core.status.route.push("hard:" + num);
		//隐藏标题画布，继续后面的“开场剧情”
		core.removeFlag("start");
		document.getElementById("title").style.display = "none";
	}

	this.openSL = function () {
		document.getElementById("title").style.display = "none";
		core.insertAction([
			{ "type": "callLoad" },
			{ "type": "function", "function": "function(){ if (core.status.event.id === 'action') {document.getElementById('title').style.display = 'block'} }" }
		]);
	}

	this.replayHardSelect = function (action, fromOpening) {
		if (action.indexOf("hard:") < 0) return false;
		if (!fromOpening) { //把难度选项移到了开场剧情中执行，实际回放时不执行以免录像重复
			setTimeout(core.replay, core.control.__replay_getTimeout());
			return true;
		}
		var num = parseInt(action.substr(5));

		if (!Number.isInteger(num)) return false;

		core.status.route.push(action);
		core.setFlag("hard", num);
		core.status.hard = hard[num - 1];
		core.setFlag('__hardColor__', hardColor[num - 1]);
		setTimeout(core.replay, core.control.__replay_getTimeout());
		return true;
	}

	core.control._resize_canvas = function (obj) {
		var innerSize = (obj.CANVAS_WIDTH * core.domStyle.scale) + "px";
		for (var i = 0; i < core.dom.gameCanvas.length; ++i)
			core.dom.gameCanvas[i].style.width = core.dom.gameCanvas[i].style.height = innerSize;
		core.dom.gif.style.width = core.dom.gif.style.height = innerSize;
		core.dom.gif2.style.width = core.dom.gif2.style.height = innerSize;
		core.dom.gameDraw.style.width = core.dom.gameDraw.style.height = innerSize;
		core.dom.gameDraw.style.top = obj.statusBarHeightInVertical + "px";
		core.dom.gameDraw.style.right = 0;
		core.dom.gameDraw.style.border = obj.border;
		// resize bigmap
		core.bigmap.canvas.forEach(function (cn) {
			var ratio = core.canvas[cn].canvas.hasAttribute('isHD') ? core.domStyle.ratio : 1;
			core.canvas[cn].canvas.style.width = core.canvas[cn].canvas.width / ratio * core.domStyle.scale + "px";
			core.canvas[cn].canvas.style.height = core.canvas[cn].canvas.height / ratio * core.domStyle.scale + "px";
		});
		// resize dynamic canvas
		for (var name in core.dymCanvas) {
			var ctx = core.dymCanvas[name],
				canvas = ctx.canvas;
			var ratio = canvas.hasAttribute('isHD') ? core.domStyle.ratio : 1;
			canvas.style.width = canvas.width / ratio * core.domStyle.scale + "px";
			canvas.style.height = canvas.height / ratio * core.domStyle.scale + "px";
			canvas.style.left = parseFloat(canvas.getAttribute("_left")) * core.domStyle.scale + "px";
			canvas.style.top = parseFloat(canvas.getAttribute("_top")) * core.domStyle.scale + "px";
		}
		// resize next
		main.dom.next.style.width = main.dom.next.style.height = 5 * core.domStyle.scale + "px";
		main.dom.next.style.borderBottomWidth = main.dom.next.style.borderRightWidth = 4 * core.domStyle.scale + "px";
		//resize title
		var title = document.getElementById('title');
		if (title) {
			if (core.domStyle.isVertical) {
				title.style.width = newCanvas.style.height = 422 * core.domStyle.scale + 'px';
			} else {
				title.style.width = 554 * core.domStyle.scale + 'px';
				title.style.height = 422 * core.domStyle.scale + 'px';
			}
		}
	}
	core.control.registerResize("canvas", core.control._resize_canvas);
	core.control.registerReplayAction("hard", core.plugin.replayHardSelect);
},
    "autoGetItem": function () {
	// 从 9922 抄的，有改动，可能有些代码在本塔内无意义

	// NOTE：开启自动拾取时不能忽略连续瞬移！如，第一次瞬移拾取物品，第二次瞬移到某物品后面，忽略第一次会导致录像炸裂
	core.control.my_replayAction_moveDirectly = function (action) {
		if (action.indexOf("move:") != 0) return false;
		// 忽略连续的瞬移事件；如果大地图某一边超过计算范围则不合并
		// 改了这里
		if (!core.getFlag("autoGetItem") && !core.hasFlag('poison') && core.status.thisMap.width < 2 * core.bigmap.extend + core.__SIZE__ &&
			core.status.thisMap.height < 2 * core.bigmap.extend + core.__SIZE__) {
			while (core.status.replay.toReplay.length > 0 &&
				core.status.replay.toReplay[0].indexOf('move:') == 0) {
				core.status.route.push(action);
				action = core.status.replay.toReplay.shift();
			}
		}

		var pos = action.substring(5).split(":");
		var x = parseInt(pos[0]),
			y = parseInt(pos[1]);
		var nowx = core.getHeroLoc('x'),
			nowy = core.getHeroLoc('y');
		var ignoreSteps = core.canMoveDirectly(x, y);
		if (!core.moveDirectly(x, y, ignoreSteps)) return false;
		if (core.status.replay.speed == 24) {
			core.replay();
			return true;
		}

		core.ui.drawArrow('ui', 32 * nowx + 16 - core.bigmap.offsetX, 32 * nowy + 16 - core.bigmap.offsetY,
			32 * x + 16 - core.bigmap.offsetX, 32 * y + 16 - core.bigmap.offsetY, '#FF0000', 3);
		var timeout = this.__replay_getTimeout();
		//	if (ignoreSteps < 10) timeout = timeout * ignoreSteps / 10;
		setTimeout(function () {
			core.clearMap('ui');
			core.replay();
		}, timeout);
		return true;
	}
	core.registerReplayAction("moveDirectly", core.control.my_replayAction_moveDirectly);

	// 从第九大陆抄的，有改动。不适合直接抄
	function bfsFlood(sx, sy, blockfn) {
		var canMoveArray = core.generateMovableArray();
		var blocksObj = core.getMapBlocksObj();
		var bgMap = core.getBgMapArray();

		var visited = [],
			queue = [];
		visited[sx + "," + sy] = 0;
		queue.push(sx + "," + sy);

		while (queue.length > 0) {
			var now = queue.shift().split(","),
				x = ~~now[0],
				y = ~~now[1];
			for (var direction in core.utils.scan) {
				if (!core.inArray(canMoveArray[x][y], direction)) continue;
				var nx = x + core.utils.scan[direction].x,
					ny = y + core.utils.scan[direction].y,
					nindex = nx + "," + ny;
				if (visited[nindex]) continue;
				if (core.onSki(bgMap[ny][nx])) continue;
				if (blockfn && !blockfn(blocksObj, nx, ny)) continue;
				visited[nindex] = visited[now] + 1;
				queue.push(nindex);
			}
		}
	}

	function attractAnimate() {
		var name = 'attractAnimate';
		var isPlaying = false;
		this.nodes = [];

		this.add = function (id, x, y, callback) {
			this.nodes.push({ id: id, x: x - core.bigmap.posX * 32, y: y - core.bigmap.posY * 32, callback: callback });
		}
		this.start = function () {
			if (isPlaying) return;
			isPlaying = true;
			core.registerAnimationFrame(name, true, this.update);
			if (!core.dymCanvas[name]) {
				this.ctx = core.createCanvas(name, 0, 0, core.__PIXELS__, core.__PIXELS__, 120);
			} else {
				this.ctx = core.dymCanvas[name];
			}
		}
		this.remove = function () {
			core.unregisterAnimationFrame(name);
			core.clearMap(name);
			isPlaying = false;
		}
		this.clear = function () {
			this.nodes = [];
			this.remove();
		}
		var lastTime = -1;
		var self = this;
		this.update = function (timeStamp) {
			if (lastTime < 0) lastTime = timeStamp;
			if (timeStamp - lastTime < 20) return;
			lastTime = timeStamp;
			core.clearMap(name);
			var cx = core.status.heroCenter.px - 16 - core.bigmap.posX * 32,
				cy = core.status.heroCenter.py - 16 - core.bigmap.posY * 32;
			var thr = 3; // 缓动比例倒数 越大移动越慢
			self.nodes.forEach(function (n) {
				var dx = cx - n.x,
					dy = cy - n.y;
				if (Math.abs(dx) <= thr && Math.abs(dy) <= thr) {
					n.dead = true;
				} else {
					n.x += ~~(dx / thr);
					n.y += ~~(dy / thr);
				}
				core.drawIcon(name, n.id, n.x, n.y, 32, 32);
			});
			self.nodes = self.nodes.filter(function (n) {
				if (n.dead && n.callback) {
					n.callback();
				}
				return !n.dead;
			});
			if (self.nodes.length == 0)
				self.remove();
		}
	}

	var animateHwnd = new attractAnimate();

	this.stopAttractAnimate = function () {
		animateHwnd.clear();
	}

	this.autoGetItem = function () {
		if (core.getFlag("autoGetItem", 0) == 0) return;
		var canGetItems = {};
		var index_list = []; // 保序，虽然这个塔里面物品的拾取顺序没有什么影响
		var canPickFunc = function (block, idx) {
			var blkid = block ? block.event.id : "";
			var nopick = core.status.checkBlock.no_autopick[idx];
			if (!core.getFlag("autoGetGreenKey") && blkid == "greenKey") nopick = true;
			return block && !block.disable && block.event.cls == 'items' && !core.isMapBlockDisabled(core.status.floorId, block.x, block.y) &&
				block.event.trigger == 'getItem' && !nopick;
		}
		// 将可自动拾取的物品记录在 canGetItems 内，返回值代表 (x, y) 格是否需要继续向邻居格搜索
		var blockfn = function (blockMap, x, y) {
			var idx = x + ',' + y;
			if (idx in canGetItems) return false;
			var blk = blockMap[idx];
			if (canPickFunc(blk, idx)) {
				canGetItems[idx] = { x: x, y: y, id: blk.event.id };
				index_list.push(idx);
				return !core.status.checkBlock.damage[idx] && !core.status.checkBlock.repulse[idx] && !core.status.checkBlock.ambush[idx] && !core.status.checkBlock.chase[idx];
			}
			return core.maps._canMoveDirectly_checkNextPoint(blockMap, x, y);
		};
		if (!core.status.floorId || !core.status.checkBlock.damage || core.status.event.id == 'action' || core.status.lockControl) return;
		// 身處地圖傷害時只能自動拾取身邊的
		var near_only = false;
		var loc_x = core.status.hero.loc.x,
			loc_y = core.status.hero.loc.y,
			loc_idx = loc_x + "," + loc_y;
		if (Object.keys(core.status.checkBlock.damage).indexOf(loc_idx) != -1) near_only = true;
		if (Object.keys(core.status.checkBlock.repulse).indexOf(loc_idx) != -1) near_only = true;
		if (Object.keys(core.status.checkBlock.ambush).indexOf(loc_idx) != -1) near_only = true;
		if (Object.keys(core.status.checkBlock.chase).indexOf(loc_idx) != -1) near_only = true;

		if (near_only) {
			for (var direction in core.utils.scan) {
				var nx = loc_x + core.utils.scan[direction].x,
					ny = loc_y + core.utils.scan[direction].y,
					nindex = nx + "," + ny;
				var blk = core.getMapBlocksObj()[nindex];
				if (canPickFunc(blk, nindex)) {
					canGetItems[nindex] = { x: nx, y: ny, id: blk.event.id };
					index_list.push(nindex);
				}
			}
		} else {
			bfsFlood(core.getHeroLoc('x'), core.getHeroLoc('y'), blockfn);
		}
		for (var i = 0; i < index_list.length; ++i) {
			var k = index_list[i];
			var x = canGetItems[k].x,
				y = canGetItems[k].y,
				id = canGetItems[k].id;
			core.trigger(x, y);
			//core.getItem(id, null, x, y);
			if (!core.isReplaying()) animateHwnd.add(id, x * 32, y * 32);
		}
		if (!core.isReplaying()) animateHwnd.start();
	}

	this.addAutoGetItemAnimate = function (id, x, y) {
		if (core.isReplaying()) return;
		animateHwnd.add(id, x * 32, y * 32);
	}
	this.startAutoGetItemAnimate = function () {
		if (core.isReplaying()) return;
		animateHwnd.start();
	}
},
    "显伤": function () {
	enemys.prototype.getDamageString = function (enemy, x, y, floorId) {
		if (typeof enemy == 'string') enemy = core.material.enemys[enemy];
		var damage = this.getDamage(enemy, x, y, floorId);

		var color = '#000000';

		if (damage == null) {
			damage = "???";
			color = '#FF0000';
		} else {
			if (damage <= 0) color = '#00FF00';
			else if (damage < core.status.hero.hp / 3) color = '#FFFFFF';
			else if (damage < core.status.hero.hp * 2 / 3) color = '#FFFF00';
			else if (damage < core.status.hero.hp) color = '#FF7F00';
			else color = '#FF0000';
			damage = core.formatBigNumber(damage, true);
		}
		if (core.enemys.hasSpecial(enemy, 1)) damage += "先";
		if (core.enemys.hasSpecial(enemy, 2)) damage += "魔";
		if (core.enemys.hasSpecial(enemy, 3)) damage += "坚";
		if (core.enemys.hasSpecial(enemy, 4) || core.enemys.hasSpecial(enemy, 5) || core.enemys.hasSpecial(enemy, 6)) damage += "连";
		if (core.enemys.hasSpecial(enemy, 7)) damage += "破";
		if (core.enemys.hasSpecial(enemy, 8)) damage += "反";
		if (core.enemys.hasSpecial(enemy, 9)) damage += "净";
		if (core.enemys.hasSpecial(enemy, 10)) damage += "仿";
		if (core.enemys.hasSpecial(enemy, 11)) damage += "吸";
		if (core.enemys.hasSpecial(enemy, 12)) damage += "毒";
		if (core.enemys.hasSpecial(enemy, 13)) damage += "衰";
		if (core.enemys.hasSpecial(enemy, 14)) damage += "咒";
		if (core.enemys.hasSpecial(enemy, 15)) damage += "域";
		if (core.enemys.hasSpecial(enemy, 16)) damage += "夹";
		if (core.enemys.hasSpecial(enemy, 17)) damage += "仇";
		if (core.enemys.hasSpecial(enemy, 18)) damage += "阻";
		if (core.enemys.hasSpecial(enemy, 19)) damage += "爆";
		if (core.enemys.hasSpecial(enemy, 20)) damage += "神";
		if (core.enemys.hasSpecial(enemy, 21)) damage += "退";
		if (core.enemys.hasSpecial(enemy, 22)) damage += "固";
		if (core.enemys.hasSpecial(enemy, 23)) damage += "重";
		if (core.enemys.hasSpecial(enemy, 24)) damage += "激";
		if (core.enemys.hasSpecial(enemy, 25)) damage += "环";
		if (core.enemys.hasSpecial(enemy, 26)) damage += "援";
		if (core.enemys.hasSpecial(enemy, 27)) damage += "捉";
		if (core.enemys.hasSpecial(enemy, 28)) damage += "败";
		if (core.enemys.hasSpecial(enemy, 29)) damage += "阻环";
		if (core.enemys.hasSpecial(enemy, 30)) damage += "猎环";
		if (core.enemys.hasSpecial(enemy, 32)) damage += "猎";
		if (core.enemys.hasSpecial(enemy, 33)) damage += "偷";

		return {
			"damage": damage,
			"color": color
		};
	};
},
    "光环": function () {
	// 在此增加新插件
	// 效果：在一定范围内显示光环的范围，同时可以搞点神必玩意（）
	// 使用方法：在需要刷新光环的地方加上core.plugin.HuiZhiTeXiao()，例如切换楼层后，战后脚本
	/*
		this.HuiZhiTeXiao = function () {

			var floorId = core.status.floorId;

			core.plugin.getTeXiao(floorId);
			var cache = core.getFlag('teXiaoCache', {});
			var teXiao = core.getFlag('teXiaoObject', {});
			//console.log(cache, teXiao);
			if (cache == teXiao) {
				return;
			}
			core.setFlag("teXiaoCache", core.clone(teXiao));
			//console.log('?')
			var mBM = ['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity'];
			var ctx = [];
			var usedCtx = core.getFlag('usedCtx', []);
			for (var j in mBM) {
				core.clearMap('TeXiao' + j);
				//core.deleteCanvas('TeXiao' + j);
				var used = false;
				for (var k in usedCtx) {
					if (usedCtx[k] == j) used = true;
				}
				if (!used) continue;

				ctx[j] = core.createCanvas('TeXiao' + j, 0, 0, 416, 416, 101 + parseInt(j));
				var element = document.getElementById("TeXiao" + j);
				element.style.mixBlendMode = mBM[j];
			}

			teXiao[floorId] = teXiao[floorId] || [];
			for (var i in teXiao[floorId]) {
				var draw = teXiao[floorId][i];
				core.plugin.drawTexiao(ctx, draw.drawStyle, draw.drawFunction);
			}

		}
		this.ShanChuTeXiao = function () {
			for (var i = 0; i <= 15; i++) {
				core.clearMap('TeXiao' + i);
				//core.deleteCanvas('TeXiao' + i);
			}
		}
		this.addTeXiao = function (floorId, drawStyle, drawFunction) {
			var teXiao = core.getFlag('teXiaoObject', {});
			teXiao[floorId] = teXiao[floorId] || [];
			teXiao[floorId].push({ 'drawStyle': drawStyle, 'drawFunction': drawFunction });
			core.setFlag('teXiaoObject', teXiao);

		}
		this.getTeXiao = function (floorId) {
			var teXiao = core.getFlag('teXiaoObject', {});
			core.setFlag('usedCtx', []);
			teXiao[floorId] = [];
			core.setFlag('teXiaoObject', teXiao);
			var blocks = core.getMapBlocksObj(floorId);
			for (var loc in blocks) {
				var block = blocks[loc],
					x = block.x,
					y = block.y,
					id = block.event.id,
					enemy = core.material.enemys[id];
				if (block.disable) continue;
				if (enemy && core.hasSpecial(enemy.special, 67)) {
					var drawStyle = {
						'x': x,
						'y': y,
						'value': 2,
						'color': 'rgba(24,67,189,0.4)',
					}
					var drawFunction = core.plugin.drawFunctions('灵魂链接');
					core.plugin.addTeXiao(floorId, drawStyle, drawFunction);
				}
			}
		}
		this.drawTexiao = function (ctxs, drawStyle, drawFunction) {
			//drawStyle = drawStyle || {};

			if (!drawStyle || !drawFunction) return;

			//ctx.style.mixBlendMode = "multiply";
			//console.log(ctx);

			var x = drawStyle.x;
			var y = drawStyle.y;
			//var type = drawStyle.type;
			var value = drawStyle.value;
			var color = drawStyle.color;
			var others = drawStyle.others || {};
			
			//if (type == '正方形') {
			//	core.fillRect(ctx, 32 * x, 32 * y, (value * 2 + 1) * 32, (value * 2 + 1) * 32, color);
			//}
			
			drawFunction(ctxs, x, y, value, color, others);

		}
		this.drawFunctions = function (type) {
			var func;
			var usedCtx = core.getFlag('usedCtx', []);

			// 在这里可以搞一些奇怪的东西
			// 比如我就搞了个光环范围内显示怪物的行走图（？）
			// 反正大概可以搞点阴间玩意，操作空间应该挺大的

			if (type == '灵魂链接') {
				usedCtx.push(0);
				func = function (ctxs, x, y, value, color, others) {
					//core.fillRect(ctxs[3], 32 * (x - value), 32 * (y - value), (value * 2 + 1) * 32, (value * 2 + 1) * 32, color);
					core.setAlpha(ctxs[0], 0.4)
					var blockInfo = core.getBlockInfo(core.getBlock(x, y))
					//console.log(blockInfo)
					core.drawImage(ctxs[0], blockInfo.image, 0, blockInfo.posY * 32, 32, 32, 32 * (x - value), 32 * (y - value), (value * 2 + 1) * 32, (value * 2 + 1) * 32)
					core.setAlpha(ctxs[0], 1)
				}
			}
			core.setFlag('usedCtx', usedCtx);
			return func;
		}
		*/
},
    "录像优化": null
}