Files
mtgodot-poc/project/test_combat_status_effect_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

134 lines
5.9 KiB
GDScript

# test_combat_status_effect_parity.gd —— Metin2 40250 战斗状态异常与负面减益 1:1 测试套件
extends SceneTree
const CombatStatusEffectSystem = preload("res://combat_status_effect_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_combat_status_effect_parity (Direction 2: Combat Status Effects 1:1) ===")
test_stun_and_immunity()
test_slow_and_immunity()
test_poison_mechanics_and_regen_block()
test_fire_damage_ticks()
test_cure_and_serialization()
print("\n=======================================================")
print("Combat Status Effect Tests: %d Passed, %d Failed" % [_passed, _failed])
print("=======================================================\n")
if _failed > 0:
quit(1)
else:
quit(0)
func test_stun_and_immunity() -> void:
print("\n--- Test 1: Stun & IMMUNE_STUN (40250 1:1) ---")
var system = CombatStatusEffectSystem.new()
var attacker = {"id": 1, "is_pc": true, "stun_pct": 20}
var victim_normal = {"id": 101, "is_pc": false, "immune_stun": false}
var victim_shielded = {"id": 102, "is_pc": false, "immune_stun": true}
# 1. 普通怪物被击中眩晕 (PvE 4秒)
var res = system.try_apply_normal_attack_effects(attacker, victim_normal, {"stun_pct_roll": 10})
_assert(res["applied"].has(CombatStatusEffectSystem.EFFECT_STUN), "Applied stun to normal monster")
_assert(system.is_stunned(101) == true, "Target 101 is stunned")
# 2. 携带免疫眩晕盾牌的目标 (IMMUNE_STUN) -> 100% 抵抗
res = system.try_apply_normal_attack_effects(attacker, victim_shielded, {"stun_pct_roll": 5})
_assert(not res["applied"].has(CombatStatusEffectSystem.EFFECT_STUN), "Stun avoided")
_assert(res["resisted"].has("immune_stun"), "Resisted via immune_stun")
_assert(system.is_stunned(102) == false, "Target 102 is not stunned")
func test_slow_and_immunity() -> void:
print("\n--- Test 2: Slow & IMMUNE_SLOW (40250 1:1) ---")
var system = CombatStatusEffectSystem.new()
var attacker = {"id": 1, "slow_pct": 15}
var victim_normal = {"id": 201, "immune_slow": false}
var victim_immune = {"id": 202, "immune_slow": true}
# 1. 成功施加减速
var res = system.try_apply_normal_attack_effects(attacker, victim_normal, {"slow_pct_roll": 10})
_assert(res["applied"].has(CombatStatusEffectSystem.EFFECT_SLOW), "Applied slow")
_assert(system.get_slow_reduction(201) == 30, "Speed reduced by 30 points")
# 2. 免疫减速抵抗
res = system.try_apply_normal_attack_effects(attacker, victim_immune, {"slow_pct_roll": 5})
_assert(res["resisted"].has("immune_slow"), "Resisted via immune_slow")
_assert(system.get_slow_reduction(202) == 0, "No speed reduction")
func test_poison_mechanics_and_regen_block() -> void:
print("\n--- Test 3: Poison Damage, HP Regen Block & Monster Single Poison (40250 1:1) ---")
var system = CombatStatusEffectSystem.new()
var attacker = {"id": 1, "is_pc": true, "level": 50, "poison_pct": 100}
var monster = {"id": 301, "is_pc": false, "level": 50, "max_hp": 2000, "hp": 2000, "has_been_poisoned": false}
# 1. 成功施加中毒
var res = system.try_apply_normal_attack_effects(attacker, monster, {"poison_pct_roll": 50})
_assert(res["applied"].has(CombatStatusEffectSystem.EFFECT_POISON), "Applied poison")
_assert(system.has_effect(301, CombatStatusEffectSystem.EFFECT_POISON) == true, "Monster has poison effect")
_assert(system.is_natural_hp_regen_blocked(301) == true, "Poison blocks natural HP regen")
_assert(monster["has_been_poisoned"] == true, "Monster recorded has_been_poisoned")
# 2. 心跳更新 3 秒触发跳毒 (5% of 2000 = 100 伤害)
var targets_map = {301: monster}
system.update(3.0, targets_map)
_assert(monster["hp"] == 1900, "HP reduced from 2000 to 1900 after first poison tick")
# 3. 怪物在解除中毒后不再被第二次中毒 (40250 char_resist.cpp: m_bHasPoisoned && !IsPC())
system.clear_effect(301, CombatStatusEffectSystem.EFFECT_POISON)
res = system.try_apply_normal_attack_effects(attacker, monster, {"poison_pct_roll": 10})
_assert(not res["applied"].has(CombatStatusEffectSystem.EFFECT_POISON), "Monster rejected second poison")
_assert(res["resisted"].has("poison_already_poisoned_once"), "Resisted as monster poisoned once")
func test_fire_damage_ticks() -> void:
print("\n--- Test 4: Fire Burn Damage Ticks (40250 1:1) ---")
var system = CombatStatusEffectSystem.new()
var victim = {"id": 401, "hp": 500}
var targets_map = {401: victim}
# 施加烈焰灼烧: 3 次跳伤,每次 60 点
system.apply_fire(401, 3, 60)
_assert(system.has_effect(401, CombatStatusEffectSystem.EFFECT_FIRE) == true, "Has fire effect")
# 经过 3 秒
system.update(3.0, targets_map)
_assert(victim["hp"] == 440, "HP dropped by 60 to 440")
# 经过 6 秒 (跳完剩余 2 次,共 180 伤害,生命为 500 - 180 = 320)
system.update(6.0, targets_map)
_assert(victim["hp"] == 320, "HP dropped to 320")
_assert(system.has_effect(401, CombatStatusEffectSystem.EFFECT_FIRE) == false, "Fire effect expired")
func test_cure_and_serialization() -> void:
print("\n--- Test 5: Curing & State Serialization ---")
var system = CombatStatusEffectSystem.new()
system.apply_stun(501, 4.0)
var victim = {"id": 502, "max_hp": 1000, "hp": 1000}
system.apply_poison(victim)
_assert(system.has_effect(502, CombatStatusEffectSystem.EFFECT_POISON) == true, "Poison active")
# 使用解毒药剂清除中毒
var cleared = system.clear_effect(502, CombatStatusEffectSystem.EFFECT_POISON)
_assert(cleared == true, "Poison cleared via antidote")
_assert(system.has_effect(502, CombatStatusEffectSystem.EFFECT_POISON) == false, "No longer poisoned")
var data = system.serialize()
var restored = CombatStatusEffectSystem.new()
restored.deserialize(data)
_assert(restored.is_stunned(501) == true, "Restored stun effect")