- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
161 lines
6.4 KiB
GDScript
161 lines
6.4 KiB
GDScript
# test_horse_combat_parity.gd —— 40250 战马骑乘战斗与马术技能 1:1 纯净回归测试套件
|
|
extends SceneTree
|
|
|
|
const HorseCombatSystem = preload("res://horse_combat_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_horse_combat_parity (Direction 3: Mounted Combat & Horse Skills 1:1) ===")
|
|
_test_mounted_state_and_basic_attack()
|
|
_test_horse_skills_137_to_140()
|
|
_test_cooldowns_and_sp_validation()
|
|
_test_stamina_and_feeding()
|
|
_finish()
|
|
|
|
func _test_mounted_state_and_basic_attack() -> void:
|
|
print("\n--- Test 1: Mounted State & 5.0m Attack Sweep Range ---")
|
|
var hcs := HorseCombatSystem.new()
|
|
|
|
var targets: Array = [
|
|
{"id": 1, "distance": 2.5}, # In range
|
|
{"id": 2, "distance": 4.8}, # In range (<= 5.0m)
|
|
{"id": 3, "distance": 5.2} # Out of range (> 5.0m)
|
|
]
|
|
|
|
# 1. Not riding
|
|
var r1 = hcs.perform_mounted_basic_attack(500, targets)
|
|
_assert(r1["ok"] == false and r1["reason"] == "NOT_RIDING", "Attack blocked when not riding")
|
|
|
|
# 2. Beginner horse (Tier 1) cannot fight
|
|
hcs.set_mounted_state(true, HorseCombatSystem.HORSE_TIER_BEGINNER)
|
|
var r2 = hcs.perform_mounted_basic_attack(500, targets)
|
|
_assert(r2["ok"] == false and r2["reason"] == "HORSE_TIER_TOO_LOW", "Attack blocked on beginner horse")
|
|
|
|
# 3. Combat horse (Tier 2) can fight
|
|
hcs.set_mounted_state(true, HorseCombatSystem.HORSE_TIER_COMBAT)
|
|
var attack_data := {"fired": false, "targets": 0, "dmg": 0}
|
|
hcs.horse_attack_performed.connect(func(cnt: int, dmg: int):
|
|
attack_data["fired"] = true
|
|
attack_data["targets"] = cnt
|
|
attack_data["dmg"] = dmg
|
|
)
|
|
|
|
var r3 = hcs.perform_mounted_basic_attack(500, targets)
|
|
_assert(r3["ok"] == true, "Attack succeeds on combat horse")
|
|
_assert(r3["attack_range"] == 5.0, "Mounted attack range is 5.0m")
|
|
_assert(r3["hit_count"] == 2, "Hit 2 targets within 5.0m (2.5m and 4.8m)")
|
|
_assert(r3["damage_per_hit"] == 600, "1.2x damage multiplier (500 * 1.2 = 600)")
|
|
_assert(r3["total_damage"] == 1200, "Total damage is 1200 for 2 targets")
|
|
_assert(attack_data["fired"] == true and attack_data["targets"] == 2, "horse_attack_performed signal fired")
|
|
|
|
func _test_horse_skills_137_to_140() -> void:
|
|
print("\n--- Test 2: 4 Military Horse Skills (137-140) ---")
|
|
var hcs := HorseCombatSystem.new()
|
|
|
|
# Combat horse (Tier 2) cannot use skills
|
|
hcs.set_mounted_state(true, HorseCombatSystem.HORSE_TIER_COMBAT)
|
|
var r1 = hcs.use_horse_skill(137, 100, 1000)
|
|
_assert(r1["ok"] == false and r1["reason"] == "REQUIRES_MILITARY_HORSE", "Skills blocked on tier 2 horse")
|
|
|
|
# Switch to Military Horse (Tier 3)
|
|
hcs.set_mounted_state(true, HorseCombatSystem.HORSE_TIER_MILITARY)
|
|
|
|
var skill_signal := {"fired": false, "id": 0, "name": ""}
|
|
hcs.horse_skill_used.connect(func(sid: int, sname: String, _sp: int):
|
|
skill_signal["fired"] = true
|
|
skill_signal["id"] = sid
|
|
skill_signal["name"] = sname
|
|
)
|
|
|
|
# 1. Skill 137: 战马砍击 (Horse Slash)
|
|
var s137 = hcs.use_horse_skill(137, 100, 1000)
|
|
_assert(s137["ok"] == true, "Skill 137 used successfully")
|
|
_assert(s137["damage"] == 2500, "Skill 137 deals 250% damage (2500)")
|
|
_assert(s137["effect"] == "KNOCKBACK", "Skill 137 inflicts KNOCKBACK")
|
|
_assert(skill_signal["fired"] == true and skill_signal["id"] == 137, "horse_skill_used signal fired for 137")
|
|
|
|
# 2. Skill 138: 战马突刺 (Horse Charge)
|
|
var s138 = hcs.use_horse_skill(138, 100, 1000)
|
|
_assert(s138["ok"] == true, "Skill 138 used successfully")
|
|
_assert(s138["damage"] == 2000, "Skill 138 deals 200% damage (2000)")
|
|
_assert(s138["effect"] == "STUMBLE", "Skill 138 inflicts STUMBLE")
|
|
|
|
# 3. Skill 139: 战马震击 (Horse Stomp)
|
|
var s139 = hcs.use_horse_skill(139, 100, 1000)
|
|
_assert(s139["ok"] == true, "Skill 139 used successfully")
|
|
_assert(s139["damage"] == 3000, "Skill 139 deals 300% damage (3000)")
|
|
_assert(s139["effect"] == "AOE_STUN", "Skill 139 inflicts AOE_STUN")
|
|
|
|
# 4. Skill 140: 破浪击 (Power Wave)
|
|
var s140 = hcs.use_horse_skill(140, 100, 1000)
|
|
_assert(s140["ok"] == true, "Skill 140 used successfully")
|
|
_assert(s140["damage"] == 2200, "Skill 140 deals 220% damage (2200)")
|
|
_assert(s140["range"] == 12.0, "Skill 140 has 12.0m ranged reach")
|
|
|
|
func _test_cooldowns_and_sp_validation() -> void:
|
|
print("\n--- Test 3: Skill Cooldowns & SP Verification ---")
|
|
var hcs := HorseCombatSystem.new()
|
|
hcs.set_mounted_state(true, HorseCombatSystem.HORSE_TIER_MILITARY)
|
|
|
|
# Cast 137
|
|
hcs.use_horse_skill(137, 100, 1000)
|
|
|
|
# Cast again immediately -> on cooldown
|
|
var cd_res = hcs.use_horse_skill(137, 100, 1000)
|
|
_assert(cd_res["ok"] == false and cd_res["reason"] == "SKILL_ON_COOLDOWN", "Skill 137 is on cooldown")
|
|
|
|
# Update 10s -> still on cooldown
|
|
hcs.update(10.0)
|
|
_assert(hcs.skill_cooldowns[137] == 5.0, "5.0s remaining cooldown on 137")
|
|
|
|
# Update 6s -> ready
|
|
hcs.update(6.0)
|
|
_assert(hcs.skill_cooldowns[137] == 0.0, "Cooldown expired")
|
|
|
|
# Try with insufficient SP (< 30)
|
|
var low_sp_res = hcs.use_horse_skill(137, 20, 1000)
|
|
_assert(low_sp_res["ok"] == false and low_sp_res["reason"] == "INSUFFICIENT_SP", "Skill blocked with low SP")
|
|
|
|
func _test_stamina_and_feeding() -> void:
|
|
print("\n--- Test 4: Horse Stamina & Feeding (Carrot / Ginseng) ---")
|
|
var hcs := HorseCombatSystem.new()
|
|
hcs.set_mounted_state(true, HorseCombatSystem.HORSE_TIER_MILITARY)
|
|
|
|
# Drain stamina manually to test exhaustion
|
|
hcs.horse_stamina = 2.0
|
|
var ex_res = hcs.use_horse_skill(137, 100, 1000)
|
|
_assert(ex_res["ok"] == false and ex_res["reason"] == "HORSE_EXHAUSTED", "Skill blocked when stamina < 5")
|
|
|
|
# Feed invalid food
|
|
var r_bad = hcs.feed_horse(99999)
|
|
_assert(r_bad["ok"] == false and r_bad["reason"] == "WRONG_FOOD", "Invalid food rejected")
|
|
|
|
# Feed Carrot (50054) -> +40 stamina (2 + 40 = 42)
|
|
var r_carrot = hcs.feed_horse(50054)
|
|
_assert(r_carrot["ok"] == true, "Fed carrot successfully")
|
|
_assert(hcs.horse_stamina == 42.0, "Stamina increased by 40 to 42.0")
|
|
|
|
# Feed Ginseng (50058) -> +80 stamina (42 + 80 = 122 -> capped at 100.0)
|
|
var r_ginseng = hcs.feed_horse(50058)
|
|
_assert(r_ginseng["ok"] == true, "Fed ginseng successfully")
|
|
_assert(hcs.horse_stamina == 100.0, "Stamina capped at 100.0")
|
|
|
|
func _finish() -> void:
|
|
print("\n==========================================")
|
|
print("Horse Combat Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("==========================================")
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|