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