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

171 lines
4.4 KiB
GDScript

# combat_replay_snapshot_system.gd —— Metin2 40250 战斗高光战报与首领击杀瞬间快照 1:1
# 对照 40250 服务端 battle.cpp, packet.h, char_battle.cpp
class_name CombatReplaySnapshotSystem
extends RefCounted
signal combat_session_started(target_name: String)
signal combat_highlight_generated(card: Dictionary)
signal new_personal_record(target_name: String, record_type: String, new_val: int)
const MAX_STORED_CARDS := 20
var is_in_session: bool = false
var session_id: int = 1
var target_vnum: int = 0
var target_name: String = ""
var target_max_hp: int = 0
var session_start_time: float = 0.0
var elapsed_time: float = 0.0
var total_damage_dealt: int = 0
var total_damage_taken: int = 0
var total_attacks: int = 0
var total_skills_used: int = 0
var max_single_hit: int = 0
var critical_hits_count: int = 0
# 历史高光战报归档
var highlight_cards: Array = []
# 个人最高纪录:target_name -> { "best_dps": int, "fastest_time": float, "highest_hit": int }
var personal_bests: Dictionary = {}
# 开始一场战斗记录
func start_combat_session(vnum: int, tname: String, max_hp: int) -> Dictionary:
is_in_session = true
target_vnum = vnum
target_name = tname
target_max_hp = max_hp
session_start_time = Time.get_unix_time_from_system()
elapsed_time = 0.0
total_damage_dealt = 0
total_damage_taken = 0
total_attacks = 0
total_skills_used = 0
max_single_hit = 0
critical_hits_count = 0
combat_session_started.emit(target_name)
return {
"ok": true,
"session_id": session_id,
"target_name": target_name,
"target_hp": target_max_hp
}
# 记录一次攻击命中
func record_hit(dmg: int, is_crit: bool, skill_name: String = "") -> void:
if not is_in_session:
return
total_damage_dealt += dmg
total_attacks += 1
if is_crit:
critical_hits_count += 1
if not skill_name.is_empty():
total_skills_used += 1
if dmg > max_single_hit:
max_single_hit = dmg
# 记录受到的伤害
func record_damage_taken(dmg: int) -> void:
if not is_in_session:
return
total_damage_taken += dmg
# 帧心跳更新战斗时长
func update(delta: float) -> void:
if is_in_session:
elapsed_time += delta
# 结束战斗并生成结构化高光快照战报
func end_combat_session(is_kill: bool, drops: Array = []) -> Dictionary:
if not is_in_session:
return {"ok": false, "reason": "NO_ACTIVE_SESSION"}
is_in_session = false
var duration = max(0.5, elapsed_time)
var dps = int(float(total_damage_dealt) / duration)
# 战斗评级评估 (S / A / B / C)
var rank = "C"
if is_kill:
if dps >= 4000:
rank = "S"
elif dps >= 2500:
rank = "A"
elif dps >= 1200:
rank = "B"
else:
rank = "C"
var card: Dictionary = {
"session_id": session_id,
"target_vnum": target_vnum,
"target_name": target_name,
"is_kill": is_kill,
"rank": rank,
"duration": duration,
"total_damage": total_damage_dealt,
"dps": dps,
"max_single_hit": max_single_hit,
"crit_count": critical_hits_count,
"damage_taken": total_damage_taken,
"skills_used": total_skills_used,
"drops": drops.duplicate(true),
"datetime": Time.get_datetime_string_from_system()
}
session_id += 1
highlight_cards.push_front(card)
if highlight_cards.size() > MAX_STORED_CARDS:
highlight_cards.pop_back()
# 刷新个人纪录判定
_update_personal_bests(card)
combat_highlight_generated.emit(card)
return card
# 检查并更新个人生涯纪录
func _update_personal_bests(card: Dictionary) -> void:
if not card["is_kill"]:
return
var tname: String = card["target_name"]
if not personal_bests.has(tname):
personal_bests[tname] = {
"best_dps": card["dps"],
"fastest_time": card["duration"],
"highest_hit": card["max_single_hit"]
}
new_personal_record.emit(tname, "FIRST_KILL", card["dps"])
return
var pb = personal_bests[tname]
if card["dps"] > pb["best_dps"]:
pb["best_dps"] = card["dps"]
new_personal_record.emit(tname, "BEST_DPS", card["dps"])
if card["duration"] < pb["fastest_time"]:
pb["fastest_time"] = card["duration"]
new_personal_record.emit(tname, "FASTEST_TIME", int(card["duration"]))
if card["max_single_hit"] > pb["highest_hit"]:
pb["highest_hit"] = card["max_single_hit"]
new_personal_record.emit(tname, "HIGHEST_HIT", card["max_single_hit"])
# 查询最近战报
func get_recent_highlights() -> Array:
return highlight_cards
# 查询特定目标的最佳战报纪录
func get_personal_best(tname: String) -> Dictionary:
return personal_bests.get(tname, {})