var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = 
{
    "init": function () {

		// console.log("插件编写测试");

		// 可以写一些直接执行的代码
		// 在这里写的代码将会在【资源加载前】被执行，此时图片等资源尚未被加载。
		// 请勿在这里对包括bgm，图片等资源进行操作。


		this._afterLoadResources = function () {
			// 本函数将在所有资源加载完毕后，游戏开启前被执行
			// 可以在这个函数里面对资源进行一些操作，比如切分图片等。

			// 这是一个将assets.png拆分成若干个32x32像素的小图片并保存的样例。
			// var arr = core.splitImage("assets.png", 32, 32);
			// for (var i = 0; i < arr.length; i++) {
			//     core.material.images.images["asset"+i+".png"] = arr[i];
			// }

		}

		// 可以在任何地方（如afterXXX或自定义脚本事件）调用函数，方法为 core.plugin.xxx();
		// 从V2.6开始，插件中用this.XXX方式定义的函数也会被转发到core中，详见文档-脚本-函数的转发。
	},

	"override": function () {
		core.events.getItem = function (id, num, x, y, isGentleClick, callback) {
		    return this.eventdata.getItem(...arguments);
		}

		core.enemys.getEnemyValue = function (enemy, name, x, y, floorId) {
		    return this.enemydata.getEnemyValue(...arguments);
		}
	},

	"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]);
				}
			});
		}
	},

    "shooting": function () {
		/* 远程攻击插件
		作者：柠檬酸
		插件的主体部分，在其它文件中需要改动的地方见 插件说明.js */
		core.enable_shooting = true;
		core.shooting = false; // 正在射击
		core.target = null;

		this.setShooting = (bool) => { 
			if (!core.enable_shooting) return;
			core.shooting = bool;
			if (bool) {
				this.startShooting();
			} else {
				this.cancelShooting();
			}
		}

		// 开始射击
		this.startShooting = (callback) => {
			if (!core.enable_shooting) return;
			if (core.shooting) return;
			// console.log('开始射击');
			core.shooting = true;
			core.playSound('equip.mp3');
			// 更新攻击力
			core.status.hero.originalatk = core.status.hero.atk;
			core.status.hero.atk *= core.values.shootingatk;
			[core.target, core.line] = this.createTarget(core.status.hero.loc); // 在当前位置生成一个target
			core.drawMap(); // 更新地图显示（如伤害）
			if (callback) this.callback = callback; // 保存回调函数，允许其它道具借用实现不同效果
		}

		// 重载 移动
		core.moveHero = (direction, callback) => { 
			if (core.shooting) {
				this.moveTarget(direction);
				if (callback) callback();
			} else {
				core.control.moveHero(direction, callback); // 默认的移动
			}
		 }

		 // 重载 瞬移
		core.setAutomaticRoute = (destX, destY, stepPostfix) => {
			if (core.shooting) {
		 		this.moveTargetTo(destX, destY);
		 	} else {
				core.control.setAutomaticRoute(destX, destY, stepPostfix); // 默认的寻路
		 	}
		}

		this.createTarget = (loc) => {
			var [px, py] = pixelOf(loc.x, loc.y);
			// 创建绘制target的画布
			var target = core.createCanvas('target', px, py, 32, 32, 62); // z = 62, 在前景层上方
			target.x = loc.x;
			target.y = loc.y;

			// 绘制半透明的红色
			target.fillStyle = "rgba(255, 0, 0, 0.65)";
			target.fillRect(0, 0, 32, 32);

			const size = core.__PIXELS__;
			// 绘制瞄准线，玩家到目标的连线
			var line = core.createCanvas('line', 0, 0, size, size, 61); // z = 61, 在前景层上方
			line.start = [px + 16, py + 16];
			line.strokeStyle = "rgba(255, 0, 0, 0.5)";

			return [target, line]
		}

		this.moveTarget = (direction) => {
			var target = core.target, line = core.line;
			if (!target) return;
			var nx = nextX(target, direction), ny = nextY(target, direction);
			// 是否超出地图
			if (nx < 0 || nx >= core.bigmap.width || ny < 0 || ny >= core.bigmap.height) return;
			// 避免移动过快, 移动速度与行走速度一致
			var t = new Date().getTime();
			if (target.time && t - target.time < core.values.moveSpeed) return;

			this.renewTarget(target, line, nx, ny);
			target.time = t;
		}

		// 直接移动到
		this.moveTargetTo = (nx, ny) => {
			var target = core.target, line = core.line;
			if (!target) return;
			// 再次点击相同位置攻击
			if (target.x == nx && target.y == ny) this.shootEnemy();
			// 是否超出地图
			if (nx < 0 || nx >= core.bigmap.width || ny < 0 || ny >= core.bigmap.height) return;
			this.renewTarget(target, line, nx, ny);
		}

		this.renewTarget = (target, line, nx, ny) => {
			// 移动target到nx, ny
			target.x = nx;
			target.y = ny;
			let [px, py] = pixelOf(nx, ny);
			core.relocateCanvas('target', px, py);

			// 清除line
			const size = line.canvas.width;
			core.clearMap('line', 0, 0, size, size);
			let { x: currx, y: curry } = core.getHeroLoc();
			let [lx, ly] = pixelOf(currx, curry);
			if (this.shootingAllowed(target)) {
				// 绘制新的line
				core.drawLine('line', lx + 16, ly + 16, px + 16, py + 16);
			}
		}

		this.cancelShooting = () => {
			if (!core.shooting) return;
			// console.log('结束射击');
			core.shooting = false;
			core.status.hero.atk = core.status.hero.originalatk; // 还原攻击力
			core.deleteCanvas('target'); // 移除target
			core.target = null;
			core.deleteCanvas('line');
			core.line = null;
			// 如果有callback则删除
			if (this.callback) this.callback = null;
			core.drawMap(); // 更新地图显示（如伤害）
		}

		this.toggleShooting = (callback) => {
			if (!core.enable_shooting) return;
			if (core.shooting) {
				this.cancelShooting();
			} else {
				this.startShooting(callback);
			}
		}

		this.shootEnemy = () => {
			var target = core.target;
			if (!target) return;
			// 处理其它功能
			if (this.callback && typeof this.callback === "function") {
				return this.callback(target);
			}
			if (!this.shootingAllowed(target)) {
				core.playSound('操作失败');
				core.drawTip("当前无法远程攻击");
				return
			}
			// 获得敌人信息
			let [x, y] = [target.x, target.y];
			var enemy = core.getBlockId(x, y);
			core.battle(enemy, x, y);
			// 记录route, 用来支持录像回放
			core.status.route.push(`shooting:${x}:${y}`);

			if (!core.flags.multiple_shooting) this.cancelShooting();
		}

		this.shootingAllowed = (target, loc=null) => {
			// 判断能否攻击
			if (!target) return false;
			if (loc === null) loc = core.status.hero.loc; // 角色坐标
			// 检查路径上是否有不可破坏图块，除非unrestrained_shooting是true
			if (!core.hasFlag('unrestrained_shooting')) {
				let path = throughBlocks(loc, target);
				let p = path.every((item) => {
					if (Array.isArray(item)) {
						// 当路线经过角上时，斜对角的两格中有至少一格可穿透即可
						return canPenetrate(item[0]) || canPenetrate(item[1]);
					} else {
						return canPenetrate(item);
					}
				});
				if (!p) return false;
			}
			let block_type = core.getBlockCls(target.x, target.y);
			if (block_type == 'enemys' || block_type == 'enemy48') return true; // 这一格是敌人
			return false
		}

		// 几个辅助函数，不用转发
		function throughBlocks(from, to) {
			/* 从一点到另一点的线段穿过的格子
			返回包含起点，不包含终点的数组 
			当路线恰好穿过角上时，把斜对角的两格也放入数组
			本插件中最复杂的函数了，编写和测试都很耗时间 */
			var result = [];
			// 简单的情况单列
			if (from.x == to.x) { // 垂直的线段
				let i = from.x;
				if (from.y < to.y) {
					for (let j = from.y; j < to.y; j++) {
						result.push({x: i, y: j});
					}
				} else {
					for (let j = from.y; j > to.y; j--) {
						result.push({x: i, y: j});
					}
				}
				return result
			}
			if (from.y == to.y) { // 水平的线段
				let j = from.y;
				if (from.x < to.x) {
					for (let i = from.x; i < to.x; i++) {
						result.push({x: i, y: j});
					}
				} else {
					for (let i = from.x; i > to.x; i--) {
						result.push({x: i, y: j});
					}
				}
				return result
			}
			// 以下是一般情况
			let dx = to.x - from.x, dy = to.y - from.y;
			let slope = dy / dx; // 先求斜率
			let x = from.x + 0.5, y = from.y + 0.5; // 从一格的中心出发
			let step = 0;

			while (Math.floor(x) != to.x || Math.floor(y) != to.y) {
				// 沿着线段走，记录下经过的每一格
				step++;
				if (step > 100) throw `Error! from (${from.x}, ${from.y}) to (${to.x}, ${to.y})`; // 避免bug造成死循环
				if (x < 0 || y < 0 || x > core.bigmap.width+1 || y > core.bigmap.height+1) break;
				let d1 = (dx > 0) - x % 1, // x方向到下一根x线的距离
					d2 = ((dy > 0) - y % 1) / slope; // x方向到下一根y线的距离
				if (d1 == 0) d1 = -1; // dx < 0 且 x 是整数时要改为-1
				if (d2 == 0) d2 = -1 / slope; 

				let i, j;
				if (Math.abs(d1) < Math.abs(d2)) {
					// 先碰到x线
					i = Math.floor(x + d1 / 2);
					j = Math.floor(y + d1 * slope / 2);
					x += d1;
					y += d1 * slope;
					if (i == to.x && j == to.y) break;
					result.push({'x': i, 'y': j});
				} else {
					// 先碰到y线
					i = Math.floor(x + d2 / 2);
					j = Math.floor(y + d2 * slope / 2);
					x += d2;
					y += d2 * slope;
					if (i == to.x && j == to.y) break;
					result.push({'x': i, 'y': j});
				}
				if (Math.abs(d1 - d2) < 1e-6) { // d1 = d2
					// 恰好碰到角上
					// 此时传斜对角的两格，两格中有一格能通行即可
					i = Math.round(x); // round避免舍入误差
					j = Math.round(y);
					if (slope > 0) {
						result.push([{'x': i-1, 'y': j}, {'x': i, 'y': j-1}]);
					} else {
						result.push([{'x': i-1, 'y': j-1}, {'x': i, 'y': j}]);
					}
				} 
			}
			return result
		}

		function canPenetrate(loc) {
			const block = core.getBlock(loc.x, loc.y);
			if (!block) return true; // 空
			const block_type = block.event.cls
			if (block_type == 'enemys' || block_type == 'enemy48') return true; // 敌人
			if (!block.event.noPass || block.event.canPenetrate) return true; // 可通行或可穿透
			return false
		}

		function nextX(now, direction) {
			return now.x + core.utils.scan[direction].x
		}

		function nextY(now, direction) {
			return now.y + core.utils.scan[direction].y
		}

		function pixelOf(x, y) {
			// 用大地图时要减去大地图的偏移
			return [x * 32 - core.bigmap.offsetX, y * 32 - core.bigmap.offsetY]
		}

		// 在设置-操作设置中添加 连续射击
		core.ui._drawSwitchs_action = () => {
			// 需把原来的this改成core.ui
			core.status.event.id = 'switchs-action';
		    var choices = [
		        // 数值越大耗时越长
		        " <   步时：" + core.values.moveSpeed + "   > ",
		        " <   转场：" + core.values.floorChangeTime + "   > ",
		        "血瓶绕路： "+(core.hasFlag('__potionNoRouting__') ? "[ON]":"[OFF]"),
		        "单击瞬移： "+(!core.hasFlag("__noClickMove__") ? "[ON]":"[OFF]"),
		        "连续射击：" + (core.flags.multiple_shooting ? "[ON]":"[OFF]"), // 添加连续射击
		        "左手模式： "+(core.flags.leftHandPrefer ? "[ON]":"[OFF]"),
		        "返回上一级",
		    ];
		    core.ui.drawChoices(null, choices);
		}
		core.actions._clickSwitchs_action = (x, y) => {
			// 需把原来的this改成core.actions
			var choices = core.status.event.ui.choices;
		    var topIndex = core.actions._getChoicesTopIndex(choices.length);
		    var selection = y - topIndex;
		    if (x < core.actions.CHOICES_LEFT || x > core.actions.CHOICES_RIGHT) {
		        if (selection != 0 && selection != 1) return;
		    }
		    if (selection >= 0 && selection < choices.length) {
		        var width = choices[selection].width;
		        var leftPos = (core.__PIXELS__ - width) / 2, rightPos = (core.__PIXELS__ + width) / 2;
		        var leftGrid = parseInt(leftPos / 32), rightGrid = parseInt(rightPos / 32) - 1;
		        core.status.event.selection = selection;
		        switch (selection) {
		            case 0:
		                if (x == leftGrid || x == leftGrid + 1) { core.playSound('确定'); return core.actions._clickSwitchs_action_moveSpeed(-10); }
		                if (x == rightGrid || x == rightGrid + 1) { core.playSound('确定'); return core.actions._clickSwitchs_action_moveSpeed(10); }
		                return;
		            case 1:
		                if (x == leftGrid || x == leftGrid + 1) { core.playSound('确定'); return core.actions._clickSwitchs_action_floorChangeTime(-100); }
		                if (x == rightGrid || x == rightGrid + 1) { core.playSound('确定'); return core.actions._clickSwitchs_action_floorChangeTime(100); }
		            case 2:
		                core.playSound('确定');
		                return core.actions._clickSwitchs_action_potionNoRouting();
		            case 3:
		                core.playSound('确定');
		                return core.actions._clickSwitchs_action_clickMove();
		            case 4: // 添加连续射击设置
		            	core.playSound('确定');
		            	core.flags.multiple_shooting = !core.flags.multiple_shooting; // toggle
		            	core.ui._drawSwitchs_action();
		            	return
		            case 5:
		                core.playSound('确定');
		                return core.actions._clickSwitchs_action_leftHandPrefer();
		            case 6:
		                core.status.event.selection = 2;
		                core.playSound('取消');
		                core.ui._drawSwitchs();
		                return;
		        }
		    }
		}

		// 支持播放录像
		core.control.registerReplayAction("shooting", (action) => {
			let a = action.split(':');
			if (a[0] != "shooting") return false;
			let [x, y] = a.slice(1);
			x = parseInt(x);
			y = parseInt(y);
			this.startShooting();
			this.moveTargetTo(x, y);
			setTimeout(() => {
				this.shootEnemy();
				this.cancelShooting();
				core.replay();
			}, core.control.__replay_getTimeout());
			return true;
		});

		// 播放录像时，该道具的item记录无效化，避免shooting的状态错误
		core.control.registerReplayAction("item", (action) => {
			if (['item:gun', 'item:placePortal', 'item:fishingRod'].includes(action)) {
				core.status.route.push(action);
				core.replay();
				return true;
			}
			return core.control._replayAction_item(action); // 其它道具使用原本的效果
		})

		// 其它利用本插件的道具的播放录像
		core.control.registerReplayAction("placePortal", (action) => {
			let a = action.split(':');
			if (a[0] != "placePortal") return false;
			let [x, y] = a.slice(1);
			x = parseInt(x);
			y = parseInt(y);
			this.cancelShooting();
			core.useItem('placePortal', true);
			this.moveTargetTo(x, y);
			setTimeout(() => {
				this.shootEnemy();
				this.cancelShooting();
				core.replay();
			}, core.control.__replay_getTimeout());
			return true;
		});

		core.control.registerReplayAction("fishing", (action) => {
			let a = action.split(':');
			if (a[0] != "fishing") return false;
			let [x, y] = a.slice(1);
			x = parseInt(x);
			y = parseInt(y);
			this.cancelShooting();
			core.useItem('fishingRod', true);
			this.moveTargetTo(x, y);
			setTimeout(() => {
				this.shootEnemy();
				this.cancelShooting();
				core.replay();
			}, core.control.__replay_getTimeout());
			return true;
		});
	},

    "tempBuff": function() {
		/* 临时属性加成, 每经过一次战斗持续时间-1
		type: 加成类型, 可以为hp, atk, def, mdef
		value: 加成数值, 乘算
		duration: 持续时间 */

		core.setFlag("tempbuffs", []); // 初始化

		this.getTempBuff = () => core.getFlag("tempbuffs", []);

		this.addTempBuff = (type, value, duration) => {
			core.getFlag("tempbuffs").push({
				"type": type,
				"value": value,
				"duration": duration
			})
		}

		this.updateTempBuff = (duration=1) => {
			let new_tempbuffs = [];
			for (let buff of this.getTempBuff()) {
				if (buff.duration > 1) {
					buff.duration -= 1;
					new_tempbuffs.push(buff);
				}
			}
			core.setFlag("tempbuffs", new_tempbuffs);
		}

		this.drawTempBuff = () => {
			const buffnames = {"atk": "力量", "def": "抵抗", "recovery": "回复"};
			let bufflayers = {"atk": 0, "def": 0, "recovery": 0};
			for (let buff of this.getTempBuff()) {
				if (buff['type'] == 'atk' && buff.value < 1) continue;
				bufflayers[buff['type']] += 1;
			}
			let buffstrs = [];
			for (let bufftype in bufflayers) {
				if (bufflayers[bufftype] > 1) {
					buffstrs.push(buffnames[bufftype] + '×' + bufflayers[bufftype]);
				} else if (bufflayers[bufftype] == 1) {
					buffstrs.push(buffnames[bufftype]);
				}
			}
			core.setStatusBarInnerHTML('skill', buffstrs.join(' ') || '空');
		}

		// 重载 获取真实属性
		core.getRealStatusOrDefault = (status, name) => {
			// 普通加成之间叠加，临时加成之间叠乘
		    let value = core.getStatusOrDefault(status, name) * core.getBuff(name);
		    for (let buff of this.getTempBuff()) {
		    	if (buff['type'] == name) value *= buff.value;
		    }
		    return value;
		}
	},
    "editorShowDamage": function () {
		// 在此增加新插件
		/////// 用户设置 ///////
		// 将__enable置为false将关闭插件
		var __enable = true;
		// 魔防攻速之类的属性可以在这里加 ['atk', 'def', 'mdef']
		var heroStatus = ['atk', 'def', 'mdef', 'hp'];
		// saveHero为true 将会把每次造塔测试时的角色数据存下来 否则会读取初始属性
		// 用不着可以关了 节约缓存空间 (虽然根本没多少 还没一个存档大
		// 也可以手动清理 控制台输入core.removeLocalStorage('editorHero')即可
		var saveHero = true;

		// 下为具体实现 懒得写注释了 大概就是写HTML然后注册交互
		if (!__enable || main.mode != 'editor') return;
		core.plugin.initEditorDamage = false;
		if (heroStatus.length >= 4 && !editor.isMobile) editor.dom.mid2.style.top = 650 + 30 * (heroStatus.length - 3) + 'px';
		editor.statusRatio = core.getLocalStorage('statusRatio', 1);
		editor.saveHero = saveHero;
		editor._heroStatus = heroStatus;
		editor.dom.mapEdit.appendChild(core.canvas.damage.canvas)
		var HTML = "<input type='button' value='←'/><input type='button' value='↑'/><input type='button' value='↓'/><input type='button' value='→'/><input type='button' id='bigmapBtn' value='大地图'' style='margin-left: '5px'/>";

		//if (heroStatus.length >= 4 && !editor.isMobile) editor.dom.mid2.style.top = 650 + 30 * (heroStatus.length - 3) + 'px';
		heroStatus.forEach(function (status) {
			var id = status + 'set',
				id2 = status + 'add',
				id3 = status + 'rec',
				id4 = status + 'help';
			HTML += "<br/><input type='text' size='15' id='" + id + "'><input type='button' id='" + id2 + "' value = '+'><input type='button' id='" + id3 + "' value = '-'><input type='button' value='?' id = '" + id4 + "'>"
		});
		document.getElementById('viewportButtons').innerHTML = HTML;
		['set', 'add', 'rec', 'help'].forEach(function (e) {
			heroStatus.forEach(function (status) {
				editor.dom[status + e] = document.getElementById(status + e);
			});
		});
		var _hasItem = core.items.hasItem;
		core.items.hasItem = function (itemId) {
			if (itemId == 'book' && main.mode == 'editor') return true;
			return _hasItem.call(core.items, itemId);
		}
		if (main.mode == "editor") {
			var applyList = ["getDamageString", "nextCriticals", "getEnemyInfo", "getEnemyValue"];
			applyList.forEach(function (name) {
				var func = core.enemys[name];
				core.enemys[name] = function () {
					var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
					if (typeof args[0] == "string") args[0] = core.enemys.enemys[args[0]];
					return func.apply(core.enemys, args);
				}
			});
		}

		////// 获得勇士属性 //////
		core.control.getStatus = function (name) {
			if (!core.status.hero) return null;
			if (name == 'x' || name == 'y' || name == 'direction')
				return this.getHeroLoc(name);
			/*if ( main.mode == 'editor' && !core.hasFlag('__statistics__')) {
				return data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.firstData.hero[name];
			}*/
			return core.status.hero[name];
		}

		/*core.control.updateDamage = function (floorId, ctx) {
			// 合并editorShowDamage和displayItemDetail
			floorId = floorId || core.status.floorId;
			if (!floorId || core.status.gameOver) return;
			var onMap = ctx == null;
			if (main.mode == 'editor') {
				ctx = core.canvas.damage;
				core.updateCheckBlock();
				core.clearMap(ctx);
				if (editor.uivalues.bigmap) return;
			}

			// 没有怪物手册
			if (!core.hasItem('book')) return;
			core.status.damage.posX = core.bigmap.posX;
			core.status.damage.posY = core.bigmap.posY;
			if (!onMap) {
				var 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);
		}*/

		core.control.drawDamage = function (ctx) {
			if (core.status.gameOver || !core.status.damage /* || main.mode != 'play'*/ ) return;
			var onMap = false;
			if (ctx == null) {
				ctx = core.canvas.damage;
				core.clearMap('damage');
				onMap = true;
			}

			if (onMap && core.bigmap.v2) {
				// 检查是否需要重算...
				if (Math.abs(core.bigmap.posX - core.status.damage.posX) >= core.bigmap.extend - 1 ||
					Math.abs(core.bigmap.posY - core.status.damage.posY) >= core.bigmap.extend - 1) {
					return this.updateDamage();
				}
			}
			return this._drawDamage_draw(ctx, onMap);
		}

		////// 以x,y的形式返回每个点的事件 //////
		core.maps.getMapBlocksObj = function (floorId, noCache) {
			floorId = floorId || core.status.floorId;
			if (core.status.mapBlockObjs[floorId] && !noCache && main.mode != 'editor')
				return core.status.mapBlockObjs[floorId];

			var obj = {};
			core.extractBlocks(floorId);
			core.status.maps[floorId].blocks.forEach(function (block) {
				obj[block.x + "," + block.y] = block;
			});
			core.status.mapBlockObjs[floorId] = obj
			return obj;
		}

		this.bignum = function (num, defaultValue) {
			if (num == null || num == "") return defaultValue;
			num = num + "";
			var list = {
				'w': 1e4,
				'e': 1e8,
				'z': 1e12,
				'j': 1e16,
				'g': 1e20
			};
			// 浮点数问题
			function checkFloat(num) {
				if (!core.isset(num)) return 0;
				num = num + "";
				var index = num.indexOf(".");
				if (index < 0) return 0;
				else return num.slice(index + 1).length;
			}
			var index = num.search(/w|e|z|j|g/);
			if (index <= 0) {
				num = parseInt(num);
				if (core.isset(num)) return num;
				else {
					alert('不正确的输入');
					return defaultValue;
				}
			}
			for (; index > 0; index = num.search(/w|e|z|j|g/)) {
				var p = num[index],
					q = list[p],
					n = num.slice(0, index),
					m = Math.pow(10, checkFloat(n));
				num = n * m * q / m + num.slice(index + 1);
			}
			return parseInt(num);
		}

		this.updateEditorDamage = function (noSave) {
			core.updateDamage();
			heroStatus.forEach(function (status) {
				editor.dom[status + 'set'].value = core.status.hero[status];
			});
			if (!noSave && editor.saveHero) core.setLocalStorage('editorHero', core.status.hero);
		}

		var _resizeMap = core.maps.resizeMap;
		core.maps.resizeMap = function (floorId) {
			_resizeMap.call(core.maps, floorId);
			if (!core.plugin.initEditorDamage && main.mode == 'editor') {
				core.plugin.initEditorDamage = true;
				var editorHero = core.getLocalStorage('editorHero');
				if (editorHero && saveHero) core.status.hero = editorHero;
				else core.removeLocalStorage('editorHero');
				editor._heroStatus.forEach(function (e) {
					editor.dom[e + 'set'].onchange = function () {
						var status = this.id.slice(0, -3);
						core.status.hero[status] = core.bignum(this.value, core.status.hero[status]);
						core.updateEditorDamage();
					}
					editor.dom[e + 'add'].onclick = function () {
						var status = this.id.slice(0, -3);
						core.status.hero[status] += editor.statusRatio;
						core.updateEditorDamage();
					}
					editor.dom[e + 'rec'].onclick = function () {
						var status = this.id.slice(0, -3);
						core.status.hero[status] -= editor.statusRatio;
						core.updateEditorDamage();
					}
					editor.dom[e + 'help'].onclick = function () {
						var status = this.id.slice(0, -4),
							name = core.getStatusLabel(status);
						var ratio = parseInt(prompt("当前属性:" + name + "\n现在的点击按钮变化值:" + editor.statusRatio + ",请输入按下一次+/-按钮的属性变化量,可以写4w 10.2e这种字母缩写"));
						if (!core.isset(ratio)) {
							printe('不合法的输入');
							return;
						}
						editor.statusRatio = ratio;
						core.setLocalStorage('statusRatio', ratio);
					}
				});
				var _updateMap = editor.updateMap;
				editor.updateMap = function () {
					_updateMap.call(editor);
					core.updateEditorDamage(true);
				}
				editor.mode.onmode = function (mode, callback) {
					if (editor_mode.mode != mode) {
						if (mode === 'save') {
							editor_mode.doActionList(editor_mode.mode, editor_mode.actionList, function () {
								if (callback) callback();
								core.updateEditorDamage();
							});
						}
						if (editor_mode.mode === 'nextChange' && mode) editor_mode.showMode(mode);
						if (mode !== 'save') editor_mode.mode = mode;
						editor_mode.actionList = [];
					}
				}
			}
		}
	},
    "displayItemDetail": function() {
		/* 宝石血瓶左下角显示数值
		 * 需要将 变量：itemDetail改为true才可正常运行
		 * 请尽量减少勇士的属性数量，否则可能会出现严重卡顿（划掉，现在你放一万个属性也不会卡）
		 * 注意：这里的属性必须是core.status.hero里面的，flag无法显示
		 * 如果不想显示，可以core.setFlag("itemDetail", false);
		 * 然后再core.getItemDetail();
		 * 如有bug在大群或造塔群@古祠
		 */

		// 忽略的道具
		const ignore = ['superPotion', 'divingMask', 'itemBox', 'itemBox2'];

		// 取消注释下面这句可以减少超大地图的判定。
		// 如果地图宝石过多，可能会略有卡顿，可以尝试取消注释下面这句话来解决。
		// 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) {
			// 合并editorShowDamage和displayItemDetail
			floorId = floorId || core.status.floorId;
			if (!floorId || core.status.gameOver) return;
			var onMap = ctx == null;
			if (main.mode == 'editor') {
				ctx = core.canvas.damage;
				core.updateCheckBlock();
				core.clearMap(ctx);
				if (editor.uivalues.bigmap) return;
			}

			// 没有怪物手册
			if (!core.hasItem('book')) return;
			core.status.damage.posX = core.bigmap.posX;
			core.status.damage.posY = core.bigmap.posY;
			if (!onMap) {
				var 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._SIZE_ + core.bigmap.extend ||
		                y < core.bigmap.posY - core.bigmap.extend ||
		                y > core.bigmap.posY + core._SIZE_ + 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 (let 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++;
		    }
		}
	},

	"dynamicBarrier": function () {
		/* 动态加载地图的障碍 */

		this.setBarrier = (floorId) => {
			if (!core.hasItem('divingMask')) {
				// 添加无法入水的障碍
				const directions = ['up', 'down', 'left', 'right'];
				floorId = floorId || core.status.floorId;
				const floor = core.floors[floorId];
				for (let x = 0; x < floor.width; x++) {
					for (let y = 0; y < floor.height; y++) {
						if (core.getBgNumber(x, y) != 16 || core.getBlockId(x, y) == 'X20006') {
							// 当前格不是水
							let cannotMove = directions.filter((d) => {
								const nextX = x + core.utils.scan[d].x,
								    nextY = y + core.utils.scan[d].y;
								return (nextX >= 0 && nextX < floor.width && nextY >= 0 && nextY < floor.height && 
									core.getBgNumber(nextX, nextY) == 16) && core.getBlockId(nextX, nextY) != 'X20006';
							});
							if (cannotMove.length) {
								floor.cannotMove[`${x},${y}`] = cannotMove;
							}
						}
					}
				}
				if (!flags.cannotMove) flags.cannotMove = {};
				flags.cannotMove[floorId] = floor.cannotMove;
			}
		}

		this.loadBarrier = (data) => {
			// 从存档加载障碍
			const cannotMove = data.hero.flags.cannotMove;
			if (cannotMove) {
				for (let floorId in cannotMove) {
					core.floors[floorId].cannotMove = cannotMove[floorId];
				}
			}
		}
	},

	"flashLight": function () {
		/* 在黑暗环境中绘制手电筒光照
		可以看到能照亮的地方及被照到的墙壁，绘制在light图层 */

		var TILE_SIZE = 32,
			MAP_SIZE = 13,
			NUM_RAYS = 720;

		function outofMapBoundary(x, y) {
			return x < 0 || x > MAP_SIZE || y < 0 || y > MAP_SIZE;
		}

		function canPenetrate(x, y) {
			const block = core.getBlock(x, y);
			if (!block) return true; // 空
			const block_type = block.event.cls;
			if (block_type == 'enemys' || block_type == 'enemy48') return true; // 敌人
			if (!block.event.noPass || block.event.canPenetrate) return true; // 可通行或可穿透
			return false;
		}

		// --- DDA 光线投射 ---
		function castRay(heroX, heroY, dx, dy, visibleWalls=null, maxDist=MAP_SIZE*1.42) {
		  let x = heroX + 0.5, y = heroY + 0.5;
		  let mapX = Math.floor(x), mapY = Math.floor(y);

		  const stepX = dx > 0 ? 1 : -1;
		  const stepY = dy > 0 ? 1 : -1;

		  const deltaX = Math.abs(1 / dx);
		  const deltaY = Math.abs(1 / dy);

		  let maxX = (dx > 0 ? (mapX+1 - x) : (x - mapX)) / Math.abs(dx);
		  let maxY = (dy > 0 ? (mapY+1 - y) : (y - mapY)) / Math.abs(dy);

		  let hitWall = null;

		  for (var dist = 0; dist < maxDist;) {
		    if (maxX < maxY) {
		      dist = maxX; maxX += deltaX; mapX += stepX;
		    } else {
		      dist = maxY; maxY += deltaY; mapY += stepY;
		    }

			if (outofMapBoundary(mapX, mapY)) break;

		    if (hitWall !== null) {
		    	if (visibleWalls === null || !visibleWalls.has(mapX+","+mapY)) break;
		    } else if (!canPenetrate(mapX, mapY)) {
		      // 撞到墙 → 记录交点，输出离开此格时的坐标
		      hitWall = {x: mapX, y: mapY};
		    }
		  }
		  return {x: x + dx * dist, y: y + dy * dist, hitWall};
		}

		// --- 计算光照多边形 ---
		function computeLightPolygon(heroX, heroY) {
		  const points = [];
		  const visibleWalls = new Set();

		  for (let a = 0; a < NUM_RAYS; a++) {
		    let dx = Math.cos(a * 2 * Math.PI / NUM_RAYS);
		    let dy = Math.sin(a * 2 * Math.PI / NUM_RAYS);

		    let ray = castRay(heroX, heroY, dx, dy);
		    if (ray.hitWall) {
		        visibleWalls.add(ray.hitWall.x+","+ray.hitWall.y);
		    }
		  }
		  // 再调用一遍castRay以找到光线穿出需要绘制的墙的位置
		  for (let a = 0; a < NUM_RAYS; a++) {
		    let dx = Math.cos(a * 2 * Math.PI / NUM_RAYS);
		    let dy = Math.sin(a * 2 * Math.PI / NUM_RAYS);

		    let ray = castRay(heroX, heroY, dx, dy, visibleWalls);
		    points.push(ray);
		  }
		  return points;
		}

		function drawLightPolygon(ctx, points, color, tileSize=TILE_SIZE) {
		  ctx.beginPath();

		  for (let i = 0; i < points.length; i++) {
		  	let p = points[i];
		  	if (i == 0) ctx.moveTo(p.x * tileSize, p.y * tileSize);
		    else ctx.lineTo(p.x * tileSize, p.y * tileSize);
		  }

		  ctx.closePath();
		  ctx.fillStyle = color;
		  ctx.fill();
		}

		function findCandlePos(floorId) {
			// 查找并缓存火烛的位置
			const map = core.floors[core.status.floorId];
			if (map.candlePosCache) return map.candlePosCache;
			let candlePos = [];
			for (let i = 0; i < map.width; i++) {
				for (let j = 0; j < map.height; j++) {
					if (core.getBgNumber(i, j) == 103) candlePos.push([i * TILE_SIZE + TILE_SIZE / 2, j * TILE_SIZE + TILE_SIZE / 2, 50]);
				}
			}
			map.candlePosCache = candlePos;
			return candlePos;
		}

		this.drawFlashLight = (offset=0) => {
			let ctx = core.getContextByName('light');
			if (ctx === null) {
				ctx = core.createCanvas('light', 0, 0, core.__PIXELS__, core.__PIXELS__, 130);
			}

			const { x, y, direction } = core.getHeroLoc();
			let offsetX, offsetY;
			if (typeof offset === 'object' && offset !== null) {
				offsetX = offset.x;
				offsetY = offset.y;
			} else {
			    offsetX = offset ? offset * core.utils.scan[direction].x : 0;
			    offsetY = offset ? offset * core.utils.scan[direction].y : 0;
			}
			let posX = x * TILE_SIZE + TILE_SIZE / 2 + offsetX, posY = y * TILE_SIZE + TILE_SIZE / 2 + offsetY;
			let lightPolygon = computeLightPolygon(x + offsetX / TILE_SIZE, y + offsetY / TILE_SIZE);

			// 清除画布
			core.clearMap('light');
			// 绘制黑色遮罩
			ctx.fillStyle = "rgba(0,0,0,1)";
			ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
			// 绘制火烛照亮部分
			let candlePos = findCandlePos(core.status.floorId).slice();
			candlePos.push([posX, posY, 80]);
			core.plugin.drawLight('light', 1, candlePos, 0.4);

			// 绘制照亮部分
			ctx.globalCompositeOperation = 'destination-out';
			const grd = ctx.createRadialGradient(posX, posY, 36, posX, posY, 600);
			grd.addColorStop(0, "rgba(0,0,0,1)");
			grd.addColorStop(0.2, "rgba(127,127,127,0.65)");
			grd.addColorStop(0.5, "rgba(191,191,191,0.35)");
			grd.addColorStop(1, "rgba(255,255,255,0.1)");
			drawLightPolygon(ctx, lightPolygon, grd);
			ctx.globalCompositeOperation = 'source-over';
		}

		this.drawCandleLight = (offset=0) => {
			// 绘制火烛照亮部分和角色周围
			let ctx = core.getContextByName('light');
			if (ctx === null) {
				ctx = core.createCanvas('light', 0, 0, core.__PIXELS__, core.__PIXELS__, 130);
			}

			const { x, y, direction } = core.getHeroLoc();
			let offsetX, offsetY;
			if (typeof offset === 'object' && offset !== null) {
				offsetX = offset.x;
				offsetY = offset.y;
			} else {
			    offsetX = offset ? offset * core.utils.scan[direction].x : 0;
			    offsetY = offset ? offset * core.utils.scan[direction].y : 0;
			}
			let posX = x * TILE_SIZE + TILE_SIZE / 2 + offsetX, posY = y * TILE_SIZE + TILE_SIZE / 2 + offsetY;
			let candlePos = findCandlePos(core.status.floorId).slice();
			candlePos.push([posX, posY, 80]);
			core.plugin.drawLight('light', 1, candlePos, 0.4);
		}

		core.drawHero = (status, offset, frame) => {
			if (core.status.floorId && core.floors[core.status.floorId].underGround && !core.isReplaying()) {
				if (core.hasItem('flashLight')) core.plugin.drawFlashLight(offset);
				else core.plugin.drawCandleLight(offset);
			}
			return core.control.drawHero(status, offset, frame);
		}
	},

	"variableEvent": function () {
		/* 允许改变事件
		core.setBlock和core.removeBlock只能启用或移除某点事件，改变图块时也不能改变该点的事件。
		本插件允许直接设置一点的事件。
		beforeBattle, afterBattle, afterOpenDoor, afterGetItem, 读取floor的属性
		changeFloor, action 读取block.event.data
		autoEvent, pushBox和ski不处理 */

		function saveEventtoFlag(x, y, floorId, event, trigger) {
			// 保存修改后的事件
			const locStr = `${x},${y}`;
			let variableEvent = core.getFlag('variableEvent', {});
			if (!variableEvent[floorId]) variableEvent[floorId] = {};
			if (!variableEvent[floorId][locStr]) variableEvent[floorId][locStr] = {};
			if (trigger === null) {
				variableEvent[floorId][locStr] = {};
			} else {
				variableEvent[floorId][locStr][trigger] = event;
			}
			core.setFlag('variableEvent', variableEvent);
		}

		this.setEvent = (x, y, floorId, event, trigger, saveFlag=true) => {
			const locStr = `${x},${y}`;
			if (!floorId) floorId = core.status.floorId;

			// 同时设置多种事件
			if (trigger == "all") {
				if (Object.keys(event).length === 0) { // 空对象
					return this.setEvent(x, y, floorId, null, null);
				}
				for (let t in event) {
					this.setEvent(x, y, floorId, event[t], t, saveFlag);
				}
				return
			}

			// 保存在楼层上的事件
			if (['beforeBattle', 'afterBattle', 'afterOpenDoor', 'afterGetItem'].includes(trigger)) {
				if (!core.floors[floorId][trigger]) core.floors[floorId][trigger] = {};
				if (event)
					core.floors[floorId][trigger][locStr] = event;
				else
					delete core.floors[floorId][trigger][locStr];
				if (saveFlag)
					saveEventtoFlag(x, y, floorId, event, trigger);
				return
			}

			// 其它事件保存在图块上
			let block = core.getBlock(x, y, floorId);
			
			if (block && !event && !trigger) {
				// 将事件和触发都设置为空
				delete block.event.data;
				delete block.event.trigger;
				if (saveFlag)
					saveEventtoFlag(x, y, floorId, null, null);
				return
			}

			trigger = trigger || "action";
			if (block === null && event) {
				// 原本无物体也无事件
				block = {
					x: x,
					y: y,
					id: 0,
					event: {
						data: event,
						trigger: trigger,
						id: "none",
						animate: 1,
						cls: "terrains",
						height: 32,
						noPass: false
					}
				}
				if (event.enable !== undefined) block.disable = !event.enable;
				core.status.maps[floorId].blocks.push(block);
				core.status.mapBlockObjs[floorId][locStr] = block;

			} else if (block) {
				if (event) block.event.data = event;
				block.event.trigger = trigger;
				if (event && event.enable !== undefined) block.disable = !event.enable;
			}
			if (saveFlag) {
				saveEventtoFlag(x, y, floorId, event, trigger);
			}
		}

		this.copyEvent = (fromX, fromY, toX, toY, floorId) => {
			if (!floorId) floorId = core.status.floorId;
			const floor = core.floors[floorId];

			// 保存在楼层上的事件
			for (let trigger of ['beforeBattle', 'afterBattle', 'afterOpenDoor', 'afterGetItem']) {
				if (floor[trigger] && floor[trigger][`${fromX},${fromY}`]) {
					this.setEvent(toX, toY, floorId, floor[trigger][`${fromX},${fromY}`], trigger);
				}
			}

			// 保存在图块上的事件
			const block = core.getBlock(fromX, fromY, floorId);
			if (block) {
				if (!['beforeBattle', 'afterBattle', 'afterOpenDoor', 'afterGetItem'].includes(block.event.trigger))
					this.setEvent(toX, toY, floorId, block.event.data, block.event.trigger);
			} else {
				this.setEvent(toX, toY, floorId, null, null);
			}
		}

		this.cacheEvent = (x, y, floorId) => {
			// 保存一个位置的所有事件
			if (!floorId) floorId = core.status.floorId;
			const floor = core.floors[floorId];
			event = {};

			// 保存在楼层上的事件
			for (let trigger of ['beforeBattle', 'afterBattle', 'afterOpenDoor', 'afterGetItem']) {
				if (floor[trigger] && floor[trigger][`${x},${y}`]) {
					event[trigger] = floor[trigger][`${x},${y}`];
				}
			}

			// 保存在图块上的事件
			const block = core.getBlock(x, y, floorId);
			if (block) {
				if (block.event.trigger && !['beforeBattle', 'afterBattle', 'afterOpenDoor', 'afterGetItem'].includes(block.event.trigger))
					event[block.event.trigger] = block.event.data;
			}
			return event;
		}

		this.copyBlockandEvent = (fromX, fromY, toX, toY, floorId) => {
			const block = core.getBlock(fromX, fromY, floorId);
			if (block === null) {
				core.removeBlock(toX, toY, floorId);
			} else {
				core.setBlock(block.id, toX, toY, floorId);
				this.copyEvent(fromX, fromY, toX, toY, floorId)
			}
		}

		this.moveBlockandEvent = (x, y, steps, time, keep) => {
			let event = this.cacheEvent(x, y);
			let toX = x, toY = y;
			steps.forEach((direction) => {
				toX += core.utils.scan[direction].x;
				toY += core.utils.scan[direction].y;
			});
			core.moveBlock(x, y, steps, time, keep, () => {
				if (keep) {
					core.plugin.setEvent(toX, toY, null, event, "all");
					core.moveEnemyOnPoint(x, y, toX, toY);
				} else {
					core.resetEnemyOnPoint(x, y);
				}
			});
		}

		this.loadVariableEvent = (data) => {
			const variableEvent = data.hero.flags.variableEvent;
			if (!variableEvent) return;
			for (let floorId in variableEvent) {
				for (let locStr in variableEvent[floorId]) {
					let [ x, y ] = locStr.split(',');
					x = parseInt(x);
					y = parseInt(y);
					this.setEvent(x, y, floorId, variableEvent[floorId][locStr], "all", false);
				}
			}
		}
	},

	"random": function () {
		/* 随机地图和随机道具
		不同区域的敌人和道具有不同权重，每个种类有数量上限。
		不在游戏一开始就全部生成，而是在初次到达楼层时生成。这样随机数的消耗次数就会改变生成的结果。
		（是否要做）打怪和使用道具还会改变随机数种子。
		既不能SL，也不能提前知道随机的结果。 */

		const random_items = {
			"sword2": { region: 1, max_count: 1 },
			"shield2": { region: 1, max_count: 1 },
			"shield3": { region: 1, max_count: 1 },
			"pickaxe": { region: 1, max_count: null },
			"bomb": { region: 1, max_count: null },
			"cake": { region: 1, max_count: null },
			"placePortal": { region: 1, max_count: 2 },
			"I546": { region: 1, max_count: null },
			"I547": { region: 1, max_count: null },
			"I548": { region: 1, max_count: null },
			"magicWand": { region: 2, max_count: 1 },
			"reactiveArmor": { region: 2, max_count: 1 },
			"nanoArmor": { region: 2, max_count: 1 },
			"stun": { region: 2, max_count: 1 },
			"coke": { region: 2, max_count: 1 },
			"specialKey": { region: 2, max_count: null },
			"laser": { region: 2, max_count: null },
			"I331": { region: 2, max_count: null },
			"treasureMap": { region: 2, max_count: 1 },
			"flashLight": { region: 3, max_count: 1 },
			"atkBook": { region: 3, max_count: 1 },
			"defBook": { region: 3, max_count: 1 },
			"hammer": { region: 3, max_count: 1 },
			"I523": { region: 3, max_count: 1 },
			"I533": { region: 3, max_count: 1 },
			"I353": { region: 3, max_count: 1 },
			"invisibilityCloak": { region: 3, max_count: 1 },
			"weakWine": { region: 3, max_count: null },
			"poisonWine": { region: 3, max_count: null },
			"divingMask": { region: 4, max_count: 1 },
			"electricity": { region: 4, max_count: 1 },
			"trident": { region: 4, max_count: 1 },
			"magicArmor": { region: 4, max_count: 1 },
			"frozenFish": { region: 4, max_count: 1 },
			"I354": { region: 4, max_count: 1 },
			"fishingRod": { region: 4, max_count: 2 },
			"push": { region: 4, max_count: 2 },
			"dice": { region: 5, max_count: 1 },  // 5是随机道具
			"magicDice": { region: 5, max_count: 1 },
			"curseDice": { region: 5, max_count: 1 },
			"luckyDice": { region: 5, max_count: 1 },
			"I424": { region: 5, max_count: 1 },
		}; // name: { region, max_count }

		// item_weight[i-1][j-1] 是在i区获得j区每个道具的权重，无需归一化
		const item_weight = [
			[1, 0.75, 0.5, 0.35, 0.65],
			[1, 1, 0.8, 0.6, 0.85],
			[0.85, 0.9, 1, 0.85, 0.9],
			[1, 1, 1, 1.4, 1.1],
			[1, 1, 1, 1, 1]  // 商店
		];

		const random_enemies = {
			"greenSlime": { region: 1, max_count: 16 },
			"redSlime": { region: 1, max_count: 16 },
			"goldHornSlime": { region: 1, max_count: 16 },
			"E576": { region: 1, max_count: 12 },
			"E580": { region: 1, max_count: 8 },
			"zombie": { region: 2, max_count: 16 },
			"crimsonZombie": { region: 2, max_count: 16 },
			"E577": { region: 2, max_count: 12 },
			"swordsman": { region: 2, max_count: 16 },
			"liteBowman": { region: 2, max_count: 16 },
			"bat": { region: 3, max_count: 16 },
			"redBat": { region: 3, max_count: 16 },
			"E581": { region: 3, max_count: 8 },
			"E590": { region: 3, max_count: 16 },
			"skeleton": { region: 3, max_count: 16 },
			"E574": { region: 4, max_count: 16 },
			"E573": { region: 4, max_count: 16 },
			"E585": { region: 4, max_count: 12 },
			"mutantSlimeman": { region: 4, max_count: 16 },
			"E550": { region: 4, max_count: 16 },
		};

		// enemy_weight[i-1][j-1] 是在i区生成j区每个怪物的权重，无需归一化
		const enemy_weight = [
			[1, 0.95, 0.9, 0.8],
			[0.9, 1, 0.95, 0.85],
			[0.8, 0.8, 1, 0.9],
			[0.8, 0.8, 0.8, 1]
		];

		const random_boss = {
			"slimelord": { region: 1, max_count: 1 },
			"bandit": { region: 2, max_count: 1 },
			"hoodedman": { region: 3, max_count: 1 },
			"xiu": { region: 4, max_count: 1 },
		};

		const boss_weight = [
			[1, 1, 1, 1],
			[1, 1, 1, 1],
			[1, 1, 1, 1],
			[1, 1, 1, 1]
		];

		function computeWeights(choices, defaultWeight, currRegion, count) {
			currRegion ||= core.floors[core.status.floorId].area || 1;
			let weights = {};
			for (let c in choices) {
				let { region, max_count } = choices[c];
				if (max_count !== null && count[c] >= max_count) continue; // 已达到最大个数
				weights[c] = defaultWeight[currRegion-1][region-1];
			}
			return weights;
		}

		this.randomMode_init = () => {
			// 随机模式初始化
			core.setFlag("randomMode", true);

			let item_count = {};
			for (let item in random_items) {
				item_count[item] = 0;
			}
			core.setFlag("item_count", item_count);

			let enemy_count = {};
			for (let enemy in random_enemies) {
				enemy_count[enemy] = 0;
			}
			core.setFlag("enemy_count", enemy_count);

			let boss_count = {};
			for (let boss in random_boss) {
				boss_count[boss] = 0;
			}
			core.setFlag("boss_count", boss_count);
		}

		this.randomChoice = (choices, defaultWeight, currRegion, count) => {
			if (!core.hasFlag('randomMode')) return;
			const weights = computeWeights(choices, defaultWeight, currRegion, count);
			let choices_arr = [],
				cum_weight = [],
				w = 0;
			for (let c in weights) {
				choices_arr.push(c);
				w += weights[c];
				cum_weight.push(w);
			}
			let r = core.rand() * w;
			for (var i = 0; i < choices_arr.length; i++) {
				if (r < cum_weight[i]) break;
			}
			const pick = choices_arr[i];
			count[pick]++;
			return pick;
		}

		this.getRandomItem = (currRegion) => {
			return this.randomChoice(random_items, item_weight, currRegion, core.getFlag("item_count"));
		}

		this.getRandomEnemy = (currRegion) => {
			return this.randomChoice(random_enemies, enemy_weight, currRegion, core.getFlag("enemy_count"));
		}

		this.getRandomBoss = (currRegion) => {
			return this.randomChoice(random_boss, boss_weight, currRegion, core.getFlag("boss_count"));
		}

		this.randomizeMap = (floorId) => {
			// 在地图上生成随机怪物，随机升级一部分宝箱
			const { width, height, area } = core.floors[floorId];
			for (let x = 0; x < width; x++) {
				for (let y = 0; y < height; y++) {
					let blockId = core.getBlockId(x, y);
					if (blockId == 'X10192') {
						// 生成随机怪物
						let enemy = this.getRandomEnemy(area);
						core.setBlock(enemy, x, y);
					} else if (blockId == 'X10263') {
						// 生成随机boss
						let boss = this.getRandomBoss(area);
						core.setBlock(boss, x, y);
					} else if (blockId == 'itemBox') {
						// 10%概率升级成高级宝物盒
						if (core.rand() < 0.1) {
							core.setBlock('itemBox2', x, y);
						}
					}
				}
			}
		}

		this.randMangling = (x=0, y=0, id=0) => {
			// mix32 打散函数（32位）
			function mix32(z) {
			    z |= 0; // 确保是32位整数
			    z ^= z >>> 16;
			    z = Math.imul(z, 0x85ebca6b);
			    z ^= z >>> 13;
			    z = Math.imul(z, 0xc2b2ae35);
			    z ^= z >>> 16;
			    return z >>> 0; // 保证非负
			}

			let rand = core.getFlag("__rand__");
			let h = ((x + 1) << 20) + ((y + 1) << 16) + id;
			h = mix32(h);
			rand = mix32(rand ^ h) % 2147483647;
			if (rand == 0) rand = 1;
			core.setFlag("__rand__", rand);
			return rand;
		}

		this.randomMode_test = () => {
			const TRIALS = 100000;
			const N_ITEMS = [8, 8, 10, 8, 50],
				  N_ENEMIES = [28, 59, 59, 62];
			const item_count = {};
			for (let item in random_items) {
				item_count[item] = 0;
			}
			const item_firstget = {};
			for (let item in random_items) {
				item_firstget[item] = 0;
			}
			const enemy_count = {};
			for (let enemy in random_enemies) {
				enemy_count[enemy] = 0;
			}
			
			const oneTrial = () => {
				this.randomMode_init();
				n = 1;
				for (let area = 0; area < 5; area++) {
					for (let i = 0; i < N_ITEMS[area]; i++) {
						let item = this.getRandomItem(area);
						item_count[item]++;
						if (flags.item_count[item] == 1)
							item_firstget[item] += n;
						n++;
					}
				}
				for (let area = 0; area < 4; area++) {
					for (let i = 0; i < N_ENEMIES[area]; i++) {
						let enemy = this.getRandomEnemy(area);
						enemy_count[enemy]++;
					}
				}
			}

			// run trials
			if (!core.isPlaying()) core.startGame();
			for (let t = 0; t < TRIALS; t++) {
				oneTrial();
			}

			// calculate average
			let item_count_avg = {};
			for (let item in random_items) {
				item_count_avg[item] = item_count[item] / TRIALS;
			}
			let item_firstget_avg = {};
			for (let item in random_items) {
				item_firstget_avg[item] = item_firstget[item] / TRIALS;
			}
			let enemy_count_avg = {};
			for (let enemy in random_enemies) {
				enemy_count_avg[enemy] = enemy_count[enemy] / TRIALS;
			}
			console.log("avg item count:", item_count_avg);
			console.log("avg item firstget:", item_firstget_avg);
			console.log("avg enemy count:", enemy_count_avg);

			core.restart();
		}
	},

	"multilineTip": function () {
		core.ui.drawTip = (text, id, frame) => {
			let lines = text.split('\n');
			let height = 18 + 24 * lines.length;
			let width = 26 + Math.max(...lines.map((line) => core.calWidth('data', line, "16px Arial")));
		    var one = {
		        text: lines,
		        textX: 21,
		        height: height,
		        width: width,
		        opacity: 0.1,
		        stage: 1,
		        frame: frame || 0,
		        time: 0
		    };
		    if (id != null) {
		        var info = core.getBlockInfo(id);
		        if (info == null || !info.image) {
		            // 检查状态栏图标
		            if (core.statusBar.icons[id] instanceof Image) {
		                info = {image: core.statusBar.icons[id], posX: 0, posY: 0, height: 32};
		            }
		            else info = null;
		        }
		        if (info != null) {
		            one.image = info.image;
		            one.posX = info.posX;
		            one.posY = info.posY;
		            one.imgheight = info.height;
		            one.height = Math.max(info.height + 10, one.height);
		            one.textX += 24;
		            one.width += 24;
		        }
		    }
		    core.animateFrame.tip = one;
		}

		core.ui._drawTip_drawOne = (tip) => {
		    core.setAlpha('data', tip.opacity);
		    core.fillRect('data', 5, 5, tip.width, tip.height, '#000000');
		    if (tip.image)
		        core.drawImage('data', tip.image, (tip.posX + tip.frame) * 32, tip.posY * tip.imgheight, 32, tip.imgheight, 10, 10, 32, tip.imgheight);
		    for (let i = 0; i < tip.text.length; i++) {
		    	core.fillText('data', tip.text[i], tip.textX, 33 + 24 * i, '#FFFFFF');
		    }
		    core.setAlpha('data', 1);
		}

		core.registerAnimationFrame("tip", true, (timestamp) => {
		    if (core.animateFrame.tip == null) return;
		    var tip = core.animateFrame.tip;
		    if (timestamp - tip.time <= 30) return;
		    var delta = timestamp - tip.time;
		    tip.time = timestamp;

		    core.setFont('data', "16px Arial");
		    core.setTextAlign('data', 'left');
		    core.clearMap('data', 5, 5, core.__PIXELS__, 114);
		    core.ui._drawTip_drawOne(tip);
		    if (tip.stage == 1) {
		        tip.opacity += 0.05;
		        if (tip.opacity >= 0.6) {
		            tip.stage = 2;
		            tip.displayTime = 0;
		        }
		    } else if (tip.stage == 2) {
		        tip.displayTime += delta;
		        if (tip.displayTime >= 1000) tip.stage = 3;
		    } else tip.opacity -= 0.05;

		    if (tip.opacity <= 0) {
		        core.animateFrame.tip = null;
		    }
		}); 
	}
}