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 客户端对拍缺陷发现与修复工程指南
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
# dragon_soul_system.gd —— Metin2 40250 龙神炼金石系统 1:1
|
||||
# 对照 40250 服务端 DragonSoul.cpp, char_dragonsoul.cpp 及客户端 uidragonsoul.py
|
||||
class_name DragonSoulSystem
|
||||
extends RefCounted
|
||||
|
||||
signal deck_activated(deck_idx: int)
|
||||
signal deck_deactivated(deck_idx: int)
|
||||
|
||||
# 6 种官方龙神石类型 (DragonSoul.cpp:EDSStoneTypes)
|
||||
enum DragonStoneType {
|
||||
NONE = 0,
|
||||
DIAMOND = 1, # 钻石: INT, 魔法抗性
|
||||
RUBY = 2, # 红宝石: STR, 物理攻击力, 防御力
|
||||
JADE = 3, # 玉石: Max HP, HP 恢复
|
||||
SAPPHIRE = 4,# 蓝宝石: DEX, 职业减伤
|
||||
GARNET = 5, # 石榴石: CON, Max SP
|
||||
ONYX = 6 # 缟玛瑙: 格挡, 反弹, 穿透抵抗
|
||||
}
|
||||
|
||||
# 5 档品阶 (Grade)
|
||||
enum Grade {
|
||||
ROUGH = 0, # 粗糙
|
||||
CUT = 1, # 切割
|
||||
RARE = 2, # 稀有
|
||||
ANCIENT = 3, # 古代
|
||||
LEGENDARY = 4 # 传奇
|
||||
}
|
||||
|
||||
# 5 档净度 (Clarity)
|
||||
enum Clarity {
|
||||
MATT = 0, # 哑光
|
||||
CLEAR = 1, # 清晰
|
||||
FLAWLESS = 2, # 无瑕
|
||||
BRILLIANT = 3, # 辉煌
|
||||
EXCELLENT = 4 # 卓越
|
||||
}
|
||||
|
||||
const DECK_SKY := 0 # 天空卡槽
|
||||
const DECK_EARTH := 1 # 大地卡槽
|
||||
const DECK_COUNT := 2
|
||||
const SLOTS_PER_DECK := 6 # 每卡槽 6 个神石槽位
|
||||
|
||||
# 双卡槽数据结构: decks[deck_idx][slot_idx] = stone_dict or null
|
||||
var decks: Array[Array] = [[], []]
|
||||
var active_deck: int = -1 # -1: 未激活, 0: 天空卡槽, 1: 大地卡槽
|
||||
|
||||
func _init() -> void:
|
||||
for d in range(DECK_COUNT):
|
||||
var slot_list: Array = []
|
||||
for s in range(SLOTS_PER_DECK):
|
||||
slot_list.append(null)
|
||||
decks[d] = slot_list
|
||||
|
||||
# 创建一颗龙神石
|
||||
static func create_dragon_stone(type: int, grade: int = 0, clarity: int = 0, refine: int = 0, duration: float = 86400.0) -> Dictionary:
|
||||
var stone: Dictionary = {
|
||||
"stone_type": type,
|
||||
"grade": clampi(grade, 0, 4),
|
||||
"clarity": clampi(clarity, 0, 4),
|
||||
"refine": clampi(refine, 0, 6),
|
||||
"duration": duration, # 激活消耗时间 (秒)
|
||||
"total_duration": duration
|
||||
}
|
||||
stone["attributes"] = calculate_stone_attributes(stone)
|
||||
return stone
|
||||
|
||||
# 计算单颗神石属性 (对齐 40250 DragonSoulTable.txt)
|
||||
static func calculate_stone_attributes(stone: Dictionary) -> Dictionary:
|
||||
var type: int = int(stone.get("stone_type", 0))
|
||||
var gr: int = int(stone.get("grade", 0))
|
||||
var cl: int = int(stone.get("clarity", 0))
|
||||
var rf: int = int(stone.get("refine", 0))
|
||||
|
||||
var attrs: Dictionary = {}
|
||||
|
||||
match type:
|
||||
DragonStoneType.DIAMOND:
|
||||
attrs["int_bonus"] = 5 + (gr * 3) + (cl * 2) + rf
|
||||
attrs["magic_defense"] = 2 + (gr * 2) + (cl * 2) + (rf * 2)
|
||||
DragonStoneType.RUBY:
|
||||
attrs["str_bonus"] = 5 + (gr * 3) + (cl * 2) + rf
|
||||
attrs["att_grade"] = 20 + (gr * 15) + (cl * 10) + (rf * 5)
|
||||
attrs["def_grade"] = 15 + (gr * 10) + (cl * 5) + (rf * 4)
|
||||
DragonStoneType.JADE:
|
||||
attrs["max_hp"] = 300 + (gr * 250) + (cl * 150) + (rf * 100)
|
||||
attrs["hp_recovery_pct"] = 3 + gr + cl + rf
|
||||
DragonStoneType.SAPPHIRE:
|
||||
attrs["dex_bonus"] = 5 + (gr * 3) + (cl * 2) + rf
|
||||
attrs["resist_warrior"] = 2 + gr + cl + rf
|
||||
attrs["resist_assassin"] = 2 + gr + cl + rf
|
||||
DragonStoneType.GARNET:
|
||||
attrs["con_bonus"] = 5 + (gr * 3) + (cl * 2) + rf
|
||||
attrs["max_sp"] = 200 + (gr * 150) + (cl * 100) + (rf * 50)
|
||||
DragonStoneType.ONYX:
|
||||
attrs["block_pct"] = 2 + (gr * 2) + cl + rf
|
||||
attrs["reflect_pct"] = 2 + (gr * 2) + cl + rf
|
||||
|
||||
return attrs
|
||||
|
||||
# 装备神石到卡槽
|
||||
func equip_stone(deck_idx: int, stone: Dictionary) -> Dictionary:
|
||||
if deck_idx < 0 or deck_idx >= DECK_COUNT:
|
||||
return {"ok": false, "reason": "INVALID_DECK"}
|
||||
|
||||
var type: int = int(stone.get("stone_type", 0))
|
||||
if type < 1 or type > 6:
|
||||
return {"ok": false, "reason": "INVALID_STONE_TYPE"}
|
||||
|
||||
var slot_idx: int = type - 1 # 1~6 映射到 0~5 号槽位
|
||||
decks[deck_idx][slot_idx] = stone
|
||||
|
||||
return {
|
||||
"ok": true,
|
||||
"deck": deck_idx,
|
||||
"slot": slot_idx,
|
||||
"stone": stone
|
||||
}
|
||||
|
||||
# 卸下神石
|
||||
func unequip_stone(deck_idx: int, slot_idx: int) -> Dictionary:
|
||||
if deck_idx < 0 or deck_idx >= DECK_COUNT:
|
||||
return {"ok": false, "reason": "INVALID_DECK"}
|
||||
if slot_idx < 0 or slot_idx >= SLOTS_PER_DECK:
|
||||
return {"ok": false, "reason": "INVALID_SLOT"}
|
||||
|
||||
var removed = decks[deck_idx][slot_idx]
|
||||
decks[deck_idx][slot_idx] = null
|
||||
return {"ok": true, "stone": removed}
|
||||
|
||||
# 激活/停用卡槽
|
||||
func toggle_deck(deck_idx: int) -> bool:
|
||||
if active_deck == deck_idx:
|
||||
active_deck = -1
|
||||
deck_deactivated.emit(deck_idx)
|
||||
return false
|
||||
else:
|
||||
if active_deck != -1:
|
||||
deck_deactivated.emit(active_deck)
|
||||
active_deck = deck_idx
|
||||
deck_activated.emit(deck_idx)
|
||||
return true
|
||||
|
||||
# 计算当前已激活卡槽的总战力属性
|
||||
func get_active_bonuses() -> Dictionary:
|
||||
var total_bonuses: Dictionary = {}
|
||||
if active_deck < 0 or active_deck >= DECK_COUNT:
|
||||
return total_bonuses
|
||||
|
||||
var deck: Array = decks[active_deck]
|
||||
for slot in deck:
|
||||
if slot != null and float(slot.get("duration", 0.0)) > 0.0:
|
||||
var attrs: Dictionary = slot.get("attributes", {})
|
||||
for key in attrs:
|
||||
total_bonuses[key] = int(total_bonuses.get(key, 0)) + int(attrs[key])
|
||||
|
||||
return total_bonuses
|
||||
|
||||
# 逐帧消耗激活中的龙神石能量时间
|
||||
func update(dt: float) -> Array:
|
||||
var expired_stones: Array = []
|
||||
if active_deck < 0 or active_deck >= DECK_COUNT:
|
||||
return expired_stones
|
||||
|
||||
var deck: Array = decks[active_deck]
|
||||
for i in range(deck.size()):
|
||||
var stone = deck[i]
|
||||
if stone != null:
|
||||
var dur: float = float(stone.get("duration", 0.0))
|
||||
dur -= dt
|
||||
if dur <= 0.0:
|
||||
dur = 0.0
|
||||
expired_stones.append({"deck": active_deck, "slot": i, "stone": stone})
|
||||
stone["duration"] = dur
|
||||
|
||||
return expired_stones
|
||||
|
||||
# 升阶 (Grade Refine): 2 颗同种类、同品阶神石融合升级
|
||||
static func refine_grade(stone_a: Dictionary, stone_b: Dictionary, force_success: bool = false) -> Dictionary:
|
||||
var ta: int = int(stone_a.get("stone_type", 0))
|
||||
var tb: int = int(stone_b.get("stone_type", 0))
|
||||
var ga: int = int(stone_a.get("grade", 0))
|
||||
var gb: int = int(stone_b.get("grade", 0))
|
||||
|
||||
if ta != tb or ga != gb:
|
||||
return {"ok": false, "reason": "MISMATCHED_STONES", "msg": "只有同种类且同品阶的神石才能融合升阶!"}
|
||||
if ga >= Grade.LEGENDARY:
|
||||
return {"ok": false, "reason": "MAX_GRADE", "msg": "该神石已达到最高传奇品阶!"}
|
||||
|
||||
# 成功率 60%
|
||||
var success: bool = force_success or randf() < 0.60
|
||||
if success:
|
||||
var new_stone = create_dragon_stone(ta, ga + 1, 0, 0)
|
||||
return {"ok": true, "success": true, "stone": new_stone, "msg": "神石品阶提升成功!"}
|
||||
else:
|
||||
return {"ok": true, "success": false, "msg": "升阶失败,融合材料已碎裂。"}
|
||||
|
||||
# 升净度 (Clarity Refine): 2 颗同种类、同品阶、同净度神石融合升级
|
||||
static func refine_clarity(stone_a: Dictionary, stone_b: Dictionary, force_success: bool = false) -> Dictionary:
|
||||
var ta: int = int(stone_a.get("stone_type", 0))
|
||||
var tb: int = int(stone_b.get("stone_type", 0))
|
||||
var ca: int = int(stone_a.get("clarity", 0))
|
||||
var cb: int = int(stone_b.get("clarity", 0))
|
||||
|
||||
if ta != tb or ca != cb:
|
||||
return {"ok": false, "reason": "MISMATCHED_STONES", "msg": "只有同种类且同净度的神石才能融合提升净度!"}
|
||||
if ca >= Clarity.EXCELLENT:
|
||||
return {"ok": false, "reason": "MAX_CLARITY", "msg": "该神石已达到最高卓越净度!"}
|
||||
|
||||
var success: bool = force_success or randf() < 0.50
|
||||
if success:
|
||||
var ga: int = int(stone_a.get("grade", 0))
|
||||
var new_stone = create_dragon_stone(ta, ga, ca + 1, 0)
|
||||
return {"ok": true, "success": true, "stone": new_stone, "msg": "神石净度提升成功!"}
|
||||
else:
|
||||
return {"ok": true, "success": false, "msg": "净度提升失败,辅助材料碎裂。"}
|
||||
Reference in New Issue
Block a user