257 lines
13 KiB
GDScript
257 lines
13 KiB
GDScript
# 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()
|