- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
117 lines
4.2 KiB
GDScript
117 lines
4.2 KiB
GDScript
# test_equipment_inspect_parity.gd —— 40250 装备透视检视与 GM 监察系统回归测试
|
|
extends SceneTree
|
|
|
|
const SystemClass = preload("res://equipment_inspect_system.gd")
|
|
|
|
var _passed: int = 0
|
|
var _failed: int = 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_equipment_inspect_parity (Batch 28 Direction 2) ===")
|
|
_test_open_and_load_slots()
|
|
_test_privacy_and_gm_bypass()
|
|
_test_slot_tooltip_generation()
|
|
_test_close_lifecycle()
|
|
|
|
print("\n=== Result: %d Passed, %d Failed ===" % [_passed, _failed])
|
|
if _failed == 0:
|
|
print("ALL TESTS PASSED!\n")
|
|
else:
|
|
printerr("SOME TESTS FAILED!\n")
|
|
quit(0 if _failed == 0 else 1)
|
|
|
|
func _test_open_and_load_slots() -> void:
|
|
print("\n--- Test 1: Open Dialog & Equipment Slots Loading ---")
|
|
var ins = SystemClass.new()
|
|
|
|
var dummy_equips = {
|
|
SystemClass.WEAR_WEAPON: {
|
|
"vnum": 189,
|
|
"name": "毒剑+9 (Poison Sword+9)",
|
|
"sockets": [28430, 28431, 28432],
|
|
"attributes": [{ "type": 72, "value": 42 }]
|
|
},
|
|
SystemClass.WEAR_BODY: {
|
|
"vnum": 11299,
|
|
"name": "黑铁战甲+9 (Black Steel Armor+9)",
|
|
"sockets": [28440, 28441, 0],
|
|
"attributes": [{ "type": 1, "value": 2000 }]
|
|
},
|
|
SystemClass.WEAR_COSTUME_BODY: {
|
|
"vnum": 41001,
|
|
"name": "沙漠武士服 (Desert Warrior Body)",
|
|
"attributes": [{ "type": 1, "value": 1500 }]
|
|
}
|
|
}
|
|
|
|
var r = ins.open_dialog(1001, "TargetWarrior", dummy_equips, true, false)
|
|
_assert(r["success"] == true, "Opened inspect dialog for TargetWarrior")
|
|
_assert(ins.is_open == true, "Inspect dialog is open")
|
|
_assert(ins.inspected_vid == 1001, "Target VID is 1001")
|
|
|
|
# Verify Weapon Slot
|
|
var wpn = ins.get_slot_item(SystemClass.WEAR_WEAPON)
|
|
_assert(wpn["vnum"] == 189, "Weapon Vnum matches 189")
|
|
_assert(wpn["sockets"].size() == 3, "Weapon has 3 sockets")
|
|
|
|
# Verify Costume Body Slot
|
|
var cst = ins.get_slot_item(SystemClass.WEAR_COSTUME_BODY)
|
|
_assert(cst["vnum"] == 41001, "Costume body loaded")
|
|
|
|
func _test_privacy_and_gm_bypass() -> void:
|
|
print("\n--- Test 2: Privacy Shield & GM Privilege Bypass ---")
|
|
var ins = SystemClass.new()
|
|
var dummy_equips = { SystemClass.WEAR_WEAPON: { "vnum": 10, "name": "Sword+0" } }
|
|
|
|
# 1. Normal player inspecting private player -> REJECTED
|
|
var r_fail = ins.open_dialog(1002, "PrivateLord", dummy_equips, false, false)
|
|
_assert(r_fail["error_code"] == "INSPECT_FORBIDDEN", "Reject inspection if target hidden privacy")
|
|
_assert(ins.is_open == false, "Dialog remains closed")
|
|
|
|
# 2. GM inspecting private player -> BYPASS SUCCESS
|
|
var r_gm = ins.open_dialog(1002, "PrivateLord", dummy_equips, false, true)
|
|
_assert(r_gm["success"] == true, "GM successfully bypassed privacy shield")
|
|
_assert(ins.is_open == true, "Dialog opened for GM")
|
|
|
|
func _test_slot_tooltip_generation() -> void:
|
|
print("\n--- Test 3: Slot Tooltip Generation on Hover ---")
|
|
var ins = SystemClass.new()
|
|
var dummy_equips = {
|
|
SystemClass.WEAR_WEAPON: {
|
|
"vnum": 189,
|
|
"name": "Poison Sword+9",
|
|
"sockets": [28430, 28431, 0],
|
|
"attributes": [{ "type": 72, "value": 45 }]
|
|
}
|
|
}
|
|
ins.open_dialog(1001, "TargetWarrior", dummy_equips, true, false)
|
|
|
|
# Equipped weapon tooltip
|
|
var tip_wpn = ins.build_slot_tooltip(SystemClass.WEAR_WEAPON)
|
|
_assert(tip_wpn.contains("Poison Sword+9"), "Tooltip contains weapon name")
|
|
_assert(tip_wpn.contains("部位: 武器"), "Tooltip contains slot title")
|
|
_assert(tip_wpn.contains("属性 72 +45"), "Tooltip contains bonus attribute")
|
|
|
|
# Empty shield slot tooltip
|
|
var tip_shield = ins.build_slot_tooltip(SystemClass.WEAR_SHIELD)
|
|
_assert(tip_shield.contains("[盾牌] 未佩戴装备"), "Empty slot displays placeholder notice")
|
|
|
|
func _test_close_lifecycle() -> void:
|
|
print("\n--- Test 4: Close Dialog & Memory Cleanup ---")
|
|
var ins = SystemClass.new()
|
|
ins.open_dialog(1001, "Target", { SystemClass.WEAR_BODY: { "vnum": 1, "name": "Armor" } })
|
|
_assert(ins.is_open == true, "Dialog open")
|
|
|
|
ins.close_dialog()
|
|
_assert(ins.is_open == false, "Dialog closed")
|
|
_assert(ins.equipment_slots.is_empty(), "Slots wiped on close")
|
|
_assert(ins.get_slot_item(SystemClass.WEAR_BODY).is_empty(), "Cannot retrieve slot when closed")
|