- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
119 lines
6.2 KiB
GDScript
119 lines
6.2 KiB
GDScript
# test_metin_socket_parity.gd —— 验证方向 2:魔石镶嵌、碎石残渣与打孔清除 40250 1:1 对齐
|
|
extends SceneTree
|
|
|
|
const MetinSocketSystem = preload("res://metin_socket_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_metin_socket_parity (Direction 2: Metin Socketing & Cleaning 1:1) ===")
|
|
test_metin_vnum_identification()
|
|
test_equipment_target_rules()
|
|
test_socket_metin_success_and_broken()
|
|
test_duplicate_metin_prevention()
|
|
test_clean_broken_sockets_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_metin_vnum_identification() -> void:
|
|
print("\n--- Test 1: Metin Stone & Scroll Identification ---")
|
|
_assert(MetinSocketSystem.is_metin_stone(28430), "28430 (Stone of Penetration+4) is a Metin Stone")
|
|
_assert(MetinSocketSystem.is_metin_stone(28442), "28442 (Stone of Defense+4) is a Metin Stone")
|
|
_assert(MetinSocketSystem.is_metin_stone(28033), "28033 (Stone of Warrior+0) is a Metin Stone")
|
|
_assert(not MetinSocketSystem.is_metin_stone(10), "Sword 10 is not a Metin Stone")
|
|
_assert(not MetinSocketSystem.is_metin_stone(28960), "Broken stone 28960 is not a Metin stone to socket")
|
|
_assert(MetinSocketSystem.is_clean_socket_scroll(71006), "71006 is Clean Socket Scroll")
|
|
_assert(not MetinSocketSystem.is_clean_socket_scroll(71085), "71085 is not clean socket scroll")
|
|
|
|
func test_equipment_target_rules() -> void:
|
|
print("\n--- Test 2: Target Equipment Restrictions (Body Armor / Weapon vs Shield/Arrow) ---")
|
|
var body_armor := {"type": MetinSocketSystem.ITEM_TYPE_ARMOR, "sub_type": 0} # ARMOR_BODY
|
|
var helmet := {"type": MetinSocketSystem.ITEM_TYPE_ARMOR, "sub_type": 1} # ARMOR_HEAD
|
|
var weapon := {"type": MetinSocketSystem.ITEM_TYPE_WEAPON, "sub_type": 0} # WEAPON_SWORD
|
|
var arrow := {"type": MetinSocketSystem.ITEM_TYPE_WEAPON, "sub_type": 5} # WEAPON_ARROW
|
|
var stone_proto := {"type": MetinSocketSystem.ITEM_TYPE_METIN}
|
|
|
|
var r_body := MetinSocketSystem.can_attach_metin(28442, stone_proto, body_armor)
|
|
_assert(r_body.ok, "Can attach metin stone to body armor")
|
|
|
|
var r_helmet := MetinSocketSystem.can_attach_metin(28442, stone_proto, helmet)
|
|
_assert(not r_helmet.ok and r_helmet.code == "ARMOR_NOT_BODY", "Cannot attach metin stone to helmet (must be body)")
|
|
|
|
var r_weapon := MetinSocketSystem.can_attach_metin(28430, stone_proto, weapon)
|
|
_assert(r_weapon.ok, "Can attach metin stone to weapon")
|
|
|
|
var r_arrow := MetinSocketSystem.can_attach_metin(28430, stone_proto, arrow)
|
|
_assert(not r_arrow.ok and r_arrow.code == "WEAPON_ARROW", "Cannot attach metin stone to arrows")
|
|
|
|
func test_socket_metin_success_and_broken() -> void:
|
|
print("\n--- Test 3: 40250 Socketing Success vs Broken Fragment (28960) ---")
|
|
var weapon_proto := {"type": MetinSocketSystem.ITEM_TYPE_WEAPON, "sub_type": 0}
|
|
var stone_proto := {"type": MetinSocketSystem.ITEM_TYPE_METIN}
|
|
|
|
# Sword with 3 open sockets: [1, 1, 1]
|
|
var sword := {"vnum": 10, "sockets": [1, 1, 1]}
|
|
|
|
# 1st stone: force success (28430 Penetration+4)
|
|
var res1 := MetinSocketSystem.socket_metin_stone(28430, stone_proto, sword, weapon_proto, true, false)
|
|
_assert(res1.ok and res1.code == "SUCCESS", "1st metin stone socketed successfully")
|
|
_assert(sword.sockets[0] == 28430, "Socket 0 contains 28430")
|
|
_assert(sword.sockets[1] == 1 and sword.sockets[2] == 1, "Socket 1 and 2 remain open (1)")
|
|
|
|
# 2nd stone: force failure -> creates broken metin stone 28960
|
|
var res2 := MetinSocketSystem.socket_metin_stone(28431, stone_proto, sword, weapon_proto, false, true)
|
|
_assert(not res2.ok and res2.code == "BROKEN_METIN", "2nd metin stone broke on failure")
|
|
_assert(sword.sockets[1] == MetinSocketSystem.ITEM_BROKEN_METIN_VNUM, "Socket 1 is now blocked by broken fragment 28960")
|
|
_assert(sword.sockets[2] == 1, "Socket 2 remains open (1)")
|
|
|
|
# 3rd stone: force success (28437 Monster+4) into remaining slot 2
|
|
var res3 := MetinSocketSystem.socket_metin_stone(28437, stone_proto, sword, weapon_proto, true, false)
|
|
_assert(res3.ok and res3.code == "SUCCESS", "3rd metin stone socketed into remaining slot 2")
|
|
_assert(sword.sockets[2] == 28437, "Socket 2 contains 28437")
|
|
|
|
# 4th stone: no available sockets
|
|
var res4 := MetinSocketSystem.socket_metin_stone(28432, stone_proto, sword, weapon_proto, true, false)
|
|
_assert(not res4.ok and res4.code == "NO_AVAILABLE_SOCKET", "No available sockets rejected properly")
|
|
|
|
func test_duplicate_metin_prevention() -> void:
|
|
print("\n--- Test 4: Prevention of Duplicate Stone Groups ---")
|
|
var weapon_proto := {"type": MetinSocketSystem.ITEM_TYPE_WEAPON, "sub_type": 0}
|
|
var stone_proto := {"type": MetinSocketSystem.ITEM_TYPE_METIN}
|
|
var sword := {"vnum": 10, "sockets": [28430, 1, 1]} # Already has Penetration+4 (28430)
|
|
|
|
# Trying to socket Penetration+3 (28330) or another Penetration stone
|
|
var res_dup := MetinSocketSystem.socket_metin_stone(28330, stone_proto, sword, weapon_proto, true, false)
|
|
_assert(not res_dup.ok and res_dup.code == "DUPLICATE_METIN", "Duplicate metin group (Penetration) prevented from socketing")
|
|
|
|
func test_clean_broken_sockets_parity() -> void:
|
|
print("\n--- Test 5: 40250 USE_CLEAN_SOCKET (71006) Fragment Cleaning ---")
|
|
# Equipment has: [28430 (valid), 28960 (broken), 28437 (valid)]
|
|
var item_with_broken := {
|
|
"vnum": 10,
|
|
"sockets": [28430, MetinSocketSystem.ITEM_BROKEN_METIN_VNUM, 28437]
|
|
}
|
|
|
|
var res_clean := MetinSocketSystem.clean_broken_sockets(item_with_broken)
|
|
_assert(res_clean.ok and res_clean.code == "SUCCESS", "Clean scroll removed broken stone fragments")
|
|
_assert(item_with_broken.sockets[0] == 28430, "Socket 0 still has valid stone 28430")
|
|
_assert(item_with_broken.sockets[1] == 28437, "Valid stone 28437 compacted forward to socket 1")
|
|
_assert(item_with_broken.sockets[2] == 1, "Socket 2 restored back to open state 1")
|
|
|
|
# Using clean scroll on an item with no broken stones must fail (40250 char_item.cpp:4523)
|
|
var res_clean_no_op := MetinSocketSystem.clean_broken_sockets(item_with_broken)
|
|
_assert(not res_clean_no_op.ok and res_clean_no_op.code == "NO_BROKEN_STONE", "Clean scroll rejected on item without broken stones")
|