- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
169 lines
5.6 KiB
GDScript
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)
|