Files
mtgodot-poc/project/dragon_lair_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

258 lines
6.9 KiB
GDScript

# dragon_lair_system.gd —— Metin2 40250 冰龙贝兰-塞陶挑战巢穴 1:1
# 对照 40250 服务端 dragon_lair.quest, dungeon.cpp, char_battle.cpp, mob 2493, NPC 30121
class_name DragonLairSystem
extends RefCounted
signal dungeon_started(time_limit: float)
signal metin_destroyed(metin_vnum: int, metin_name: String)
signal minions_spawned(count: int, mob_vnum: int)
signal dragon_hp_changed(current_hp: int, max_hp: int)
signal dragon_defeated(drops: Array)
signal dungeon_failed(reason: String)
const NPC_GHOST_OF_SURA := 30121 # 修罗鬼魂
const ITEM_TWISTED_KEY := 30179 # 扭曲钥匙
const KEY_REQUIRED_COUNT := 3
const MIN_LEVEL := 75
const TIME_LIMIT := 3600.0 # 60 分钟限时
const BOSS_VNUM := 2493 # 贝兰-塞陶 (Beran-Setaou)
const BOSS_MAX_HP := 800000
const BOSS_BASE_ATK := 1800
const BOSS_BASE_DEF := 650
const METIN_TABLE: Dictionary = {
8031: {"name": "傲慢之石", "desc": "提供 30% 物理抗性减免"},
8032: {"name": "寂寞之石", "desc": "提供生命自然恢复"},
8033: {"name": "平静之石", "desc": "提供 30% 魔法抗性减免"},
8034: {"name": "复仇之石", "desc": "提供 40% 攻击力增幅"}
}
var is_active: bool = false
var remaining_time: float = 0.0
var dragon_hp: int = 0
var dragon_max_hp: int = BOSS_MAX_HP
var is_dragon_alive: bool = false
var active_metins: Dictionary = {} # vnum -> bool (true = alive)
var active_minions: Array = []
var regen_timer: float = 0.0
# 进入冰龙巢穴 (通过 NPC 30121 验证)
func enter_lair(player_level: int, inventory: Array) -> Dictionary:
if player_level < MIN_LEVEL:
return {
"ok": false,
"reason": "LEVEL_TOO_LOW",
"msg": "你的等级不足 75 级,无法承受冰龙巢穴的严寒!"
}
# 检查背包中的 3 把扭曲钥匙 (30179)
var key_slots: Array = []
var total_keys: int = 0
for i in range(inventory.size()):
var item = inventory[i]
if item != null and int(item.get("vnum", 0)) == ITEM_TWISTED_KEY:
var cnt: int = int(item.get("count", 1))
total_keys += cnt
key_slots.append({"slot": i, "count": cnt})
if total_keys < KEY_REQUIRED_COUNT:
return {
"ok": false,
"reason": "MISSING_KEYS",
"current_keys": total_keys,
"required_keys": KEY_REQUIRED_COUNT,
"msg": "进入冰龙巢穴需要 3 把扭曲钥匙!你当前拥有 %d 把。" % total_keys
}
# 扣除 3 把扭曲钥匙
var needed: int = KEY_REQUIRED_COUNT
for ks in key_slots:
var slot_idx: int = ks["slot"]
var available: int = ks["count"]
if available <= needed:
needed -= available
inventory[slot_idx] = null
else:
inventory[slot_idx]["count"] = available - needed
needed = 0
if needed <= 0:
break
# 初始化巢穴状态
is_active = true
remaining_time = TIME_LIMIT
dragon_hp = BOSS_MAX_HP
dragon_max_hp = BOSS_MAX_HP
is_dragon_alive = true
active_minions.clear()
regen_timer = 0.0
# 刷新 4 块龙神魔石
active_metins.clear()
for vnum in METIN_TABLE.keys():
active_metins[vnum] = true
dungeon_started.emit(remaining_time)
return {
"ok": true,
"time_limit": remaining_time,
"dragon_hp": dragon_hp,
"metins": active_metins.duplicate(),
"msg": "已使用 3 把扭曲钥匙,成功开启冰龙巢穴!限时 60 分钟!"
}
# 获取当前冰龙实际攻击力 (受 8034 复仇之石影响)
func get_dragon_atk() -> int:
var atk := BOSS_BASE_ATK
if active_metins.get(8034, false):
atk = int(atk * 1.4) # +40% ATK
return atk
# 获取当前冰龙防御力
func get_dragon_def() -> int:
return BOSS_BASE_DEF
# 击破魔石机制
func destroy_metin(metin_vnum: int) -> Dictionary:
if not is_active:
return {"ok": false, "reason": "DUNGEON_NOT_ACTIVE"}
if not active_metins.get(metin_vnum, false):
return {"ok": false, "reason": "METIN_ALREADY_DESTROYED"}
active_metins[metin_vnum] = false
var mname: String = METIN_TABLE[metin_vnum]["name"]
# 击破魔石唤醒 4 只塞陶龙宫护卫 (vnum: 2401)
var spawn_count := 4
for i in range(spawn_count):
active_minions.append({"vnum": 2401, "hp": 30000})
metin_destroyed.emit(metin_vnum, mname)
minions_spawned.emit(spawn_count, 2401)
return {
"ok": true,
"metin_vnum": metin_vnum,
"metin_name": mname,
"remaining_metins": get_alive_metin_count(),
"spawned_minions": spawn_count,
"msg": "【%s】已被击破!对应龙神加成已破除,但唤醒了龙宫护卫!" % mname
}
# 获取存活魔石数量
func get_alive_metin_count() -> int:
var count := 0
for v in active_metins.values():
if v:
count += 1
return count
# 对冰龙造成伤害
func apply_damage(raw_dmg: int, is_magic: bool = false) -> Dictionary:
if not is_active or not is_dragon_alive:
return {"ok": false, "reason": "DRAGON_NOT_ALIVE"}
var def := get_dragon_def()
var net_dmg: float = max(1, raw_dmg - def)
# 8031 傲慢之石减免 30% 物理伤害
if not is_magic and active_metins.get(8031, false):
net_dmg *= 0.70
# 8033 平静之石减免 30% 魔法伤害
if is_magic and active_metins.get(8033, false):
net_dmg *= 0.70
var final_dmg := int(round(net_dmg))
dragon_hp = max(0, dragon_hp - final_dmg)
dragon_hp_changed.emit(dragon_hp, dragon_max_hp)
# 击杀冰龙判定
if dragon_hp <= 0:
is_dragon_alive = false
is_active = false
var drops := _generate_dragon_drops()
dragon_defeated.emit(drops)
return {
"ok": true,
"killed": true,
"net_dmg": final_dmg,
"remaining_hp": 0,
"drops": drops,
"msg": "【冰龙贝兰-塞陶】已被彻底击败!龙鳞与龙爪散落一地!"
}
return {
"ok": true,
"killed": false,
"net_dmg": final_dmg,
"remaining_hp": dragon_hp,
"hp_ratio": float(dragon_hp) / float(dragon_max_hp)
}
# 逐帧更新 (计时与 8032 寂寞之石生命回复)
func update(dt: float) -> void:
if not is_active:
return
remaining_time -= dt
if remaining_time <= 0.0:
is_active = false
is_dragon_alive = false
dungeon_failed.emit("TIME_OUT")
return
# 8032 寂寞之石:每 5 秒为冰龙回复 2% 最大生命 (16000 HP)
if is_dragon_alive and active_metins.get(8032, false):
regen_timer += dt
if regen_timer >= 5.0:
regen_timer = 0.0
var heal_amount := int(BOSS_MAX_HP * 0.02)
dragon_hp = mini(BOSS_MAX_HP, dragon_hp + heal_amount)
dragon_hp_changed.emit(dragon_hp, dragon_max_hp)
# 生成冰龙掉落物 (71123 龙鳞, 71129 龙爪, 50186 水龙宝箱)
func _generate_dragon_drops() -> Array:
var drops: Array = []
# 1. 必掉史诗升阶材料:龙鳞 (71123)
drops.append({
"vnum": 71123,
"name": "龙鳞",
"count": randi_range(1, 2)
})
# 2. 必掉史诗升阶材料:龙爪 (71129)
drops.append({
"vnum": 71129,
"name": "龙爪",
"count": randi_range(1, 2)
})
# 3. 必掉水龙专属宝箱 (50186)
drops.append({
"vnum": 50186,
"name": "贝兰-塞陶宝箱",
"count": 1
})
# 4. 必掉灵魂之石 (50513)
drops.append({
"vnum": 50513,
"name": "灵魂之石",
"count": 1
})
# 5. 大额金币掉落
drops.append({
"vnum": 1,
"name": "金币",
"count": randi_range(150000, 300000)
})
return drops