Files
mtgodot-poc/project/flame_dungeon_razador_system.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

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))