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

369 lines
12 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 + 物品实例状态的统一文字提示。
#
# 参考:ClientVS22/POC/assets/root/uitooltip.py::ItemToolTip.AddItemData。
# Godot 的原生 tooltip 负责显示位置与换行;本类只负责把同一份物品快照
# 组织成标题、描述、限制、基础数值、附加属性、魂石槽和精炼关联。
extends RefCounted
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_DS := 29
const ITEM_TYPE_RING := 33
const ITEM_TYPE_BELT := 34
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: "抗穿透",
}
var _descriptions: Dictionary = {}
var _skill_names: Dictionary = {}
var _mob_proto: Object = null
func setup(assets_root: String, lang := "en", proto_node: Object = null) -> bool:
_descriptions.clear()
_skill_names.clear()
_mob_proto = proto_node
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()
func format(vnum: int, count: int, proto_data: Dictionary, instance_data: Dictionary = {}) -> String:
if vnum <= 0:
return ""
var name := String(proto_data.get("locale_name", proto_data.get("name", "物品 #%d" % vnum)))
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 != 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 _mob_proto and _mob_proto.has_method("mob"):
var mob: Dictionary = _mob_proto.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 _append_realtime_limit(lines: Array[String], proto_data: Dictionary, sockets: Array,
instance_data: Dictionary) -> void:
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 == 7 or limit_type == 8:
remaining = maxi(0, value - int(Time.get_unix_time_from_system()))
else:
remaining = maxi(0, value)
if remaining <= 0:
lines.append("剩余时间:已超时")
return
var minutes := remaining / 60
var seconds := remaining % 60
var text := "%d秒" % seconds
if minutes > 0:
text = "%d%s" % [minutes, text] if seconds > 0 else "%d分" % minutes
lines.append("剩余时间:%s" % 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])