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 客户端对拍缺陷发现与修复工程指南
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# dungeon_ranking_system.gd —— Metin2 40250 地牢信息面板与最速通关记录系统 1:1
|
||||
# 对照 40250 服务端与客户端 dungeon_info.cpp, dungeon.cpp, uidungeoninfo.py
|
||||
class_name DungeonRankingSystem
|
||||
extends RefCounted
|
||||
|
||||
signal record_updated(dungeon_id: String, fastest_time: float, total_clears: int)
|
||||
signal new_fastest_record(dungeon_id: String, new_time: float, old_time: float)
|
||||
|
||||
# 40250 官方经典地牢登记表
|
||||
const DUNGEON_REGISTRY: Dictionary = {
|
||||
"demon_tower": {
|
||||
"name": "恶魔之塔",
|
||||
"min_level": 40,
|
||||
"ticket_vnum": 0,
|
||||
"ticket_count": 0,
|
||||
"ticket_name": "无",
|
||||
"cooldown_seconds": 0.0
|
||||
},
|
||||
"spider_dungeon_3": {
|
||||
"name": "蜘蛛洞穴三层",
|
||||
"min_level": 50,
|
||||
"ticket_vnum": 30327,
|
||||
"ticket_count": 1,
|
||||
"ticket_name": "蜘蛛钥匙",
|
||||
"cooldown_seconds": 1800.0 # 30 分钟
|
||||
},
|
||||
"dragon_lair": {
|
||||
"name": "冰龙贝兰巢穴",
|
||||
"min_level": 75,
|
||||
"ticket_vnum": 30179,
|
||||
"ticket_count": 3,
|
||||
"ticket_name": "扭曲钥匙",
|
||||
"cooldown_seconds": 3600.0 # 60 分钟
|
||||
},
|
||||
"devils_catacomb": {
|
||||
"name": "地下墓穴",
|
||||
"min_level": 75,
|
||||
"ticket_vnum": 30311,
|
||||
"ticket_count": 1,
|
||||
"ticket_name": "灵魂水晶钥匙",
|
||||
"cooldown_seconds": 3600.0 # 60 分钟
|
||||
}
|
||||
}
|
||||
|
||||
# 个人通关数据字典: dungeon_id -> Dictionary
|
||||
var dungeon_records: Dictionary = {}
|
||||
|
||||
func _init() -> void:
|
||||
for d_id in DUNGEON_REGISTRY.keys():
|
||||
dungeon_records[d_id] = {
|
||||
"total_clears": 0,
|
||||
"fastest_clear_time": 999999.0, # 初始极大值
|
||||
"highest_peak_damage": 0,
|
||||
"last_clear_timestamp": 0.0
|
||||
}
|
||||
|
||||
# 记录通关结算
|
||||
func record_clear(
|
||||
dungeon_id: String,
|
||||
clear_time_sec: float,
|
||||
peak_damage: int,
|
||||
current_timestamp: float = 0.0
|
||||
) -> Dictionary:
|
||||
if not DUNGEON_REGISTRY.has(dungeon_id):
|
||||
return {"ok": false, "reason": "INVALID_DUNGEON_ID", "msg": "未知的地牢!"}
|
||||
|
||||
var rec = dungeon_records[dungeon_id]
|
||||
rec["total_clears"] += 1
|
||||
rec["last_clear_timestamp"] = current_timestamp
|
||||
|
||||
# 检查并更新最高单次输出伤害
|
||||
if peak_damage > int(rec["highest_peak_damage"]):
|
||||
rec["highest_peak_damage"] = peak_damage
|
||||
|
||||
# 检查是否刷新最速通关记录
|
||||
var is_new_record := false
|
||||
var old_time: float = float(rec["fastest_clear_time"])
|
||||
if clear_time_sec < old_time:
|
||||
is_new_record = true
|
||||
rec["fastest_clear_time"] = clear_time_sec
|
||||
new_fastest_record.emit(dungeon_id, clear_time_sec, old_time)
|
||||
|
||||
record_updated.emit(dungeon_id, rec["fastest_clear_time"], rec["total_clears"])
|
||||
|
||||
var dname: String = DUNGEON_REGISTRY[dungeon_id]["name"]
|
||||
return {
|
||||
"ok": true,
|
||||
"dungeon_id": dungeon_id,
|
||||
"dungeon_name": dname,
|
||||
"clear_time": clear_time_sec,
|
||||
"is_new_record": is_new_record,
|
||||
"fastest_time": rec["fastest_clear_time"],
|
||||
"total_clears": rec["total_clears"],
|
||||
"highest_peak_damage": rec["highest_peak_damage"],
|
||||
"msg": "【%s】通关成功!耗时 %.1f 秒%s!" % [dname, clear_time_sec, " (刷新最快纪录!)" if is_new_record else ""]
|
||||
}
|
||||
|
||||
# 获取地牢详情与入场/冷却状态
|
||||
func get_dungeon_info(dungeon_id: String, current_timestamp: float = 0.0) -> Dictionary:
|
||||
if not DUNGEON_REGISTRY.has(dungeon_id):
|
||||
return {"ok": false, "reason": "INVALID_DUNGEON_ID"}
|
||||
|
||||
var cfg = DUNGEON_REGISTRY[dungeon_id]
|
||||
var rec = dungeon_records[dungeon_id]
|
||||
|
||||
# 判定冷却时间
|
||||
var cd_sec: float = float(cfg["cooldown_seconds"])
|
||||
var last_ts: float = float(rec["last_clear_timestamp"])
|
||||
var elapsed := current_timestamp - last_ts
|
||||
var is_on_cooldown := (cd_sec > 0.0 and last_ts > 0.0 and elapsed < cd_sec)
|
||||
var remaining_cd := maxf(0.0, cd_sec - elapsed) if is_on_cooldown else 0.0
|
||||
|
||||
return {
|
||||
"ok": true,
|
||||
"dungeon_id": dungeon_id,
|
||||
"name": cfg["name"],
|
||||
"min_level": cfg["min_level"],
|
||||
"ticket_vnum": cfg["ticket_vnum"],
|
||||
"ticket_count": cfg["ticket_count"],
|
||||
"ticket_name": cfg["ticket_name"],
|
||||
"is_on_cooldown": is_on_cooldown,
|
||||
"remaining_cooldown": remaining_cd,
|
||||
"total_clears": rec["total_clears"],
|
||||
"fastest_clear_time": rec["fastest_clear_time"] if rec["fastest_clear_time"] < 999999.0 else 0.0,
|
||||
"highest_peak_damage": rec["highest_peak_damage"]
|
||||
}
|
||||
|
||||
# 获取全部地牢摘要速览列表
|
||||
func get_all_dungeon_summaries(current_timestamp: float = 0.0) -> Array:
|
||||
var list: Array = []
|
||||
for d_id in DUNGEON_REGISTRY.keys():
|
||||
list.append(get_dungeon_info(d_id, current_timestamp))
|
||||
return list
|
||||
Reference in New Issue
Block a user