- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
131 lines
3.8 KiB
GDScript
131 lines
3.8 KiB
GDScript
# title_achievement_system.gd —— Metin2 40250 殿堂级头衔与成就系统 1:1
|
|
# 对照 40250 服务端 char.cpp, questmanager.cpp, locale_string.txt
|
|
class_name TitleAchievementSystem
|
|
extends RefCounted
|
|
|
|
signal achievement_unlocked(ach_id: String, ach_name: String, title: String)
|
|
signal title_equipped(title_name: String, bonuses: Dictionary)
|
|
signal title_unequipped()
|
|
|
|
const ACHIEVEMENTS: Dictionary = {
|
|
"ACH_DEMON_TOWER": {
|
|
"name": "恶魔之塔登顶",
|
|
"desc": "突破恶魔之塔 9 层并斩杀死神",
|
|
"title": "恶魔征服者",
|
|
"points": 50,
|
|
"bonuses": {"max_hp": 500}
|
|
},
|
|
"ACH_SPIDER_BARONESS": {
|
|
"name": "蛛皇陨落",
|
|
"desc": "在蜘蛛洞穴三层巢穴击败蜘蛛男爵夫人",
|
|
"title": "蛛巢克星",
|
|
"points": 50,
|
|
"bonuses": {"monster_dmg_pct": 5}
|
|
},
|
|
"ACH_BERAN_SETAOU": {
|
|
"name": "冰龙之诛",
|
|
"desc": "击破四大魔石并消灭冰龙贝兰-塞陶",
|
|
"title": "屠龙勇士",
|
|
"points": 100,
|
|
"bonuses": {"boss_dmg_pct": 10}
|
|
},
|
|
"ACH_AZRAEL": {
|
|
"name": "冥王末日",
|
|
"desc": "穿过地下墓穴七层击破冥王阿兹瑞尔",
|
|
"title": "深渊征服者",
|
|
"points": 100,
|
|
"bonuses": {"def": 50}
|
|
},
|
|
"ACH_FOUR_WORLD_BOSSES": {
|
|
"name": "四方平定",
|
|
"desc": "击杀野外九尾妖狐、沙漠龟、黄虎之魂与烈焰领主",
|
|
"title": "大陆守护者",
|
|
"points": 150,
|
|
"bonuses": {"critical_pct": 5}
|
|
},
|
|
"ACH_SKILL_GRANDMASTER": {
|
|
"name": "大巧不工",
|
|
"desc": "将任意核心战斗技能修炼至 P 级完美境界",
|
|
"title": "武学宗师",
|
|
"points": 80,
|
|
"bonuses": {"cast_speed": 10}
|
|
},
|
|
"ACH_EPIC_BLACKSMITH": {
|
|
"name": "神兵天成",
|
|
"desc": "成功完成修元平史诗武器或蓝钢神甲升阶转换",
|
|
"title": "神工锻造师",
|
|
"points": 80,
|
|
"bonuses": {"att": 50}
|
|
}
|
|
}
|
|
|
|
var unlocked_achievements: Dictionary = {} # ach_id -> bool
|
|
var unlocked_titles: Array = [] # list of title strings
|
|
var total_achievement_points: int = 0
|
|
var active_title: String = ""
|
|
|
|
# 追踪并达成成就
|
|
func complete_achievement(ach_id: String) -> Dictionary:
|
|
if not ACHIEVEMENTS.has(ach_id):
|
|
return {"ok": false, "reason": "INVALID_ACH_ID", "msg": "未知的成就标识!"}
|
|
|
|
if unlocked_achievements.get(ach_id, false):
|
|
return {"ok": false, "reason": "ALREADY_COMPLETED", "msg": "该成就已达成!"}
|
|
|
|
var cfg: Dictionary = ACHIEVEMENTS[ach_id]
|
|
unlocked_achievements[ach_id] = true
|
|
total_achievement_points += int(cfg["points"])
|
|
|
|
var title_name: String = cfg["title"]
|
|
if not unlocked_titles.has(title_name):
|
|
unlocked_titles.append(title_name)
|
|
|
|
achievement_unlocked.emit(ach_id, cfg["name"], title_name)
|
|
|
|
return {
|
|
"ok": true,
|
|
"ach_id": ach_id,
|
|
"name": cfg["name"],
|
|
"title": title_name,
|
|
"points_awarded": cfg["points"],
|
|
"total_points": total_achievement_points,
|
|
"msg": "达成里程碑成就【%s】!解锁荣耀称号【%s】!" % [cfg["name"], title_name]
|
|
}
|
|
|
|
# 佩戴称号
|
|
func equip_title(title_name: String) -> Dictionary:
|
|
if not unlocked_titles.has(title_name):
|
|
return {"ok": false, "reason": "TITLE_NOT_UNLOCKED", "msg": "你尚未解锁该头衔称号!"}
|
|
|
|
active_title = title_name
|
|
var bonuses := get_active_bonuses()
|
|
|
|
title_equipped.emit(title_name, bonuses)
|
|
|
|
return {
|
|
"ok": true,
|
|
"active_title": active_title,
|
|
"bonuses": bonuses,
|
|
"msg": "已成功佩戴头衔【%s】!战力属性已生效!" % title_name
|
|
}
|
|
|
|
# 卸下称号
|
|
func unequip_title() -> void:
|
|
active_title = ""
|
|
title_unequipped.emit()
|
|
|
|
# 获取当前佩戴称号的属性加成
|
|
func get_active_bonuses() -> Dictionary:
|
|
if active_title.is_empty():
|
|
return {}
|
|
|
|
for cfg in ACHIEVEMENTS.values():
|
|
if cfg["title"] == active_title:
|
|
return cfg["bonuses"].duplicate()
|
|
|
|
return {}
|
|
|
|
# 查询成就状态
|
|
func is_achievement_unlocked(ach_id: String) -> bool:
|
|
return unlocked_achievements.get(ach_id, false)
|