# 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()