- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
98 lines
4.3 KiB
GDScript
98 lines
4.3 KiB
GDScript
# test_mob_combat_reaction_parity.gd —— 验证方向 2:怪物主动仇恨视野、追击脱战与连击击退硬直 40250 1:1 对齐
|
|
extends SceneTree
|
|
|
|
const MobCombatReactionSystem = preload("res://mob_combat_reaction_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_mob_combat_reaction_parity (Direction 2: Mob Aggro & Knockback 1:1) ===")
|
|
test_aggro_radius_detection()
|
|
test_leash_distance_and_reset()
|
|
test_combo_reaction_steps()
|
|
test_knockback_direction_vector()
|
|
|
|
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_aggro_radius_detection() -> void:
|
|
print("\n--- Test 1: Aggro Radius (18m) Detection ---")
|
|
var mob_pos := Vector3(0, 0, 0)
|
|
var player_in_range := Vector3(10, 0, 10) # distance = ~14.14m <= 18m
|
|
var player_out_range := Vector3(20, 0, 20) # distance = ~28.28m > 18m
|
|
|
|
# Active aggressive mob
|
|
_assert(MobCombatReactionSystem.check_aggro(mob_pos, player_in_range, true, 18.0),
|
|
"Aggressive mob detects player within 18m")
|
|
_assert(not MobCombatReactionSystem.check_aggro(mob_pos, player_out_range, true, 18.0),
|
|
"Aggressive mob ignores player outside 18m")
|
|
|
|
# Passive mob ignores player even close
|
|
_assert(not MobCombatReactionSystem.check_aggro(mob_pos, player_in_range, false, 18.0),
|
|
"Passive mob does not aggro even when close")
|
|
|
|
func test_leash_distance_and_reset() -> void:
|
|
print("\n--- Test 2: Leash Distance (40m) & Full HP Reset ---")
|
|
var spawn_pos := Vector3(100, 0, 100)
|
|
var mob_chasing := Vector3(120, 0, 120) # distance = 28.28m <= 40m
|
|
var mob_leashed := Vector3(150, 0, 150) # distance = 70.71m > 40m
|
|
|
|
_assert(not MobCombatReactionSystem.check_leash_exceeded(mob_chasing, spawn_pos, 40.0),
|
|
"Mob within 40m remains in chase")
|
|
_assert(MobCombatReactionSystem.check_leash_exceeded(mob_leashed, spawn_pos, 40.0),
|
|
"Mob exceeding 40m triggers leash exceeded")
|
|
|
|
# Leash reset
|
|
var damaged_mob := {"hp": 200, "max_hp": 2000, "target_vid": 1, "state": MobCombatReactionSystem.MobAIState.CHASE}
|
|
var reset_res := MobCombatReactionSystem.perform_leash_reset(damaged_mob, spawn_pos)
|
|
_assert(reset_res.leashed, "Leash reset executed")
|
|
_assert(damaged_mob.hp == 2000, "HP fully restored to 2000")
|
|
_assert(damaged_mob.target_vid == 0, "Target cleared")
|
|
_assert(damaged_mob.state == MobCombatReactionSystem.MobAIState.RETREAT, "State set to RETREAT")
|
|
|
|
func test_combo_reaction_steps() -> void:
|
|
print("\n--- Test 3: 40250 Combo Step Reactions (1-2 Damage vs 3-4 Knockdown) ---")
|
|
var mob_pos := Vector3(0, 0, 0)
|
|
|
|
# Step 1: Hit 1 -> normal damage stun
|
|
var hit1 := MobCombatReactionSystem.calculate_combo_reaction(1, mob_pos, 0.0)
|
|
_assert(hit1.reaction_type == "DAMAGE_STUN" and not hit1.is_knockback, "Combo 1 is DAMAGE_STUN (no knockback)")
|
|
_assert(hit1.anim == "damage", "Plays damage animation")
|
|
|
|
# Step 2: Hit 2 -> normal damage stun
|
|
var hit2 := MobCombatReactionSystem.calculate_combo_reaction(2, mob_pos, 0.0)
|
|
_assert(hit2.reaction_type == "DAMAGE_STUN" and not hit2.is_knockback, "Combo 2 is DAMAGE_STUN")
|
|
|
|
# Step 3: Hit 3 -> heavy knockback & knockdown
|
|
var hit3 := MobCombatReactionSystem.calculate_combo_reaction(3, mob_pos, 0.0)
|
|
_assert(hit3.reaction_type == "KNOCKBACK_FLYING" and hit3.is_knockback, "Combo 3 triggers KNOCKBACK_FLYING")
|
|
_assert(hit3.anim == "knockdown" and hit3.standup_anim == "standup", "Triggers knockdown and standup")
|
|
|
|
# Step 4: Hit 4 -> heavy knockback & knockdown
|
|
var hit4 := MobCombatReactionSystem.calculate_combo_reaction(4, mob_pos, 0.0)
|
|
_assert(hit4.reaction_type == "KNOCKBACK_FLYING" and hit4.is_knockback, "Combo 4 triggers KNOCKBACK_FLYING")
|
|
|
|
func test_knockback_direction_vector() -> void:
|
|
print("\n--- Test 4: Knockback Push Vector Along Attack Heading ---")
|
|
var mob_pos := Vector3(10, 0, 10)
|
|
|
|
# Attack heading = 0 radians (North / +Z)
|
|
var hit_north := MobCombatReactionSystem.calculate_combo_reaction(3, mob_pos, 0.0)
|
|
var offset_z: float = hit_north.push_offset.z
|
|
_assert(is_equal_approx(offset_z, 1.5), "Knockback pushed 1.5m along attack heading")
|
|
_assert(is_equal_approx(hit_north.target_pos.z, 11.5), "Target position updated to 11.5m")
|