Files
mtgodot-poc/project/ui/item_tooltip.gd
T

774 lines
26 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.
# ItemTooltip —— item_proto + itemdesc + 物品实例状态的统一属性提示与 40250 模型构建。
#
# 参考:ClientVS22/POC/assets/root/uitooltip.py::ItemToolTip.AddItemData。
# 支持 40250 调色板、居中排版数据模型导出、攻防数值加算、固有属性与附加属性着色、
# 适用职业判断(红字警示)、魂石槽图文解析与纯文本向下兼容。
extends RefCounted
# 40250 官方调色板 (uitooltip.py)
const TITLE_COLOR := Color(0.9490, 0.9058, 0.7568, 1.0) # #f2e7c1 浅金象牙白 (白装/普通)
const SPECIAL_TITLE_COLOR := Color(1.0, 0.7843, 0.0, 1.0) # #ffc800 亮金黄色 (有附加属性/稀有)
const NORMAL_COLOR := Color(0.7607, 0.7607, 0.7607, 1.0) # #c2c2c2 普通浅灰
const FONT_COLOR := Color(0.7607, 0.7607, 0.7607, 1.0)
const PRICE_COLOR := Color(1.0, 0.7255, 0.4275, 1.0) # #ffb96d 价格金橙色
const CONDITION_COLOR := Color(0.7451, 0.7059, 0.4902, 1.0) # #beb47d 说明/要求淡金
const ENABLE_COLOR := Color(0.7607, 0.7607, 0.7607, 1.0)
const DISABLE_COLOR := Color(0.9, 0.4745, 0.4627, 1.0) # #e67976 限制不满足/警示红
const POSITIVE_COLOR := Color(0.5411, 0.7254, 0.5568, 1.0) # #8ab98e 基础攻防与固有词条浅绿
const NEGATIVE_COLOR := Color(0.9, 0.4745, 0.4627, 1.0) # #e67976 负向词条警示红
const SPECIAL_POSITIVE_COLOR := Color(0.6911, 0.8754, 0.7068, 1.0) # #b0dfb4 附加属性1~5薄荷青绿
const SPECIAL_POSITIVE_COLOR2 := Color(0.8824, 0.9804, 0.8824, 1.0)# #e1fae1 附加属性6~7浅薄荷绿
const ITEM_TYPE_WEAPON := 1
const ITEM_TYPE_ARMOR := 2
const ITEM_TYPE_LOTTERY := 8
const ITEM_TYPE_FISH := 12
const ITEM_TYPE_ROD := 13
const ITEM_TYPE_UNIQUE := 16
const ITEM_TYPE_PICK := 24
const ITEM_TYPE_BLEND := 27
const ITEM_TYPE_COSTUME := 28
const ITEM_TYPE_DS := 29
const ITEM_TYPE_RING := 33
const ITEM_TYPE_BELT := 34
const CHARACTER_NAMES := ["战士", "刺客", "修罗", "萨满"]
const ANTIFLAG_FEMALE := 1 << 0
const ANTIFLAG_MALE := 1 << 1
const ANTIFLAG_WARRIOR := 1 << 2
const ANTIFLAG_ASSASSIN := 1 << 3
const ANTIFLAG_SURA := 1 << 4
const ANTIFLAG_SHAMAN := 1 << 5
const LIMIT_NAMES := {
1: "等级要求",
2: "力量要求",
3: "敏捷要求",
4: "智力要求",
5: "体力要求",
6: "网吧要求",
7: "实时期限",
8: "首次使用期限",
9: "穿戴计时",
}
const TYPE_NAMES := {
1: "武器",
2: "防具",
3: "使用品",
4: "自动使用品",
5: "材料",
6: "特殊物品",
7: "工具",
8: "抽奖品",
9: "金币",
10: "魔石",
11: "容器",
12: "鱼",
13: "鱼竿",
14: "资源",
16: "独特物品",
17: "技能书",
18: "任务物品",
19: "变身物品",
20: "宝箱",
21: "宝箱钥匙",
22: "遗忘书",
23: "礼盒",
24: "镐",
25: "发型",
26: "图腾",
27: "混合物",
28: "时装",
29: "龙魂石",
30: "特殊龙魂石",
31: "提取物",
32: "副货币",
33: "戒指",
34: "腰带",
}
# APPLY_* 编号来自 ClientVS22/source/GameLib/ItemData.h::EApplyTypes。
const APPLY_NAMES := {
1: "最大生命",
2: "最大法力",
3: "体质",
4: "智力",
5: "力量",
6: "敏捷",
7: "攻击速度",
8: "移动速度",
9: "施法速度",
10: "生命恢复",
11: "法力恢复",
12: "中毒概率",
13: "眩晕概率",
14: "缓慢概率",
15: "暴击概率",
16: "穿透概率",
17: "对人族伤害",
18: "对动物伤害",
19: "对兽人伤害",
20: "对秘境伤害",
21: "对不死族伤害",
22: "对恶魔伤害",
23: "生命吸收",
24: "法力吸收",
25: "法力燃烧",
26: "伤害转法力",
27: "格挡",
28: "闪避",
29: "剑防御",
30: "双手防御",
31: "匕首防御",
32: "铃防御",
33: "扇防御",
34: "弓防御",
35: "火抗性",
36: "雷抗性",
37: "魔法抗性",
38: "风抗性",
39: "近战反射",
40: "诅咒反射",
41: "中毒抵抗",
42: "击杀恢复法力",
43: "经验加成",
44: "金币加成",
45: "掉落加成",
46: "药水效果",
47: "击杀恢复生命",
48: "免疫眩晕",
49: "免疫缓慢",
50: "免疫跌落",
51: "技能",
52: "弓箭距离",
53: "攻击等级",
54: "防御等级",
55: "魔法攻击等级",
56: "魔法防御等级",
57: "诅咒概率",
58: "最大耐力",
59: "对战士伤害",
60: "对刺客伤害",
61: "对武士伤害",
62: "对萨满伤害",
63: "对怪物伤害",
64: "商城攻击加成",
65: "商城防御加成",
66: "商城经验加成",
67: "商城掉落加成",
68: "商城金币加成",
69: "最大生命百分比",
70: "最大法力百分比",
71: "技能伤害",
72: "普通攻击伤害",
73: "技能防御",
74: "普通攻击防御",
75: "生命提取",
76: "网吧经验加成",
77: "网吧掉落加成",
78: "战士抗性",
79: "刺客抗性",
80: "武士抗性",
81: "萨满抗性",
82: "能量",
83: "防御",
84: "时装属性加成",
85: "魔法攻击百分比",
86: "近战魔法攻击百分比",
87: "冰抗性",
88: "土抗性",
89: "暗抗性",
90: "抗暴击",
91: "抗穿透",
}
# 40250 uitooltip.py AFFECT_DICT 规定为百分比显示的词条
const PERCENT_APPLIES := {
7: true, # 攻击速度
8: true, # 移动速度
9: true, # 施法速度
10: true, # 生命恢复
11: true, # 法力恢复
12: true, # 中毒概率
13: true, # 眩晕概率
14: true, # 缓慢概率
15: true, # 暴击概率
16: true, # 穿透概率
17: true, # 对人族伤害
18: true, # 对动物伤害
19: true, # 对兽人伤害
20: true, # 对秘境伤害
21: true, # 对不死族伤害
22: true, # 对恶魔伤害
23: true, # 生命吸收
24: true, # 法力吸收
25: true, # 法力燃烧
26: true, # 伤害转法力
27: true, # 格挡
28: true, # 闪避
29: true, # 剑防御
30: true, # 双手防御
31: true, # 匕首防御
32: true, # 铃防御
33: true, # 扇防御
34: true, # 弓防御
35: true, # 火抗性
36: true, # 雷抗性
37: true, # 魔法抗性
38: true, # 风抗性
39: true, # 近战反射
40: true, # 诅咒反射
41: true, # 中毒抵抗
42: true, # 击杀恢复法力
43: true, # 经验加成
44: true, # 金币加成
45: true, # 掉落加成
46: true, # 药水效果
47: true, # 击杀恢复生命
57: true, # 诅咒概率
59: true, # 对战士伤害
60: true, # 对刺客伤害
61: true, # 对武士伤害
62: true, # 对萨满伤害
63: true, # 对怪物伤害
64: true, # 商城攻击加成
65: true, # 商城防御加成
66: true, # 商城经验加成
67: true, # 商城掉落加成
68: true, # 商城金币加成
69: true, # 最大生命百分比
70: true, # 最大法力百分比
71: true, # 技能伤害
72: true, # 普通攻击伤害
73: true, # 技能防御
74: true, # 普通攻击防御
78: true, # 战士抗性
79: true, # 刺客抗性
80: true, # 武士抗性
81: true, # 萨满抗性
85: true, # 魔法攻击百分比
86: true, # 近战魔法攻击百分比
87: true, # 冰抗性
88: true, # 土抗性
89: true, # 暗抗性
90: true, # 抗暴击
91: true, # 抗穿透
}
var _descriptions: Dictionary = {}
var _item_names: Dictionary = {}
var _skill_names: Dictionary = {}
var _proto_node: Object = null
var _assets_root: String = ""
func setup(assets_root: String, lang := "", proto_node: Object = null) -> bool:
_descriptions.clear()
_item_names.clear()
_skill_names.clear()
_assets_root = assets_root
_proto_node = proto_node
if lang.is_empty():
var LocaleScript = load("res://locale.gd")
if LocaleScript and LocaleScript.has_method("get_default_lang"):
lang = LocaleScript.get_default_lang()
else:
lang = "zh"
var names_path := assets_root.path_join("locale/locale/%s/item_names.txt" % lang)
if FileAccess.file_exists(names_path):
var nf := FileAccess.open(names_path, FileAccess.READ)
if nf:
while not nf.eof_reached():
var line := nf.get_line().strip_edges()
if line.is_empty() or line.begins_with("#") or line.begins_with("VNUM"):
continue
var tab := line.find("\t")
if tab < 0:
tab = line.find(" ")
if tab < 0:
continue
var k := line.substr(0, tab).strip_edges()
var v := line.substr(tab + 1).strip_edges()
if k.is_valid_int():
_item_names[int(k)] = v
var path := assets_root.path_join("locale/locale/%s/itemdesc.txt" % lang)
if FileAccess.file_exists(path):
var file := FileAccess.open(path, FileAccess.READ)
if file:
while not file.eof_reached():
var parts := file.get_line().split("\t", true)
if parts.size() < 2:
continue
var vnum := int(parts[0].strip_edges())
if vnum <= 0:
continue
_descriptions[vnum] = {
"name": parts[1].strip_edges(),
"description": parts[2].strip_edges() if parts.size() > 2 else "",
}
_load_skill_names(assets_root, lang)
return not _descriptions.is_empty() or not _skill_names.is_empty() or not _item_names.is_empty()
func get_proto_node() -> Object:
return _proto_node
func get_stone_info(stone_vnum: int) -> Dictionary:
var name := ""
if _item_names.has(stone_vnum):
name = String(_item_names[stone_vnum])
var affect := ""
var icon_path := ""
if name.is_empty() and _proto_node and _proto_node.has_method("item"):
var sp: Dictionary = _proto_node.item(stone_vnum)
if not sp.is_empty():
name = String(sp.get("locale_name", sp.get("name", "")))
var sa: Array = sp.get("applies", [])
if not sa.is_empty():
var atype := int(sa[0].get("type", 0))
var aval := int(sa[0].get("value", 0))
if atype != 0 and aval != 0:
affect = format_apply_string(atype, aval)
elif not affect.is_empty() or (_proto_node and _proto_node.has_method("item")):
var sp: Dictionary = _proto_node.item(stone_vnum)
if not sp.is_empty():
var sa: Array = sp.get("applies", [])
if not sa.is_empty():
var atype := int(sa[0].get("type", 0))
var aval := int(sa[0].get("value", 0))
if atype != 0 and aval != 0:
affect = format_apply_string(atype, aval)
if name.is_empty() and _descriptions.has(stone_vnum):
name = String(_descriptions[stone_vnum].get("name", ""))
if name.is_empty():
name = "魂石 #%d" % stone_vnum
return {"name": name, "affect": affect, "icon_path": icon_path}
func format_apply_string(apply_type: int, value: int) -> String:
var label: String = APPLY_NAMES.get(apply_type, "属性 %d" % apply_type)
if PERCENT_APPLIES.get(apply_type, false):
return "%s %+d%%" % [label, value]
return "%s %+d" % [label, value]
# 核心 40250 数据模型导出(供 ItemTooltipView 九宫格控件渲染)
func build_model(vnum: int, count: int, proto_data: Dictionary, instance_data: Dictionary = {},
player_ctx: Dictionary = {}) -> Dictionary:
if vnum <= 0:
return {}
var name: String = _item_names.get(vnum, "")
if name.is_empty():
name = String(proto_data.get("locale_name", proto_data.get("name", "")))
if name.is_empty() and _descriptions.has(vnum):
name = String(_descriptions[vnum].get("name", ""))
if name.is_empty():
name = "物品 #%d" % vnum
var sockets: Array = instance_data.get("sockets", proto_data.get("sockets", []))
name = _special_title(vnum, name, sockets)
var attrs: Array = instance_data.get("attrs", proto_data.get("attrs", []))
var has_attrs := false
for a in attrs:
if int(a.get("type", 0)) != 0 and int(a.get("value", 0)) != 0:
has_attrs = true
break
var title_col := SPECIAL_TITLE_COLOR if has_attrs else TITLE_COLOR
var desc_info: Dictionary = _descriptions.get(vnum, {})
var desc_text := String(desc_info.get("description", ""))
var summary_text := String(desc_info.get("summary", ""))
# 限制要求
var limits_out: Array[Dictionary] = []
var player_level: int = int(player_ctx.get("level", 0))
for limit in proto_data.get("limits", []):
var limit_type := int(limit.get("type", 0))
var limit_value := int(limit.get("value", 0))
if limit_type == 6:
limits_out.append({"text": "PC房专用道具", "color": NORMAL_COLOR})
continue
if limit_type == 1 and limit_value > 0:
var col := DISABLE_COLOR if (player_level > 0 and player_level < limit_value) else NORMAL_COLOR
limits_out.append({"text": "要求等级: %d" % limit_value, "color": col, "limit_type": 1, "value": limit_value})
elif limit_type != 0 and limit_value > 0:
var lim_name: String = LIMIT_NAMES.get(limit_type, "限制 %d" % limit_type)
limits_out.append({"text": "%s: %d" % [lim_name, limit_value], "color": NORMAL_COLOR, "limit_type": limit_type, "value": limit_value})
# 基础战斗属性 (Weapon / Armor)
var stats_out: Array[Dictionary] = []
var item_type := int(proto_data.get("type", 0))
var item_subtype := int(proto_data.get("sub_type", proto_data.get("subtype", 0)))
var values: Array = proto_data.get("values", [])
if item_type == ITEM_TYPE_WEAPON and values.size() >= 6:
var min_attack := int(values[3]) + int(values[5])
var max_attack := int(values[4]) + int(values[5])
var min_magic := int(values[1]) + int(values[5])
var max_magic := int(values[2]) + int(values[5])
var atk_str := ""
if max_attack > min_attack:
atk_str = "攻击力 %d - %d" % [min_attack, max_attack]
elif min_attack > 0:
atk_str = "攻击力 %d" % min_attack
var mag_str := ""
if max_magic > min_magic:
mag_str = "魔法攻击 %d - %d" % [min_magic, max_magic]
elif min_magic > 0:
mag_str = "魔法攻击 %d" % min_magic
if item_subtype == 4: # WEAPON_FAN 扇子先魔法后物理
if not mag_str.is_empty():
stats_out.append({"text": mag_str, "color": POSITIVE_COLOR})
if not atk_str.is_empty():
stats_out.append({"text": atk_str, "color": POSITIVE_COLOR})
else:
if not atk_str.is_empty():
stats_out.append({"text": atk_str, "color": POSITIVE_COLOR})
if not mag_str.is_empty():
stats_out.append({"text": mag_str, "color": POSITIVE_COLOR})
elif item_type == ITEM_TYPE_ARMOR and values.size() >= 6:
var def_val := int(values[1]) + int(values[5]) * 2
if def_val > 0:
stats_out.append({"text": "防御力 %d" % def_val, "color": POSITIVE_COLOR})
if int(values[0]) > 0:
stats_out.append({"text": "魔法防御 %d" % int(values[0]), "color": POSITIVE_COLOR})
# 固有属性 (Proto Applies)
var applies_out: Array[Dictionary] = []
for apply in proto_data.get("applies", []):
var atype := int(apply.get("type", 0))
var aval := int(apply.get("value", 0))
if atype == 0 or aval == 0:
continue
var astr := format_apply_string(atype, aval)
var acol := POSITIVE_COLOR if aval > 0 else NEGATIVE_COLOR
applies_out.append({"text": astr, "color": acol, "type": atype, "value": aval})
# 附加属性 (Instance Attrs,蓝字/强化属性)
var attrs_out: Array[Dictionary] = []
for i in attrs.size():
var attr: Dictionary = attrs[i]
var atype := int(attr.get("type", 0))
var aval := int(attr.get("value", 0))
if atype == 0 or aval == 0:
continue
var astr := format_apply_string(atype, aval)
var acol := SPECIAL_POSITIVE_COLOR if i < 5 else SPECIAL_POSITIVE_COLOR2
if aval < 0:
acol = NEGATIVE_COLOR
attrs_out.append({"text": astr, "color": acol, "type": atype, "value": aval, "index": i})
# 穿戴职业 (Wearable Info)
var wearable_out: Dictionary = {}
if item_type == ITEM_TYPE_WEAPON or item_type == ITEM_TYPE_ARMOR or item_type == ITEM_TYPE_COSTUME:
var anti: int = int(proto_data.get("anti_flags", 0))
var jobs: Array[String] = []
var player_job: int = int(player_ctx.get("job", -1))
var player_job_can := true
for j in 4:
if (anti & (1 << (2 + j))) == 0:
jobs.append(CHARACTER_NAMES[j])
else:
if player_job == j:
player_job_can = false
var jobs_str := " ".join(jobs)
var jobs_col := DISABLE_COLOR if (player_job >= 0 and not player_job_can) else NORMAL_COLOR
var gender_str := ""
var gender_col := NORMAL_COLOR
var player_sex: int = int(player_ctx.get("sex", -1))
if (anti & ANTIFLAG_FEMALE) != 0:
gender_str = "男性专用"
if player_sex == 1:
gender_col = DISABLE_COLOR
elif (anti & ANTIFLAG_MALE) != 0:
gender_str = "女性专用"
if player_sex == 0:
gender_col = DISABLE_COLOR
wearable_out = {
"header": "[ 穿戴职业 ]",
"jobs": jobs_str,
"jobs_color": jobs_col,
"gender": gender_str,
"gender_color": gender_col,
}
# 魂石凹槽
var sockets_out: Array[Dictionary] = []
var has_sockets := false
for s in sockets:
if int(s) != 0:
has_sockets = true
break
if has_sockets or item_type == ITEM_TYPE_WEAPON or item_type == ITEM_TYPE_ARMOR:
for i in sockets.size():
var sval := int(sockets[i])
if sval == 0:
sockets_out.append({
"index": i,
"type": "empty",
"name": "未使用的凹槽",
"name_color": NORMAL_COLOR,
"affect": "",
"affect_color": POSITIVE_COLOR,
"vnum": 0,
"icon_path": "",
})
elif sval < 0:
sockets_out.append({
"index": i,
"type": "broken",
"name": "损坏的凹槽",
"name_color": DISABLE_COLOR,
"affect": "",
"affect_color": POSITIVE_COLOR,
"vnum": sval,
"icon_path": "",
})
elif sval == 1:
sockets_out.append({
"index": i,
"type": "empty",
"name": "未使用的凹槽",
"name_color": NORMAL_COLOR,
"affect": "",
"affect_color": POSITIVE_COLOR,
"vnum": 1,
"icon_path": "",
})
else:
var sinfo := get_stone_info(sval)
sockets_out.append({
"index": i,
"type": "stone",
"name": sinfo.get("name", "魂石 #%d" % sval),
"name_color": NORMAL_COLOR,
"affect": sinfo.get("affect", ""),
"affect_color": POSITIVE_COLOR,
"vnum": sval,
"icon_path": sinfo.get("icon_path", ""),
})
# 价格与限时
var price_val := int(instance_data.get("price", 0))
var realtime_str := _get_realtime_text(proto_data, sockets, instance_data)
return {
"vnum": vnum,
"title": name,
"title_color": title_col,
"count": count,
"desc": desc_text,
"summary": summary_text,
"limits": limits_out,
"stats": stats_out,
"applies": applies_out,
"attrs": attrs_out,
"wearable": wearable_out,
"sockets": sockets_out,
"price": price_val,
"realtime": realtime_str,
"item_type": item_type,
}
func format(vnum: int, count: int, proto_data: Dictionary, instance_data: Dictionary = {}) -> String:
if vnum <= 0:
return ""
var name: String = _item_names.get(vnum, "")
if name.is_empty():
name = String(proto_data.get("locale_name", proto_data.get("name", "")))
if name.is_empty() and _descriptions.has(vnum):
name = String(_descriptions[vnum].get("name", ""))
if name.is_empty():
name = "物品 #%d" % vnum
var sockets: Array = instance_data.get("sockets", proto_data.get("sockets", []))
name = _special_title(vnum, name, sockets)
var lines: Array[String] = [name]
if count > 1:
lines.append("数量:%d" % count)
var desc: Dictionary = _descriptions.get(vnum, {})
if not String(desc.get("description", "")).is_empty():
lines.append(String(desc["description"]))
var item_type := int(proto_data.get("type", 0))
var type_name: String = TYPE_NAMES.get(item_type, "")
if not type_name.is_empty():
lines.append("类型:" + type_name)
var values: Array = proto_data.get("values", [])
if item_type == ITEM_TYPE_WEAPON and values.size() >= 6:
var min_attack := int(values[3]) + int(values[5])
var max_attack := int(values[4]) + int(values[5])
if min_attack > 0 or max_attack > 0:
lines.append("攻击力:%d - %d" % [min_attack, max_attack])
if int(values[1]) > 0 or int(values[2]) > 0:
lines.append("魔法攻击:%d - %d" % [int(values[1]), int(values[2])])
elif item_type == ITEM_TYPE_ARMOR and values.size() >= 6:
if int(values[1]) > 0:
lines.append("防御力:%d" % (int(values[1]) + int(values[5]) * 2))
if int(values[0]) > 0:
lines.append("魔法防御:%d" % int(values[0]))
_append_special_item_data(lines, item_type, vnum, values, proto_data, instance_data)
for limit in proto_data.get("limits", []):
var limit_type := int(limit.get("type", 0))
var limit_value := int(limit.get("value", 0))
if limit_type == 6:
lines.append("PC房专用道具")
continue
if limit_type != 0 and limit_value > 0:
lines.append("%s%d" % [LIMIT_NAMES.get(limit_type, "限制 %d" % limit_type), limit_value])
for apply in proto_data.get("applies", []):
_append_bonus(lines, int(apply.get("type", 0)), int(apply.get("value", 0)))
for attr in instance_data.get("attrs", proto_data.get("attrs", [])):
_append_bonus(lines, int(attr.get("type", 0)), int(attr.get("value", 0)), "附加")
var has_socket := false
for socket in sockets:
if int(socket) != 0:
has_socket = true
break
if has_socket:
for i in sockets.size():
var socket_value := int(sockets[i])
lines.append("孔 %d%s" % [i + 1, "空" if socket_value == 0 else ("损坏" if socket_value < 0 else "#%d" % socket_value)])
var refined_vnum := int(proto_data.get("refined_vnum", 0))
if refined_vnum > 0:
lines.append("精炼后:#%d" % refined_vnum)
var refine_set := int(proto_data.get("refine_set", 0))
if refine_set > 0:
lines.append("精炼套件:%d" % refine_set)
_append_realtime_limit(lines, proto_data, sockets, instance_data)
return "\n".join(lines)
func _load_skill_names(assets_root: String, lang: String) -> void:
var path := assets_root.path_join("locale/locale/%s/skilldesc.txt" % lang)
if not FileAccess.file_exists(path):
return
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
return
while not file.eof_reached():
var parts := file.get_line().split("\t", false)
if parts.size() < 3:
continue
var skill_id := int(parts[0])
var skill_name := parts[2].strip_edges()
if skill_id > 0 and skill_name != "":
_skill_names[skill_id] = skill_name
func _special_title(vnum: int, item_name: String, sockets: Array) -> String:
if sockets.is_empty() or int(sockets[0]) <= 0:
return item_name
if vnum == 50300 or vnum == 70037 or vnum == 70055:
var skill_name := String(_skill_names.get(int(sockets[0]), ""))
return skill_name + " " + item_name if skill_name != "" else item_name
if vnum >= 70103 and vnum <= 70106 and _proto_node and _proto_node.has_method("mob"):
var mob: Dictionary = _proto_node.mob(int(sockets[0]))
var mob_name := String(mob.get("locale_name", mob.get("name", "")))
return mob_name + " " + item_name if mob_name != "" else item_name
return item_name
func _get_realtime_text(proto_data: Dictionary, sockets: Array, instance_data: Dictionary) -> String:
if bool(instance_data.get("realtime_rendered", false)) or sockets.is_empty():
return ""
var limit_type := 0
for limit in proto_data.get("limits", []):
var candidate := int(limit.get("type", 0))
if candidate == 7 or candidate == 8 or candidate == 9:
limit_type = candidate
break
if limit_type == 0:
return ""
var value := int(sockets[0])
var remaining := 0
if limit_type == 8:
var used := int(sockets[1]) if sockets.size() > 1 else 0
if used == 0:
return "首次使用后计时:%s" % _duration_text(value)
remaining = maxi(0, value - int(Time.get_unix_time_from_system()))
elif limit_type == 7:
remaining = maxi(0, value - int(Time.get_unix_time_from_system()))
else:
remaining = maxi(0, value)
if remaining <= 0:
return "已超时"
return _duration_text(remaining)
func _append_realtime_limit(lines: Array[String], proto_data: Dictionary, sockets: Array,
instance_data: Dictionary) -> void:
var t := _get_realtime_text(proto_data, sockets, instance_data)
if not t.is_empty():
if t.begins_with("首次使用"):
lines.append(t)
else:
lines.append("剩余时间:%s" % t)
func _duration_text(seconds: int) -> String:
var minutes := seconds / 60
var rest := seconds % 60
var text := "%d秒" % rest
if minutes > 0:
text = "%d%s" % [minutes, text] if rest > 0 else "%d分" % minutes
return text
func _append_special_item_data(lines: Array[String], item_type: int, vnum: int,
values: Array, proto_data: Dictionary, instance_data: Dictionary) -> void:
var sockets: Array = instance_data.get("sockets", [])
if item_type == ITEM_TYPE_ROD or item_type == ITEM_TYPE_PICK:
if values.size() >= 3 and not sockets.is_empty():
var level := int(values[0]) / 10
var current_exp := int(sockets[0])
var max_exp := int(values[2])
lines.append("等级:%d" % level)
lines.append("经验:%d / %d" % [current_exp, max_exp])
if max_exp > 0 and current_exp >= max_exp:
lines.append("经验已满,可升级")
elif item_type == ITEM_TYPE_FISH:
if not sockets.is_empty() and int(sockets[0]) > 0:
lines.append("长度:%.2f cm" % (float(sockets[0]) / 100.0))
elif item_type == ITEM_TYPE_LOTTERY:
if sockets.size() >= 2 and (int(sockets[0]) != 0 or int(sockets[1]) != 0):
lines.append("彩票号码:%d" % int(sockets[0]))
lines.append("彩票步数:%d" % int(sockets[1]))
elif item_type == ITEM_TYPE_BLEND:
if sockets.size() >= 3 and int(sockets[0]) != 0:
var affect_type := int(sockets[0])
var affect_value := int(sockets[1])
_append_bonus(lines, affect_type, affect_value, "效果:")
var duration := int(sockets[2])
lines.append("持续时间:%d 秒" % duration if duration > 0 else "无持续时间")
elif item_type == ITEM_TYPE_UNIQUE:
_append_item_remaining(lines, proto_data, sockets, instance_data)
elif item_type == ITEM_TYPE_DS:
var step := (vnum / 100) % 10
var refine := (vnum / 10) % 10
if step >= 0 and step <= 4:
lines.append("龙魂石阶段:%d" % (step + 1))
lines.append("龙魂石强化:%d" % refine)
func _append_item_remaining(lines: Array[String], proto_data: Dictionary, sockets: Array,
instance_data: Dictionary) -> void:
var remaining := int(instance_data.get("remaining_seconds", instance_data.get("duration", 0)))
if remaining <= 0 and not sockets.is_empty():
var limit_type := int(instance_data.get("limit_type", 0))
if limit_type == 0:
for limit in proto_data.get("limits", []):
if int(limit.get("type", 0)) != 0:
limit_type = int(limit.get("type", 0))
break
if limit_type == 7 or limit_type == 8:
remaining = maxi(0, int(sockets[0]) - int(Time.get_unix_time_from_system()))
elif limit_type == 9:
remaining = int(sockets[0])
elif sockets.size() >= 3:
remaining = int(sockets[2]) * 60
if remaining > 0:
lines.append("剩余时间:%d 秒" % remaining)
func _append_bonus(lines: Array[String], apply_type: int, value: int, prefix := "") -> void:
if apply_type == 0 or value == 0:
return
var label: String = APPLY_NAMES.get(apply_type, "属性 %d" % apply_type)
if not prefix.is_empty():
label = prefix + label
lines.append("%s%+d" % [label, value])