- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
265 lines
7.6 KiB
GDScript
265 lines
7.6 KiB
GDScript
# guild_skill_system.gd —— Metin2 40250 公会等级与 7 大神龙技能树系统 1:1
|
|
# 对照 40250 服务端 guild.cpp, guild_manager.cpp, PythonGuild.cpp, skilldesc.txt:23-29
|
|
class_name GuildSkillSystem
|
|
extends RefCounted
|
|
|
|
signal guild_level_up(new_level: int, skill_points: int)
|
|
signal guild_skill_cast(skill_id: int, duration: float)
|
|
|
|
const MAX_GUILD_LEVEL := 20
|
|
const MAX_SKILL_LEVEL := 7
|
|
|
|
# 7 官方公会技能 (skilldesc.txt:23-29)
|
|
const SKILL_DRAGON_EYES := 151 # 龙神之眼: 被动增加最大神龙精力
|
|
const SKILL_DRAGON_BLOOD := 152 # 龙神之血: 最大 HP 提升
|
|
const SKILL_DRAGON_BLESSING := 153# 龙神庇护: 最大 SP 提升
|
|
const SKILL_HOLY_ARMOR := 154 # 圣灵之甲: 防御力百分比提升
|
|
const SKILL_ACCELERATE := 155 # 疾风步: 攻击与移动速度提升
|
|
const SKILL_DRAGON_RAGE := 156 # 龙神之怒: 致命一击几率提升
|
|
const SKILL_CASTING_AID := 157 # 咏唱神助: 施法提速 / 技能冷却缩减
|
|
|
|
const GUILD_SKILLS: Dictionary = {
|
|
SKILL_DRAGON_EYES: {
|
|
"name": "龙神之眼",
|
|
"is_passive": true,
|
|
"spirit_cost": 0,
|
|
"cooldown": 0.0,
|
|
"duration": 0.0
|
|
},
|
|
SKILL_DRAGON_BLOOD: {
|
|
"name": "龙神之血",
|
|
"is_passive": false,
|
|
"spirit_cost": 20,
|
|
"cooldown": 60.0,
|
|
"duration": 300.0 # 5 分钟
|
|
},
|
|
SKILL_DRAGON_BLESSING: {
|
|
"name": "龙神庇护",
|
|
"is_passive": false,
|
|
"spirit_cost": 20,
|
|
"cooldown": 60.0,
|
|
"duration": 300.0
|
|
},
|
|
SKILL_HOLY_ARMOR: {
|
|
"name": "圣灵之甲",
|
|
"is_passive": false,
|
|
"spirit_cost": 25,
|
|
"cooldown": 60.0,
|
|
"duration": 300.0
|
|
},
|
|
SKILL_ACCELERATE: {
|
|
"name": "疾风步",
|
|
"is_passive": false,
|
|
"spirit_cost": 25,
|
|
"cooldown": 60.0,
|
|
"duration": 300.0
|
|
},
|
|
SKILL_DRAGON_RAGE: {
|
|
"name": "龙神之怒",
|
|
"is_passive": false,
|
|
"spirit_cost": 30,
|
|
"cooldown": 60.0,
|
|
"duration": 300.0
|
|
},
|
|
SKILL_CASTING_AID: {
|
|
"name": "咏唱神助",
|
|
"is_passive": false,
|
|
"spirit_cost": 30,
|
|
"cooldown": 60.0,
|
|
"duration": 300.0
|
|
}
|
|
}
|
|
|
|
# 公会基础属性
|
|
var guild_level: int = 1
|
|
var guild_exp: int = 0
|
|
var skill_points: int = 0
|
|
var max_spirit: int = 100
|
|
var current_spirit: int = 100
|
|
|
|
# 技能加点等级: skill_id -> level (0~7)
|
|
var skill_levels: Dictionary = {
|
|
SKILL_DRAGON_EYES: 0,
|
|
SKILL_DRAGON_BLOOD: 0,
|
|
SKILL_DRAGON_BLESSING: 0,
|
|
SKILL_HOLY_ARMOR: 0,
|
|
SKILL_ACCELERATE: 0,
|
|
SKILL_DRAGON_RAGE: 0,
|
|
SKILL_CASTING_AID: 0
|
|
}
|
|
|
|
# 技能冷却与生效增益
|
|
var skill_cooldowns: Dictionary = {}
|
|
var active_buffs: Dictionary = {} # skill_id -> { duration, value }
|
|
|
|
# 计算升级所需经验 (guild.cpp)
|
|
static func get_exp_needed_for_level(lvl: int) -> int:
|
|
return lvl * 50000
|
|
|
|
# 捐献公会经验
|
|
func donate_exp(amount: int) -> Dictionary:
|
|
if guild_level >= MAX_GUILD_LEVEL:
|
|
return {"ok": false, "reason": "MAX_LEVEL", "msg": "公会已达到最高 20 级!"}
|
|
|
|
guild_exp += amount
|
|
var leveled_up := false
|
|
var levels_gained := 0
|
|
|
|
while guild_level < MAX_GUILD_LEVEL:
|
|
var needed = get_exp_needed_for_level(guild_level)
|
|
if guild_exp >= needed:
|
|
guild_exp -= needed
|
|
guild_level += 1
|
|
skill_points += 1
|
|
levels_gained += 1
|
|
leveled_up = true
|
|
else:
|
|
break
|
|
|
|
if leveled_up:
|
|
_recalculate_spirit()
|
|
guild_level_up.emit(guild_level, skill_points)
|
|
|
|
return {
|
|
"ok": true,
|
|
"guild_level": guild_level,
|
|
"guild_exp": guild_exp,
|
|
"needed_exp": get_exp_needed_for_level(guild_level),
|
|
"skill_points": skill_points,
|
|
"leveled_up": leveled_up
|
|
}
|
|
|
|
# 升级公会技能
|
|
func level_up_skill(skill_id: int) -> Dictionary:
|
|
if not GUILD_SKILLS.has(skill_id):
|
|
return {"ok": false, "reason": "INVALID_SKILL"}
|
|
if skill_points <= 0:
|
|
return {"ok": false, "reason": "NO_SKILL_POINTS", "msg": "可用公会技能点不足!"}
|
|
|
|
var cur_lvl: int = int(skill_levels.get(skill_id, 0))
|
|
if cur_lvl >= MAX_SKILL_LEVEL:
|
|
return {"ok": false, "reason": "MAX_SKILL_LEVEL", "msg": "该公会技能已满级!"}
|
|
|
|
skill_levels[skill_id] = cur_lvl + 1
|
|
skill_points -= 1
|
|
_recalculate_spirit()
|
|
|
|
return {
|
|
"ok": true,
|
|
"skill_id": skill_id,
|
|
"new_level": skill_levels[skill_id],
|
|
"remaining_points": skill_points
|
|
}
|
|
|
|
func _recalculate_spirit() -> void:
|
|
# 151 龙神之眼扩充神龙精力
|
|
var eyes_lvl: int = int(skill_levels.get(SKILL_DRAGON_EYES, 0))
|
|
var bonus_sp: int = int(round(float(eyes_lvl) / float(MAX_SKILL_LEVEL) * 100.0))
|
|
max_spirit = 100 + bonus_sp
|
|
current_spirit = mini(current_spirit, max_spirit)
|
|
|
|
# 施放公会技能
|
|
func cast_guild_skill(skill_id: int) -> Dictionary:
|
|
if not GUILD_SKILLS.has(skill_id):
|
|
return {"ok": false, "reason": "INVALID_SKILL"}
|
|
|
|
var cfg: Dictionary = GUILD_SKILLS[skill_id]
|
|
if bool(cfg["is_passive"]):
|
|
return {"ok": false, "reason": "PASSIVE_SKILL", "msg": "被动技能无需主动施放!"}
|
|
|
|
var lvl: int = int(skill_levels.get(skill_id, 0))
|
|
if lvl <= 0:
|
|
return {"ok": false, "reason": "NOT_LEARNED", "msg": "尚未学习该公会技能!"}
|
|
|
|
# 检查冷却
|
|
if float(skill_cooldowns.get(skill_id, 0.0)) > 0.0:
|
|
return {"ok": false, "reason": "COOLDOWN_ACTIVE", "remaining": skill_cooldowns[skill_id]}
|
|
|
|
# 检查精力消耗
|
|
var cost: int = int(cfg["spirit_cost"])
|
|
if current_spirit < cost:
|
|
return {"ok": false, "reason": "NOT_ENOUGH_SPIRIT", "msg": "公会神龙精力不足!"}
|
|
|
|
# 扣除精力并进入冷却
|
|
current_spirit -= cost
|
|
skill_cooldowns[skill_id] = float(cfg["cooldown"])
|
|
|
|
# 计算技能数值 (对齐 skilldesc.txt: k = lvl / 7.0)
|
|
var k: float = float(lvl) / float(MAX_SKILL_LEVEL)
|
|
var dur: float = float(cfg["duration"])
|
|
var val: float = 0.0
|
|
|
|
match skill_id:
|
|
SKILL_DRAGON_BLOOD: val = k * 20.0 # Max HP +20%
|
|
SKILL_DRAGON_BLESSING: val = k * 20.0 # Max SP +20%
|
|
SKILL_HOLY_ARMOR: val = k * 10.0 # DEF +10%
|
|
SKILL_ACCELERATE: val = k * 30.0 # Atk & Motion Speed +30
|
|
SKILL_DRAGON_RAGE: val = k * 50.0 # Crit +50%
|
|
SKILL_CASTING_AID: val = k * 50.0 # Cooldown reduction +50%
|
|
|
|
active_buffs[skill_id] = {
|
|
"name": cfg["name"],
|
|
"value": val,
|
|
"duration": dur,
|
|
"total_duration": dur
|
|
}
|
|
|
|
guild_skill_cast.emit(skill_id, dur)
|
|
|
|
return {
|
|
"ok": true,
|
|
"skill_id": skill_id,
|
|
"name": cfg["name"],
|
|
"value": val,
|
|
"duration": dur,
|
|
"current_spirit": current_spirit
|
|
}
|
|
|
|
# 逐帧更新冷却与生效增益
|
|
func update(dt: float) -> void:
|
|
# 更新技能冷却
|
|
for sid in skill_cooldowns.keys():
|
|
skill_cooldowns[sid] = maxf(0.0, float(skill_cooldowns[sid]) - dt)
|
|
if float(skill_cooldowns[sid]) <= 0.0:
|
|
skill_cooldowns.erase(sid)
|
|
|
|
# 更新增益持续时间
|
|
var expired: Array[int] = []
|
|
for sid in active_buffs.keys():
|
|
var b: Dictionary = active_buffs[sid]
|
|
b["duration"] = maxf(0.0, float(b["duration"]) - dt)
|
|
if float(b["duration"]) <= 0.0:
|
|
expired.append(sid)
|
|
|
|
for sid in expired:
|
|
active_buffs.erase(sid)
|
|
|
|
# 恢复精力 (每秒恢复 0.5)
|
|
current_spirit = mini(max_spirit, current_spirit + int(round(0.5 * dt)))
|
|
|
|
# 查询当前激活的所有公会增益
|
|
func get_active_bonuses() -> Dictionary:
|
|
var res: Dictionary = {
|
|
"max_hp_pct": 0.0,
|
|
"max_sp_pct": 0.0,
|
|
"def_pct": 0.0,
|
|
"speed_bonus": 0.0,
|
|
"critical_pct": 0.0,
|
|
"casting_aid_pct": 0.0
|
|
}
|
|
|
|
if active_buffs.has(SKILL_DRAGON_BLOOD):
|
|
res["max_hp_pct"] = float(active_buffs[SKILL_DRAGON_BLOOD]["value"])
|
|
if active_buffs.has(SKILL_DRAGON_BLESSING):
|
|
res["max_sp_pct"] = float(active_buffs[SKILL_DRAGON_BLESSING]["value"])
|
|
if active_buffs.has(SKILL_HOLY_ARMOR):
|
|
res["def_pct"] = float(active_buffs[SKILL_HOLY_ARMOR]["value"])
|
|
if active_buffs.has(SKILL_ACCELERATE):
|
|
res["speed_bonus"] = float(active_buffs[SKILL_ACCELERATE]["value"])
|
|
if active_buffs.has(SKILL_DRAGON_RAGE):
|
|
res["critical_pct"] = float(active_buffs[SKILL_DRAGON_RAGE]["value"])
|
|
if active_buffs.has(SKILL_CASTING_AID):
|
|
res["casting_aid_pct"] = float(active_buffs[SKILL_CASTING_AID]["value"])
|
|
|
|
return res
|