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

205 lines
7.1 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.
# metin_socket_system.gd —— 魔石镶嵌与打孔清除系统(40250 官方 1:1 对齐)
# 对齐源码:
# - 40250 char_item.cpp:5004-5080 (case ITEM_METIN: 镶嵌魔石、30% 成功率、碎石 28960)
# - 40250 char_item.cpp:4510-4540 (case USE_CLEAN_SOCKET: 卷轴 71006 清除 28960 碎石残渣)
# - 40250 char_item.cpp:53 (const int ITEM_BROKEN_METIN_VNUM = 28960)
# - 40250 ItemData.h (ITEM_METIN, ITEM_WEAPON, ITEM_ARMOR)
class_name MetinSocketSystem
extends RefCounted
const ITEM_BROKEN_METIN_VNUM := 28960 # 碎石残片 Vnum (40250 官方标准)
const VNUM_CLEAN_SOCKET_SCROLL := 71006 # 提石卷轴 / 清除残渣卷轴 (USE_CLEAN_SOCKET)
const ITEM_TYPE_WEAPON := 1
const ITEM_TYPE_ARMOR := 2
const ITEM_TYPE_METIN := 10
const WEARABLE_WEAPON := 1 << 0
const WEARABLE_BODY := 1 << 1
# 魔石基础分类(根据 40250 官方属性定义,同一分类不可在同一装备上重复镶嵌)
# 例如:突击石、穿透石、致命石、冷却石、怪物石、保护石、防御石、回避石、生命石等
const METIN_GROUPS := {
# 穿透石 (28030..28430)
"PENETRATE": [28030, 28130, 28230, 28330, 28430],
# 致命石 / 暴击石 (28031..28431)
"CRITICAL": [28031, 28131, 28231, 28331, 28431],
# 冷却石 / 施法石 (28032..28432)
"COOLDOWN": [28032, 28132, 28232, 28332, 28432],
# 战士突击石 (28033..28433)
"WARRIOR": [28033, 28133, 28233, 28333, 28433],
# 刺客突击石 (28034..28434)
"ASSASSIN": [28034, 28134, 28234, 28334, 28434],
# 武士突击石 (28035..28435)
"SURA": [28035, 28135, 28235, 28335, 28435],
# 萨满突击石 (28036..28436)
"SHAMAN": [28036, 28136, 28236, 28336, 28436],
# 怪物石 (28037..28437)
"MONSTER": [28037, 28137, 28237, 28337, 28437],
# 躲闪石 / 回避石 (28038..28438)
"EVASION": [28038, 28138, 28238, 28338, 28438],
# 抵挡石 / 格挡石 (28039..28439)
"BLOCK": [28039, 28139, 28239, 28339, 28439],
# 坚韧石 / 魔法石 (28040..28440)
"MAGIC": [28040, 28140, 28240, 28340, 28440],
# 活力石 / 生命石 (28041..28441)
"VITALITY": [28041, 28141, 28241, 28341, 28441],
# 防御石 (28042..28442)
"DEFENSE": [28042, 28142, 28242, 28342, 28442],
# 速度石 (28043..28443)
"SPEED": [28043, 28143, 28243, 28343, 28443],
}
# 官方默认镶嵌成功率:30%40250 char_item.cpp:5070 number(1, 100) <= 30
const DEFAULT_METIN_SUCCESS_RATE := 30
## 检查是否为魔石
static func is_metin_stone(vnum: int, proto: Dictionary = {}) -> bool:
if proto and int(proto.get("type", 0)) == ITEM_TYPE_METIN:
return true
return vnum >= 28000 and vnum <= 28450
## 检查是否为清除魔石残渣卷轴
static func is_clean_socket_scroll(vnum: int) -> bool:
return vnum == VNUM_CLEAN_SOCKET_SCROLL
## 获取魔石的类别分组(用于防同类同插槽重复)
static func get_metin_group(vnum: int) -> String:
for grp in METIN_GROUPS.keys():
if vnum in METIN_GROUPS[grp]:
return grp
return str(vnum / 100)
## 检查装备能否镶嵌该魔石(武器魔石 vs 防具魔石)
static func can_attach_metin(stone_vnum: int, stone_proto: Dictionary, target_proto: Dictionary) -> Dictionary:
var target_type := int(target_proto.get("type", 0))
if target_type not in [ITEM_TYPE_WEAPON, ITEM_TYPE_ARMOR]:
return {"ok": false, "code": "CANNOT_SOCKET_TYPE", "msg": "此物品类型无法镶嵌魔石。"}
var stone_wear := int(stone_proto.get("wear_flag", 0))
if target_type == ITEM_TYPE_ARMOR:
# 防具魔石必须匹配 WEARABLE_BODY (衣服)
var sub := int(target_proto.get("sub_type", 0))
if sub != 0: # 0 = ARMOR_BODY
return {"ok": false, "code": "ARMOR_NOT_BODY", "msg": "防具类魔石只能镶嵌在盔甲上。"}
elif target_type == ITEM_TYPE_WEAPON:
var sub := int(target_proto.get("sub_type", 0))
if sub == 5: # WEAPON_ARROW
return {"ok": false, "code": "WEAPON_ARROW", "msg": "箭矢无法镶嵌魔石。"}
return {"ok": true}
## 执行魔石镶嵌 (40250 char_item.cpp:5004-5080)
static func socket_metin_stone(stone_vnum: int, stone_proto: Dictionary,
target_item: Dictionary, target_proto: Dictionary,
force_success := false, force_fail := false) -> Dictionary:
var check := can_attach_metin(stone_vnum, stone_proto, target_proto)
if not bool(check.get("ok", false)):
return check
var sockets: Array = target_item.get("sockets", [1, 1, 1]).duplicate()
while sockets.size() < 3:
sockets.append(1)
var stone_grp := get_metin_group(stone_vnum)
# 检查装备上是否已有同类魔石 (40250 char_item.cpp:5035 item->GetValue(5) == p->alValues[5])
for s in sockets:
var sv := int(s)
if sv > 2 and sv != ITEM_BROKEN_METIN_VNUM:
if get_metin_group(sv) == stone_grp:
return {
"ok": false,
"code": "DUPLICATE_METIN",
"msg": "装备上已经镶嵌了同类型的魔石,无法重复镶嵌!"
}
# 寻找可用孔位 (40250: socket >= 1 && socket <= 2 表示开孔可用状态)
var target_slot := -1
for i in sockets.size():
var sv := int(sockets[i])
if sv >= 1 and sv <= 2:
target_slot = i
break
if target_slot == -1:
return {
"ok": false,
"code": "NO_AVAILABLE_SOCKET",
"msg": "此装备没有可用的空魔石插槽(已插满或被碎石堵塞)。"
}
# 判定成功/失败概率(40250 官方 30% 成功)
var success := false
if force_success:
success = true
elif force_fail:
success = false
else:
success = (randi() % 100 + 1) <= DEFAULT_METIN_SUCCESS_RATE
if success:
sockets[target_slot] = stone_vnum
target_item["sockets"] = sockets
return {
"ok": true,
"code": "SUCCESS",
"msg": "魔石成功镶嵌到了装备上!",
"consumed": true,
"socket_index": target_slot,
"result_vnum": stone_vnum,
}
else:
# 镶嵌失败:碎石堵塞孔位 (ITEM_BROKEN_METIN_VNUM = 28960)
sockets[target_slot] = ITEM_BROKEN_METIN_VNUM
target_item["sockets"] = sockets
return {
"ok": false,
"code": "BROKEN_METIN",
"msg": "魔石破碎了,碎石残渣堵塞了插槽!",
"consumed": true,
"socket_index": target_slot,
"result_vnum": ITEM_BROKEN_METIN_VNUM,
}
## 执行提石卷轴 / 清除碎石残渣 (40250 char_item.cpp:4510 USE_CLEAN_SOCKET)
static func clean_broken_sockets(target_item: Dictionary) -> Dictionary:
var sockets: Array = target_item.get("sockets", [1, 1, 1]).duplicate()
while sockets.size() < 3:
sockets.append(1)
var has_broken := false
for s in sockets:
if int(s) == ITEM_BROKEN_METIN_VNUM:
has_broken = true
break
if not has_broken:
return {
"ok": false,
"code": "NO_BROKEN_STONE",
"msg": "该装备插槽内没有碎石残渣,无需清除。"
}
# 40250 char_item.cpp:4526 算法:
# 1. 把所有有效魔石(不等于 28960 且不为 0)按序向前压实
# 2. 剩余的孔位重新置为 1(可用空孔)
var cleaned: Array = []
for s in sockets:
var sv := int(s)
if sv != ITEM_BROKEN_METIN_VNUM and sv > 2:
cleaned.append(sv)
while cleaned.size() < sockets.size():
cleaned.append(1) # 恢复为空孔位 1
target_item["sockets"] = cleaned
return {
"ok": true,
"code": "SUCCESS",
"msg": "成功清除了装备上的碎石残渣,插槽已恢复可用!",
"consumed": true,
"sockets": cleaned,
}