Files
mtgodot-poc/project/cursed_monolith_spire_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.0 KiB
GDScript

# cursed_monolith_spire_system.gd —— Metin2 40250 野外魔石破除之【封印魔塔】限时防守挑战 1:1
# 对照 40250 服务端 regen.cpp, dungeon.cpp, char_battle.cpp, item_manager.cpp
class_name CursedMonolithSpireSystem
extends RefCounted
signal spire_emerged(pos: Vector3, time_limit: float)
signal wave_spawned(wave_index: int, wave_name: String, mob_count: int)
signal trial_completed(success: bool, time_used: float)
signal reward_chest_spawned(chest_vnum: int, pos: Vector3)
const TRIAL_TIME_LIMIT: float = 180.0 # 3 分钟倒计时
const CHEST_VNUM: int = 50130 # 上古魔塔宝箱 (Ancient Spire Chest)
const WAVE_CONFIG: Dictionary = {
1: {
"name": "第 1 波:狂暴魔兵突袭",
"mob_vnum": 701, # 恶魔士兵
"count": 6
},
2: {
"name": "第 2 波:双生魔将合围",
"mob_vnum": 1092, # 恶魔大将
"count": 2
},
3: {
"name": "第 3 波:上古守墓魔王",
"mob_vnum": 1093, # 恶魔死神幻影
"count": 1
}
}
var is_active: bool = false
var current_wave: int = 0
var time_remaining: float = 0.0
var spire_pos: Vector3 = Vector3.ZERO
var difficulty: int = 50
var active_mobs: Dictionary = {} # mob_id -> { "vnum", "alive" }
var next_mob_id: int = 1
var is_victory: bool = false
var chest_available: bool = false
# 启动封印魔塔挑战
func start_trial(pos: Vector3, diff_level: int = 50) -> Dictionary:
if is_active:
return {"ok": false, "reason": "TRIAL_ALREADY_ACTIVE", "msg": "封印魔塔挑战正在进行中!"}
is_active = true
is_victory = false
chest_available = false
spire_pos = pos
difficulty = diff_level
time_remaining = TRIAL_TIME_LIMIT
current_wave = 0
active_mobs.clear()
next_mob_id = 1
spire_emerged.emit(pos, TRIAL_TIME_LIMIT)
# 立即召唤第 1 波
_spawn_wave(1)
return {
"ok": true,
"pos": pos,
"time_limit": TRIAL_TIME_LIMIT,
"msg": "大地剧烈震颤!【诅咒的封印黑石魔塔】破土升起!请在 180 秒内清除所有魔潮!"
}
# 产生指定阶段怪潮
func _spawn_wave(wave_idx: int) -> void:
current_wave = wave_idx
var cfg = WAVE_CONFIG[wave_idx]
var count: int = cfg["count"]
var vnum: int = cfg["mob_vnum"]
active_mobs.clear()
for i in range(count):
var mid := next_mob_id
next_mob_id += 1
active_mobs[mid] = {
"id": mid,
"vnum": vnum,
"alive": true
}
wave_spawned.emit(wave_idx, cfg["name"], count)
# 击杀魔潮怪物
func kill_mob(mob_id: int) -> Dictionary:
if not is_active:
return {"ok": false, "reason": "TRIAL_NOT_ACTIVE"}
if not active_mobs.has(mob_id) or not active_mobs[mob_id]["alive"]:
return {"ok": false, "reason": "MOB_NOT_FOUND"}
active_mobs[mob_id]["alive"] = false
active_mobs.erase(mob_id)
# 检查当前波次是否全清
if active_mobs.is_empty():
if current_wave < 3:
# 进入下一波
var next_w = current_wave + 1
_spawn_wave(next_w)
return {
"ok": true,
"wave_cleared": current_wave - 1,
"next_wave": next_w,
"msg": "当前波次肃清!更加凶险的魔物正在凝聚!"
}
else:
# 第 3 波首领击破 -> 大获全胜!
return _finish_trial(true)
return {
"ok": true,
"remaining_in_wave": active_mobs.size()
}
# 帧心跳更新限时倒计时
func update(delta: float) -> Dictionary:
if not is_active:
return {"status": "inactive"}
time_remaining -= delta
if time_remaining <= 0.0:
time_remaining = 0.0
return _finish_trial(false)
return {
"status": "running",
"wave": current_wave,
"remaining_mobs": active_mobs.size(),
"time_remaining": time_remaining
}
# 结算挑战胜负
func _finish_trial(success: bool) -> Dictionary:
is_active = false
is_victory = success
var time_used = TRIAL_TIME_LIMIT - time_remaining
if success:
chest_available = true
reward_chest_spawned.emit(CHEST_VNUM, spire_pos)
trial_completed.emit(true, time_used)
return {
"ok": true,
"victory": true,
"time_used": time_used,
"msg": "【封印魔塔】封印彻底瓦解!降下【上古魔塔宝箱】!耗时: %.1f 秒" % time_used
}
else:
active_mobs.clear()
trial_completed.emit(false, TRIAL_TIME_LIMIT)
return {
"ok": true,
"victory": false,
"time_used": TRIAL_TIME_LIMIT,
"msg": "时间耗尽!封印黑石魔塔重新沉入深渊地底,挑战失败!"
}
# 开启上古宝箱获取掉落
func open_reward_chest() -> Array:
if not chest_available:
return []
chest_available = false
var drops: Array = [
{"vnum": 71003, "name": "祝福卷轴", "count": 2},
{"vnum": 50513, "name": "灵魂之石", "count": 1},
{"vnum": 70024, "name": "祝福宝珠", "count": 1},
{"vnum": 1, "name": "金币", "count": 3500000}
]
return drops
# 取消/中断挑战
func cancel_trial() -> Dictionary:
if not is_active:
return {"ok": false, "reason": "NOT_ACTIVE"}
is_active = false
active_mobs.clear()
return {"ok": true, "msg": "挑战已提前终止。"}
# 获取状态
func get_trial_state() -> Dictionary:
return {
"is_active": is_active,
"is_victory": is_victory,
"current_wave": current_wave,
"remaining_mobs": active_mobs.size(),
"time_remaining": time_remaining,
"chest_available": chest_available
}