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

214 lines
7.4 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# skill_book_reading_system.gd —— 40250 官方大师技能书悟道研读与突破系统 1:1
# 严格对照:
# metin2/src/server/game/src/char_skill.cpp:416-560 (CHARACTER::LearnSkillByBook)
# metin2/src/server/game/src/char_item.cpp:2128-2160 (ITEM_SKILLBOOK / 50300)
# metin2/src/server/game/src/affect.h (AFFECT_SKILL_BOOK_BONUS 512, AFFECT_SKILL_NO_BOOK_DELAY 513)
# metin2/src/server/share/conf/item_proto.txt (71001 驱魔卷轴, 71094 隐士之药)
extends RefCounted
const ITEM_SKILLBOOK_VNUM := 50300
const EXORCISM_SCROLL_VNUM := 71001 # 驱魔卷轴: AFFECT_SKILL_NO_BOOK_DELAY
const HERMIT_ADVICE_VNUM := 71094 # 隐士之药: AFFECT_SKILL_BOOK_BONUS
const EXP_COST := 20000 # 40250 char_skill.cpp:430 need_exp = 20000
const COOLDOWN_DURATION := 86400 # 24小时冷却 (LC_IsKorea 86400 秒)
const BASE_SUCCESS_RATE := 35 # 基础成功率 35% (char_skill.cpp:524: percent = 65 -> 100 - 65 = 35%)
# 技能进阶档次 (MasterType)
const SKILL_NORMAL := 0 # Lv 1 - 19
const SKILL_MASTER := 1 # M1 (Lv 20) ~ M10 (Lv 29)
const SKILL_GRAND_MASTER := 2 # G1 (Lv 30) ~ G10 (Lv 39)
const SKILL_PERFECT_MASTER := 3 # P (Lv 40)
# 角色技能书研读状态
var skills: Dictionary = {} # skill_id -> {"level": int, "read_count": int, "next_read_time": int}
var affects: Dictionary = {} # affect_type -> bool
var player_exp: int = 100000
var current_time: int = 1000000
# 累计统计
var total_books_read: int = 0
var total_books_succeeded: int = 0
var total_books_failed: int = 0
func _init(initial_exp: int = 100000, initial_time: int = 1000000) -> void:
player_exp = initial_exp
current_time = initial_time
skills = {}
affects = {}
total_books_read = 0
total_books_succeeded = 0
total_books_failed = 0
# 注册/初始化技能
func init_skill(skill_id: int, level: int = 20, read_count: int = 0, next_read_time: int = 0) -> void:
skills[skill_id] = {
"level": level,
"read_count": read_count,
"next_read_time": next_read_time
}
# 获取技能档次
func get_skill_master_type(skill_id: int) -> int:
if not skills.has(skill_id):
return SKILL_NORMAL
var lvl: int = skills[skill_id]["level"]
if lvl < 20:
return SKILL_NORMAL
elif lvl < 30:
return SKILL_MASTER
elif lvl < 40:
return SKILL_GRAND_MASTER
else:
return SKILL_PERFECT_MASTER
# 计算升下一级所需的成功书本数 (char_skill.cpp:509 need_bookcount = level - 20)
# M1(Lv20) 升 M2: 需 1 本 (20 - 20 + 1 = 1)
# M2(Lv21) 升 M3: 需 2 本
# ...
# M10(Lv29) 升 G1: 需 10 本
# 累计突破至 G 需 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55 本成功!
func get_need_books_for_next_level(skill_id: int) -> int:
if not skills.has(skill_id):
return 0
var lvl: int = skills[skill_id]["level"]
if lvl < 20 or lvl >= 30:
return 0
return lvl - 20 + 1
# 使用驱魔卷轴 (71001)
func use_exorcism_scroll() -> bool:
affects[EXORCISM_SCROLL_VNUM] = true
return true
# 使用隐士之药 (71094)
func use_hermit_advice() -> bool:
affects[HERMIT_ADVICE_VNUM] = true
return true
# 检查是否存在状态
func has_affect(affect_vnum: int) -> bool:
return affects.get(affect_vnum, false)
# 研读技能书核心方法 (1:1 CHARACTER::LearnSkillByBook)
# roll_override: 传入 1~100 用于单元测试确定性判定,-1 表示根据随机数判定
func learn_skill_by_book(skill_id: int, book_socket_skill_vnum: int = -1, roll_override: int = -1) -> Dictionary:
var result := {
"success": false,
"level_up": false,
"breakthrough_g": false,
"error_code": "OK",
"read_count": 0,
"needed_books": 0,
"remaining_books": 0,
"current_level": 0,
"exp_deducted": 0,
"consumed_exorcism": false,
"consumed_hermit": false,
"message": ""
}
# 1. 校验技能是否存在
if not skills.has(skill_id):
result["error_code"] = "NOT_LEARNABLE"
result["message"] = "无法研读未掌握或非本流派的技能。"
return result
# 2. 校验技能书 Socket 绑定的技能编号 (40250 char_item.cpp:2132)
if book_socket_skill_vnum != -1 and book_socket_skill_vnum != skill_id:
result["error_code"] = "SKILL_MISMATCH"
result["message"] = "该技能书并非此技能专研典籍。"
return result
var skill_data: Dictionary = skills[skill_id]
var current_lvl: int = skill_data["level"]
result["current_level"] = current_lvl
# 3. 校验阶档
var master_type: int = get_skill_master_type(skill_id)
if master_type < SKILL_MASTER:
result["error_code"] = "NOT_MASTER_TIER"
result["message"] = "此技能尚未达到大师 (M) 阶档,无法研读书籍。"
return result
elif master_type >= SKILL_GRAND_MASTER:
result["error_code"] = "ALREADY_G_OR_P"
result["message"] = "此技能已达到宗师 (G) 阶或更高,书籍无法再提升造诣。"
return result
# 4. 经验值检查与消耗 (char_skill.cpp:430: need_exp = 20000)
if player_exp < EXP_COST:
result["error_code"] = "INSUFFICIENT_EXP"
result["message"] = "角色经验值不足 20,000,精神无法专注研读。"
return result
# 5. 冷却时间与驱魔卷轴检查 (char_skill.cpp:455)
var next_time: int = skill_data.get("next_read_time", 0)
var bypass_cooldown := false
if current_time < next_time:
if has_affect(EXORCISM_SCROLL_VNUM):
affects.erase(EXORCISM_SCROLL_VNUM)
bypass_cooldown = true
result["consumed_exorcism"] = true
else:
result["error_code"] = "COOLDOWN_ACTIVE"
result["message"] = "心智尚需沉淀,请在冷却结束后再读,或使用驱魔卷轴。"
return result
# 扣除 20,000 EXP 并产生 24 小时冷却 (86,400 秒)
player_exp -= EXP_COST
result["exp_deducted"] = EXP_COST
skill_data["next_read_time"] = current_time + COOLDOWN_DURATION
total_books_read += 1
# 6. 成功率计算与隐士之药加成
var success_chance: int = BASE_SUCCESS_RATE
if has_affect(HERMIT_ADVICE_VNUM):
affects.erase(HERMIT_ADVICE_VNUM)
success_chance = 100 # 隐士之药 100% 保底成功 (char_skill.cpp:527)
result["consumed_hermit"] = true
var roll := roll_override if roll_override > 0 else (randi() % 100 + 1)
var is_read_success: bool = (roll <= success_chance)
var need_books: int = get_need_books_for_next_level(skill_id)
result["needed_books"] = need_books
if not is_read_success:
# 研读失败 (char_skill.cpp:613 READ_FAIL)
total_books_failed += 1
result["success"] = false
result["read_count"] = skill_data["read_count"]
result["remaining_books"] = need_books - skill_data["read_count"]
result["error_code"] = "READ_FAIL"
result["message"] = "研读失败:经脉运转不顺,书中奥义未能参透。"
return result
# 研读成功
total_books_succeeded += 1
result["success"] = true
var cur_count: int = skill_data["read_count"] + 1
if cur_count >= need_books:
# 晋级提升一阶等级!(SkillLevelUp)
skill_data["level"] += 1
skill_data["read_count"] = 0
result["level_up"] = true
result["current_level"] = skill_data["level"]
result["read_count"] = 0
result["remaining_books"] = 0
if skill_data["level"] >= 30:
result["breakthrough_g"] = true
result["message"] = "顿悟成功!技能历经千锤百炼,成功突破至宗师境界 (G1)!"
else:
result["message"] = "顿悟成功!技能境界提升至 M%d" % (skill_data["level"] - 19)
else:
# 累积研读进度
skill_data["read_count"] = cur_count
result["read_count"] = cur_count
result["remaining_books"] = need_books - cur_count
result["message"] = "心领神会!还需研读 %d 本成功即可突破进阶。" % (need_books - cur_count)
return result