var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 
{
    "init": function () {
		this._afterLoadResources = function () {
			// 本函数将在所有资源加载完毕后，游戏开启前被执行
		}
	},
    "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 anti-aliasing';
			// 编辑器模式下设置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代码 （必装） --------------------------------------- //

		/* ---------------- 用法说明 ---------------- *
		 * 1. 创建sprite: var 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();
		 * ---------------- 用法说明 ---------------- */

		var count = 0;

		/** 创建一个sprite画布
		 * @param {number} x
		 * @param {number} y
		 * @param {number} w
		 * @param {number} h
		 * @param {number} z
		 * @param {'game' | 'window'} reference 参考系，游戏画面或者窗口
		 * @param {string} name 可选，sprite的名称，方便通过core.dymCanvas获取
		 */
		function Sprite (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;
			this.canvas = null;
			this.context = null;
			this.count = 0;
			this.name = name || '_sprite_' + count;
			this.style = null;
			/** 初始化 */
			this.init = function () {
				if (reference === 'window') {
					var canvas = document.createElement('canvas');
					this.canvas = canvas;
					this.context = canvas.getContext('2d');
					canvas.width = w;
					canvas.height = h;
					canvas.style.width = w + 'px';
					canvas.style.height = h + 'px';
					canvas.style.position = 'absolute';
					canvas.style.top = y + 'px';
					canvas.style.left = x + 'px';
					canvas.style.zIndex = z.toString();
					document.body.appendChild(canvas);
					this.style = canvas.style;
				} else {
					this.context = core.createCanvas(this.name || '_sprite_' + count, x, y, w, h, z);
					this.canvas = this.context.canvas;
					this.canvas.style.pointerEvents = 'auto';
					this.style = this.canvas.style;
				}
				this.count = count;
				count++;
			}
			this.init();

			/** 设置css特效
			 * @param {string} css
			 */
			this.setCss = function (css) {
				css = css.replace('\n', ';').replace(';;', ';');
				var effects = css.split(';');
				var self = this;
				effects.forEach(function (v) {
					var content = v.split(':');
					var name = content[0];
					var value = content[1];
					name = name.trim().split('-').reduce(function (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);
					}, '');
					var canvas = self.canvas;
					if (name in canvas.style) canvas.style[name] = value;
				});
				return this;
			}

			/** 
			 * 移动sprite
			 * @param {boolean} isDelta 是否是相对位置，如果是，那么sprite会相对于原先的位置进行移动
			 */
			this.move = function (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;
			}

			/** 
			 * 重新设置sprite的大小
			 * @param {boolean} styleOnly 是否只修改css效果，如果是，那么将会不高清，如果不是，那么会清空画布
			 */
			this.resize = function (w, h, styleOnly) {
				if (w !== undefined && w !== null) this.w = w;
				if (h !== undefined && h !== null) this.h = h;
				if (reference === 'window') {
					var 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;
			}

			/**
			 * 旋转画布
			 */
			this.rotate = function (angle, cx, cy) {
				if (this.reference === 'window') {
					var left = this.x;
					var 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;
			}

			/**
			 * 清除sprite
			 */
			this.clear = function (x, y, w, h) {
				if (this.reference === 'window') {
					this.context.clearRect(x, y, w, h);
				} else {
					core.clearMap(this.context, x, y, w, h);
				}
				return this;
			}

			/** 删除 */
			this.destroy = function () {
				if (this.reference === 'window') {
					if (this.canvas) document.body.removeChild(this.canvas);
				} else {
					core.deleteCanvas(this.name || '_sprite_' + this.count);
				}
			}

			/** 添加事件监听器 */
			this.addEventListener = function () {
				this.canvas.addEventListener.apply(this.canvas, arguments);
			}

			/** 移除事件监听器 */
			this.removeEventListener = function () {
				this.canvas.removeEventListener.apply(this.canvas, arguments);
			}
		}

		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);
			}
		})();
	},
    "新建插件": function () {
	// 在此增加新插件

	//人名命名 tomori anon taki rana soyo uika umiri nyamu mutsumi sakiko bocchi ryou ikuyo nijika nina momoka subaru tomo rupa yui mio ritsu tsumugi azusa mana

	//各种角色费用
	const totalFee = 15;

	const allIds = ['tomori', 'anon', 'taki', 'rana', 'soyo', 'uika', 'umiri', 'nyamu', 'mutsumi', 'sakiko', 'bocchi', 'ryou', 'ikuyo', 'nijika', 'nina', 'momoka', 'subaru', 'tomo', 'rupa', 'yui', 'mio', 'ritsu', 'tsumugi', 'azusa', 'mana'];

	//怪物分类
	//const bossEnemys = ['Timoris', 'Doloris', 'Amoris', 'Oblivionis', 'Mortis'];
	//const guardEnemys = ['blackSlime', 'blackSlimeHang', 'blackSlimeBadge', 'blackSlimeEarphone', 'blackSlimeCall'];
	const bossEnemys = ['skeletonCaptain', 'vampire', 'yellowKnight', 'octopus', 'dragon', 'blackMagician', 'redKing', "E405", "E406", "E407", "E408", "E409", "E410", "E411", "E412"];
	const guardEnemys = ['yellowGuard', 'blueGuard', 'redGuard'];

	//各种技能数值，涉及ratio的均为增加量
	this.getSkillValues = function (taki) {
		taki = taki || flags.taki;
		const a = {
			anon: 5,
			soyo: 1,
			uika: 5,
			//umiri: 20,
			bocchi: [60, 40, 20, 0, -20],
			ikuyo: 5,
			nijika: 1,
			nina: 5,
			subaru: 10,
			rupa: { man: 100, def: 4 },
			yui: 20,
			ritsu: 20,
			tsumugi: 90,
			azusa: 5,
			taki: 100,
			//mio: 5,
			momoka: 10,
			nyamu: 25,
			mutsumi: 80,
		};
		if (taki) {
			const b = {
				anon: 7,
				//umiri: 40,
				ikuyo: 7,
				nijika: 1,
				nina: 7,
				subaru: 15,
				yui: 25,
				tsumugi: 135,
				azusa: 7,
				//mio: 7,
			}
			return { ...a, ...b };
		} else return { ...a };
	}

	//是否是boss层
	this.isBossFloor = function (floorId) {
		floorId = floorId || core.status.floorId;
		return ['MT10', 'MT20', 'MT30', 'MT40', 'MT50'].includes(floorId);
	}

	//老人配置
	this.getMan = function (floorId) {
		floorId = floorId || core.status.floorId;
		const man = {
			'MTx': { words: '我吃饭我也请你吃饭', length: 1 }, //示例
			'MT3': { words: '你好啊，欢迎来到50层魔塔，这里的原来的剧情都被删除了，因此你甚至都没有受到埋伏。4楼有全塔唯一的加点商店，到达新的区域就会自动升级。剑不在5楼，盾也不在9楼，因为这座魔塔没有剑盾。不仅如此，这座魔塔也没有隐藏楼层，也没有上楼器，也没有下楼器。这样的魔塔是不是很令人向往呢？', length: 2 },
			'MT4': { words: '有些怪物有特技，有些特技和熟知的其他魔塔的同名特技有些许出入。什么，别说胡话了，你对话后没有特殊的能力。', length: 1 },
			'MT6': { words: '魔塔共有50层，每10层是一个区域，不打败一个区域的头目就无法到达更高的楼层。商人不会跟你东拉西扯，只会卖东西。', length: 2 },
			'MT16': { words: '魔塔里没有藏在墙里的红钥匙。14楼的红钥匙没有藏在墙里，34楼没有红钥匙。魔塔里也没有什么墙人会给你圣水啦其实。', length: 2 },
			'MT18': { words: '切记前人教训，前人教训是什么来着？我忘了。', length: 1 },
			'MT21': { words: '这个区域非常空旷，所有的内容似乎都跑到了30楼。', length: 1 },
			'MT23': { words: 'no 0F。这座魔塔只有1-50层，并且50层要从30层过去。我听说41层就是普通的一层，不会发生什么特殊事情。', length: 2 },
			'MT27': { words: '祝贺你，你的乐队的前期发展得是比较璀璨的。', length: 1 },
			'MT31': { words: '双手剑士的攻击力实在是太高了，不过双手剑士并不会主动攻击你，只有骑士队长才会主动攻击你。', length: 2 },
			'MT33': { words: '别不匆忙，加快速度。', length: 1 },
			'MT36': { words: '别找了，本层没有暗道。', length: 1 },
			'MT37': { words: '你需要用地震卷轴才能取走这里的所有宝物，当然你也可以用剪切线什么的。', length: 1 },
			'MT39': { words: '谜题：在3点……哦没有谜题了，你走吧。', length: 1 },
			'MT42': { words: '没有怪物会攻击过路的人。', length: 1 },
			'MT45': { words: '楼下好像扩建了，不过仍然时不时传来爆炸声。', length: 1 },
			'MT46': { words: '如果你要打败魔龙，你需要屠龙匕，但是你也可以没有屠龙匕就去打败魔龙。', length: 1 },
			'MT48': { words: '没有封印，没有假魔王，什么都没有，不过旁边倒是有一只不知道在干什么的魔法警卫。', length: 1 },
		};
		return man[floorId];
	}

	//触发老人
	this.triggerMan = function (floorId) {
		floorId = floorId || core.status.floorId;
		const actions = [
			{ type: "setValue", name: "flag:traderX", value: "core.nextX()" },
			{ type: "setValue", name: "flag:traderY", value: "core.nextY()" },
			"\t[老人,man]" + this.getMan(floorId).words,
			{ type: "hide", loc: ["flag:traderX", "flag:traderY"], remove: true, time: 500 },
			{ type: "setValue", name: "flag:traderX", value: "null" },
			{ type: "setValue", name: "flag:traderY", value: "null" },
		];

		flags.noteBook.push(floorId);
		core.insertAction(actions);
	}

	//使用记事本
	this.useNoteBook = function () {
		let actions = [];
		flags.noteBook.forEach(
			(floorId, i) => {
				const j = Math.round((i - i % 6) / 6);
				let words = actions[j] || '';
				words += floorId.slice(2) + '层老人：' + this.getMan(floorId).words + '\\n';
				actions[j] = words;
			}
		)
		if (actions.length === 0) actions[0] = '（尚未与老人对话过）'
		core.insertAction(actions);
	}

	this.useNoteBook = function () {
		let actions = [];
		let positionCounter = 0; // 当前位置计数器（0-5）
		let currentGroupIndex = 0; // 当前组索引

		flags.noteBook.forEach((floorId, i) => {
			const man = this.getMan(floorId);
			const words = floorId.slice(2) + '层老人：' + man.words + '\\n';

			// 根据老人的length属性确定占位数量
			const spaceNeeded = man.length || 1; // 默认占用1个位置

			// 如果当前位置不够放置这个老人，需要创建新组
			if (positionCounter + spaceNeeded > 6) {
				currentGroupIndex++;
				positionCounter = 0;
			}

			// 获取或创建当前组的字符串
			let groupContent = actions[currentGroupIndex] || '';
			groupContent += words;

			// 更新当前位置计数器
			positionCounter += spaceNeeded;

			// 保存到对应组
			actions[currentGroupIndex] = groupContent;

			// 如果当前位置已满（达到6），则开始新组
			if (positionCounter >= 6) {
				currentGroupIndex++;
				positionCounter = 0;
			}
		});

		if (actions.length === 0) actions[0] = '（尚未与老人对话过）';

		// 清理可能的空组
		actions = actions.filter(group => group && group.trim().length > 0);

		core.insertAction(actions);
	}

	//商人配置
	this.getMerchant = function (floorId) {
		floorId = floorId || core.status.floorId;
		const merchant = {
			'MTx': { money: 50, trade: [{ item: 'yellowKey', value: 5 }], words: '我有5把黄钥匙，你出50金币我就卖给你' }, //示例
			'MT6': { money: 50, trade: [{ item: 'blueKey', value: 1 }], words: '我有1把蓝钥匙，你出50金币我就卖给你' },
			'MT7': { money: 50, trade: [{ item: 'yellowKey', value: 5 }], words: '我有5把黄钥匙，你出50金币我就卖给你' },
			'MT12': { money: 800, trade: [{ item: 'redKey', value: 1 }], words: '我有1把珍稀的红钥匙，你出800金币我就卖给你' },
			'MT15': { money: 200, trade: [{ item: 'blueKey', value: 1 }], words: '我有1把蓝钥匙，你出200金币我就卖给你' },
			'MT31': { money: 1000, trade: [{ item: 'yellowKey', value: 4 }, { item: 'blueKey', value: 1 }], words: '我有4把黄钥匙1把蓝钥匙，你出1000金币我就卖给你' },
			'MT38': { money: 200, trade: [{ item: 'yellowKey', value: 3 }], words: '大甩卖！3把黄钥匙200金币！' },
			'MT39': { money: 2000, trade: [{ item: 'blueKey', value: 3 }], words: '超值钥匙商人，3把蓝钥匙2000金币！' },
			'MT45': { money: 1000, trade: [{ item: 'hp', value: 2000 }], words: '你给我1000金币，我帮你回复2000生命值' },
			'MT47': { money: 4000, trade: [{ item: 'earthquake', value: 1 }], words: '我有地震卷轴，你出4000金币我就卖给你' },
		};
		return merchant[floorId];
	}

	//触发商人
	this.triggerMerchant = function (floorId) {
		floorId = floorId || core.status.floorId;
		const merchant = this.getMerchant(floorId);
		const cost = merchant.money;
		const specialItems = ['atk', 'def', 'hp', 'money'];
		const choiceActions = merchant.trade.map(({ item, value }) => {
			const a = specialItems.indexOf(item);
			if (a !== -1) return ({ type: "setValue", name: "status:" + specialItems[a], operator: "+=", value: value })
			else return ({ type: "setValue", name: "item:" + item, operator: "+=", value: value });
		});

		const actions = [{
			type: "choices",
			text: "\t[商人,trader]" + merchant.words,
			choices: [{
					text: "确认购买",
					need: flags.ryou ? ("status:money>=0") : ("status:money>=" + cost),
					action: [
						{ type: "setValue", name: "flag:traderX", value: "core.nextX()" },
						{ type: "setValue", name: "flag:traderY", value: "core.nextY()" },
						...choiceActions,
						{ type: "setValue", name: "status:money", operator: "-=", value: cost },
						{ type: "playSound", name: "确定" },
						{ type: "hide", loc: ["flag:traderX", "flag:traderY"], remove: true, time: 500 },
						{ type: "setValue", name: "flag:traderX", value: "null" },
						{ type: "setValue", name: "flag:traderY", value: "null" },
					]
				},
				{
					text: "下次再说",
					action: []
				},
			]
		}, ];

		core.insertAction(actions);
	}

	//战后脚本
	this.myAfterBattle = function (enemyId, x, y) {

	}

	//拾取道具后脚本
	this.myAfterGetItem = function (itemId, x, y, isGentleClick) {
		//如果处于归去来兮状态中，则传送回去，喜多郁代
		if (flags.kitaIkuyo) {
			core.setFlag('kitaIkuyo');
			const action = [{ type: "changeFloor", floorId: flags.kitaIkuyoLocation.floorId, loc: [flags.kitaIkuyoLocation.x, flags.kitaIkuyoLocation.y], direction: flags.kitaIkuyoLocation.direction, time: 0 }, ];
			core.setFlag('kitaIkuyoLocation');
			core.insertAction(action);
		}
	}

	//首次到达楼层
	this.myFirstArrive = function (floorId) {
		const r = core.status.maps[floorId].ratio;
		const ac = [];
		if (flags.tsumugi) core.mySetStatus('hp', this.getSkillValues().tsumugi, '+'); //恢复生命值，琴吹䌷
		//if (flags.mutsumi && !this.isBossFloor(floorId)) core.insertAction(this.mutsumiArrive(floorId)); //若叶睦随机事件
		if (floorId === 'MT11') { //商店倍率到达新区域增加
			flags.shopRatio = 2;
			if (flags.sakiko) {
				hero.money -= 16800;
				ac.push("欠款16800！");
			}
		} else if (floorId === 'MT31') {
			flags.shopRatio = 4;
		} else if (floorId === 'MT41') {
			flags.shopRatio = 5;
		}
		if (flags.leftFeeBonus) { //剩余费用加成，每点剩余费用增加5攻5防，每个区域至多15攻15防
			let a;
			switch (floorId) {
			case 'MT1':
				a = flags.leftFeeBonus[0];
				break;
			case 'MT11':
				a = flags.leftFeeBonus[1];
				break;
			case 'MT21':
				a = flags.leftFeeBonus[2];
				break;
			case 'MT31':
				a = flags.leftFeeBonus[3];
				break;
			case 'MT41':
				a = flags.leftFeeBonus[4];
				break;
			default:
				a = 0;
			}
			if (a) {
				core.mySetStatus('atk', a, '+');
				core.mySetStatus('def', a, '+');
				ac.push("剩余费用奖励！+" + a + "攻防");
			}
		}
		if (ac.length) {
			core.insertAction(ac);
		}
	}

	//跟随
	this.followOrUnfollow = function () {
		if (flags.following) {
			core.unfollow();
		} else {
			flags.followers.forEach(x => { core.follow(x); });
		}
		flags.following = !flags.following;
	}

	//设置属性值
	this.mySetStatus = function (name, value, operator) {
		let x = hero[name];
		switch (operator) {
		case '+':
		case '+=':
			x += value;
			break;
		case '-':
		case '-=':
			x -= value;
			break;
		case '*':
		case '*=':
			x *= value;
			break;
		case '/':
		case '/=':
			x /= value;
			break;
		default:
			x = value;
		}
		x = Math.round(x);
		core.setStatus(name, x);
	}

	//在drawTextContent之前进行${}计算
	this.myDrawTextContent = function (ctx, content, config) {
		const content2 = core.replaceText(content);
		core.drawTextContent(ctx, content2, config);
	}

	//选角色中
	this.choosingMembers = function () {
		//锁定控制
		core.lockControl();
		//设定eventid
		flags.statuseventid = core.status.event.id;
		core.status.event.id = 'chooseMember';
		//角色排列
		const memberIds = [
			['tomori', 'anon', 'taki', 'rana', 'soyo'],
			['sakiko', 'mutsumi', 'umiri', 'nyamu', 'uika'],
			['bocchi', 'ryou', 'ikuyo', 'nijika', 'mana'],
			['nina', 'momoka', 'subaru', 'tomo', 'rupa'],
			['yui', 'mio', 'ritsu', 'tsumugi', 'azusa']
		];

		//尺寸位置常数
		const MAX = 13 * 32;

		const KKLITTLE = 40;
		const KKGAP = KKLITTLE + 6;
		const KKLEFT = 30;
		const KKWIDTH = KKGAP * 5 + 5;
		const KKHEIGHT = KKGAP * 5 + 7;
		const KKBOTTOM = MAX - 10;
		const KKTOP = KKBOTTOM - KKHEIGHT;

		const NOWFEEX = KKLEFT + KKWIDTH + 115;
		const NOWFEEY = KKTOP + KKGAP;

		const OKX = NOWFEEX - 90;
		const OKY = KKBOTTOM - 2;

		//绑定角色和位置
		const memberkks = [];
		for (let i = 0; i < 5; i++) {
			for (let j = 0; j < 5; j++) {
				const a = {};
				a.id = memberIds[j][i];
				a.x = KKLEFT + KKGAP * i + 5; //小框左上角
				a.y = KKTOP + KKGAP * j + 5;
				a.centerx = a.x + KKLITTLE / 2; //中心
				a.centery = a.y + KKLITTLE / 2;
				a.image = a.id + '.png';
				a.fee = members[a.id].fee;
				memberkks.push({ ...a });
			}
		}

		//初始化变量
		let nowMember = {}; //当前正在选中查看的角色
		let nowIndex = -1; //当前正在选中的位置
		let nowFee = 0; //当前产生的费用
		const chosenMembers = []; //已选择角色

		//创建画布
		core.createCanvas('选角背景', 0, 0, MAX, MAX, 100);
		core.createCanvas('框框', 0, 0, MAX, MAX, 110);
		core.createCanvas('框框颜色', 0, 0, MAX, MAX, 109);
		core.createCanvas('角色', 0, 0, MAX, MAX, 120);
		core.createCanvas('信息展示', 0, 0, MAX, MAX, 110);
		core.createCanvas('选择确认', 0, 0, MAX, MAX, 111);
		core.createCanvas('疑问', 0, 0, MAX, MAX, 150);
		core.createCanvas('疑问信息', 0, 0, MAX, MAX, 151);
		const memberFeeCtx = core.createCanvas('角色费用', 0, 0, MAX, MAX, 121);
		const chosenFeeCtx = core.createCanvas('已选费用', 0, 0, MAX, MAX, 130);
		core.createCanvas('确认按钮', 0, 0, MAX, MAX, 140);


		//工具函数
		const strokeRoundRect = (x, y, width, height, radius, chosen) => core.strokeRoundRect('框框', x, y, width, height, radius, chosen ? '#32cd32' : '#ffffff', 2);
		const fillRoundRect = (x, y, width, height, radius, deepen) => core.fillRoundRect('框框颜色', x, y, width, height, radius, deepen ? '#555555' : '#a9a9a9');
		const drawMember = (image, x, y) => {
			if (image === 'nijika.png' || image === 'subaru.png')
				core.drawImage('角色', image, 0, 0, 32, 35, x, y - 3 / 2, 32, 35);
			else
				core.drawImage('角色', image, 0, 0, 32, 32, x, y, 32, 32);
		};

		//设定背景为黑色底色
		const drawBackground = () => {
			core.clearMap('选角背景');
			core.fillRect('选角背景', 0, 0, MAX, MAX, '#151515');
		}

		//绘制大框框
		const drawBigRectangle = () => {
			strokeRoundRect(KKLEFT, KKTOP, KKWIDTH, KKHEIGHT, 10);
		}

		//绘制小框框和小框框颜色和角色和费用
		const drawLittleRectangleAndMember = () => {
			memberkks.forEach(
				mk => {
					fillRoundRect(mk.x, mk.y, KKLITTLE, KKLITTLE, 5);
					strokeRoundRect(mk.x, mk.y, KKLITTLE, KKLITTLE, 5, chosenMembers.includes(mk.id));
					drawMember(mk.image, mk.centerx - 19, mk.centery - 16);
					core.fillText('角色费用', mk.fee, mk.centerx + 18, mk.centery + 16, '#000000', 'bold 14px 方正像素15');
				}
			);
		}
		//根据角色重绘一个小框
		const drawOneLittleRectangle = (mk) => {
			strokeRoundRect(mk.x, mk.y, KKLITTLE, KKLITTLE, 5, chosenMembers.includes(mk.id));
		}

		//制造一个字体设置
		const createConfig = (info, colour) => {
			const config = {};
			config.font = '方正像素15';
			config.left = 30;
			config.maxWidth = MAX - 30 * 2;
			switch (info) {
			case 'name':
				config.fontSize = 28;
				config.top = 10;
				config.color = colour;
				break;
			case 'skillName':
				config.fontSize = 18;
				config.top = 46;
				config.color = '#DDA0DD';
				break;
			case 'skillDescription':
				config.fontSize = 18;
				config.top = 70;
				config.color = '#BC8F8F';
				break;
			case 'skillDescriptionNijika':
				config.fontSize = 14;
				config.top = 70;
				config.color = '#BC8F8F';
				break;
			case 'fee':
				config.fontSize = 20;
				config.top = 10;
				config.left = 280;
				config.color = '#A9A9A9';
				break;
			case 'choose':
				config.fontSize = 40;
				config.top = 7;
				config.left = 330;
				config.color = '#A9A9A9';
				break;
			case 'cancel':
				config.fontSize = 41;
				config.top = 5;
				config.left = 330;
				config.color = '#32cd32';
				break;
			case 'chooseText1':
				config.fontSize = 14;
				config.top = 14;
				config.left = 373;
				config.color = '#A9A9A9';
				break;
			case 'chooseText2':
				config.fontSize = 14;
				config.top = 30;
				config.left = 373;
				config.color = '#A9A9A9';
				break;
			case 'heroIcon':
				config.fontSize = 18;
				config.top = 70;
				config.color = '#BC8F8F';
				break;
			}
			return config;
		}

		//绘制已选费用
		const drawNowFee = () => {
			core.clearMap('已选费用');
			core.fillText('已选费用', chosenMembers.length + ' /  ' + 5 + ' 人', NOWFEEX, NOWFEEY, '#A9A9A9', 'bold 20px 方正像素15');
			core.fillText('已选费用', nowFee + ' / ' + totalFee + ' 费', NOWFEEX, NOWFEEY + 25, '#A9A9A9', 'bold 20px 方正像素15');
		}
		//绘制确认按钮
		const drawOK = () => {
			core.fillText('确认按钮', '进入游戏', OKX, OKY, '#ff4500', 'bold 28px 方正像素15');
		}

		//绘制信息按钮
		const drawQuestionMark = () => {
			core.clearMap('疑问');
			core.fillText('疑问', '?', 366, 168, '#4169E1', 'bold 16px 方正像素16');
			core.strokeCircle('疑问', 370, 162, 11, '#4169E1', 2);
		}

		//绘制信息
		const drawQuestionInformation = () => {
			const config = {};
			config.font = '方正像素15';
			config.top = 28;
			config.left = 70;
			config.maxWidth = MAX - config.left * 2;
			config.color = '#BC8F8F';
			config.fontSize = 16;

			const text = '选择角色：选择至多5名角色组队，每名角色右下角的数字即为费用，总费用不得超过15，选定角色后，选择主角，视为主角拥有这些角色的技能进入魔塔\\n\\n剩余费用：选完角色后，剩余的费用每点会自动转化为1攻1防（每个区域至多转化3点）\\n\\n区域：魔塔共50层，每10层为一个区域\\n\\n宝石血瓶基础值（一区）：\\n\\i[redGem]2攻击\\i[blueGem]2防御\\n\\i[redPotion]50生命\\i[bluePotion]200生命\\n\\n楼层传送器：当角色不能直接走到楼梯边上时，不能使用';

			const x = 60,
				y = 20,
				width = MAX - 2 * x,
				height = MAX - 2 * y;

			core.strokeRoundRect('疑问信息', x, y, width, height, 10, '#ffffff', 2);
			core.fillRoundRect('疑问信息', x, y, width, height, 10, '#151515');

			this.myDrawTextContent('疑问信息', text, config);
		}

		//角色确认框
		const chooseOrNot = (id) => {
			core.clearMap('选择确认');
			if (chosenMembers.includes(id)) {
				this.myDrawTextContent('选择确认', '☑', createConfig('cancel'));
			} else {
				this.myDrawTextContent('选择确认', '□', createConfig('choose'));
			}
			this.myDrawTextContent('选择确认', '选她', createConfig('chooseText1'));
			this.myDrawTextContent('选择确认', '组队', createConfig('chooseText2'));
		}

		//选中角色显示信息
		const displayMemberInfo = (mk) => {
			core.clearMap('信息展示');
			const member = members[mk.id];
			this.myDrawTextContent('信息展示', member.name, createConfig('name', member.colour));
			this.myDrawTextContent('信息展示', member.skillName, createConfig('skillName'));
			if (mk.id === 'nijika')
				this.myDrawTextContent('信息展示', member.skillDescriptionTaki ? member.skillDescriptionTaki : member.skillDescription, createConfig('skillDescriptionNijika'));
			else
				this.myDrawTextContent('信息展示', member.skillDescriptionTaki ? member.skillDescriptionTaki : member.skillDescription, createConfig('skillDescription'));
			this.myDrawTextContent('信息展示', member.fee + '费', createConfig('fee'));
			chooseOrNot(mk.id);
		};
		//选择角色或取消选择
		const chooseOneMember = (mk) => {
			const id = mk.id;
			const index = chosenMembers.indexOf(id);
			const fee = members[id].fee;
			if (index >= 0) {
				chosenMembers.splice(index, 1);
				nowFee -= fee;
			} else {
				if (nowFee + fee <= 15) {
					if (chosenMembers.length >= 5) {
						core.drawTip('超出人数');
					} else {
						chosenMembers.push(id);
						nowFee += fee;
					}
				} else core.drawTip('超出费用');
			}
			chooseOrNot(id);
			drawOneLittleRectangle(mk);
			drawNowFee();
		}
		//选中角色加深颜色，退出时恢复颜色
		const deepen = (mk) => fillRoundRect(mk.x, mk.y, KKLITTLE, KKLITTLE, 5, true);
		const recover = (mk) => fillRoundRect(mk.x, mk.y, KKLITTLE, KKLITTLE, 5);

		//进入游戏
		const startGame = () => {
			//删除画布
			core.deleteCanvas('选角背景');
			core.deleteCanvas('框框');
			core.deleteCanvas('框框颜色');
			core.deleteCanvas('角色');
			core.deleteCanvas('信息展示');
			core.deleteCanvas('选择确认');
			core.deleteCanvas('角色费用');
			core.deleteCanvas('已选费用');
			core.deleteCanvas('确认按钮');
			core.deleteCanvas('疑问');
			core.deleteCanvas('疑问信息');
			//执行下一阶段
			this.afterChoosingMembers();
		}

		const chosenmemberkks = [];
		const bindchosenmemberkks = (arr) => {
			chosenmemberkks.length = 0;
			//绑定角色和位置
			for (let j = 0; j < 5; j++) {
				const a = {};
				arr = arr || chosenMembers;
				a.id = arr[j];
				a.x = (MAX - KKLITTLE) / 2 + KKGAP * (j - 2); //小框左上角
				a.y = (MAX - KKLITTLE) / 2;
				a.centerx = a.x + KKLITTLE / 2; //中心
				a.centery = a.y + KKLITTLE / 2;
				if (a.id) {
					a.image = a.id + '.png';
					//a.fee = members[a.id].fee;
				}
				chosenmemberkks.push({ ...a });
			}
		}
		const drawLittleRectangleAndMemberHeroIcon = () => {
			chosenmemberkks.forEach(
				mk => {
					core.clearMap('角色', mk.centerx - 20, mk.centery - 20, 40, 40);
					fillRoundRect(mk.x, mk.y, KKLITTLE, KKLITTLE, 5);
					strokeRoundRect(mk.x, mk.y, KKLITTLE, KKLITTLE, 5, mk.id);
					if (mk.id) drawMember(mk.image, mk.centerx - 16, mk.centery - 16);
					//core.fillText('角色费用', mk.fee, mk.centerx + 18, mk.centery + 16, '#000000', 'bold 14px 方正像素15');
				}
			);
		}
		const drawHeroIconText = () => {
			const text = '点击两个格子交换，最左侧的角色成为主角，其余角色按从左到右顺序跟随';
			this.myDrawTextContent('信息展示', text, createConfig('heroIcon'));
		}
		//选行走图和跟随图
		const drawChooseHeroIcon = function () {
			if (core.status.event.id !== 'chooseHeroIcon') return;
			drawLittleRectangleAndMemberHeroIcon();
			drawOK();
			drawHeroIconText();
		}
		core.registerResize('drawChooseHeroIcon', drawChooseHeroIcon);
		const chooseHeroIcon = () => {
			//进入行走图选择界面
			core.status.event.id = 'chooseHeroIcon';
			//core.clearMap('选角背景');
			core.clearMap('框框');
			core.clearMap('框框颜色');
			core.clearMap('角色');
			core.clearMap('信息展示');
			core.clearMap('选择确认');
			core.clearMap('角色费用');
			core.clearMap('已选费用');
			core.deleteCanvas('疑问');
			core.deleteCanvas('疑问信息');
			//core.clearMap('确认按钮');
			bindchosenmemberkks();
			nowMember = {};


			drawChooseHeroIcon();

		}

		//制造一个判断在不在某个角色框内的函数
		const isinmk = (px, py) => {
			const inmk = (mk) => {
				const dx = px - mk.x;
				const dy = py - mk.y;
				return dx >= 0 && dx <= KKLITTLE && dy >= 0 && dy <= KKLITTLE;
			};
			return inmk;
		}
		//制造一个判断在不在确认框的函数
		const makeInBox = (x1, y1, x2, y2) => {
			const isInBox = (px, py) => px >= x1 && px <= x2 && py >= y1 && py <= y2;
			return isInBox;
		}
		const isInChooseButton = makeInBox(329, 12, 363, 48);
		const isInStartButton = makeInBox(290, 379, 398, 406);
		const isInQuestionButton = makeInBox(359, 150, 381, 172);
		//点击时要做的事情，点击角色则查看信息，点击确认则选定角色，点击开始则开始游戏，点击问号则进入信息显示页面
		const memberclick = (x, y, px, py) => {
			if (core.status.event.id !== 'chooseMember' && core.status.event.id !== 'chooseHeroIcon' && core.status.event.id !== 'chooseMemberQuestion') return false;
			const inmk = isinmk(px, py);
			if (core.status.event.id === 'chooseMember') {
				const mk = memberkks.find(inmk);
				if (mk) { //如果选中的是角色
					if (nowMember.id) recover(nowMember);
					nowMember = mk;
					deepen(nowMember);
					displayMemberInfo(nowMember);
				} else if (nowMember) { //如果有当前角色
					if (isInChooseButton(px, py)) {
						chooseOneMember(nowMember);
					}
				}
				if (isInStartButton(px, py)) { //点击进入游戏
					if (chosenMembers.length) {
						chooseHeroIcon();
					} else {
						core.drawTip('尚未选择角色');
					}
				}
				if (isInQuestionButton(px, py)) { //点击问号
					drawQuestionInformation(); //TODO
					core.status.event.id = 'chooseMemberQuestion';
				}
			} else if (core.status.event.id === 'chooseMemberQuestion') {
				core.clearMap('疑问信息');
				core.status.event.id = 'chooseMember';
			} else if (core.status.event.id === 'chooseHeroIcon') {
				const mki = chosenmemberkks.findIndex(inmk);
				if (mki > -1) { //如果选中了框
					if (nowIndex > -1) { //如果有选择的框
						if (mki === nowIndex) { //如果两个一样，取消选择
							recover(chosenmemberkks[mki]);
							nowIndex = -1;
						} else { //如果两个不一样，交换
							const arr = [0, 0, 0, 0, 0].map((_, i) => chosenmemberkks[i].id);
							[arr[mki], arr[nowIndex]] = [arr[nowIndex], arr[mki]];
							bindchosenmemberkks(arr);
							drawLittleRectangleAndMemberHeroIcon();
							nowIndex = -1;
						}
					} else { //如果没有选择的框
						nowIndex = mki;
						deepen(chosenmemberkks[mki]);
					}
				}
				if (isInStartButton(px, py)) { //点击进入游戏
					//let replay = 'chooseM:';
					let replays = [];
					let followers = [];
					let j = -1;
					for (let i = 0; i < 5; i++) {
						mk = chosenmemberkks[i];
						if (mk.id) {
							if (j === -1) {
								//replay += mk.id + ':';
								replays.push('choices:' + allIds.indexOf(mk.id));
								core.setHeroIcon(mk.image);
								core.setStatus('name', members[mk.id].name);

								j = i;
							} else {
								//replay += mk.id + ':';
								replays.push('choices:' + allIds.indexOf(mk.id));
								followers.push(mk.image);
							}
						} else {
							replays.push('choices:' + allIds.length);
						}
					}
					//core.status.route.push(replay);
					replays = replays.sort((a, b) => {
						const m = 'choices:' + allIds.length;
						if (a === m && b !== m) return 1;
						else if (b === m && a !== m) return -1;
						else return 0;
					});
					replays.forEach(x => core.status.route.push(x));
					//将已选角色和剩余费用加入flags
					flags.members = [...chosenMembers];
					flags.leftFee = totalFee - nowFee;
					flags.followers = followers;
					flags.following = false;
					this.followOrUnfollow();
					startGame();
				}
			}

			return true;
		}
		core.registerAction('ondown', 'memberclick', memberclick, 100);

		//具体执行
		const drawChoosingMembers = function () {
			if (core.status.event.id !== 'chooseMember') return;
			memberFeeCtx.textAlign = 'right';
			chosenFeeCtx.textAlign = 'right';
			drawBackground();
			drawBigRectangle();
			drawLittleRectangleAndMember();
			drawNowFee();
			drawOK();
			drawQuestionMark();
			if (nowMember.id) {
				deepen(nowMember);
				displayMemberInfo(nowMember);
			}
		}

		drawChoosingMembers();
		core.registerResize('drawChoosingMembers', drawChoosingMembers);
	}

	//选完角色后进塔开始前
	this.afterChoosingMembers = function () {
		//获得每个所选角色的flag
		flags.members.forEach((member) => {
			flags[member] = true;
		});
		//将选择组合计入flag方便结局使用
		flags.选择组合 = allIds.filter(id => core.getFlag(id, false)).map(id => members[id].name).join('+');
		switch (flags.选择组合) {
		case '高松灯+千早爱音+椎名立希+要乐奈+长崎素世':
			flags.选择组合 = 'MyGO!!!!!';
			break;
		case '三角初华+八幡海铃+祐天寺若麦+若叶睦+丰川祥子':
			flags.选择组合 = 'Ave Mujica';
			break;
		case '后藤一里+山田凉+喜多郁代+伊地知虹夏':
			flags.选择组合 = '孤独摇滚';
			break;
		case '井芹仁菜+河原木桃香+安和昴+海老冢智+RUPA':
			flags.选择组合 = '无刺有刺';
			break;
		case '平泽唯+秋山澪+田井中律+琴吹䌷+中野梓':
			flags.选择组合 = '放课后TEA TIME';
			break;
		case '高松灯+椎名立希+长崎素世+若叶睦+丰川祥子':
			flags.选择组合 = 'CRYCHIC';
			break;
		}
		//主动技能，存入flag
		flags.主动技能 = allIds.filter(id => flags[id] && members[id].主动技能).map(id => members[id].主动技能);
		//部分角色需要初始设置

		if (flags.nijika) { //在其他角色改变地图之前，虹夏先记录地图，但是交换怪物颜色在乐奈之后
			flags.nijikaMap = {};
			core.floorIds.forEach(
				(floorId) => {
					if (this.isBossFloor(floorId)) { //boss层不用记录
						return;
					}
					flags.nijikaMap[floorId] = {};
					for (let x = 0; x < 13; x++) {
						for (let y = 0; y < 13; y++) {
							const block = core.getBlock(x, y, floorId);
							if (block) {
								const cls = block.event.cls;
								const id = block.event.id;
								if (cls === 'enemys' || (cls === 'items' && id !== 'redKey') || id === 'yellowDoor' || id === 'blueDoor' || (flags.taki && id === 'redKey')) {
									flags.nijikaMap[floorId][x + ',' + y] = id; //记录在这个flag中，之后和这个位置无关的不操作
								}
							}
						}
					}
				}
			);
			flags.nijikaSkillFloor = {};
		}

		if (flags.tomori) flags.canPushBox = true; //高松灯允许推箱子
		//if (flags.rana) this.exchangeYellowAndBlue(); //要乐奈互换黄蓝
		if (flags.soyo) { //获得长崎素世的技能专属道具，TODO，并获得每个区域的次数
			core.getItem('soyoCut');
			flags.soyoCutTimes = [0, 0, 0, 0, 0, 0].map(x => this.getSkillValues().soyo);
		}
		if (flags.uika) { //获得三角初华的技能专属道具，并获得每个区域的次数
			core.getItem('uikaWall');
			flags.uikaWallTimes = [0, 0, 0, 0, 0, 0].map(x => this.getSkillValues().uika);
		}
		if (flags.mutsumi) { //获得若叶睦的技能专属道具，TODO
			//core.getItem('');
			//确定随机事件
			//this.mutsumiArriveEvents();
		}
		if (flags.nyamu) this.removeSpecials(); //祐天寺若麦删除怪物特殊属性
		if (flags.sakiko) { //丰川祥子给幸运金币
			//hero.money -= 168;
			core.getItem('coin');
		}
		if (flags.ikuyo) { //获得喜多郁代的技能专属道具
			core.getItem('kitaIkuyo');
			flags.kitaIkuyoTimes = this.getSkillValues().ikuyo;
		}
		if (flags.nijika) { //伊地知虹夏互换怪物颜色，并获得技能次数
			this.enemyColourExchange();
			core.getItem('nijikaWand');
			//flags.nijikaTimes = this.getSkillValues().nijika;
			flags.nijikaTimes = [0, 0, 0, 0, 0, 0].map(x => this.getSkillValues().nijika);
		}
		if (flags.umiri) { //八幡海铃设置红宝石修正值
			//flags.redGemRatio = this.getSkillValues().umiri;
			flags.umiriRegion = [0, 0, 0, 0, 0, 0];
		}
		if (flags.anon) { //千早爱音设置蓝宝石修正值
			//flags.blueGemRatio = this.getSkillValues().anon;
			//获得圣水
			core.getItem('superPotion');
			this.mySetStatus('def', this.getSkillValues().anon, '+');
		}
		if (flags.subaru) { //安和昴设置血瓶修正值
			//this.mySetStatus('hp', (100 + this.getSkillValues().subaru) / 100, '*');
			flags.potionRatio = this.getSkillValues().subaru;
		}
		if (flags.tomo) core.getItem('snow');
		if (flags.rupa) core.getItem('rupaWine'); //获得RUPA的技能专属道具，TODO
		if (flags.mio) { //秋山澪设置血瓶变宝石
			//this.convertToGem();
			core.getItem('mioWand');
			//flags.mioTimes = this.getSkillValues().mio;
		}
		if (flags.ritsu) {
			core.getItem('ritsuMelody');
			flags.ritsuTimes = 1;
		} //获得田井中律的技能专属道具
		if (flags.mana) flags.leftFee = totalFee; //选纯田真奈直接把剩余费用重置到15
		if (flags.momoka) flags.momokaNoHit = []; //选河原木桃香初始化

		if (flags.leftFee) {
			const c = 3;
			const a = flags.leftFee % c;
			const b = (flags.leftFee - a) / c;
			const d = [0, 0, 0, 0, 0, 0];
			let i;
			for (i = 0; i < b; i++) d[i] = 3;
			d[i] = a;
			flags.leftFeeBonus = [...d];
		}
		if (!core.isReplaying()) {
			core.status.event.id = flags.statuseventid;
			core.doAction();
		}
	}

	//黄蓝互换，要乐奈
	this.exchangeYellowAndBlue = function () {
		//定义交换id数组
		const exchangeIds = [
			['yellowKey', 'blueKey'],
			['yellowDoor', 'blueDoor'],
			['yellowPotion', 'bluePotion'],
			['yellowSlime', 'blueSlime'],
			['yellowSlimeHang', 'blueSlimeHang'],
			['yellowSlimeBadge', 'blueSlimeBadge'],
			['yellowSlimeEarphone', 'blueSlimeEarphone'],
			['yellowSlimeCall', 'blueSlimeCall'],
			['yellowGem', 'blueGem'],
		];
		const id1s = exchangeIds.map(x => x[0]);
		const id2s = exchangeIds.map(x => x[1]);
		//每层需要做的事情
		const toExchange = (floorId) => {
			//首先遍历一遍记录数组中出现过的id以及需要变更的东西
			const toExchangeItem = [];
			for (let x = 0; x < 13; x++) {
				for (let y = 0; y < 13; y++) {
					const id = core.getBlockId(x, y, floorId);
					let index = id1s.indexOf(id);
					if (index !== -1) {
						toExchangeItem.push({
							x: x,
							y: y,
							newId: id2s[index]
						});
					} else {
						index = id2s.indexOf(id);
						if (index !== -1) {
							toExchangeItem.push({
								x: x,
								y: y,
								newId: id1s[index]
							});
						}
					}
				}
			}
			//然后依次对记录的格子执行变更
			toExchangeItem.forEach(({ x, y, newId }) => core.setBlock(newId, x, y, floorId))
		}
		//应用到所有楼层
		core.floorIds.forEach(toExchange);
	}

	//怪物将以下面这个作为id前缀
	const colours = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'violet', 'red'];
	//推移怪物颜色，触发在要乐奈之后，伊地知虹夏
	/*this.enemyColourExchange = function () {
		const colourExchange = (floorId) => {
			const f = (x, y, id) => {
				const f1 = (colour, i, arr) => {
					if (i === 7) return;
					if (id.startsWith(colour)) {
						const newId = arr[i + 1] + id.slice(colour.length);
						core.setBlock(newId, x, y, floorId);
					} else return;
				}
				return f1;
			}

			for (let x = 0; x < 13; x++) {
				for (let y = 0; y < 13; y++) {
					const block = core.getBlock(x, y, floorId);
					const cls = block ? block.event.cls : undefined;
					if (cls === 'enemys') {
						const id = block.event.id;
						const g = f(x, y, id);
						colours.forEach(g);
					}
				}
			}
		}
		core.floorIds.forEach(colourExchange);
	}*/
	const nijikaEnemys = [
		['greenSlime', 'redSlime', 'bat', 'bluePriest', 'skeleton', 'skeletonSoilder', 'yellowGuard', 'greenSlime'],
		['blackSlime', 'bigBat', 'redPriest', 'zombie', 'zombieKnight', 'rock', 'blackSlime'],
		['slimeMan', 'ghostSkeleton', 'soldier', 'swordsman', 'redKnight', 'blueGuard', 'slimeMan'],
		['slimelord', 'redBat', 'brownWizard', 'redWizard', 'whiteKing', 'darkKnight', 'redGuard', 'slimelord']
	];
	this.enemyColourExchange = function () {
		const f = (i, id) => {
			const index = nijikaEnemys[i].indexOf(id);
			const newId = nijikaEnemys[i][index + 1];
			return newId;
		}
		const levelExchange = (floorId) => {
			for (let x = 0; x < 13; x++) {
				for (let y = 0; y < 13; y++) {
					if (core.getBlockCls(x, y, floorId) === 'enemys') {
						const id = core.getBlockId(x, y, floorId);
						let newId;
						if (nijikaEnemys[0].includes(id)) {
							newId = f(0, id);
						} else if (nijikaEnemys[1].includes(id)) {
							newId = f(1, id);
						} else if (nijikaEnemys[2].includes(id)) {
							newId = f(2, id);
						} else if (nijikaEnemys[3].includes(id)) {
							newId = f(3, id);
						}
						if (newId) core.setBlock(newId, x, y, floorId);
					}
				}
			}
		}
		core.floorIds.forEach(levelExchange);
	}
	this.enemyColourRecover = function () {
		if (flags.nijikaTimes <= 0) {
			core.drawTip('次数已用完，不许变');
			return;
		}
		const callback = () => {
			let recovered = false;
			const f = (x, y, id) => {
				const f1 = (colour, i, arr) => {
					if (i === 0) return;
					if (id.startsWith(colour)) {
						const newId = arr[i - 1] + id.slice(colour.length);
						core.setBlock(newId, x, y);
						recovered = true;
					} else return;
				}
				return f1;
			}
			for (let x = 0; x < 13; x++) {
				for (let y = 0; y < 13; y++) {
					const block = core.getBlock(x, y);
					const cls = block ? block.event.cls : undefined;
					if (cls === 'enemys') {
						const id = block.event.id;
						const g = f(x, y, id);
						colours.forEach(g);
					}
				}
			}
			if (recovered) flags.nijikaTimes -= 1;
			else core.drawTip('本层没有小怪，不许变');
			core.unlockControl();
		}
		if (!core.isReplaying()) {
			core.lockControl();
			core.nijikaGray(callback);
		} else {
			callback();
		}
	}
	this.enemyColourRecover2 = function () {
		if (this.isBossFloor()) {
			core.drawTip('处于boss层，不许发动');
			return;
		}
		if (flags.nijikaTimes[this.getRegion()] <= 0) {
			core.drawTip('本区次数已用完，不许发动');
			return;
		}
		if (flags.nijikaSkillFloor[core.status.floorId]) {
			core.drawTip('本层已恢复过，不许发动');
			return;
		}
		const callback = () => {
			for (let x = 0; x < 13; x++) {
				for (let y = 0; y < 13; y++) {
					const id = flags.nijikaMap[core.status.floorId][x + ',' + y];
					if (id) core.setBlock(id, x, y);
				}
			}
			flags.nijikaTimes[this.getRegion()] -= 1;
			flags.nijikaSkillFloor[core.status.floorId] = true;
			core.insertAction([
				{ "type": "changeFloor", "floorId": ":now", "stair": "downFloor" },
			]);
			//core.unlockControl();
		}
		if (!core.isReplaying()) {
			core.lockControl();
			core.nijikaGray(callback);
		} else {
			callback();
		}
	}

	//删除怪物颜色技能，祐天寺若麦
	/*this.removeSpecials = function () {
		Object.entries(core.material.enemys).forEach(([id, enemy]) => {
			if (!bossEnemys.includes(id)) {
				if (enemy.special instanceof Array) {
					const special = enemy.special.filter(x => x >= 2000);
					core.setEnemy(id, 'special', special);
				}
			}
		});
	}
	*/
	this.removeSpecials = function () {
		Object.entries(core.material.enemys).forEach(([id, enemy]) => {
			core.setEnemy(id, 'special', []);
		});
	}

	//判定是否可以爬墙，三角初华
	//哪些id是墙壁
	this.getWalls = function () {
		return ['whiteWall', 'yellowWall'];
	}
	/*
	this.canClimbWall = function () {
		//哪些id是墙壁
		const walls = this.getWalls();
		//面前是否是墙壁
		const id1 = core.getBlockId(core.nextX(), core.nextY());
		const flag1 = walls.includes(id1);
		//脚下是否是墙壁
		const id2 = core.getBlockId(core.nextX(0), core.nextY(0));
		const flag2 = walls.includes(id2);
		//次数是否用尽
		const flag3 = (flags.uikaWallTimes[this.getRegion()] > 0);
		return flag3 && (flag1 !== flag2);
	}
	*/
	//返回不能爬墙的理由，返回步数表示可以爬墙
	this.canClimbWall = function () {
		if (this.isBossFloor()) return '处于boss层，不许爬'; //boss层不能爬
		if (flags.uikaWallTimes[this.getRegion()] === 0) return '当前区域次数已尽，不许爬'; //该区域没有次数直接不能爬
		const walls = core.getWalls();
		for (let i = 1;; i++) { //遍历勇者前方
			const x = core.nextX(i);
			const y = core.nextY(i);
			if (x < 0 || x > 12 || y < 0 || y > 12) return '达到楼层边缘，不许爬'; //超出地图范围直接不能爬
			const block = core.getBlock(x, y);
			const id = block ? block.event.id : undefined;
			const cls = block ? block.event.cls : undefined;
			if (!walls.includes(id)) {
				if (i === 1) { //第一步不是墙直接不能爬
					return '面前不是墙，不许爬';
				} else if (cls === undefined || cls === 'items') { //落点是空地或道具，能爬，并且给出步数
					return i;
				} else return '墙的另一头不是空地或者道具，不许爬'; //落点不是空地或道具不能爬
			}
		}
	}
	this.climbWall = function () {
		const step = this.canClimbWall();
		if (typeof step === 'string') {
			core.drawTip(step);
			return;
		}
		const action = [{ type: "moveHero", time: 200, steps: [core.getHeroLoc('direction') + ":" + step] }, { "type": "trigger", "loc": ["core.getHeroLoc('x')", "core.getHeroLoc('y')"] }, ];
		/*
		const callback = () => {
			const walls = core.getWalls();
			const id = core.getBlockId(core.nextX(0), core.nextY(0));
			if (walls.includes(id)) {
				flags.uikaWallTimes[core.getRegion()] -= 1;
				flags.onWall = true;
			} else flags.onWall = false;
		}
		*/
		core.insertAction(action);
		flags.uikaWallTimes[core.getRegion()] -= 1;
	}

	//假酒害人，RUPA
	this.fakeWine = function (floorId) {
		floorId = floorId || core.status.floorId;
		const x = core.nextX();
		const y = core.nextY();
		const id = core.getBlockId(x, y, floorId);
		if (['man', 'trader', 'specialDoor'].includes(id) && hero.def >= this.getSkillValues().rupa.def) {

		} else {
			core.drawTip('没有面对老人商人机关门，不许喝');
			return;
		}
		//扣除防御力
		this.mySetStatus('def', this.getSkillValues().rupa.def, '-');
		//如果发动技能攻击商人，则获得商人出售的东西
		if (id === 'trader') {
			this.getMerchant(floorId).trade.forEach(
				({ item, value }) => {
					if (item === 'hp' || item === 'atk' || item === 'def' || item === 'money') {
						this.mySetStatus(item, value, '+');
					} else
						core.getItem(item, value);
				}
			)
		}
		//如果发动技能攻击老人，则记事本仍然记录老人的话
		if (id === 'man') {
			// 获得金币
			let money = this.getSkillValues().rupa.man;
			if (core.hasItem('coin')) money *= 2;
			core.status.hero.money += money;
			core.status.hero.statistics.money += money;

			flags.noteBook.push(floorId);
		}

		// 播放战斗音效和动画
		let animate = 'hand'; // 默认动画
		if (flags.tomo) animate = 'iced';
		// 检查当前装备是否存在攻击动画
		let equipId = core.getEquip(0);
		if (equipId && (core.material.items[equipId].equip || {}).animate)
			animate = core.material.items[equipId].equip.animate;
		if (animate === 'iced') core.playSound('attack.mp3');

		// 播放动画；如果不存在坐标（强制战斗）则播放到勇士自身
		if (x != null && y != null)
			core.drawAnimate(animate, x, y);
		else
			core.drawHeroAnimate(animate);

		core.removeBlock(x, y, floorId);

	}

	//血瓶变宝石，秋山澪
	this.convertToGem = function () {
		const toGem = (floorId) => {
			let isPotion = false;
			for (let x = 0; x < 13; x++) {
				for (let y = 0; y < 13; y++) {
					isPotion = false;
					const block = core.getBlock(x, y, floorId);
					const id = block ? block.event.id : undefined;
					let newId;
					switch (id) {
					case 'redPotion':
						newId = 'redGem';
						isPotion = true;
						break;
					case 'bluePotion':
						newId = 'blueGem';
						isPotion = true;
						break;
					case 'yellowPotion':
						newId = 'yellowGem';
						isPotion = true;
						break;
					}
					if (isPotion) {
						core.setBlock(newId, x, y, floorId);
					}
				}
			}
		}
		core.floorIds.forEach(toGem);
	}
	//宝石变血瓶，秋山澪
	this.convertToPotion = function (floorId) {
		if (core.getFlag('mioTimes', 0) <= 0) { core.drawTip('技能次数已尽，不许发动') }
		floorId = floorId || core.status.floorId;
		const toPotion = (floorId) => {
			let isGem = false;
			let converted = false;
			for (let x = 0; x < 13; x++) {
				for (let y = 0; y < 13; y++) {
					isGem = false;
					const block = core.getBlock(x, y, floorId);
					const id = block ? block.event.id : undefined;
					let newId;
					switch (id) {
					case 'redGem':
						newId = 'redPotion';
						isGem = true;
						break;
					case 'blueGem':
						newId = 'bluePotion';
						isGem = true;
						break;
					case 'yellowGem':
						newId = 'yellowPotion';
						isGem = true;
						break;
					}
					if (isGem) {
						core.setBlock(newId, x, y, floorId);
						converted = true;
					}
				}
			}
			return converted;
		}
		const converted = toPotion(floorId);
		if (converted) flags.mioTimes -= 1;
	}

	//剪切线，长崎素世
	this.cutline = function (floorId) {
		floorId = floorId || core.status.floorId;
		if (this.isBossFloor()) { //boss层不能剪
			core.drawTip('处于boss层，不许剪');
			return;
		}
		if (flags.soyoCutTimes[this.getRegion()] === 0) { //该区域没有次数直接不能剪
			core.drawTip('当前区域次数已尽，不许剪');
			return;
		}
		core.insertCommonEvent('剪切线');
	}

	//获得一个楼层所处的区域
	this.getRegion = function (floorId) {
		floorId = floorId || core.status.floorId;
		const num = parseInt(floorId.slice(2));
		if (num <= 10) return 1;
		else if (num <= 20) return 2;
		else if (num <= 30) return 3;
		else if (num <= 40) return 4;
		else return 5;
	}

	//检测心，如果已有则直接读取，在触碰心后会调用force为true的即强制重算
	this.getHeart = function (floorId, force) {
		floorId = floorId || core.status.floorId;
		const heartFlag = core.getFlag('heart', {})[floorId];
		if (!force && heartFlag) return heartFlag;
		let redHeart = 0,
			blueHeart = 0,
			greenHeart = 0;
		for (let i = 0; i < 13; i++) {
			for (let j = 0; j < 13; j++) {
				switch (core.getBlockId(i, j, floorId)) {
				case 'redHeart':
					redHeart++;
					break;
				case 'blueHeart':
					blueHeart++;
					break;
				case 'greenHeart':
					greenHeart++;
					break;
				}
			}
		}
		return (core.getFlag('heart', {})[floorId] = { redHeart: redHeart, blueHeart: blueHeart, greenHeart: greenHeart });
	}

	//应援图块
	const colourBlocks = {
		红: ['redSlime', 'redSlimeHang', 'redSlimeBadge', 'redSlimeEarphone', 'redSlimeCall', 'redKey', 'redPotion', 'redGem', 'redHeart', 'redDoor', 'merchant'],
		橙: ['orangeSlime', 'orangeSlimeHang', 'orangeSlimeBadge', 'orangeSlimeEarphone', 'orangeSlimeCall'],
		黄: ['yellowSlime', 'yellowSlimeHang', 'yellowSlimeBadge', 'yellowSlimeEarphone', 'yellowSlimeCall', 'yellowKey', 'yellowPotion', 'yellowGem', 'yellowHeart', 'yellowDoor'],
		绿: ['greenSlime', 'greenSlimeHang', 'greenSlimeBadge', 'greenSlimeEarphone', 'greenSlimeCall', 'greenKey', 'greenPotion', 'greenGem', 'greenHeart', 'greenDoor'],
		青: ['cyanSlime', 'cyanSlimeHang', 'cyanSlimeBadge', 'cyanSlimeEarphone', 'cyanSlimeCall'],
		蓝: ['blueSlime', 'blueSlimeHang', 'blueSlimeBadge', 'blueSlimeEarphone', 'blueSlimeCall', 'blueKey', 'bluePotion', 'blueGem', 'blueHeart', 'blueDoor', 'man'],
		紫: ['violetSlime', 'violetSlimeHang', 'violetSlimeBadge', 'violetSlimeEarphone', 'violetSlimeCall'],
		黑: ['blackSlime', 'blackSlimeHang', 'blackSlimeBadge', 'blackSlimeEarphone', 'blackSlimeCall', 'star'],
	}

	//获得应援伤害增幅
	this.getCall = function (colour, floorId) {
		floorId = floorId || core.status.floorId;
		const blocks = colourBlocks[colour];
		let count = 0;
		for (let i = 0; i < 13; i++) {
			for (let j = 0; j < 13; j++) {
				const id = core.getBlockId(i, j, floorId);
				if (blocks.includes(id)) {
					count++;
				}
			}
		}
		return count;
	}

	/*
	//选角色录像处理
	const chooseM = (action) => {
		if (action.indexOf('chooseM:') !== 0) return false;
		if (!core.hasFlag('录像选择角色')) return false;
		const ids = Array.from(new Set(action.split(':')));
		let i = 1;
		let j = 0;
		let fee = 0;
		const chosenMembers = [];
		if (ids.length > 7) return false; //超过人数
		while (i < ids.length) {
			if (ids[i] === '') break;
			if (members[ids[i]]) { //存在这个人物
				fee += members[ids[i]].fee;
			}
			i++;
		}
		if (fee > totalFee) return false; //超过费用
		i = 1;
		j = 0;
		while (i < ids.length) {
			if (ids[i] === '') break;
			if (members[ids[i]]) { //存在这个人物
				chosenMembers.push(ids[i]);
				if (j === 0) {
					core.setHeroIcon(ids[i] + '.png');
					j = i;
				} else {
					core.follow(ids[i] + '.png');
				}
			}
			i++;
		}
		flags.members = [...chosenMembers];
		flags.leftFee = totalFee - fee;
		this.afterChoosingMembers();
		flags.录像选择角色 = false;
		core.status.route.push(action);
		core.replay();
		return true;
	}
	core.registerReplayAction('chooseM', chooseM);
	*/

	//各种角色费用
	const fees = {
		tomori: 5,
		anon: 1,
		taki: 4,
		rana: 3,
		soyo: 2,
		uika: 4,
		umiri: 2,
		nyamu: 5,
		mutsumi: 3,
		sakiko: 1,
		bocchi: 6,
		ryou: 1,
		ikuyo: 3,
		nijika: 5,
		nina: 2,
		momoka: 4,
		subaru: 1,
		tomo: 3,
		rupa: 5,
		yui: 3,
		mio: 5,
		ritsu: 4,
		tsumugi: 1,
		azusa: 2,
		mana: 15,
	};
	this.getFee = function (id) {
		if (id) return fees[id];
		else return fees;
	}

	//角色描述一览
	const members = {
		tomori: {
			name: '高松灯',
			skillName: '灵感菇',
			skillDescription: '热爱搬石，可以推动\\i[whiteWall]，boss层禁用\\n可以秒杀\\i[rock]',
			fee: fees.tomori,
			colour: '#77bbdd',
		},
		anon: {
			name: '千早爱音',
			skillName: '圣爱音',
			skillDescription: '进入魔塔后直接增加${core.getSkillValues().anon}点防御\\n获得一瓶圣水\\i[superPotion]，使用后增加10倍攻击5倍防御之和的生命值',
			skillDescriptionTaki: '进入魔塔后直接增加${core.getSkillValues().anon}点（\\i[T350]：${core.getSkillValues(true).anon}点）防御\\n获得一瓶圣水\\i[superPotion]，使用后增加10倍攻击5倍防御之和的生命值',
			fee: fees.anon,
			colour: '#ff8899',
		},
		taki: {
			name: '椎名立希',
			skillName: '压力怪',
			skillDescription: '压力部分角色，加强她们的技能（对应角色技能描述中含有图标\\i[T350]）\\n对守卫系怪物\\i[redGuard]增加${core.getSkillValues().taki}%伤害',
			fee: fees.taki,
			colour: '#7777AA',
		},
		rana: {
			name: '要乐奈',
			skillName: '流浪猫',
			skillDescription: '到达一个新区域后，直接连续传送到该区域的前九层',
			fee: fees.rana,
			colour: '#77dd77',
		},
		soyo: {
			name: '长崎素世',
			skillName: '剪切线',
			skillDescription: '主动技，每一区限${core.getSkillValues().soyo}次，可以选择一层的一列图块移除，楼梯和商店除外，boss层禁用',
			fee: fees.soyo,
			colour: '#ffdd88',
			主动技能: 'soyoCut',
		},
		uika: {
			name: '三角初华',
			skillName: '飞檐走壁',
			skillDescription: '喜欢cos蜘蛛侠，每一区限${core.getSkillValues().uika}次，可以向一个方向越过连续的墙壁，boss层禁用',
			fee: fees.uika,
			colour: '#bb9955',
			主动技能: 'uikaWall',
		},
		umiri: {
			name: '八幡海铃',
			skillName: '信任拉满',
			skillDescription: '在击败一个区域的boss前，每在该区域因战斗损失10点生命，该区域boss伤害减少1点',
			//skillDescriptionTaki: '攻击宝石提高${core.getSkillValues().umiri}%（\\i[T350]：${core.getSkillValues(true).umiri}%）',
			fee: fees.umiri,
			colour: '#335566',
		},
		nyamu: {
			name: '祐天寺若麦',
			skillName: '喵姆喵姆',
			skillDescription: '所有怪物失去特技\\n与非boss系怪物战斗时额外受到${core.getSkillValues().nyamu}%伤害',
			fee: fees.nyamu,
			colour: '#aa4477',
		},

		mutsumi: {
			name: '若叶睦',
			skillName: '和睦相处',
			//skillNameTaki: '\\r[red]${core.getSkillValues(true).mutsumi.skillName}\\r重人格',
			skillDescription: '攻击降低至${core.getSkillValues().mutsumi}%，但是2连击',
			//skillDescriptionTaki: '攻击降低\\r[red]${core.getSkillValues(true).mutsumi.ratio}\\r，但是\\r[red]${core.getSkillValues(true).mutsumi.n}\\r连击',
			fee: fees.mutsumi,
			colour: '#779977',
		},

		/*
		mutsumi: {
			name: '若叶睦',
			skillName: '和睦相处',
			//skillDescription: '可以化身成为任何角色一次，发动相应的角色技能（削弱版）',
			//skillDescription: 'demo版本，这个角色还没有做完，选了会游戏失败',
			skillDescription: '擅长模仿，首次到达非boss楼层时，会随机模仿一个角色，然后相应地增减属性，或产生特殊效果',
			fee: fees.mutsumi,
			colour: '#779977',
			主动技能: 'mutsumiCucumber',
		},
		*/
		sakiko: {
			name: '丰川祥子',
			skillName: '神人大祥',
			skillDescription: '到达11层时，欠债16800金币\\n进入游戏即获得丰川家的大手\\i[coin]，持有时每场战斗可以获得双倍金币',
			fee: fees.sakiko,
			colour: '#7799cc',
		},
		bocchi: {
			name: '后藤一里',
			skillName: '吉他英雄',
			skillDescription: '战斗时，增加60%伤害，但是每有一名队友（不包括自己），伤害就减少20%',
			fee: fees.bocchi,
			colour: '#f8a3ba',
		},
		ryou: {
			name: '山田凉',
			skillName: '凉的诱惑',
			skillDescription: '\\i[trader]\\i[moneyShop]金币不足时也可进行交易，金币数低于0时不可进行交易（通关不要求金币数）',
			fee: fees.ryou,
			colour: '#3b64a9',
		},
		ikuyo: {
			name: '喜多郁代',
			skillName: '归去来兮',
			skillDescription: '主动技，每局限${core.getSkillValues().ikuyo}次，可以记录当前位置，并传送至同层任意空地，拾取任一道具后会被传送回原位，boss层禁用',
			skillDescriptionTaki: '主动技，每局限${core.getSkillValues().ikuyo}次（\\i[T350]：${core.getSkillValues(true).ikuyo}次），可以记录当前位置，并传送至同层任意空地，拾取任一道具后会被传送回原位，boss层禁用',
			fee: fees.ikuyo,
			colour: '#df4b57',
			主动技能: 'kitaIkuyo',
		},
		nijika: {
			name: '伊地知虹夏',
			skillName: '\\r[red]虹\\r[orange]之\\r[green]记\\r[violet]忆\\r',
			skillDescription: '怪物强度轮换向后变化一次\\n\\i[greenSlime]→\\i[redSlime]→\\i[bat]→\\i[bluePriest]→\\i[skeleton]→\\i[skeletonSoilder]→\\i[yellowGuard]→\\i[greenSlime]\\n主动技，每区限${core.getSkillValues().nijika}次，可以让本层怪物道具门（\\i[redKey]和\\i[specialDoor]除外）恢复成最原本初始的状态并传送角色至下楼梯，boss层禁用',
			//skillDescriptionTaki: '七色怪物颜色轮换向后变化一次\\n\\i[redSlime]→\\i[orangeSlime]→\\i[yellowSlime]→\\i[greenSlime]→\\i[cyanSlime]→\\i[blueSlime]→\\i[violetSlime]→\\i[redSlime]\\n主动技，每局限${core.getSkillValues().nijika}次（\\i[T350]：${core.getSkillValues(true).nijika}次），可以让本层怪物恢复原本颜色',
			skillDescriptionTaki: '怪物强度轮换向后变化一次\\n\\i[greenSlime]→\\i[redSlime]→\\i[bat]→\\i[bluePriest]→\\i[skeleton]→\\i[skeletonSoilder]→\\i[yellowGuard]→\\i[greenSlime]\\n主动技，每区限${core.getSkillValues().nijika}次，可以让本层怪物道具门（\\i[redKey]和\\i[specialDoor]除外）（\\i[T350]：\\i[redKey]也可恢复）恢复成最原本初始的状态并传送角色至下楼梯，boss层禁用',
			fee: fees.nijika,
			colour: '#f3d367',
			主动技能: 'nijikaWand',
		},
		nina: {
			name: '井芹仁菜',
			skillName: '正论怪物',
			skillDescription: '无视怪物X点防御力，X为自身${core.getSkillValues().nina}%攻击',
			skillDescriptionTaki: '无视怪物X点防御力，X为自身${core.getSkillValues().nina}%（\\i[T350]：${core.getSkillValues(true).nina}%）攻击',
			fee: fees.nina,
			colour: '#d90e2c',
		},
		momoka: {
			name: '河原木桃香',
			skillName: '学籍杀手',
			//skillDescription: '进入新区域后，可以秒杀旧区域怪物\\n受到boss系怪物的伤害增加${core.getSkillValues().momoka}%',
			skillDescription: '击杀一个区域的boss后，可以秒杀该区域怪物',
			fee: fees.momoka,
			colour: '#85c9dc',
		},
		subaru: {
			name: '安和昴',
			skillName: '星昴之歌',
			skillDescription: '血瓶回复量提升${core.getSkillValues().subaru}%',
			skillDescriptionTaki: '血瓶回复量提升${core.getSkillValues().subaru}%（\\i[T350]：${core.getSkillValues(true).subaru}%）',
			fee: fees.subaru,
			colour: '#76bd53',
		},
		tomo: {
			name: '海老冢智',
			skillName: '严厉冰晶',
			skillDescription: '战斗前，冻住怪物一轮，该轮内怪物无法反击但是防御力加倍\\n开局直接获得\\i[snow]',
			fee: fees.tomo,
			colour: '#e34d8d',
		},
		rupa: {
			name: 'RUPA',
			skillName: '摇滚诗篇',
			skillDescription: '喝假酒了，使用酒会扣除${core.getSkillValues().rupa.def}防御并将面前的\\i[man]\\i[trader]\\i[specialDoor]直接打爆，\\i[man]给${core.getSkillValues().rupa.man}金币，\\i[trader]给其售卖物品',
			fee: fees.rupa,
			colour: '#eeda01',
			主动技能: 'rupaWine',
		},
		yui: {
			name: '平泽唯',
			skillName: '软绵时间',
			skillDescription: '额外拥有${core.getSkillValues().yui}%防御的护盾',
			skillDescriptionTaki: '额外拥有${core.getSkillValues().yui}%（\\i[T350]：${core.getSkillValues(true).yui}%）防御的护盾',
			fee: fees.yui,
			colour: '#734919',
		},
		mio: {
			name: '秋山澪',
			skillName: '秋波萌动',
			//skillDescription: '血瓶变成相应颜色的宝石，禁用商店加攻防\\n主动技，每局限${core.getSkillValues().mio}次（\\i[T350]：${core.getSkillValues(true).mio}次），可以让本层剩余宝石变成血瓶',
			skillDescription: '消耗一把\\i[blueKey]，弹贝斯震飞面前一排所有怪物，boss层禁用',
			fee: fees.mio,
			colour: '#ac5144',
			主动技能: 'mioWand',
		},
		ritsu: {
			name: '田井中律',
			skillName: '迷糊旋律',
			skillDescription: '主动技，消耗${core.getSkillValues().ritsu}点生命值，敲鼓震开面前的\\i[yellowDoor]\\i[blueDoor]\\i[redDoor]，下次消耗的生命值增加\\n\\i[yellowDoor]+20点  \\i[blueDoor]+60点  \\i[redDoor]+200点',
			fee: fees.ritsu,
			colour: '#734919',
			主动技能: 'ritsuMelody',
		},
		tsumugi: {
			name: '琴吹䌷',
			skillName: '琴瑟茶会',
			skillDescription: '到达新的楼层，恢复${core.getSkillValues().tsumugi}点生命',
			skillDescriptionTaki: '到达新的楼层，恢复${core.getSkillValues().tsumugi}点（\\i[T350]：${core.getSkillValues(true).tsumugi}点）生命',
			fee: fees.tsumugi,
			colour: '#e6c59a',
		},
		azusa: {
			name: '中野梓',
			skillName: '相遇天使',
			skillDescription: '拥有${core.getSkillValues().azusa}%伤害减免',
			skillDescriptionTaki: '拥有${core.getSkillValues().azusa}%（\\i[T350]：${core.getSkillValues(true).azusa}%）伤害减免',
			fee: fees.azusa,
			colour: '#633fe3',
		},
		mana: {
			name: '纯田真奈',
			skillName: '局外之人',
			skillDescription: '没有技能，进入游戏后返还全部费用',
			fee: fees.mana,
			colour: '#6c5e53',
		}
	};

	//所有角色列表
	const memberList = Object.entries(members).map(([key, value]) => ({ id: key, ...value }));

	//获得一个角色或角色列表
	this.getMember = function (id) {
		if (id) return members[id];
		else return memberList;
	}

	//技能查看器
	this.useSkillViewer = function () {
		let text = [];
		flags.members.forEach(id => {
			text.push(members[id].name + '\\n' + members[id].skillName + '\\n' + members[id].skillDescription);
		});
		if (text.length > 0)
			core.insertAction(text);
	}

	//选角色的录像处理
	const firstMemberChoice = allIds.map(
		id => ({
			text: id,
			action: [
				{ type: "setValue", name: "flag:replayFee", operator: "+=", value: fees[id] },
				{ type: "setValue", name: "flag:replayChosen", value: "[...flag:replayChosen,'" + id + "']" },
				{ type: "setValue", name: "status:name", value: "'" + members[id].name + "'" },
				{ type: "setHeroIcon", name: id + ".png" },
				{ type: "setValue", name: "flag:followers", value: "[]" },
				{ type: "setValue", name: "flag:following", value: "true" },
			]
		})
	);

	const otherMemberChoice = allIds.map(
		id => ({
			text: id,
			action: [
				{ type: "setValue", name: "flag:replayFee", operator: "+=", value: fees[id] },
				{ type: "setValue", name: "flag:replayChosen", value: "[...flag:replayChosen,'" + id + "']" },
				{ type: "follow", name: id + ".png" },
				{ type: "setValue", name: "flag:followers", value: "[...flag:followers,'" + id + ".png']" },
			],
			need: "!flag:replayChosen.includes('" + id + "')"
		})
	);

	const actions = [{
			"type": "choices",
			"text": "选择角色一",
			"choices": firstMemberChoice,
		},
		{
			"type": "choices",
			"text": "选择角色二",
			"choices": [...otherMemberChoice,
				{
					"text": "没有",
					"action": [

					]
				},
			]
		},
		{
			"type": "choices",
			"text": "选择角色三",
			"choices": [...otherMemberChoice,
				{
					"text": "没有",
					"action": [

					]
				},
			]
		},
		{
			"type": "choices",
			"text": "选择角色四",
			"choices": [...otherMemberChoice,
				{
					"text": "没有",
					"action": [

					]
				},
			]
		},
		{
			"type": "choices",
			"text": "选择角色五",
			"choices": [...otherMemberChoice,
				{
					"text": "没有",
					"action": [

					]
				},
			]
		},
		{
			"type": "if",
			"condition": "(flag:replayFee<=" + totalFee + ")",
			"true": [
				{ type: "setValue", name: "flag:members", value: "[...flag:replayChosen]" },
				{ type: "setValue", name: "flag:leftFee", value: totalFee + "-flag:replayFee" },
				{ type: "setValue", name: "flag:replayChosen", value: null },
				{ type: "setValue", name: "flag:replayFee", value: null }, { "type": "function", "function": "function(){\ncore.afterChoosingMembers();\n}" },
			],
			"false": [
				{ "type": "lose", "reason": "录像错误" },
			]
		},
	];

	this.getChoosingMemberActions = () => actions;

	this.myGetDamageInfo = function (enemy, hero, x, y, floorId) {
		// 获得战斗伤害信息（实际伤害计算函数）
		// 
		// 参数说明：
		// enemy：该怪物信息
		// hero：勇士的当前数据；如果对应项不存在则会从core.status.hero中取。
		// x,y：该怪物的坐标（查看手册和强制战斗时为undefined）
		// floorId：该怪物所在的楼层
		// 后面三个参数主要是可以在光环等效果上可以适用
		floorId = floorId || core.status.floorId;

		let hero_hp = core.getRealStatusOrDefault(hero, 'hp'),
			hero_atk = core.getRealStatusOrDefault(hero, 'atk'),
			hero_def = core.getRealStatusOrDefault(hero, 'def'),
			origin_hero_hp = core.getStatusOrDefault(hero, 'hp'),
			origin_hero_atk = core.getStatusOrDefault(hero, 'atk'),
			origin_hero_def = core.getStatusOrDefault(hero, 'def');

		// 勇士的负属性都按0计算
		hero_hp = Math.max(0, hero_hp);
		hero_atk = Math.max(0, hero_atk);
		hero_def = Math.max(0, hero_def);

		// 十字架
		if (core.hasItem('cross') && ['zombie', 'zombieKnight', 'vampire'].includes(enemy.id))
			hero_atk *= 2;
		// 屠龙匕
		if (core.hasItem('knife') && enemy.id === 'dragon')
			hero_atk *= 2;

		// 勇士固伤，井芹仁菜
		//let hero_init_damage = flags.nina ? hero_atk * core.getSkillValues().nina / 100 : 0;
		let hero_init_damage = 0;

		// 爱音
		/*
		if (flags.anon) {
			let anonatk = Math.round(hero_atk * 0.05);
			hero_atk -= anonatk;
			hero_def += anonatk * core.getSkillValues().anon;
		}
		*/

		// 勇士的魔防，平泽唯额外获得魔防
		let hero_mdef = flags.yui ? hero_def * core.getSkillValues().yui / 100 : 0;
		// 若叶睦额外获得魔防
		if (flags.mutsumi)
			hero_mdef += core.getFlag('mutsumiMdef', 0);

		// 怪物的各项数据
		// 对坚固模仿等处理扔到了脚本编辑-getEnemyInfo之中
		const enemyInfo = core.enemys.getEnemyInfo(enemy, hero, x, y, floorId);
		const mon_hp = enemyInfo.hp,
			mon_atk = enemyInfo.atk,
			mon_def = enemyInfo.def,
			mon_special = enemyInfo.special;

		// 高松灯秒杀石头人
		if (flags.tomori && enemy.id === 'rock') {
			return {
				"mon_hp": Math.floor(mon_hp),
				"mon_atk": Math.floor(mon_atk),
				"mon_def": Math.floor(mon_def),
				"init_damage": 0,
				"per_damage": 0,
				"hero_per_damage": 0,
				"turn": 0,
				"damage": 0
			};
		}

		// 河原木桃香秒杀低区域怪物
		if (flags.momoka && flags.momokaNoHit.includes(enemy.id)) {
			return {
				"mon_hp": Math.floor(mon_hp),
				"mon_atk": Math.floor(mon_atk),
				"mon_def": Math.floor(mon_def),
				"init_damage": 0,
				"per_damage": 0,
				"hero_per_damage": 0,
				"turn": 0,
				"damage": 0
			};
		}

		// 处理勇士固伤，如果怪物生命减少到0则不再进行后续计算，固伤先于怪物先攻
		let mon_hp0 = mon_hp - hero_init_damage;
		if (mon_hp0 <= 0) {
			return {
				"mon_hp": Math.floor(mon_hp),
				"mon_atk": Math.floor(mon_atk),
				"mon_def": Math.floor(mon_def),
				"init_damage": 0,
				"per_damage": 0,
				"hero_per_damage": 0,
				"turn": 0,
				"damage": 0
			};
		}

		// 净化
		if (core.hasSpecial(mon_special, 1005))
			hero_mdef = 0;

		// 破甲
		if (core.hasSpecial(mon_special, 1006))
			hero_def /= 2;

		// 战前造成的额外伤害（可被护盾抵消）
		let init_damage = 0;

		// 每回合怪物对勇士造成的战斗伤害
		let per_damage = mon_atk - hero_def;

		// 先攻，战斗前以1.5倍攻击力攻击一次
		if (core.hasSpecial(mon_special, 1002)) {
			init_damage += mon_atk * 1.5 - hero_def;
			if (init_damage < 0) init_damage = 0;
		}

		// 战斗伤害不能为负值
		if (per_damage < 0) per_damage = 0;

		// 连击
		if (core.hasSpecial(mon_special, 1003)) per_damage *= 2;

		// 中毒，每回合怪物额外对勇士造成伤害
		if (core.hasSpecial(mon_special, 1004)) per_damage += enemy.value;

		// 勇士每回合对怪物造成的伤害
		let hero_per_damage = Math.max(hero_atk - mon_def, 0);
		if (flags.mutsumi) hero_per_damage = Math.max(2 * (this.getSkillValues().mutsumi / 100 * hero_atk - mon_def), 0);

		// 勇士增伤
		let damageIncrease = 0;

		// 后藤一里
		if (flags.bocchi)
			damageIncrease += core.getSkillValues().bocchi[flags.members.length - 1];

		// 椎名立希对守卫系造成额外伤害
		if (flags.taki && guardEnemys.includes(enemy.id))
			damageIncrease += core.getSkillValues().taki;

		hero_per_damage *= 1 + damageIncrease / 100;

		// 海老冢智
		let mon_hp1 = mon_hp0;
		if (flags.tomo) {
			let tomo_per_damage = Math.max(hero_atk - 2 * mon_def, 0);
			// 睦连击
			if (flags.mutsumi) tomo_per_damage = Math.max(2 * (this.getSkillValues().mutsumi / 100 * hero_atk - 2 * mon_def), 0);
			// 增伤
			tomo_per_damage *= 1 + damageIncrease / 100;
			if (tomo_per_damage <= 0) //return null;
				tomo_per_damage = 0; //冰冻状态下打不过就不打
			// 吧唧
			//if (core.hasSpecial(mon_special, 2003) && tomo_per_damage > mon_hp * 0.2)
			//tomo_per_damage = (tomo_per_damage - mon_hp * 0.2) / 5 + mon_hp * 0.2;
			// 耳机
			//if (core.hasSpecial(mon_special, 2004) && tomo_per_damage < mon_hp * 0.2)
			//return null;
			//tomo_per_damage = 0;
			//mon_hp1 -= 3 * tomo_per_damage;
			mon_hp1 -= tomo_per_damage;
			if (mon_hp1 <= 0) {
				return {
					"mon_hp": Math.floor(mon_hp),
					"mon_atk": Math.floor(mon_atk),
					"mon_def": Math.floor(mon_def),
					"init_damage": 0,
					"per_damage": 0,
					"hero_per_damage": 0,
					"turn": 0,
					"damage": 0
				};
			}
		}

		// 吧唧
		//if (core.hasSpecial(mon_special, 2003) && hero_per_damage > mon_hp * 0.2)
		//hero_per_damage = (hero_per_damage - mon_hp * 0.2) / 5 + mon_hp * 0.2;
		// 耳机
		//if (core.hasSpecial(mon_special, 2004) && hero_per_damage < mon_hp * 0.2)
		//return null;

		// 如果没有破防，则不可战斗
		if (hero_per_damage <= 0) return null;

		// 勇士的攻击回合数；为怪物生命除以每回合伤害向上取整
		let turn = Math.ceil(mon_hp1 / hero_per_damage);

		// 最终伤害：初始伤害 + 怪物对勇士造成的伤害
		let damage = init_damage + (turn - 1) * per_damage;

		// 减伤
		let damageReduction = 0;

		// 若叶睦获得的减伤
		if (flags.mutsumi)
			damageReduction += core.getFlag('mutsumiDamageReduction', 0);

		// 中野梓
		if (flags.azusa)
			damageReduction += core.getSkillValues().azusa;

		// 若麦，非boss额外受到伤害
		if (flags.nyamu && !(bossEnemys.includes(enemy.id)))
			damageReduction -= core.getSkillValues().nyamu;

		// 河原木桃香受到boss系怪物的伤害
		//if (flags.momoka && bossEnemys.includes(enemy.id))
		//damageReduction -= core.getSkillValues().momoka;

		// 应援效果，和减伤一起处理
		let call = (core.hasSpecial(mon_special, 2005)) ? (5 * this.getCall(enemy.colour, floorId)) : 0;
		damageReduction -= call;

		// 减伤效果，加算
		damage *= 1 - damageReduction / 100;

		// 再扣去护盾
		damage -= hero_mdef;

		// 扣去海铃导致的减伤
		if (flags.umiri && bossEnemys.includes(enemy.id))
			damage -= Math.floor(flags.umiriRegion[core.getRegion()] / 10 + 0.01);

		// 禁止负伤
		if (damage < 0) damage = 0;

		// 怪物固伤
		if (core.hasSpecial(mon_special, 1007) && damage > 0) damage += enemy.damage;

		return {
			"mon_hp": Math.floor(mon_hp),
			"mon_atk": Math.floor(mon_atk),
			"mon_def": Math.floor(mon_def),
			"init_damage": Math.floor(init_damage),
			"per_damage": Math.floor(per_damage),
			"hero_per_damage": Math.floor(hero_per_damage),
			"turn": Math.floor(turn),
			"damage": Math.floor(damage)
		};
	}

	function shuffleArray(array, rand) {
		array = [...array];
		const r = rand ? (core.rand) : ((x) => (Math.floor(Math.random() * (x))));
		for (let i = array.length - 1; i > 0; i--) {
			// 生成 0 到 i 之间的随机索引
			let j = r(i + 1);

			// 交换元素
			[array[i], array[j]] = [array[j], array[i]]
		}
		return array;
	}

	//bgm
	const bgmList = shuffleArray([
		"btr1.mp3",
		"btr2.mp3",
		"btr3.mp3",
		"btr4.mp3",
		"gbc1.mp3",
		"gbc2.mp3",
		"gbc3.mp3",
		"gbc4.mp3",
		"kon1.mp3",
		"kon2.mp3",
		"kon3.mp3",
		"kon4.mp3",
		"mujica1.mp3",
		"mujica2.mp3",
		"mujica3.mp3",
		"mujica4.mp3",
		"mygo1.mp3",
		"mygo2.mp3",
		"mygo3.mp3",
		"mygo4.mp3"
	]);
	this.getBgmList = function () { return bgmList; }

	this.playNextBgm = function () {
		core.musicStatus.currentBgmIndex = (core.musicStatus.currentBgmIndex + 1) % this.getBgmList().length;
		var nextBgm = this.getBgmList()[core.musicStatus.currentBgmIndex];
		core.control.playBgm(nextBgm);
	}

	//和睦相处
	//游戏开始时，随机打乱若叶睦事件表
	let mutsumiArrive = [ //角色，事件，值范围，文字描述
		{
			character: 'sakiko',
			event: '-money',
			range: [1, 50],
			text: '扮演丰川祥子，遭遇破产，损失${flags.mutsumiEvent[0]}金币',
		},
		{
			character: 'sakiko',
			event: '-atk',
			range: [1, 3],
			text: '扮演丰川祥子，结束乐队，损失${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'tsumugi',
			event: '+money',
			range: [10, 100],
			text: '扮演琴吹䌷，成为大小姐，获得${flags.mutsumiEvent[0]}金币',
		},
		{
			character: 'tsumugi',
			event: '+hp',
			range: [100, 250],
			text: '扮演琴吹䌷，成为大小姐，增加${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'soyo',
			event: '-hp',
			range: [50, 200],
			text: '扮演长崎素世，向敌怪下跪，损失${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'soyo',
			event: '-def',
			range: [3, 7],
			text: '扮演长崎素世，大喊一声“为什么要演奏春日影？！”，损失${flags.mutsumiEvent[0]}点防御',
		},
		{
			character: 'azusa',
			event: '+damageReduction',
			value: 3,
			text: '扮演中野梓，喵喵叫后永久获得3%减伤',
		},
		{
			character: 'azusa',
			event: '-hp',
			range: [10, 100],
			text: '扮演中野梓，被晒黑了，损失${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'mio',
			event: '+atk',
			range: [1, 5],
			text: '扮演秋山澪，拥有粉丝团，增加${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'mio',
			event: '+def',
			range: [1, 5],
			text: '扮演秋山澪，拥有粉丝团，增加${flags.mutsumiEvent[0]}点防御',
		},
		{
			character: 'yui',
			event: '+mdef',
			range: [5, 15],
			text: '扮演平泽唯，吃了滑滑蛋，永久获得${flags.mutsumiEvent[0]}点护盾',
		},
		{
			character: 'yui',
			event: '+mdef',
			range: [5, 15],
			text: '扮演平泽唯，加入了演奏轻便的音乐的学生社团，永久获得${flags.mutsumiEvent[0]}点护盾',
		},
		{
			character: 'ritsu',
			event: 'openDoor',
			text: '扮演田井中律，敲鼓震开了本层随机两扇门',
		},
		{
			character: 'ritsu',
			event: '-hp',
			range: [10, 100],
			text: '扮演田井中律，忘记交表，损失${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'mutsumi',
			event: '-damageReduction',
			value: 1,
			text: '我从来没有觉得玩魔塔开心过，永久额外受到1%伤害',
		},
		{
			character: 'mutsumi',
			event: 'buhui',
			text: '全都不会拆！',
		},
		{
			character: 'tomori',
			event: '+def',
			range: [1, 5],
			text: '扮演高松灯，不知怎地，发出了咕咕嘎嘎的怪叫，变出了一包企鹅创可贴，永久增加${flags.mutsumiEvent[0]}点防御',
		},
		{
			character: 'tomori',
			event: 'earthquake',
			text: '扮演高松灯，直接搬走本层全部石头',
		},
		{
			character: 'anon',
			event: '+def',
			range: [7, 15],
			text: '扮演千早爱音，永久增加${flags.mutsumiEvent[0]}点防御',
		},
		{
			character: 'anon',
			event: '+hp',
			range: [1000, 2000],
			text: '扮演千早爱音，成为圣人，增加${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'taki',
			event: '+atk',
			range: [1, 5],
			text: '扮演椎名立希，被“主唱太拼命了”激怒，增加${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'taki',
			event: '-def',
			range: [1, 5],
			text: '扮演椎名立希，疑问什么时候开始称呼ano酱，损失${flags.mutsumiEvent[0]}点防御',
		},
		{
			character: 'umiri',
			event: '-atk',
			range: [1, 3],
			text: '扮演八幡海铃，不值得信任，损失${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'umiri',
			event: '-def',
			range: [1, 5],
			text: '扮演八幡海铃，向丰川祥子哭泣，损失${flags.mutsumiEvent[0]}点防御',
		},
		{
			character: 'rana',
			event: '+hp',
			range: [100, 250],
			text: '扮演要乐奈，吃抹茶芭菲，增加${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'rana',
			event: '+key',
			colour: ['黄', '蓝'],
			text: '扮演要乐奈，拥有异瞳，获得一把${flags.mutsumiEvent[0]}钥匙',
		},
		{
			character: 'nyamu',
			event: '+atk',
			range: [1, 5],
			text: '扮演祐天寺若麦，开启哈气模式，增加${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'nyamu',
			event: '+hp',
			range: [100, 250],
			text: '扮演祐天寺若麦，当美妆博主，增加${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'mana',
			event: 'none',
			text: '扮演纯田真奈，吃甜甜圈，无事发生',
		},
		{
			character: 'mana',
			event: 'none',
			text: '扮演纯田真奈，吃甜甜圈，无事发生',
		},
		{
			character: 'uika',
			event: 'none',
			text: '扮演三角初音，扮演三角初音扮演三角初华，把自己演懵了，无事发生',
		},
		{
			character: 'uika',
			event: '-money',
			value: 100,
			text: '扮演三角初音，发现自己成为丰川祥子的长辈，给丰川祥子发压岁钱，损失100金币',
		},
		{
			character: 'nina',
			event: 'bomb',
			text: '扮演井芹仁菜，竟敢舞室灯，吓跑了三只敌怪',
		},
		{
			character: 'nina',
			event: '+atk',
			range: [1, 5],
			text: '扮演井芹仁菜，做出感谢的手势，被认为是双手匕士，增加${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'momoka',
			event: '-hp',
			range: [50, 200],
			text: '扮演河原木桃香，被蛇吓到，损失${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'momoka',
			event: '-atk',
			range: [1, 3],
			text: '扮演河原木桃香，退出了钻石星尘，损失${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'rupa',
			event: '-money',
			range: [10, 20],
			text: '扮演RUPA，酒驾罚款，损失${flags.mutsumiEvent[0]}金币',
		},
		{
			character: 'rupa',
			event: '+hp',
			range: [100, 250],
			text: '扮演RUPA，成为海王，增加${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'subaru',
			event: '+money',
			range: [10, 100],
			text: '扮演安和昴，拍广告赚钱，获得${flags.mutsumiEvent[0]}金币',
		},
		{
			character: 'subaru',
			event: '+def',
			range: [1, 5],
			text: '扮演安和昴，喜欢打游戏，尤其是喜欢玩魔塔加防，增加${flags.mutsumiEvent[0]}点防御',
		},
		{
			character: 'tomo',
			event: '+money',
			range: [10, 100],
			text: '扮演海老冢智，在吉野家打工，获得${flags.mutsumiEvent[0]}金币',
		},
		{
			character: 'tomo',
			event: '+atk',
			range: [1, 5],
			text: '扮演海老冢智，养了蛇，增加${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'nijika',
			event: '+damageReduction',
			text: '扮演伊地知虹夏，善解人意，永久获得3%减伤',
		},
		{
			character: 'nijika',
			event: '+all',
			text: '扮演伊地知虹夏，成为大天使，增加500点生命，5点攻击，5点防御',
		},
		{
			character: 'ikuyo',
			event: 'upFly',
			text: '扮演喜多郁代，直接传送到楼上的下楼梯位置',
		},
		{
			character: 'ikuyo',
			event: 'resetShop',
			text: '扮演喜多郁代，散发出了光芒，重置加点价格',
		},
		{
			character: 'ryou',
			event: '+hp',
			range: [1, 9],
			text: '扮演山田凉，吃草增加${flags.mutsumiEvent[0]}点生命',
		},
		{
			character: 'ryou',
			event: '+moneyS',
			text: '扮演山田凉，为队友上香，获得队伍人数*20金币',
		},
		{
			character: 'bocchi',
			event: '-atk',
			range: [1, 3],
			text: '扮演后藤一里，化了，损失${flags.mutsumiEvent[0]}点攻击',
		},
		{
			character: 'bocchi',
			event: '+atk',
			range: [1, 5],
			text: '扮演后藤一里，成为吉他英雄，增加${flags.mutsumiEvent[0]}点攻击',
		}
	];

	//this.getMutsumiArrive = function () { return mutsumiArrive; }

	const mutsumiFloor = {
		MT1: 0,
		MT2: 1,
		MT3: 2,
		MT4: 3,
		MT5: 4,
		MT6: 5,
		MT7: 6,
		MT8: 7,
		MT9: 8,
		MT11: 9,
		MT12: 10,
		MT13: 11,
		MT14: 12,
		MT15: 13,
		MT16: 14,
		MT17: 15,
		MT18: 16,
		MT19: 17,
		MT21: 18,
		MT22: 19,
		MT23: 20,
		MT24: 21,
		MT25: 22,
		MT26: 23,
		MT27: 24,
		MT28: 25,
		MT29: 26,
		MT31: 27,
		MT32: 28,
		MT33: 29,
		MT34: 30,
		MT35: 31,
		MT36: 32,
		MT37: 33,
		MT38: 34,
		MT39: 35,
		MT41: 36,
		MT42: 37,
		MT43: 38,
		MT44: 39,
		MT45: 40,
		MT46: 41,
		MT47: 42,
		MT48: 43,
		MT49: 44
	}
	const mutsumiFloor2 = {};
	for (let key in mutsumiFloor) {
		mutsumiFloor2[mutsumiFloor[key]] = key;
	}

	//在这里根据关键词生成事件，进入游戏时便生成好，包括填写随机数值
	this.mutsumiArriveEvents = function () {
		let mutsumiArrive2 = shuffleArray(mutsumiArrive, true);
		flags.mutsumiArrive = mutsumiArrive2.map(
			(a, i) => {
				a = { ...a };
				//将确定的值放到对象的value属性里
				if (a.range) {
					a.value = core.rand(a.range[1] - a.range[0] + 1) + a.range[0];
				} else if (a.colour) {
					a.value = a.colour[core.rand(a.colour.length)];
				}
				//用value替换掉文本里的一串东西
				if (a.value || a.value === 0) {
					a.text = a.text.replace('${flags.mutsumiEvent[0]}', a.value);
				}
				//制作事件
				const actions = [];
				actions.push({ "type": "strokeRect", "x": "6*32", "y": 32, "width": 32, "height": 32, "style": [77, 99, 77, 1], "lineWidth": 3 }, { "type": "strokeRect", "x": "6*32-3", "y": "32-3", "width": 38, "height": 38, "style": [255, 255, 255, 1], "lineWidth": 3 }, { "type": "fillRect", "x": "6*32", "y": 32, "width": 32, "height": 32, "style": [55, 55, 55, 1] }, { "type": "drawImage", "image": a.character + ".png", "x": 0, "y": 0, "w": 32, "h": 32, "x1": "6*32", "y1": 32, "w1": 32, "h1": 32 });
				actions.push(a.text);
				actions.push({ "type": "clearMap" });
				if (a.event === 'none') {

				} else if (a.event === 'openDoor') {
					const floorId = mutsumiFloor2[i];
					const doors = core.searchBlockWithFilter(function (block) { return block.event.id === 'yellowDoor' || block.event.id === 'blueDoor' }, floorId);
					const doorsOpen = shuffleArray(doors, true).slice(0, 2).map(x => ([x.block.x, x.block.y]));
					actions.push({ type: "openDoor", loc: doorsOpen[0], async: true }, { type: "openDoor", loc: doorsOpen[1], async: true }, { type: "waitAsync" });
				} else if (a.event === 'bomb') {
					const floorId = mutsumiFloor2[i];
					const enemys = core.searchBlockWithFilter(function (block) { return block.event.cls === 'enemys' }, floorId);
					const enemysBomb = shuffleArray(enemys, true).slice(0, 3).map(x => ([x.block.x, x.block.y]));
					const afterBattles = [];
					for (let j = 0; j < 3; j++)
						core.push(afterBattles, core.floors[floorId].afterBattle[enemysBomb[j][0] + ',' + enemysBomb[j][1]]);
					actions.push({ type: "hide", loc: enemysBomb, remove: true, time: 500 });
					actions.push(...afterBattles);
				} else if (a.event === 'shuffleEnemy') {
					const floorId = mutsumiFloor2[i];
					const enemys = core.searchBlockWithFilter(function (block) { return block.event.cls === 'enemys' }, floorId);
					const enemysLocs = enemys.map(x => ([x.block.x, x.block.y]));
					const enemysIds = shuffleArray(enemys.map(x => x.block.event.id), true);
					for (let j = 0; j < enemys.length; j++) {
						actions.push({ type: "setBlock", number: enemysIds[j], loc: enemysLocs[j] });
					}
				} else if (a.event === 'buhui') {
					actions.push({
						type: "for",
						name: "temp:A",
						from: "0",
						to: "12",
						step: "1",
						data: [{
							type: "for",
							name: "temp:B",
							from: "0",
							to: "12",
							step: "1",
							data: [{
								type: "if",
								condition: "(core.getBlockCls(temp:A,temp:B)==='enemys')",
								true: [
									{ type: "setBlock", number: "E386", loc: ["temp:A", "temp:B"] },
								]
							}, ]
						}, ]
					}, { type: "waitAsync" });
				} else if (a.event === 'randomMove') {
					const floorId = mutsumiFloor2[i];
					const locs = [];
					for (let i = 0; i < 13; i++) {
						for (let j = 0; j < 13; j++) {
							if (core.getBlockId(i, j, floorId) === null || core.getBlockCls(i, j, floorId) === 'items') {
								locs.push([i, j]);
							}
						}
					}
					const [x, y] = locs[core.rand(locs.length)];
					actions.push({ type: "jumpHero", loc: [x, y], time: 500 }, { type: "trigger", loc: ["core.nextX(0)", "core.nextY(0)"] });
				} else if (a.event === 'earthquake') {
					actions.push({
						type: "for",
						name: "temp:A",
						from: "0",
						to: "12",
						step: "1",
						data: [{
							type: "for",
							name: "temp:B",
							from: "0",
							to: "12",
							step: "1",
							data: [{
								type: "if",
								condition: "(core.getBlockId(temp:A,temp:B)==='whiteWall')",
								true: [
									{ type: "hide", loc: ["temp:A", "temp:B"], remove: true, time: 500, async: true },
								]
							}, ]
						}, ]
					}, { type: "waitAsync" });
				} else if (a.event === 'resetShop') {
					actions.push({ type: "setValue", name: "flag:shopTimes", value: 0 });
				} else if (a.event === 'upFly') {
					actions.push({ type: "changeFloor", floorId: ":next", stair: "downFloor" }, );
				} else if (a.event === '+damageReduction') {
					actions.push({ type: "setValue", name: "flag:mutsumiDamageReduction", operator: "+=", value: 3 });
				} else if (a.event === '-damageReduction') {
					actions.push({ type: "setValue", name: "flag:mutsumiDamageReduction", operator: "-=", value: 1 });
				} else if (a.event === '+mdef') {
					actions.push({ type: "setValue", name: "flag:mutsumiMdef", operator: "+=", value: a.value });
				} else if (a.event === '+key') {
					let item;
					if (a.value === '黄') item = 'yellowKey';
					else if (a.value === '蓝') item = 'blueKey';
					else if (a.value === '红') item = 'redKey';
					actions.push({ type: "setValue", name: "item:" + item, operator: "+=", value: 1 });
				} else if (a.event === '+all') {
					actions.push({ type: "setValue", name: "status:hp", operator: "+=", value: 500 }, { type: "setValue", name: "status:atk", operator: "+=", value: 5 }, { type: "setValue", name: "status:def", operator: "+=", value: 5 });
				} else if (a.event === '+moneyS') {
					actions.push({ type: "setValue", name: "status:money", operator: "+=", value: 20 * flags.members.length });
				} else { //其他事件，全是+atk之流
					const op = a.event[0];
					const n = a.event.slice(1);
					actions.push({ type: "setValue", name: "status:" + n, operator: op + '=', value: a.value });
				}
				a.actions = actions;
				return a;
			}
		);
	}

	//返回对应事件
	this.mutsumiArrive = function (floorId) {
		return flags.mutsumiArrive[mutsumiFloor[floorId]].actions;
	}









},
    "函数复写": function () {
	// 在此增加新插件
	////// 推箱子 //////
	events.prototype.pushBox = function (data) {
		if (!flags.tomori || core.isBossFloor()) return;

		core.autosave(true);

		const id = data.event.id;
		const isWhiteWall = id === 'whiteWall';
		if (!isWhiteWall && id != 'box' && id != 'boxed') return;

		// 判断还能否前进，看看是否存在事件
		var direction = core.getHeroLoc('direction'),
			nx = data.x + core.utils.scan[direction].x,
			ny = data.y + core.utils.scan[direction].y;

		// 检测能否推上去
		if (!core.canMoveHero()) return;
		var canGoDeadZone = core.flags.canGoDeadZone;
		core.flags.canGoDeadZone = true;
		if (!core.canMoveHero(data.x, data.y, direction)) {
			core.flags.canGoDeadZone = canGoDeadZone;
			return;
		}
		core.flags.canGoDeadZone = canGoDeadZone;

		var nextId = core.getBlockId(nx, ny);
		if (nextId != null && nextId != 'flower') return;

		if (isWhiteWall) {
			core.setBlock(id, nx, ny);
			core.removeBlock(data.x, data.y);
		} else {
			core.setBlock(nextId == null ? 'box' : 'boxed', nx, ny);
			if (id == 'box')
				core.removeBlock(data.x, data.y);
			else
				core.setBlock('flower', data.x, data.y);
		}
		// 勇士前进一格，然后触发推箱子后事件
		core.insertAction([
			//{ "type": "moveAction" }, //取消前进一格
			{ "type": "function", "function": "function() { core.afterPushBox(); }" }
		]);
	}

	/// 未破防临界采用二分计算
	enemys.prototype._nextCriticals_overAtk = function (enemy, x, y, floorId) {
		var calNext = function (currAtk, maxAtk) {
			var start = currAtk,
				end = maxAtk;
			if (start > end) return null;

			while (start < end) {
				var mid = Math.floor((start + end) / 2);
				if (mid - start > end - mid) mid--;
				var nextInfo = core.enemys.getDamageInfo(enemy, { "atk": mid }, x, y, floorId);
				if (nextInfo != null) end = mid;
				else start = mid + 1;
			}
			var nextInfo = core.enemys.getDamageInfo(enemy, { "atk": start }, x, y, floorId);
			return nextInfo == null ? null : [start - core.status.hero.atk, nextInfo];
		}
		return calNext(core.status.hero.atk + 1,
			1000 * core.getEnemyValue(enemy, 'hp', x, y, floorId) + 1000 * core.getEnemyValue(enemy, 'def', x, y, floorId));
	}

	////// 楼层切换 //////
	events.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback) {
		time = 0;
		var info = this._changeFloor_getInfo(floorId, stair, heroLoc, time);
		if (info == null) {
			if (callback) callback();
			return;
		}
		floorId = info.floorId;
		info.locked = core.status.lockControl;

		core.dom.floorNameLabel.innerText = core.status.maps[floorId].title;
		core.lockControl();
		core.stopAutomaticRoute();
		core.clearContinueAutomaticRoute();
		core.status.replay.animate = true;
		clearInterval(core.interval.onDownInterval);
		core.interval.onDownInterval = 'tmp';

		this._changeFloor_beforeChange(info, callback);
	}

	maps.prototype._canMoveDirectly_checkStartPoint = function (sx, sy) {
		if (core.status.checkBlock.damage[sx + "," + sy]) return false;
		var block = core.getBlock(sx, sy);
		if (block != null) {
			// 只有起点是传送点才是能无视
			return block.event.trigger == 'changeFloor' || block.event.id === 'cutline';
		}
		return true;
	}

	////// 使用道具 //////
	items.prototype.useItem = function (itemId, noRoute, callback) {
		if (!this.canUseItem(itemId)) {
			if (callback) callback();
			return;
		}

		//主动技能自动存档
		if (itemId === 'soyoCut' || itemId === 'uikaWall' || itemId === 'kitaIkuyo' || itemId === 'mutsumiMortis' || itemId === 'ritsuMelody' || itemId === 'mioWand' || itemId === 'nijikaWand' || itemId === 'rupaWine')
			core.autosave();

		// 执行道具效果
		this._useItemEffect(itemId);
		// 执行完毕
		this._afterUseItem(itemId);
		// 记录路线
		if (!noRoute) core.status.route.push("item:" + itemId);
		if (callback) callback();
	}

	////// 当前位置是否在楼梯边 //////
	maps.prototype.nearStair = function () {
		//var x = core.getHeroLoc('x'),
		//	y = core.getHeroLoc('y');
		//return this.stairExists(x, y) || this.stairExists(x - 1, y) || this.stairExists(x, y - 1) || this.stairExists(x + 1, y) || this.stairExists(x, y + 1);
		const { x, y } = core.getHeroLoc();
		const stairid = ['upFloor', 'downFloor']; //自行添加视为楼梯的id
		if (stairid.includes(core.getBlockId(x, y))) return true;

		const stairs = stairid.reduce(
			(stairs, stair) => {
				core.searchBlock(stair).forEach(({ x, y }) => { stairs.push([x, y]) });
				return stairs;
			},
			[]
		);
		return stairs.some(
			([x, y]) => {
				const scan = core.utils.scan;
				const canMoveHero = (nx, ny, dir) => {
					if (nx < 0 || ny < 0 || nx > 12 || ny > 12) return false;
					return core.canMoveHero(nx, ny, dir);
				}
				const canMoveDirectly = (nx, ny) => {
					if (nx < 0 || ny < 0 || nx > 12 || ny > 12) return false;
					return core.canMoveDirectly(nx, ny);
				}
				const reverse = { up: 'down', down: 'up', left: 'right', right: 'left' };
				for (const dir in scan) {
					const nx = x + scan[dir].x,
						ny = y + scan[dir].y;
					if (canMoveHero(nx, ny, reverse[dir]) && canMoveDirectly(nx, ny) >= 0) return true;
				}
			}
		);
	}

	////// 点击楼层传送器时的打开操作 //////
	events.prototype.useFly = function (fromUserAction) {
		if (core.isReplaying()) return;
		// 从“浏览地图”页面：尝试直接传送到该层
		if (core.status.event.id == 'viewMaps') {
			if (!core.hasItem('fly')) {
				core.playSound('操作失败');
				core.drawTip('你没有' + core.material.items['fly'].name, 'fly');
			} else if (!core.canUseItem('fly')) {
				core.playSound('操作失败');
				core.drawTip('无法传送到当前层', 'fly');
			} else {
				core.flyTo(core.status.event.data.floorId);
			}
			return;
		}

		if (!this._checkStatus('fly', fromUserAction, true)) return;
		if (core.flags.flyNearStair && !core.nearStair()) {
			core.playSound('操作失败');
			//core.drawTip("只有在楼梯边才能使用" + core.material.items['fly'].name, 'fly');
			core.drawTip("只有能走到楼梯边时才能使用" + core.material.items['fly'].name, 'fly');
			core.unlockControl();
			core.status.event.data = null;
			core.status.event.id = null;
			return;
		}
		if (!core.canUseItem('fly')) {
			core.playSound('操作失败');
			core.drawTip(core.material.items['fly'].name + "好像失效了", 'fly');
			core.unlockControl();
			core.status.event.data = null;
			core.status.event.id = null;
			return;
		}
		core.playSound('打开界面');
		core.useItem('fly', true);
		return;
	}

	loader.prototype.loadOneMusic = function (name) {
		var music = new Audio();
		music.preload = 'none';
		if (main.bgmRemote) music.src = main.bgmRemoteRoot + core.firstData.name + '/' + name;
		else music.src = 'project/bgms/' + name;

		// 获取bgmList，表示楼层中播放的歌曲
		const bgmList = core.getBgmList();
		// 如果bgm在bgmList中，则不循环播放，并且指定下一首bgm
		if (bgmList.includes(name)) {
			// 列表循环或随机模式：不使用loop，添加ended事件
			music.loop = false;

			// 移除之前的事件监听（避免重复）
			if (core.musicStatus.bgmEndedHandler) {
				music.removeEventListener('ended', core.musicStatus.bgmEndedHandler);
			}

			// 创建新的事件处理函数
			core.musicStatus.bgmEndedHandler = function () {
				core.playNextBgm();
			};

			music.addEventListener('ended', core.musicStatus.bgmEndedHandler);
		} else {
			// 单曲循环模式：使用loop属性
			music.loop = 'loop';
		}

		core.material.bgms[name] = music;
	}

	////// 播放背景音乐 //////
	control.prototype.playBgm = function (bgm, startTime) {
		bgm = core.getMappedName(bgm);
		if (main.mode != 'play' || !core.material.bgms[bgm]) return;

		// 如果当前正在播放的BGM和请求的不同，更新索引
		const bgmList = core.getBgmList();
		if (core.musicStatus.playingBgm != bgm) {
			const index = bgmList.indexOf(bgm);
			if (index !== -1) {
				core.musicStatus.currentBgmIndex = index;
			}
		}

		// 如果不允许播放
		if (!core.musicStatus.bgmStatus) {
			try {
				core.musicStatus.playingBgm = bgm;
				core.musicStatus.lastBgm = bgm;
				core.material.bgms[bgm].pause();
			} catch (e) {
				console.error(e);
			}
			return;
		}
		this.setMusicBtn();

		try {
			this._playBgm_play(bgm, startTime);
		} catch (e) {
			console.log("无法播放BGM " + bgm);
			console.error(e);
			core.musicStatus.playingBgm = null;
		}
	}

	/*
	utils.prototype.rand = function (num) {
		var rand = core.getFlag('__rand__');
		rand = this.__next_rand(rand);
		core.setFlag('__rand__', rand);
		var ans = rand / 2147483647;
		if (num && num > 0) {
			console.log('调用了rand(' + num + ')，结果是' + Math.floor(ans * num));
			return Math.floor(ans * num);
		}
		return ans;
	}
	*/

},
    "宝石血瓶显示": function () {
	// 在此增加新插件
	/* 宝石血瓶左下角显示数值
	 * 注意！！！不要在道具属性中直接操作flags，使用core.status.hero.flags或core.setFlag系列函数代替！
	 * 需要将 变量：itemDetail改为true才可正常运行
	 * 请尽量减少勇士的属性数量，否则可能会出现严重卡顿（划掉，现在你放一万个属性也不会卡）
	 * 注意：这里的属性必须是core.status.hero里面的，flag无法显示
	 * 如果不想显示，可以core.setFlag("itemDetail", false);
	 * 然后再core.getItemDetail();
	 * 如有bug在大群或造塔群@古祠
	 */

	// 忽略的道具
	const ignore = ['superPotion'];

	// 取消注释下面这句可以减少超大地图的判定。
	// 如果地图宝石过多，可能会略有卡顿，可以尝试取消注释下面这句话来解决。
	// core.bigmap.threshold = 256;
	const origin = core.control.updateStatusBar;
	core.updateStatusBar = core.control.updateStatusBar = function () {
		if (core.getFlag('__statistics__')) return;
		else return origin.apply(core.control, arguments);
	}

	core.control.updateDamage = function (floorId, ctx) {
		floorId = floorId || core.status.floorId;
		if (!floorId || core.status.gameOver || main.mode != 'play') return;
		const onMap = ctx == null;

		// 没有怪物手册
		if (!core.hasItem('book')) return;
		core.status.damage.posX = core.bigmap.posX;
		core.status.damage.posY = core.bigmap.posY;
		if (!onMap) {
			const width = core.floors[floorId].width,
				height = core.floors[floorId].height;
			// 地图过大的缩略图不绘制显伤
			if (width * height > core.bigmap.threshold) return;
		}
		this._updateDamage_damage(floorId, onMap);
		this._updateDamage_extraDamage(floorId, onMap);
		core.getItemDetail(floorId); // 宝石血瓶详细信息
		this.drawDamage(ctx);
	};
	// 获取宝石信息 并绘制
	this.getItemDetail = function (floorId) {
		if (!core.getFlag('itemDetail')) return;
		if (!core.status.thisMap) return;
		floorId = floorId ?? core.status.thisMap.floorId;
		const beforeRatio = core.status.thisMap.ratio;
		core.status.thisMap.ratio = core.status.maps[floorId].ratio;
		let diff = {};
		const before = core.status.hero;
		const hero = core.clone(core.status.hero);
		const handler = {
			set(target, key, v) {
				diff[key] = v - (target[key] || 0);
				if (!diff[key]) diff[key] = void 0;
				return true;
			}
		};
		core.status.hero = new Proxy(hero, handler);
		core.status.maps[floorId].blocks.forEach(function (block) {
			if (
				block.event.cls !== 'items' ||
				ignore.includes(block.event.id) ||
				block.disable
			)
				return;
			const x = block.x,
				y = block.y;
			// v2优化，只绘制范围内的部分
			if (core.bigmap.v2) {
				if (
					x < core.bigmap.posX - core.bigmap.extend ||
					x > core.bigmap.posX + core._WIDTH_ + core.bigmap.extend ||
					y < core.bigmap.posY - core.bigmap.extend ||
					y > core.bigmap.posY + core._HEIGHT_ + core.bigmap.extend
				) {
					return;
				}
			}
			diff = {};
			const id = block.event.id;
			const item = core.material.items[id];
			if (item.cls === 'equips') {
				// 装备也显示
				const diff = item.equip.value ?? {};
				const per = item.equip.percentage ?? {};
				for (const name in per) {
					diff[name + 'per'] = per[name].toString() + '%';
				}
				drawItemDetail(diff, x, y);
				return;
			}
			// 跟数据统计原理一样 执行效果 前后比较
			core.setFlag('__statistics__', true);
			try {
				eval(item.itemEffect);
			} catch (error) {}
			drawItemDetail(diff, x, y);
		});
		core.status.thisMap.ratio = beforeRatio;
		core.status.hero = before;
		window.hero = before;
		window.flags = before.flags;
	};

	// 绘制
	function drawItemDetail(diff, x, y) {
		const px = 32 * x + 2,
			py = 32 * y + 30;
		let content = '';
		// 获得数据和颜色
		let i = 0;
		for (const name in diff) {
			if (!diff[name]) continue;
			let color = '#fff';

			if (typeof diff[name] === 'number')
				content = core.formatBigNumber(diff[name], true);
			else content = diff[name];
			switch (name) {
			case 'atk':
			case 'atkper':
				color = '#FF7A7A';
				break;
			case 'def':
			case 'defper':
				color = '#00E6F1';
				break;
			case 'mdef':
			case 'mdefper':
				color = '#6EFF83';
				break;
			case 'hp':
				color = '#A4FF00';
				break;
			case 'hpmax':
			case 'hpmaxper':
				color = '#F9FF00';
				break;
			case 'mana':
				color = '#c66';
				break;
			}
			// 绘制
			core.status.damage.data.push({
				text: content,
				px: px,
				py: py - 10 * i,
				color: color
			});
			i++;
		}
	}
},
    "自动清怪自动拾取": function () {
	// 在此增加新插件
	// 自动拾取和自动清怪二合一插件，根据插件库中“磁吸特效+自动拾取物品”和魔塔“纳可物语”中的自动清怪插件进行拼接和微调，清理重复部分
	// 两种功能分开跑bfs，性能不如插件库中的“玩家体验优化 --- 各种自动清”，但是道具后事件和战后事件都能正常触发
	// 使用hasFlag控制，两个flag分别是zdsq和autoBattle，可以在对应的控制开关处更改

	// 插件总开关，取消注释则禁用插件

	////// 每移动一格后执行的事件 //////
	control.prototype.moveOneStep = function (callback) {
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		return this.controldata.moveOneStep(callback);
	}

	control.prototype.moveDirectly = function (x, y, ignoreSteps) {
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		core.plugin.autoGetItem();
		core.plugin.autoBattle();
		return this.controldata.moveDirectly(x, y, ignoreSteps);
	}

	function bfsFlood(sx, sy, blockfn) {
		const canMoveArray = core.generateMovableArray();
		const blocksObj = core.getMapBlocksObj();
		const bgMap = core.getBgMapArray();

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

		while (queue.length > 0) {
			let now = queue.shift().split(","),
				x = ~~now[0],
				y = ~~now[1];
			for (let direction in core.utils.scan) {
				if (!core.inArray(canMoveArray[x][y], direction)) continue;
				const 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 intercept() {
		const 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 ||
			Object.keys(core.status.checkBlock.repulse).indexOf(loc_idx) != -1 ||
			Object.keys(core.status.checkBlock.ambush).indexOf(loc_idx) != -1 ||
			core.getBlockId(loc_x, loc_y) === "poisonNet" || core.getBlockId(loc_x, loc_y) === "weakNet" || core.getBlockId(loc_x, loc_y) === "curseNet" ||
			core.getBlockId(loc_x, loc_y) === "none" ||
			core.hasFlag('poison')) return true;
		if (core.getBlock(loc_x, loc_y) && (core.getBlockCls(loc_x, loc_y) !== 'items' && core.getBlockId(loc_x, loc_y) !== 'upFloor' && core.getBlockId(loc_x, loc_y) !== 'downFloor')) return true;
	}

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

		this.add = function (id, x, y, callback) {
			this.nodes.push({ id: id, x: x, y: y, callback: callback });
		}
		this.start = function () {
			if (isPlaying) return;
			isPlaying = true;
			core.registerAnimationFrame(name, true, this.update);
			this.ctx = core.createCanvas(name, 0, 0, core.__PIXELS__, core.__PIXELS__, 120);
		}
		this.remove = function () {
			core.unregisterAnimationFrame(name);
			core.deleteCanvas(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,
				cy = core.status.heroCenter.py - 16;
			var thr = 2; //缓动比例倒数 越大移动越慢
			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 () {
		var canGetItems = {};
		if (intercept() ||
			!core.status.floorId || !core.status.checkBlock.damage || core.status.event.id == 'action' || core.status.lockControl ||
			!core.hasFlag('zdsq') || core.getFlag('kitaIkuyo', false)) return; //归去来兮状态下不得自动拾取

		bfsFlood(core.getHeroLoc('x'), core.getHeroLoc('y'), function (blockMap, x, y) {
			var idx = x + ',' + y;
			if (idx in canGetItems) return false;
			var blk = blockMap[idx];
			if (blk && !blk.disable && blk.event.cls == 'items' && !core.isMapBlockDisabled(core.status.floorId, blk.x, blk.y) && blk.event.trigger == 'getItem') {
				if (!core.status.checkBlock.damage[idx] && !core.status.checkBlock.ambush[idx])
					canGetItems[idx] = { x: x, y: y, id: blk.event.id };
				return !core.status.checkBlock.damage[idx] && !core.status.checkBlock.ambush[idx];
			}
			return core.maps._canMoveDirectly_checkNextPoint(blockMap, x, y);
		});
		for (var k in canGetItems) {
			var x = canGetItems[k].x,
				y = canGetItems[k].y,
				id = canGetItems[k].id;
			core.trigger(x, y);
			animateHwnd.add(id, x * 32, y * 32);
		}
		animateHwnd.start();
	}

	control.prototype._replayAction_moveDirectly = function (action) {
		if (action.indexOf("move:") != 0) return false;
		// 忽略连续的瞬移事件；如果大地图某一边超过计算范围则不合并
		// if (!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.control.registerReplayAction("moveDirectly", core.control._replayAction_moveDirectly);

	//战斗
	core.events.battle = function (id, x, y, force, callback) {
		core.saveAndStopAutomaticRoute();
		id = id || core.getBlockId(x, y);
		if (!id) return core.clearContinueAutomaticRoute(callback);
		// 非强制战斗
		if (!core.enemys.canBattle(id, x, y) && !force && !core.status.event.id) {
			core.stopSound();
			core.playSound('操作失败');
			core.drawTip("你打不过此怪物！", id);
			return core.clearContinueAutomaticRoute(callback);
		}
		// 自动清怪时禁止自动存档
		var need_save = !core.status.event.id && !core.getFlag("disable_autosave2", 0);
		// 自动存档
		if (need_save) core.autosave(true);
		// 战前事件
		if (!this.beforeBattle(id, x, y))
			return core.clearContinueAutomaticRoute(callback);
		// 战后事件
		this.afterBattle(id, x, y);
		core.setFlag("disable_autosave", 0);
		if (callback) callback();
	}


	this.isEnemy = function (x, y) {
		return core.getBlockCls(x, y) === 'enemys' || core.getBlockCls(x, y) === 'enemy48';
	}

	// 战斗控制开关
	this.autoBattle = function () {
		if (intercept() ||
			/*core.status.floorId === 'MT2' ||
			core.hasItem('I361') || core.hasItem('I363') ||*/
			!core.hasFlag('autoBattle')) return;
		var canBattleEnemys = {};
		var index_list = []; // 保序
		var canBattleFunc = function (block, idx, x, y) {
			var blkid = block ? block.event.id : "";
			var result = block && !block.disable && core.plugin.isEnemy(x, y) && !core.isMapBlockDisabled(core.status.floorId, block.x, block.y) &&
				block.event.trigger == 'battle';
			if (!result) return false;
			var enemy = core.material.enemys[blkid];
			if (core.getDamage(blkid, x, y) == null || core.getDamage(blkid, x, y) > 0) return false;

			// 根据怪物特殊属性排除，可根据自身需求更改
			/*
			if (core.enemys.hasSpecial(blkid, 25) ||
				core.enemys.hasSpecial(blkid, 30) ||
				core.enemys.hasSpecial(blkid, 31) ||
				core.enemys.hasSpecial(blkid, 32) ||
				core.enemys.hasSpecial(blkid, 34) ||
				core.enemys.hasSpecial(blkid, 35) ||
				core.enemys.hasSpecial(blkid, 19) && !core.hasItem('cross')) return false;
			*/
			if (blkid === 'E386') return false;

			return true;
		}
		var blockfn = function (blockMap, x, y) {
			var idx = x + ',' + y;
			if (idx in canBattleEnemys) return false;
			var blk = blockMap[idx];

			if (canBattleFunc(blk, idx, x, y)) {
				canBattleEnemys[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];
			}
			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;

		// 执行自动清怪
		bfsFlood(core.getHeroLoc('x'), core.getHeroLoc('y'), blockfn);

		// 处理存档
		core.setFlag("disable_autosave2", 1);
		for (var i = 0; i < index_list.length; ++i) {
			var k = index_list[i];
			var x = canBattleEnemys[k].x,
				y = canBattleEnemys[k].y,
				id = canBattleEnemys[k].id;
			if (core.plugin.isEnemy(x, y) && core.getDamage(id, x, y) <= 0) { // double check，比如打了某只暴戾之后本来0伤的怪突然非0了
				core.battle(id, x, y);
			}
		}
		core.setFlag("disable_autosave2", 0);
	}
},
    "新建新建插件": function () {
	// 在此增加新插件

	this.nijikaGray = function (callback) {
		const duration = 1000;

		// 创建画布
		const ctx = core.createCanvas('灰色', 0, 0, 13 * 32, 13 * 32, 1000);

		const width = 13 * 32;
		const height = 13 * 32;
		const centerX = width / 2;
		const centerY = height / 2;
		const radius = Math.sqrt(centerX * centerX + centerY * centerY); // 覆盖整个画布的最大半径

		// 第一遍：顺时针画灰色圆
		let startTime = null;
		let isFirstPassCompleted = false;

		function drawFirstPass(timestamp) {
			if (!startTime) startTime = timestamp;
			const elapsed = timestamp - startTime;
			const progress = Math.min(elapsed / duration, 1);

			// 清空画布
			ctx.clearRect(0, 0, width, height);

			// 绘制背景（如果需要）
			// ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
			// ctx.fillRect(0, 0, width, height);

			// 绘制扇形：从-90度（12点方向）开始，顺时针扫过
			const startAngle = -Math.PI / 2; // 12点方向（-90度）
			const endAngle = startAngle + (Math.PI * 2 * progress); // 顺时针扫过

			ctx.beginPath();
			ctx.moveTo(centerX, centerY);
			ctx.arc(centerX, centerY, radius, startAngle, endAngle);
			ctx.closePath();

			// 使用灰色填充
			//ctx.fillStyle = 'rgba(128, 128, 128, 0.7)'; // 半透明灰色
			ctx.fillStyle = 'rgba(96, 96, 96, 0.95)';
			ctx.fill();

			// 添加描边（可选）
			ctx.strokeStyle = 'rgba(96, 96, 96, 0.95)';
			ctx.lineWidth = 2;
			ctx.stroke();

			if (progress < 1) {
				// 继续动画
				requestAnimationFrame(drawFirstPass);
			} else {
				// 第一遍完成，标记并开始执行回调
				isFirstPassCompleted = true;

				// 执行回调函数
				if (typeof callback === 'function') {
					callback();
				}

				// 重置时间，开始第二遍
				startTime = null;
				requestAnimationFrame(drawSecondPass);
			}
		}

		function drawSecondPass(timestamp) {
			if (!startTime) startTime = timestamp;
			const elapsed = timestamp - startTime;
			const progress = Math.min(elapsed / duration, 1);

			// 清空画布
			ctx.clearRect(0, 0, width, height);

			// 绘制背景（如果需要）
			// ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
			// ctx.fillRect(0, 0, width, height);

			if (progress < 1) {
				// 绘制灰色圆（开始被擦除的部分）
				const startAngle = -Math.PI / 2; // 12点方向
				const endAngle = startAngle + (Math.PI * 2); // 完整的圆

				ctx.beginPath();
				ctx.moveTo(centerX, centerY);
				ctx.arc(centerX, centerY, radius, startAngle, endAngle);
				ctx.closePath();
				ctx.fillStyle = 'rgba(96, 96, 96, 0.95)';
				ctx.fill();
				ctx.strokeStyle = 'rgba(96, 96, 96, 0.95)';
				ctx.lineWidth = 2;
				ctx.stroke();

				// 绘制一个白色（透明）的扇形来"擦除"灰色
				// 这个扇形从12点方向开始，顺时针增长
				const eraseStartAngle = -Math.PI / 2;
				const eraseEndAngle = eraseStartAngle + (Math.PI * 2 * progress);

				// 使用剪辑区域来擦除
				ctx.save();

				// 创建剪辑路径（要擦除的区域）
				ctx.beginPath();
				ctx.moveTo(centerX, centerY);
				ctx.arc(centerX, centerY, radius, eraseStartAngle, eraseEndAngle);
				ctx.closePath();
				ctx.clip();

				// 用透明色填充剪辑区域（擦除灰色）
				ctx.clearRect(0, 0, width, height);

				ctx.restore();

				// 继续动画
				requestAnimationFrame(drawSecondPass);
			} else {
				// 第二遍完成，清空整个画布
				ctx.clearRect(0, 0, width, height);
			}
		}

		// 开始第一遍动画
		requestAnimationFrame(drawFirstPass);
	};

}
}