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

179 lines
6.2 KiB
GDScript

# soul_stone_grandmaster_system.gd —— Metin2 40250 官方宗师绝技灵魂石悟道与善恶值走火入魔系统 1:1
# 100% 对照 40250 服务端 training_grandmaster_skill.quest, char_skill.cpp, questlua_pc.cpp
class_name SoulStoneGrandmasterSystem
extends RefCounted
signal grandmaster_trained(skill_id: int, skill_name: String, new_level: int, alignment_deducted: int)
signal grandmaster_failed(skill_id: int, skill_name: String, alignment_penalty: int)
signal cooldown_cleared()
signal training_error(reason: String)
# 40250 官方核心道具 Vnum
const VNUM_SOUL_STONE := 50513 # 灵魂石 (Ruh Taşı / Soul Stone)
const VNUM_EXORCISM_SCROLL := 71001 # 驱魔卷轴 (清除修炼间隔)
# 技能等级境界常数
const GRAND_MASTER_MIN_LEVEL := 30 # G1 (30级)
const PERFECT_MASTER_LEVEL := 40 # P 阶绝顶境界 (40级)
# 最低极限负善恶值 (-20000 封底)
const MIN_ALIGNMENT_CAP := -20000
# 修炼冷却时间
var training_cooldown_left: float = 0.0 # 8~12 小时倒计时
# 判断技能是否处于宗师境界 (G1 ~ G10)
func can_train_skill(skill_level: int) -> bool:
return skill_level >= GRAND_MASTER_MIN_LEVEL and skill_level < PERFECT_MASTER_LEVEL
# 计算升级所需善恶值 (40250 官方公式: 1000 + 500 * (level - 30),负道德翻倍)
func get_required_alignment(skill_level: int, current_alignment: int) -> int:
if not can_train_skill(skill_level):
return 0
var base_cost = 1000 + 500 * (skill_level - GRAND_MASTER_MIN_LEVEL)
if current_alignment < 0:
return base_cost * 2
return base_cost
# 获取技能境界文本 (如 "G1", "G5", "P")
func get_skill_tier_text(level: int) -> String:
if level < GRAND_MASTER_MIN_LEVEL:
return "Normal/Master"
elif level >= PERFECT_MASTER_LEVEL:
return "P 阶 (Perfect Master)"
else:
return "G%d" % (level - GRAND_MASTER_MIN_LEVEL + 1)
# 使用灵魂石闭关冲击宗师绝技
func train_grandmaster_skill(
inventory: Array,
soul_stone_slot: int,
skill_id: int,
skill_name: String,
current_skill_level: int,
current_alignment: int,
has_exorcism_scroll: bool = false,
forced_success: bool = false,
forced_roll: int = -1
) -> Dictionary:
# 检查技能是否在 G 阶
if not can_train_skill(current_skill_level):
training_error.emit("INVALID_SKILL_LEVEL")
return {
"ok": false,
"reason": "INVALID_SKILL_LEVEL",
"msg": "只有达到宗师境界 (G1~G10) 的技能方可使用灵魂石闭关突破!"
}
# 检查冷却
if training_cooldown_left > 0.0 and not has_exorcism_scroll:
training_error.emit("COOLDOWN_ACTIVE")
return {
"ok": false,
"reason": "COOLDOWN_ACTIVE",
"msg": "心神尚未平复!需等待 %d 秒方可再次闭关,或使用【驱魔卷轴】重置心境。" % int(training_cooldown_left)
}
# 检查灵魂石
if soul_stone_slot < 0 or soul_stone_slot >= inventory.size() or inventory[soul_stone_slot] == null:
training_error.emit("NO_SOUL_STONE")
return {"ok": false, "reason": "NO_SOUL_STONE", "msg": "未找到灵魂石!"}
var item = inventory[soul_stone_slot]
var vnum = int(item.get("vnum", 0))
if vnum != VNUM_SOUL_STONE:
training_error.emit("NOT_A_SOUL_STONE")
return {"ok": false, "reason": "NOT_A_SOUL_STONE", "msg": "该物品并不是灵魂石!"}
# 计算所需善恶值
var need_align = get_required_alignment(current_skill_level, current_alignment)
# 检查是否跌破道德底线 (40250: cur_alignment < -19000 + need_alignment)
if current_alignment < (MIN_ALIGNMENT_CAP + 1000 + need_align):
training_error.emit("ALIGNMENT_TOO_LOW")
return {
"ok": false,
"reason": "ALIGNMENT_TOO_LOW",
"msg": "你的罪孽太过深重,心灵已被心魔完全吞噬!必须行善恢复善恶值后方能参悟。"
}
# 消耗灵魂石 (40250 pc.remove_item(50513))
_consume_item_one(inventory, soul_stone_slot)
# 设置下次领悟冷却时间 8~12 小时 (28800 ~ 43200 秒)
training_cooldown_left = randf_range(28800.0, 43200.0)
# 判定成功概率 (40250 官方约为 30% ~ 35%)
var r = forced_roll if forced_roll > 0 else randi_range(1, 100)
var is_success = (r <= 30) or forced_success
if is_success:
# 悟道成功
var new_lvl = current_skill_level + 1
var new_alignment = current_alignment - need_align
grandmaster_trained.emit(skill_id, skill_name, new_lvl, need_align)
var tier_str = get_skill_tier_text(new_lvl)
var reach_p = (new_lvl >= PERFECT_MASTER_LEVEL)
var msg = ""
if reach_p:
msg = "金光贯顶,大道终成!你的【%s】突破桎梏,登峰造极达成了【%s】绝顶境界!" % [skill_name, tier_str]
else:
msg = "神识升华,灵智通达!你的【%s】成功精进为【%s】!消耗善恶值 %d 点。" % [skill_name, tier_str, need_align]
return {
"ok": true,
"success": true,
"new_level": new_lvl,
"tier_text": tier_str,
"is_perfect_master": reach_p,
"alignment_cost": need_align,
"new_alignment": new_alignment,
"msg": msg
}
else:
# 走火入魔失败:扣除 1/3 ~ 1/2 的善恶值惩罚
var penalty_ratio = randf_range(0.33, 0.50)
var penalty = max(1, int(floor(need_align * penalty_ratio)))
var new_alignment = current_alignment - penalty
grandmaster_failed.emit(skill_id, skill_name, penalty)
return {
"ok": true,
"success": false,
"new_level": current_skill_level,
"tier_text": get_skill_tier_text(current_skill_level),
"is_perfect_master": false,
"alignment_cost": penalty,
"new_alignment": new_alignment,
"msg": "走火入魔!灵魂石在狂暴的灵气中化为碎粉,未能参悟真谛。惩罚扣除 %d 点善恶值!" % penalty
}
# 使用驱魔卷轴清除冷却
func use_exorcism_scroll() -> void:
training_cooldown_left = 0.0
cooldown_cleared.emit()
func update(delta: float) -> void:
if training_cooldown_left > 0.0:
training_cooldown_left = max(0.0, training_cooldown_left - delta)
func _consume_item_one(inventory: Array, slot: int) -> void:
if slot < 0 or slot >= inventory.size() or inventory[slot] == null:
return
var item = inventory[slot]
var count = int(item.get("count", 1))
if count > 1:
item["count"] = count - 1
else:
inventory[slot] = null
# 序列化与反序列化
func serialize() -> Dictionary:
return {
"cooldown_left": training_cooldown_left
}
func deserialize(data: Dictionary) -> void:
training_cooldown_left = float(data.get("cooldown_left", 0.0))