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:
shen
2026-09-19 08:51:25 -07:00
parent 1db9e9a129
commit 66d217b313
650 changed files with 53046 additions and 1586 deletions
+210
View File
@@ -0,0 +1,210 @@
# epic_refine_system.gd —— Metin2 40250 修元平史诗级神装升阶转换锻造 1:1
# 对照 40250 服务端 refine.cpp, char_item.cpp, NPC 20091 (修元平 Seon-Pyeong)
class_name EpicRefineSystem
extends RefCounted
signal epic_refine_succeeded(old_vnum: int, new_vnum: int, item_data: Dictionary)
signal epic_refine_failed(reason: String)
const NPC_SEON_PYEONG := 20091
const ITEM_DRAGON_SCALE := 71123 # 龙鳞
const ITEM_DRAGON_CLAW := 71129 # 龙爪
const REQUIRED_SCALE_COUNT := 3
const REQUIRED_CLAW_COUNT := 4
const REQUIRED_YANG := 200000
# 官方 40250 经典史诗升阶配方表
const EPIC_RECIPES: Dictionary = {
# 武器篇:65 级 +9 武器 -> 80~90 级 +0 史诗神兵
169: {
"target_vnum": 279,
"name": "崔顿之剑+0",
"desc": "战神之剑+9 升阶为 崔顿之剑+0"
},
3159: {
"target_vnum": 3209,
"name": "神圣之剑+0",
"desc": "鬼牙刃+9 升阶为 神圣之剑+0"
},
1109: {
"target_vnum": 1139,
"name": "太阳之刃+0",
"desc": "党人斧+9 升阶为 太阳之刃+0"
},
1119: {
"target_vnum": 1129,
"name": "龙神刀+0",
"desc": "龙刀+9 升阶为 龙神刀+0"
},
2149: {
"target_vnum": 2159,
"name": "巨鸦弓+0",
"desc": "黄龙弓+9 升阶为 巨鸦弓+0"
},
5109: {
"target_vnum": 5119,
"name": "恶魔之杖+0",
"desc": "救赎之杖+9 升阶为 恶魔之杖+0"
},
# 铠甲篇:66 级 +9 黑钢铠甲 -> 90 级 +0 蓝钢铠甲
11299: {
"target_vnum": 12019,
"name": "蓝钢甲+0",
"desc": "黑钢甲+9 升阶为 蓝钢甲+0"
},
11499: {
"target_vnum": 12029,
"name": "蓝风甲+0",
"desc": "黑风甲+9 升阶为 蓝风甲+0"
},
11699: {
"target_vnum": 12039,
"name": "蓝仙袍+0",
"desc": "黑仙袍+9 升阶为 蓝仙袍+0"
},
11899: {
"target_vnum": 12049,
"name": "蓝龙袍+0",
"desc": "黑龙袍+9 升阶为 蓝龙袍+0"
}
}
# 判定物品是否可进行史诗升阶
func can_epic_refine(item: Dictionary) -> bool:
if item.is_empty():
return false
var vnum: int = int(item.get("vnum", 0))
return EPIC_RECIPES.has(vnum)
# 获取升阶配方详情
func get_recipe(vnum: int) -> Dictionary:
return EPIC_RECIPES.get(vnum, {})
# 执行史诗装备升阶转换锻造
func do_epic_refine(
target_slot: int,
inventory: Array,
player_data: Dictionary
) -> Dictionary:
if target_slot < 0 or target_slot >= inventory.size():
return {"ok": false, "reason": "INVALID_SLOT", "msg": "无效的装备栏位!"}
var base_item = inventory[target_slot]
if base_item == null:
return {"ok": false, "reason": "EMPTY_SLOT", "msg": "该格子上没有装备!"}
# 安全锁拦截
if base_item.get("is_locked", false):
return {"ok": false, "reason": "ITEM_LOCKED", "msg": "已加锁的装备无法进行史诗锻造!"}
var base_vnum: int = int(base_item.get("vnum", 0))
if not EPIC_RECIPES.has(base_vnum):
return {"ok": false, "reason": "NOT_AN_EPIC_BASE_ITEM", "msg": "该物品无法进行修元平史诗升阶!"}
var recipe: Dictionary = EPIC_RECIPES[base_vnum]
# 1. 检查金币 (Yang)
var current_yang: int = int(player_data.get("gold", 0))
if current_yang < REQUIRED_YANG:
return {
"ok": false,
"reason": "INSUFFICIENT_YANG",
"required_yang": REQUIRED_YANG,
"current_yang": current_yang,
"msg": "史诗锻造需要 200,000 金币!你的金币不足。"
}
# 2. 统计并收集龙鳞与龙爪
var scale_slots: Array = []
var claw_slots: Array = []
var total_scales: int = 0
var total_claws: int = 0
for i in range(inventory.size()):
var item = inventory[i]
if item == null:
continue
var v: int = int(item.get("vnum", 0))
var cnt: int = int(item.get("count", 1))
if v == ITEM_DRAGON_SCALE:
total_scales += cnt
scale_slots.append({"slot": i, "count": cnt})
elif v == ITEM_DRAGON_CLAW:
total_claws += cnt
claw_slots.append({"slot": i, "count": cnt})
if total_scales < REQUIRED_SCALE_COUNT:
return {
"ok": false,
"reason": "MISSING_DRAGON_SCALES",
"required": REQUIRED_SCALE_COUNT,
"current": total_scales,
"msg": "史诗锻造需要 3 个龙鳞!当前只有 %d 个。" % total_scales
}
if total_claws < REQUIRED_CLAW_COUNT:
return {
"ok": false,
"reason": "MISSING_DRAGON_CLAWS",
"required": REQUIRED_CLAW_COUNT,
"current": total_claws,
"msg": "史诗锻造需要 4 个龙爪!当前只有 %d 个。" % total_claws
}
# 3. 扣除金币
player_data["gold"] = current_yang - REQUIRED_YANG
# 4. 扣除材料 (龙鳞 3, 龙爪 4)
_consume_materials(inventory, scale_slots, REQUIRED_SCALE_COUNT)
_consume_materials(inventory, claw_slots, REQUIRED_CLAW_COUNT)
# 5. 生成新史诗装备,1:1 完整继承 5 条附加属性与已镶嵌灵石!
var new_vnum: int = recipe["target_vnum"]
var inherited_attrs: Array = []
if base_item.has("attributes"):
for attr in base_item["attributes"]:
inherited_attrs.append(attr.duplicate())
var inherited_sockets: Array = []
if base_item.has("sockets"):
for sock in base_item["sockets"]:
inherited_sockets.append(sock)
var epic_item: Dictionary = {
"vnum": new_vnum,
"name": recipe["name"],
"count": 1,
"attributes": inherited_attrs,
"sockets": inherited_sockets,
"socket_count": base_item.get("socket_count", 3),
"is_locked": false
}
# 替换背包中原位置的装备
inventory[target_slot] = epic_item
epic_refine_succeeded.emit(base_vnum, new_vnum, epic_item)
return {
"ok": true,
"old_vnum": base_vnum,
"new_vnum": new_vnum,
"item": epic_item,
"msg": "恭喜!修元平大师成功将装备升阶锻造为【%s】!" % recipe["name"]
}
# 辅助函数:按需从多个格子中扣除材料数量
func _consume_materials(inventory: Array, slots_info: Array, count_needed: int) -> void:
var needed := count_needed
for info in slots_info:
var s: int = info["slot"]
var have: int = info["count"]
if have <= needed:
needed -= have
inventory[s] = null
else:
inventory[s]["count"] = have - needed
needed = 0
if needed <= 0:
break