- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
216 lines
7.3 KiB
GDScript
216 lines
7.3 KiB
GDScript
# 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": "净度提升失败,辅助材料碎裂。"}
|