Files
mtgodot-poc/project/test_metin_spawn_event_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
6.7 KiB
GDScript

# test_metin_spawn_event_parity.gd —— 40250 野外 8 档魔石与怪潮暴动 1:1 回归测试套件
extends SceneTree
const MetinSpawnEventSystem = preload("res://metin_spawn_event_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_metin_spawn_event_parity (Direction 3: Field Metin Spawn & Waves 1:1) ===")
_test_table_configuration()
_test_spawn_metin()
_test_damage_and_phases()
_test_destruction_and_drops()
_test_invalid_inputs()
_finish()
func _test_table_configuration() -> void:
print("\n--- Test 1: Metin Table Configuration (8008~8014, 8027) ---")
var expected_vnums = [8008, 8009, 8010, 8011, 8012, 8013, 8014, 8027]
_assert(MetinSpawnEventSystem.METIN_TABLE.size() == 8, "Table has exactly 8 Metin stone types")
for vnum in expected_vnums:
_assert(MetinSpawnEventSystem.METIN_TABLE.has(vnum), "Has Metin stone %d" % vnum)
var entry = MetinSpawnEventSystem.METIN_TABLE[vnum]
_assert(entry.has("name") and entry.has("level") and entry.has("max_hp") and entry.has("def") and entry.has("wave_mob") and entry.has("wave_count"),
"Metin %d has all required fields" % vnum)
_assert(entry["max_hp"] > 0 and entry["def"] > 0 and entry["wave_count"] > 0,
"Metin %d has positive stats" % vnum)
func _test_spawn_metin() -> void:
print("\n--- Test 2: Spawning Metin Stones ---")
var sys := MetinSpawnEventSystem.new()
var spawn_sig := {"fired": false, "vnum": 0, "name": "", "pos": Vector3.ZERO}
sys.metin_spawned.connect(func(v: int, n: String, p: Vector3):
spawn_sig["fired"] = true
spawn_sig["vnum"] = v
spawn_sig["name"] = n
spawn_sig["pos"] = p
)
var spawn_pos := Vector3(100.5, 0.0, 200.5)
var res = sys.spawn_metin(8008, spawn_pos)
_assert(res["ok"] == true, "Spawned 8008 successfully")
_assert(res["id"] == 1, "First instance ID is 1")
_assert(sys.active_metins.has(1), "Instance 1 in active_metins")
_assert(spawn_sig["fired"] == true and spawn_sig["vnum"] == 8008 and spawn_sig["pos"] == spawn_pos,
"metin_spawned signal emitted correctly")
var res2 = sys.spawn_metin(8027, Vector3(50.0, 0.0, 50.0))
_assert(res2["ok"] == true and res2["id"] == 2, "Second stone instance ID is 2")
_assert(sys.active_metins.size() == 2, "2 stones active")
func _test_damage_and_phases() -> void:
print("\n--- Test 3: Damage Calculation & 75%, 50%, 25% Mob Waves ---")
var sys := MetinSpawnEventSystem.new()
var res = sys.spawn_metin(8008, Vector3.ZERO) # 8008: HP 70000, Def 120, Mob 391, count 4
var id: int = res["id"]
var waves := []
sys.mob_wave_erupted.connect(func(vnum: int, count: int, minion_vnum: int):
waves.append({"vnum": vnum, "count": count, "minion_vnum": minion_vnum})
)
# Raw damage 200 - 120 def = 80 net damage
var atk1 = sys.attack_metin(id, 200)
_assert(atk1["ok"] == true, "Attack successful")
_assert(atk1["net_dmg"] == 80, "Net damage = 200 - 120 = 80")
_assert(atk1["remaining_hp"] == 70000 - 80, "Remaining HP correct")
_assert(atk1["wave_triggered"] == false, "No wave at > 75% HP")
_assert(waves.size() == 0, "No wave signal fired")
# Damage to trigger <= 75% HP: 70000 * 0.75 = 52500. We deal raw dmg to bring it below 52500.
# Remaining is 69920. Need to deal at least 69920 - 52500 = 17420 net dmg.
# Raw dmg = 17420 + 120 = 17540
var atk_phase_75 = sys.attack_metin(id, 17540)
_assert(atk_phase_75["wave_triggered"] == true, "Phase 75% wave triggered")
_assert(atk_phase_75["spawned_count"] == 4, "Spawned 4 minions")
_assert(waves.size() == 1 and waves[0]["minion_vnum"] == 391, "Wave signal received for 391")
# Another hit while still in 50%~75% bracket should NOT trigger wave again
var atk_repeat = sys.attack_metin(id, 1000)
_assert(atk_repeat["wave_triggered"] == false, "Subsequent hit doesn't retrigger 75% wave")
_assert(waves.size() == 1, "Wave count still 1")
# Damage to trigger <= 50% HP (35000)
# Current HP is around 51620. Need 17000 net dmg.
var atk_phase_50 = sys.attack_metin(id, 20000)
_assert(atk_phase_50["wave_triggered"] == true, "Phase 50% wave triggered")
_assert(waves.size() == 2, "Wave count now 2")
# Damage to trigger <= 25% HP (17500)
var atk_phase_25 = sys.attack_metin(id, 20000)
_assert(atk_phase_25["wave_triggered"] == true, "Phase 25% wave triggered")
_assert(waves.size() == 3, "Wave count now 3")
func _test_destruction_and_drops() -> void:
print("\n--- Test 4: Metin Destruction & Drops ---")
var sys := MetinSpawnEventSystem.new()
var res = sys.spawn_metin(8014, Vector3(10, 0, 10)) # 8014: 死亡之石, lvl 65, HP 260000
var id: int = res["id"]
var destroy_sig := {"fired": false, "vnum": 0, "drops": []}
sys.metin_destroyed.connect(func(vnum: int, pos: Vector3, drops: Array):
destroy_sig["fired"] = true
destroy_sig["vnum"] = vnum
destroy_sig["drops"] = drops
)
# Overkill blow
var kill_atk = sys.attack_metin(id, 1000000)
_assert(kill_atk["ok"] == true and kill_atk["destroyed"] == true, "Metin destroyed")
_assert(sys.active_metins.has(id) == false, "Stone removed from active_metins")
_assert(destroy_sig["fired"] == true and destroy_sig["vnum"] == 8014, "metin_destroyed signal emitted")
var drops: Array = kill_atk["drops"]
_assert(drops.size() == 4, "4 drops generated")
var has_skill_book := false
var has_soul_stone := false
var has_spirit_stone := false
var has_gold := false
for d in drops:
if d["vnum"] == 50300:
has_skill_book = true
elif d["vnum"] == 50513:
has_soul_stone = true
elif d["vnum"] == 28430:
has_spirit_stone = true
elif d["vnum"] == 1 and d["count"] >= 65 * 500:
has_gold = true
_assert(has_skill_book, "Dropped Skill Book 50300")
_assert(has_soul_stone, "Dropped Soul Stone 50513")
_assert(has_spirit_stone, "Dropped Spirit Stone 28430")
_assert(has_gold, "Dropped Gold scaled with level 65")
func _test_invalid_inputs() -> void:
print("\n--- Test 5: Invalid Metin Inputs ---")
var sys := MetinSpawnEventSystem.new()
var spawn_fail = sys.spawn_metin(99999)
_assert(spawn_fail["ok"] == false and spawn_fail["reason"] == "INVALID_METIN_VNUM",
"Correctly rejected invalid vnum 99999")
var atk_fail = sys.attack_metin(8888, 100)
_assert(atk_fail["ok"] == false and atk_fail["reason"] == "METIN_NOT_FOUND",
"Correctly rejected attack on missing instance 8888")
func _finish() -> void:
print("\n==========================================")
print("Metin Spawn Event 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 Metin Spawn Event parity tests passed!")
quit(0)