- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
157 lines
6.4 KiB
GDScript
157 lines
6.4 KiB
GDScript
# test_mercenary_companion_parity.gd —— 40250 单人单机假人/陪练佣兵助战 1:1 回归测试套件
|
|
extends SceneTree
|
|
|
|
const MercenaryCompanionSystem = preload("res://mercenary_companion_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_mercenary_companion_parity (Direction 2: Mercenary Companion 1:1) ===")
|
|
_test_summon_and_stats()
|
|
_test_buff_and_healing()
|
|
_test_assist_combat_and_stances()
|
|
_test_teleport_and_movement()
|
|
_test_downed_and_revival()
|
|
_finish()
|
|
|
|
func _test_summon_and_stats() -> void:
|
|
print("\n--- Test 1: Summoning & Level Scaling ---")
|
|
var merc := MercenaryCompanionSystem.new()
|
|
|
|
var summon_sig := {"fired": false, "type": 0, "name": ""}
|
|
merc.mercenary_summoned.connect(func(t: int, n: String):
|
|
summon_sig["fired"] = true
|
|
summon_sig["type"] = t
|
|
summon_sig["name"] = n
|
|
)
|
|
|
|
# Level 50 Shaman
|
|
var res = merc.summon_mercenary(MercenaryCompanionSystem.MercenaryType.SHAMAN_HEALER, Vector3(10, 0, 10), 50)
|
|
_assert(res["ok"] == true, "Shaman summoned")
|
|
_assert(summon_sig["fired"] == true and summon_sig["type"] == 1, "mercenary_summoned signal emitted")
|
|
_assert(merc.is_active == true and merc.is_alive == true, "Merc is active and alive")
|
|
_assert(merc.level == 50, "Mercenary level is 50")
|
|
# Level 50 growth: 1 + 49 * 0.05 = 3.45x base_hp (8000 * 3.45 = 27600)
|
|
_assert(merc.max_hp == int(8000 * 3.45), "HP properly scaled by level")
|
|
|
|
# Dismiss
|
|
var dis_sig := {"fired": false}
|
|
merc.mercenary_dismissed.connect(func(): dis_sig["fired"] = true)
|
|
var dis_res = merc.dismiss_mercenary()
|
|
_assert(dis_res["ok"] == true, "Merc dismissed")
|
|
_assert(dis_sig["fired"] == true, "mercenary_dismissed signal emitted")
|
|
_assert(merc.is_active == false, "Merc is inactive")
|
|
|
|
func _test_buff_and_healing() -> void:
|
|
print("\n--- Test 2: Shaman Buffs & Smart Emergency Healing ---")
|
|
var merc := MercenaryCompanionSystem.new()
|
|
merc.summon_mercenary(MercenaryCompanionSystem.MercenaryType.SHAMAN_HEALER, Vector3.ZERO, 30)
|
|
|
|
var buffs_received := []
|
|
merc.buff_applied_to_player.connect(func(bname: String, dur: float, val: float):
|
|
buffs_received.append({"name": bname, "duration": dur, "value": val})
|
|
)
|
|
|
|
var heal_received := {"fired": false, "amount": 0}
|
|
merc.heal_applied_to_player.connect(func(amt: int):
|
|
heal_received["fired"] = true
|
|
heal_received["amount"] = amt
|
|
)
|
|
|
|
# Update when player is healthy (10000 / 10000)
|
|
var up1 = merc.update_mercenary(0.1, Vector3.ZERO, 10000, 10000)
|
|
_assert(buffs_received.size() == 2, "Shaman applied 2 buffs (Blessing & Dragon's Aid)")
|
|
_assert(heal_received["fired"] == false, "No heal cast when player is healthy")
|
|
|
|
# Player HP drops to 40% (4000 / 10000 < 60%)
|
|
var up2 = merc.update_mercenary(0.1, Vector3.ZERO, 4000, 10000)
|
|
_assert(up2["status"] == "cast_heal", "Shaman triggered emergency heal")
|
|
_assert(heal_received["fired"] == true, "heal_applied_to_player signal fired")
|
|
_assert(heal_received["amount"] == 3500, "Healed 35% of max HP (3500)")
|
|
|
|
# Second hit while heal is on cooldown should not retrigger heal
|
|
heal_received["fired"] = false
|
|
var up3 = merc.update_mercenary(0.1, Vector3.ZERO, 3000, 10000)
|
|
_assert(heal_received["fired"] == false, "Heal respects cooldown")
|
|
|
|
func _test_assist_combat_and_stances() -> void:
|
|
print("\n--- Test 3: Stances & Warrior Assist Combat ---")
|
|
var merc := MercenaryCompanionSystem.new()
|
|
merc.summon_mercenary(MercenaryCompanionSystem.MercenaryType.WARRIOR_TANK, Vector3.ZERO, 50)
|
|
|
|
# Check stance change
|
|
var st_res = merc.set_stance(MercenaryCompanionSystem.Stance.FOLLOW)
|
|
_assert(st_res["ok"] == true and merc.current_stance == MercenaryCompanionSystem.Stance.FOLLOW, "Stance set to FOLLOW")
|
|
|
|
# Change to ASSIST
|
|
merc.set_stance(MercenaryCompanionSystem.Stance.ASSIST)
|
|
_assert(merc.current_stance == MercenaryCompanionSystem.Stance.ASSIST, "Stance set to ASSIST")
|
|
|
|
var atk_sig := {"fired": false, "tgt": "", "dmg": 0}
|
|
merc.mercenary_attacked.connect(func(tgt: String, dmg: int):
|
|
atk_sig["fired"] = true
|
|
atk_sig["tgt"] = tgt
|
|
atk_sig["dmg"] = dmg
|
|
)
|
|
|
|
# Target at (1.5, 0, 0) within melee range
|
|
merc.pos = Vector3.ZERO
|
|
var act = merc.update_mercenary(0.1, Vector3.ZERO, 10000, 10000, "mob_101", Vector3(1.5, 0, 0))
|
|
_assert(act["status"] == "attacked_target", "Warrior attacked target")
|
|
_assert(atk_sig["fired"] == true and atk_sig["tgt"] == "mob_101", "mercenary_attacked signal fired")
|
|
_assert(atk_sig["dmg"] == merc.atk, "Damage matches warrior's ATK")
|
|
|
|
func _test_teleport_and_movement() -> void:
|
|
print("\n--- Test 4: Distance Teleport Catch-up ---")
|
|
var merc := MercenaryCompanionSystem.new()
|
|
merc.summon_mercenary(MercenaryCompanionSystem.MercenaryType.SHAMAN_HEALER, Vector3.ZERO, 10)
|
|
|
|
# Place mercenary 30 meters away (> 25m TELEPORT_DISTANCE)
|
|
merc.pos = Vector3(30, 0, 0)
|
|
var tp_act = merc.update_mercenary(0.1, Vector3.ZERO, 5000, 5000)
|
|
_assert(tp_act["status"] == "teleported_to_player", "Mercenary teleported back to player")
|
|
_assert(merc.pos.distance_to(Vector3.ZERO) < 3.0, "Position is now close to player")
|
|
|
|
func _test_downed_and_revival() -> void:
|
|
print("\n--- Test 5: Downed State & Revival ---")
|
|
var merc := MercenaryCompanionSystem.new()
|
|
merc.summon_mercenary(MercenaryCompanionSystem.MercenaryType.WARRIOR_TANK, Vector3.ZERO, 10)
|
|
|
|
var down_sig := {"fired": false}
|
|
var rev_sig := {"fired": false}
|
|
merc.mercenary_downed.connect(func(): down_sig["fired"] = true)
|
|
merc.mercenary_revived.connect(func(): rev_sig["fired"] = true)
|
|
|
|
# Take fatal damage
|
|
var hit = merc.receive_damage(999999)
|
|
_assert(hit["ok"] == true and hit["downed"] == true, "Mercenary downed")
|
|
_assert(down_sig["fired"] == true, "mercenary_downed signal fired")
|
|
_assert(merc.is_alive == false, "is_alive is false")
|
|
|
|
# Instant revive
|
|
var rev_res = merc.revive(true)
|
|
_assert(rev_res["ok"] == true, "Instant revive succeeded")
|
|
_assert(rev_sig["fired"] == true, "mercenary_revived signal fired")
|
|
_assert(merc.is_alive == true and merc.current_hp == merc.max_hp, "HP restored to 100% on instant revive")
|
|
|
|
func _finish() -> void:
|
|
print("\n==========================================")
|
|
print("Mercenary Companion Parity Tests Finished:")
|
|
print("Passed: %d, Failed: %d" % [_passed, _failed])
|
|
print("==========================================")
|
|
if _failed > 0:
|
|
printerr("FAILED: Some tests did not pass.")
|
|
quit(1)
|
|
else:
|
|
print("SUCCESS: All Mercenary Companion parity tests passed!")
|
|
quit(0)
|