var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = 
{
    "events": {
        "resetGame": function (hero, hard, floorId, maps, values) {
			// 重置整个游戏；此函数将在游戏开始时，或者每次读档时最先被调用
			// hero：勇士信息；hard：难度；floorId：当前楼层ID；maps：地图信息；values：全局数值信息

			// 清除游戏数据
			// 这一步会清空状态栏和全部画布内容，并删除所有动态创建的画布
			core.clearStatus();
			// 读档/回档会删除动态Canvas；同时重置BOSS代码绘制运行标记，避免旧标记挡住重绘。
			try {
				if (core.plugin) {
					core.plugin.__mt4BossBodyRunning = false;
					if (core.plugin.setMT4BossInkBlackWhiteMode) core.plugin.setMT4BossInkBlackWhiteMode(false);
				}
			} catch (e) {}
			// 初始化status
			core.status = core.clone(core.initStatus, function (name) {
				return name != 'hero' && name != 'maps';
			});
			core.control._bindRoutePush();
			core.status.played = true;
			// 初始化人物，图标，统计信息
			core.status.hero = core.clone(hero);
			window.hero = core.status.hero;
			window.flags = core.status.hero.flags;
			core.events.setHeroIcon(core.status.hero.image, true);
			core.control._initStatistics(core.animateFrame.totalTime);
			core.status.hero.statistics.totalTime = core.animateFrame.totalTime =
				Math.max(core.status.hero.statistics.totalTime, core.animateFrame.totalTime);
			core.status.hero.statistics.start = null;
			// 初始难度
			core.status.hard = hard || "";
			// 初始化地图
			core.status.floorId = floorId;
			core.status.maps = maps;
			core.maps._resetFloorImages();
			// 初始化怪物和道具
			core.material.enemys = core.enemys.getEnemys();
			core.material.items = core.items.getItems();
			// 安装"降龙分裂怪"头顶标记绘制器；只安装一次，避免读档/重开时重复包裹。
			if (core.control && core.control.controldata && core.control.controldata.installDragonSplitMarkRenderer) {
				core.control.controldata.installDragonSplitMarkRenderer();
			}
			// 安装豹跃录像动作；录像只记录最终落点 leopardTo:x:y。
			if (core.control && core.control.controldata && core.control.controldata.installLeopardReplayAction) {
				core.control.controldata.installLeopardReplayAction();
			}
			// 安装穿梭录像动作；录像只记录最终传送点 shuttleTo:floorId:x:y:enemyId。
			if (core.control && core.control.controldata && core.control.controldata.installShuttleReplayAction) {
				core.control.controldata.installShuttleReplayAction();
			}
			// 安装躲避录像动作；录像只记录怪物最终躲避点 dodgeTo:floorId:fromX:fromY:toX:toY:enemyId。
			if (core.control && core.control.controldata && core.control.controldata.installDodgeReplayAction) {
				core.control.controldata.installDodgeReplayAction();
			}
			// 安装MT4龙魂王录像动作；只记录最终阶段结果，回放时不播放异步动画。
			if (core.control && core.control.controldata && core.control.controldata.installMT4BossReplayAction) {
				core.control.controldata.installMT4BossReplayAction();
			}
			// 安装MT4 P4阻击献祭钩子：等待阻击移动真正完成后再检查祭坛。
			if (core.control && core.control.controldata && core.control.controldata.installMT4BossP4RepulseSacrificeHook) {
				core.control.controldata.installMT4BossP4RepulseSacrificeHook();
			}
			// MT0龙魂试炼期间保持商店可用：不再安装商店封印。
			// 初始化全局数值和全局开关
			core.values = core.clone(core.data.values);
			for (var key in values || {})
				core.values[key] = values[key];
			core.flags = core.clone(core.data.flags);
			var globalFlags = core.getFlag("globalFlags", {});
			for (var key in globalFlags)
				core.flags[key] = globalFlags[key];
			core._init_sys_flags();
			// 初始化界面，状态栏等
			core.resize();
			// 状态栏是否显示
			if (core.hasFlag('hideStatusBar'))
				core.hideStatusBar(core.hasFlag('showToolbox'));
			else
				core.showStatusBar();
			// 隐藏右下角的音乐按钮
			core.dom.musicBtn.style.display = 'none';

			// 读档/回退会清空动态画布；如果恢复到MT4，稍后把代码绘制BOSS和血条补回来。
			setTimeout(function () {
				if (core.status && core.status.floorId == 'MT4') {
					// 读档/回档到P6及以后时，恢复342黑白底图和黑白UI；P5动画前仍保持原图。
					try {
						if (core.control && core.control.controldata && core.control.controldata.syncMT4BossPhaseBackground) {
							core.control.controldata.syncMT4BossPhaseBackground(core.getFlag('__MT4BossPhase__', 1), core.getFlag('__MT4BossPhase__', 1) >= 6);
						}
					} catch (eBgLoad) {}
					// 读档/回档到P2及以后时，也必须清 core.floors.MT4 的透明墙。
					if (core.getFlag('__MT4BossPhase__', 1) >= 2 && core.control && core.control.controldata && core.control.controldata.removeMT4BossP3TopBarriers) {
						core.control.controldata.removeMT4BossP3TopBarriers();
					}
					if (core.plugin && core.plugin.ensureMT4BossScene
						&& (core.hasFlag('__MT4BossIntroPlayed__') || core.getFlag('__MT4BossPhase__', null) != null || core.getFlag('__MT4BossHpSegments__', null) != null)) {
						core.plugin.ensureMT4BossScene();
					}
				}
			}, 80);
		},
        "win": function (reason, norank, noexit) {
			// 游戏获胜事件
			// 请注意，成绩统计时是按照hp进行上传并排名
			// 可以先在这里对最终分数进行计算，比如将2倍攻击和5倍黄钥匙数量加到分数上
			// core.status.hero.hp += 2 * core.getRealStatus('atk') + 5 * core.itemCount('yellowKey');

			// 如果不退出，则临时存储数据
			if (noexit) {
				core.status.extraEvent = core.clone(core.status.event);
			}

			// 游戏获胜事件 
			core.ui.closePanel();
			var replaying = core.isReplaying();
			if (replaying) core.stopReplay();
			core.waitHeroToStop(function () {
				if (!noexit) {
					core.clearMap('all'); // 清空全地图
					core.deleteAllCanvas(); // 删除所有创建的画布
					core.dom.gif2.innerHTML = "";
				}
				reason = core.replaceText(reason);
				core.drawText([
					"\t[" + (reason || "恭喜通关") + "]你的分数是${status:hp}。"
				], function () {
					core.events.gameOver(reason || '', replaying, norank);
				})
			});
		},
        "lose": function (reason) {
			// 游戏失败事件
			core.ui.closePanel();
			var replaying = core.isReplaying();
			core.stopReplay();
			core.waitHeroToStop(function () {
				core.drawText([
					"\t[" + (reason || "结局1") + "]你死了。\n如题。"
				], function () {
					core.events.gameOver(null, replaying);
				});
			})
		},
        "changingFloor": function (floorId, heroLoc) {
			// 正在切换楼层过程中执行的操作；此函数的执行时间是“屏幕完全变黑“的那一刻
			// floorId为要切换到的楼层ID；heroLoc表示勇士切换到的位置

			// ---------- 此时还没有进行切换，当前floorId还是原来的 ---------- //
			var currentId = core.status.floorId || null; // 获得当前的floorId，可能为null
			var fromLoad = core.hasFlag('__fromLoad__'); // 是否是读档造成的切换
			var isFlying = core.hasFlag('__isFlying__'); // 是否是楼传造成的切换
			if (!fromLoad && !(isFlying && currentId == floorId)) {
				if (!core.hasFlag("__leaveLoc__")) core.setFlag("__leaveLoc__", {});
				if (currentId != null) core.getFlag("__leaveLoc__")[currentId] = core.clone(core.status.hero.loc);
			}

			// 可以对currentId进行判定，比如删除某些自定义图层等
			// if (currentId == 'MT0') {
			//     core.deleteAllCanvas();
			// }

			// 根据分区信息自动砍层与恢复
			if (core.autoRemoveMaps) core.autoRemoveMaps(floorId);

			// 重置画布尺寸
			core.maps.resizeMap(floorId);
			// 设置勇士的位置
			heroLoc.direction = core.turnDirection(heroLoc.direction);
			core.status.hero.loc = heroLoc;
			// 检查重生怪并重置
			if (!fromLoad) {
				core.extractBlocks(floorId);
				core.status.maps[floorId].blocks.forEach(function (block) {
					if (block.disable && core.enemys.hasSpecial(block.event.id, 23)) {
						block.disable = false;
						core.setMapBlockDisabled(floorId, block.x, block.y, false);
						core.maps._updateMapArray(floorId, block.x, block.y);
					}
				});
				core.control.gatherFollowers();
			}

			// ---------- 重绘新地图；这一步将会设置core.status.floorId ---------- //
			core.drawMap(floorId);

			// 切换楼层BGM
			if (core.status.maps[floorId].bgm) {
				var bgm = core.status.maps[floorId].bgm;
				if (bgm instanceof Array) bgm = bgm[Math.floor(Math.random() * bgm.length)]; // 多个bgm则随机播放一个
				if (!core.hasFlag("__bgm__")) core.playBgm(bgm);
			} else if (fromLoad && !core.hasFlag("__bgm__")) {
				core.pauseBgm();
			}
			// 更改画面色调
			var color = core.getFlag('__color__', null);
			if (!color && core.status.maps[floorId].color)
				color = core.status.maps[floorId].color;
			core.clearMap('curtain');
			core.status.curtainColor = color;
			if (color) core.fillRect('curtain', 0, 0, core._PX_ || core.__PIXELS__, core._PY_ || core.__PIXELS__, core.arrayToRGBA(color));
			// 更改天气
			var weather = core.getFlag('__weather__', null);
			if (!weather && core.status.maps[floorId].weather)
				weather = core.status.maps[floorId].weather;
			if (weather)
				core.setWeather(weather[0], weather[1]);
			else core.setWeather();

			// ...可以新增一些其他内容，比如创建个画布在右上角显示什么内容等等

		},
        "afterChangeFloor": function (floorId) {
			// 转换楼层结束的事件；此函数会在整个楼层切换完全结束后再执行
			// floorId是切换到的楼层

			// 恢复 碎甲(35)/破功(36) 的累计削减：仅在【真的切换到不同楼层】时恢复
			// 关键：读档时 afterChangeFloor 也会被调用，但 floorId 跟 flag 里记录的相同，所以不会误恢复
			var ab = core.getFlag('__armorBroken__', null);
			var sb = core.getFlag('__skillBroken__', null);
			var didRestore = false;
			if (ab && ab.floorId && ab.floorId !== floorId && (ab.amount || 0) > 0) {
				core.status.hero.def = (core.status.hero.def || 0) + ab.amount;
				core.removeFlag('__armorBroken__');
				didRestore = true;
			}
			if (sb && sb.floorId && sb.floorId !== floorId && (sb.amount || 0) > 0) {
				core.status.hero.atk = (core.status.hero.atk || 0) + sb.amount;
				core.removeFlag('__skillBroken__');
				didRestore = true;
			}
			if (didRestore) {
				core.updateStatusBar(false, true);
			}

			// 成长(42)/寄生(43) 累计器：切楼时清除，但读档同层不清除
			// 判别方式：跟踪上一次 afterChangeFloor 的 floorId；不同→真切楼
			var lastFloor = core.getFlag('__lastVisitedFloor__', null);
			if (lastFloor !== floorId && !core.hasFlag('__fromLoad__')) {
				// 真正切换楼层（不是读档触发）→ 清空累计
				core.removeFlag('__growthAccum__');
				core.removeFlag('__parasiteAccum__');
			}
			core.setFlag('__lastVisitedFloor__', floorId);

			// 如果是读档，则进行检查（是否需要恢复事件）
			if (core.hasFlag('__fromLoad__')) {
				core.events.recoverEvents(core.getFlag("__events__"));
				core.removeFlag("__events__");
			} else {
				// 每次抵达楼层执行的事件
				core.insertAction(core.floors[floorId].eachArrive);

				// 首次抵达楼层时执行的事件（后插入，先执行）
				if (!core.hasVisitedFloor(floorId)) {
					core.insertAction(core.floors[floorId].firstArrive);
					core.visitFloor(floorId);
				}
			}

			// MT4：BOSS层默认无法轮回；刚进这层就把本层轮回机会置为已使用。
			if (floorId == 'MT4') {
				// 从进入MT4开始直到通关，不再有新的轮回次数。
				core.setFlag('__MT4BossNoRevive__', true);
				var __mt4ReviveUsed = core.getFlag('__firstBattleFailReviveUsed__', {});
				__mt4ReviveUsed.MT4 = true;
				core.setFlag('__firstBattleFailReviveUsed__', __mt4ReviveUsed);
				// MT4 BOSS 阶段本体只读取你设置好的 260~265；268以后恢复为普通怪，不再作为BOSS编号。
				// 进入/读档/回档时确保当前阶段本体存在；P4未献祭完成前只隐藏代码BOSS，地图本体仍显示但无敌。
				if (core.control && core.control.controldata && core.control.controldata.ensureMT4BossCenterBlock) {
					core.control.controldata.ensureMT4BossCenterBlock();
				}
				// MT4 BOSS分阶段BGM：只播放音乐，不写录像、不插事件，避免影响录像链。
				if (core.control && core.control.controldata && core.control.controldata.syncMT4BossPhaseBgm) {
					core.control.controldata.syncMT4BossPhaseBgm(core.getFlag('__MT4BossPhase__', 1));
				}
			}

			// MT4：首次到达BOSS层后播放纯Canvas入场演出，并在演出后保留代码绘制BOSS本体。
			// 读档/回退时不重复播放入场，只恢复BOSS本体和血条。
			if (floorId == 'MT4' && !core.hasFlag('__fromLoad__') && !core.hasFlag('__MT4BossIntroPlayed__')) {
				core.setFlag('__MT4BossIntroPlayed__', true);
				core.setFlag('__MT4BossPhase__', core.getFlag('__MT4BossPhase__', 1));
				core.setFlag('__MT4BossHpSegments__', Math.max(0, Math.min(6, core.getFlag('__MT4BossHpSegments__', 6))));
				setTimeout(function () {
					if (core.status && core.status.floorId == 'MT4' && core.plugin && core.plugin.playMT4BossIntro) {
						core.plugin.playMT4BossIntro();
					}
				}, 80);
			} else if (floorId == 'MT4') {
				setTimeout(function () {
					if (core.status && core.status.floorId == 'MT4') {
						// 读档、回退、重新进入到P2及以后，都清掉MT4透明墙。
						if (core.getFlag('__MT4BossPhase__', 1) >= 2 && core.control && core.control.controldata && core.control.controldata.removeMT4BossP3TopBarriers) {
							core.control.controldata.removeMT4BossP3TopBarriers();
						}
						if (core.plugin && core.plugin.ensureMT4BossScene) {
							core.plugin.ensureMT4BossScene();
						}
						if (core.control && core.control.controldata && core.control.controldata.syncMT4BossPhaseBgm) {
							core.control.controldata.syncMT4BossPhaseBgm(core.getFlag('__MT4BossPhase__', 1));
						}
					}
				}, 80);
			} else if (core.hasFlag('__MT4BossTrialActive__') && floorId == core.getFlag('__MT4BossTrialFloor__', 'MT0')) {
				// 龙魂王轮回试炼刚切入时不要立刻做清层检测；
				// 否则在地图尚未完整重绘/恢复时可能误判为空层并直接返回MT4。
				setTimeout(function () {
					if (core.status && core.status.floorId == floorId) {
						try { core.drawTip(core.getFlag('__MT4BossTrialTip__', '龙魂试炼开始，清空本层所有怪物后返回BOSS层。')); } catch (e) {}
					}
				}, 80);
			} else if (floorId != 'MT4' && core.plugin) {
				if (core.plugin.hideMT4BossHud) core.plugin.hideMT4BossHud();
				if (core.plugin.hideMT4BossBody) core.plugin.hideMT4BossBody();
			}

		},
        "flyTo": function (toId, callback) {
			// 楼层传送器的使用，从当前楼层飞往toId
			// 如果不能飞行请返回false

			var fromId = core.status.floorId;

			// 检查能否飞行
			if (!core.status.maps[fromId].canFlyFrom || !core.status.maps[toId].canFlyTo || !core.hasVisitedFloor(toId)) {
				core.playSound('操作失败');
				core.drawTip("无法飞往" + core.status.maps[toId].title + "！", 'fly');
				return false;
			}

			// 平面塔模式
			var stair = null,
				loc = null;
			if (core.flags.flyRecordPosition) {
				loc = core.getFlag("__leaveLoc__", {})[toId] || null;
			}
			if (core.status.maps[toId].flyPoint != null && core.status.maps[toId].flyPoint.length == 2) {
				stair = 'flyPoint';
			}
			if (stair == null && loc == null) {
				// 获得两个楼层的索引，以决定是上楼梯还是下楼梯
				var fromIndex = core.floorIds.indexOf(fromId),
					toIndex = core.floorIds.indexOf(toId);
				var stair = fromIndex <= toIndex ? "downFloor" : "upFloor";
				// 地下层：同层传送至上楼梯
				if (fromIndex == toIndex && core.status.maps[fromId].underGround) stair = "upFloor";
			}

			// 记录录像
			core.status.route.push("fly:" + toId);
			// 传送
			core.ui.closePanel();
			core.setFlag('__isFlying__', true);
			core.changeFloor(toId, stair, loc, null, function () {
				core.removeFlag("__isFlying__");
				if (callback) callback();
			});

			return true;
		},
        "beforeBattle": function (enemyId, x, y) {
			// 战斗前触发的事件，可以加上一些战前特效（详见下面支援的例子）
			// 此函数在“检测能否战斗和自动存档”【之后】执行。如果需要更早的战前事件，请在插件中覆重写 core.events.doSystemEvent 函数。
			// 返回true则将继续战斗，返回false将不再战斗。

			// ------ 支援技能 ------ //
			if (x != null && y != null) {
				var index = x + "," + y,
					cache = core.status.checkBlock.cache[index] || {},
					guards = cache.guards || [];
				// 如果存在支援怪
				if (guards.length > 0) {
					// 记录flag，当前要参与支援的怪物
					core.setFlag("__guards__" + x + "_" + y, guards);
					var actions = [{ "type": "playSound", "name": "跳跃" }];
					// 增加支援的特效动画（图块跳跃）
					guards.forEach(function (g) {
						core.push(actions, { "type": "jump", "from": [g[0], g[1]], "to": [x, y], "time": 300, "keep": false, "async": true });
					});
					core.push(actions, [
						{ "type": "waitAsync" }, // 等待所有异步事件执行完毕
						{ "type": "setBlock", "number": enemyId, "loc": [[x, y]] }, // 重新设置怪物自身
						{ "type": "battle", "loc": [x, y] } // 重要！重新触发本次战斗
					]);
					core.insertAction(actions);
					return false;
				}
			}

			// 45 躲避：勇士即将和此怪物战斗时，怪物先朝空地躲开并增加20生命；本次战斗取消。
			if (x != null && y != null) {
				var dodgeEnemy = core.material.enemys[enemyId];
				var dodgeSpecial = core.getEnemyValue(dodgeEnemy, 'special', x, y);
				if (core.enemys.hasSpecial(dodgeSpecial, 45)
					&& core.control && core.control.controldata && core.control.controldata.triggerMonsterDodge) {
					if (core.control.controldata.triggerMonsterDodge(enemyId, x, y, core.status.floorId)) return false;
				}
			}

			// MT4 龙魂王 P2：护法未清完前，中心本体只读取编号261对应怪物，但临时禁止战斗；不改怪物属性。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9
				&& core.getFlag('__MT4BossPhase__', 1) == 2
				&& !core.hasFlag('__MT4BossP2Unlocked__')) {
				core.drawTip('护法尚在，龙魂王暂时无敌。');
				return false;
			}

			// MT4 龙魂王 P3：阵眼/怪物未处理完前，中心本体只读取编号262对应怪物，但临时禁止战斗。
			// 注意：这里每次战斗前都实时检查，避免旧的 __MT4BossP3Unlocked__ 标记导致还有怪也能打BOSS。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9
				&& core.getFlag('__MT4BossPhase__', 1) == 3
				&& core.control && core.control.controldata && core.control.controldata.canAttackMT4BossP3
				&& !core.control.controldata.canAttackMT4BossP3()) {
				core.drawTip(core.hasFlag('__MT4BossP3SealsCleared__') ? '护阵残敌尚在，龙魂王暂时无敌。' : '四方封印尚未破除，龙魂王暂时无敌。');
				return false;
			}

			// MT4 龙魂王 P4：献祭怪未全部消失前，中心本体只读取编号263对应怪物，但临时禁止战斗。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9
				&& core.getFlag('__MT4BossPhase__', 1) == 4
				&& core.control && core.control.controldata && core.control.controldata.canAttackMT4BossP4
				&& !core.control.controldata.canAttackMT4BossP4()) {
				core.drawTip('祭坛献祭尚未完成，龙魂王暂时无敌。');
				return false;
			}

			// MT4 龙魂王 P5：已恢复为MT0试炼返回后直接打BOSS。
			// 仅兼容旧存档中“棋阵正在播放”的状态，正常流程不再用棋阵锁BOSS。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9
				&& core.getFlag('__MT4BossPhase__', 1) == 5
				&& core.hasFlag('__MT4BossP5ChessRunning__')
				&& !core.hasFlag('__MT4BossP5ChessDone__')) {
				core.drawTip('终局棋阵尚未结束，龙魂王暂时无敌。');
				return false;
			}

			// MT4 P4 录像：正常游戏在真正开打P4 BOSS前，记录一次“勇士+BOSS最终状态”。
			// 回放时会从P3打完BOSS直接跳到这里，跳过中间所有推祭坛过程。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9
				&& core.getFlag('__MT4BossPhase__', 1) == 4
				&& !(core.isReplaying && core.isReplaying())
				&& core.control && core.control.controldata && core.control.controldata.canAttackMT4BossP4
				&& core.control.controldata.canAttackMT4BossP4()
				&& core.control.controldata.recordMT4BossP4ReadyRoute) {
				core.control.controldata.recordMT4BossP4ReadyRoute();
			}

			return true;
		},
        "afterBattle": function (enemyId, x, y) {
			// 战斗结束后触发的事件

			var enemy = core.material.enemys[enemyId];
			// 使用坐标点上的怪物属性覆盖，保证复活怪等 point-special 能正确生效
			var special = core.getEnemyValue(enemy, 'special', x, y);

			// 播放战斗音效和动画
			// 默认播放的动画；你也可以使用
			var animate = 'hand'; // 默认动画
			// 检查当前装备是否存在攻击动画
			var equipId = core.getEquip(0);
			if (equipId && (core.material.items[equipId].equip || {}).animate)
				animate = core.material.items[equipId].equip.animate;
			// 你也可以在这里根据自己的需要，比如enemyId或special或flag来修改播放的动画效果
			// if (enemyId == '...') animate = '...';

			// 检查该动画是否存在SE，如果不存在则使用默认音效
			if (!(core.material.animates[animate] || {}).se)
				core.playSound('attack.mp3');

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

			// 获得战斗伤害信息
			var damageInfo = core.getDamageInfo(enemyId, null, x, y) || {};
			// 战斗伤害
			var damage = damageInfo.damage;
			// 当前战斗回合数，可用于战后所需的判定
			var turn = damageInfo.turn;
			// 判定是否致死
			if (damage == null) {
				// 伤害为???表示无法破防/无法结算，这种“不破防”的怪不触发轮回重置。
				core.status.hero.hp = 0;
				core.updateStatusBar(false, true);
				core.events.lose('战斗失败');
				return;
			}

			if (damage >= core.status.hero.hp) {
				// 每层第一次“真实进入战斗后”的失败：不游戏结束，重置当前地图并在(6,12)重生。
				// 注意：只有 damage 是数字且大于等于勇士生命时才触发；???不触发。
				if (core.control && core.control.controldata && core.control.controldata.tryFirstBattleFailRevive
					&& core.control.controldata.tryFirstBattleFailRevive(damage, core.status.hero.hp, enemyId, x, y, !!damageInfo.bloodWarTriggered)) {
					return;
				}
				core.status.hero.hp = 0;
				core.updateStatusBar(false, true);
				core.events.lose('战斗失败');
				return;
			}

			// 扣减体力值并记录统计数据
			core.status.hero.hp -= damage;
			core.status.hero.statistics.battleDamage += damage;
			core.status.hero.statistics.battle++;

			// 计算当前怪物的支援怪物
			var guards = [];
			if (x != null && y != null) {
				guards = core.getFlag("__guards__" + x + "_" + y, []);
				core.removeFlag("__guards__" + x + "_" + y);
			}

			// 获得金币
			var money = guards.reduce(function (curr, g) {
				return curr + core.material.enemys[g[2]].money;
			}, core.getEnemyValue(enemy, "money", x, y));
			if (core.hasItem('coin')) money *= 2; // 幸运金币：双倍
			if (core.hasFlag('curse')) money = 0; // 诅咒效果
			core.status.hero.money += money;
			core.status.hero.statistics.money += money;

			// 获得经验
			var exp = guards.reduce(function (curr, g) {
				return curr + core.material.enemys[g[2]].exp;
			}, core.getEnemyValue(enemy, "exp", x, y));
			if (core.hasFlag('curse')) exp = 0;
			core.status.hero.exp += exp;
			core.status.hero.statistics.exp += exp;

			var hint = "打败 " + core.getEnemyValue(enemy, "name", x, y);
			if (core.flags.statusBarItems.indexOf('enableMoney') >= 0)
				hint += ',' + core.getStatusLabel('money') + '+' + money; // hint += "，金币+" + money;
			if (core.flags.statusBarItems.indexOf('enableExp') >= 0)
				hint += ',' + core.getStatusLabel('exp') + '+' + exp; // hint += "，经验+" + exp;
			core.drawTip(hint, enemy.id);

			// 中毒
			if (core.enemys.hasSpecial(special, 12)) {
				core.triggerDebuff('get', 'poison');
			}
			// 衰弱
			if (core.enemys.hasSpecial(special, 13)) {
				core.triggerDebuff('get', 'weak');
			}
			// 诅咒
			if (core.enemys.hasSpecial(special, 14)) {
				core.triggerDebuff('get', 'curse');
			}
			// 仇恨怪物将仇恨值减半
			if (core.enemys.hasSpecial(special, 17)) {
				core.setFlag('hatred', Math.floor(core.getFlag('hatred', 0) / 2));
			}
			// 自爆
			if (core.enemys.hasSpecial(special, 19)) {
				core.status.hero.statistics.battleDamage += core.status.hero.hp - 1;
				core.status.hero.hp = 1;
			}
			// 退化
			if (core.enemys.hasSpecial(special, 21)) {
				core.status.hero.atk -= (enemy.atkValue || 0);
				core.status.hero.def -= (enemy.defValue || 0);
				if (core.status.hero.atk < 0) core.status.hero.atk = 0;
				if (core.status.hero.def < 0) core.status.hero.def = 0;
			}
			// 碎甲 (35)：战斗后防御 -1，累计记录于 __armorBroken__ = {floorId, amount}
			// 切换楼层时（楼层 ID 变化）整体恢复；轮回时也恢复
			if (core.enemys.hasSpecial(special, 35)) {
				if ((core.status.hero.def || 0) > 0) {
					core.status.hero.def -= 1;
					var ab = core.getFlag('__armorBroken__', null);
					var curFloor = core.status.floorId;
					if (ab && ab.floorId === curFloor) {
						ab.amount = (ab.amount || 0) + 1;
					} else {
						ab = { floorId: curFloor, amount: 1 };
					}
					core.setFlag('__armorBroken__', ab);
				}
			}
			// 破功 (36)：战斗后攻击 -1，累计记录于 __skillBroken__ = {floorId, amount}
			// 切换楼层时（楼层 ID 变化）整体恢复；轮回时也恢复
			if (core.enemys.hasSpecial(special, 36)) {
				if ((core.status.hero.atk || 0) > 0) {
					core.status.hero.atk -= 1;
					var sb = core.getFlag('__skillBroken__', null);
					var curFloor2 = core.status.floorId;
					if (sb && sb.floorId === curFloor2) {
						sb.amount = (sb.amount || 0) + 1;
					} else {
						sb = { floorId: curFloor2, amount: 1 };
					}
					core.setFlag('__skillBroken__', sb);
				}
			}
			// 共生 (37)：连续击杀方可真正消灭
			//   击杀共生怪 → 加入本层 __symbiosisPending__ 列表
			//   击杀非共生怪 → 若本层还有活着的共生怪 + pending 列表非空，全部复活 pending
			//   全部共生怪都进 pending（本层无活着的共生）→ 清空 pending（链条完成，永久死亡）
			(function () {
				var fId = core.status.floorId;
				if (!fId) return;
				var pendingKey = '__symbiosisPending_' + fId + '__';
				var pending = core.getFlag(pendingKey, []);
				var isSym = core.enemys.hasSpecial(special, 37);
				if (isSym) {
					// 把这只共生加入 pending（注意 x,y 可能为 undefined → 强制战斗，无坐标，跳过）
					if (x != null && y != null) {
						pending.push({ x: x, y: y });
						core.setFlag(pendingKey, pending);
					}
					// 看本层是否还有活着的共生
					var liveCount = (core.control && core.control.controldata && core.control.controldata.countLiveSymbiosis)
						? core.control.controldata.countLiveSymbiosis(fId) : 0;
					if (liveCount === 0) {
						// 链条完成，pending 内的怪已经真死，清空标记
						core.removeFlag(pendingKey);
					}
				} else {
					// 非共生怪：若 pending 非空 + 本层还有活着的共生 → 全部复活
					if (pending.length > 0) {
						var liveCount2 = (core.control && core.control.controldata && core.control.controldata.countLiveSymbiosis)
							? core.control.controldata.countLiveSymbiosis(fId) : 0;
						if (liveCount2 > 0) {
							pending.forEach(function (p) {
								try {
									core.showBlock(p.x, p.y);
									// 标记一下方便 plugins.js 做复活特效
									core.setFlag('__lastSymbiosisRevive__', {
										positions: pending.slice(),
										timestamp: Date.now(),
										floorId: fId
									});
								} catch (e) {}
							});
							core.removeFlag(pendingKey);
							core.updateStatusBar(false, true);
							try { core.drawMap(fId); } catch (e) {}
						}
					}
				}
			})();
			// 增加仇恨值
			core.setFlag('hatred', core.getFlag('hatred', 0) + core.values.hatred);

			// 战后的技能处理，比如扣除魔力值
			if (core.flags.statusBarItems.indexOf('enableSkill') >= 0) {
				// 检测当前开启的技能类型
				var skill = core.getFlag('skill', 0);
				if (skill == 1) { // 技能1：二倍斩
					core.status.hero.mana -= 5; // 扣除5点魔力值
				}
				// 关闭技能
				core.setFlag('skill', 0);
				core.setFlag('skillName', '无');
			}


			// 事件的处理
			var todo = [];

			// 加点事件
			var point = guards.reduce(function (curr, g) {
				return curr + core.material.enemys[g[2]].point;
			}, core.getEnemyValue(enemy, "point", x, y)) || 0;
			if (core.flags.enableAddPoint && point > 0) {
				core.push(todo, [{ "type": "insert", "name": "加点事件", "args": [point] }]);
			}

			// 战后事件
			if (core.status.floorId != null) {
				core.push(todo, core.floors[core.status.floorId].afterBattle[x + "," + y]);
			}
			core.push(todo, enemy.afterBattle);

			// 在这里增加其他的自定义事件需求
			/*
			if (enemyId=='xxx') {
				core.push(todo, [
					{"type": "...", ...},
				]);
			}
			*/

			// 如果事件不为空，将其插入
			if (todo.length > 0) core.insertAction(todo, x, y);

			// 记录该点怪物的独立属性，屠龙复活时要继承这些属性
			var enemyPointInfo = core.clone(((flags.enemyOnPoint || {})[core.status.floorId] || {})[x + "," + y] || {});

			// 删除该点设置的怪物信息
			delete ((flags.enemyOnPoint || {})[core.status.floorId] || {})[x + "," + y];

			// 因为removeBlock和hideBlock都会刷新状态栏，因此将删除部分移动到这里并保证刷新只执行一次，以提升效率
			// 在删除/隐藏之前，先捕获死亡怪的完整属性，供后续吞噬使用
			var deadEnemyInfoForDevour = null;
			try {
				deadEnemyInfoForDevour = core.enemys.getEnemyInfo(enemy, null, x, y, core.status.floorId);
			} catch (e) {}

			if (core.getBlock(x, y) != null) {
				// 检查是否是重生怪(23) 或 共生怪(37)；都使用 hideBlock 保留 block 数据
				// 重生：转换楼层时恢复
				// 共生：链条中断时通过 showBlock 复活
				if (core.hasSpecial(special, 23) || core.hasSpecial(special, 37)) {
					core.hideBlock(x, y);
				} else {
					core.removeBlock(x, y);
				}
			} else {
				core.updateStatusBar();
			}

			// 38 吞噬：本层每死一只怪 → 所有活着的吞噬怪获得 死亡怪 5% HP/ATK/DEF（保底+1）
			// 注意：要在 removeBlock/hideBlock 之后执行，确保 countLive*类查询不会把刚死的算进活的
			if (deadEnemyInfoForDevour && core.control && core.control.controldata && core.control.controldata.applyDevourBuff) {
				core.control.controldata.applyDevourBuff(core.status.floorId, deadEnemyInfoForDevour, x, y);
			}

			// 清理该点的临时攻击加成，避免以后同坐标刷怪时残留。
			if (core.control && core.control.controldata && core.control.controldata.clearEnemyAtkBonus) {
				core.control.controldata.clearEnemyAtkBonus(core.status.floorId, x, y);
			}

			// MT4 龙魂王 P1：使用编号260的怪物 mt4BossP1，地图位置(6,9)。
			// 击败后：顶部BOSS血条掉1格，BOSS发射一格宽激光束，将勇士击退到(6,12)，并进入P2投送演出。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9 && core.getFlag('__MT4BossPhase__', 1) == 1 && !core.hasFlag('__MT4BossP1Done__')) {
				core.setFlag('__MT4BossP1Done__', true);
				var bossHpSeg = Math.max(0, Math.min(6, core.getFlag('__MT4BossHpSegments__', 6)) - 1);
				if (!(core.isReplaying && core.isReplaying()) && core.status && core.status.route) {
					core.status.route.push('mt4BossP1:' + bossHpSeg + ':6:12');
				}
				var finishP1 = function () {
					if (core.control && core.control.controldata && core.control.controldata.applyMT4BossP1State) {
						core.control.controldata.applyMT4BossP1State(bossHpSeg, 6, 12, false);
					} else {
						core.setFlag('__MT4BossPhase__', 2);
						core.setFlag('__MT4BossHpSegments__', bossHpSeg);
						core.setBlock(261, 6, 9, 'MT4');
						core.setHeroLoc('x', 6); core.setHeroLoc('y', 12); core.setHeroLoc('direction', 'up'); core.drawHero();
						try { core.unlockControl(); } catch (e) {}
					}
				};
				if (core.plugin && core.plugin.playMT4BossP1Counter) {
					core.plugin.playMT4BossP1Counter(finishP1);
				} else {
					finishP1();
				}
				return;
			}

			// MT4 龙魂王 P2：编号261，护法清完后无敌解除；击败后掉1格血，击退到(6,12)，切到P3编号262。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9 && core.getFlag('__MT4BossPhase__', 1) == 2 && core.hasFlag('__MT4BossP2Unlocked__') && !core.hasFlag('__MT4BossP2Done__')) {
				core.setFlag('__MT4BossP2Done__', true);
				var bossHpSeg2 = Math.max(0, Math.min(5, core.getFlag('__MT4BossHpSegments__', 5)) - 1);
				if (!(core.isReplaying && core.isReplaying()) && core.status && core.status.route) {
					core.status.route.push('mt4BossP2:' + bossHpSeg2 + ':6:12');
				}
				var finishP2 = function () {
					if (core.control && core.control.controldata && core.control.controldata.applyMT4BossP2State) {
						core.control.controldata.applyMT4BossP2State(bossHpSeg2, 6, 12, false);
					} else {
						core.setFlag('__MT4BossPhase__', 3);
						core.setFlag('__MT4BossHpSegments__', bossHpSeg2);
						core.setBlock(262, 6, 9, 'MT4');
						core.setHeroLoc('x', 6); core.setHeroLoc('y', 12); core.setHeroLoc('direction', 'up'); core.drawHero();
						try { core.unlockControl(); } catch (e) {}
					}
				};
				if (core.plugin && core.plugin.playMT4BossP1Counter) {
					core.plugin.playMT4BossP1Counter(finishP2);
				} else {
					finishP2();
				}
				return;
			}

			// MT4 龙魂王 P3：编号262，阵眼全破且护阵怪清完后无敌解除；击败后掉1格血，击退到(6,12)，切到P4编号263。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9 && core.getFlag('__MT4BossPhase__', 1) == 3
				&& core.control && core.control.controldata && core.control.controldata.canAttackMT4BossP3
				&& core.control.controldata.canAttackMT4BossP3() && !core.hasFlag('__MT4BossP3Done__')) {
				core.setFlag('__MT4BossP3Done__', true);
				var bossHpSeg3 = Math.max(0, Math.min(4, core.getFlag('__MT4BossHpSegments__', 4)) - 1);
				if (!(core.isReplaying && core.isReplaying()) && core.status && core.status.route) {
					core.status.route.push('mt4BossP3:' + bossHpSeg3 + ':6:12');
				}
				var finishP3 = function () {
					if (core.control && core.control.controldata && core.control.controldata.applyMT4BossP3State) {
						core.control.controldata.applyMT4BossP3State(bossHpSeg3, 6, 12, false);
					} else {
						core.setFlag('__MT4BossPhase__', 4);
						core.setFlag('__MT4BossHpSegments__', bossHpSeg3);
						core.setBlock(263, 6, 9, 'MT4');
						core.setHeroLoc('x', 6); core.setHeroLoc('y', 12); core.setHeroLoc('direction', 'up'); core.drawHero();
						try { core.unlockControl(); } catch (e) {}
					}
				};
				if (core.plugin && core.plugin.playMT4BossP1Counter) {
					core.plugin.playMT4BossP1Counter(finishP3);
				} else {
					finishP3();
				}
				return;
			}

			// MT4 龙魂王 P4：编号263，献祭怪全部消失后无敌解除；击败后掉1格血，击退到(6,12)，切到P5编号264。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9 && core.getFlag('__MT4BossPhase__', 1) == 4
				&& core.control && core.control.controldata && core.control.controldata.canAttackMT4BossP4
				&& core.control.controldata.canAttackMT4BossP4() && !core.hasFlag('__MT4BossP4Done__')) {
				core.setFlag('__MT4BossP4Done__', true);
				var bossHpSeg4 = Math.max(0, Math.min(3, core.getFlag('__MT4BossHpSegments__', 3)) - 1);
				if (!(core.isReplaying && core.isReplaying()) && core.status && core.status.route) {
					core.status.route.push('mt4BossP4:' + bossHpSeg4 + ':6:12');
				}
				var finishP4 = function () {
					if (core.control && core.control.controldata && core.control.controldata.applyMT4BossP4State) {
						core.control.controldata.applyMT4BossP4State(bossHpSeg4, 6, 12, false);
					} else {
						core.setFlag('__MT4BossPhase__', 5);
						core.setFlag('__MT4BossHpSegments__', bossHpSeg4);
						core.setBlock(264, 6, 9, 'MT4');
						core.setHeroLoc('x', 6); core.setHeroLoc('y', 12); core.setHeroLoc('direction', 'up'); core.drawHero();
						try { core.unlockControl(); } catch (e) {}
					}
				};
				if (core.plugin && core.plugin.playMT4BossP1Counter) {
					core.plugin.playMT4BossP1Counter(finishP4);
				} else finishP4();
				return;
			}


			// MT4 龙魂王 P5以后：改为同一张MT4地图上的单BOSS连续阶段；不再进入MT1/MT2/MT3重置试炼。
			if (core.status.floorId == 'MT4' && x == 6 && y == 9
				&& core.getFlag('__MT4BossPhase__', 1) >= 5 && core.getFlag('__MT4BossPhase__', 1) <= 6) {
				var __phase = core.getFlag('__MT4BossPhase__', 1);
				var __doneFlag = '__MT4BossP' + __phase + 'Done__';
				if (!core.hasFlag(__doneFlag)) {
					core.setFlag(__doneFlag, true);
					var __expectedSeg = Math.max(1, 7 - __phase);
					var __bossHpSeg = Math.max(0, Math.min(__expectedSeg, core.getFlag('__MT4BossHpSegments__', __expectedSeg)) - 1);
					if (!(core.isReplaying && core.isReplaying()) && core.status && core.status.route) {
						core.status.route.push('mt4BossP' + __phase + ':' + __bossHpSeg + ':6:12');
					}
					var __finishBossPhase = function () {
						if (core.control && core.control.controldata && core.control.controldata.applyMT4BossLinearState) {
							core.control.controldata.applyMT4BossLinearState(__phase, __bossHpSeg, 6, 12, false);
						} else {
							var __nextPhase = Math.min(6, __phase + 1);
							core.setFlag('__MT4BossPhase__', __nextPhase);
							core.setFlag('__MT4BossHpSegments__', __bossHpSeg);
							try { core.setBlock(259 + __nextPhase, 6, 9, 'MT4'); } catch (e) {}
							core.setHeroLoc('x', 6); core.setHeroLoc('y', 12); core.setHeroLoc('direction', 'up'); core.drawHero();
							try { core.unlockControl(); } catch (e2) {}
						}
					};
					var __runBossPhaseAfterFx = function () {
						// P5击破后先播放30秒水墨演出；演出结束后才切342黑白底图和黑白UI，再进入P6。
						if (__phase == 5 && !(core.isReplaying && core.isReplaying())
							&& core.plugin && core.plugin.playMT4BossP5InkSequence && !core.hasFlag('__MT4BossP5InkEndingPlayed__')) {
							core.setFlag('__MT4BossP5InkEndingPlayed__', true);
							try { core.lockControl(); } catch (eInkLock) {}
							core.plugin.playMT4BossP5InkSequence(function () {
								try { if (core.plugin && core.plugin.setMT4BossInkBlackWhiteMode) core.plugin.setMT4BossInkBlackWhiteMode(true); } catch (eBW) {}
								__finishBossPhase();
							});
							return;
						}
						// P6最终击破后使用胜利水墨收束动画；动画结束后直接游戏胜利。
						if (__phase == 6 && !(core.isReplaying && core.isReplaying())
							&& core.plugin && core.plugin.playMT4BossP6ToP7InkSequence && !core.hasFlag('__MT4BossP6ToP7InkPlayed__')) {
							core.setFlag('__MT4BossP6ToP7InkPlayed__', true);
							try { core.lockControl(); } catch (eP6P7Lock) {}
							core.plugin.playMT4BossP6ToP7InkSequence(function () {
								__finishBossPhase();
							});
							return;
						}
						__finishBossPhase();
					};
					// P6最终CG本身包含“最后一击冻结”，不要再叠加龙魂王反击激光。
					if (__phase == 6) __runBossPhaseAfterFx();
					else if (core.plugin && core.plugin.playMT4BossP1Counter) core.plugin.playMT4BossP1Counter(__runBossPhaseAfterFx);
					else __runBossPhaseAfterFx();
					return;
				}
			}

			// MT4 P2：每次战斗后检查护法是否全部清完，清完则解除261无敌。
			if (core.status.floorId == 'MT4' && core.getFlag('__MT4BossPhase__', 1) == 2
				&& core.control && core.control.controldata && core.control.controldata.checkMT4BossP2Unlock) {
				core.control.controldata.checkMT4BossP2Unlock();
			}

			// MT4 P3：每次战斗后检查护阵怪是否全部清完，清完则解除262无敌。
			if (core.status.floorId == 'MT4' && core.getFlag('__MT4BossPhase__', 1) == 3
				&& core.control && core.control.controldata && core.control.controldata.checkMT4BossP3Unlock) {
				core.control.controldata.checkMT4BossP3Unlock();
			}

			// MT4 P4：每次战斗后检查献祭怪是否全部处理完，清完则解除263无敌。
			if (core.status.floorId == 'MT4' && core.getFlag('__MT4BossPhase__', 1) == 4
				&& core.control && core.control.controldata && core.control.controldata.checkMT4BossP4Unlock) {
				core.control.controldata.checkMT4BossP4Unlock();
			}

			// 30 灭霸：与本怪物战斗每满3回合，本层所有降龙怪攻击增加10%。
			if (core.enemys.hasSpecial(special, 30) && turn != null && turn >= 3) {
				var thanosStacks = Math.floor(turn / 3);
				if (core.control && core.control.controldata && core.control.controldata.applyThanosDragonAtkBuff) {
					core.control.controldata.applyThanosDragonAtkBuff(core.status.floorId, thanosStacks);
				}
			}

			// 31 屠虎：每击杀一只降龙，现存屠虎攻击增加10%。
			if (core.enemys.hasSpecial(special, 29)) {
				if (core.control && core.control.controldata && core.control.controldata.applyTigerSlayerAtkBuff) {
					core.control.controldata.applyTigerSlayerAtkBuff(core.status.floorId, 1);
				}
			}

			// 46 畏缩：本层每死亡一个怪物，所有存活的畏缩怪攻击/防御各 -1。
			// 这是确定性结算，录像回放会随本次战斗同步执行；回放时跳过动画。
			if (core.control && core.control.controldata && core.control.controldata.applyCowardiceOnMonsterDeath) {
				core.control.controldata.applyCowardiceOnMonsterDeath(core.status.floorId, x, y);
			}

			// 屠龙：战斗胜利后，在战斗方向的左右两侧复活为降龙怪。
			// 冲刺战斗使用冲刺时记录的落点；勇士主动攻击时，使用屠龙死亡的原坐标。
			if (core.hasSpecial(special, 28)) {
				var dragonInfo = core.getFlag('__dragonSlayerRevive__', null);
				var hasRushInfo = dragonInfo && dragonInfo.enemyId == enemyId && dragonInfo.floorId == core.status.floorId && dragonInfo.x == x && dragonInfo.y == y;
				if (hasRushInfo) {
					core.removeFlag('__dragonSlayerRevive__');
				} else if (core.control && core.control.controldata && core.control.controldata.getDragonSlayerBattleDir) {
					// 没有发动冲刺也要分裂：例如勇士飞到屠龙旁边后主动战斗。
					dragonInfo = {
						enemyId: enemyId,
						x: x,
						y: y,
						centerX: x,
						centerY: y,
						dir: core.control.controldata.getDragonSlayerBattleDir(x, y),
						floorId: core.status.floorId
					};
				}
				if (dragonInfo) {
					core.control.controldata.reviveDragonDescendants(enemyId, dragonInfo.centerX, dragonInfo.centerY, dragonInfo.floorId, enemyPointInfo, dragonInfo.dir);
				}
			}

			// 44 穿梭：击杀后传送到最近的同名怪物身上。
			// 录像只记录最终传送点；回放时跳过特效，只同步传送重合，不自动战斗。
			var shuttleTriggered = false;
			if (core.enemys.hasSpecial(special, 44) && x != null && y != null) {
				if (core.control && core.control.controldata && core.control.controldata.triggerMonsterShuttle) {
					shuttleTriggered = core.control.controldata.triggerMonsterShuttle(enemyId, x, y, core.status.floorId);
				}
			}

			// 穿梭会把勇士传送到最近同名怪物身上并与其重合；这里不要继续自动路线。
			if (shuttleTriggered) return;

			// 32 豹跃：击杀后立刻四选一，跳到该怪上下左右3格（无视障碍，可穿越边界）。
			// 如果豹跃触发，则清层检测放到玩家选择落点之后执行，避免选择框和自动上楼冲突。
			var leopardLeapTriggered = false;
			if (core.enemys.hasSpecial(special, 32) && x != null && y != null) {
				if (core.control && core.control.controldata && core.control.controldata.triggerLeopardLeap) {
					leopardLeapTriggered = core.control.controldata.triggerLeopardLeap(enemyId, x, y, core.status.floorId);
				}
			}

			// 豹跃需要等待玩家点击地图上的黄色落点框；不要继续自动路线，也不要立刻清层上楼。
			if (leopardLeapTriggered) return;

			// 清层规则：每次击杀任意怪物后检测。若本层已无怪物，则传送到下一层坐标(6,12)。
			if (core.control && core.control.controldata && core.control.controldata.checkFloorClearAndGoNext) {
				core.control.controldata.checkFloorClearAndGoNext(core.status.floorId);
			}

			// 如果已有事件正在处理中
			if (core.status.event.id == null)
				core.continueAutomaticRoute();
			else
				core.clearContinueAutomaticRoute();

		},
        "afterOpenDoor": function (doorId, x, y) {
			// 开一个门后触发的事件

			var todo = [];
			// 检查该点的开门后事件
			if (core.status.floorId) {
				core.push(todo, core.floors[core.status.floorId].afterOpenDoor[x + "," + y]);
			}
			// 检查批量开门事件
			var door = core.getBlockById(doorId);
			if (door && door.event.doorInfo) {
				core.push(todo, door.event.doorInfo.afterOpenDoor);
			}

			if (todo.length > 0) core.insertAction(todo, x, y);

			if (core.status.event.id == null)
				core.continueAutomaticRoute();
			else
				core.clearContinueAutomaticRoute();
		},
        "afterGetItem": function (itemId, x, y, isGentleClick) {
			// 获得一个道具后触发的事件
			// itemId：获得的道具ID；x和y是该道具所在的坐标
			// isGentleClick：是否是轻按触发的
			if (itemId.endsWith('Potion') && core.material.items[itemId].cls == 'items')
				core.playSound('回血');
			else if (itemId.endsWith('Gem') && core.material.items[itemId].cls == 'items')
				core.playSound('宝石')
			else
				core.playSound('获得道具');

			var todo = [];
			// 检查该点的获得道具后事件。
			if (core.status.floorId == null) return;
			var event = core.floors[core.status.floorId].afterGetItem[x + "," + y];
			if (event && (event instanceof Array || !isGentleClick || !event.disableOnGentleClick)) {
				if (event.data) event = event.data;
				core.unshift(todo, event);
			}

			if (todo.length > 0) core.insertAction(todo, x, y);
		},
        "afterPushBox": function () {
			// 推箱子后的事件
			if (core.searchBlock('box').length == 0) {
				// 可以通过if语句来进行开门操作
				/*
				if (core.status.floorId=='xxx') { // 在某个楼层
					core.insertAction([ // 插入一条事件
						{"type": "openDoor", "loc": [x,y]} // 开门
					])
				}
				*/
			}
		}
    },
    "enemys": {
        "getSpecials": function () {
			// 获得怪物的特殊属性，每一行定义一个特殊属性。
			// 分为五项，第一项为该特殊属性的数字，第二项为特殊属性的名字，第三项为特殊属性的描述
			// 第四项为该特殊属性的颜色，可以写十六进制 #RRGGBB 或者 [r,g,b,a] 四元数组
			// 第五项为该特殊属性的标记；目前 1 代表是地图类技能（需要进行遍历全图）
			// 名字和描述可以直接写字符串，也可以写个function将怪物传进去
			return [
				[1, "先攻", "怪物首先攻击", "#ffcc33"],
				[2, "魔攻", "怪物无视角色的防御", "#bbb0ff"],
				[3, "坚固", "怪物防御不小于角色攻击-1", "#c0b088"],
				[4, "2连击", "怪物每回合攻击2次", "#ffee77"],
				[5, "3连击", "怪物每回合攻击3次", "#ffee77"],
				[6, function (enemy) { return (enemy.n || '') + "连击"; }, function (enemy) { return "怪物每回合攻击" + (enemy.n || 4) + "次"; }, "#ffee77"],
				[7, "破甲", function (enemy) { return "战斗前，怪物附加角色防御的" + Math.floor(100 * (enemy.breakArmor || core.values.breakArmor || 0)) + "%作为伤害"; }, "#88c0ff"],
				[8, "反击", function (enemy) { return "战斗时，怪物每回合附加角色攻击的" + Math.floor(100 * (enemy.counterAttack || core.values.counterAttack || 0)) + "%作为伤害，无视角色防御"; }, "#ffaa44"],
				[9, "净化", function (enemy) { return "战斗前，怪物附加角色护盾的" + (enemy.purify || core.values.purify) + "倍作为伤害"; }, "#80eed6"],
				[10, "模仿", "怪物的攻防和角色攻防相等", "#b0c0dd"],
				[11, "吸血", function (enemy) { return "战斗前，怪物首先吸取角色的" + Math.floor(100 * enemy.vampire || 0) + "%生命（约" + Math.floor((enemy.vampire || 0) * core.getStatus('hp')) + "点）作为伤害" + (enemy.add ? "，并把伤害数值加到自身生命上" : ""); }, "#dd4448"],
				[12, "中毒", "战斗后，角色陷入中毒状态，每一步损失生命" + core.values.poisonDamage + "点", "#99ee88"],
				[13, "衰弱", "战斗后，角色陷入衰弱状态，攻防暂时下降" + (core.values.weakValue >= 1 ? core.values.weakValue + "点" : parseInt(core.values.weakValue * 100) + "%"), "#f0bbcc"],
				[14, "诅咒", "战斗后，角色陷入诅咒状态，战斗无法获得金币和经验", "#bbeef0"],
				[15, "领域", function (enemy) { return "经过怪物周围" + (enemy.zoneSquare ? "九宫格" : "十字") + "范围内" + (enemy.range || 1) + "格时自动减生命" + (enemy.zone || 0) + "点"; }, "#c677dd"],
				[16, "夹击", "经过两只相同的怪物中间，角色生命值变成一半", "#bb99ee"],
				[17, "仇恨", "战斗前，怪物附加之前积累的仇恨值作为伤害；战斗后，释放一半的仇恨值。（每杀死一个怪物获得" + (core.values.hatred || 0) + "点仇恨值）", "#b0b666"],
				[18, "阻击", function (enemy) { return "经过怪物周围" + (enemy.zoneSquare ? "九宫格" : "十字") + "时自动减生命" + (enemy.repulse || 0) + "点，同时怪物后退一格"; }, "#8888e6"],
				[19, "自爆", "战斗后角色的生命值变成1", "#ff6666"],
				[20, "无敌", "角色无法打败怪物，除非拥有十字架", "#aaaaaa"],
				[21, "退化", function (enemy) { return "战斗后角色永久下降" + (enemy.atkValue || 0) + "点攻击和" + (enemy.defValue || 0) + "点防御"; }],
				[22, "固伤", function (enemy) { return "战斗前，怪物对角色造成" + (enemy.damage || 0) + "点固定伤害，未开启负伤时无视角色护盾。"; }, "#ff9977"],
				[23, "重生", "怪物被击败后，角色转换楼层则怪物将再次出现", "#a0e0ff"],
				[24, "激光", function (enemy) { return "经过怪物同行或同列时自动减生命" + (enemy.laser || 0) + "点"; }, "#dda0dd"],
				[25, "光环", function (enemy) { return (enemy.range != null ? ((enemy.haloSquare ? "该怪物九宫格" : "该怪物十字") + enemy.haloRange + "格范围内") : "同楼层所有") + "怪物生命提升" + (enemy.hpBuff || 0) + "%，攻击提升" + (enemy.atkBuff || 0) + "%，防御提升" + (enemy.defBuff || 0) + "%，" + (enemy.haloAdd ? "可叠加" : "不可叠加"); }, "#e6e099", 1],
				[26, "支援", "当周围一圈的怪物受到攻击时将上前支援，并组成小队战斗。", "#77c0b6", 1],
				[27, "捕捉", function (enemy) { return "当走到怪物周围" + (enemy.zoneSquare ? "九宫格" : "十字") + "时会强制进行战斗。"; }, "#c0ddbb"],
				[28, "屠龙", "当勇士出现在该怪物同行或同列时，无视障碍冲刺到勇士面前并强制战斗；战败后会在左右方向的第一个空地各复活一只降龙怪。", "#ff8844", 1],
				[29, "降龙", "本层每有一只降龙怪，所有降龙怪生命、攻击各增加自身10%；每次增加向上取整，最低增加1点，可叠加；不增加防御。", "#88ddff", 1],
				[30, "灭霸", "与本怪物战斗时，每满3回合，本层所有降龙怪攻击增加10%（向上取整，最低增加1点）。", "#b06cff", 1],
				[31, "屠虎", "本层每击杀一只降龙怪，现存屠虎攻击增加10%（向上取整，最低增加1点）。", "#ff6655", 1],
				[32, "豹跃", "击杀后以它为中心，上下左右3格选择跳跃！", "#ffcc66", 1],
				[33, "血战", "当此怪物造成伤害比30低时，最终伤害平方化（血战触发时轮回无效）。", "#dc1428", 1],
				[34, "魂锁", "带有魂锁的所有怪物共享生命。", "#a96bff", 1],
				[35, "碎甲", "战斗后防御临时-1，轮回或者切换楼层恢复。", "#5a85b0", 1],
				[36, "破功", "战斗后攻击临时-1，轮回或者切换楼层恢复。", "#c95440", 1],
				[37, "共生", "连续击杀方可真正消灭。", "#3dc7a8", 1],
				[38, "吞噬", "地图上每死亡一个怪，吞噬其属性的5%增加自身（保底+1）。", "#a01b6a", 1],
				[39, "克飞", "玩家身上每拥有一个飞行符，怪物攻击额外+3。", "#56cfd4", 1],
				[40, "克跳", "玩家身上每拥有一个跳跃鞋，怪物攻击额外+3。", "#a8e860", 1],
				[41, "克金", "玩家每拥有 1 金币，怪物生命额外+1。", "#d4af37", 1],
				[42, "成长", "本层勇士每获得 1 攻击，怪物 +1 攻击。轮回不清除，切层清除。", "#5fc472", 1],
				[43, "寄生", "本层勇士每恢复 1 生命，怪物增加 1 生命。切层、轮回都清除。", "#c25fa3", 1],
				[44, "穿梭", "击杀后，勇士会传送到最近的同名怪物身上并与其重合，不立即战斗；距离按横向距离+纵向距离计算，若距离相同，优先选择横向距离更近者。", "#7ad7ff", 1],
				[45, "躲避", "勇士即将和此怪物战斗时，怪物会朝相邻空地躲开并增加20生命，本次战斗取消；躲避方向优先级为上、下、左、右。", "#9cff7a", 1],
				[46, "畏缩", "本层每死亡一个怪物，所有存活的畏缩怪物攻击、防御各减少1点；最低降到0。", "#9aa0ff", 1]
			];
		},
        "getEnemyInfo": function (enemy, hero, x, y, floorId) {
	// 获得某个怪物变化后的数据；该函数将被伤害计算和怪物手册使用
	// 例如：坚固、模仿、仿攻等等
	// 
	// 参数说明：
	// enemy：该怪物信息
	// hero_hp,hero_atk,hero_def,hero_mdef：勇士的生命攻防护盾数据
	// x,y：该怪物的坐标（查看手册和强制战斗时为undefined）
	// floorId：该怪物所在的楼层
	// 后面三个参数主要是可以在光环等效果上可以适用（也可以按需制作部分范围光环效果）
	floorId = floorId || core.status.floorId;
	var hero_hp = core.getRealStatusOrDefault(hero, 'hp'),
		hero_atk = core.getRealStatusOrDefault(hero, 'atk'),
		hero_def = core.getRealStatusOrDefault(hero, 'def'),
		hero_mdef = core.getRealStatusOrDefault(hero, 'mdef');

	var mon_hp = core.getEnemyValue(enemy, 'hp', x, y, floorId),
		mon_atk = core.getEnemyValue(enemy, 'atk', x, y, floorId),
		mon_def = core.getEnemyValue(enemy, 'def', x, y, floorId),
		mon_special = core.getEnemyValue(enemy, 'special', x, y, floorId);
	var mon_money = core.getEnemyValue(enemy, 'money', x, y, floorId),
		mon_exp = core.getEnemyValue(enemy, 'exp', x, y, floorId),
		mon_point = core.getEnemyValue(enemy, 'point', x, y, floorId);
	// 模仿
	if (core.hasSpecial(mon_special, 10)) {
		mon_atk = hero_atk;
		mon_def = hero_def;
	}
	// 坚固
	if (core.hasSpecial(mon_special, 3) && mon_def < hero_atk - 1) {
		mon_def = hero_atk - 1;
	}

	var guards = [];

	// 光环和支援检查
	if (!core.status.checkBlock) core.status.checkBlock = {};

	if (core.status.checkBlock.needCache) {
		// 从V2.5.4开始，对光环效果增加缓存，以解决多次重复计算的问题，从而大幅提升运行效率。
		var hp_buff = 0,
			atk_buff = 0,
			def_buff = 0;
		// 已经计算过的光环怪ID列表，用于判定叠加
		var usedEnemyIds = {};
		// 检查光环和支援的缓存
		var index = x != null && y != null ? (x + "," + y) : floorId;
		if (!core.status.checkBlock.cache) core.status.checkBlock.cache = {};
		var cache = core.status.checkBlock.cache[index];
		if (!cache) {
			// 没有该点的缓存，则遍历每个图块
			core.extractBlocks(floorId);
			core.status.maps[floorId].blocks.forEach(function (block) {
				if (!block.disable) {
					// 获得该图块的ID
					var id = block.event.id,
						enemy = core.material.enemys[id];
					// 检查【光环】技能，数字25
					if (enemy && core.hasSpecial(enemy.special, 25)) {
						// 检查是否是范围光环
						var inRange = enemy.haloRange == null;
						if (enemy.haloRange != null && x != null && y != null) {
							var dx = Math.abs(block.x - x),
								dy = Math.abs(block.y - y);
							// 检查十字和九宫格光环
							if (dx + dy <= enemy.haloRange) inRange = true;
							if (enemy.haloSquare && dx <= enemy.haloRange && dy <= enemy.haloRange) inRange = true;
						}
						// 检查是否可叠加
						if (inRange && (enemy.haloAdd || !usedEnemyIds[enemy.id])) {
							hp_buff += enemy.hpBuff || 0;
							atk_buff += enemy.atkBuff || 0;
							def_buff += enemy.defBuff || 0;
							usedEnemyIds[enemy.id] = true;
						}
					}
					// 检查【支援】技能，数字26
					if (enemy && core.hasSpecial(enemy.special, 26) &&
						// 检查支援条件，坐标存在，距离为1，且不能是自己
						// 其他类型的支援怪，比如十字之类的话.... 看着做是一样的
						x != null && y != null && Math.abs(block.x - x) <= 1 && Math.abs(block.y - y) <= 1 && !(x == block.x && y == block.y)) {
						// 记录怪物的x,y，ID
						guards.push([block.x, block.y, id]);
					}

					// TODO：如果有其他类型光环怪物在这里仿照添加检查
					// 注：新增新的类光环属性（需要遍历全图的）需要在特殊属性定义那里的第五项写1，参见光环和支援的特殊属性定义。
				}
			});

			core.status.checkBlock.cache[index] = { "hp_buff": hp_buff, "atk_buff": atk_buff, "def_buff": def_buff, "guards": guards };
		} else {
			// 直接使用缓存数据
			hp_buff = cache.hp_buff;
			atk_buff = cache.atk_buff;
			def_buff = cache.def_buff;
			guards = cache.guards;
		}

		// 增加比例；如果要增加数值可以直接在这里修改
		mon_hp *= (1 + hp_buff / 100);
		mon_atk *= (1 + atk_buff / 100);
		mon_def *= (1 + def_buff / 100);
	}

	// 29号特殊属性：降龙
	// 本层地图每有一个“降龙”怪，所有降龙怪的生命、攻击各增加自身10%；不增加防御。
	// 关键：每一只降龙怪都单独提供一次加成；每次加成向上取整且最低+1。
	// 例如原始生命/攻击/防御为1/1/1，本层4只降龙，则变成5/5/1。
	// 说明：这里放在getEnemyInfo里处理，因此怪物手册、地图显伤和实际战斗都会使用增强后的数值。
	if (core.hasSpecial(mon_special, 29)) {
		var dragonCount = 0;
		if (core.control && core.control.controldata && core.control.controldata.countDragonDescendants) {
			dragonCount = core.control.controldata.countDragonDescendants(floorId);
		}
		if (dragonCount > 0) {
			var addHp = Math.max(1, Math.ceil(mon_hp * 0.1));
			var addAtk = Math.max(1, Math.ceil(mon_atk * 0.1));
			mon_hp = Math.max(1, Math.ceil(mon_hp + dragonCount * addHp));
			mon_atk = Math.max(1, Math.ceil(mon_atk + dragonCount * addAtk));
			// 注意：降龙不修改 mon_def，防御保持原值。
		}
	}

	// 34号特殊属性：魂锁
	// 本层每多一只魂锁怪，所有魂锁怪生命 × N（N=本层魂锁怪总数）。
	// 实际效果：3只各30HP的魂锁怪 → 每只战斗时按 90HP 计算。
	// 击杀一只后只它自己消失，其他魂锁怪在下一次战斗时按 N-1 倍计算。
	// 不影响攻击/防御。
	if (core.hasSpecial(mon_special, 34)) {
		var soulCount = 0;
		if (core.control && core.control.controldata && core.control.controldata.countSoulLockEnemies) {
			soulCount = core.control.controldata.countSoulLockEnemies(floorId);
		}
		if (soulCount > 1) {
			mon_hp = Math.max(1, Math.ceil(mon_hp * soulCount));
		}
	}

	// 38号特殊属性：吞噬
	// 本层每死亡一只怪，所有活着的吞噬怪获得 死亡怪 5% HP/ATK/DEF（保底+1）
	// 加成累计存储于 __devourBonus__ flag，本函数读取并加到当前数值上。
	if (core.hasSpecial(mon_special, 38) && x != null && y != null) {
		if (core.control && core.control.controldata && core.control.controldata.getDevourBonus) {
			var devourBonus = core.control.controldata.getDevourBonus(floorId, x, y);
			if (devourBonus) {
				mon_hp  = Math.max(1, mon_hp  + (devourBonus.hp  || 0));
				mon_atk = Math.max(0, mon_atk + (devourBonus.atk || 0));
				mon_def = Math.max(0, mon_def + (devourBonus.def || 0));
			}
		}
	}

	// 39号特殊属性：克飞
	// 玩家身上每拥有一个飞行符（centerFly + upFly + downFly + fly），攻击+3
	if (core.hasSpecial(mon_special, 39)) {
		var flyCount = (core.itemCount('centerFly') || 0)
			+ (core.itemCount('upFly') || 0)
			+ (core.itemCount('downFly') || 0)
			+ (core.itemCount('fly') || 0);
		mon_atk += 3 * flyCount;
	}

	// 40号特殊属性：克跳
	// 玩家身上每拥有一个跳跃鞋(jumpShoes)，攻击+3
	if (core.hasSpecial(mon_special, 40)) {
		var jumpCount = core.itemCount('jumpShoes') || 0;
		mon_atk += 3 * jumpCount;
	}

	// 41号特殊属性：克金
	// 玩家每拥有 1 金币，生命+1
	if (core.hasSpecial(mon_special, 41)) {
		var goldCount = (core.status.hero && core.status.hero.money) || 0;
		mon_hp += goldCount;
	}

	// 42号特殊属性：成长
	// 本层勇士每获得 1 攻击，该怪物 +1 攻击（1:1，不影响防御）
	// 累计器：__growthAccum__ = N（单值，仅 atk）
	// 切楼层清空（在 afterChangeFloor），轮回保留（resetFloorToInitialForRevive 不删）
	if (core.hasSpecial(mon_special, 42)) {
		var growth = core.getFlag('__growthAccum__', 0);
		mon_atk += growth;
	}

	// 43号特殊属性：寄生
	// 本层勇士每恢复 1 生命，该怪物 +1 生命（1:1）
	// 累计器：__parasiteAccum__ = N
	// 切楼层清空（afterChangeFloor）；轮回也清空（resetFloorToInitialForRevive）
	if (core.hasSpecial(mon_special, 43)) {
		var parasite = core.getFlag('__parasiteAccum__', 0);
		mon_hp += parasite;
	}

	// 30灭霸、31屠虎等造成的临时攻击增加。
	// 这个加成按坐标保存，因此手册、地图显伤和实际战斗都会显示增加后的攻击。
	if (x != null && y != null && core.control && core.control.controldata && core.control.controldata.getEnemyAtkBonus) {
		mon_atk += core.control.controldata.getEnemyAtkBonus(floorId, x, y);
	}

	// TODO：可以在这里新增其他的怪物数据变化
	// 比如仿攻（怪物攻击不低于勇士攻击）：
	// if (core.hasSpecial(mon_special, 27) && mon_atk < hero_atk) {
	//     mon_atk = hero_atk;
	// }
	// 也可以按需增加各种自定义内容

	return {
		"hp": Math.floor(mon_hp),
		"atk": Math.floor(mon_atk),
		"def": Math.floor(mon_def),
		"money": Math.floor(mon_money),
		"exp": Math.floor(mon_exp),
		"point": Math.floor(mon_point),
		"special": mon_special,
		"guards": guards, // 返回支援情况
	};
},
        "getDamageInfo": function (enemy, hero, x, y, floorId) {
			// 获得战斗伤害信息（实际伤害计算函数）
			// 
			// 参数说明：
			// enemy：该怪物信息
			// hero：勇士的当前数据；如果对应项不存在则会从core.status.hero中取。
			// x,y：该怪物的坐标（查看手册和强制战斗时为undefined）
			// floorId：该怪物所在的楼层
			// 后面三个参数主要是可以在光环等效果上可以适用
			floorId = floorId || core.status.floorId;

			var hero_hp = core.getRealStatusOrDefault(hero, 'hp'),
				hero_atk = core.getRealStatusOrDefault(hero, 'atk'),
				hero_def = core.getRealStatusOrDefault(hero, 'def'),
				hero_mdef = core.getRealStatusOrDefault(hero, 'mdef'),
				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);
			hero_mdef = Math.max(0, hero_mdef);

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

			// 技能的处理
			if (core.getFlag('skill', 0) == 1) { // 开启了技能1：二倍斩
				hero_atk *= 2; // 计算时攻击力翻倍	
			}

			// 如果是无敌属性，且勇士未持有十字架
			if (core.hasSpecial(mon_special, 20) && !core.hasItem("cross"))
				return null; // 不可战斗

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

			// 吸血
			if (core.hasSpecial(mon_special, 11)) {
				var vampire_damage = hero_hp * enemy.vampire;

				// 如果有神圣盾免疫吸血等可以在这里写
				// 也可以用hasItem和hasEquip来判定装备
				// if (core.hasFlag('shield5')) vampire_damage = 0;

				vampire_damage = Math.floor(vampire_damage) || 0;
				// 加到自身
				if (enemy.add) // 如果加到自身
					mon_hp += vampire_damage;

				init_damage += vampire_damage;
			}

			// 每回合怪物对勇士造成的战斗伤害
			var per_damage = mon_atk - hero_def;
			// 魔攻：战斗伤害就是怪物攻击力
			if (core.hasSpecial(mon_special, 2)) per_damage = mon_atk;
			// 战斗伤害不能为负值
			if (per_damage < 0) per_damage = 0;

			// 2连击 & 3连击 & N连击
			if (core.hasSpecial(mon_special, 4)) per_damage *= 2;
			if (core.hasSpecial(mon_special, 5)) per_damage *= 3;
			if (core.hasSpecial(mon_special, 6)) per_damage *= (enemy.n || 4);

			// 每回合的反击伤害；反击是按照勇士的攻击次数来计算回合
			var counterDamage = 0;
			if (core.hasSpecial(mon_special, 8))
				counterDamage += Math.floor((enemy.counterAttack || core.values.counterAttack) * hero_atk);

			// 先攻
			if (core.hasSpecial(mon_special, 1)) init_damage += per_damage;

			// 破甲
			if (core.hasSpecial(mon_special, 7))
				init_damage += Math.floor((enemy.breakArmor || core.values.breakArmor) * hero_def);

			// 净化
			if (core.hasSpecial(mon_special, 9))
				init_damage += Math.floor((enemy.purify || core.values.purify) * hero_mdef);

			// 勇士每回合对怪物造成的伤害
			var hero_per_damage = Math.max(hero_atk - mon_def, 0);

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

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

			// ------ 支援 ----- //
			// 这个递归最好想明白为什么，flag:__extraTurn__是怎么用的
			var guards = core.getFlag("__guards__" + x + "_" + y, enemyInfo.guards);
			var guard_before_current_enemy = false; // ------ 支援怪是先打(true)还是后打(false)？
			turn += core.getFlag("__extraTurn__", 0);
			if (guards.length > 0) {
				if (!guard_before_current_enemy) { // --- 先打当前怪物，记录当前回合数
					core.setFlag("__extraTurn__", turn);
				}
				// 获得那些怪物组成小队战斗
				for (var i = 0; i < guards.length; i++) {
					var gx = guards[i][0],
						gy = guards[i][1],
						gid = guards[i][2];
					// 递归计算支援怪伤害信息，这里不传x,y保证不会重复调用
					// 这里的mdef传0，因为护盾应该只会被计算一次
					var info = core.enemys.getDamageInfo(core.material.enemys[gid], { hp: origin_hero_hp, atk: origin_hero_atk, def: origin_hero_def, mdef: 0 });
					if (info == null) { // 小队中任何一个怪物不可战斗，直接返回null
						core.removeFlag("__extraTurn__");
						return null;
					}
					// 已经进行的回合数
					core.setFlag("__extraTurn__", info.turn);
					init_damage += info.damage;
				}
				if (guard_before_current_enemy) { // --- 先打支援怪物，增加当前回合数
					turn += core.getFlag("__extraTurn__", 0);
				}
			}
			core.removeFlag("__extraTurn__");
			// ------ 支援END ------ //

			// 最终伤害：初始伤害 + 怪物对勇士造成的伤害 + 反击伤害
			var damage = init_damage + (turn - 1) * per_damage + turn * counterDamage;
			// 再扣去护盾
			damage -= hero_mdef;

			// 检查是否允许负伤
			if (!core.flags.enableNegativeDamage)
				damage = Math.max(0, damage);

			// 先处理仇恨和固伤（这两个不能被护盾减伤）
			if (core.hasSpecial(mon_special, 17)) { // 仇恨
				damage += core.getFlag('hatred', 0);
			}
			if (core.hasSpecial(mon_special, 22)) { // 固伤
				damage += enemy.damage || 0;
			}

			// 血战 (33)：若【最终伤害】< 30，则平方化（spec 用"最终伤害"做判定）
			// 必须在 仇恨/固伤 加完之后做，因为这些会影响最终伤害
			// 双重检查：先看 mon_special；如果不含 33，再看 enemy 基础配置（防止 enemyOnPoint 异常覆盖）
			var bloodWarTriggered = false;
			var hasBloodWar = core.hasSpecial(mon_special, 33);
			if (!hasBloodWar && enemy && enemy.special != null) {
				// fallback：基础配置上是否标了血战？
				// 此处不应该走到——除非 enemyOnPoint[x,y].special 异常覆盖了 33
				hasBloodWar = core.hasSpecial(enemy.special, 33);
				if (hasBloodWar) {
					console.warn('[血战] mon_special 不含 33 但 enemy.special 含——可能 enemyOnPoint 异常覆盖', {
						x: x, y: y, mon_special: mon_special, enemy_special: enemy.special,
						floorId: floorId, override: ((flags.enemyOnPoint || {})[floorId] || {})[x + ',' + y]
					});
				}
			}
			if (hasBloodWar && damage < 30 && damage > 0) {
				damage = damage * damage;
				bloodWarTriggered = true;
			}

			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),
				"bloodWarTriggered": bloodWarTriggered
			};
		}
    },
    "actions": {
        "onKeyUp": function (keyCode, altKey) {
			// 键盘按键处理，可以在这里自定义快捷键列表
			// keyCode：当前按键的keyCode（每个键的keyCode自行百度）
			// altKey：Alt键是否被按下，为true代表同时按下了Alt键
			// 可以在这里任意增加或编辑每个按键的行为

			// 如果处于正在行走状态，则不处理
			if (core.isMoving()) return;

			// 商店长按时忽略
			if (core.status.onShopLongDown) return core.status.onShopLongDown = false;

			// Alt+0~9，快捷换上套装
			if (altKey && keyCode >= 48 && keyCode <= 57) {
				core.items.quickLoadEquip(keyCode - 48);
				return;
			}

			// 根据keyCode值来执行对应操作
			switch (keyCode) {
				case 27: // ESC：打开菜单栏
					core.openSettings(true);
					break;
				case 88: // X：使用怪物手册
					core.openBook(true);
					break;
				case 71: // G：使用楼传器
					core.useFly(true);
					break;
				case 65: // A：读取自动存档（回退）
					core.doSL("autoSave", "load");
					break;
				case 87: // W：撤销回退
					core.doSL("autoSave", "reload");
					break;
				case 83: // S：存档
					core.save(true);
					break;
				case 68: // D：读档
					core.load(true);
					break;
				case 69: // E：打开光标
					core.ui._drawCursor();
					break;
				case 84: // T：打开道具栏
					core.openToolbox(true);
					break;
				case 81: // Q：打开装备栏
					core.openEquipbox(true);
					break;
				case 90: // Z：转向
					core.turnHero();
					break;
				case 86: // V：打开快捷商店列表
					core.openQuickShop(true);
					break;
				case 32: // SPACE：轻按
					core.getNextItem();
					break;
				case 82: // R：回放录像
					core.ui._drawReplay();
					break;
				case 33:
				case 34: // PgUp/PgDn：浏览地图
					core.ui._drawViewMaps();
					break;
				case 66: // B：打开数据统计
					core.ui._drawStatistics();
					break;
				case 72: // H：打开帮助页面
					core.ui._drawHelp();
					break;
				case 77: // M：打开存档笔记
					core.actions._clickNotes_show();
					break;
				case 78: // N：重新开始
					core.confirmRestart();
					break;
				case 79: // O：查看工程
					core.actions._clickGameInfo_openProject();
					break;
				case 80: // P：游戏主页
					core.actions._clickGameInfo_openComments();
					break;
				case 49: // 快捷键1: 破
					if (core.hasItem('pickaxe')) {
						core.status.route.push("key:49"); // 将按键记在录像中
						core.useItem('pickaxe', true); // 第二个参数true代表该次使用道具是被按键触发的，使用过程不计入录像
					}
					break;
				case 50: // 快捷键2: 炸
					if (core.hasItem('bomb')) {
						core.status.route.push("key:50"); // 将按键记在录像中
						core.useItem('bomb', true); // 第二个参数true代表该次使用道具是被按键触发的，使用过程不计入录像
					}
					break;
				case 51: // 快捷键3: 飞
					if (core.hasItem('centerFly')) {
						core.ui._drawCenterFly();
					}
					break;
				case 52: // 快捷键4：破冰/冰冻/地震/上下楼器/... 其他道具依次判断
					{
						var list = ["icePickaxe", "freezeBadge", "earthquake", "upFly", "downFly", "jumpShoes", "lifeWand", "poisonWine", "weakWine", "curseWine", "superWine"];
						for (var i = 0; i < list.length; i++) {
							var itemId = list[i];
							if (core.canUseItem(itemId)) {
								core.status.route.push("key:52");
								core.useItem(itemId, true);
								break;
							}
						}
					}
					break;
				case 53: // 5：读取自动存档（回退），方便手机版操作
					core.doSL("autoSave", "load");
					break;
				case 54: // 6：撤销回退，方便手机版操作
					core.doSL("autoSave", "reload");
					break;
				case 55: // 快捷键7：绑定为轻按，方便手机版操作
					core.getNextItem();
					break;
				case 118: // F7：开启debug模式
				case 119: // F8：由于F7与部分浏览器有冲突，故新增F8
					core.debug();
					break;
				case 70: // F：开启技能“二倍斩”
					// 检测是否拥有“二倍斩”这个技能道具
					if (core.hasItem('skill1')) {
						core.status.route.push("key:70");
						core.useItem('skill1', true);
					}
					break;
				// 在这里可以任意新增或编辑已有的快捷键内容
				/*
				case 0: // 使用该按键的keyCode
					// 还可以再判定altKey是否被按下，即 if (altKey) { ...
		
					// ... 在这里写你要执行脚本
					// **强烈建议所有新增的自定义快捷键均能给个对应的道具可点击，以方便手机端的行为**
					if (core.hasItem('...')) {
						core.status.route.push("key:0");
						core.useItem('...', true); // 增加true代表该使用道具不计入录像
					}
		
					break;
				*/
			}

		},
        "onStatusBarClick": function (px, py, vertical) {
			// 点击状态栏时触发的事件，仅在自绘状态栏开启时生效
			// px和py为点击的像素坐标
			// vertical为录像播放过程中的横竖屏信息
			// 
			// 横屏模式下状态栏的画布大小是 129*416 （开启拓展装备栏后是 129*457）
			// 竖屏模式下状态栏的画布大小是 416*(32*rows+9) 其中rows为状态栏行数，即全塔属性中statusCanvasRowsOnMobile值
			// 可以使用 _isVertical() 来判定当前是否是竖屏模式

			// 判定当前是否是竖屏模式。录像播放过程中可能会记录当时的横竖屏信息以覆盖。
			var _isVertical = function () {
				if (core.isReplaying() && vertical != null) return vertical;
				return core.domStyle.isVertical;
			}

			// 如果正在执行事件，则忽略
			if (core.status.lockControl) return;
			// 如果当前正在行走，则忽略；也可以使用 core.waitHeroToStop(callback) 来停止行走再回调执行脚本
			if (core.isMoving()) return;

			// 判定px和py来执行自己的脚本内容.... 注意横竖屏
			// console.log("onStatusBarClick: ", px, py, _isVertical());

			// 样例一：点击某个区域后使用一个道具
			/*
			if (core.hasItem("pickaxe")) {
				if (_isVertical()) {
					// 竖屏模式下
					if (px >= 200 && px <= 250 && py >= 50 && py <= 100) {
						core.useItem("pickaxe");
					}
				} else {
					// 横屏模式下
					if (px >= 50 && px <= 100 && py >= 200 && py <= 250) {
						core.useItem("pickaxe");
					}
				}
			}
			*/

			// 样例二：点击某个区域后执行一段公共事件或脚本
			/*
			if (core.hasFlag("xxx")) {
				if (_isVertical()) {
					// 竖屏模式下
					if (px >= 200 && px <= 250 && py >= 50 && py <= 100) {
						// 记录点击坐标。这里的1代表此时是竖屏！
						core.status.route.push("click:1:" + px + ":" + py);
						// 可以插入公共事件 / 普通事件 / 执行一段脚本（如打开自绘UI或增减flag）
						core.insertCommonEvent("道具商店");
						// core.insertAction(["一段事件"]);
						// core.openItemShop("shop1");
					}
				} else {
					// 横屏模式下
					if (px >= 50 && px <= 100 && py >= 200 && py <= 250) {
						// 记录点击坐标。这里的0代表此时是横屏！
						core.status.route.push("click:0:" + px + ":" + py);
						// 可以插入公共事件 / 普通事件 / 执行一段脚本（如打开自绘UI或增减flag）
						core.insertCommonEvent("道具商店");
						// core.insertAction(["一段事件"]);
						// core.openItemShop("shop1");
					}
				}
			}
			*/

		}
    },
    "control": {
        "saveData": function () {
			// 存档操作，此函数应该返回“具体要存档的内容”

			// 差异化存储values
			var values = {};
			for (var key in core.values) {
				if (!core.same(core.values[key], core.data.values[key]))
					values[key] = core.clone(core.values[key]);
			}

			// 要存档的内容
			var data = {
				'floorId': core.status.floorId,
				'hero': core.clone(core.status.hero),
				'hard': core.status.hard,
				'maps': core.clone(core.maps.saveMap()),
				'route': core.encodeRoute(core.status.route),
				'values': values,
				'version': core.firstData.version,
				'guid': core.getGuid(),
				"time": new Date().getTime()
			};

			return data;
		},
        "loadData": function (data, callback) {
			// 读档操作；从存储中读取了内容后的行为

			// 重置游戏和路线
			core.resetGame(data.hero, data.hard, data.floorId, core.maps.loadMap(data.maps, null, data.hero.flags), data.values);
			core.status.route = core.decodeRoute(data.route);
			core.control._bindRoutePush();
			// 文字属性，全局属性
			core.status.textAttribute = core.getFlag('textAttribute', core.status.textAttribute);
			var toAttribute = core.getFlag('globalAttribute', core.status.globalAttribute);
			if (!core.same(toAttribute, core.status.globalAttribute)) {
				core.status.globalAttribute = toAttribute;
				core.resize();
			}
			// 重置音量
			core.events.setVolume(core.getFlag("__volume__", 1), 0);
			// 加载勇士图标
			var icon = core.status.hero.image;
			icon = core.getMappedName(icon);
			if (core.material.images.images[icon]) {
				core.material.images.hero = core.material.images.images[icon];
				core.material.icons.hero.width = core.material.images.images[icon].width / 4;
				core.material.icons.hero.height = core.material.images.images[icon].height / 4;
			}
			core.setFlag('__fromLoad__', true);

			// TODO：增加自己的一些读档处理

			// 切换到对应的楼层
			core.changeFloor(data.floorId, null, data.hero.loc, 0, function () {
				// TODO：可以在这里设置读档后播放BGM
				if (core.hasFlag("__bgm__")) { // 持续播放
					core.playBgm(core.getFlag("__bgm__"));
				}

				core.removeFlag('__fromLoad__');
				if (callback) callback();
			});
		},
        "getStatusLabel": function (name) {
			// 返回某个状态英文名的对应中文标签，如atk -> 攻击，def -> 防御等。
			// 请注意此项仅影响 libs/ 下的内容（如绘制怪物手册、数据统计等）
			// 自行定义的（比如获得道具效果）中用到的“攻击+3”等需要自己去对应地方修改

			return {
				name: "名称",
				lv: "等级",
				hpmax: "生命上限",
				hp: "生命",
				manamax: "魔力上限",
				mana: "魔力",
				atk: "攻击",
				def: "防御",
				mdef: "护盾",
				money: "金币",
				exp: "经验",
				point: "加点",
				steps: "步数",
			}[name] || name;
		},
        "triggerDebuff": function (action, type) {
			// 毒衰咒效果的获得与解除
			// action：获得还是解除；'get'表示获得，'remove'表示解除
			// type：一个数组表示获得了哪些毒衰咒效果；poison, weak，curse
			if (!(type instanceof Array)) type = [type];

			if (action == 'get') {
				if (core.inArray(type, 'poison') && !core.hasFlag("poison")) {
					// 获得毒效果
					core.setFlag('poison', true);
				}
				if (core.inArray(type, 'weak') && !core.hasFlag('weak')) {
					// 获得衰效果
					core.setFlag('weak', true);
					if (core.values.weakValue >= 1) {
						// >=1，直接扣数值
						core.addStatus('atk', -core.values.weakValue);
						core.addStatus('def', -core.values.weakValue);
					} else {
						// <1，扣比例
						core.addBuff('atk', -core.values.weakValue);
						core.addBuff('def', -core.values.weakValue);
					}
				}
				if (core.inArray(type, 'curse') && !core.hasFlag('curse')) {
					// 获得咒效果
					core.setFlag('curse', true);
				}
			} else if (action == 'remove') {
				var success = false;
				if (core.inArray(type, "poison") && core.hasFlag("poison")) {
					success = true;
					// 移除毒效果
					core.setFlag("poison", false);
				}
				if (core.inArray(type, "weak") && core.hasFlag("weak")) {
					success = true;
					// 移除衰效果
					core.setFlag("weak", false);
					if (core.values.weakValue >= 1) {
						// >=1，直接扣数值
						core.addStatus('atk', core.values.weakValue);
						core.addStatus('def', core.values.weakValue);
					} else {
						// <1，扣比例
						core.addBuff('atk', core.values.weakValue);
						core.addBuff('def', core.values.weakValue);
					}
				}
				if (core.inArray(type, "curse") && core.hasFlag("curse")) {
					success = true;
					// 移除咒效果
					core.setFlag("curse", false);
				}
				if (success) core.playSound('回血');
			}
		},
        "updateStatusBar": function () {
			// 更新状态栏

			// 检查等级
			core.events.checkLvUp();

			// 检查HP上限
			if (core.flags.statusBarItems.indexOf('enableHPMax') >= 0) {
				core.setStatus('hp', Math.min(core.getRealStatus('hpmax'), core.getStatus('hp')));
			}

			// 设置楼层名
			if (core.status.floorId) {
				core.setStatusBarInnerHTML('floor', core.status.maps[core.status.floorId].name);
			}

			// 设置勇士名字和图标
			core.setStatusBarInnerHTML('name', core.getStatus('name'));
			// 设置等级名称
			core.setStatusBarInnerHTML('lv', core.getLvName());

			// 设置生命上限、生命值、攻防护盾金币和经验值
			var statusList = ['hpmax', 'hp', 'mana', 'atk', 'def', 'mdef', 'money', 'exp'];
			statusList.forEach(function (item) {
				// 向下取整
				core.status.hero[item] = Math.floor(core.status.hero[item]);
				// 大数据格式化
				core.setStatusBarInnerHTML(item, core.getRealStatus(item));
			});

			// 设置魔力值; status:manamax 只有在非负时才生效。
			if (core.status.hero.manamax != null && core.getRealStatus('manamax') >= 0) {
				core.status.hero.mana = Math.min(core.status.hero.mana, core.getRealStatus('manamax'));
				core.setStatusBarInnerHTML('mana', core.status.hero.mana + "/" + core.getRealStatus('manamax'));
			} else {
				core.setStatusBarInnerHTML("mana", core.status.hero.mana);
			}
			// 设置技能栏
			// 可以用flag:skill表示当前开启的技能类型，flag:skillName显示技能名；详见文档-个性化-技能塔的支持
			core.setStatusBarInnerHTML('skill', core.getFlag('skillName', '无'));

			// 可以在这里添加自己额外的状态栏信息，比如想攻击显示 +0.5 可以这么写：
			// if (core.hasFlag('halfAtk')) core.setStatusBarInnerHTML('atk', core.statusBar.atk.innerText + "+0.5");

			// 如果是自定义添加的状态栏，也需要在这里进行设置显示的数值

			// 进阶
			if (core.flags.statusBarItems.indexOf('enableLevelUp') >= 0) {
				core.setStatusBarInnerHTML('up', core.formatBigNumber(core.getNextLvUpNeed()) || "");
			} else core.setStatusBarInnerHTML('up', "");

			// 钥匙
			var keys = ['yellowKey', 'blueKey', 'redKey', 'greenKey'];
			keys.forEach(function (key) {
				core.setStatusBarInnerHTML(key, core.setTwoDigits(core.itemCount(key)));
			});
			// 毒衰咒
			core.setStatusBarInnerHTML('poison', core.hasFlag('poison') ? "毒" : "");
			core.setStatusBarInnerHTML('weak', core.hasFlag('weak') ? "衰" : "");
			core.setStatusBarInnerHTML('curse', core.hasFlag('curse') ? "咒" : "");
			// 破炸飞
			core.setStatusBarInnerHTML('pickaxe', "破" + core.itemCount('pickaxe'));
			core.setStatusBarInnerHTML('bomb', "炸" + core.itemCount('bomb'));
			core.setStatusBarInnerHTML('fly', "飞" + core.itemCount('centerFly'));

			// 难度
			if (core.statusBar.hard.innerText != core.status.hard) {
				core.statusBar.hard.innerText = core.status.hard;
			}
			var hardColor = core.getFlag('__hardColor__');
			if (hardColor == null) core.statusBar.hard.innerText = '';
			if (core.statusBar.hard.getAttribute('_style') != hardColor) {
				core.statusBar.hard.style.color = hardColor;
				core.statusBar.hard.setAttribute('_style', hardColor);
			}
			// 自定义状态栏绘制
			core.drawStatusBar();

			// 更新阻激夹域的伤害值
			core.updateCheckBlock();
			// updateDamage只能在此处执行！！更新全地图显伤
			core.updateDamage();
		},
        "installDragonSplitMarkRenderer": function () {
			// 给“屠龙分裂出来的降龙怪”绘制头顶“分”字标记。
			// 只包裹一次；core.drawBlock 会转发到 core.maps.drawBlock，因此这里包裹 maps.drawBlock 即可。
			if (core.__dragonSplitMarkRendererInstalled) return;
			core.__dragonSplitMarkRendererInstalled = true;
			var originDrawBlock = core.maps.drawBlock;
			core.maps.drawBlock = function (block, animate, ctx) {
				var result = originDrawBlock.apply(this, arguments);
				try {
					if (core.control && core.control.controldata && core.control.controldata.drawDragonSplitMark) {
						core.control.controldata.drawDragonSplitMark(block, ctx);
					}
					if (core.control && core.control.controldata && core.control.controldata.drawEnemyAttackBonusMark) {
						core.control.controldata.drawEnemyAttackBonusMark(block, ctx);
					}
				} catch (e) {
					console.error(e);
				}
				return result;
			};
		},
        "drawDragonSplitMark": function (block, ctx) {
			if (!block || !block.event || block.disable || block.name) return;
			var floorId = core.status.floorId;
			if (!floorId) return;
			var enemy = core.material.enemys[block.event.id];
			if (!enemy) return;

			// 只给屠龙分裂出来的降龙怪显示“分”，普通降龙怪不显示。
			var isSplit = core.getEnemyValue(enemy, 'dragonSplit', block.x, block.y, floorId);
			if (!isSplit) return;

			var drawCtx = core.getContextByName(ctx || 'event');
			if (!drawCtx) return;
			var px = 32 * (block.x - core.bigmap.posX);
			var py = 32 * (block.y - core.bigmap.posY);
			if (px < -32 || py < -32 || px > core._PX_ || py > core._PY_) return;

			drawCtx.save();
			// 小红牌，不额外占格，放在怪物格子上方内部。
			drawCtx.fillStyle = 'rgba(210, 58, 58, 0.92)';
			drawCtx.fillRect(px + 9, py + 1, 14, 14);
			drawCtx.strokeStyle = 'rgba(255, 245, 210, 0.95)';
			drawCtx.lineWidth = 1;
			drawCtx.strokeRect(px + 9.5, py + 1.5, 13, 13);
			drawCtx.font = 'bold 13px Arial, sans-serif';
			drawCtx.textAlign = 'center';
			drawCtx.textBaseline = 'middle';
			drawCtx.fillStyle = '#fff4c8';
			drawCtx.fillText('分', px + 16, py + 8);
			drawCtx.restore();
		},
        "drawEnemyAttackBonusMark": function (block, ctx) {
			// 给攻击已经临时增加的怪物，在右上角显示 +数值。
			if (!block || !block.event || block.disable || block.name) return;
			var floorId = core.status.floorId;
			if (!floorId) return;
			var enemy = core.material.enemys[block.event.id];
			if (!enemy) return;
			var bonus = this.getEnemyAtkBonus(floorId, block.x, block.y);
			if (!bonus) return;

			var drawCtx = core.getContextByName(ctx || 'event');
			if (!drawCtx) return;
			var px = 32 * (block.x - core.bigmap.posX);
			var py = 32 * (block.y - core.bigmap.posY);
			if (px < -32 || py < -32 || px > core._PX_ || py > core._PY_) return;

			drawCtx.save();
			drawCtx.fillStyle = 'rgba(66, 145, 255, 0.92)';
			drawCtx.strokeStyle = 'rgba(255, 250, 210, 0.95)';
			drawCtx.lineWidth = 1;
			var text = '+' + bonus;
			drawCtx.font = 'bold 10px Arial, sans-serif';
			var tw = Math.max(14, Math.ceil(drawCtx.measureText(text).width) + 4);
			drawCtx.fillRect(px + 31 - tw, py + 1, tw, 12);
			drawCtx.strokeRect(px + 31 - tw + 0.5, py + 1.5, tw - 1, 11);
			drawCtx.textAlign = 'center';
			drawCtx.textBaseline = 'middle';
			drawCtx.fillStyle = '#fff4c8';
			drawCtx.fillText(text, px + 31 - tw / 2, py + 7);
			drawCtx.restore();
		},
        "getEnemyAtkBonus": function (floorId, x, y) {
			floorId = floorId || core.status.floorId;
			var data = core.getFlag('__enemyAtkBonus__', {});
			return Math.max(0, (((data[floorId] || {})[x + ',' + y]) || 0));
		},
        "setEnemyAtkBonus": function (floorId, x, y, value) {
			floorId = floorId || core.status.floorId;
			var data = core.getFlag('__enemyAtkBonus__', {});
			data[floorId] = data[floorId] || {};
			var key = x + ',' + y;
			value = Math.max(0, Math.floor(value || 0));
			if (value > 0) data[floorId][key] = value;
			else delete data[floorId][key];
			core.setFlag('__enemyAtkBonus__', data);
		},
        "addEnemyAtkBonus": function (floorId, x, y, value) {
			value = Math.max(0, Math.floor(value || 0));
			if (value <= 0) return;
			this.setEnemyAtkBonus(floorId, x, y, this.getEnemyAtkBonus(floorId, x, y) + value);
		},
        "clearEnemyAtkBonus": function (floorId, x, y) {
			floorId = floorId || core.status.floorId;
			if (x == null || y == null) return;
			this.setEnemyAtkBonus(floorId, x, y, 0);
		},
        "getEnemyCurrentAtkForBuff": function (enemy, x, y, floorId) {
			// 用当前实际显示攻击计算下一次10%加成，保证多次加成可以叠加。
			try {
				var info = core.enemys.getEnemyInfo(enemy, null, x, y, floorId);
				return Math.max(0, Math.floor((info || {}).atk || 0));
			} catch (e) {
				return Math.max(0, Math.floor(core.getEnemyValue(enemy, 'atk', x, y, floorId) || 0));
			}
		},
        "getTenPercentAtkAdd": function (enemy, x, y, floorId) {
			return Math.max(1, Math.ceil(this.getEnemyCurrentAtkForBuff(enemy, x, y, floorId) * 0.1));
		},
        "applyThanosDragonAtkBuff": function (floorId, stacks) {
			// 30 灭霸：每满3回合，让本层所有降龙攻击+10%。
			floorId = floorId || core.status.floorId;
			stacks = Math.max(0, Math.floor(stacks || 0));
			if (!floorId || stacks <= 0) return 0;
			var blocks = core.getMapBlocksObj(floorId) || {};
			var count = 0;
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var enemy = core.material.enemys[block.event.id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (!core.enemys.hasSpecial(special, 29)) continue;
				for (var i = 0; i < stacks; i++) {
					this.addEnemyAtkBonus(floorId, block.x, block.y, this.getTenPercentAtkAdd(enemy, block.x, block.y, floorId));
				}
				count++;
			}
			if (count > 0) {
				core.drawTip('灭霸发动：降龙攻击提升', 'sword1');
				core.updateStatusBar();
				core.updateDamage();
				core.drawMap();
			}
			return count;
		},
        "applyTigerSlayerAtkBuff": function (floorId, stacks) {
			// 31 屠虎：每击杀一只降龙，现存屠虎攻击+10%。
			floorId = floorId || core.status.floorId;
			stacks = Math.max(0, Math.floor(stacks || 0));
			if (!floorId || stacks <= 0) return 0;
			var blocks = core.getMapBlocksObj(floorId) || {};
			var count = 0;
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var enemy = core.material.enemys[block.event.id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (!core.enemys.hasSpecial(special, 31)) continue;
				for (var i = 0; i < stacks; i++) {
					this.addEnemyAtkBonus(floorId, block.x, block.y, this.getTenPercentAtkAdd(enemy, block.x, block.y, floorId));
				}
				count++;
			}
			if (count > 0) {
				core.drawTip('屠虎发动：攻击提升', 'sword1');
				core.updateStatusBar();
				core.updateDamage();
				core.drawMap();
			}
			return count;
		},
        "installLeopardReplayAction": function () {
			// 豹跃录像改为只记录最终落点：leopardTo:x:y。
			// 回放时不显示黄框、不半黑、不播放跳跃动画，直接把勇士放到最终坐标。
			if (!core.control || !core.control.registerReplayAction) return;
			core.control.registerReplayAction('leopardFinalTo', function (action) {
				if (typeof action != 'string' || action.indexOf('leopardTo:') != 0) return false;
				var arr = action.split(':');
				if (arr.length < 3) return false;
				var tx = parseInt(arr[1]), ty = parseInt(arr[2]);
				if (isNaN(tx) || isNaN(ty)) return false;

				var cd = core.control && core.control.controldata;
				if (!cd || !cd.performLeopardLeapTo) return false;
				cd.performLeopardLeapTo(tx, ty, true, action);
				return true;
			});
		},
        "installShuttleReplayAction": function () {
			// 穿梭录像：新版记录 原坐标+目标坐标：shuttleTo:floorId:fromX:fromY:toX:toY:enemyId。
			// 兼容旧格式 shuttleTo:floorId:toX:toY:enemyId。回放时不播放特效，直接同步位置，不自动战斗。
			//
			// 这个 handler 是穿梭回放的【正常主路径】（与豹跃 leopardFinalTo 一样）：
			// triggerMonsterShuttle 回放分支只 return true（暂停清层），击杀的移动动作跑完后，
			// 引擎自然弹出紧跟其后的 shuttleTo 动作交给本 handler，由 performMonsterShuttleTo 完成传送。
			if (!core.control || !core.control.registerReplayAction) return;
			if (core.__monsterShuttleReplayActionInstalled) return;
			core.__monsterShuttleReplayActionInstalled = true;
			core.control.registerReplayAction('monsterShuttleTo', function (action) {
				if (typeof action != 'string' || action.indexOf('shuttleTo:') != 0) return false;
				var arr = action.split(':');
				if (arr.length < 5) return false;
				var floorId = arr[1], fromX = null, fromY = null, tx, ty, enemyId, targetEnemyId = null;
				if (arr.length >= 7) {
					fromX = parseInt(arr[2]); fromY = parseInt(arr[3]);
					tx = parseInt(arr[4]); ty = parseInt(arr[5]);
					enemyId = arr[6];
					if (arr.length >= 8) targetEnemyId = arr.slice(7).join(':');
				} else {
					tx = parseInt(arr[2]); ty = parseInt(arr[3]);
					enemyId = arr.slice(4).join(':');
				}
				if (isNaN(tx) || isNaN(ty)) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.performMonsterShuttleTo) return false;
				cd.performMonsterShuttleTo(tx, ty, true, action, { floorId: floorId, enemyId: enemyId, targetEnemyId: targetEnemyId, fromX: fromX, fromY: fromY });
				return true;
			});
		},
        "installDodgeReplayAction": function () {
			// 躲避录像：只记录怪物最终躲避点 dodgeTo:floorId:fromX:fromY:toX:toY:enemyId。
			// 回放时不播放特效，直接把怪物从原格移动到目标空地，并继承+20生命。
			if (!core.control || !core.control.registerReplayAction) return;
			if (core.__monsterDodgeReplayActionInstalled) return;
			core.__monsterDodgeReplayActionInstalled = true;
			core.control.registerReplayAction('monsterDodgeTo', function (action) {
				if (typeof action != 'string' || action.indexOf('dodgeTo:') != 0) return false;
				var arr = action.split(':');
				if (arr.length < 7) return false;
				var floorId = arr[1];
				var fx = parseInt(arr[2]), fy = parseInt(arr[3]), tx = parseInt(arr[4]), ty = parseInt(arr[5]);
				var enemyId = arr.slice(6).join(':');
				if (isNaN(fx) || isNaN(fy) || isNaN(tx) || isNaN(ty)) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.performMonsterDodgeTo) return false;
				cd.performMonsterDodgeTo(enemyId, fx, fy, tx, ty, true, action, { floorId: floorId });
				return true;
			});
		},
        "installMT4BossMT0ShopLock": function () {
			// 旧版本这里会封印 MT0 龙魂试炼商店。
			// 现在 P5/MT0 试炼需要保留商店可用，所以此函数改成空函数。
			return;
		}, 
        "installMT4BossReplayAction": function () {
			// MT4龙魂王录像接口：只记录最终阶段结果，回放时不播放异步动画，避免动画时序影响实际路线。
			if (!core.control || !core.control.registerReplayAction) return;
			if (core.control.__mt4BossReplayInstalled) return;
			core.control.__mt4BossReplayInstalled = true;
			core.control.registerReplayAction('mt4BossP1Result', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP1:') != 0) return false;
				var arr = action.split(':');
				if (arr.length < 4) return false;
				var seg = parseInt(arr[1]), hx = parseInt(arr[2]), hy = parseInt(arr[3]);
				if (isNaN(seg) || isNaN(hx) || isNaN(hy)) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP1State) return false;
				cd.applyMT4BossP1State(seg, hx, hy, true);
				return true;
			});
			core.control.registerReplayAction('mt4BossP2Result', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP2:') != 0) return false;
				var arr = action.split(':');
				if (arr.length < 4) return false;
				var seg = parseInt(arr[1]), hx = parseInt(arr[2]), hy = parseInt(arr[3]);
				if (isNaN(seg) || isNaN(hx) || isNaN(hy)) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP2State) return false;
				cd.applyMT4BossP2State(seg, hx, hy, true);
				return true;
			});
			core.control.registerReplayAction('mt4BossP3Result', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP3:') != 0) return false;
				var arr = action.split(':');
				if (arr.length < 4) return false;
				var seg = parseInt(arr[1]), hx = parseInt(arr[2]), hy = parseInt(arr[3]);
				if (isNaN(seg) || isNaN(hx) || isNaN(hy)) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP3State) return false;
				cd.applyMT4BossP3State(seg, hx, hy, true);
				return true;
			});
			core.control.registerReplayAction('mt4BossP4Ready', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP4Ready:') != 0) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP4ReadyReplay) return false;
				cd.applyMT4BossP4ReadyReplay(action, true);
				return true;
			});
			core.control.registerReplayAction('mt4BossP4Result', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP4:') != 0) return false;
				var arr = action.split(':');
				if (arr.length < 4) return false;
				var seg = parseInt(arr[1]), hx = parseInt(arr[2]), hy = parseInt(arr[3]);
				if (isNaN(seg) || isNaN(hx) || isNaN(hy)) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP4State) return false;
				// 新录像流程会保留P4开打那一步，让真实战斗先执行；随后遇到mt4BossP4结果动作时只对齐录像并继续，避免重复进入试炼。
				if (core.hasFlag('__MT4BossP4Done__') && core.getFlag('__MT4BossPhase__', 1) >= 5) {
					try { if (core.status && core.status.route) core.status.route.push(action); } catch (e) {}
					setTimeout(function () { core.replay(); }, 0);
					return true;
				}
				cd.applyMT4BossP4State(seg, hx, hy, true);
				return true;
			});
			core.control.registerReplayAction('mt4BossP4Sacrifice', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP4Sacrifice:') != 0) return false;
				var arr = action.split(':');
				var key = arr[1] || '';
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP4SacrificeReplay) return false;
				cd.applyMT4BossP4SacrificeReplay(key);
				return true;
			});
			core.control.registerReplayAction('mt4BossP4Setup', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP4Setup') != 0) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP4SetupReplay) return false;
				cd.applyMT4BossP4SetupReplay();
				return true;
			});
			core.control.registerReplayAction('mt4BossP4Sacrifice2', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP4Sacrifice2:') != 0) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP4SacrificeReplay2) return false;
				cd.applyMT4BossP4SacrificeReplay2(action);
				return true;
			});
			core.control.registerReplayAction('mt4BossP4SacrificeState', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP4SacrificeState:') != 0) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP4SacrificeStateReplay) return false;
				cd.applyMT4BossP4SacrificeStateReplay(action);
				return true;
			});
			core.control.registerReplayAction('mt4BossP4RepulseSacrifice', function (action) {
				if (typeof action != 'string' || action.indexOf('mt4BossP4RepulseSacrifice:') != 0) return false;
				var cd = core.control && core.control.controldata;
				if (!cd || !cd.applyMT4BossP4RepulseSacrificeReplay) return false;
				cd.applyMT4BossP4RepulseSacrificeReplay(action);
				return true;
			});
			[5, 6].forEach(function (__phase) {
				core.control.registerReplayAction('mt4BossP' + __phase + 'Result', function (action) {
					if (typeof action != 'string' || action.indexOf('mt4BossP' + __phase + ':') != 0) return false;
					var arr = action.split(':');
					if (arr.length < 4) return false;
					var seg = parseInt(arr[1]), hx = parseInt(arr[2]), hy = parseInt(arr[3]);
					if (isNaN(seg) || isNaN(hx) || isNaN(hy)) return false;
					var cd = core.control && core.control.controldata;
					if (!cd) return false;
					if (cd.applyMT4BossLinearState) cd.applyMT4BossLinearState(__phase, seg, hx, hy, true);
					else if (cd.applyMT4BossGauntletState) cd.applyMT4BossGauntletState(__phase, seg, hx, hy, true);
					else return false;
					return true;
				});
			});
		},
        "getMT4BossPhaseBgm": function (phase) {
			phase = Math.max(1, Math.min(6, parseInt(phase || core.getFlag('__MT4BossPhase__', 1) || 1)));
			var map = {
				1: 'mt4_boss_p1_dragon_eye_loop.mp3',
				2: 'mt4_boss_p2_summon_loop.mp3',
				3: 'mt4_boss_p3_seal_break_loop.mp3',
				4: 'mt4_boss_p4_altar_clash_loop.mp3',
				5: 'mt4_boss_p5_reincarnation_loop.mp3',
				6: 'mt4_boss_p6_final_soul_loop.mp3'
			};
			return map[phase] || map[1];
		},
        "syncMT4BossPhaseBgm": function (phase) {
			// 只切换音乐，不插事件、不动路线、不调用 replay/stopAutomaticRoute，避免影响录像。
			if (!core.status || core.status.floorId != 'MT4') return false;
			var bgm = this.getMT4BossPhaseBgm ? this.getMT4BossPhaseBgm(phase) : null;
			if (!bgm) return false;
			try {
				if (core.__mt4BossCurrentBgm === bgm) return true;
				core.__mt4BossCurrentBgm = bgm;
				if (core.playBgm) core.playBgm(bgm);
			} catch (e) {}
			return true;
		},
        "applyMT4BossP1State": function (segments, heroX, heroY, fromReplay) {
			segments = Math.max(0, Math.min(5, parseInt(segments == null ? core.getFlag('__MT4BossHpSegments__', 5) : segments)));
			heroX = parseInt(heroX == null ? 6 : heroX);
			heroY = parseInt(heroY == null ? 12 : heroY);
			core.setFlag('__MT4BossPhase__', 2);
			core.setFlag('__MT4BossHpSegments__', segments);
			if (this.syncMT4BossPhaseBgm) this.syncMT4BossPhaseBgm(2);
			try {
				core.setHeroLoc('x', heroX);
				core.setHeroLoc('y', heroY);
				core.setHeroLoc('direction', 'up');
				core.drawHero();
			} catch (e) {}
			this.ensureMT4BossP2Boss(true);
			// P2阶段开始就清空MT4所有透明墙/不可通行限制，避免P2护法阶段还被旧透明墙挡路。
			if (this.removeMT4BossP3TopBarriers) this.removeMT4BossP3TopBarriers();
			if (core.plugin) {
				if (core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(segments);
				if (core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
			}
			core.updateStatusBar(false, true);
			if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute();

			var self = this;
			if (fromReplay || !core.plugin || !core.plugin.playMT4BossP2SummonSequence) {
				self.finalizeMT4BossP2Summons(true);
				try { core.unlockControl(); } catch (e) {}
			} else {
				try { core.lockControl(); } catch (e) {}
				var startP2Summon = function () {
					core.plugin.playMT4BossP2SummonSequence(function () {
						self.finalizeMT4BossP2Summons(false);
						try { core.unlockControl(); } catch (e) {}
					});
				};
				if (core.plugin.playMT4BossPhaseTransition) core.plugin.playMT4BossPhaseTransition(2, '第二阶段 · 护法降临', startP2Summon);
				else startP2Summon();
			}
			return true;
		},
        "applyMT4BossP2State": function (segments, heroX, heroY, fromReplay) {
			segments = Math.max(0, Math.min(4, parseInt(segments == null ? core.getFlag('__MT4BossHpSegments__', 4) : segments)));
			heroX = parseInt(heroX == null ? 6 : heroX);
			heroY = parseInt(heroY == null ? 12 : heroY);
			core.setFlag('__MT4BossPhase__', 3);
			core.setFlag('__MT4BossHpSegments__', segments);
			if (this.syncMT4BossPhaseBgm) this.syncMT4BossPhaseBgm(3);
			try {
				core.setBlock(262, 6, 9, 'MT4');
				core.setHeroLoc('x', heroX);
				core.setHeroLoc('y', heroY);
				core.setHeroLoc('direction', 'up');
				core.drawHero();
			} catch (e) {}
			if (core.plugin) {
				if (core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(segments);
				if (core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
			}
			core.updateStatusBar(false, true);
			if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute();

			var self = this;
			// 切换P3时立刻取消MT4内所有透明不可通行限制，避免过场后仍被透明墙挡住。
			if (self.removeMT4BossP3TopBarriers) self.removeMT4BossP3TopBarriers();
			var startP3Setup = function () {
				self.setupMT4BossP3(fromReplay, function () {
					try { core.unlockControl(); } catch (e) {}
				});
			};
			var startP3Transform = function () {
				if (fromReplay || !core.plugin || !core.plugin.playMT4BossP3Transform) startP3Setup();
				else core.plugin.playMT4BossP3Transform(startP3Setup);
			};
			if (fromReplay || !core.plugin) {
				startP3Transform();
			} else {
				try { core.lockControl(); } catch (e) {}
				if (core.plugin.playMT4BossPhaseTransition) core.plugin.playMT4BossPhaseTransition(3, '第三阶段 · 三魂裂变', startP3Transform);
				else startP3Transform();
			}
			return true;
		},
        "applyMT4BossP3State": function (segments, heroX, heroY, fromReplay) {
			segments = Math.max(0, Math.min(3, parseInt(segments == null ? core.getFlag('__MT4BossHpSegments__', 3) : segments)));
			heroX = parseInt(heroX == null ? 6 : heroX);
			heroY = parseInt(heroY == null ? 12 : heroY);
			core.setFlag('__MT4BossPhase__', 4);
			core.setFlag('__MT4BossHpSegments__', segments);
			if (this.syncMT4BossPhaseBgm) this.syncMT4BossPhaseBgm(4);
			core.removeFlag('__MT4BossP3NoTeleport__');
			core.removeFlag('cannotMoveDirectly');
			try {
				core.setBlock(263, 6, 9, 'MT4');
				core.setHeroLoc('x', heroX);
				core.setHeroLoc('y', heroY);
				core.setHeroLoc('direction', 'up');
				core.drawHero();
			} catch (e) {}
			var self = this;
			var replayingNow = !!fromReplay || !!(core.isReplaying && core.isReplaying());
			var finishPhase4 = function () {
				if (core.plugin) {
					if (core.plugin.hideMT4BossP3SealFx) core.plugin.hideMT4BossP3SealFx();
					if (core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(segments);
					if (core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
				}
				if (self.setupMT4BossP4) {
					self.setupMT4BossP4(replayingNow, function () {
						core.updateStatusBar(false, true);
						if (replayingNow && self.skipMT4BossP4ProcessInReplay && self.skipMT4BossP4ProcessInReplay()) return;
						if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute();
						try { core.unlockControl(); } catch (e) {}
					});
				} else {
					core.updateStatusBar(false, true);
					if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute();
					try { core.unlockControl(); } catch (e) {}
				}
			};
			if (!replayingNow && core.plugin && core.plugin.playMT4BossPhaseTransition) {
				try { core.lockControl(); } catch (e) {}
				core.plugin.playMT4BossPhaseTransition(4, '第四阶段 · 献祭祭坛', finishPhase4);
			} else finishPhase4();
			return true;
		},
        "removeMT4BossP3TopBarriers": function () {
			// P2开始后，取消MT4里所有透明不可通行限制。
			// h5mota 实际行走判定读的是 core.floors[floorId].cannotMove / cannotMoveIn；
			// 之前只清 core.status.maps.MT4 不够，所以看起来“透明墙还在”。
			var floor = core.floors && core.floors.MT4;
			var map = core.status && core.status.maps && core.status.maps.MT4;
			if (floor) {
				floor.cannotMove = {};
				floor.cannotMoveIn = {};
			}
			if (map) {
				map.cannotMove = {};
				map.cannotMoveIn = {};
			}
			if (core.status && core.status.thisMap && core.status.thisMap.floorId == 'MT4') {
				core.status.thisMap.cannotMove = {};
				core.status.thisMap.cannotMoveIn = {};
			}
			// 清掉移动/显伤缓存，避免还沿用旧透明墙路径。
			try {
				if (core.status && core.status.checkBlock) core.status.checkBlock.cache = {};
				if (core.updateDamage) core.updateDamage();
			} catch (e) {}
		},
        "getMT4BossP3Seals": function () {
			return [
				{ key: 'lt', name: '左上阵眼', text: '左上阵眼破除：地图上怪物生命下降25%。', x: 3, y: 3, color: [255, 80, 80], effect: 'hp25' },
				{ key: 'rt', name: '右上阵眼', text: '右上阵眼破除：地图上怪物攻击下降20%。', x: 9, y: 3, color: [90, 170, 255], effect: 'atk20' },
				{ key: 'lb', name: '左下阵眼', text: '左下阵眼破除：地图上怪物防御下降50%。', x: 3, y: 9, color: [120, 255, 170], effect: 'def50' },
				{ key: 'rb', name: '右下阵眼', text: '右下阵眼破除：地图上怪物生命下降25%。', x: 9, y: 9, color: [255, 215, 80], effect: 'hp25' }
			];
		},
        "setupMT4BossP3": function (fromReplay, callback) {
			if (!core.status || core.status.floorId != 'MT4') return;
			core.setFlag('__MT4BossPhase__', 3);
			core.setFlag('__MT4BossP3NoTeleport__', true);
			core.setFlag('cannotMoveDirectly', true);
			core.removeFlag('__MT4BossP3Unlocked__');
			core.removeFlag('__MT4BossP3SealsCleared__');
			this.removeMT4BossP3TopBarriers();
			try { core.setBlock(262, 6, 9, 'MT4'); } catch (e) {}

			// 初始化阵眼状态；读档已存在时不覆盖，避免回档/读档丢进度。
			if (!core.hasFlag('__MT4BossP3Seals__')) {
				var seals = {};
				this.getMT4BossP3Seals().forEach(function (s) { seals[s.key] = true; });
				core.setFlag('__MT4BossP3Seals__', seals);
			}

			var self = this;
			var finishSetup = function () {
				if (core.drawMap) core.drawMap('MT4');
				if (core.plugin) {
					if (core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
					if (core.plugin.showMT4BossP3SealFx) core.plugin.showMT4BossP3SealFx();
				}
				if (core.updateStatusBar) core.updateStatusBar(false, true);
				if (core.updateDamage) core.updateDamage();
				if (!fromReplay) core.drawTip('四方封印展开，空间被锁定。');
				if (callback) callback();
			};

			if (!core.hasFlag('__MT4BossP3SetupDone__')) {
				core.setFlag('__MT4BossP3SetupDone__', true);
				if (fromReplay || !core.plugin || !core.plugin.playMT4BossP3SpawnSequence) {
					self.spawnMT4BossP3Objects();
					finishSetup();
				} else {
					core.plugin.playMT4BossP3SpawnSequence(function () {
						finishSetup();
					});
				}
			} else finishSetup();
		},
        "safeSetMT4Block": function (id, x, y, fallbackList) {
			if (!core.status || core.status.floorId != 'MT4') return false;
			var trySet = function (tx, ty) {
				if (tx < 0 || tx > 12 || ty < 0 || ty > 12) return false;
				var block = core.getBlock(tx, ty);
				if (block && !block.disable) return false;
				try { core.setBlock(id, tx, ty, 'MT4'); return true; } catch (e) { return false; }
			};
			if (trySet(x, y)) return true;
			fallbackList = fallbackList || [];
			for (var i = 0; i < fallbackList.length; i++) {
				if (trySet(fallbackList[i][0], fallbackList[i][1])) return true;
			}
			return false;
		},
        "getMT4BossP3SpawnWaves": function () {
			return [
				{ title: '四方封印', color: [255, 120, 80], points: [[3,3],[9,3],[3,9],[9,9]], blocks: [] },
				{ title: '护阵兽影', color: [255, 80, 180], blocks: [[235,3,4],[235,2,10],[235,10,5],[235,9,8]] },
				{ title: '赤晶坠落', color: [255, 60, 60], blocks: [[27,2,2],[27,10,10]] },
				{ title: '蓝晶坠落', color: [80, 150, 255], blocks: [[28,1,11],[28,11,4]] }
			];
		},
        "spawnMT4BossP3Wave": function (waveIndex) {
			if (!core.status || core.status.floorId != 'MT4') return;
			var waves = this.getMT4BossP3SpawnWaves();
			var wave = waves[waveIndex];
			if (!wave) return;
			var spawned = core.getFlag('__MT4BossP3SpawnedWaves__', {});
			if (spawned[waveIndex]) return;
			spawned[waveIndex] = true;
			core.setFlag('__MT4BossP3SpawnedWaves__', spawned);
			(wave.blocks || []).forEach(function (b) {
				try { core.setBlock(b[0], b[1], b[2], 'MT4'); } catch (e) {}
			});
		},
        "spawnMT4BossP3Objects": function () {
			if (!core.status || core.status.floorId != 'MT4') return;
			var waves = this.getMT4BossP3SpawnWaves();
			for (var i = 0; i < waves.length; i++) this.spawnMT4BossP3Wave(i);
		},
        "isMT4BossP3SealActive": function () {
			if (!core.status || core.status.floorId != 'MT4') return false;
			if (core.getFlag('__MT4BossPhase__', 1) != 3) return false;
			if (core.hasFlag('__MT4BossP3SealsCleared__')) return false;
			var seals = core.getFlag('__MT4BossP3Seals__', {});
			for (var k in seals) if (seals[k]) return true;
			return false;
		},
        "handleMT4BossP3Step": function (x, y) {
			if (!core.status || core.status.floorId != 'MT4') return false;
			if (core.getFlag('__MT4BossPhase__', 1) != 3) return false;
			// 阵眼存在期间，每走一步三道激光命中，每道5点，共15点。
			if (this.isMT4BossP3SealActive()) {
				if (core.plugin && core.plugin.playMT4BossP3StepLaser) core.plugin.playMT4BossP3StepLaser(x, y, [5, 5, 5]);
				for (var _laserI = 0; _laserI < 3; _laserI++) {
					core.status.hero.hp -= 5;
					core.status.hero.statistics.extraDamage = (core.status.hero.statistics.extraDamage || 0) + 5;
				}
				if (core.status.hero.hp <= 0) {
					core.status.hero.hp = 0;
					core.updateStatusBar(false, true);
					core.events.lose('被龙魂王激光击溃');
					return true;
				}
				core.updateStatusBar(false, true);
			}
			if (this.checkMT4BossP3SealAt(x, y)) return true;
			return false;
		},
        "checkMT4BossP3SealAt": function (x, y) {
			if (!this.isMT4BossP3SealActive()) return false;
			var seals = core.getFlag('__MT4BossP3Seals__', {});
			var list = this.getMT4BossP3Seals();
			for (var i = 0; i < list.length; i++) {
				var s = list[i];
				if (!seals[s.key]) continue;
				if (s.x == x && s.y == y) {
					seals[s.key] = false;
					core.setFlag('__MT4BossP3Seals__', seals);
					this.applyMT4BossP3SealEffect(s.effect);
					if (core.plugin && core.plugin.playMT4BossP3SealBreak) core.plugin.playMT4BossP3SealBreak(s);
					core.drawText([s.text.replace(/^.*?：/, '')]);
					if (core.plugin && core.plugin.showMT4BossP3SealFx) core.plugin.showMT4BossP3SealFx();
					this.checkMT4BossP3SealsCleared();
					return true;
				}
			}
			return false;
		},
        "applyMT4BossP3SealEffect": function (effect) {
			var blocks = core.getMapBlocksObj('MT4') || {};
			flags.enemyOnPoint = flags.enemyOnPoint || {};
			flags.enemyOnPoint.MT4 = flags.enemyOnPoint.MT4 || {};
			for (var key in blocks) {
				var b = blocks[key];
				if (!b || b.disable || !b.event || !b.event.id || !b.event.cls || b.event.cls.indexOf('enemy') !== 0) continue;
				// 中心本体只读你设置的BOSS属性，不被阵眼改写。
				if (b.x == 6 && b.y == 9) continue;
				var enemy = core.material.enemys[b.event.id];
				if (!enemy) continue;
				var pos = b.x + ',' + b.y;
				var info = flags.enemyOnPoint.MT4[pos] || {};
				if (effect == 'hp25') info.hp = Math.max(1, Math.floor((core.getEnemyValue(enemy, 'hp', b.x, b.y, 'MT4') || 1) * 0.75));
				if (effect == 'atk20') info.atk = Math.max(0, Math.floor((core.getEnemyValue(enemy, 'atk', b.x, b.y, 'MT4') || 0) * 0.8));
				if (effect == 'def50') info.def = Math.max(0, Math.floor((core.getEnemyValue(enemy, 'def', b.x, b.y, 'MT4') || 0) * 0.5));
				flags.enemyOnPoint.MT4[pos] = info;
			}
			if (core.updateDamage) core.updateDamage();
		},
        "checkMT4BossP3SealsCleared": function () {
			var seals = core.getFlag('__MT4BossP3Seals__', {});
			for (var k in seals) if (seals[k]) return false;
			core.setFlag('__MT4BossP3SealsCleared__', true);
			core.removeFlag('__MT4BossP3NoTeleport__');
			core.removeFlag('cannotMoveDirectly');
			if (core.plugin && core.plugin.hideMT4BossP3SealFx) core.plugin.hideMT4BossP3SealFx();
			if (core.plugin && core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
			core.drawTip('四方封印尽破，空间封锁解除。');
			this.checkMT4BossP3Unlock();
			return true;
		},
        "hasMT4BossP3BlockingEnemies": function () {
			if (!core.status || core.status.floorId != 'MT4') return true;
			// 必须实时从楼层块读取，保证屠龙分身、共生复活、读档恢复后的怪都能被算进去。
			try { if (core.extractBlocks) core.extractBlocks('MT4'); } catch (e) {}
			var blocks = ((core.status.maps || {}).MT4 || {}).blocks || [];
			for (var i = 0; i < blocks.length; i++) {
				var b = blocks[i];
				if (!b || b.disable || !b.event) continue;
				if (!b.event.cls || b.event.cls.indexOf('enemy') !== 0) continue;
				// 中心本体不是护阵怪。其余所有活着的怪都必须清掉，包括屠龙分身。
				if (b.x == 6 && b.y == 9) continue;
				return true;
			}
			return false;
		},
        "canAttackMT4BossP3": function () {
			if (!core.status || core.status.floorId != 'MT4') return false;
			if (core.getFlag('__MT4BossPhase__', 1) != 3) return false;
			if (!core.hasFlag('__MT4BossP3SealsCleared__')) {
				core.removeFlag('__MT4BossP3Unlocked__');
				return false;
			}
			if (this.hasMT4BossP3BlockingEnemies()) {
				core.removeFlag('__MT4BossP3Unlocked__');
				return false;
			}
			core.setFlag('__MT4BossP3Unlocked__', true);
			return true;
		},
        "checkMT4BossP3Unlock": function () {
			if (!core.status || core.status.floorId != 'MT4') return false;
			if (core.getFlag('__MT4BossPhase__', 1) != 3) return false;
			if (!this.canAttackMT4BossP3()) return false;
			if (core.updateStatusBar) core.updateStatusBar(false, true);
			if (core.updateDamage) core.updateDamage();
			core.drawTip('护阵残敌尽灭，龙魂王无敌解除。');
			return true;
		},
        "setMT4BossBackgroundImage": function (imageName, redraw) {
			// MT4底图切换：P5使用用户准备的黑白水墨底图342.png；其余阶段恢复341.png。
			if (!imageName) imageName = '341.png';
			try {
				if (!core.status.maps) core.status.maps = {};
				if (!core.status.maps.MT4 && core.floors && core.floors.MT4) {
					core.status.maps.MT4 = core.clone ? core.clone(core.floors.MT4) : JSON.parse(JSON.stringify(core.floors.MT4));
				}
				var map = core.status.maps.MT4 || (core.floors && core.floors.MT4);
				if (!map) return false;
				var images = map.images || [];
				var replaced = false;
				for (var i = 0; i < images.length; i++) {
					var one = images[i];
					if (!one || one.canvas != 'bg' || one.x != 0 || one.y != 0) continue;
					if (one.name == imageName) { replaced = true; break; }
					if (one.name == '341.png' || one.name == '342.png') {
						one.name = imageName;
						replaced = true;
						break;
					}
				}
				if (!replaced) images.push({ name: imageName, canvas: 'bg', x: 0, y: 0 });
				map.images = images;
				if (core.status.maps.MT4) core.status.maps.MT4.images = images;
				if (redraw && core.status && core.status.floorId == 'MT4' && core.drawMap) core.drawMap('MT4');
				return true;
			} catch (e) { return false; }
		},
        "syncMT4BossPhaseBackground": function (phase, redraw) {
			phase = parseInt(phase == null ? core.getFlag('__MT4BossPhase__', 1) : phase) || 1;
			// 342黑白底图必须等P5水墨演出播放完才切。P5回到MT4时仍保持341；P6起进入黑白世界。
			var inkMode = phase >= 6 || core.hasFlag('__MT4BossInkBlackWhiteMode__');
			try { if (core.plugin && core.plugin.setMT4BossInkBlackWhiteMode) core.plugin.setMT4BossInkBlackWhiteMode(inkMode); } catch (e) {}
			return this.setMT4BossBackgroundImage(inkMode ? '342.png' : '341.png', redraw);
		},
        "ensureMT4BossCenterBlock": function () {
			if (!core.status || core.status.floorId != 'MT4') return;
			var phase = Math.max(1, Math.min(6, parseInt(core.getFlag('__MT4BossPhase__', 1)) || 1));
			// P4 机制期间只隐藏Canvas代码BOSS；地图上的263本体保持显示，但由 beforeBattle 临时无敌拦截。
			if (phase == 4 && !core.hasFlag('__MT4BossP4Unlocked__')) {
				if (core.plugin && core.plugin.hideMT4BossBody) core.plugin.hideMT4BossBody();
			}
			// P5 已恢复为MT0试炼返回后直接打BOSS；只有旧存档棋阵运行中才临时隐藏中心BOSS。
			if (phase == 5 && core.hasFlag('__MT4BossP5ChessRunning__') && !core.hasFlag('__MT4BossP5ChessDone__')) {
				try { core.removeBlock(6, 9, 'MT4'); } catch (eSkip) {}
				if (core.plugin && core.plugin.hideMT4BossBody) core.plugin.hideMT4BossBody();
				return;
			}
			var id = 259 + phase; // P1=260 ... P6=265；268以后不再作为BOSS使用
			try {
				var block = core.getBlock(6, 9, 'MT4');
				if (!block || block.disable || !block.event || block.event.id != id) core.setBlock(id, 6, 9, 'MT4');
			} catch (e) {}
		},
        "hideMT4BossCenterBlock": function () {
			if (!core.status || core.status.floorId != 'MT4') return;
			try {
				var block = core.getBlock(6, 9, 'MT4');
				if (block && !block.disable && block.event && block.event.cls && block.event.cls.indexOf('enemy') === 0) {
					core.removeBlock(6, 9);
				}
			} catch (e) {}
		},
        "getMT4BossGauntletConfig": function (defeatedPhase) {
			defeatedPhase = parseInt(defeatedPhase || 4);
			var table = {
				// P4打完后恢复为MT0试炼，但不再重置勇士属性；MT0投放规则在 prepare/restore 中按 replaceNumbers/skipNumbers 应用。
				4: { floorId: 'MT0', noResetStats: true, returnPhase: 5, returnBossId: 264, title: '第五阶段开启', tip: '龙魂试炼开始，清空本层所有怪物后返回BOSS层。', replaceNumbers: { 201: 268, 202: 269, 203: 279, 206: 280, 207: 325, 209: 326 }, skipNumbers: [46, 50, 51, 52, 69] },
				5: { floorId: 'MT1', atk: 8,  def: 5,  mdef: 0, returnPhase: 6, returnBossId: 265, title: '第六阶段开启', tip: '第二重龙魂试炼开始，清空本层所有怪物后返回BOSS层。' },
				6: { floorId: 'MT2', atk: 11, def: 10, mdef: 0, returnPhase: 7, returnBossId: 266, title: '第七阶段开启', tip: '第三重龙魂试炼开始，清空本层所有怪物后返回BOSS层。', keepEventLocs: [[6, 1]] },
				7: { floorId: 'MT3', atk: 20, def: 19, mdef: 0, returnPhase: 8, returnBossId: 267, title: '第八阶段开启', tip: '第四重龙魂试炼开始，清空本层所有怪物后返回BOSS层。' }
			};
			return table[defeatedPhase] || table[4];
		},
        "applyMT4BossP4State": function (segments, heroX, heroY, fromReplay) {
			// P4恢复为“推阻击 + 献祭祭坛”。P4 BOSS击破后进入MT0试炼；
			// 清空MT0试炼怪物后返回MT4打P5 BOSS，再进入最终P6。
			return this.applyMT4BossGauntletState ? this.applyMT4BossGauntletState(4, segments, heroX, heroY, fromReplay) : false;
		},
        "setupMT4BossP5ChessState": function (segments, heroX, heroY, fromReplay) {
			segments = Math.max(0, Math.min(2, parseInt(segments == null ? core.getFlag('__MT4BossHpSegments__', 2) : segments)));
			heroX = parseInt(heroX == null ? 6 : heroX);
			heroY = parseInt(heroY == null ? 12 : heroY);
			var replaying = !!fromReplay || !!(core.isReplaying && core.isReplaying());
			var self = this;
			core.setFlag('__MT4BossPhase__', 5);
			core.setFlag('__MT4BossHpSegments__', segments);
			if (this.syncMT4BossPhaseBgm) this.syncMT4BossPhaseBgm(5);
			core.setFlag('__MT4BossNoRevive__', true);
			core.removeFlag('__MT4BossP5ChessDone__');
			core.removeFlag('__MT4BossP5ChessRunning__');
			core.removeFlag('__MT4BossP5Ready__');
			core.removeFlag('__MT4BossTrialActive__');
			core.removeFlag('__MT4BossMT0TrialActive__');
			core.removeFlag('__MT4BossTrialReturned__');
			core.removeFlag('__MT4BossNextTrialFloor__');
			core.removeFlag('__MT4BossNextTrialTitle__');
			try { if (core.plugin && core.plugin.hideMT4BossP4AltarFx) core.plugin.hideMT4BossP4AltarFx(); } catch (eHideAltar) {}
			try { if (self.clearMT4BossP4SacrificeBlocks) self.clearMT4BossP4SacrificeBlocks(); } catch (eClear) {}
			try { if (core.plugin && core.plugin.hideMT4BossBody) core.plugin.hideMT4BossBody(); } catch (eBody) {}
			try { core.removeBlock(6, 9, 'MT4'); } catch (eBlock) {}
			try {
				core.setHeroLoc('x', 6);
				core.setHeroLoc('y', 12);
				core.setHeroLoc('direction', 'up');
				if (core.drawHero) core.drawHero();
			} catch (eHero) {}
			try { if (core.drawMap && core.status && core.status.floorId == 'MT4') core.drawMap('MT4'); } catch (eDraw) {}
			try { if (core.plugin && core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(segments); } catch (eHud) {}
			try { if (core.updateStatusBar) core.updateStatusBar(false, true); } catch (eSt) {}
			try { if (core.updateDamage) core.updateDamage(); } catch (eDmg) {}
			try { if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute(); } catch (eRoute) {}

			var applyChessHpCost = function () {
				try {
					var hero = core.status && core.status.hero;
					if (!hero) return;
					var hp = Math.max(1, parseInt(hero.hp || 1) - 5000);
					if (core.setStatus) core.setStatus('hp', hp); else hero.hp = hp;
					if (core.updateStatusBar) core.updateStatusBar(false, true);
				} catch (eCost) {}
			};

			var finishChess = function () {
				if (!core.status || core.status.floorId != 'MT4') { try { core.unlockControl(); } catch (eU0) {} return true; }
				core.setFlag('__MT4BossPhase__', 5);
				core.setFlag('__MT4BossHpSegments__', segments);
				core.setFlag('__MT4BossP5ChessDone__', true);
				core.setFlag('__MT4BossP5Ready__', true);
				core.removeFlag('__MT4BossP5ChessRunning__');
				try { core.setBlock(264, 6, 9, 'MT4'); } catch (eB) {}
				try { core.setHeroLoc('x', 6); core.setHeroLoc('y', 12); core.setHeroLoc('direction', 'up'); if (core.drawHero) core.drawHero(); } catch (eH) {}
				try { if (core.drawMap) core.drawMap('MT4'); } catch (eM) {}
				try { if (core.plugin && core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(segments); } catch (eH2) {}
				try { if (core.plugin && core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene(); } catch (eScene) {}
				try { if (core.updateStatusBar) core.updateStatusBar(false, true); } catch (eUS) {}
				try { if (core.updateDamage) core.updateDamage(); } catch (eUD) {}
				try { if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute(); } catch (eCR) {}
				try { core.unlockControl(); } catch (eUnlock) {}
				if (!replaying) { try { core.drawTip('终局棋阵两败俱伤，龙魂王坠回战场。'); } catch (eTip) {} }
				return true;
			};

			if (replaying || !core.plugin || !core.plugin.playMT4BossP4ChessDuel) {
				applyChessHpCost();
				return finishChess();
			}
			core.setFlag('__MT4BossP5ChessRunning__', true);
			try { core.lockControl(); } catch (eLock) {}
			core.plugin.playMT4BossP4ChessDuel(function () {
				finishChess();
			});
			return true;
		},
        "applyMT4BossLinearState": function (defeatedPhase, segments, heroX, heroY, fromReplay) {
			// P5以后改为“只有一个中心BOSS”的连续阶段；P6击破后不再进入P7，直接判定游戏胜利。
			defeatedPhase = parseInt(defeatedPhase || 5);
			segments = Math.max(0, Math.min(2, parseInt(segments == null ? core.getFlag('__MT4BossHpSegments__', 2) : segments)));
			heroX = parseInt(heroX == null ? 6 : heroX);
			heroY = parseInt(heroY == null ? 12 : heroY);

			if (defeatedPhase >= 6) {
				core.setFlag('__MT4BossPhase__', 6);
				core.setFlag('__MT4BossHpSegments__', 0);
				if (this.syncMT4BossPhaseBgm) this.syncMT4BossPhaseBgm(6);
				core.setFlag('__MT4BossDefeated__', true);
				core.setFlag('__MT4BossNoRevive__', true);
				try { if (core.plugin && core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(0); } catch (eHud) {}
				try { if (core.plugin && core.plugin.hideMT4BossBody) core.plugin.hideMT4BossBody(); } catch (eBody) {}
				try { if (core.plugin && core.plugin.hideMT4BossHud) core.plugin.hideMT4BossHud(); } catch (eHud2) {}
				try { core.removeBlock(6, 9, 'MT4'); } catch (eBlock) {}
				try { core.setHeroLoc('x', heroX); core.setHeroLoc('y', heroY); core.setHeroLoc('direction', 'up'); if (core.drawHero) core.drawHero(); } catch (eLoc) {}
				try { if (core.updateStatusBar) core.updateStatusBar(false, true); } catch (eSt) {}
				try { if (core.updateDamage) core.updateDamage(); } catch (eDmg) {}
				try { if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute(); } catch (eRoute) {}
				setTimeout(function () {
					try { core.unlockControl(); } catch (eUnlock) {}
					if (core.status && core.status.gameOver) return;
					try { core.events.win('游戏胜利'); }
					catch (eWin) { try { core.win('游戏胜利'); } catch (eWin2) {} }
				}, fromReplay ? 0 : 60);
				return true;
			}

			var nextPhase = 6;
			core.setFlag('__MT4BossPhase__', nextPhase);
			core.setFlag('__MT4BossHpSegments__', segments);
			if (this.syncMT4BossPhaseBgm) this.syncMT4BossPhaseBgm(nextPhase);
			core.setFlag('__MT4BossNoRevive__', true);
			core.setFlag('__MT4BossInkBlackWhiteMode__', true);
			core.removeFlag('__MT4BossTrialActive__');
			core.removeFlag('__MT4BossMT0TrialActive__');
			core.removeFlag('__MT4BossTrialReturned__');
			core.removeFlag('__MT4BossNextTrialFloor__');
			core.removeFlag('__MT4BossNextTrialTitle__');
			try { if (core.plugin && core.plugin.setMT4BossInkBlackWhiteMode) core.plugin.setMT4BossInkBlackWhiteMode(true); } catch (eBW) {}
			try { this.syncMT4BossPhaseBackground(nextPhase, false); } catch (eBg) {}
			try {
				core.setBlock(259 + nextPhase, 6, 9, 'MT4');
				core.setHeroLoc('x', heroX);
				core.setHeroLoc('y', heroY);
				core.setHeroLoc('direction', 'up');
				if (core.status && core.status.floorId == 'MT4' && core.drawMap) core.drawMap('MT4');
				if (core.drawHero) core.drawHero();
			} catch (e) {}
			if (core.plugin) {
				if (core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(segments);
				if (core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
			}
			core.updateStatusBar(false, true);
			if (core.updateDamage) core.updateDamage();
			if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute();
			try { core.unlockControl(); } catch (e2) {}
			return true;
		},
        "applyMT4BossGauntletState": function (defeatedPhase, segments, heroX, heroY, fromReplay) {
			defeatedPhase = parseInt(defeatedPhase || 4);
			if (defeatedPhase >= 5 && this.applyMT4BossLinearState) return this.applyMT4BossLinearState(defeatedPhase, segments, heroX, heroY, fromReplay);
			var cfg = this.getMT4BossGauntletConfig(defeatedPhase);
			segments = Math.max(0, Math.min(2, parseInt(segments == null ? core.getFlag('__MT4BossHpSegments__', 2) : segments)));
			heroX = parseInt(heroX == null ? 6 : heroX);
			heroY = parseInt(heroY == null ? 12 : heroY);
			core.setFlag('__MT4BossPhase__', cfg.returnPhase);
			core.setFlag('__MT4BossHpSegments__', segments);
			core.setFlag('__MT4BossNoRevive__', true);
			core.removeFlag('__MT4BossP4Unlocked__');
			try {
				if (core.plugin && core.plugin.hideMT4BossP4AltarFx) core.plugin.hideMT4BossP4AltarFx();
				if (core.plugin && core.plugin.hideMT4BossBody) core.plugin.hideMT4BossBody();
				if (core.plugin && core.plugin.hideMT4BossHud) core.plugin.hideMT4BossHud();
			} catch (e) {}
			var self = this;
			var startTrial = function () {
				if (self.startMT4BossGauntlet) self.startMT4BossGauntlet(cfg, segments, fromReplay);
			};
			core.setFlag('__MT4BossNextTrialFloor__', cfg.floorId);
			core.setFlag('__MT4BossNextTrialTitle__', cfg.title);
			if (!fromReplay && core.plugin && core.plugin.playMT4BossRebirthToMT0Transition) {
				try { core.lockControl(); } catch (e) {}
				core.plugin.playMT4BossRebirthToMT0Transition(startTrial);
			} else startTrial();
			return true;
		},
        "startMT4BossMT0Gauntlet": function (segments, fromReplay) {
			return this.startMT4BossGauntlet(this.getMT4BossGauntletConfig(4), segments, fromReplay);
		},
        "startMT4BossGauntlet": function (cfg, segments, fromReplay) {
			cfg = cfg || this.getMT4BossGauntletConfig(4);
			segments = Math.max(0, Math.min(2, parseInt(segments == null ? core.getFlag('__MT4BossHpSegments__', 2) : segments)));
			core.setFlag('__MT4BossPhase__', cfg.returnPhase);
			core.setFlag('__MT4BossHpSegments__', segments);
			core.setFlag('__MT4BossNoRevive__', true);
			core.setFlag('__MT4BossTrialActive__', true);
			core.setFlag('__MT4BossTrialFloor__', cfg.floorId);
			core.setFlag('__MT4BossTrialReturnPhase__', cfg.returnPhase);
			core.setFlag('__MT4BossTrialReturnBossId__', cfg.returnBossId);
			core.setFlag('__MT4BossTrialTitle__', cfg.title);
			core.setFlag('__MT4BossTrialTip__', cfg.tip);
			core.setFlag('__MT4BossTrialReturned__', false);
			// 兼容旧MT0试炼判断。
			if (cfg.floorId == 'MT0') core.setFlag('__MT4BossMT0TrialActive__', true);
			else core.removeFlag('__MT4BossMT0TrialActive__');

			// P5 MT0试炼按新规则不再重置属性，保留勇士当前攻防/护盾。
			// 其他旧试炼若仍使用 cfg.atk/cfg.def，则保留原来的临时重置逻辑。
			if (cfg.noResetStats) {
				core.removeFlag('__MT4BossStoredHeroStats__');
			} else {
				if (!core.hasFlag('__MT4BossStoredHeroStats__')) {
					core.setFlag('__MT4BossStoredHeroStats__', {
						atk: core.status.hero.atk || 0,
						def: core.status.hero.def || 0,
						mdef: core.status.hero.mdef || 0
					});
				}
				core.status.hero.atk = cfg.atk;
				core.status.hero.def = cfg.def;
				core.status.hero.mdef = cfg.mdef || 0;
				// 临时削减记录清掉，避免回到MT4时二次恢复。
				core.removeFlag('__armorBroken__');
				core.removeFlag('__skillBroken__');
			}
			if (this.clearMT4BossGauntletRuntimeFlags) this.clearMT4BossGauntletRuntimeFlags(cfg);
			core.updateStatusBar(false, true);
			if (core.updateDamage) core.updateDamage();
			try {
				core.changeFloor(cfg.floorId, null, { x: 6, y: 12, direction: 'up' }, 0, function () {
					try {
						if (core.status && core.status.floorId == cfg.floorId) {
							core.status.hero.loc.x = 6;
							core.status.hero.loc.y = 12;
							core.status.hero.loc.direction = 'up';
							if (core.drawMap) core.drawMap(cfg.floorId);
						}
					} catch (eReset) {}
					var finishAll = function () {
						try { core.drawTip(cfg.tip || '龙魂试炼开始，清空本层所有怪物后返回BOSS层。'); } catch (e0) {}
						try { core.unlockControl(); } catch (e1) {}
					};
					var restoreMissing = function () {
						try {
							var cd = core.control && core.control.controldata;
							if (cd && cd.restoreMT4BossGauntletMissingBlocks) return cd.restoreMT4BossGauntletMissingBlocks(cfg, !fromReplay, finishAll);
						} catch (eR) {}
						finishAll();
					};
					core.setFlag('__MT4BossRevealFloor__', cfg.floorId);
					if (!fromReplay && core.plugin && core.plugin.playMT4BossMT0RevealTransition) {
						try { core.lockControl(); } catch (e2) {}
						core.plugin.playMT4BossMT0RevealTransition(restoreMissing);
					} else restoreMissing();
				});
			} catch (e) {
				try { core.unlockControl(); } catch (e2) {}
			}
		},
        "clearMT4BossGauntletRuntimeFlags": function (cfg) {
			cfg = cfg || this.getMT4BossGauntletConfig(4);
			var floorId = cfg.floorId;
			try {
				if (flags.enemyOnPoint && flags.enemyOnPoint[floorId]) delete flags.enemyOnPoint[floorId];
				core.removeFlag('__symbiosisPending_' + floorId + '__');
				core.removeFlag('__lastSymbiosisRevive__');
				var auto = core.getFlag('__floorClearAutoNext__', {});
				if (auto[floorId]) { delete auto[floorId]; core.setFlag('__floorClearAutoNext__', auto); }
				var leaveLoc = core.getFlag('__leaveLoc__', {});
				if (leaveLoc[floorId]) { delete leaveLoc[floorId]; core.setFlag('__leaveLoc__', leaveLoc); }
				try { if (core.status && core.status.checkBlock) core.status.checkBlock.cache = {}; } catch (e0) {}
			} catch (e) {}
		},
        "getMT4BossGauntletOriginalBlocks": function (floorId, cfg) {
			var ans = [];
			try {
				var floor = core.floors && core.floors[floorId];
				var map = floor && floor.map;
				if (!map) return ans;
				for (var y = 0; y < map.length; y++) {
					var row = map[y] || [];
					for (var x = 0; x < row.length; x++) {
						var num = parseInt(row[x] || 0);
						if (!num || isNaN(num)) continue;
						if (this.isMT4BossGauntletSkipNumber && this.isMT4BossGauntletSkipNumber(cfg, num)) continue;
						if (this.getMT4BossGauntletReplacementNumber) num = this.getMT4BossGauntletReplacementNumber(cfg, num);
						ans.push({ number: num, x: x, y: y });
					}
				}
			} catch (e) {}
			return ans;
		},
        "isMT4BossGauntletKeepEventLoc": function (cfg, x, y) {
			var list = (cfg && cfg.keepEventLocs) || [];
			for (var i = 0; i < list.length; i++) {
				if (list[i] && list[i][0] == x && list[i][1] == y) return true;
			}
			return false;
		},
        "getMT4BossGauntletReplacementNumber": function (cfg, number) {
			cfg = cfg || {};
			number = parseInt(number || 0);
			var rep = cfg.replaceNumbers || {};
			return rep[number] || rep[String(number)] || number;
		},
        "isMT4BossGauntletSkipNumber": function (cfg, number) {
			cfg = cfg || {};
			number = parseInt(number || 0);
			var list = cfg.skipNumbers || [];
			for (var i = 0; i < list.length; i++) if (parseInt(list[i]) == number) return true;
			return false;
		},
        "applyMT4BossGauntletMapRulesToFloorData": function (cfg, floorData) {
			// P5 MT0试炼专用投放规则：
			// 1. 不投放飞/跳类道具；2. 指定旧怪位置替换为新怪。
			if (!cfg || !floorData || !floorData.map) return floorData;
			try {
				for (var y = 0; y < floorData.map.length; y++) {
					var row = floorData.map[y] || [];
					for (var x = 0; x < row.length; x++) {
						var num = parseInt(row[x] || 0);
						if (!num || isNaN(num)) continue;
						if (this.isMT4BossGauntletSkipNumber(cfg, num)) row[x] = 0;
						else row[x] = this.getMT4BossGauntletReplacementNumber(cfg, num);
					}
				}
				if (floorData.blocks) delete floorData.blocks;
			} catch (e) {}
			return floorData;
		},
        "getMT4BossGauntletMissingBlocks": function (cfg) {
			cfg = cfg || this.getMT4BossGauntletConfig(4);
			var floorId = cfg.floorId, missing = [];
			try {
				if (!core.status.maps) core.status.maps = {};
				if (!core.status.maps[floorId] && core.floors && core.floors[floorId]) {
					core.status.maps[floorId] = core.clone ? core.clone(core.floors[floorId]) : JSON.parse(JSON.stringify(core.floors[floorId]));
				}
				try { if (core.extractBlocks) core.extractBlocks(floorId); } catch (e0) {}
				var originals = this.getMT4BossGauntletOriginalBlocks(floorId, cfg);
				for (var i = 0; i < originals.length; i++) {
					var one = originals[i];
					if (this.isMT4BossGauntletKeepEventLoc(cfg, one.x, one.y)) continue;
					// 原位置还有活着的图块就不重复投放；没有或已禁用才按原坐标恢复。
					var block = null;
					try { block = core.getBlock(one.x, one.y, floorId); } catch (e1) {}
					if (block && !block.disable) continue;
					missing.push(one);
				}
			} catch (e) {}
			return missing;
		},
        "restoreMT4BossGauntletMissingBlocks": function (cfg, withAnimation, callback) {
			cfg = cfg || this.getMT4BossGauntletConfig(4);
			var floorId = cfg.floorId;
			this.clearMT4BossGauntletRuntimeFlags(cfg);
			var missing = this.getMT4BossGauntletMissingBlocks(cfg);
			var self = this;
			var apply = function () {
				try {
					for (var i = 0; i < missing.length; i++) {
						var one = missing[i];
						try { core.setBlock(one.number, one.x, one.y, floorId); } catch (e0) {}
					}
					try { if (core.extractBlocks) core.extractBlocks(floorId); } catch (e1) {}
					try { if (core.maps && core.maps._updateMapArray) core.maps._updateMapArray(floorId); } catch (e2) {}
					if (core.status && core.status.floorId == floorId && core.drawMap) core.drawMap(floorId);
					if (core.updateStatusBar) core.updateStatusBar(false, true);
					if (core.updateDamage) core.updateDamage();
				} catch (e) {}
				if (callback) callback();
			};
			if (missing.length == 0) return apply();
			if (withAnimation && core.plugin && core.plugin.playMT4BossGauntletDropRestore) {
				core.plugin.playMT4BossGauntletDropRestore(floorId, missing, apply);
			} else apply();
		},
        "prepareMT4BossMT0GauntletMap": function () {
			return this.prepareMT4BossGauntletMap(this.getMT4BossGauntletConfig(4));
		},
        "prepareMT4BossGauntletMap": function (cfg) {
			cfg = cfg || this.getMT4BossGauntletConfig(4);
			var floorId = cfg.floorId;
			try {
				var preserved = {};
				if (cfg.keepEventLocs && core.status && core.status.maps && core.status.maps[floorId]) {
					core.extractBlocks(floorId);
					var oldBlocks = core.status.maps[floorId].blocks || [];
					cfg.keepEventLocs.forEach(function (loc) {
						var key = loc[0] + ',' + loc[1];
						for (var i = 0; i < oldBlocks.length; i++) {
							var b = oldBlocks[i];
							if (b && b.x == loc[0] && b.y == loc[1]) {
								preserved[key] = core.clone ? core.clone(b) : JSON.parse(JSON.stringify(b));
								break;
							}
						}
					});
				}

				var initMap = null;
				try {
					// 必须从楼层原始 map 重新 load，而不是复用 status.maps，否则试炼层可能带着旧的击杀/拾取状态。
					initMap = core.maps.loadFloor ? core.maps.loadFloor(floorId, core.floors && core.floors[floorId] ? core.cloneArray(core.floors[floorId].map) : null) : null;
				} catch (e0) {}
				if (!initMap && core.floors && core.floors[floorId]) initMap = core.clone ? core.clone(core.floors[floorId]) : JSON.parse(JSON.stringify(core.floors[floorId]));
				if (!initMap) return;
				// 应用本次试炼投放规则：移除飞/跳道具，替换指定旧怪为新怪。
				if (this.applyMT4BossGauntletMapRulesToFloorData) this.applyMT4BossGauntletMapRulesToFloorData(cfg, initMap);

				// 防止原始 floor 对象或旧复制里已经带有 blocks，导致 extractBlocks 直接返回不重新解析。
				if (initMap.blocks) delete initMap.blocks;
				if (!core.status.maps) core.status.maps = {};
				core.status.maps[floorId] = core.clone ? core.clone(initMap) : JSON.parse(JSON.stringify(initMap));
				if (core.status.maps[floorId].blocks) delete core.status.maps[floorId].blocks;

				// MT2的(6,1)商店启用事件不重置：如果它在进入试炼前已经被触发/禁用，保留该状态。
				if (cfg.keepEventLocs && Object.keys(preserved).length > 0) {
					core.extractBlocks(floorId);
					var blocks = core.status.maps[floorId].blocks || [];
					for (var key in preserved) {
						var p = preserved[key];
						var found = false;
						for (var j = 0; j < blocks.length; j++) {
							if (blocks[j] && blocks[j].x == p.x && blocks[j].y == p.y) {
								blocks[j].disable = p.disable;
								found = true;
								break;
							}
						}
						if (!found && p.disable) blocks.push(p);
					}
				}

				// 清理会导致“刚进入试炼层就认为已清层/旧分裂怪残留/旧坐标属性残留”的运行期标记。
				if (flags.enemyOnPoint && flags.enemyOnPoint[floorId]) delete flags.enemyOnPoint[floorId];
				core.removeFlag('__symbiosisPending_' + floorId + '__');
				core.removeFlag('__lastSymbiosisRevive__');

				var auto = core.getFlag('__floorClearAutoNext__', {});
				if (auto[floorId]) { delete auto[floorId]; core.setFlag('__floorClearAutoNext__', auto); }

				var leaveLoc = core.getFlag('__leaveLoc__', {});
				if (leaveLoc[floorId]) { delete leaveLoc[floorId]; core.setFlag('__leaveLoc__', leaveLoc); }

				// 重新生成地图数组，确保新 blocks 立即参与碰撞/战斗/清层检测。
				try { core.extractBlocks(floorId); } catch (e1) {}
				try { core.maps._updateMapArray(floorId); } catch (e2) {}
				if (core.status.floorId == floorId && core.drawMap) core.drawMap(floorId);
			} catch (e) {}
		},
        "checkMT4BossMT0GauntletClear": function () {
			return this.checkMT4BossGauntletClear();
		},
        "checkMT4BossGauntletClear": function () {
			if (!core.hasFlag('__MT4BossTrialActive__')) return false;
			var floorId = core.getFlag('__MT4BossTrialFloor__', 'MT0');
			if (core.status.floorId != floorId) return false;
			if (this.hasAliveEnemyOnFloor && this.hasAliveEnemyOnFloor(floorId)) return false;
			return this.completeMT4BossGauntlet();
		},
        "completeMT4BossMT0Gauntlet": function () {
			return this.completeMT4BossGauntlet();
		},
        "completeMT4BossGauntlet": function () {
			if (!core.hasFlag('__MT4BossTrialActive__')) return false;
			var floorId = core.getFlag('__MT4BossTrialFloor__', 'MT0');
			var returnPhase = parseInt(core.getFlag('__MT4BossTrialReturnPhase__', 5));
			var returnBossId = parseInt(core.getFlag('__MT4BossTrialReturnBossId__', 259 + returnPhase));
			var title = core.getFlag('__MT4BossTrialTitle__', '下一阶段开启');
			core.removeFlag('__MT4BossTrialActive__');
			core.removeFlag('__MT4BossMT0TrialActive__');
			core.setFlag('__MT4BossTrialReturned__', true);
			core.setFlag('__MT4BossPhase__', returnPhase);
			var saved = core.getFlag('__MT4BossStoredHeroStats__', null);
			if (saved) {
				core.status.hero.atk = saved.atk || 0;
				core.status.hero.def = saved.def || 0;
				core.status.hero.mdef = saved.mdef || 0;
				core.removeFlag('__MT4BossStoredHeroStats__');
			}
			var segments = Math.max(0, Math.min(2, core.getFlag('__MT4BossHpSegments__', 2)));
			var goBack = function () {
				try { core.setBlock(returnBossId, 6, 9, 'MT4'); } catch (e) {}
				core.updateStatusBar(false, true);
				if (core.updateDamage) core.updateDamage();
				try {
					core.changeFloor('MT4', null, { x: 6, y: 12, direction: 'up' }, 0, function () {
						try { core.setBlock(returnBossId, 6, 9, 'MT4'); } catch (e) {}
						try {
							if (core.control && core.control.controldata && core.control.controldata.syncMT4BossPhaseBackground) {
								core.control.controldata.syncMT4BossPhaseBackground(returnPhase, returnPhase == 5);
							}
						} catch (eBg) {}
						if (core.plugin) {
							if (core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(segments);
							if (core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
						}
						core.drawTip('试炼完成，力量归还。龙魂王' + title + '。');
						try { core.unlockControl(); } catch (e2) {}
					});
				} catch (e) { try { core.unlockControl(); } catch (e2) {} }
			};
			core.setFlag('__MT4BossReturnTitle__', title);
			if (core.plugin && core.plugin.playMT4BossReturnFromMT0Transition && !(core.isReplaying && core.isReplaying())) {
				try { core.lockControl(); } catch (e) {}
				core.plugin.playMT4BossReturnFromMT0Transition(goBack);
			} else goBack();
			return true;
		},
        "installMT4BossP4RepulseSacrificeHook": function () {
			if (!core.control || core.control.__mt4BossP4RepulseSacrificeHookInstalled) return;
			var original = core.control._checkBlock_repulse;
			if (typeof original != 'function') return;
			core.control.__mt4BossP4RepulseSacrificeHookInstalled = true;
			core.control._checkBlock_repulse = function (repulse) {
				var ret = original.apply(this, arguments);
				try {
					if (repulse && repulse.length > 0 && core.status && core.status.floorId == 'MT4' && core.getFlag('__MT4BossPhase__', 1) == 4) {
						// 原阻击会插入 move(async)+waitAsync；这里追加到事件末尾，确保移动结束后再献祭。
						core.insertAction({
							"type": "function",
							"function": "function () { if (core.status && core.status.floorId == 'MT4' && core.control && core.control.controldata && core.control.controldata.checkMT4BossP4Sacrifices) core.control.controldata.checkMT4BossP4Sacrifices(); }"
						}, null, null, null, true);
					}
				} catch (e) {}
				return ret;
			};
		},
        "getMT4BossP4Altars": function () {
			return [
				{ key: 'top', name: '噬血祭坛', text: '噬血祭坛吞噬祭品，龙魂王生命下降20点。', x: 6, y: 2, color: [255, 70, 90], effect: 'hp20' },
				{ key: 'left', name: '碎甲祭坛', text: '碎甲祭坛吞噬祭品，龙魂王防御下降1点。', x: 2, y: 10, color: [80, 200, 255], effect: 'def1' },
				{ key: 'right', name: '破功祭坛', text: '破功祭坛吞噬祭品，龙魂王攻击下降1点。', x: 10, y: 10, color: [255, 210, 80], effect: 'atk1' }
			];
		},
        "setupMT4BossP4": function (fromReplay, callback) {
			if (!core.status || core.status.floorId != 'MT4') return;
			// 录像回放时，afterBattle 也可能以 fromReplay=false 进入这里；
			// 必须主动识别 core.isReplaying()，否则插件动画会直接 callback，导致241没有真正投放。
			var replaying = !!fromReplay || !!(core.isReplaying && core.isReplaying());
			if (!replaying && !core.hasFlag('__MT4BossP4SetupDone__')) core.removeFlag('__MT4BossP4SetupReplayApplied__');
			core.setFlag('__MT4BossPhase__', 4);
			core.removeFlag('__MT4BossP4Unlocked__');
			// P4开局：只隐藏Canvas代码BOSS，地图上的263本体不隐藏，只保持无敌。
			this.ensureMT4BossCenterBlock();
			if (core.plugin && core.plugin.hideMT4BossBody) core.plugin.hideMT4BossBody();
			var self = this;
			var finishSetup = function () {
				if (core.drawMap) core.drawMap('MT4');
				if (core.plugin) {
					if (core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
					if (core.plugin.showMT4BossP4AltarFx) core.plugin.showMT4BossP4AltarFx();
				}
				if (core.updateStatusBar) core.updateStatusBar(false, true);
				if (core.updateDamage) core.updateDamage();
				if (!fromReplay) core.drawTip('三座献祭祭坛浮现。');
				if (callback) callback();
			};
			if (!core.hasFlag('__MT4BossP4SetupDone__')) {
				core.setFlag('__MT4BossP4SetupDone__', true);
				core.setFlag('__MT4BossP4SacrificeCount__', { hp: 0, atk: 0, def: 0 });
				if (replaying || !core.plugin || !core.plugin.playMT4BossP4SpawnSequence) {
					self.spawnMT4BossP4Minions(true);
					finishSetup();
				} else {
					var spawnMinions = function () {
						core.plugin.playMT4BossP4SpawnSequence(function () {
							finishSetup();
						});
					};
					if (core.plugin.playMT4BossP4AltarIntro) core.plugin.playMT4BossP4AltarIntro(spawnMinions);
					else spawnMinions();
				}
			} else {
				// 回放/读档如果已经有 setup 标记但图块没有投放，强制补投放；
				// 正常游戏不会重复投放，避免献祭后的怪被刷回来。
				if (replaying && !core.hasFlag('__MT4BossP4MinionsSpawned__') && !core.hasFlag('__MT4BossP4Unlocked__')) {
					self.spawnMT4BossP4Minions(true);
				}
				finishSetup();
			}
		},
        "getMT4BossP4Minions": function () {
			// 用户给的投放表；6,9是BOSS位置，改为6,10。
			// 这里用最终坐标表，避免重复坐标触发自动顺延造成位置不对：
			// 原来顺延到6,7的怪改到5,7；顺延到2,9的怪改到9,3；顺延到5,10的怪改到6,8。
			return [[3,3],[3,6],[3,9],[4,4],[8,4],[5,5],[7,5],[6,3],[6,6],[6,10],[7,7],[5,7],[4,8],[8,8],[9,3],[6,8],[9,9]];
		},
        "getMT4BossP4RequiredSacrificeCount": function () {
			return (this.getMT4BossP4Minions ? this.getMT4BossP4Minions().length : 17);
		},
        "getMT4BossP4SacrificeDoneCount": function () {
			var cnt = core.getFlag('__MT4BossP4SacrificeCount__', { hp: 0, atk: 0, def: 0 }) || {};
			return (parseInt(cnt.hp || 0) || 0) + (parseInt(cnt.atk || 0) || 0) + (parseInt(cnt.def || 0) || 0);
		},
        "spawnMT4BossP4Minions": function (force) {
			if (!core.status || core.status.floorId != 'MT4') return;
			if (core.hasFlag('__MT4BossP4MinionsSpawned__') && !force) return;
			// 录像/读档强制投放时先清掉旧241，避免 mt4BossP4Setup 被重复执行后顺延投放出一堆重复祭品。
			if (force && this.clearMT4BossP4SacrificeBlocks) this.clearMT4BossP4SacrificeBlocks();
			core.setFlag('__MT4BossP4MinionsSpawned__', true);
			var pts = this.getMT4BossP4Minions();
			for (var i = 0; i < pts.length; i++) {
				var p = pts[i];
				this.safeSetMT4Block(241, p[0], p[1], this.getMT4BossP4Fallbacks(p[0], p[1]));
			}
			if (core.updateStatusBar) core.updateStatusBar(false, true);
			if (core.updateDamage) core.updateDamage();
		},
        "applyMT4BossP4SetupReplay": function () {
			// 录像专用：直接恢复P4开局最终状态，不播放投放/祭坛动画。
			if (!core.status || core.status.floorId != 'MT4') return false;
			// 同一段回放里如果 setup 重复出现，后续直接忽略，避免把已献祭/已投放的241再次刷出来。
			if (core.hasFlag('__MT4BossP4SetupReplayApplied__')) {
				try { if (core.drawMap) core.drawMap('MT4'); } catch (e0) {}
				try { if (core.updateStatusBar) core.updateStatusBar(false, true); } catch (e1) {}
				try { if (core.updateDamage) core.updateDamage(); } catch (e2) {}
				return true;
			}
			core.setFlag('__MT4BossP4SetupReplayApplied__', true);
			core.setFlag('__MT4BossPhase__', 4);
			core.removeFlag('__MT4BossP4Unlocked__');
			if (!core.hasFlag('__MT4BossP4SetupDone__')) {
				core.setFlag('__MT4BossP4SetupDone__', true);
				core.setFlag('__MT4BossP4SacrificeCount__', { hp: 0, atk: 0, def: 0 });
			}
			this.ensureMT4BossCenterBlock();
			if (core.plugin && core.plugin.hideMT4BossBody) core.plugin.hideMT4BossBody();
			this.spawnMT4BossP4Minions(true);
			try { if (core.drawMap) core.drawMap('MT4'); } catch (e) {}
			try { if (core.updateStatusBar) core.updateStatusBar(false, true); } catch (e2) {}
			try { if (core.updateDamage) core.updateDamage(); } catch (e3) {}
			return true;
		},
        "getMT4BossP4Fallbacks": function (x, y) {
			var list = [];
			for (var r = 1; r <= 3; r++) {
				for (var dx = -r; dx <= r; dx++) for (var dy = -r; dy <= r; dy++) {
					if (Math.abs(dx) + Math.abs(dy) != r) continue;
					list.push([x + dx, y + dy]);
				}
			}
			return list;
		},
        "buildMT4BossP4ReadyRoute": function () {
			if (!core.status || core.status.floorId != 'MT4') return null;
			var hero = core.status.hero || {};
			var loc = hero.loc || {};
			var enemy = core.material && core.material.enemys && core.material.enemys.mt4BossP4;
			if (!enemy) {
				var block = core.getBlock(6, 9, 'MT4');
				var id = (block && block.event && block.event.id) || 263;
				enemy = core.material && core.material.enemys && core.material.enemys[id];
			}
			function num(v, d) { v = parseInt(v); return isNaN(v) ? (d || 0) : v; }
			function val(v) { return String(num(v, 0)); }
			var cnt = core.getFlag('__MT4BossP4SacrificeCount__', { hp: 0, atk: 0, def: 0 }) || {};
			var bossHp = enemy ? core.getEnemyValue(enemy, 'hp', 6, 9, 'MT4') : 0;
			var bossAtk = enemy ? core.getEnemyValue(enemy, 'atk', 6, 9, 'MT4') : 0;
			var bossDef = enemy ? core.getEnemyValue(enemy, 'def', 6, 9, 'MT4') : 0;
			return [
				'mt4BossP4Ready',
				val(loc.x), val(loc.y), loc.direction || 'up',
				val(hero.hp), val(hero.atk), val(hero.def), val(hero.mdef), val(hero.mana), val(hero.money), val(hero.exp),
				val(bossHp), val(bossAtk), val(bossDef), val(core.getFlag('__MT4BossHpSegments__', 3)),
				val(cnt.hp), val(cnt.atk), val(cnt.def)
			].join(':');
		},
        "recordMT4BossP4ReadyRoute": function () {
			if (!core.status || !core.status.route || core.hasFlag('__MT4BossP4ReadyRouteRecorded__')) return false;
			var action = this.buildMT4BossP4ReadyRoute ? this.buildMT4BossP4ReadyRoute() : null;
			if (!action) return false;
			var route = core.status.route;
			var last = route.length > 0 ? route[route.length - 1] : null;
			// 战斗触发时引擎已先记录方向；把状态动作插到方向前面，回放才能“跳到P4开打前，再执行这一步战斗”。
			if (['up', 'down', 'left', 'right'].indexOf(last) >= 0) {
				Array.prototype.splice.call(route, route.length - 1, 0, action);
				try { if (core.clearRouteFolding) core.clearRouteFolding(); } catch (e) {}
			} else {
				route.push(action);
			}
			core.setFlag('__MT4BossP4ReadyRouteRecorded__', true);
			return true;
		},
        "applyMT4BossP4ReadyReplay": function (action, fromReplay, noContinue) {
			if (!core.status || core.status.floorId != 'MT4') return false;
			var arr = String(action || '').split(':');
			if (arr.length < 18) return false;
			function num(i, d) { var v = parseInt(arr[i]); return isNaN(v) ? (d || 0) : v; }
			var hx = num(1, 6), hy = num(2, 10), dir = arr[3] || 'up';
			var hero = core.status.hero || {};
			hero.hp = Math.max(1, num(4, hero.hp || 1));
			hero.atk = Math.max(0, num(5, hero.atk || 0));
			hero.def = Math.max(0, num(6, hero.def || 0));
			hero.mdef = Math.max(0, num(7, hero.mdef || 0));
			hero.mana = Math.max(0, num(8, hero.mana || 0));
			hero.money = Math.max(0, num(9, hero.money || 0));
			hero.exp = Math.max(0, num(10, hero.exp || 0));
			core.setFlag('__MT4BossPhase__', 4);
			core.setFlag('__MT4BossHpSegments__', Math.max(0, Math.min(3, num(14, core.getFlag('__MT4BossHpSegments__', 3)))));
			core.setFlag('__MT4BossP4SetupDone__', true);
			core.setFlag('__MT4BossP4MinionsSpawned__', true);
			core.setFlag('__MT4BossP4Unlocked__', true);
			core.setFlag('__MT4BossP4ReadyRouteRecorded__', true);
			var hpCount = num(15, 0), atkCount = num(16, 0), defCount = num(17, 0);
			if (hpCount + atkCount + defCount <= 0 && this.getMT4BossP4RequiredSacrificeCount) hpCount = this.getMT4BossP4RequiredSacrificeCount();
			core.setFlag('__MT4BossP4SacrificeCount__', { hp: hpCount, atk: atkCount, def: defCount });
			flags.enemyOnPoint = flags.enemyOnPoint || {};
			flags.enemyOnPoint.MT4 = flags.enemyOnPoint.MT4 || {};
			flags.enemyOnPoint.MT4['6,9'] = { hp: Math.max(1, num(11, 1)), atk: Math.max(0, num(12, 0)), def: Math.max(0, num(13, 0)) };
			try { if (this.clearMT4BossP4SacrificeBlocks) this.clearMT4BossP4SacrificeBlocks(); } catch (e0) {}
			try { core.setBlock(263, 6, 9, 'MT4'); } catch (e1) {}
			try { core.setHeroLoc('x', hx); core.setHeroLoc('y', hy); core.setHeroLoc('direction', dir); core.drawHero(); } catch (e2) {}
			try { if (core.plugin && core.plugin.hideMT4BossP4AltarFx) core.plugin.hideMT4BossP4AltarFx(); } catch (e3) {}
			try { if (core.plugin && core.plugin.setMT4BossHpSegments) core.plugin.setMT4BossHpSegments(core.getFlag('__MT4BossHpSegments__', 3)); } catch (e4) {}
			try { if (core.plugin && core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene(); } catch (e5) {}
			try { if (core.drawMap) core.drawMap('MT4'); } catch (e6) {}
			try { if (core.updateStatusBar) core.updateStatusBar(false, true); } catch (e7) {}
			try { if (core.updateDamage) core.updateDamage(); } catch (e8) {}
			try { if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute(); } catch (e9) {}
			try { core.unlockControl(); } catch (e10) {}
			if (fromReplay && core.status && core.status.route && !noContinue) {
				core.status.route.push(action);
				setTimeout(function () { core.replay(); }, 0);
			}
			return true;
		},
        "skipMT4BossP4ProcessInReplay": function () {
			if (!(core.isReplaying && core.isReplaying()) || !core.status || !core.status.replay || !core.status.replay.toReplay) return false;
			var list = core.status.replay.toReplay;
			var idx = -1;
			for (var i = 0; i < list.length; i++) {
				if (typeof list[i] == 'string' && list[i].indexOf('mt4BossP4Ready:') == 0) { idx = i; break; }
			}
			if (idx < 0) return false;
			var skipped = list.splice(0, idx + 1);
			for (var si = 0; si < skipped.length; si++) {
				try { if (core.status && core.status.route) core.status.route.push(skipped[si]); } catch (e) {}
			}
			var readyAction = skipped[skipped.length - 1];
			return this.applyMT4BossP4ReadyReplay ? this.applyMT4BossP4ReadyReplay(readyAction, true, true) : false;
		},
        "handleMT4BossP4AfterStep": function () {
			if (!core.status || core.status.floorId != 'MT4') return false;
			if (core.getFlag('__MT4BossPhase__', 1) != 4) return false;
			var self = this;
			// 正常游戏恢复祭坛献祭动画/文字；录像会从P3后直接跳过整个P4推祭坛过程。
			this.checkMT4BossP4Sacrifices();
			// 阻击移动是异步动作，可能在 checkBlock 后才真正落位；延迟复查只负责补结果。
			[120, 320, 620, 980].forEach(function (delay) {
				setTimeout(function () {
					try {
						if (core.status && core.status.floorId == 'MT4' && core.getFlag('__MT4BossPhase__', 1) == 4) {
							self.checkMT4BossP4Sacrifices();
						}
					} catch (e) {}
				}, delay);
			});
			// 关键：不要 return true。否则会中断自动路线/录像，表现为推到第一个祭坛后录像停止。
			return false;
		},
        "isMT4BossP4SacrificeBlock": function (block) {
			if (!block || block.disable || !block.event) return false;
			// 注意：地图编号 241 在 maps.js 里对应的怪物 id 不是字符串 "241"，而是实际怪物 id。
			// 之前只判断 event.id == '241'，所以阻击推到祭坛后永远识别不到。
			if (block.id == 241 || block.event.number == 241 || block.event.id == 241) return true;
			var id = String(block.event.id || '');
			return id == '241' || (core.getNumberById && core.getNumberById(id) == 241);
		},
        "getMT4BossP4RepulseTarget": function (t) {
			if (!t) return null;
			var x = parseInt(t[0]), y = parseInt(t[1]), dir = t[3];
			if (isNaN(x) || isNaN(y)) return null;
			if (dir == 'up') y--;
			else if (dir == 'down') y++;
			else if (dir == 'left') x--;
			else if (dir == 'right') x++;
			else return null;
			return { fromX: parseInt(t[0]), fromY: parseInt(t[1]), x: x, y: y };
		},
        "buildMT4BossP4RepulseSacrificeRoute": function (t) {
			var target = this.getMT4BossP4RepulseTarget(t);
			if (!target) return null;
			var block = null;
			try { block = core.getBlock(target.fromX, target.fromY, 'MT4'); } catch (e) {}
			if (!this.isMT4BossP4SacrificeBlock(block)) return null;
			var altars = this.getMT4BossP4Altars();
			for (var i = 0; i < altars.length; i++) {
				var a = altars[i];
				if (a.x == target.x && a.y == target.y) {
					return 'mt4BossP4RepulseSacrifice:' + a.key + ':' + target.fromX + ':' + target.fromY + ':' + target.x + ':' + target.y;
				}
			}
			return null;
		},
        "applyMT4BossP4RepulseSacrificeReplay": function (action) {
			// mt4BossP4RepulseSacrifice:key:fromX:fromY:toX:toY
			if (!core.status || core.status.floorId != 'MT4') return false;
			var arr = String(action || '').split(':');
			var key = arr[1] || '';
			var fx = parseInt(arr[2]), fy = parseInt(arr[3]), tx = parseInt(arr[4]), ty = parseInt(arr[5]);
			var altars = this.getMT4BossP4Altars();
			var altar = null;
			for (var i = 0; i < altars.length; i++) if (altars[i].key == key) { altar = altars[i]; break; }
			if (!altar) return false;

			// 回放时阻击动画/异步时序可能还没把怪移动到祭坛；源点和目标点都清一次。
			var removed = false;
			var tryRemove = function (x, y, self) {
				if (isNaN(x) || isNaN(y)) return;
				try {
					var b = core.getBlock(x, y, 'MT4');
					if (self.isMT4BossP4SacrificeBlock(b)) {
						core.removeBlock(x, y, 'MT4');
						removed = true;
					}
				} catch (e) {}
			};
			tryRemove(tx, ty, this);
			tryRemove(fx, fy, this);
			tryRemove(altar.x, altar.y, this);

			// 如果本次动作已经被后面的状态同步处理过，避免重复加计数。
			var lastKey = '__MT4BossP4ReplayPlanDone_' + key + '_' + fx + '_' + fy + '_' + tx + '_' + ty + '__';
			if (!core.hasFlag(lastKey)) {
				this.applyMT4BossP4Sacrifice(altar.effect);
				core.setFlag(lastKey, true);
			}
			try { if (core.drawMap) core.drawMap('MT4'); } catch (e2) {}
			try { if (core.updateStatusBar) core.updateStatusBar(false, true); } catch (e3) {}
			try { if (core.updateDamage) core.updateDamage(); } catch (e4) {}
			this.checkMT4BossP4Unlock();
			return true;
		},
        "applyMT4BossP4SacrificeReplay": function (key) {
			if (!core.status || core.status.floorId != 'MT4') return false;
			var altars = this.getMT4BossP4Altars();
			var altar = null;
			for (var i = 0; i < altars.length; i++) if (altars[i].key == key) { altar = altars[i]; break; }
			if (!altar) return false;
			try {
				var b = core.getBlock(altar.x, altar.y, 'MT4');
				if (this.isMT4BossP4SacrificeBlock(b)) core.removeBlock(altar.x, altar.y, 'MT4');
			} catch (e) {}
			this.applyMT4BossP4Sacrifice(altar.effect);
			if (core.drawMap) core.drawMap('MT4');
			if (core.updateDamage) core.updateDamage();
			this.checkMT4BossP4Unlock();
			return true;
		},
        "applyMT4BossP4SacrificeReplay2": function (action) {
			// mt4BossP4Sacrifice2:key:hpCount:atkCount:defCount:hp:atk:def
			if (!core.status || core.status.floorId != 'MT4') return false;
			var arr = String(action || '').split(':');
			var key = arr[1] || '';
			var hpCount = parseInt(arr[2]), atkCount = parseInt(arr[3]), defCount = parseInt(arr[4]);
			var hp = arr[5], atk = arr[6], def = arr[7];
			var altars = this.getMT4BossP4Altars();
			var altar = null;
			for (var i = 0; i < altars.length; i++) if (altars[i].key == key) { altar = altars[i]; break; }
			if (!altar) return false;
			try {
				var b = core.getBlock(altar.x, altar.y, 'MT4');
				if (this.isMT4BossP4SacrificeBlock(b)) core.removeBlock(altar.x, altar.y, 'MT4');
			} catch (e) {}
			flags.enemyOnPoint = flags.enemyOnPoint || {};
			flags.enemyOnPoint.MT4 = flags.enemyOnPoint.MT4 || {};
			var info = flags.enemyOnPoint.MT4['6,9'] || {};
			if (hp != null && hp !== '' && hp !== '-') info.hp = Math.max(1, parseInt(hp));
			if (atk != null && atk !== '' && atk !== '-') info.atk = Math.max(0, parseInt(atk));
			if (def != null && def !== '' && def !== '-') info.def = Math.max(0, parseInt(def));
			flags.enemyOnPoint.MT4['6,9'] = info;
			core.setFlag('__MT4BossP4SacrificeCount__', {
				hp: isNaN(hpCount) ? 0 : hpCount,
				atk: isNaN(atkCount) ? 0 : atkCount,
				def: isNaN(defCount) ? 0 : defCount
			});
			if (core.drawMap) core.drawMap('MT4');
			if (core.updateDamage) core.updateDamage();
			this.checkMT4BossP4Unlock();
			return true;
		},
        "buildMT4BossP4SacrificeRoute": function (key) {
			var cnt = core.getFlag('__MT4BossP4SacrificeCount__', { hp: 0, atk: 0, def: 0 });
			var info = (((flags.enemyOnPoint || {}).MT4 || {})['6,9'] || {});
			function val(v) { return v == null || isNaN(parseInt(v)) ? '-' : String(parseInt(v)); }
			return 'mt4BossP4Sacrifice2:' + key + ':' +
				parseInt(cnt.hp || 0) + ':' + parseInt(cnt.atk || 0) + ':' + parseInt(cnt.def || 0) + ':' +
				val(info.hp) + ':' + val(info.atk) + ':' + val(info.def);
		},
        "getMT4BossP4AliveSacrificePositions": function () {
			var result = [];
			try { if (core.extractBlocks) core.extractBlocks('MT4'); } catch (e) {}
			var blocks = (((core.status.maps || {}).MT4 || {}).blocks || []);
			for (var i = 0; i < blocks.length; i++) {
				var b = blocks[i];
				if (this.isMT4BossP4SacrificeBlock(b)) result.push([b.x, b.y]);
			}
			result.sort(function (a, b) { return a[1] == b[1] ? a[0] - b[0] : a[1] - b[1]; });
			return result;
		},
        "encodeMT4BossP4Positions": function (positions) {
			positions = positions || [];
			if (positions.length == 0) return '-';
			return positions.map(function (p) { return parseInt(p[0]) + '_' + parseInt(p[1]); }).join(',');
		},
        "decodeMT4BossP4Positions": function (text) {
			if (!text || text == '-') return [];
			return String(text).split(',').map(function (s) {
				var a = s.split('_');
				return [parseInt(a[0]), parseInt(a[1])];
			}).filter(function (p) { return !isNaN(p[0]) && !isNaN(p[1]); });
		},
        "clearMT4BossP4SacrificeBlocks": function () {
			try { if (core.extractBlocks) core.extractBlocks('MT4'); } catch (e) {}
			var blocks = (((core.status.maps || {}).MT4 || {}).blocks || []).slice();
			for (var i = 0; i < blocks.length; i++) {
				var b = blocks[i];
				if (!this.isMT4BossP4SacrificeBlock(b)) continue;
				try { core.removeBlock(b.x, b.y, 'MT4'); } catch (e) { try { core.hideBlock(b.x, b.y, 'MT4'); } catch (e2) {} }
			}
		},
        "restoreMT4BossP4SacrificePositions": function (positions) {
			this.clearMT4BossP4SacrificeBlocks();
			positions = positions || [];
			for (var i = 0; i < positions.length; i++) {
				var p = positions[i];
				this.safeSetMT4Block(241, p[0], p[1], this.getMT4BossP4Fallbacks(p[0], p[1]));
			}
		},
        "buildMT4BossP4SacrificeStateRoute": function (key) {
			var cnt = core.getFlag('__MT4BossP4SacrificeCount__', { hp: 0, atk: 0, def: 0 });
			var info = (((flags.enemyOnPoint || {}).MT4 || {})['6,9'] || {});
			var positions = this.getMT4BossP4AliveSacrificePositions();
			function val(v) { return v == null || isNaN(parseInt(v)) ? '-' : String(parseInt(v)); }
			return 'mt4BossP4SacrificeState:' + key + ':' +
				parseInt(cnt.hp || 0) + ':' + parseInt(cnt.atk || 0) + ':' + parseInt(cnt.def || 0) + ':' +
				val(info.hp) + ':' + val(info.atk) + ':' + val(info.def) + ':' + this.encodeMT4BossP4Positions(positions);
		},
        "applyMT4BossP4SacrificeStateReplay": function (action) {
			// mt4BossP4SacrificeState:key:hpCount:atkCount:defCount:hp:atk:def:x_y,x_y
			if (!core.status || core.status.floorId != 'MT4') return false;
			var arr = String(action || '').split(':');
			var hpCount = parseInt(arr[2]), atkCount = parseInt(arr[3]), defCount = parseInt(arr[4]);
			var hp = arr[5], atk = arr[6], def = arr[7];
			flags.enemyOnPoint = flags.enemyOnPoint || {};
			flags.enemyOnPoint.MT4 = flags.enemyOnPoint.MT4 || {};
			var info = flags.enemyOnPoint.MT4['6,9'] || {};
			if (hp != null && hp !== '' && hp !== '-') info.hp = Math.max(1, parseInt(hp));
			if (atk != null && atk !== '' && atk !== '-') info.atk = Math.max(0, parseInt(atk));
			if (def != null && def !== '' && def !== '-') info.def = Math.max(0, parseInt(def));
			flags.enemyOnPoint.MT4['6,9'] = info;
			core.setFlag('__MT4BossP4SacrificeCount__', {
				hp: isNaN(hpCount) ? 0 : hpCount,
				atk: isNaN(atkCount) ? 0 : atkCount,
				def: isNaN(defCount) ? 0 : defCount
			});

			// 回放状态同步：恢复“本次献祭后”的剩余241坐标。
			// 这样后续路线看到的地图与正常游戏一致，不会因为祭品残留而卡死。
			var positions = this.decodeMT4BossP4Positions(arr[8] || '-');
			this.restoreMT4BossP4SacrificePositions(positions);
			try { if (core.drawMap) core.drawMap('MT4'); } catch (e) {}
			try { if (core.plugin && core.plugin.showMT4BossP4AltarFx && !core.getFlag('__MT4BossP4Unlocked__')) core.plugin.showMT4BossP4AltarFx(); } catch (e2) {}
			try { if (core.updateStatusBar) core.updateStatusBar(false, true); } catch (e3) {}
			try { if (core.updateDamage) core.updateDamage(); } catch (e4) {}
			this.checkMT4BossP4Unlock();
			return true;
		},
        "checkMT4BossP4Sacrifices": function () {
			if (!core.status || core.status.floorId != 'MT4') return false;
			// 回放时只吃 mt4BossP4SacrificeState，避免阻击动画/异步检测重复改变结果。
			if (core.isReplaying && core.isReplaying()) return false;
			try { if (core.extractBlocks) core.extractBlocks('MT4'); } catch (e) {}
			var changed = false;
			var altars = this.getMT4BossP4Altars();
			var blocks = (((core.status.maps || {}).MT4 || {}).blocks || []);
			for (var i = 0; i < altars.length; i++) {
				var a = altars[i];
				var b = core.getBlock(a.x, a.y, 'MT4');
				// 某些异步移动刚结束时 getBlock 可能还没刷新；再从 blocks 里按坐标找一次。
				if (!this.isMT4BossP4SacrificeBlock(b)) {
					b = null;
					for (var bi = 0; bi < blocks.length; bi++) {
						var one = blocks[bi];
						if (one && one.x == a.x && one.y == a.y && this.isMT4BossP4SacrificeBlock(one)) { b = one; break; }
					}
				}
				if (!this.isMT4BossP4SacrificeBlock(b)) continue;
				try { core.removeBlock(a.x, a.y, 'MT4'); } catch (e) { try { core.hideBlock(a.x, a.y, 'MT4'); } catch (e2) {} }
				this.applyMT4BossP4Sacrifice(a.effect);
				if (!(core.isReplaying && core.isReplaying())) {
					try { if (core.plugin && core.plugin.playMT4BossP4SacrificeFx) core.plugin.playMT4BossP4SacrificeFx(a); } catch (e3) {}
					try { core.drawText(a.text || '祭坛吞噬祭品，龙魂王力量被削弱。'); } catch (e4) { try { core.drawTip(a.text || '祭坛吞噬祭品，龙魂王力量被削弱。'); } catch (e5) {} }
				}
				changed = true;
			}
			if (changed) {
				if (core.drawMap) core.drawMap('MT4');
				if (core.plugin && core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
				if (core.updateStatusBar) core.updateStatusBar(false, true);
				if (core.updateDamage) core.updateDamage();
				this.checkMT4BossP4Unlock();
			}
			return changed;
		},
        "applyMT4BossP4Sacrifice": function (effect) {
			flags.enemyOnPoint = flags.enemyOnPoint || {};
			flags.enemyOnPoint.MT4 = flags.enemyOnPoint.MT4 || {};
			var key = '6,9';
			var info = flags.enemyOnPoint.MT4[key] || {};
			var block = core.getBlock(6, 9);
			var enemyId = (block && block.event && block.event.id) || 263;
			var enemy = core.material && core.material.enemys && core.material.enemys[enemyId];
			if (!enemy) return;
			if (effect == 'hp20') info.hp = Math.max(1, (core.getEnemyValue(enemy, 'hp', 6, 9, 'MT4') || 1) - 20);
			if (effect == 'atk1') info.atk = Math.max(0, (core.getEnemyValue(enemy, 'atk', 6, 9, 'MT4') || 0) - 1);
			if (effect == 'def1') info.def = Math.max(0, (core.getEnemyValue(enemy, 'def', 6, 9, 'MT4') || 0) - 1);
			flags.enemyOnPoint.MT4[key] = info;
			var cnt = core.getFlag('__MT4BossP4SacrificeCount__', { hp: 0, atk: 0, def: 0 });
			if (effect == 'hp20') cnt.hp = (cnt.hp || 0) + 1;
			if (effect == 'atk1') cnt.atk = (cnt.atk || 0) + 1;
			if (effect == 'def1') cnt.def = (cnt.def || 0) + 1;
			core.setFlag('__MT4BossP4SacrificeCount__', cnt);
			if (core.updateDamage) core.updateDamage();
		},
        "hasMT4BossP4BlockingEnemies": function () {
			if (!core.status || core.status.floorId != 'MT4') return true;
			try { if (core.extractBlocks) core.extractBlocks('MT4'); } catch (e) {}
			var blocks = ((core.status.maps || {}).MT4 || {}).blocks || [];
			for (var i = 0; i < blocks.length; i++) {
				var b = blocks[i];
				if (!b || b.disable || !b.event) continue;
				if (!b.event.cls || b.event.cls.indexOf('enemy') !== 0) continue;
				if (b.x == 6 && b.y == 9) continue;
				return true;
			}
			return false;
		},
        "canAttackMT4BossP4": function () {
			if (!core.status || core.status.floorId != 'MT4') return false;
			if (core.getFlag('__MT4BossPhase__', 1) != 4) return false;
			// 必须献祭满P4投放总数，才允许解除无敌。
			// 录像回放会跳过推阻击过程并清空241，不能仅靠“地图无怪”判断，否则第一只献祭后会提前解锁。
			if (this.getMT4BossP4SacrificeDoneCount && this.getMT4BossP4RequiredSacrificeCount
				&& this.getMT4BossP4SacrificeDoneCount() < this.getMT4BossP4RequiredSacrificeCount()) {
				core.removeFlag('__MT4BossP4Unlocked__');
				return false;
			}
			if (this.hasMT4BossP4BlockingEnemies()) {
				core.removeFlag('__MT4BossP4Unlocked__');
				return false;
			}
			core.setFlag('__MT4BossP4Unlocked__', true);
			try { core.setBlock(263, 6, 9, 'MT4'); } catch (e) {}
			return true;
		},
        "checkMT4BossP4Unlock": function () {
			if (!core.status || core.status.floorId != 'MT4') return false;
			if (core.getFlag('__MT4BossPhase__', 1) != 4) return false;
			if (!this.canAttackMT4BossP4()) return false;
			// 所有祭品献祭完成后，解除P4无敌，并让Canvas代码BOSS重新出现。
			this.ensureMT4BossCenterBlock();
			if (core.drawMap) core.drawMap('MT4');
			if (core.plugin && core.plugin.hideMT4BossP4AltarFx) core.plugin.hideMT4BossP4AltarFx();
			if (core.plugin && core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
			if (core.updateStatusBar) core.updateStatusBar(false, true);
			if (core.updateDamage) core.updateDamage();
			core.drawTip('祭坛献祭完成，龙魂王无敌解除。');
			return true;
		},
        "ensureMT4BossP2Boss": function (keepInvincible) {
			// 只把中心本体切换为编号261；临时无敌由 beforeBattle 拦截，不写怪物属性/point special。
			if (!core.status || core.status.floorId != 'MT4') return;
			try { core.setBlock(261, 6, 9, 'MT4'); } catch (e) {}
		},
        "getMT4BossP2SummonWaves": function () {
			return [
				{ title: '屠龙护法', blocks: [[235, 5, 10], [235, 7, 10], [235, 0, 11], [235, 12, 11]] },
				{ title: '灭虎护法', blocks: [[236, 4, 11], [236, 8, 11]] },
				{ title: '屠虎护法', blocks: [[237, 3, 12], [237, 9, 12]] },
				{ title: '豹跃护法', blocks: [[238, 2, 11], [238, 10, 11]] },
				{ title: '魂锁护法', blocks: [[239, 1, 10], [239, 11, 10]] },
				{ title: '共生护法', blocks: [[240, 1, 12], [240, 11, 12], [240, 3, 9], [240, 9, 9]] },
				// 红宝石左侧从(3,9)移到(2,9)，避免和共生护法(3,9)冲突。
				{ title: '赤晶赐福', blocks: [[27, 2, 9], [27, 10, 9]] },
				{ title: '蓝晶赐福', blocks: [[28, 0, 12], [28, 12, 12]] }
			];
		},
        "spawnMT4BossP2Wave": function (waveIndex) {
			if (!core.status || core.status.floorId != 'MT4') return;
			var waves = this.getMT4BossP2SummonWaves();
			var wave = waves[waveIndex];
			if (!wave) return;
			(wave.blocks || []).forEach(function (one) {
				try { core.setBlock(one[0], one[1], one[2], 'MT4'); } catch (e) {}
			});
			if (core.updateStatusBar) core.updateStatusBar(false, true);
		},
        "finalizeMT4BossP2Summons": function (fromReplay) {
			if (!core.status || core.status.floorId != 'MT4') return;
			core.setFlag('__MT4BossPhase__', 2);
			core.setFlag('__MT4BossP2SummonsDone__', true);
			this.ensureMT4BossP2Boss(true);
			if (this.removeMT4BossP3TopBarriers) this.removeMT4BossP3TopBarriers();
			var waves = this.getMT4BossP2SummonWaves();
			for (var i = 0; i < waves.length; i++) this.spawnMT4BossP2Wave(i);
			if (core.plugin && core.plugin.ensureMT4BossScene) core.plugin.ensureMT4BossScene();
			if (core.drawMap) core.drawMap('MT4');
			if (core.updateDamage) core.updateDamage();
			if (!fromReplay) core.drawTip('护法投送完毕');
		},
        "checkMT4BossP2Unlock": function () {
			if (!core.status || core.status.floorId != 'MT4') return false;
			if (core.getFlag('__MT4BossPhase__', 1) != 2) return false;
			if (core.hasFlag('__MT4BossP2Unlocked__')) return true;
			var blocks = core.getMapBlocksObj('MT4') || {};
			for (var key in blocks) {
				var b = blocks[key];
				if (!b || b.disable || !b.event) continue;
				if (!b.event.cls || b.event.cls.indexOf('enemy') !== 0) continue;
				// 中心(6,9)是P2本体，忽略；除此之外任何活着的敌人都算护法未清完。
				if (b.x == 6 && b.y == 9) continue;
				return false;
			}
			core.setFlag('__MT4BossP2Unlocked__', true);
			if (core.updateStatusBar) core.updateStatusBar(false, true);
			if (core.updateDamage) core.updateDamage();
			core.drawTip('护法尽灭，龙魂王无敌解除。');
			return true;
		},
        "wrapLeopardLeapCoord": function (value, delta, size, min) {
			// 豹跃边界穿越，按“逐格走3步”计算，而不是简单取模。
			// 上下左右都按 0..12 循环，允许落到第0行和第0列。
			value = parseInt(value || 0);
			delta = parseInt(delta || 0);
			size = parseInt(size || 13);
			if (min == null) min = 0;
			min = parseInt(min || 0);
			var max = Math.max(min, size - 1);
			var ans = value;
			if (ans < min) ans = min;
			if (ans > max) ans = max;
			var step = delta >= 0 ? 1 : -1;
			for (var i = 0; i < Math.abs(delta); i++) {
				ans += step;
				if (ans < min) ans = max;
				if (ans > max) ans = min;
			}
			return ans;
		},
        "getLeopardLeapTargets": function (x, y, floorId) {
			floorId = floorId || core.status.floorId;
			var map = core.floors[floorId] || {};
			var width = map.width || core.bigmap.width || 13;
			var height = map.height || core.bigmap.height || 13;
			return [
				{ name: '上', loc: [x, this.wrapLeopardLeapCoord(y, -3, height, 0)] },
				{ name: '下', loc: [x, this.wrapLeopardLeapCoord(y, 3, height, 0)] },
				{ name: '左', loc: [this.wrapLeopardLeapCoord(x, -3, width, 0), y] },
				{ name: '右', loc: [this.wrapLeopardLeapCoord(x, 3, width, 0), y] }
			];
		},
        "triggerLeopardLeap": function (enemyId, x, y, floorId) {
			// 32 豹跃：击杀后地图上出现4个黄色落点框。
			// 录像模式下不再生成选择层，也不再消费 choices/leopard 之类动作；
			// 只让 afterBattle 暂停清层，然后由下一条 leopardTo:x:y 录像动作直接跳到最终坐标。
			floorId = floorId || core.status.floorId;
			if (core.isReplaying && core.isReplaying()) {
				return true;
			}
			var targets = this.getLeopardLeapTargets(x, y, floorId);
			return this.startLeopardLeapSelection(enemyId, x, y, floorId, targets);
		},
        "startLeopardLeapSelection": function (enemyId, x, y, floorId, targets) {
			if (!core.status) return false;
			core.stopAutomaticRoute();
			core.clearContinueAutomaticRoute();
			core.status.__leopardLeapSelector = {
				active: true,
				enemyId: enemyId,
				floorId: floorId || core.status.floorId,
				x: x,
				y: y,
				targets: targets || []
			};
			core.status.lockControl = true;
			if (core.plugin && core.plugin.drawLeopardLeapSelector) core.plugin.drawLeopardLeapSelector();
			core.drawTip('豹跃：点击黄色框选择落点', enemyId);
			return true;
		},
        "handleLeopardLeapClick": function (x, y, px, py) {
			var data = core.status && core.status.__leopardLeapSelector;
			if (!data || !data.active) return false;
			// x/y 是屏幕格坐标；大地图滚动时需要换算成地图坐标。
			var mapX = x + ((core.bigmap && core.bigmap.posX) || 0);
			var mapY = y + ((core.bigmap && core.bigmap.posY) || 0);
			for (var i = 0; i < data.targets.length; i++) {
				var loc = data.targets[i].loc || [];
				if (loc[0] == mapX && loc[1] == mapY) {
					this.selectLeopardLeapTarget(i);
					return true;
				}
			}
			// 豹跃选择期间，点击非黄色框不移动勇士。
			return true;
		},
        "selectLeopardLeapTarget": function (index) {
			var data = core.status && core.status.__leopardLeapSelector;
			if (!data || !data.active) return false;
			index = parseInt(index) || 0;
			if (index < 0) index = 0;
			if (index >= data.targets.length) index = data.targets.length - 1;
			var one = data.targets[index];
			if (!one || !one.loc) return false;
			var tx = one.loc[0], ty = one.loc[1];

			// 录像只记录最终落点，不记录黄框选择和跳跃动画。
			// 回放时由 leopardTo:x:y 直接瞬移到最终坐标。
			var action = 'leopardTo:' + tx + ':' + ty;
			if (core.status && core.status.route) core.status.route.push(action);

			return this.performLeopardLeapTo(tx, ty, false, action);
		},
        "performLeopardLeapTo": function (tx, ty, fromReplay, routeAction) {
			tx = parseInt(tx);
			ty = parseInt(ty);
			if (isNaN(tx) || isNaN(ty)) return false;

			// 回放时该动作是正式录像动作，需要写入 route，保证录像结束时校验一致。
			if (fromReplay && core.status && core.status.route && routeAction) {
				core.status.route.push(routeAction);
			}

			this.clearLeopardLeapSelector();

			// 直接设置最终坐标；不播放跳跃动画，避免录像时序卡住。
			try { core.clearMap('hero'); } catch (e) {}
			core.setHeroLoc('x', tx);
			core.setHeroLoc('y', ty);
			core.drawHero();
			core.updateStatusBar(false, true);

			// 豹跃落地后再检测清层自动上楼。
			var changed = false;
			if (core.control && core.control.controldata && core.control.controldata.checkFloorClearAndGoNext) {
				changed = core.control.controldata.checkFloorClearAndGoNext(core.status.floorId);
			}

			// 回放中，如果没有插入上楼等事件，则继续播放下一步。
			if (fromReplay && !changed && !(core.status && core.status.event && core.status.event.id)) {
				setTimeout(function () { core.replay(); }, 0);
			}
			return true;
		},
        "clearLeopardLeapSelector": function () {
			// 清理豹跃选择层：不仅清空，还直接删除动态 canvas，避免半黑遮罩/黄框残留。
			if (core.status) {
				delete core.status.__leopardLeapSelector;
				core.status.lockControl = false;
			}
			try { core.clearMap('leopardLeap'); } catch (e) {}
			try { core.deleteCanvas('leopardLeap'); } catch (e) {}
		},
        "getNearestSameNameMonster": function (enemyId, x, y, floorId) {
			// 穿梭目标：同楼层、仍存活、同名怪物；距离=|dx|+|dy|，同距离优先横向距离更近。
			floorId = floorId || core.status.floorId;
			if (!floorId || x == null || y == null || !core.material || !core.material.enemys) return null;
			var src = core.material.enemys[enemyId];
			if (!src) return null;
			var srcName = core.getEnemyValue ? core.getEnemyValue(src, 'name', x, y, floorId) : src.name;
			if (srcName == null) return null;
			var blocks = core.getMapBlocksObj ? (core.getMapBlocksObj(floorId) || {}) : {};
			var best = null;
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var id = block.event.id;
				var enemy = core.material.enemys[id];
				if (!enemy) continue;
				var cls = block.event.cls || '';
				if (cls.indexOf('enemy') !== 0) continue;
				// 被击杀的原坐标已经 remove/hide；这里仍保险排除一次。
				if (block.x == x && block.y == y) continue;
				var name = core.getEnemyValue ? core.getEnemyValue(enemy, 'name', block.x, block.y, floorId) : enemy.name;
				if (name !== srcName) continue;
				var dx = Math.abs(block.x - x), dy = Math.abs(block.y - y);
				var dist = dx + dy;
				var cand = { x: block.x, y: block.y, enemyId: id, name: name, dist: dist, dx: dx, dy: dy };
				if (!best
					|| cand.dist < best.dist
					|| (cand.dist == best.dist && cand.dx < best.dx)
					|| (cand.dist == best.dist && cand.dx == best.dx && cand.dy < best.dy)
					|| (cand.dist == best.dist && cand.dx == best.dx && cand.dy == best.dy && cand.x < best.x)
					|| (cand.dist == best.dist && cand.dx == best.dx && cand.dy == best.dy && cand.x == best.x && cand.y < best.y)) {
					best = cand;
				}
			}
			return best;
		},
        "triggerMonsterShuttle": function (enemyId, x, y, floorId) {
			floorId = floorId || core.status.floorId;

			// 录像回放：完全镜像豹跃 triggerLeopardLeap 的处理方式。
			// afterBattle 只负责"暂停清层/不继续自动路线"，真正的传送交给随后的
			// shuttleTo:... 录像动作（installShuttleReplayAction 注册的 handler）来做。
			// 关键：这里绝对不要消费队列、不要在战斗结算里 setHeroLoc、更不要调用
			// stopAutomaticRoute —— 那会在战斗收尾时把 hero 位置覆盖、或打断引擎录像续播，
			// 正是"打了 A 直接到 C、B 没打"的根因。
			if (core.isReplaying && core.isReplaying()) {
				var list = (core.status && core.status.replay && core.status.replay.toReplay) || [];
				// 真实游戏里：击杀穿梭怪后只有"存在可传送的同名目标"才会记录一条 shuttleTo，
				// 并且这条 shuttleTo 紧跟在击杀动作之后、就在队头（index 0）。
				var next = list.length ? list[0] : '';
				if (typeof next == 'string' && next.indexOf('shuttleTo:') == 0) {
					// 此次击杀在真实游戏里发生了穿梭：返回 true 让 afterBattle 像豹跃一样提前 return
					// （暂停清层/自动路线），随后引擎自然弹出这条 shuttleTo 交给 handler 完成传送。
					return true;
				}
				// 队头不是 shuttleTo => 真实游戏此次击杀【没有】可传送的同名目标
				// （典型情况：这是穿梭链的最后一只同名怪，打完已无同名怪可传送）。
				// 因此不传送，返回 false 让 afterBattle 正常清层并 continueAutomaticRoute 续播，
				// 否则会误判成"缺同步点"而停掉回放，导致打完最后一只穿梭怪后不再往后走。
				return false;
			}

			var realTarget = this.getNearestSameNameMonster(enemyId, x, y, floorId);
			if (!realTarget) return false;

			// 新版把"被击杀怪坐标 + 穿梭目标坐标 + 目标怪物编号"一起记录。
			// 目标怪物编号只做校验/调试，不影响旧格式兼容。
			var action = 'shuttleTo:' + floorId + ':' + x + ':' + y + ':' + realTarget.x + ':' + realTarget.y + ':' + enemyId + ':' + realTarget.enemyId;
			if (core.status && core.status.route) core.status.route.push(action);
			return this.performMonsterShuttleTo(realTarget.x, realTarget.y, false, action, {
				floorId: floorId,
				enemyId: enemyId,
				fromX: x,
				fromY: y,
				targetEnemyId: realTarget.enemyId,
				name: realTarget.name
			});
		},
        "performMonsterShuttleTo": function (tx, ty, fromReplay, routeAction, info) {
			tx = parseInt(tx); ty = parseInt(ty);
			if (isNaN(tx) || isNaN(ty)) return false;
			info = info || {};
			var floorId = info.floorId || core.status.floorId;

			// 回放时该动作是正式录像动作，需要写入 route，保证录像结束时校验一致。
			if (fromReplay && core.status && core.status.route && routeAction) {
				core.status.route.push(routeAction);
			}

			// 关键修复：stopAutomaticRoute / clearContinueAutomaticRoute / lockControl
			// 只在"真实游戏"里调用。回放时调用 stopAutomaticRoute 会打断引擎的录像续播，
			// 导致后续 'up'/'down' 不被处理（豹跃 performLeopardLeapTo 也不调这些，所以豹跃正常）。
			if (!fromReplay) {
				try { core.stopAutomaticRoute(); } catch (e0) {}
				try { if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute(); } catch (e1) {}
				try { core.lockControl(); } catch (e2) { if (core.status) core.status.lockControl = true; }
			}

			var finish = function () {
				if (!core.status || core.status.gameOver) return;
				if (floorId && core.status.floorId !== floorId) {
					if (!fromReplay) { try { core.unlockControl(); } catch (e3) { if (core.status) core.status.lockControl = false; } }
					else { setTimeout(function () { core.replay(); }, 0); }
					return;
				}
				try { core.clearMap('hero'); } catch (e4) {}
				core.setHeroLoc('x', tx);
				core.setHeroLoc('y', ty);
				try { core.drawHero(); } catch (e5) {}
				try { core.updateStatusBar(false, true); } catch (e6) {}
				try { core.drawTip('穿梭：与最近的同名怪物重合', info.enemyId || 'fly'); } catch (e7) {}

				// 只传送到目标怪物格，与该怪物重合；不主动触发战斗。
				// 玩家之后可以自行移动/处理，怪物仍保留在该格。
				if (!fromReplay) {
					try { core.unlockControl(); } catch (e8) { if (core.status) core.status.lockControl = false; }
					try { if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute(); } catch (e9) {}
				}
				// 回放：【不要】在这里再调用 setTimeout(core.replay)。
				// 击杀穿梭怪的那一步移动动作，其引擎自带的移动回调链会在本 handler 返回后
				// 继续驱动后续的 up/down 等动作，并且会正确等待 heroMoving 归零再走下一步。
				// 若这里再补一个 setTimeout(core.replay)，就会形成“第二驱动”和引擎链抢时序：
				// 一个弹出 up（开始移动，heroMoving=5），另一个紧接着弹出 down，
				// 此时 heroMoving>0，down 被引擎吞掉，导致中间这只穿梭怪没被撞到（B 被跳过）。
				// 单驱动即可，交给引擎链。
				//
				// 看门狗兜底：仅当回放“真的卡住”（一段时间后队列长度没变 && hero 静止 && 仍在回放）
				// 才补一次 replay。正常情况下引擎链早已推进、队列长度变化，看门狗就什么都不做，
				// 因此不会形成第二驱动、不会和引擎链抢时序。
				else if (!(core.status && core.status.event && core.status.event.id)) {
					var __rp = (core.status.replay && core.status.replay.toReplay) || null;
					var snapshotLen = __rp ? __rp.length : -1;
					var watchdog = function () {
						if (!core.isReplaying || !core.isReplaying()) return;
						if (core.status && core.status.heroMoving > 0) { setTimeout(watchdog, 60); return; }
						if (core.status && core.status.event && core.status.event.id) return;
						var rp2 = (core.status.replay && core.status.replay.toReplay) || null;
						var nowLen = rp2 ? rp2.length : -1;
						if (nowLen === snapshotLen && nowLen > 0) {
							core.replay();
						}
					};
					setTimeout(watchdog, 150);
				}
			};

			// 正常游戏给一点点停顿，让插件特效有起点收束和光束感；录像直接跳过。
			if (fromReplay) finish();
			else setTimeout(finish, 180);
			return true;
		},
        "getMonsterDodgeTarget": function (x, y, floorId) {
			// 躲避目标：只找相邻空地，优先级 上、下、左、右；不能躲到勇士当前格。
			floorId = floorId || core.status.floorId;
			if (!floorId || x == null || y == null || !core.floors || !core.floors[floorId]) return null;
			var width = core.floors[floorId].width || core.__SIZE__ || 13;
			var height = core.floors[floorId].height || core.__SIZE__ || 13;
			var heroX = core.getHeroLoc ? core.getHeroLoc('x') : ((core.status.hero || {}).loc || {}).x;
			var heroY = core.getHeroLoc ? core.getHeroLoc('y') : ((core.status.hero || {}).loc || {}).y;
			var dirs = [
				{ dx: 0, dy: -1 },
				{ dx: 0, dy: 1 },
				{ dx: -1, dy: 0 },
				{ dx: 1, dy: 0 }
			];
			for (var i = 0; i < dirs.length; i++) {
				var nx = x + dirs[i].dx, ny = y + dirs[i].dy;
				if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
				if (nx == heroX && ny == heroY) continue;
				if (core.getBlock(nx, ny, floorId) == null) return { x: nx, y: ny, dir: i };
			}
			return null;
		},
        "__replayStallWatchdog": function () {
			// 回放专用 stall-only 看门狗：仅当回放“真卡住”
			// （150ms 后队列长度没变 && hero 静止 && 仍在回放 && 无事件处理中）才补一次 core.replay()。
			// 引擎自身的移动回调链正常推进时它什么都不做，因此【不会形成第二驱动、不会和引擎链抢时序】，
			// 从根本上避免“在 heroMoving>0 时发出下一步导致被吞”的竞争（穿梭/闪避同源问题）。
			if (!core.isReplaying || !core.isReplaying()) return;
			var rp0 = (core.status && core.status.replay && core.status.replay.toReplay) || null;
			var snapshotLen = rp0 ? rp0.length : -1;
			var tick = function () {
				if (!core.isReplaying || !core.isReplaying()) return;
				if (core.status && core.status.heroMoving > 0) { setTimeout(tick, 60); return; }
				if (core.status && core.status.event && core.status.event.id) return;
				var rp = (core.status && core.status.replay && core.status.replay.toReplay) || null;
				var nowLen = rp ? rp.length : -1;
				if (nowLen === snapshotLen && nowLen > 0) {
					try { core.replay(); } catch (e2) {}
				}
			};
			setTimeout(tick, 150);
		},
        "triggerMonsterDodge": function (enemyId, x, y, floorId) {
			floorId = floorId || core.status.floorId;

			// 录像回放时不要重新计算“现在能不能躲避”。
			// 正常游戏已经把最终躲避结果记录成下一条 dodgeTo:... 动作；
			// 回放阶段这里只负责取消本次战斗，并交给下一条 dodgeTo 同步结果。
			// 这样可以避免目标格在回放瞬间因异步刷新/重绘判定不同，导致有时直接进入战斗而录像报错。
			if (core.isReplaying && core.isReplaying()) {
				var list = (core.status && core.status.replay && core.status.replay.toReplay) || [];
				var next = list.length ? list[0] : '';
				if (typeof next == 'string' && next.indexOf('dodgeTo:') == 0) {
					var arr = next.split(':');
					// 只在下一条确实是当前怪物的躲避结果时跳过战斗；
					// 老录像/无可躲避时不会误跳过正常战斗。
					if (arr.length >= 7 && arr[1] == floorId && parseInt(arr[2]) == x && parseInt(arr[3]) == y && arr.slice(6).join(':') == enemyId) {
						// 取消本次战斗，让引擎自身的移动回调链续播下一条（dodgeTo）。
						// 不再加即时 setTimeout(core.replay)：那是“第二驱动”，会和引擎链抢时序，
						// 在上一步移动动画未结束（heroMoving>0）时发出下一步导致被吞（追击精灵时 left 被吞、move 报错的根因）。
						// 仅用 stall-only 看门狗兜底，确保万一引擎链没接管也不会卡死。
						this.__replayStallWatchdog();
						return true;
					}
				}
				return false;
			}

			var target = this.getMonsterDodgeTarget(x, y, floorId);
			if (!target) return false;
			var action = 'dodgeTo:' + floorId + ':' + x + ':' + y + ':' + target.x + ':' + target.y + ':' + enemyId;
			if (core.status && core.status.route) core.status.route.push(action);
			return this.performMonsterDodgeTo(enemyId, x, y, target.x, target.y, false, action, { floorId: floorId });
		},
        "performMonsterDodgeTo": function (enemyId, x, y, tx, ty, fromReplay, routeAction, info) {
			x = parseInt(x); y = parseInt(y); tx = parseInt(tx); ty = parseInt(ty);
			if (isNaN(x) || isNaN(y) || isNaN(tx) || isNaN(ty)) return false;
			info = info || {};
			var floorId = info.floorId || core.status.floorId;
			if (!floorId || !core.status || !core.material || !core.material.enemys) return false;
			var enemy = core.material.enemys[enemyId];
			if (!enemy) return false;

			// 回放时该动作是正式录像动作，需要写入 route，保证录像结束时校验一致。
			if (fromReplay && core.status && core.status.route && routeAction) {
				core.status.route.push(routeAction);
			}

			// 关键修复（与穿梭同源）：stopAutomaticRoute / clearContinueAutomaticRoute / lockControl
			// 只在真实游戏里调用。回放时调用 stopAutomaticRoute 会打断引擎的录像续播。
			if (!fromReplay) {
				try { core.stopAutomaticRoute(); } catch (e0) {}
				try { if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute(); } catch (e1) {}
				try { core.lockControl(); } catch (e2) { if (core.status) core.status.lockControl = true; }
			}

			var finish = function () {
				if (!core.status || core.status.gameOver) return;
				if (floorId && core.status.floorId !== floorId) {
					if (!fromReplay) { try { core.unlockControl(); } catch (e3) { if (core.status) core.status.lockControl = false; } }
					return;
				}

				var block = core.getBlock(x, y, floorId);
				// 如果原格已经不是这只怪，说明状态已变化；回放时仍继续下一步，正常游戏直接结束。
				if (!block || !block.event || block.event.id != enemyId) {
					if (!fromReplay) { try { core.unlockControl(); } catch (e4) { if (core.status) core.status.lockControl = false; } }
					else core.control.controldata.__replayStallWatchdog();
					return;
				}
				// 目标必须仍为空；否则躲避失败，不移动。
				if (core.getBlock(tx, ty, floorId) != null) {
					if (!fromReplay) { try { core.unlockControl(); } catch (e5) { if (core.status) core.status.lockControl = false; } }
					else core.control.controldata.__replayStallWatchdog();
					return;
				}

				flags.enemyOnPoint = flags.enemyOnPoint || {};
				flags.enemyOnPoint[floorId] = flags.enemyOnPoint[floorId] || {};
				var srcKey = x + ',' + y, dstKey = tx + ',' + ty;
				var pointInfo = core.clone(flags.enemyOnPoint[floorId][srcKey] || {});
				delete flags.enemyOnPoint[floorId][srcKey];
				var oldHp = parseInt(pointInfo.hp);
				if (isNaN(oldHp)) {
					try { oldHp = parseInt(core.getEnemyValue(enemy, 'hp', x, y, floorId)); } catch (e6) { oldHp = parseInt(enemy.hp || 1); }
				}
				if (isNaN(oldHp)) oldHp = 1;
				pointInfo.hp = Math.max(1, oldHp + 20);
				flags.enemyOnPoint[floorId][dstKey] = pointInfo;

				var number = core.getNumberById ? core.getNumberById(enemyId) : null;
				try { core.removeBlock(x, y, floorId); } catch (e7) { try { core.hideBlock(x, y, floorId); } catch (e8) {} }
				try { core.setBlock(number || enemyId, tx, ty, floorId); } catch (e9) {}
				try { core.updateStatusBar(false, true); } catch (e10) {}
				try { core.updateDamage(); } catch (e11) {}
				try { core.drawMap(floorId); } catch (e12) { try { core.drawMap(); } catch (e13) {} }
				try { core.drawTip('躲避：怪物生命 +20', enemyId); } catch (e14) {}

				if (!fromReplay) {
					try { core.unlockControl(); } catch (e15) { if (core.status) core.status.lockControl = false; }
					try { if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute(); } catch (e16) {}
				} else {
					// 回放：单驱动交给引擎链续播；stall-only 看门狗兜底，绝不形成第二驱动。
					core.control.controldata.__replayStallWatchdog();
				}
			};

			if (fromReplay) finish();
			else if (core.plugin && core.plugin.playMonsterDodgeFx) {
				try { core.plugin.playMonsterDodgeFx(x, y, tx, ty, enemyId, finish); }
				catch (e17) { setTimeout(finish, 220); }
			} else setTimeout(finish, 220);
			return true;
		},
        "applyCowardiceOnMonsterDeath": function (floorId, deadX, deadY) {
			// 46 畏缩：当前楼层每死亡一个怪物，所有存活的畏缩怪物攻击/防御各 -1。
			// 只改坐标点 enemyOnPoint 覆盖，不改怪物模板；最低降到0。
			floorId = floorId || core.status.floorId;
			if (!floorId || !core.status || !core.status.maps || !core.material || !core.material.enemys) return false;
			try { core.extractBlocks(floorId); } catch (e0) {}
			var blocks = (core.status.maps[floorId] || {}).blocks || [];
			var changed = [];
			flags.enemyOnPoint = flags.enemyOnPoint || {};
			flags.enemyOnPoint[floorId] = flags.enemyOnPoint[floorId] || {};
			for (var i = 0; i < blocks.length; i++) {
				var block = blocks[i];
				if (!block || block.disable || !block.event) continue;
				if (block.x == deadX && block.y == deadY) continue;
				var enemyId = block.event.id;
				var enemy = core.material.enemys[enemyId];
				if (!enemy) continue;
				var cls = block.event.cls || '';
				if (cls.indexOf('enemy') !== 0) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (!core.enemys.hasSpecial(special, 46)) continue;
				var key = block.x + ',' + block.y;
				var point = core.clone(flags.enemyOnPoint[floorId][key] || {});
				var oldAtk = parseInt(point.atk);
				if (isNaN(oldAtk)) {
					try { oldAtk = parseInt(core.getEnemyValue(enemy, 'atk', block.x, block.y, floorId)); } catch (e1) { oldAtk = parseInt(enemy.atk || 0); }
				}
				var oldDef = parseInt(point.def);
				if (isNaN(oldDef)) {
					try { oldDef = parseInt(core.getEnemyValue(enemy, 'def', block.x, block.y, floorId)); } catch (e2) { oldDef = parseInt(enemy.def || 0); }
				}
				if (isNaN(oldAtk)) oldAtk = 0;
				if (isNaN(oldDef)) oldDef = 0;
				var newAtk = Math.max(0, oldAtk - 1);
				var newDef = Math.max(0, oldDef - 1);
				if (newAtk == oldAtk && newDef == oldDef) continue;
				point.atk = newAtk;
				point.def = newDef;
				flags.enemyOnPoint[floorId][key] = point;
				changed.push({ x: block.x, y: block.y, enemyId: enemyId, atk: newAtk, def: newDef, oldAtk: oldAtk, oldDef: oldDef });
			}
			if (!changed.length) return false;
			try { core.updateStatusBar(false, true); } catch (e3) {}
			try { core.updateDamage(); } catch (e4) {}
			try { core.drawMap(floorId); } catch (e5) { try { core.drawMap(); } catch (e6) {} }
			if (!(core.isReplaying && core.isReplaying()) && core.plugin && core.plugin.playMonsterCowardiceFx) {
				try { core.plugin.playMonsterCowardiceFx(changed, floorId); } catch (e7) {}
			}
			return true;
        },
        "hasAliveEnemyOnFloor": function (floorId) {
			// 检查指定楼层是否还有未被禁用的怪物图块。
			floorId = floorId || core.status.floorId;
			if (!floorId || !core.status || !core.status.maps || !core.status.maps[floorId]) return false;
			core.extractBlocks(floorId);
			var blocks = (core.status.maps[floorId].blocks || []);
			for (var i = 0; i < blocks.length; i++) {
				var block = blocks[i];
				if (!block || block.disable || !block.event) continue;
				var cls = block.event.cls || '';
				if (cls.indexOf('enemy') == 0 && core.material.enemys[block.event.id]) return true;
			}
			return false;
		},
        "checkFloorClearAndGoNext": function (floorId) {
			// 击杀任意怪物后调用：如果本层已经没有怪物，则自动传送到下一层(6,12)。
			floorId = floorId || core.status.floorId;
			if (!floorId || core.status.gameOver) return false;
			if (this.hasAliveEnemyOnFloor(floorId)) return false;

			// 龙魂王轮回试炼：试炼层全清后返回MT4，恢复攻击/防御/护盾，并开启下一阶段。
			if (core.hasFlag('__MT4BossTrialActive__') && floorId == core.getFlag('__MT4BossTrialFloor__', 'MT0')) {
				if (this.completeMT4BossGauntlet) return this.completeMT4BossGauntlet();
			}

			var index = core.floorIds.indexOf(floorId);
			if (index < 0 || index >= core.floorIds.length - 1) return false;
			var nextFloorId = core.floorIds[index + 1];
			if (!core.status.maps[nextFloorId]) return false;

			// 同一楼层只触发一次，避免清层后被其他刷新重复插入传送动作。
			var flags = core.getFlag('__floorClearAutoNext__', {});
			if (flags[floorId]) return false;
			flags[floorId] = true;
			core.setFlag('__floorClearAutoNext__', flags);

			core.drawTip('本层怪物已清空，前往下一层', 'fly');
			core.insertAction([{
				"type": "changeFloor",
				"floorId": nextFloorId,
				"loc": [6, 12],
				"direction": "up"
			}]);
			return true;
		},
        "getDragonSlayerTargetAt": function (hx, hy, floorId) {
			// 在指定勇士坐标处，查找会被 28 屠龙触发的怪物。
			// 这一步独立出来，是为了让“正常走路”和“鼠标瞬移经过路线”都走同一套判定。
			floorId = floorId || core.status.floorId;
			if (!floorId || !core.status.maps || core.status.event.id != null) return null;
			if (core.hasFlag('no_dragonSlayer')) return null;

			var width = core.floors[floorId].width, height = core.floors[floorId].height;
			var blocks = core.getMapBlocksObj(floorId) || {};
			var target = null;

			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var id = block.event.id;
				var enemy = core.material.enemys[id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (!core.enemys.hasSpecial(special, 28)) continue;
				if (block.x != hx && block.y != hy) continue;
				if (block.x == hx && block.y == hy) continue;

				var dir = null, frontX = hx, frontY = hy, distance = 0;
				if (block.x < hx) { dir = 'right'; frontX = hx - 1; distance = hx - block.x; }
				else if (block.x > hx) { dir = 'left'; frontX = hx + 1; distance = block.x - hx; }
				else if (block.y < hy) { dir = 'down'; frontY = hy - 1; distance = hy - block.y; }
				else if (block.y > hy) { dir = 'up'; frontY = hy + 1; distance = block.y - hy; }
				if (dir == null) continue;
				if (frontX < 0 || frontX >= width || frontY < 0 || frontY >= height) continue;

				// 多只屠龙同时满足时，取距离勇士最近的一只，避免同一步触发多场战斗。
				if (target == null || distance < target.distance) {
					target = { x: block.x, y: block.y, id: id, dir: dir, frontX: frontX, frontY: frontY, distance: distance };
				}
			}
			return target;
		},
        "getDragonSlayerBattleDir": function (x, y) {
			// 根据勇士和屠龙的相对位置，推断本次主动战斗的方向；只需要方向轴来决定左右分裂。
			var hx = core.getHeroLoc('x'), hy = core.getHeroLoc('y');
			if (hx < x && hy == y) return 'left';
			if (hx > x && hy == y) return 'right';
			if (hy < y && hx == x) return 'up';
			if (hy > y && hx == x) return 'down';

			// 极少数强制事件不在相邻格战斗时，退回到勇士朝向，避免不分裂。
			var direction = core.getHeroLoc('direction');
			if (direction == 'left' || direction == 'right' || direction == 'up' || direction == 'down') return direction;
			return 'up';
		},
        "triggerDragonSlayerTarget": function (target, floorId) {
			// 执行 28 屠龙：跳到勇士面前并强制战斗。
			if (target == null) return false;
			floorId = floorId || core.status.floorId;
			var actions = [
				{ "type": "playSound", "name": "跳跃" },
				{ "type": "tip", "text": "屠龙发动！", "icon": target.id }
			];
			// 跳到勇士面前；jump 不检查路径，因此可穿越墙和障碍。
			if (target.x != target.frontX || target.y != target.frontY) {
				actions.push({ "type": "jump", "from": [target.x, target.y], "to": [target.frontX, target.frontY], "time": 250, "keep": false });
			}
			actions.push({
				"type": "function",
				"function": "function() { core.setFlag('__dragonSlayerRevive__', { enemyId: '" + target.id + "', x: " + target.x + ", y: " + target.y + ", centerX: " + target.frontX + ", centerY: " + target.frontY + ", dir: '" + target.dir + "', floorId: '" + floorId + "' }); core.battle('" + target.id + "', " + target.x + ", " + target.y + ", true, core.doAction); }",
				"async": true
			});
			core.insertAction(actions);
			return true;
		},
        "checkDragonSlayer": function (callback, hx, hy) {
			// 28号特殊属性：屠龙
			// 勇士出现在怪物同行或同列时，怪物无视障碍冲刺到勇士面前并强制战斗。
			var floorId = core.status.floorId;
			if (hx == null) hx = core.getHeroLoc('x');
			if (hy == null) hy = core.getHeroLoc('y');
			var target = this.getDragonSlayerTargetAt(hx, hy, floorId);
			if (target == null) return false;
			return this.triggerDragonSlayerTarget(target, floorId);
		},
        "getMoveDirectlyRoute": function (destX, destY) {
			// 复刻样板 canMoveDirectly 的 BFS，额外保留路径。
			// 用于判断鼠标瞬移“经过”的每一个格子是否触发屠龙。
			var fromX = core.getHeroLoc('x'), fromY = core.getHeroLoc('y');
			if (destX == fromX && destY == fromY) return [];
			if (!core.maps._canMoveDirectly_checkGlobal()) return null;
			if (!core.maps._canMoveDirectly_checkStartPoint(fromX, fromY)) return null;

			var width = core.bigmap.width, height = core.bigmap.height;
			if (destX < 0 || destX >= width || destY < 0 || destY >= height) return null;

			var canMoveArray = core.maps.generateMovableArray();
			var blocksObj = core.getMapBlocksObj();
			var bgMap = core.maps.getBgMapArray();
			var visited = {}, parent = {}, queue = [];
			var start = fromX + "," + fromY;
			visited[start] = true;
			queue.push({ x: fromX, y: fromY });

			while (queue.length > 0) {
				var now = queue.shift(), x = now.x, y = now.y;
				for (var direction in core.utils.scan) {
					if (!core.inArray(canMoveArray[x][y], direction)) continue;
					var nx = x + core.utils.scan[direction].x, ny = y + core.utils.scan[direction].y;
					if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
					var key = nx + "," + ny;
					if (visited[key]) continue;
					if (core.onSki(bgMap[ny][nx])) continue;
					if (!core.maps._canMoveDirectly_checkNextPoint(blocksObj, nx, ny)) continue;
					visited[key] = true;
					parent[key] = { x: x, y: y, direction: direction };

					if (nx == destX && ny == destY) {
						var ans = [], cx = destX, cy = destY;
						while (cx != fromX || cy != fromY) {
							var p = parent[cx + "," + cy];
							ans.push({ x: cx, y: cy, direction: p.direction });
							cx = p.x; cy = p.y;
						}
						ans.reverse();
						return ans;
					}
					queue.push({ x: nx, y: ny });
				}
			}
			return null;
		},
        "getDragonSlayerTriggerOnDirectRoute": function (destX, destY) {
			// 鼠标瞬移会直接跳到终点，不执行每一步的 moveOneStep。
			// 这里补上“路过每一格”的屠龙检查，防止瞬移绕开屠龙。
			var floorId = core.status.floorId;
			if (!floorId || !core.status.maps || core.status.event.id != null) return null;
			if (core.hasFlag('no_dragonSlayer')) return null;

			// 起点也检查一次，避免玩家站在同行/同列时直接瞬移逃离。
			var startTarget = this.getDragonSlayerTargetAt(core.getHeroLoc('x'), core.getHeroLoc('y'), floorId);
			if (startTarget != null) return { x: core.getHeroLoc('x'), y: core.getHeroLoc('y'), step: 0, target: startTarget };

			var route = this.getMoveDirectlyRoute(destX, destY);
			if (!route || route.length == 0) return null;
			for (var i = 0; i < route.length; i++) {
				var step = route[i];
				var target = this.getDragonSlayerTargetAt(step.x, step.y, floorId);
				if (target != null) return { x: step.x, y: step.y, step: i + 1, target: target };
			}
			return null;
		},
        "tryFirstBattleFailRevive": function (damage, heroHpBeforeBattle, enemyId, x, y, bloodWarTriggered) {
			// 每层第一次“真实战斗失败”触发轮回：
			// 1) damage必须是数字；damage==null(???)不触发。
			// 2) 每层只触发一次。
			// 3) 地图必须完全还原成该层初始状态：清除屠龙分裂怪、分字标记、临时图块和本层局部flag。
			// 4) 勇士回到(6,12)，道具/攻防/金币保留，HP=怪物伤害-战斗时HP。
			// ★ 血战（special 33）：触发战斗失败后【不可轮回】，哪怕本层还有死亡次数也直接真死
			var floorId = core.status.floorId;
			if (!floorId || !core.status || !core.status.hero) return false;
			if (damage == null || !isFinite(damage)) return false;

			// 进入MT4龙魂王战后直到通关前，不再获得任何新的轮回机会。
			if (core.hasFlag('__MT4BossNoRevive__')) return false;

			// 血战检测：直接读取 getDamageInfo 传过来的标志位
			// （不再用 sqrt 反推，避免天然完全平方数被误判）
			if (bloodWarTriggered === true) {
				return false;  // 平方化实际触发了，禁止轮回
			}

			var used = core.getFlag('__firstBattleFailReviveUsed__', {});
			if (used[floorId]) return false;
			used[floorId] = true;
			core.setFlag('__firstBattleFailReviveUsed__', used);

			var oldHp = Math.max(0, Math.floor(heroHpBeforeBattle || 0));
			var battleDamage = Math.max(0, Math.floor(damage));
			var reviveHp = Math.max(1, battleDamage - oldHp);

			core.stopAutomaticRoute();
			if (core.clearContinueAutomaticRoute) core.clearContinueAutomaticRoute();
			if (core.ui && core.ui.closePanel) core.ui.closePanel();

			// 不能只调用 core.maps.resetMap；屠龙分裂怪的信息在 flags.enemyOnPoint 里，
			// 不清掉会导致地图重置后仍然残留分裂怪属性和“分”字标记。
			this.resetFloorToInitialForRevive(floorId);

			core.clearMap('hero');
			core.setHeroLoc('x', 6);
			core.setHeroLoc('y', 12);
			core.status.hero.hp = reviveHp;

			// 轮回时恢复 碎甲(35)/破功(36) 的累计削减
			var ab_r = core.getFlag('__armorBroken__', null);
			var sb_r = core.getFlag('__skillBroken__', null);
			if (ab_r && (ab_r.amount || 0) > 0) {
				core.status.hero.def = (core.status.hero.def || 0) + ab_r.amount;
				core.removeFlag('__armorBroken__');
			}
			if (sb_r && (sb_r.amount || 0) > 0) {
				core.status.hero.atk = (core.status.hero.atk || 0) + sb_r.amount;
				core.removeFlag('__skillBroken__');
			}

			core.drawHero();
			core.updateStatusBar(false, true);
			core.drawTip('本层第一次战斗失败，地图已重置', enemyId);
			return true;
		},
        "resetFloorToInitialForRevive": function (floorId) {
			// 彻底还原某层地图到初始状态；用于第一次战斗失败轮回。
			floorId = floorId || core.status.floorId;
			if (!floorId || !core.status || !core.floors || !core.floors[floorId]) return;

			// 清除屠龙相关临时状态。
			core.removeFlag('__dragonSlayerRevive__');

			// 清除坐标点怪物属性覆盖：屠龙分裂怪的 special=29、dragonSplit=true 都在这里。
			if (flags.enemyOnPoint && flags.enemyOnPoint[floorId]) {
				delete flags.enemyOnPoint[floorId];
			}

			// 清除灭霸/屠虎造成的临时攻击加成，轮回后恢复原数值。
			var atkBonus = core.getFlag('__enemyAtkBonus__', {});
			if (atkBonus[floorId]) {
				delete atkBonus[floorId];
				core.setFlag('__enemyAtkBonus__', atkBonus);
			}

			// 清除寄生累计：寄生在轮回时也要重置
			core.removeFlag('__parasiteAccum__');
			// 让 plugins.js 的差值追踪器跳过下一帧（避免把轮回恢复的血当成"勇士回血"再累计回去）
			core.setFlag('__justRevived__', true);

			// 清除本层图块隐藏/显示记录，避免动态 setBlock、removeBlock、hideBlock 造成残留。
			if (flags.__disabled__ && flags.__disabled__[floorId]) {
				delete flags.__disabled__[floorId];
			}

			// 清除本层共生 pending 状态：轮回后地图重置，pending 列表必须同步清空
			core.removeFlag('__symbiosisPending_' + floorId + '__');

			// 清除本层吞噬加成：轮回后所有吞噬怪回到基础属性
			var devourData = core.getFlag('__devourBonus__', {});
			if (devourData[floorId]) {
				delete devourData[floorId];
				core.setFlag('__devourBonus__', devourData);
			}

			// 如果整层曾被移除，轮回时也恢复。
			if (flags.__removed__ instanceof Array) {
				flags.__removed__ = flags.__removed__.filter(function (one) { return one != floorId; });
			}

			// 清除本层局部flag，例如 floorId@xxx。
			Object.keys(core.status.hero.flags || {}).forEach(function (name) {
				if (name.indexOf(floorId + '@') === 0) delete core.status.hero.flags[name];
			});

			// 清除地图对象缓存、显伤缓存、动画缓存。
			if (core.status.mapBlockObjs) delete core.status.mapBlockObjs[floorId];
			if (core.status.checkBlock && core.status.checkBlock.cache) core.status.checkBlock.cache = {};
			if (core.maps && core.maps.removeGlobalAnimate) core.maps.removeGlobalAnimate();

			// 从 core.floors 的初始地图重新加载，保证分裂怪和临时图块全部消失。
			core.status.maps[floorId] = core.maps.loadFloor(floorId);

			// MT0 第一次轮回后，背景贴图由 333.png 变为 334.png。
			if (floorId == 'MT0') {
				var reviveUsed = core.getFlag('__firstBattleFailReviveUsed__', {});
				if (reviveUsed.MT0) {
					var floorImages = core.status.maps[floorId].images || [];
					var hasTargetImage = false;
					var replacedImage = false;
					floorImages.forEach(function (one) {
						if (!one || one.canvas != 'bg' || one.x != 0 || one.y != 0) return;
						if (one.name == '334.png') hasTargetImage = true;
						if (one.name == '333.png') {
							one.name = '334.png';
							hasTargetImage = true;
							replacedImage = true;
						}
					});
					if (!hasTargetImage && !replacedImage) {
						floorImages.push({ name: '334.png', canvas: 'bg', x: 0, y: 0 });
					}
					core.status.maps[floorId].images = floorImages;
				}
			}

			if (floorId == core.status.floorId) {
				core.maps.redrawMap();
			}
		},
        "countDragonDescendants": function (floorId) {
			// 统计当前楼层仍然存在的“降龙”怪数量。
			// 注意要读取坐标点上的特殊属性覆盖，屠龙复活怪就是靠 enemyOnPoint 把 special 改成 29。
			floorId = floorId || core.status.floorId;
			if (!floorId || !core.status.maps || !core.status.maps[floorId]) return 0;
			var blocks = core.getMapBlocksObj(floorId) || {};
			var count = 0;
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var enemy = core.material.enemys[block.event.id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (core.enemys.hasSpecial(special, 29)) count++;
			}
			return count;
		},
        "countSoulLockEnemies": function (floorId) {
			// 统计当前楼层仍然存在的“魂锁”怪数量（special 34）。
			// 用于 HP 倍增计算和地图链条渲染。
			floorId = floorId || core.status.floorId;
			if (!floorId || !core.status.maps || !core.status.maps[floorId]) return 0;
			var blocks = core.getMapBlocksObj(floorId) || {};
			var count = 0;
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var enemy = core.material.enemys[block.event.id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (core.enemys.hasSpecial(special, 34)) count++;
			}
			return count;
		},
        "getSoulLockPositions": function (floorId) {
			// 返回本层所有魂锁怪的坐标数组，用于绘制连接链条。
			floorId = floorId || core.status.floorId;
			var positions = [];
			if (!floorId || !core.status.maps || !core.status.maps[floorId]) return positions;
			var blocks = core.getMapBlocksObj(floorId) || {};
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var enemy = core.material.enemys[block.event.id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (core.enemys.hasSpecial(special, 34)) {
					positions.push({ x: block.x, y: block.y });
				}
			}
			return positions;
		},
        "countLiveSymbiosis": function (floorId) {
			// 统计本层【活着的】共生怪（special 37）。block.disable 表示已死。
			floorId = floorId || core.status.floorId;
			if (!floorId || !core.status.maps || !core.status.maps[floorId]) return 0;
			var blocks = core.getMapBlocksObj(floorId) || {};
			var count = 0;
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var enemy = core.material.enemys[block.event.id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (core.enemys.hasSpecial(special, 37)) count++;
			}
			return count;
		},
        "getSymbiosisLivePositions": function (floorId) {
			// 返回本层【活着的】共生怪坐标，用于地图连接链渲染
			floorId = floorId || core.status.floorId;
			var positions = [];
			if (!floorId || !core.status.maps || !core.status.maps[floorId]) return positions;
			var blocks = core.getMapBlocksObj(floorId) || {};
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var enemy = core.material.enemys[block.event.id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (core.enemys.hasSpecial(special, 37)) {
					positions.push({ x: block.x, y: block.y });
				}
			}
			return positions;
		},
        // ===== 吞噬 (38) 增益系统 =====
        // 存储结构：__devourBonus__[floorId][x,y] = { hp, atk, def }
        // 与 __enemyAtkBonus__（屠虎/灭霸用）分开，避免互相干扰
        "getDevourBonus": function (floorId, x, y) {
			floorId = floorId || core.status.floorId;
			var data = core.getFlag('__devourBonus__', {});
			var entry = ((data[floorId] || {})[x + ',' + y]) || {};
			return { hp: entry.hp || 0, atk: entry.atk || 0, def: entry.def || 0 };
		},
        "addDevourBonus": function (floorId, x, y, dHp, dAtk, dDef) {
			floorId = floorId || core.status.floorId;
			var data = core.getFlag('__devourBonus__', {});
			data[floorId] = data[floorId] || {};
			var key = x + ',' + y;
			var entry = data[floorId][key] || { hp: 0, atk: 0, def: 0 };
			entry.hp  = (entry.hp  || 0) + Math.max(0, Math.floor(dHp  || 0));
			entry.atk = (entry.atk || 0) + Math.max(0, Math.floor(dAtk || 0));
			entry.def = (entry.def || 0) + Math.max(0, Math.floor(dDef || 0));
			data[floorId][key] = entry;
			core.setFlag('__devourBonus__', data);
		},
        "applyDevourBuff": function (floorId, deadEnemyInfo, deadX, deadY) {
			// 本层每死一只怪：对所有活着的吞噬怪发放 死亡怪 5% HP/ATK/DEF 加成（保底+1）
			floorId = floorId || core.status.floorId;
			if (!floorId || !deadEnemyInfo) return 0;
			var dHp  = Math.max(1, Math.ceil(0.05 * (deadEnemyInfo.hp  || 0)));
			var dAtk = Math.max(1, Math.ceil(0.05 * (deadEnemyInfo.atk || 0)));
			var dDef = Math.max(1, Math.ceil(0.05 * (deadEnemyInfo.def || 0)));
			var blocks = core.getMapBlocksObj(floorId) || {};
			var count = 0;
			var targets = [];
			for (var loc in blocks) {
				var block = blocks[loc];
				if (!block || block.disable || !block.event) continue;
				var enemy = core.material.enemys[block.event.id];
				if (!enemy) continue;
				var special = core.getEnemyValue(enemy, 'special', block.x, block.y, floorId);
				if (!core.enemys.hasSpecial(special, 38)) continue;
				// 不给刚死的那只（虽然它已被 disable，多一道防御保险）
				if (block.x === deadX && block.y === deadY) continue;
				this.addDevourBonus(floorId, block.x, block.y, dHp, dAtk, dDef);
				targets.push({ x: block.x, y: block.y });
				count++;
			}
			if (count > 0) {
				core.updateStatusBar();
				try { core.updateDamage(); } catch (e) {}
				try { core.drawMap(); } catch (e) {}
				// 视觉标志：plugins.js 会读取并触发"吸收"动画
				core.setFlag('__lastDevourAbsorb__', {
					fromX: deadX, fromY: deadY,
					targets: targets,
					gain: { hp: dHp, atk: dAtk, def: dDef },
					timestamp: Date.now(),
					floorId: floorId
				});
			}
			return count;
		},
        "reviveDragonDescendants": function (enemyId, centerX, centerY, floorId, enemyPointInfo, battleDir) {
			// 屠龙死亡后：以它和勇士战斗的方向为基准，只在左右两侧第一个空地各复活一只“降龙”怪。
			floorId = floorId || core.status.floorId;
			if (!floorId || centerX == null || centerY == null) return 0;
			var width = core.floors[floorId].width, height = core.floors[floorId].height;
			var number = core.getNumberById(enemyId);
			if (!number) return 0;

			var dirs;
			if (battleDir == 'left' || battleDir == 'right') {
				dirs = [
					{ x: 0, y: -1 },
					{ x: 0, y: 1 }
				];
			} else {
				dirs = [
					{ x: -1, y: 0 },
					{ x: 1, y: 0 }
				];
			}
			var count = 0;
			for (var i = 0; i < dirs.length; i++) {
				var nx = centerX + dirs[i].x, ny = centerY + dirs[i].y;
				while (nx >= 0 && nx < width && ny >= 0 && ny < height) {
					// 勇士当前所在格不算空地，避免复活怪直接刷在勇士脚下
					if (nx == core.getHeroLoc('x') && ny == core.getHeroLoc('y')) {
						nx += dirs[i].x;
						ny += dirs[i].y;
						continue;
					}
					if (core.getBlock(nx, ny, floorId) == null) {
						core.setBlock(number, nx, ny, floorId);
						// 属性沿用本体；如果原怪在该点有独立属性，也一并复制。最后只把技能改成 29：降龙。
						flags.enemyOnPoint = flags.enemyOnPoint || {};
						flags.enemyOnPoint[floorId] = flags.enemyOnPoint[floorId] || {};
						flags.enemyOnPoint[floorId][nx + "," + ny] = core.clone(enemyPointInfo || {});
						flags.enemyOnPoint[floorId][nx + "," + ny].special = 29;
						// 标记为屠龙分裂出来的降龙怪；绘制时会在头顶显示“分”。
						flags.enemyOnPoint[floorId][nx + "," + ny].dragonSplit = true;
						count++;
						break;
					}
					nx += dirs[i].x;
					ny += dirs[i].y;
				}
			}
			if (count > 0) {
				core.drawTip("屠龙一化为" + count, enemyId);
				core.updateStatusBar();
			}
			return count;
		},
        "updateCheckBlock": function (floorId) {
			// 领域、夹击、阻击等的伤害值计算
			floorId = floorId || core.status.floorId;
			if (!floorId || !core.status.maps) return;

			var width = core.floors[floorId].width,
				height = core.floors[floorId].height;
			var blocks = core.getMapBlocksObj(floorId);

			var damage = {}, // 每个点的伤害值
				type = {}, // 每个点的伤害类型
				repulse = {}, // 每个点的阻击怪信息
				ambush = {}; // 每个点的捕捉信息
			var betweenAttackLocs = {}; // 所有可能的夹击点
			var needCache = false;
			var canGoDeadZone = core.flags.canGoDeadZone;
			core.flags.canGoDeadZone = true;

			// 计算血网和领域、阻击、激光的伤害，计算捕捉信息
			for (var loc in blocks) {
				var block = blocks[loc],
					x = block.x,
					y = block.y,
					id = block.event.id,
					enemy = core.material.enemys[id];
				if (block.disable) continue;

				type[loc] = type[loc] || {};

				// 血网
				// 如需调用当前楼层的ratio可使用  core.status.maps[floorId].ratio
				if (id == 'lavaNet' && !core.hasItem('amulet')) {
					damage[loc] = (damage[loc] || 0) + core.values.lavaDamage;
					type[loc][(block.event.name || "血网") + "伤害"] = true;
				}

				// 领域
				// 如果要防止领域伤害，可以直接简单的将 flag:no_zone 设为true
				if (enemy && core.hasSpecial(enemy.special, 15) && !core.hasFlag('no_zone')) {
					// 领域范围，默认为1
					var range = enemy.range || 1;
					// 是否是九宫格领域
					var zoneSquare = false;
					if (enemy.zoneSquare != null) zoneSquare = enemy.zoneSquare;
					// 在范围内进行搜索，增加领域伤害值
					for (var dx = -range; dx <= range; dx++) {
						for (var dy = -range; dy <= range; dy++) {
							if (dx == 0 && dy == 0) continue;
							var nx = x + dx,
								ny = y + dy,
								currloc = nx + "," + ny;
							if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
							// 如果是十字领域，则还需要满足 |dx|+|dy|<=range
							if (!zoneSquare && Math.abs(dx) + Math.abs(dy) > range) continue;
							damage[currloc] = (damage[currloc] || 0) + (enemy.zone || 0);
							type[currloc] = type[currloc] || {};
							type[currloc]["领域伤害"] = true;
						}
					}
				}

				// 阻击
				// 如果要防止阻击伤害，可以直接简单的将 flag:no_repulse 设为true
				if (enemy && core.hasSpecial(enemy.special, 18) && !core.hasFlag('no_repulse')) {
					var scan = enemy.zoneSquare ? core.utils.scan2 : core.utils.scan;
					for (var dir in scan) {
						var nx = x + scan[dir].x,
							ny = y + scan[dir].y,
							currloc = nx + "," + ny;
						if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
						damage[currloc] = (damage[currloc] || 0) + (enemy.repulse || 0);
						type[currloc] = type[currloc] || {};
						type[currloc]["阻击伤害"] = true;

						var rdir = core.turnDirection(":back", dir);
						// 检查下一个点是否存在事件（从而判定是否移动）
						var rnx = x + scan[rdir].x,
							rny = y + scan[rdir].y;
						if (rnx < 0 || rnx >= width || rny < 0 || rny >= height) continue;
						// 如需禁止阻击被推到已隐藏的事件处（如重生怪处），可将这一句的false改为true
						if (core.getBlock(rnx, rny, floorId, false) != null) continue;
						if (core.utils.scan[rdir] && !core.canMoveHero(x, y, rdir, floorId)) continue;
						repulse[currloc] = (repulse[currloc] || []).concat([
							[x, y, id, rdir]
						]);
					}
				}

				// 激光
				// 如果要防止激光伤害，可以直接简单的将 flag:no_laser 设为true
				if (enemy && core.hasSpecial(enemy.special, 24) && !core.hasFlag("no_laser")) {
					for (var nx = 0; nx < width; nx++) {
						var currloc = nx + "," + y;
						if (nx != x) {
							damage[currloc] = (damage[currloc] || 0) + (enemy.laser || 0);
							type[currloc] = type[currloc] || {};
							type[currloc]["激光伤害"] = true;
						}
					}
					for (var ny = 0; ny < height; ny++) {
						var currloc = x + "," + ny;
						if (ny != y) {
							damage[currloc] = (damage[currloc] || 0) + (enemy.laser || 0);
							type[currloc] = type[currloc] || {};
							type[currloc]["激光伤害"] = true;
						}
					}
				}

				// 捕捉
				// 如果要防止捕捉效果，可以直接简单的将 flag:no_ambush 设为true
				if (enemy && core.enemys.hasSpecial(enemy.special, 27) && !core.hasFlag("no_ambush")) {
					var scan = enemy.zoneSquare ? core.utils.scan2 : core.utils.scan;
					// 给周围格子加上【捕捉】记号
					for (var dir in scan) {
						var nx = x + scan[dir].x,
							ny = y + scan[dir].y,
							currloc = nx + "," + ny;
						if (nx < 0 || nx >= width || ny < 0 || ny >= height || (core.utils.scan[dir] && !core.canMoveHero(x, y, dir, floorId))) continue;
						ambush[currloc] = (ambush[currloc] || []).concat([
							[x, y, id, dir]
						]);
					}
				}

				// 夹击；在这里提前计算所有可能的夹击点，具体计算逻辑在下面
				// 如果要防止夹击伤害，可以简单的将 flag:no_betweenAttack 设为true
				if (enemy && core.enemys.hasSpecial(enemy.special, 16) && !core.hasFlag('no_betweenAttack')) {
					for (var dir in core.utils.scan) {
						var nx = x + core.utils.scan[dir].x,
							ny = y + core.utils.scan[dir].y,
							currloc = nx + "," + ny;
						if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
						betweenAttackLocs[currloc] = true;
					}
				}

				// 检查地图范围类技能
				var specialFlag = core.getSpecialFlag(enemy);
				if (specialFlag & 1) needCache = true;
				if (core.status.event.id == 'viewMaps') needCache = true;
				if ((core.status.event.id == 'book' || core.status.event.id == 'bool-detail') && core.status.event.ui) needCache = true;
			}

			// 对每个可能的夹击点计算夹击伤害
			for (var loc in betweenAttackLocs) {
				var xy = loc.split(","),
					x = parseInt(xy[0]),
					y = parseInt(xy[1]);
				// 夹击怪物的ID
				var enemyId1 = null,
					enemyId2 = null;
				// 检查左右夹击
				var leftBlock = blocks[(x - 1) + "," + y],
					rightBlock = blocks[(x + 1) + "," + y];
				var leftId = core.getFaceDownId(leftBlock),
					rightId = core.getFaceDownId(rightBlock);
				if (leftBlock && !leftBlock.disable && rightBlock && !rightBlock.disable && leftId == rightId) {
					if (core.hasSpecial(leftId, 16))
						enemyId1 = leftId;
				}
				// 检查上下夹击
				var topBlock = blocks[x + "," + (y - 1)],
					bottomBlock = blocks[x + "," + (y + 1)];
				var topId = core.getFaceDownId(topBlock),
					bottomId = core.getFaceDownId(bottomBlock);
				if (topBlock && !topBlock.disable && bottomBlock && !bottomBlock.disable && topId == bottomId) {
					if (core.hasSpecial(topId, 16))
						enemyId2 = topId;
				}

				if (enemyId1 != null || enemyId2 != null) {
					var leftHp = core.status.hero.hp - (damage[loc] || 0);
					if (leftHp > 1) {
						// 夹击伤害值
						var value = Math.floor(leftHp / 2);
						// 是否不超过怪物伤害值
						if (core.flags.betweenAttackMax) {
							var enemyDamage1 = core.getDamage(enemyId1, x, y, floorId);
							if (enemyDamage1 != null && enemyDamage1 < value)
								value = enemyDamage1;
							var enemyDamage2 = core.getDamage(enemyId2, x, y, floorId);
							if (enemyDamage2 != null && enemyDamage2 < value)
								value = enemyDamage2;
						}
						if (value > 0) {
							damage[loc] = (damage[loc] || 0) + value;
							type[loc] = type[loc] || {};
							type[loc]["夹击伤害"] = true;
						}
					}
				}
			}

			// 取消注释下面这一段可以让护盾抵御阻激夹域伤害
			/*
			for (var loc in damage) {
				damage[loc] = Math.max(0, damage[loc] - core.getRealStatus('mdef'));
			}
			*/

			core.flags.canGoDeadZone = canGoDeadZone;
			core.status.checkBlock = {
				damage: damage,
				type: type,
				repulse: repulse,
				ambush: ambush,
				needCache: needCache,
				cache: {} // clear cache
			};
		},
        "moveOneStep": function (callback) {
			// 勇士每走一步后执行的操作。callback为行走完毕后的回调
			// 这个函数执行在“刚走完”的时候，即还没有检查该点的事件和领域伤害等。
			// 请注意：瞬间移动不会执行该函数。如果要控制能否瞬间移动有三种方法：
			// 1. 将全塔属性中的cannotMoveDirectly这个开关勾上，即可在全塔中全程禁止使用瞬移。
			// 2, 将楼层属性中的cannotMoveDirectly这个开关勾上，即禁止在该层楼使用瞬移。
			// 3. 将flag:cannotMoveDirectly置为true，即可使用flag控制在某段剧情范围内禁止瞬移。

			// 增加步数
			core.status.hero.steps++;
			// 更新跟随者状态，并绘制
			core.updateFollowers();
			core.drawHero();
			// 检查中毒状态的扣血和死亡
			if (core.hasFlag('poison')) {
				core.status.hero.statistics.poisonDamage += core.values.poisonDamage;
				core.status.hero.hp -= core.values.poisonDamage;
				if (core.status.hero.hp <= 0) {
					core.status.hero.hp = 0;
					core.updateStatusBar(false, true);
					core.events.lose();
					return;
				} else {
					core.updateStatusBar(false, true);
				}
			}

			// 从v2.7开始，每一步行走不会再刷新状态栏。
			// 如果有特殊要求（如每走一步都加buff之类），可手动取消注释下面这一句：
			// core.updateStatusBar(true, true);

			// 检查自动事件
			core.checkAutoEvents();

			// ------ 检查目标点事件 ------ //
			// 无事件的道具（如血瓶）需要优先于阻激夹域判定
			var nowx = core.getHeroLoc('x'),
				nowy = core.getHeroLoc('y');
			// MT4 P3：阵眼存在期间，勇士每走一步都会被主BOSS和两个分身三道激光命中。
			if (core.control && core.control.controldata && core.control.controldata.handleMT4BossP3Step) {
				if (core.control.controldata.handleMT4BossP3Step(nowx, nowy)) return;
			}
			var block = core.getBlock(nowx, nowy);
			var hasTrigger = false;
			if (block != null && block.event.trigger == 'getItem' &&
				!core.floors[core.status.floorId].afterGetItem[nowx + "," + nowy]) {
				hasTrigger = true;
				core.trigger(nowx, nowy, callback);
			}
			// 屠龙：勇士走到同行或同列时，怪物立刻冲刺并强制战斗
			if (this.checkDragonSlayer && this.checkDragonSlayer(callback)) return;

			// 执行目标点的阻激夹域事件
			core.checkBlock();

			// MT4 P4：阻击把241推到祭坛后，在这里检查献祭。
			if (core.control && core.control.controldata && core.control.controldata.handleMT4BossP4AfterStep) {
				if (core.control.controldata.handleMT4BossP4AfterStep()) return;
			}

			// 执行目标点的script和事件
			if (!hasTrigger)
				core.trigger(nowx, nowy, callback);

			// 检查该点是否是滑冰
			if (core.onSki()) {
				// 延迟到事件最后执行，因为这之前可能有阻激夹域动画
				core.insertAction({ "type": "moveAction" }, null, null, null, true);
			}

			// ------ 检查目标点事件 END ------ //

			// 如需强行终止行走可以在这里条件判定：
			// core.stopAutomaticRoute();
		},
        "moveDirectly": function (x, y, ignoreSteps) {
			// 瞬间移动；x,y为要瞬间移动的点；ignoreSteps为减少的步数，可能之前已经被计算过
			// 返回true代表成功瞬移，false代表没有成功瞬移

			// MT4 P3 阵眼存在期间临时禁止瞬移。
			if (core.status.floorId == 'MT4' && core.getFlag('__MT4BossPhase__', 1) == 3 && core.hasFlag('__MT4BossP3NoTeleport__')) {
				core.drawTip('封印阵压制空间，暂时无法瞬移。');
				return false;
			}
			// 判定能否瞬移到该点
			if (ignoreSteps == null) ignoreSteps = core.canMoveDirectly(x, y);
			if (ignoreSteps >= 0) {
				// 屠龙：鼠标瞬移不是一步一步走，会绕过 moveOneStep。
				// 因此先复原瞬移路线，检查“路过”的每个格子；一旦路过同行/同列，
				// 只瞬移到触发点，并立刻发动屠龙，不能继续瞬移绕开。
				var dragonTrigger = null;
				if (this.getDragonSlayerTriggerOnDirectRoute) {
					dragonTrigger = this.getDragonSlayerTriggerOnDirectRoute(x, y);
				}
				if (dragonTrigger && dragonTrigger.step == 0) {
					return this.triggerDragonSlayerTarget(dragonTrigger.target, core.status.floorId);
				}
				if (dragonTrigger) {
					x = dragonTrigger.x;
					y = dragonTrigger.y;
					ignoreSteps = dragonTrigger.step;
				}

				// 中毒也允许瞬移
				if (core.hasFlag('poison')) {
					var damage = ignoreSteps * core.values.poisonDamage;
					if (damage >= core.status.hero.hp) return false;
					core.status.hero.statistics.poisonDamage += damage;
					core.status.hero.hp -= damage;
				}

				core.clearMap('hero');
				// 获得勇士最后的朝向
				var lastDirection = core.status.route[core.status.route.length - 1];
				if (['left', 'right', 'up', 'down'].indexOf(lastDirection) >= 0)
					core.setHeroLoc('direction', lastDirection);
				// 设置坐标，并绘制
				core.control._moveDirectyFollowers(x, y);
				core.status.hero.loc.x = x;
				core.status.hero.loc.y = y;
				core.drawHero();
				// 记录录像
				core.status.route.push("move:" + x + ":" + y);
				// 统计信息
				core.status.hero.statistics.moveDirectly++;
				core.status.hero.statistics.ignoreSteps += ignoreSteps;
				if (core.hasFlag('poison')) {
					core.updateStatusBar(false, true);
				}
				core.ui.drawStatusBar();
				core.checkRouteFolding();

				if (dragonTrigger) {
					return this.triggerDragonSlayerTarget(dragonTrigger.target, core.status.floorId);
				}

				// 兜底：如果终点同行/同列，也立刻触发。
				if (this.checkDragonSlayer && this.checkDragonSlayer()) return true;

				return true;
			}
			return false;
		},
        "parallelDo": function (timestamp) {
			// 并行事件处理，可以在这里写任何需要并行处理的脚本或事件
			// 该函数将被系统反复执行，每次执行间隔视浏览器或设备性能而定，一般约为16.6ms一次
			// 参数timestamp为“从游戏资源加载完毕到当前函数执行时”的时间差，以毫秒为单位

			// 检查当前是否处于游戏开始状态
			if (!core.isPlaying()) return;

			// 执行当前楼层的并行事件处理
			if (core.status.floorId) {
				try {
					eval(core.floors[core.status.floorId].parallelDo);
				} catch (e) {
					console.error(e);
				}
			}
		}
    },
    "ui": {
        "getToolboxItems": function (cls) {
			// 获得道具栏中当前某类型道具的显示项和显示顺序
			// cls为道具类型，只可能是 tools, constants 和 equips
			// 返回一个数组，代表当前某类型道具的显示内容和顺序
			// 默认按id升序排列，您可以取消下面的注释改为按名称排列

			return Object.keys(core.status.hero.items[cls] || {})
				.filter(function (id) { return !core.material.items[id].hideInToolbox; })
				.sort( /*function (id1, id2) { return core.material.items[id1].name <= core.material.items[id2].name ? -1 : 1 }*/);
		},
        "drawStatusBar": function () {
					if (core.plugin && core.plugin._psbEnableCanvas) core.plugin._psbEnableCanvas();
					if (core.plugin && core.plugin.drawPastelStatusBar) core.plugin.drawPastelStatusBar();
				},
        "drawStatistics": function () {
			// 浏览地图时参与的统计项目

			return [
				'yellowDoor', 'blueDoor', 'redDoor', 'greenDoor', 'steelDoor',
				'yellowKey', 'blueKey', 'redKey', 'greenKey', 'steelKey',
				'redGem', 'blueGem', 'greenGem', 'yellowGem',
				'redPotion', 'bluePotion', 'greenPotion', 'yellowPotion', 'superPotion',
				'pickaxe', 'bomb', 'centerFly', 'icePickaxe', 'freezeBadge',
				'earthquake', 'upFly', 'downFly', 'jumpShoes', 'lifeWand',
				'poisonWine', 'weakWine', 'curseWine', 'superWine',
				'sword1', 'sword2', 'sword3', 'sword4', 'sword5',
				'shield1', 'shield2', 'shield3', 'shield4', 'shield5',
				// 在这里可以增加新的ID来进行统计个数，只能增加道具ID
			];
		},
        "drawAbout": function () {
			// 绘制“关于”界面
			core.ui.closePanel();
			core.lockControl();
			core.status.event.id = 'about';

			var left = 48,
				top = 36,
				right = (core._PX_ || core.__PIXELS__) - 2 * left,
				bottom = (core._PY_ || core.__PIXELS__) - 2 * top;

			core.setAlpha('ui', 0.85);
			core.fillRect('ui', left, top, right, bottom, '#000000');
			core.setAlpha('ui', 1);
			core.strokeRect('ui', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2);

			var text_start = left + 24;

			// 名称
			core.setTextAlign('ui', 'left');
			var globalAttribute = core.status.globalAttribute || core.initStatus.globalAttribute;
			core.fillText('ui', "HTML5 魔塔样板", text_start, top + 35, globalAttribute.selectColor, "bold 22px " + globalAttribute.font);
			core.fillText('ui', "版本： " + main.__VERSION__, text_start, top + 80, "#FFFFFF", "bold 17px " + globalAttribute.font);
			core.fillText('ui', "作者： 艾之葵", text_start, top + 112);
			core.fillText('ui', 'HTML5魔塔交流群：539113091', text_start, top + 112 + 32);
			// TODO: 写自己的“关于”页面，每次增加32像素即可
			core.playSound('打开界面');
		}
    }
}