- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
197 lines
5.7 KiB
GDScript
197 lines
5.7 KiB
GDScript
# flame_dungeon_razador_system.gd —— Metin2 40250 经典红龙烈焰神殿与拉扎多尔炼狱试炼 1:1
|
|
# 对照 40250 服务端 flame_dungeon.start.1.script, quest/flame_dungeon*, char_item.cpp
|
|
class_name FlameDungeonRazadorSystem
|
|
extends RefCounted
|
|
|
|
signal dungeon_entered(time_limit: float)
|
|
signal stage_advanced(new_stage: int, stage_desc: String)
|
|
signal altar_lit(altars_lit: int, total_altars: int)
|
|
signal razador_spawned(mob_vnum: int)
|
|
signal razador_slain(drop_rewards: Array)
|
|
signal dungeon_completed()
|
|
signal dungeon_failed(reason: String)
|
|
|
|
const NPC_AM_HEH_WATCHER := 20385 # 烈焰守望者 (Am-heh Watcher)
|
|
const MIN_ENTRY_LEVEL := 100 # 进入等级门槛 100 级
|
|
const VNUM_TICKET_71095 := 71095 # 通行证
|
|
const VNUM_TICKET_71130 := 71130 # 烈焰通行证
|
|
|
|
const MOB_RAZADOR := 6091 # 烈焰魔王拉扎多尔 (Razador)
|
|
const VNUM_RAZADOR_CHEST := 50186 # 拉扎多尔宝箱
|
|
|
|
const DUNGEON_TIME_LIMIT := 2700.0 # 45 分钟 (2700 秒)
|
|
|
|
# 7 大炼狱阶段定义
|
|
const STAGES_DESC: Array = [
|
|
"阶段 1: 清剿外围烈焰魔群",
|
|
"阶段 2: 寻觅正确齿轮诛灭引火者",
|
|
"阶段 3: 破灭炼狱灭世魔石",
|
|
"阶段 4: 诛灭四极炼狱护法",
|
|
"阶段 5: 使用真·玛阿特之石点燃五方火盆",
|
|
"阶段 6: 击碎烈焰火轮魔石",
|
|
"阶段 7: 进军神殿核心,决战烈焰魔王拉扎多尔!"
|
|
]
|
|
|
|
var is_active: bool = false
|
|
var current_stage: int = 1
|
|
var time_remaining: float = 0.0
|
|
var altars_lit_count: int = 0
|
|
var razador_alive: bool = false
|
|
|
|
# 检票进入烈焰神殿 (40250 flame_dungeon.start.1.script)
|
|
func enter_dungeon(player_level: int, inventory: Array) -> Dictionary:
|
|
if is_active:
|
|
return {"ok": false, "reason": "DUNGEON_ALREADY_ACTIVE", "msg": "神殿试炼已在进行中!"}
|
|
|
|
if player_level < MIN_ENTRY_LEVEL:
|
|
return {
|
|
"ok": false,
|
|
"reason": "LEVEL_TOO_LOW",
|
|
"msg": "实力不足!开启拉扎多尔烈焰试炼需角色达到 %d 级。" % MIN_ENTRY_LEVEL
|
|
}
|
|
|
|
# 检查并消耗 1 张通行证 (71095 或 71130)
|
|
var ticket_slot := -1
|
|
for i in range(inventory.size()):
|
|
if inventory[i] != null:
|
|
var v := int(inventory[i].get("vnum", 0))
|
|
if v == VNUM_TICKET_71095 or v == VNUM_TICKET_71130:
|
|
ticket_slot = i
|
|
break
|
|
|
|
if ticket_slot == -1:
|
|
return {"ok": false, "reason": "NO_ENTRY_TICKET", "msg": "守望者拦住了去路!需要持有【烈焰通行证】(71095/71130)。"}
|
|
|
|
var t_item: Dictionary = inventory[ticket_slot]
|
|
var cnt := int(t_item.get("count", 1))
|
|
if cnt > 1:
|
|
t_item["count"] = cnt - 1
|
|
else:
|
|
inventory[ticket_slot] = null
|
|
|
|
is_active = true
|
|
current_stage = 1
|
|
time_remaining = DUNGEON_TIME_LIMIT
|
|
altars_lit_count = 0
|
|
razador_alive = false
|
|
|
|
dungeon_entered.emit(DUNGEON_TIME_LIMIT)
|
|
stage_advanced.emit(1, STAGES_DESC[0])
|
|
|
|
return {
|
|
"ok": true,
|
|
"time_limit": DUNGEON_TIME_LIMIT,
|
|
"stage": 1,
|
|
"stage_desc": STAGES_DESC[0],
|
|
"msg": "通行证校验通过!烈焰之门轰然开启,限时 45 分钟挑战拉扎多尔!"
|
|
}
|
|
|
|
# 推进阶段目标
|
|
func advance_stage() -> Dictionary:
|
|
if not is_active:
|
|
return {"ok": false, "reason": "DUNGEON_NOT_ACTIVE"}
|
|
|
|
if current_stage < 7:
|
|
current_stage += 1
|
|
var desc: String = STAGES_DESC[current_stage - 1]
|
|
|
|
if current_stage == 7:
|
|
# 召唤最终首领拉扎多尔
|
|
razador_alive = true
|
|
razador_spawned.emit(MOB_RAZADOR)
|
|
|
|
stage_advanced.emit(current_stage, desc)
|
|
return {
|
|
"ok": true,
|
|
"current_stage": current_stage,
|
|
"stage_desc": desc,
|
|
"msg": "试炼突破!开启【%s】。" % desc
|
|
}
|
|
|
|
return {"ok": false, "reason": "ALREADY_AT_FINAL_STAGE"}
|
|
|
|
# 阶段 5 点燃玛阿特火盆
|
|
func light_fire_altar() -> Dictionary:
|
|
if not is_active or current_stage != 5:
|
|
return {"ok": false, "reason": "NOT_IN_ALTAR_STAGE"}
|
|
|
|
altars_lit_count = mini(5, altars_lit_count + 1)
|
|
altar_lit.emit(altars_lit_count, 5)
|
|
|
|
if altars_lit_count >= 5:
|
|
# 5 座火盆全部点燃,自动迈入第 6 阶段
|
|
advance_stage()
|
|
return {
|
|
"ok": true,
|
|
"altars_lit": 5,
|
|
"stage_cleared": true,
|
|
"msg": "五方火盆全部燃起纯阳烈焰!封印解除,进入下一阶段!"
|
|
}
|
|
|
|
return {
|
|
"ok": true,
|
|
"altars_lit": altars_lit_count,
|
|
"stage_cleared": false,
|
|
"msg": "成功点燃 1 座火盆!当前进度:(%d / 5)。" % [altars_lit_count]
|
|
}
|
|
|
|
# 斩杀烈焰魔王拉扎多尔
|
|
func slay_razador(inventory: Array) -> Dictionary:
|
|
if not is_active or not razador_alive:
|
|
return {"ok": false, "reason": "RAZADOR_NOT_ALIVE"}
|
|
|
|
razador_alive = false
|
|
|
|
var rewards: Array = [
|
|
{"vnum": VNUM_RAZADOR_CHEST, "name": "拉扎多尔宝箱", "count": 1},
|
|
{"vnum": 25040, "name": "祝福卷轴", "count": 3},
|
|
{"vnum": 51501, "name": "龙石炼金原石", "count": 2}
|
|
]
|
|
|
|
for drop in rewards:
|
|
for i in range(inventory.size()):
|
|
if inventory[i] == null:
|
|
inventory[i] = drop.duplicate(true)
|
|
break
|
|
|
|
razador_slain.emit(rewards)
|
|
|
|
# 通关副本
|
|
is_active = false
|
|
time_remaining = 0.0
|
|
dungeon_completed.emit()
|
|
|
|
return {
|
|
"ok": true,
|
|
"rewards": rewards,
|
|
"msg": "伟大的征服!烈焰魔王拉扎多尔陨落!斩获【拉扎多尔宝箱】!"
|
|
}
|
|
|
|
# 逻辑心跳更新
|
|
func update(delta: float) -> void:
|
|
if not is_active:
|
|
return
|
|
time_remaining -= delta
|
|
if time_remaining <= 0.0:
|
|
is_active = false
|
|
razador_alive = false
|
|
dungeon_failed.emit("TIMEOUT")
|
|
|
|
# 序列化
|
|
func serialize() -> Dictionary:
|
|
return {
|
|
"is_active": is_active,
|
|
"current_stage": current_stage,
|
|
"time_remaining": time_remaining,
|
|
"altars_lit_count": altars_lit_count,
|
|
"razador_alive": razador_alive
|
|
}
|
|
|
|
# 反序列化
|
|
func deserialize(data: Dictionary) -> void:
|
|
is_active = bool(data.get("is_active", false))
|
|
current_stage = int(data.get("current_stage", 1))
|
|
time_remaining = float(data.get("time_remaining", 0.0))
|
|
altars_lit_count = int(data.get("altars_lit_count", 0))
|
|
razador_alive = bool(data.get("razador_alive", false))
|