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

148 lines
4.4 KiB
GDScript

# chat_hyperlink_dice_system.gd —— 40250 聊天物品超链接与 /dice 掷骰系统 1:1
# 严格对照:
# ClientVS22/source/EterLib/TextTag.cpp:32 (Hyperlink |Hitem:vnum:flags:socket:attr|h[%s]|h)
# Client/Eternexus/root/uichat.py & uitooltip.py (ItemHyperlink, SetHyperlinkItem)
# metin2/src/server/game/src/cmd_general.cpp:2392 (do_dice)
extends RefCounted
const LINK_COLOR := "ffffc700" # 官方物品链接专用亮金黄色
# 1. 将物品数据编码为 40250 官方超链接格式
# 格式: |cffffc700|Hitem:vnum:flags:s0:s1:s2:a0_t:a0_v:a1_t:a1_v:...|h[物品名]|h|r
static func encode_item_hyperlink(item_data: Dictionary) -> String:
var vnum: int = item_data.get("vnum", 0)
var name: String = item_data.get("name", "Unknown Item")
var flags: int = item_data.get("flags", 0)
var sockets: Array = item_data.get("sockets", [0, 0, 0])
while sockets.size() < 3:
sockets.append(0)
var attrs: Array = item_data.get("attributes", [])
var attr_parts: Array = []
for a in attrs:
attr_parts.append("%d:%d" % [a.get("type", 0), a.get("value", 0)])
var payload_parts := [
str(vnum),
str(flags),
str(sockets[0]),
str(sockets[1]),
str(sockets[2])
]
if not attr_parts.is_empty():
payload_parts.append((":").join(attr_parts))
var payload := (":").join(payload_parts)
return "|c%s|Hitem:%s|h[%s]|h|r" % [LINK_COLOR, payload, name]
# 2. 从超链接中解析提取物品完整数据结构 (1:1 TextTag::FindHyperlink)
static func decode_item_hyperlink(hyperlink_text: String) -> Dictionary:
var res := {
"valid": false,
"vnum": 0,
"name": "",
"flags": 0,
"sockets": [0, 0, 0],
"attributes": []
}
# 正则或字符串匹配提取 |Hitem:(.+?)\|h\[(.*?)\]\|h
var h_start := hyperlink_text.find("|Hitem:")
if h_start == -1:
return res
var payload_start := h_start + 7 # len("|Hitem:")
var payload_end := hyperlink_text.find("|h[", payload_start)
if payload_end == -1:
return res
var payload := hyperlink_text.substr(payload_start, payload_end - payload_start)
var name_start := payload_end + 3 # len("|h[")
var name_end := hyperlink_text.find("]|h", name_start)
if name_end == -1:
return res
var display_name := hyperlink_text.substr(name_start, name_end - name_start)
res["name"] = display_name
var tokens := payload.split(":")
if tokens.size() < 5:
return res
res["vnum"] = tokens[0].to_int()
res["flags"] = tokens[1].to_int()
res["sockets"] = [tokens[2].to_int(), tokens[3].to_int(), tokens[4].to_int()]
# 解析属性对 (type:value)
var attrs: Array = []
var idx := 5
while idx + 1 < tokens.size():
var at_type := tokens[idx].to_int()
var at_val := tokens[idx + 1].to_int()
attrs.append({ "type": at_type, "value": at_val })
idx += 2
res["attributes"] = attrs
res["valid"] = true
return res
# 3. 模拟 Tooltip 生成(悬停时渲染内容)
static func build_tooltip_text(item_dict: Dictionary) -> String:
if not item_dict.get("valid", false):
return "无效的物品链接"
var lines: Array = []
lines.append("=== %s (Vnum: %d) ===" % [item_dict["name"], item_dict["vnum"]])
# 宝石孔位信息
var sockets: Array = item_dict.get("sockets", [])
for i in range(sockets.size()):
var s: int = sockets[i]
if s > 0:
lines.append("孔位 %d: 镶嵌宝石 [Vnum %d]" % [i + 1, s])
elif s == 1: # 官方孔位开孔标记
lines.append("孔位 %d: 空孔" % [i + 1])
# 附加属性信息 (1~7 条)
var attrs: Array = item_dict.get("attributes", [])
for a in attrs:
lines.append("附加属性: 类型 %d +%d" % [a["type"], a["value"]])
return ("\n").join(lines)
# 4. 40250 官方 /dice 掷骰系统 (1:1 ACMD(do_dice))
static func execute_dice(player_name: String, args: Array = [], roll_override: int = -1) -> Dictionary:
var start_val := 1
var end_val := 100
if args.size() >= 2:
start_val = int(args[0])
end_val = int(args[1])
elif args.size() == 1:
start_val = 1
end_val = int(args[0])
# 40250 规则: end = MAX(start, end); start = MIN(start, end);
var real_start := mini(start_val, end_val)
var real_end := maxi(start_val, end_val)
var roll_result: int = 0
if roll_override >= 0:
roll_result = clamp(roll_override, real_start, real_end)
else:
roll_result = randi() % (real_end - real_start + 1) + real_start
var broadcast_msg := "%s 投掷了骰子,点数为 %d (%d-%d)" % [
player_name, roll_result, real_start, real_end
]
return {
"player": player_name,
"roll": roll_result,
"start": real_start,
"end": real_end,
"message": broadcast_msg
}