Files
mtgodot-poc/project/test_auto_combat_bot_parity.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

169 lines
5.6 KiB
GDScript

# test_auto_combat_bot_parity.gd —— 40250 自动战斗猎杀挂机助手 1:1 回归测试套件
extends SceneTree
const AutoCombatBotSystem = preload("res://auto_combat_bot_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_auto_combat_bot_parity (Direction 2: Auto Combat Bot 1:1) ===")
_test_bot_toggle()
_test_auto_potion()
_test_auto_target_and_combat()
_test_auto_looting()
_finish()
func _test_bot_toggle() -> void:
print("\n--- Test 1: Bot Toggle State ---")
var bot := AutoCombatBotSystem.new()
var toggle_sig := {"fired": false, "enabled": false}
bot.bot_toggled.connect(func(en: bool):
toggle_sig["fired"] = true
toggle_sig["enabled"] = en
)
var res1 = bot.toggle_bot(true)
_assert(res1["enabled"] == true and bot.is_enabled == true, "Bot enabled")
_assert(toggle_sig["fired"] == true and toggle_sig["enabled"] == true, "bot_toggled emitted true")
bot.toggle_bot(false)
_assert(bot.is_enabled == false, "Bot disabled")
func _test_auto_potion() -> void:
print("\n--- Test 2: Smart Auto Potion (HP/SP Threshold) ---")
var bot := AutoCombatBotSystem.new()
bot.toggle_bot(true)
var player := {
"hp": 400,
"max_hp": 1000, # 40% < 50% threshold
"sp": 800,
"max_sp": 1000, # 80% > 40% threshold (no SP potion)
"pos": Vector3.ZERO
}
var inv: Array = [
{"vnum": 27003, "name": "红色药水(特大)", "count": 2},
{"vnum": 27006, "name": "蓝色药水(特大)", "count": 2}
]
var pot_events := []
bot.potion_used.connect(func(v: int, pt: String):
pot_events.append({"vnum": v, "type": pt})
)
# Update tick should trigger HP potion only
bot.update_bot(0.1, player, inv, [], [])
_assert(player["hp"] == 1000, "HP restored (capped at max 1000)")
_assert(inv[0]["count"] == 1, "Red potion consumed (2 -> 1)")
_assert(pot_events.size() == 1 and pot_events[0]["type"] == "hp", "potion_used emitted for HP")
# Now drop SP to 20% (< 40%) and reset potion cooldown
bot.potion_cooldown = 0.0
player["sp"] = 200
bot.update_bot(0.1, player, inv, [], [])
_assert(player["sp"] == 1000, "SP restored to max 1000")
_assert(inv[1]["count"] == 1, "Blue potion consumed (2 -> 1)")
_assert(pot_events.size() == 2 and pot_events[1]["type"] == "sp", "potion_used emitted for SP")
func _test_auto_target_and_combat() -> void:
print("\n--- Test 3: Auto Target, Move & Skill/Attack Execution ---")
var bot := AutoCombatBotSystem.new()
bot.toggle_bot(true)
bot.assigned_skills = [1] # Assign Triple Slash
var player := {
"atk": 500,
"hp": 1000,
"max_hp": 1000,
"sp": 1000,
"max_sp": 1000,
"pos": Vector3.ZERO
}
var mob := {
"id": "mob_99",
"hp": 2000,
"alive": true,
"pos": Vector3(8.0, 0.0, 0.0) # 8m away (> 3.0m attack range)
}
var lock_sig := {"fired": false, "id": ""}
bot.target_locked.connect(func(tid: String):
lock_sig["fired"] = true
lock_sig["id"] = tid
)
# 1. Step: Locks target and moves closer
var step1 = bot.update_bot(0.5, player, [], [mob], [])
_assert(lock_sig["fired"] == true and lock_sig["id"] == "mob_99", "target_locked emitted")
_assert(step1["status"] == "moving_to_target", "Bot moving toward target")
_assert(player["pos"].x > 0.0, "Player moved along X axis")
# Move player close (2.0m <= 3.0m attack range)
player["pos"] = Vector3(6.5, 0.0, 0.0) # 1.5m distance
var sk_sig := {"fired": false}
bot.auto_skill_fired.connect(func(_sk: int, _t: String): sk_sig["fired"] = true)
# 2. Step: Fires skill (Triple Slash, 2.0x dmg = 1000)
var step2 = bot.update_bot(0.1, player, [], [mob], [])
_assert(step2["status"] == "skill_attack", "Executed skill attack")
_assert(step2["damage"] == 1000, "Dealt 1000 skill damage (500 * 2)")
_assert(mob["hp"] == 1000, "Mob HP reduced 2000 -> 1000")
_assert(sk_sig["fired"] == true, "auto_skill_fired emitted")
# 3. Step: Skill is now on cooldown, performs basic attack (500 dmg)
var step3 = bot.update_bot(0.1, player, [], [mob], [])
_assert(step3["status"] == "basic_attack", "Executed basic attack on cooldown")
_assert(mob["hp"] == 500, "Mob HP reduced 1000 -> 500")
func _test_auto_looting() -> void:
print("\n--- Test 4: Auto Looting Within 8.0m ---")
var bot := AutoCombatBotSystem.new()
bot.toggle_bot(true)
var player := {
"hp": 1000,
"max_hp": 1000,
"sp": 1000,
"max_sp": 1000,
"pos": Vector3.ZERO
}
var inv: Array = [null, null]
var drops: Array = [
{"id": 1, "vnum": 71003, "name": "祝福卷轴", "count": 1, "pos": Vector3(3.0, 0.0, 0.0), "looted": false},
{"id": 2, "vnum": 50513, "name": "灵魂之石", "count": 1, "pos": Vector3(20.0, 0.0, 0.0), "looted": false} # > 8m
]
var loot_sig := {"fired": false, "name": ""}
bot.auto_loot_collected.connect(func(n: String, _s: int):
loot_sig["fired"] = true
loot_sig["name"] = n
)
bot.update_bot(0.1, player, inv, [], drops)
_assert(drops[0]["looted"] == true, "Drop 1 (3m) was looted")
_assert(drops[1]["looted"] == false, "Drop 2 (20m) out of range, not looted")
_assert(inv[0]["vnum"] == 71003, "Blessing Scroll placed into slot 0")
_assert(loot_sig["fired"] == true and loot_sig["name"] == "祝福卷轴", "auto_loot_collected emitted")
func _finish() -> void:
print("\n==========================================")
print("Auto Combat Bot 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 Auto Combat Bot parity tests passed!")
quit(0)