- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
139 lines
6.3 KiB
GDScript
139 lines
6.3 KiB
GDScript
# test_item_attr_parity.gd —— 验证方向 1:装备附魔、洗炼与祝福宝珠 40250 1:1 对齐
|
|
extends SceneTree
|
|
|
|
const ItemAttrSystem = preload("res://item_attr_system.gd")
|
|
|
|
var _passed := 0
|
|
var _failed := 0
|
|
|
|
func _assert(cond: bool, msg: String) -> void:
|
|
if cond:
|
|
_passed += 1
|
|
print(" [PASS] %s" % msg)
|
|
else:
|
|
_failed += 1
|
|
printerr(" [FAIL] %s" % msg)
|
|
|
|
func _init() -> void:
|
|
print("\n=== Running test_item_attr_parity (Direction 1: Item Add/Change Bonus 1:1) ===")
|
|
test_scroll_detection()
|
|
test_can_have_attributes()
|
|
test_add_attribute_parity()
|
|
test_change_attribute_parity()
|
|
test_blessing_marble_parity()
|
|
|
|
print("\n=== SUMMARY: %d passed, %d failed ===" % [_passed, _failed])
|
|
if _failed > 0:
|
|
printerr("TEST FAILED!")
|
|
quit(1)
|
|
else:
|
|
print("ALL TESTS PASSED!")
|
|
quit(0)
|
|
|
|
func test_scroll_detection() -> void:
|
|
print("\n--- Test 1: Scroll Vnum Detection (71084, 71085, 70024) ---")
|
|
_assert(ItemAttrSystem.is_attr_scroll(71085), "71085 is detected as Add Attribute Scroll")
|
|
_assert(ItemAttrSystem.is_attr_scroll(71084), "71084 is detected as Change Attribute Scroll")
|
|
_assert(ItemAttrSystem.is_attr_scroll(70024), "70024 is detected as Blessing Marble")
|
|
_assert(not ItemAttrSystem.is_attr_scroll(27001), "Normal potion is not an attr scroll")
|
|
_assert(not ItemAttrSystem.is_attr_scroll(10), "Sword is not an attr scroll")
|
|
|
|
func test_can_have_attributes() -> void:
|
|
print("\n--- Test 2: Target Equipment Compatibility (Weapon/Armor vs Arrow) ---")
|
|
var weapon := {"type": ItemAttrSystem.ITEM_TYPE_WEAPON, "sub_type": 0} # 剑
|
|
var armor := {"type": ItemAttrSystem.ITEM_TYPE_ARMOR, "sub_type": 0} # 衣服
|
|
var arrow := {"type": ItemAttrSystem.ITEM_TYPE_WEAPON, "sub_type": 5} # 箭矢
|
|
var potion := {"type": 3, "sub_type": 0} # 消耗品
|
|
|
|
_assert(ItemAttrSystem.can_have_attributes(weapon), "Weapon can have attributes")
|
|
_assert(ItemAttrSystem.can_have_attributes(armor), "Armor can have attributes")
|
|
_assert(not ItemAttrSystem.can_have_attributes(arrow), "Arrow (WEAPON_ARROW) cannot have attributes")
|
|
_assert(not ItemAttrSystem.can_have_attributes(potion), "Potion cannot have attributes")
|
|
|
|
func test_add_attribute_parity() -> void:
|
|
print("\n--- Test 3: 40250 Add Attribute Progression (1..4 Attrs & Cap) ---")
|
|
var weapon_proto := {"type": ItemAttrSystem.ITEM_TYPE_WEAPON, "sub_type": 0}
|
|
var sword_item := {"vnum": 10, "attrs": []}
|
|
|
|
# 1st attribute
|
|
var res1 := ItemAttrSystem.apply_add_attribute(sword_item, weapon_proto, true)
|
|
_assert(res1.ok and res1.code == "SUCCESS", "1st attribute added successfully")
|
|
_assert(sword_item.attrs.size() == 1, "Sword has 1 attribute now")
|
|
|
|
# 2nd attribute
|
|
var res2 := ItemAttrSystem.apply_add_attribute(sword_item, weapon_proto, true)
|
|
_assert(res2.ok and res2.code == "SUCCESS", "2nd attribute added successfully")
|
|
_assert(sword_item.attrs.size() == 2, "Sword has 2 attributes now")
|
|
_assert(sword_item.attrs[0].type != sword_item.attrs[1].type, "1st and 2nd attribute types are distinct (no duplicate)")
|
|
|
|
# 3rd attribute
|
|
var res3 := ItemAttrSystem.apply_add_attribute(sword_item, weapon_proto, true)
|
|
_assert(res3.ok and res3.code == "SUCCESS", "3rd attribute added successfully")
|
|
_assert(sword_item.attrs.size() == 3, "Sword has 3 attributes now")
|
|
|
|
# 4th attribute
|
|
var res4 := ItemAttrSystem.apply_add_attribute(sword_item, weapon_proto, true)
|
|
_assert(res4.ok and res4.code == "SUCCESS", "4th attribute added successfully")
|
|
_assert(sword_item.attrs.size() == 4, "Sword has 4 attributes now")
|
|
|
|
# 5th attempt with normal scroll 71085 MUST fail (40250 char_item.cpp:4665 item2->GetAttributeCount() < 4)
|
|
var res5 := ItemAttrSystem.apply_add_attribute(sword_item, weapon_proto, true)
|
|
_assert(not res5.ok and res5.code == "MAX_ATTR_REACHED", "Normal scroll cannot add 5th attribute (MAX_ATTR_REACHED)")
|
|
_assert(sword_item.attrs.size() == 4, "Sword remains at 4 attributes")
|
|
|
|
func test_change_attribute_parity() -> void:
|
|
print("\n--- Test 4: 40250 Change Attribute (Reroll All Attributes) ---")
|
|
var weapon_proto := {"type": ItemAttrSystem.ITEM_TYPE_WEAPON, "sub_type": 0}
|
|
var empty_item := {"vnum": 10, "attrs": []}
|
|
|
|
# Fails on item with 0 attributes (40250 char_item.cpp:4560 item2->GetAttributeCount() == 0)
|
|
var res_empty := ItemAttrSystem.apply_change_attribute(empty_item, weapon_proto)
|
|
_assert(not res_empty.ok and res_empty.code == "NO_ATTRIBUTES", "Change attribute fails when item has 0 attributes")
|
|
|
|
var geared_item := {
|
|
"vnum": 10,
|
|
"attrs": [
|
|
{"type": 1, "value": 500},
|
|
{"type": 15, "value": 10},
|
|
{"type": 16, "value": 10}
|
|
]
|
|
}
|
|
var res_reroll := ItemAttrSystem.apply_change_attribute(geared_item, weapon_proto)
|
|
_assert(res_reroll.ok and res_reroll.code == "SUCCESS", "Change attribute rerolls successfully")
|
|
_assert(geared_item.attrs.size() == 3, "Item still has exactly 3 attributes after reroll")
|
|
|
|
# Verify all rerolled attribute types are unique
|
|
var types := {}
|
|
var has_duplicate := false
|
|
for a in geared_item.attrs:
|
|
if types.has(a.type):
|
|
has_duplicate = true
|
|
types[a.type] = true
|
|
_assert(not has_duplicate, "All rerolled attribute types are distinct")
|
|
|
|
func test_blessing_marble_parity() -> void:
|
|
print("\n--- Test 5: 40250 Blessing Marble 70024 (5th Attribute Lock) ---")
|
|
var weapon_proto := {"type": ItemAttrSystem.ITEM_TYPE_WEAPON, "sub_type": 0}
|
|
|
|
# Item with 3 attributes -> marble fails (40250 char_item.cpp:4738 item2->GetAttributeCount() == 4)
|
|
var item_3_attrs := {
|
|
"vnum": 10,
|
|
"attrs": [
|
|
{"type": 1, "value": 500},
|
|
{"type": 2, "value": 200},
|
|
{"type": 3, "value": 4}
|
|
]
|
|
}
|
|
var res_marble_fail := ItemAttrSystem.apply_blessing_marble(item_3_attrs, weapon_proto, true)
|
|
_assert(not res_marble_fail.ok and res_marble_fail.code == "NEED_FOUR_ATTRS", "Blessing marble fails on item with less than 4 attributes")
|
|
|
|
# Item with 4 attributes -> marble succeeds and adds 5th
|
|
item_3_attrs.attrs.append({"type": 4, "value": 4})
|
|
var res_marble_ok := ItemAttrSystem.apply_blessing_marble(item_3_attrs, weapon_proto, true)
|
|
_assert(res_marble_ok.ok and res_marble_ok.code == "SUCCESS", "Blessing marble successfully unlocks 5th attribute")
|
|
_assert(item_3_attrs.attrs.size() == 5, "Item now has 5 attributes")
|
|
|
|
# Marble fails on item that already has 5 attributes
|
|
var res_marble_cap := ItemAttrSystem.apply_blessing_marble(item_3_attrs, weapon_proto, true)
|
|
_assert(not res_marble_cap.ok and res_marble_cap.code == "ALREADY_FIVE_ATTRS", "Blessing marble cannot exceed 5 attributes")
|