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

145 lines
5.0 KiB
GDScript

# attach_metin_dialog_system.gd
# 40250 官方 1:1 装备幽魂石/灵魂石镶嵌与双重属性对比预览系统
# 对照: uiattachmetin.py, attachstonedialog.py, char_item.cpp
class_name AttachMetinDialogSystem
extends RefCounted
const METIN_SOCKET_MAX_NUM: int = 3
const METIN_NORMAL: int = 0
const METIN_GOLD: int = 1
const METIN_SOCKET_TYPE_NONE: int = 0
const METIN_SOCKET_TYPE_SILVER: int = 1
const METIN_SOCKET_TYPE_GOLD: int = 2
const ITEM_BROKEN_METIN_VNUM: int = 28960
# 经典灵魂石属性对照表 (40250 canonical metin stones 28000~28443)
const METIN_STONES: Dictionary = {
28030: {"name": "战神之石+0", "bonus_type": "ATT_BONUS_TO_WARRIOR", "bonus_value": 5},
28130: {"name": "战神之石+1", "bonus_type": "ATT_BONUS_TO_WARRIOR", "bonus_value": 8},
28230: {"name": "战神之石+2", "bonus_type": "ATT_BONUS_TO_WARRIOR", "bonus_value": 12},
28330: {"name": "战神之石+3", "bonus_type": "ATT_BONUS_TO_WARRIOR", "bonus_value": 17},
28430: {"name": "战神之石+4", "bonus_type": "ATT_BONUS_TO_WARRIOR", "bonus_value": 25},
28434: {"name": "穿透之石+4", "bonus_type": "POINT_PENETRATE_PCT", "bonus_value": 8},
28435: {"name": "致命之石+4", "bonus_type": "POINT_CRITICAL_PCT", "bonus_value": 8},
28438: {"name": "防御之石+4", "bonus_type": "POINT_DEF_GRADE_BONUS", "bonus_value": 15},
28442: {"name": "加速之石+4", "bonus_type": "POINT_MOV_SPEED", "bonus_value": 30},
28960: {"name": "破碎的石头碎块", "bonus_type": "NONE", "bonus_value": 0}
}
var is_open: bool = false
var metin_pos: int = -1
var target_pos: int = -1
var metin_vnum: int = 0
var metin_subtype: int = METIN_NORMAL
var target_item_vnum: int = 0
var current_sockets: Array = [0, 0, 0]
var preview_sockets: Array = [0, 0, 0]
var target_socket_idx: int = -1
var last_packet_sent: Dictionary = {}
var last_sound_played: String = ""
signal dialog_opened(metin_pos: int, target_pos: int, preview_sockets: Array)
signal dialog_closed()
signal metin_attached(metin_pos: int, target_pos: int, target_slot_idx: int)
signal attach_failed(reason: String)
func can_attach_metin(slot_type: int, metin_type: int) -> bool:
if metin_type == METIN_NORMAL:
if slot_type == METIN_SOCKET_TYPE_SILVER or slot_type == METIN_SOCKET_TYPE_GOLD:
return true
elif metin_type == METIN_GOLD:
if slot_type == METIN_SOCKET_TYPE_GOLD:
return true
return false
func open_dialog(p_metin_pos: int, p_target_pos: int, p_metin_vnum: int, p_metin_subtype: int, p_target_vnum: int, p_sockets: Array) -> bool:
metin_pos = p_metin_pos
target_pos = p_target_pos
metin_vnum = p_metin_vnum
metin_subtype = p_metin_subtype
target_item_vnum = p_target_vnum
current_sockets = [0, 0, 0]
preview_sockets = [0, 0, 0]
target_socket_idx = -1
for i in range(mini(p_sockets.size(), METIN_SOCKET_MAX_NUM)):
current_sockets[i] = p_sockets[i]
preview_sockets[i] = p_sockets[i]
# 寻找首个符合条件的开放槽位
var found_slot: bool = false
for i in range(METIN_SOCKET_MAX_NUM):
var slot_state: int = current_sockets[i]
if can_attach_metin(slot_state, metin_subtype):
preview_sockets[i] = metin_vnum
target_socket_idx = i
found_slot = true
break
if not found_slot:
attach_failed.emit("NO_AVAILABLE_SOCKET")
return false
is_open = true
dialog_opened.emit(metin_pos, target_pos, preview_sockets)
return true
func accept() -> bool:
if not is_open:
return false
if target_socket_idx < 0:
attach_failed.emit("INVALID_SOCKET")
return false
# 40250 发送 net.SendItemUseToItemPacket(self.metinItemPos, self.targetItemPos)
last_packet_sent = {
"header": "HEADER_CG_ITEM_USE_TO_ITEM",
"metin_pos": metin_pos,
"target_pos": target_pos,
"target_socket_idx": target_socket_idx,
"metin_vnum": metin_vnum
}
# 官方原版音效
last_sound_played = "sound/ui/metinstone_insert.wav"
metin_attached.emit(metin_pos, target_pos, target_socket_idx)
close_dialog()
return true
func close_dialog() -> void:
is_open = false
metin_pos = -1
target_pos = -1
dialog_closed.emit()
# 模拟服务端结算镶嵌 (支持成功率判定: 成功直接嵌入,失败产生破碎石块 28960)
func simulate_server_process(target_sockets: Array, socket_idx: int, attached_metin_vnum: int, success: bool) -> Array:
var result_sockets: Array = target_sockets.duplicate()
if socket_idx >= 0 and socket_idx < result_sockets.size():
if success:
result_sockets[socket_idx] = attached_metin_vnum
else:
result_sockets[socket_idx] = ITEM_BROKEN_METIN_VNUM
return result_sockets
# 模拟卷轴清理破碎石头 (vnum 71083 / Scroll of Cleansing)
func clean_broken_socket(target_sockets: Array, socket_idx: int) -> Array:
var result_sockets: Array = target_sockets.duplicate()
if socket_idx >= 0 and socket_idx < result_sockets.size():
if result_sockets[socket_idx] == ITEM_BROKEN_METIN_VNUM:
result_sockets[socket_idx] = METIN_SOCKET_TYPE_SILVER
return result_sockets
func get_metin_info(vnum: int) -> Dictionary:
if METIN_STONES.has(vnum):
return METIN_STONES[vnum]
return {"name": "未知幽魂石", "bonus_type": "UNKNOWN", "bonus_value": 0}