- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
277 lines
9.7 KiB
GDScript
277 lines
9.7 KiB
GDScript
# item_enchant_system.gd —— Metin2 40250 装备词条重铸与精炼附魔洗炼系统 1:1
|
|
# 对照 40250 服务端 char_item.cpp, item_attribute.cpp, constants.cpp
|
|
class_name ItemEnchantSystem
|
|
extends RefCounted
|
|
|
|
const ItemAttrSystem = preload("res://item_attr_system.gd")
|
|
|
|
signal enchant_applied(target_slot: int, scroll_vnum: int, success: bool, new_attrs: Array)
|
|
signal rare_enchant_applied(target_slot: int, success: bool, rare_attrs: Array)
|
|
signal enchant_failed(target_slot: int, reason: String)
|
|
|
|
const VNUM_ATTR_ADD := 71085 # 添加属性秘籍 (添加第 1~4 条)
|
|
const VNUM_ATTR_CHANGE := 71084 # 属性改变秘籍 (洗炼现有 1~5 条)
|
|
const VNUM_BLESSING_MARBLE := 70024 # 祝福宝珠 (添加第 5 条属性)
|
|
const VNUM_RARE_ADD := 71151 # 稀有属性秘籍 (添加第 6~7 条稀有词条)
|
|
const VNUM_RARE_CHANGE := 71152 # 稀有属性改变 (单独洗炼第 6~7 条稀有词条)
|
|
|
|
const ADD_PERCENT_TABLE := [100, 80, 60, 50, 30] # 官方各阶段成功率
|
|
const RARE_ADD_PERCENT := 50 # 稀有词条附加成功率
|
|
|
|
# 稀有词条池 (6~7 槽位特有词条)
|
|
const RARE_ATTR_POOL := {
|
|
101: {"name": "全技能伤害加成", "values": [1, 2, 3, 5, 8], "weight": 8},
|
|
102: {"name": "首领怪物伤害加成", "values": [2, 4, 6, 8, 12], "weight": 8},
|
|
103: {"name": "魔石怪物伤害加成", "values": [3, 5, 8, 10, 15], "weight": 10},
|
|
104: {"name": "受首领伤害减免", "values": [2, 4, 6, 8, 10], "weight": 8},
|
|
105: {"name": "物理防御无视概率", "values": [1, 2, 3, 5, 7], "weight": 6}
|
|
}
|
|
|
|
# 检查是否为有效附魔卷轴
|
|
static func is_enchant_scroll(vnum: int) -> bool:
|
|
return vnum in [VNUM_ATTR_ADD, VNUM_ATTR_CHANGE, VNUM_BLESSING_MARBLE, VNUM_RARE_ADD, VNUM_RARE_CHANGE]
|
|
|
|
# 检查物品是否为可附魔装备
|
|
static func is_enchantable_item(item: Dictionary) -> bool:
|
|
if item.is_empty():
|
|
return false
|
|
# 检查是否上锁保护
|
|
if item.get("is_locked", false):
|
|
return false
|
|
var vnum: int = int(item.get("vnum", 0))
|
|
# 箭矢、消耗品不可附魔 (箭矢 8000~8009)
|
|
if vnum >= 8000 and vnum <= 8009:
|
|
return false
|
|
var itype: int = int(item.get("type", 1)) # 默认为 1 (Weapon) 或 2 (Armor)
|
|
return itype in [1, 2]
|
|
|
|
# 对背包内的装备使用附魔卷轴
|
|
func apply_scroll(scroll_slot: int, target_slot: int, inventory: Array, force_success: bool = false) -> Dictionary:
|
|
if scroll_slot < 0 or scroll_slot >= inventory.size() or inventory[scroll_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_SCROLL_SLOT", "msg": "附魔卷轴格子无效!"}
|
|
|
|
if target_slot < 0 or target_slot >= inventory.size() or inventory[target_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_TARGET_SLOT", "msg": "目标装备格子无效!"}
|
|
|
|
var scroll: Dictionary = inventory[scroll_slot]
|
|
var target: Dictionary = inventory[target_slot]
|
|
var scroll_vnum: int = int(scroll.get("vnum", 0))
|
|
|
|
if not is_enchant_scroll(scroll_vnum):
|
|
return {"ok": false, "reason": "NOT_AN_ENCHANT_SCROLL", "msg": "此物品不是附魔洗炼卷轴!"}
|
|
|
|
if not is_enchantable_item(target):
|
|
enchant_failed.emit(target_slot, "ITEM_CANNOT_BE_ENCHANTED")
|
|
return {"ok": false, "reason": "ITEM_CANNOT_BE_ENCHANTED", "msg": "此装备无法进行词条附魔或已被安全上锁!"}
|
|
|
|
if not target.has("attrs"):
|
|
target["attrs"] = []
|
|
if not target.has("rare_attrs"):
|
|
target["rare_attrs"] = []
|
|
|
|
var res: Dictionary = {}
|
|
match scroll_vnum:
|
|
VNUM_ATTR_ADD:
|
|
res = _handle_add_attribute(target, force_success)
|
|
VNUM_ATTR_CHANGE:
|
|
res = _handle_change_attribute(target)
|
|
VNUM_BLESSING_MARBLE:
|
|
res = _handle_blessing_marble(target, force_success)
|
|
VNUM_RARE_ADD:
|
|
res = _handle_rare_add(target, force_success)
|
|
VNUM_RARE_CHANGE:
|
|
res = _handle_rare_change(target)
|
|
|
|
if not res.get("ok", false):
|
|
enchant_failed.emit(target_slot, res.get("reason", "FAILED"))
|
|
return res
|
|
|
|
# 扣除 1 个卷轴
|
|
_consume_scroll(scroll_slot, inventory)
|
|
|
|
if res.get("success", false):
|
|
if scroll_vnum in [VNUM_RARE_ADD, VNUM_RARE_CHANGE]:
|
|
rare_enchant_applied.emit(target_slot, true, target["rare_attrs"])
|
|
else:
|
|
enchant_applied.emit(target_slot, scroll_vnum, true, target["attrs"])
|
|
else:
|
|
enchant_applied.emit(target_slot, scroll_vnum, false, target["attrs"])
|
|
|
|
return res
|
|
|
|
# 扣除背包中的卷轴
|
|
func _consume_scroll(slot: int, inventory: Array) -> void:
|
|
var item: Dictionary = inventory[slot]
|
|
var count: int = int(item.get("count", 1))
|
|
if count > 1:
|
|
item["count"] = count - 1
|
|
else:
|
|
inventory[slot] = null
|
|
|
|
# 71085: 添加第 1~4 条属性
|
|
func _handle_add_attribute(target: Dictionary, force_success: bool) -> Dictionary:
|
|
var attrs: Array = target["attrs"]
|
|
if attrs.size() >= 4:
|
|
return {"ok": false, "reason": "MAX_BASE_ATTRS_REACHED", "msg": "该装备已有 4 条属性!请使用祝福宝珠开辟第 5 条属性。"}
|
|
|
|
var rate: int = ADD_PERCENT_TABLE[attrs.size()]
|
|
var roll: int = randi_range(1, 100)
|
|
var is_succ: bool = force_success or (roll <= rate)
|
|
|
|
if not is_succ:
|
|
return {"ok": true, "success": false, "msg": "附魔失败,卷轴化为灰烬,未能追加新属性!"}
|
|
|
|
var new_attr = _roll_random_attribute(attrs, target.get("type", 1))
|
|
attrs.append(new_attr)
|
|
|
|
return {
|
|
"ok": true,
|
|
"success": true,
|
|
"new_attr": new_attr,
|
|
"total_attrs": attrs.size(),
|
|
"msg": "附魔成功!装备获得了新词条【%s +%d】!" % [new_attr["name"], new_attr["value"]]
|
|
}
|
|
|
|
# 71084: 改变所有现有 1~5 条属性
|
|
func _handle_change_attribute(target: Dictionary) -> Dictionary:
|
|
var attrs: Array = target["attrs"]
|
|
if attrs.is_empty():
|
|
return {"ok": false, "reason": "NO_ATTRS_TO_CHANGE", "msg": "该装备没有任何属性词条,无法进行洗炼!"}
|
|
|
|
var count: int = attrs.size()
|
|
attrs.clear()
|
|
|
|
for i in range(count):
|
|
var attr = _roll_random_attribute(attrs, target.get("type", 1))
|
|
attrs.append(attr)
|
|
|
|
return {
|
|
"ok": true,
|
|
"success": true,
|
|
"attrs": attrs,
|
|
"msg": "洗炼成功!%d 条词条已全新重铸!" % count
|
|
}
|
|
|
|
# 70024: 祝福宝珠 (添加第 5 条属性)
|
|
func _handle_blessing_marble(target: Dictionary, force_success: bool) -> Dictionary:
|
|
var attrs: Array = target["attrs"]
|
|
if attrs.size() < 4:
|
|
return {"ok": false, "reason": "NEED_4_ATTRS_FIRST", "msg": "装备必须先拥有 4 条属性,才能使用祝福宝珠!"}
|
|
if attrs.size() >= 5:
|
|
return {"ok": false, "reason": "ALREADY_HAS_5_ATTRS", "msg": "该装备已拥有 5 条属性,达到上限!"}
|
|
|
|
var rate: int = ADD_PERCENT_TABLE[4] # 30%
|
|
var roll: int = randi_range(1, 100)
|
|
var is_succ: bool = force_success or (roll <= rate)
|
|
|
|
if not is_succ:
|
|
return {"ok": true, "success": false, "msg": "祝福宝珠破碎,未能为装备附加第 5 条属性!"}
|
|
|
|
var new_attr = _roll_random_attribute(attrs, target.get("type", 1))
|
|
attrs.append(new_attr)
|
|
|
|
return {
|
|
"ok": true,
|
|
"success": true,
|
|
"new_attr": new_attr,
|
|
"total_attrs": 5,
|
|
"msg": "神圣光芒降临!成功为装备铭刻第 5 条核心属性【%s +%d】!" % [new_attr["name"], new_attr["value"]]
|
|
}
|
|
|
|
# 71151: 稀有属性附加 (第 6~7 条)
|
|
func _handle_rare_add(target: Dictionary, force_success: bool) -> Dictionary:
|
|
var rares: Array = target["rare_attrs"]
|
|
if rares.size() >= 2:
|
|
return {"ok": false, "reason": "MAX_RARE_ATTRS", "msg": "已达到 2 条稀有词条上限!"}
|
|
|
|
var roll: int = randi_range(1, 100)
|
|
var is_succ: bool = force_success or (roll <= RARE_ADD_PERCENT)
|
|
|
|
if not is_succ:
|
|
return {"ok": true, "success": false, "msg": "稀有附魔失败,未能附加稀有词条!"}
|
|
|
|
var new_rare = _roll_random_rare_attribute(rares)
|
|
rares.append(new_rare)
|
|
|
|
return {
|
|
"ok": true,
|
|
"success": true,
|
|
"rare_attr": new_rare,
|
|
"total_rares": rares.size(),
|
|
"msg": "稀有附魔成功!觉醒了金色稀有词条【%s +%d%%】!" % [new_rare["name"], new_rare["value"]]
|
|
}
|
|
|
|
# 71152: 稀有属性改变 (单独洗炼第 6~7 条)
|
|
func _handle_rare_change(target: Dictionary) -> Dictionary:
|
|
var rares: Array = target["rare_attrs"]
|
|
if rares.is_empty():
|
|
return {"ok": false, "reason": "NO_RARES_TO_CHANGE", "msg": "该装备尚无任何稀有词条!"}
|
|
|
|
var count: int = rares.size()
|
|
rares.clear()
|
|
|
|
for i in range(count):
|
|
var r = _roll_random_rare_attribute(rares)
|
|
rares.append(r)
|
|
|
|
return {
|
|
"ok": true,
|
|
"success": true,
|
|
"rare_attrs": rares,
|
|
"msg": "稀有词条洗炼成功!%d 条稀有属性已重新激活!" % count
|
|
}
|
|
|
|
# 生成不重复的普通属性
|
|
func _roll_random_attribute(existing_attrs: Array, item_type: int) -> Dictionary:
|
|
var existing_ids: Array = []
|
|
for a in existing_attrs:
|
|
existing_ids.append(a.get("type", 0))
|
|
|
|
# 选用武器或防具属性池
|
|
var pool = ItemAttrSystem.WEAPON_ATTR_POOL if item_type == 1 else ItemAttrSystem.ARMOR_ATTR_POOL
|
|
var candidates: Array = []
|
|
for type_id in pool.keys():
|
|
if not (type_id in existing_ids):
|
|
candidates.append(type_id)
|
|
|
|
if candidates.is_empty():
|
|
candidates = pool.keys()
|
|
|
|
var picked_id: int = candidates[randi() % candidates.size()]
|
|
var entry = pool[picked_id]
|
|
var tier_idx: int = randi() % entry["values"].size()
|
|
var val: int = entry["values"][tier_idx]
|
|
|
|
return {
|
|
"type": picked_id,
|
|
"name": entry["name"],
|
|
"value": val,
|
|
"tier": tier_idx + 1
|
|
}
|
|
|
|
# 生成不重复的稀有属性
|
|
func _roll_random_rare_attribute(existing_rares: Array) -> Dictionary:
|
|
var existing_ids: Array = []
|
|
for a in existing_rares:
|
|
existing_ids.append(a.get("type", 0))
|
|
|
|
var candidates: Array = []
|
|
for type_id in RARE_ATTR_POOL.keys():
|
|
if not (type_id in existing_ids):
|
|
candidates.append(type_id)
|
|
|
|
if candidates.is_empty():
|
|
candidates = RARE_ATTR_POOL.keys()
|
|
|
|
var picked_id: int = candidates[randi() % candidates.size()]
|
|
var entry = RARE_ATTR_POOL[picked_id]
|
|
var tier_idx: int = randi() % entry["values"].size()
|
|
var val: int = entry["values"][tier_idx]
|
|
|
|
return {
|
|
"type": picked_id,
|
|
"name": entry["name"],
|
|
"value": val,
|
|
"tier": tier_idx + 1
|
|
}
|