Files
mtgodot-poc/project/test_item_tooltip_parity.gd
T

257 lines
13 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.
# test_item_tooltip_parity.gd —— 装备属性面板 40250 官方规范 100% 对齐测试。
# 检验:
# 1. 调色板与 uitooltip.py 官方常量 1:1 对齐
# 2. 装备标题根据是否有强化附加属性判定白装 (#F2E7C1) / 金装 (#FFC800)
# 3. 武器攻防加算与防具防御力加算 (value[1] + value[5]*2),浅绿色 (#8AB98E)
# 4. 固有属性与百分比词条格式化,带正负符号与 %
# 5. 附加属性 1~5 薄荷青绿 (#B0DFB4)6~7 浅薄荷绿 (#E1FAE1),无非官方“附加”冗余前缀
# 6. 适用职业与性别判断,不符合时警示红 (#E67976)
# 7. 等级限制判断,玩家等级不足时警示红 (#E67976)
# 8. 魂石凹槽图文解析与空槽/损坏槽
# 9. ItemTooltipView 控件九宫格 ThinBoard 背景、居中对齐与文字描边
extends SceneTree
const ItemTooltip = preload("res://ui/item_tooltip.gd")
const ItemTooltipView = preload("res://ui/item_tooltip_view.gd")
class MockProto extends Node:
var items: Dictionary = {}
func item(vnum: int) -> Dictionary:
return items.get(vnum, {})
var _failed := 0
var _pass_count := 0
func _assert(cond: bool, msg: String) -> void:
if cond:
_pass_count += 1
print("PASS: %s" % msg)
else:
_failed += 1
printerr("FAIL: %s" % msg)
func _init() -> void:
print("=== Starting test_item_tooltip_parity ===")
_test_palette_constants()
_test_weapon_model_parity()
_test_armor_model_parity()
_test_attrs_coloring_and_naming()
_test_wearable_job_and_level_warnings()
_test_socket_stone_resolution()
_test_item_tooltip_view_ui_structure()
if _failed == 0:
print("\n=== ALL %d ITEM TOOLTIP PARITY TESTS PASSED ===" % _pass_count)
quit(0)
else:
printerr("\n=== %d ITEM TOOLTIP PARITY TESTS FAILED ===" % _failed)
quit(1)
# --- Test 1: 调色板与 40250 原版常量一致 ---
func _test_palette_constants() -> void:
print("\n--- Test 1: 40250 Palette Constants ---")
# uitooltip.py: TITLE_COLOR = grp.GenerateColor(0.9490, 0.9058, 0.7568, 1.0)
_assert(is_equal_approx(ItemTooltip.TITLE_COLOR.r, 0.9490) and is_equal_approx(ItemTooltip.TITLE_COLOR.g, 0.9058),
"TITLE_COLOR matches 40250 (0.9490, 0.9058, 0.7568)")
# uitooltip.py: SPECIAL_TITLE_COLOR = grp.GenerateColor(1.0, 0.7843, 0.0, 1.0)
_assert(is_equal_approx(ItemTooltip.SPECIAL_TITLE_COLOR.r, 1.0) and is_equal_approx(ItemTooltip.SPECIAL_TITLE_COLOR.g, 0.7843),
"SPECIAL_TITLE_COLOR matches 40250 (1.0, 0.7843, 0.0)")
# uitooltip.py: POSITIVE_COLOR = grp.GenerateColor(0.5411, 0.7254, 0.5568, 1.0)
_assert(is_equal_approx(ItemTooltip.POSITIVE_COLOR.r, 0.5411) and is_equal_approx(ItemTooltip.POSITIVE_COLOR.g, 0.7254),
"POSITIVE_COLOR matches 40250 (0.5411, 0.7254, 0.5568)")
# uitooltip.py: SPECIAL_POSITIVE_COLOR = grp.GenerateColor(0.6911, 0.8754, 0.7068, 1.0)
_assert(is_equal_approx(ItemTooltip.SPECIAL_POSITIVE_COLOR.r, 0.6911) and is_equal_approx(ItemTooltip.SPECIAL_POSITIVE_COLOR.g, 0.8754),
"SPECIAL_POSITIVE_COLOR matches 40250 (0.6911, 0.8754, 0.7068)")
# uitooltip.py: SPECIAL_POSITIVE_COLOR2 = grp.GenerateColor(0.8824, 0.9804, 0.8824, 1.0)
_assert(is_equal_approx(ItemTooltip.SPECIAL_POSITIVE_COLOR2.r, 0.8824) and is_equal_approx(ItemTooltip.SPECIAL_POSITIVE_COLOR2.g, 0.9804),
"SPECIAL_POSITIVE_COLOR2 matches 40250 (0.8824, 0.9804, 0.8824)")
# uitooltip.py: DISABLE_COLOR = grp.GenerateColor(0.9, 0.4745, 0.4627, 1.0)
_assert(is_equal_approx(ItemTooltip.DISABLE_COLOR.r, 0.9) and is_equal_approx(ItemTooltip.DISABLE_COLOR.g, 0.4745),
"DISABLE_COLOR matches 40250 (0.9, 0.4745, 0.4627)")
# uitooltip.py: PRICE_COLOR = 0xffFFB96D
_assert(is_equal_approx(ItemTooltip.PRICE_COLOR.r, 1.0) and is_equal_approx(ItemTooltip.PRICE_COLOR.b, 0.4275),
"PRICE_COLOR matches 40250 (0xffFFB96D)")
# --- Test 2: 武器数值计算与白装/金装标题颜色 ---
func _test_weapon_model_parity() -> void:
print("\n--- Test 2: Weapon Model & Attack Values ---")
var builder := ItemTooltip.new()
var sword_proto := {
"vnum": 19,
"name": "Sword+9",
"locale_name": "战斗剑 +9",
"type": 1, # WEAPON
"sub_type": 0, # WEAPON_SWORD
"values": [0, 50, 70, 100, 120, 15], # min_magic=50+15=65, max_magic=70+15=85, min_atk=100+15=115, max_atk=120+15=135
"limits": [{"type": 1, "value": 30}],
"applies": [{"type": 7, "value": 15}], # 攻击速度 +15%
"anti_flags": 0,
}
# 1. 普通白装 (无 attrs):标题颜色为 TITLE_COLOR
var plain_model := builder.build_model(19, 1, sword_proto, {})
_assert(plain_model["title"] == "战斗剑 +9", "Title is 战斗剑 +9")
_assert(plain_model["title_color"] == ItemTooltip.TITLE_COLOR, "Plain weapon title color is TITLE_COLOR")
_assert(plain_model["stats"].size() == 2, "Weapon has 2 stats (attack & magic attack)")
_assert(plain_model["stats"][0]["text"] == "攻击力 115 - 135", "Attack power calculation matches (100+15 - 120+15)")
_assert(plain_model["stats"][0]["color"] == ItemTooltip.POSITIVE_COLOR, "Attack power is POSITIVE_COLOR (green)")
_assert(plain_model["stats"][1]["text"] == "魔法攻击 65 - 85", "Magic attack calculation matches (50+15 - 70+15)")
_assert(plain_model["applies"][0]["text"] == "攻击速度 +15%", "Attack speed has % symbol")
_assert(plain_model["applies"][0]["color"] == ItemTooltip.POSITIVE_COLOR, "Attack speed bonus is POSITIVE_COLOR")
# 2. 带有强化附加属性 (蓝字):标题颜色升级为 SPECIAL_TITLE_COLOR (金黄色)
var enchanted_instance := {
"attrs": [
{"type": 5, "value": 12}, # STR +12
{"type": 15, "value": 10}, # 暴击概率 +10%
]
}
var enchanted_model := builder.build_model(19, 1, sword_proto, enchanted_instance)
_assert(enchanted_model["title_color"] == ItemTooltip.SPECIAL_TITLE_COLOR,
"Weapon with bonus attributes has SPECIAL_TITLE_COLOR (#FFC800)")
# --- Test 3: 防具防御力公式加算 ---
func _test_armor_model_parity() -> void:
print("\n--- Test 3: Armor Defense Formula ---")
var builder := ItemTooltip.new()
var armor_proto := {
"vnum": 11209,
"name": "Armor+9",
"locale_name": "修罗战甲 +9",
"type": 2, # ARMOR
"sub_type": 0, # ARMOR_BODY
"values": [15, 80, 0, 0, 0, 8], # magic_def=15, def = 80 + 8*2 = 96
"limits": [{"type": 1, "value": 42}],
"applies": [{"type": 8, "value": -5}], # 移动速度 -5%
"anti_flags": (1 << 2) | (1 << 3) | (1 << 5), # 仅修罗可穿 (ANTIFLAG_WARRIOR | ANTIFLAG_ASSASSIN | ANTIFLAG_SHAMAN)
}
var model := builder.build_model(11209, 1, armor_proto, {})
_assert(model["stats"].size() == 2, "Armor has 2 stats")
_assert(model["stats"][0]["text"] == "防御力 96", "Armor defense formula (value[1] + value[5]*2 = 80 + 16 = 96)")
_assert(model["stats"][0]["color"] == ItemTooltip.POSITIVE_COLOR, "Defense is POSITIVE_COLOR")
_assert(model["stats"][1]["text"] == "魔法防御 15", "Magic defense matches value[0]")
_assert(model["applies"][0]["text"] == "移动速度 -5%", "Moving speed negative apply has %")
_assert(model["applies"][0]["color"] == ItemTooltip.NEGATIVE_COLOR, "Negative moving speed is NEGATIVE_COLOR (red)")
# --- Test 4: 附加属性配色 (1~5条薄荷青绿,6~7条浅薄荷绿) 与去除非原版“附加”字样 ---
func _test_attrs_coloring_and_naming() -> void:
print("\n--- Test 4: Bonus Attributes Colors & Clean Naming ---")
var builder := ItemTooltip.new()
var proto := {"type": 1, "values": [0, 0, 0, 10, 20, 0]}
var instance := {
"attrs": [
{"type": 1, "value": 2000}, # 最大生命 +2000
{"type": 5, "value": 12}, # 力量 +12
{"type": 15, "value": 10}, # 暴击概率 +10%
{"type": 16, "value": 10}, # 穿透概率 +10%
{"type": 17, "value": 15}, # 对人族伤害 +15%
{"type": 71, "value": 8}, # 技能伤害 +8% (第6条)
{"type": 72, "value": 12}, # 普通攻击伤害 +12% (第7条)
]
}
var model := builder.build_model(10, 1, proto, instance)
_assert(model["attrs"].size() == 7, "Model parsed 7 bonus attributes")
_assert(model["attrs"][0]["text"] == "最大生命 +2000", "Attribute 1 formatted cleanly without '附加' prefix")
_assert(model["attrs"][0]["color"] == ItemTooltip.SPECIAL_POSITIVE_COLOR, "Attribute 1 is SPECIAL_POSITIVE_COLOR (#B0DFB4)")
_assert(model["attrs"][2]["text"] == "暴击概率 +10%", "Attribute 3 (percent) has % sign")
_assert(model["attrs"][4]["color"] == ItemTooltip.SPECIAL_POSITIVE_COLOR, "Attribute 5 is SPECIAL_POSITIVE_COLOR")
_assert(model["attrs"][5]["text"] == "技能伤害 +8%", "Attribute 6 has % sign")
_assert(model["attrs"][5]["color"] == ItemTooltip.SPECIAL_POSITIVE_COLOR2, "Attribute 6 is SPECIAL_POSITIVE_COLOR2 (#E1FAE1)")
_assert(model["attrs"][6]["color"] == ItemTooltip.SPECIAL_POSITIVE_COLOR2, "Attribute 7 is SPECIAL_POSITIVE_COLOR2 (#E1FAE1)")
# --- Test 5: 穿戴职业与等级不足红字警示 ---
func _test_wearable_job_and_level_warnings() -> void:
print("\n--- Test 5: Wearable Jobs & Level Warning ---")
var builder := ItemTooltip.new()
var proto := {
"type": 1,
"values": [0, 0, 0, 10, 20, 0],
"limits": [{"type": 1, "value": 50}], # 要求等级 50
"anti_flags": (1 << 3) | (1 << 4) | (1 << 5), # 仅战士可穿 (非刺客/修罗/萨满)
}
# 1. 玩家等级不足 (当前 35 < 50): 等级限制飘红
var ctx_low_level := {"level": 35, "job": 0, "sex": 0} # 战士,35级
var model_low := builder.build_model(10, 1, proto, {}, ctx_low_level)
_assert(model_low["limits"][0]["text"] == "要求等级: 50", "Limit text is 要求等级: 50")
_assert(model_low["limits"][0]["color"] == ItemTooltip.DISABLE_COLOR, "Low level triggers DISABLE_COLOR (red)")
_assert(model_low["wearable"]["jobs"] == "战士", "Only 战士 listed in wearable")
_assert(model_low["wearable"]["jobs_color"] == ItemTooltip.NORMAL_COLOR, "Warrior can wear, jobs_color is NORMAL_COLOR")
# 2. 玩家职业不符 (当前是刺客 job=1): 职业限制飘红
var ctx_wrong_job := {"level": 60, "job": 1, "sex": 0} # 刺客,60级
var model_wrong := builder.build_model(10, 1, proto, {}, ctx_wrong_job)
_assert(model_wrong["limits"][0]["color"] == ItemTooltip.NORMAL_COLOR, "Level 60 >= 50, limit is NORMAL_COLOR")
_assert(model_wrong["wearable"]["jobs_color"] == ItemTooltip.DISABLE_COLOR, "Assassin cannot wear warrior sword, jobs_color is DISABLE_COLOR (red)")
# --- Test 6: 魂石凹槽图文解析与空槽/损坏槽 ---
func _test_socket_stone_resolution() -> void:
print("\n--- Test 6: Socket Stones Resolution ---")
var proto_node := MockProto.new()
proto_node.items[28430] = {
"vnum": 28430,
"name": "Stone_of_Speed+4",
"locale_name": "加速之石 +4",
"applies": [{"type": 8, "value": 30}], # 移动速度 +30%
}
var builder := ItemTooltip.new()
var assets := AssetRoot.path()
builder.setup(assets, "en", proto_node)
var sword_proto := {"type": 1, "values": [0, 0, 0, 10, 20, 0]}
var instance := {
"sockets": [28430, 0, -1] # 1: 镶嵌加速之石, 2: 未使用空槽, 3: 损坏槽
}
var model := builder.build_model(10, 1, sword_proto, instance)
_assert(model["sockets"].size() == 3, "Weapon has 3 socket entries")
_assert(model["sockets"][0]["type"] == "stone", "Socket 0 is stone")
_assert(model["sockets"][0]["name"] == "加速之石 +4", "Socket 0 resolved stone name 加速之石 +4")
_assert(model["sockets"][0]["affect"] == "移动速度 +30%", "Socket 0 resolved stone affect 移动速度 +30%")
_assert(model["sockets"][0]["affect_color"] == ItemTooltip.POSITIVE_COLOR, "Stone affect is POSITIVE_COLOR")
_assert(model["sockets"][1]["type"] == "empty", "Socket 1 is empty slot")
_assert(model["sockets"][1]["name"] == "未使用的凹槽", "Empty socket displays 未使用的凹槽")
_assert(model["sockets"][2]["type"] == "broken", "Socket 2 is broken")
_assert(model["sockets"][2]["name"] == "损坏的凹槽", "Broken socket displays 损坏的凹槽")
_assert(model["sockets"][2]["name_color"] == ItemTooltip.DISABLE_COLOR, "Broken socket is DISABLE_COLOR (red)")
proto_node.free()
# --- Test 7: ItemTooltipView UI 控件结构与 ThinBoard 呈现 ---
func _test_item_tooltip_view_ui_structure() -> void:
print("\n--- Test 7: ItemTooltipView UI Structure & ThinBoard ---")
var assets := AssetRoot.path()
var view := ItemTooltipView.new()
view.setup(assets)
var model := {
"title": "屠龙刀 +9",
"title_color": ItemTooltip.SPECIAL_TITLE_COLOR,
"count": 1,
"desc": "上古神兵",
"summary": "天下无双",
"limits": [{"text": "要求等级: 75", "color": ItemTooltip.NORMAL_COLOR}],
"stats": [{"text": "攻击力 250 - 310", "color": ItemTooltip.POSITIVE_COLOR}],
"applies": [{"text": "半人伤害 +15%", "color": ItemTooltip.POSITIVE_COLOR}],
"attrs": [{"text": "暴击概率 +10%", "color": ItemTooltip.SPECIAL_POSITIVE_COLOR}],
"wearable": {"header": "[ 穿戴职业 ]", "jobs": "战士", "jobs_color": ItemTooltip.NORMAL_COLOR, "gender": ""},
"sockets": [
{"type": "stone", "name": "致命之石 +4", "affect": "暴击概率 +8%", "vnum": 28431, "name_color": ItemTooltip.NORMAL_COLOR, "affect_color": ItemTooltip.POSITIVE_COLOR}
],
"price": 250000,
"realtime": "",
}
view.populate_from_model(model)
_assert(view.custom_minimum_size.x >= 190.0, "Tooltip custom_minimum_size.x >= 190px standard")
var vbox: VBoxContainer = view._vbox
_assert(vbox != null, "VBoxContainer created")
_assert(vbox.get_child_count() >= 8, "VBox contains all rendered sections")
var title_lbl: Label = vbox.get_child(0)
_assert(title_lbl.text == "屠龙刀 +9", "Title text is 屠龙刀 +9")
_assert(title_lbl.horizontal_alignment == HORIZONTAL_ALIGNMENT_CENTER, "Title is centered")
_assert(title_lbl.get_theme_constant("outline_size") == 2, "Title has outline_size = 2")
view.free()