- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
75 lines
3.0 KiB
GDScript
75 lines
3.0 KiB
GDScript
# test_mob_drop_scatter_parity.gd —— 怪物死亡爆装与辐射散射 1:1 回归测试套件
|
|
# 运行方式:
|
|
# /opt/homebrew/bin/godot --headless --path metin2-client/project --script test_mob_drop_scatter_parity.gd
|
|
extends SceneTree
|
|
|
|
const MobDropScatter = preload("res://mob_drop_scatter.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_mob_drop_scatter_parity (Direction 2: Drop Scatter & Ownership 1:1) ===")
|
|
_test_single_drop_offset()
|
|
_test_multi_drop_radial_scatter()
|
|
_test_ownership_protection_window()
|
|
_test_drop_sounds()
|
|
_finish()
|
|
|
|
func _test_single_drop_offset() -> void:
|
|
print("\n--- Test 1: Single Drop Offset [-1.4m, +1.4m] (char_battle.cpp) ---")
|
|
for seed_val in range(10):
|
|
var offset := MobDropScatter.calculate_scatter_offset(0, 1, seed_val)
|
|
_assert(offset.x >= -1.4 and offset.x <= 1.4, "Seed %d dx within [-1.4m, 1.4m]" % seed_val)
|
|
_assert(offset.z >= -1.4 and offset.z <= 1.4, "Seed %d dz within [-1.4m, 1.4m]" % seed_val)
|
|
_assert(offset.y == 0.0, "Y offset is 0.0 (ground plane)")
|
|
|
|
func _test_multi_drop_radial_scatter() -> void:
|
|
print("\n--- Test 2: Multi-Item Boss Drop Radial Scatter ---")
|
|
var center := Vector3(100.0, 10.0, 50.0)
|
|
var count := 6
|
|
var positions: Array[Vector3] = []
|
|
for i in range(count):
|
|
var pos := MobDropScatter.calculate_drop_position(center, i, count, 42)
|
|
positions.append(pos)
|
|
var dist := (pos - center).length()
|
|
_assert(dist >= 0.95 and dist <= 1.75, "Item %d scatter radius is %.2fm (expected 1.0~1.6m)" % [i, dist])
|
|
|
|
# Check that items do not overlap (pairwise distance > 0.5m)
|
|
for i in range(count):
|
|
for j in range(i + 1, count):
|
|
var d := (positions[i] - positions[j]).length()
|
|
_assert(d > 0.5, "Item %d and %d separated by %.2fm (no pile overlap)" % [i, j, d])
|
|
|
|
func _test_ownership_protection_window() -> void:
|
|
print("\n--- Test 3: 30-Second Ownership Protection Window ---")
|
|
var drop_time := 100.0
|
|
_assert(MobDropScatter.is_ownership_protected(drop_time, 105.0) == true, "Protected at 5 seconds")
|
|
_assert(MobDropScatter.remaining_ownership_time(drop_time, 105.0) == 25.0, "25 seconds remaining at 5s")
|
|
|
|
_assert(MobDropScatter.is_ownership_protected(drop_time, 129.5) == true, "Protected at 29.5 seconds")
|
|
_assert(MobDropScatter.is_ownership_protected(drop_time, 130.1) == false, "Expired after 30 seconds")
|
|
_assert(MobDropScatter.remaining_ownership_time(drop_time, 135.0) == 0.0, "Remaining time clamped to 0 after expiry")
|
|
|
|
func _test_drop_sounds() -> void:
|
|
print("\n--- Test 4: Drop Sound Assignment ---")
|
|
_assert(MobDropScatter.get_drop_sound(true) == "sound/ui/money.wav", "Gold drop uses money.wav")
|
|
_assert(MobDropScatter.get_drop_sound(false) == "sound/ui/drop.wav", "Item drop uses drop.wav")
|
|
|
|
func _finish() -> void:
|
|
print("\n=== SUMMARY: %d passed, %d failed ===" % [_passed, _failed])
|
|
if _failed == 0:
|
|
print("ALL TESTS PASSED!\n")
|
|
quit(0)
|
|
else:
|
|
printerr("SOME TESTS FAILED!\n")
|
|
quit(1)
|