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

204 lines
6.6 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_system.gd —— 40250 技能书研读与技能突破进阶系统 1:1 规则引擎
# 严格对照:
# metin2/src/server/game/src/char_item.cpp:2128-2160 (ITEM_SKILLBOOK / 50300)
# metin2/src/server/game/src/char_skill.cpp:416-570 (CHARACTER::LearnSkillByBook)
# metin2/src/server/game/src/char_item.cpp:1657 (Soul Stone 50513 / G->P)
# ClientVS22/UserInterface/Packet.h
extends RefCounted
const SKILL_BOOK_VNUM := 50300
const EXORCISM_SCROLL_VNUM := 71001 # 驱魔卷轴 (AFFECT_SKILL_NO_BOOK_DELAY)
const HERMIT_ADVICE_VNUM := 71094 # 隐士教训 (AFFECT_SKILL_BOOK_BONUS)
const SOUL_STONE_VNUM := 71001 # 魂石 (50513)
const SKILL_BOOK_EXP_COST := 20000 # 40250 char_skill.cpp:430 need_exp = 20000
const SKILL_BOOK_DELAY := 86400 # 24 小时冷却 (LC_IsKorea 86400s)
# 40250 MasterType 枚举
const MASTER_NORMAL := 0 # Lv 1 - 20
const MASTER_M := 1 # M1 - M10 (Lv 21 - 30)
const MASTER_G := 2 # G1 - G10 (Lv 31 - 40)
const MASTER_P := 3 # Perfect Master (Lv 40)
# 错误码
enum Result {
SUCCESS_LEVEL_UP, # 成功并升级(或突破到 G
SUCCESS_READ_ONCE, # 成功研读一本(累积进度)
FAIL_READ, # 研读失败
ERR_NOT_LEARNABLE, # 非本职业/未入流派技能
ERR_NOT_MASTER_TIER, # 尚未达到 M 阶 (Lv < 20)
ERR_ALREADY_G_OR_P, # 已达到 G 阶或更高,无法再读技能书
ERR_INSUFFICIENT_EXP, # 经验不足 20,000
ERR_COOLDOWN, # 24小时冷却中
}
# 魂石善恶值消耗表 (char_skill.cpp / training_grandmaster_skill.quest)
const SOUL_STONE_KARMA_COST := {
1: 1000, 2: 1500, 3: 2000, 4: 2500, 5: 3000,
6: 3500, 7: 4000, 8: 4500, 9: 5000, 10: 5500
}
# 判定技能书升级所需本数:40250 char_skill.cpp:509 need_bookcount = GetSkillLevel(dwSkillVnum) - 20
static func get_need_books(skill_level: int) -> int:
# M1 (21) 需 1 本,M2 (22) 需 2 本 ... M10 (30) 需 10 本
return maxi(1, skill_level - 20)
# 研读条件前置校验
static func check_can_read(
skill_id: int,
skill_level: int,
master_type: int,
player_exp: int,
next_read_time: int,
current_time: int,
has_exorcism: bool
) -> int:
if skill_id <= 0:
return Result.ERR_NOT_LEARNABLE
if master_type < MASTER_M or skill_level < 20:
return Result.ERR_NOT_MASTER_TIER
if master_type > MASTER_M or skill_level >= 30:
return Result.ERR_ALREADY_G_OR_P
if player_exp < SKILL_BOOK_EXP_COST:
return Result.ERR_INSUFFICIENT_EXP
if current_time < next_read_time and not has_exorcism:
return Result.ERR_COOLDOWN
return 0 # OK
# 研读技能书核心判定逻辑(1:1 char_skill.cpp:488-570
static func read_skill_book(
skill_id: int,
skill_level: int,
master_type: int,
read_count: int,
player_exp: int,
next_read_time: int,
current_time: int,
has_exorcism: bool,
has_hermit: bool,
roll_val := -1 # 测试可注入 1..100
) -> Dictionary:
var err := check_can_read(skill_id, skill_level, master_type, player_exp, next_read_time, current_time, has_exorcism)
if err != 0:
return {
"result": err,
"skill_id": skill_id,
"level": skill_level,
"master_type": master_type,
"read_count": read_count,
"exp_consumed": 0,
"next_read_time": next_read_time,
"consumed_exorcism": false,
"consumed_hermit": false,
}
var consumed_exorcism := false
if current_time < next_read_time and has_exorcism:
consumed_exorcism = true
var consumed_hermit := false
var fail_percent := 65 # 40250 char_skill.cpp:525 int percent = 65;
if has_hermit:
fail_percent = 0 # 100% 成功
consumed_hermit = true
var dice := roll_val if roll_val > 0 else (randi() % 100 + 1)
var success := (dice > fail_percent)
var new_level := skill_level
var new_master := master_type
var new_read_count := read_count
var new_next_time := current_time + SKILL_BOOK_DELAY
var res := Result.FAIL_READ
if success:
var need_books := get_need_books(skill_level)
if read_count + 1 >= need_books:
# 升级!
new_level += 1
new_read_count = 0
if new_level >= 30:
new_master = MASTER_G # 突破到 G1
res = Result.SUCCESS_LEVEL_UP
else:
new_read_count += 1
res = Result.SUCCESS_READ_ONCE
return {
"result": res,
"skill_id": skill_id,
"level": new_level,
"master_type": new_master,
"read_count": new_read_count,
"exp_consumed": SKILL_BOOK_EXP_COST,
"next_read_time": new_next_time,
"consumed_exorcism": consumed_exorcism,
"consumed_hermit": consumed_hermit,
"need_books": get_need_books(new_level),
}
# 魂石修炼逻辑(1:1 G1..G10 -> P
static func soul_stone_train(
skill_id: int,
skill_level: int,
master_type: int,
current_karma: int,
roll_val := -1
) -> Dictionary:
if master_type != MASTER_G or skill_level < 30 or skill_level >= 40:
return {"success": false, "err": "not_g_tier"}
var g_step := skill_level - 30 # 1..10
var base_cost: int = SOUL_STONE_KARMA_COST.get(g_step, 1000)
var actual_cost: int = base_cost if current_karma >= 0 else (base_cost * 2)
var dice := roll_val if roll_val > 0 else (randi() % 100 + 1)
# 40250 魂石成功率约 30%
var success := (dice <= 30)
var new_level := skill_level
var new_master := master_type
if success:
new_level += 1
if new_level >= 40:
new_master = MASTER_P
return {
"success": success,
"skill_id": skill_id,
"level": new_level,
"master_type": new_master,
"karma_consumed": actual_cost,
}
# 格式化官方提示文本
static func get_result_message(res_dict: Dictionary) -> String:
match res_dict.get("result", -1):
Result.SUCCESS_LEVEL_UP:
if res_dict.get("master_type") == MASTER_G and res_dict.get("level") == 30:
return "领悟了大宗师的奥秘!技能已成功突破至大宗师(G1)!"
return "完全理解了书中内容,技能成功提升到了新的境界!"
Result.SUCCESS_READ_ONCE:
var remaining := int(res_dict.get("need_books", 1)) - int(res_dict.get("read_count", 0))
return "成功领悟了部分技巧!还需要成功研读 %d 本书以完成突破。" % remaining
Result.FAIL_READ:
return "研读失败,未能领悟书中的奥秘。"
Result.ERR_NOT_LEARNABLE:
return "无法修炼该技能书。"
Result.ERR_NOT_MASTER_TIER:
return "这个技能还未达到大师级别(M),不能通过技能书修炼。"
Result.ERR_ALREADY_G_OR_P:
return "这个技能已达到大宗师或以上级别,无法再通过普通技能书提升。"
Result.ERR_INSUFFICIENT_EXP:
return "经验值不足(需要 20,000 EXP)。"
Result.ERR_COOLDOWN:
var remaining_s := maxi(0, int(res_dict.get("next_read_time", 0)))
return "当前处于研读冷却中,请使用驱魔卷轴消除冷却或稍后再试。"
_:
return ""