- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
147 lines
6.2 KiB
GDScript
147 lines
6.2 KiB
GDScript
# test_classic_pet_parity.gd —— 40250 经典封印宠物系统 1:1 回归测试套件
|
|
extends SceneTree
|
|
|
|
const ClassicPetSystem = preload("res://classic_pet_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_classic_pet_parity (Direction 1: Classic Pet System 1:1) ===")
|
|
_test_summon_and_buff_application()
|
|
_test_auto_unsummon_previous_pet()
|
|
_test_dungeon_only_buff_condition()
|
|
_test_follow_and_teleport_ai()
|
|
_test_serialization()
|
|
_finish()
|
|
|
|
func _test_summon_and_buff_application() -> void:
|
|
print("\n--- Test 1: Summon & Buff Injection/Removal ---")
|
|
var sys := ClassicPetSystem.new()
|
|
var player := {"max_hp": 5000, "attack_pct": 0, "defense_pct": 0}
|
|
var seal_firedrake := {"vnum": ClassicPetSystem.SEAL_BABY_FIREDRAKE}
|
|
|
|
var summon_sig := {"fired": false, "vnum": 0, "name": ""}
|
|
sys.pet_summoned.connect(func(v: int, n: String, _b: Dictionary):
|
|
summon_sig["fired"] = true
|
|
summon_sig["vnum"] = v
|
|
summon_sig["name"] = n
|
|
)
|
|
|
|
var unsummon_sig := {"fired": false, "vnum": 0}
|
|
sys.pet_unsummoned.connect(func(v: int, _n: String):
|
|
unsummon_sig["fired"] = true
|
|
unsummon_sig["vnum"] = v
|
|
)
|
|
|
|
# Summon Baby Firedrake
|
|
var res = sys.summon_pet(seal_firedrake, player, false, Vector2.ZERO)
|
|
_assert(res["ok"] == true, "Summon succeeded")
|
|
_assert(sys.is_summoned == true, "System reports pet is summoned")
|
|
_assert(sys.active_mob_vnum == ClassicPetSystem.MOB_BABY_FIREDRAKE, "Mob vnum is 34005")
|
|
_assert(player["max_hp"] == 6500, "HP increased by 1500 (5000 -> 6500)")
|
|
_assert(player["attack_pct"] == 15, "Attack pct increased by 15%")
|
|
_assert(summon_sig["fired"] == true, "pet_summoned signal emitted")
|
|
|
|
# Unsummon
|
|
var un_res = sys.unsummon_pet(player)
|
|
_assert(un_res["ok"] == true, "Unsummon succeeded")
|
|
_assert(sys.is_summoned == false, "System reports pet unsummoned")
|
|
_assert(player["max_hp"] == 5000, "HP reversed back to 5000")
|
|
_assert(player["attack_pct"] == 0, "Attack pct reversed back to 0%")
|
|
_assert(unsummon_sig["fired"] == true, "pet_unsummoned signal emitted")
|
|
|
|
func _test_auto_unsummon_previous_pet() -> void:
|
|
print("\n--- Test 2: Auto Unsummon Previous Pet When Summoning New ---")
|
|
var sys := ClassicPetSystem.new()
|
|
var player := {"monster_damage_pct": 0, "defense_pct": 0, "max_hp": 10000}
|
|
|
|
# 1. Summon Boar (+15% monster damage)
|
|
sys.summon_pet({"vnum": ClassicPetSystem.SEAL_BABY_BOAR}, player, false)
|
|
_assert(player["monster_damage_pct"] == 15, "Boar buff applied (+15% monster dmg)")
|
|
|
|
# 2. Directly summon Ice Phoenix (+15% def, +1500 HP)
|
|
sys.summon_pet({"vnum": ClassicPetSystem.SEAL_ICE_PHOENIX}, player, false)
|
|
_assert(sys.active_mob_vnum == ClassicPetSystem.MOB_ICE_PHOENIX, "Now Ice Phoenix is active")
|
|
_assert(player["monster_damage_pct"] == 0, "Previous Boar buff cleared")
|
|
_assert(player["defense_pct"] == 15, "Phoenix defense buff applied (+15%)")
|
|
_assert(player["max_hp"] == 11500, "Phoenix HP buff applied (+1500)")
|
|
|
|
func _test_dungeon_only_buff_condition() -> void:
|
|
print("\n--- Test 3: 40250 Dungeon-Only Buff Validation (Baby Azrael) ---")
|
|
var sys := ClassicPetSystem.new()
|
|
var seal_azrael := {"vnum": ClassicPetSystem.SEAL_BABY_AZRAEL}
|
|
|
|
# In normal field map (not in dungeon)
|
|
var player1 := {"max_hp": 5000, "dungeon_attack_pct": 0}
|
|
sys.summon_pet(seal_azrael, player1, false)
|
|
_assert(player1["max_hp"] == 6000, "Azrael standard HP (+1000) applied in field")
|
|
_assert(player1["dungeon_attack_pct"] == 0, "Dungeon attack buff not applied outside dungeon")
|
|
sys.unsummon_pet(player1)
|
|
|
|
# Inside dungeon
|
|
var player2 := {"max_hp": 5000, "dungeon_attack_pct": 0}
|
|
sys.summon_pet(seal_azrael, player2, true)
|
|
_assert(player2["max_hp"] == 6000, "Azrael standard HP (+1000) applied in dungeon")
|
|
_assert(player2["dungeon_attack_pct"] == 20, "Dungeon attack buff (+20%) successfully applied in dungeon")
|
|
sys.unsummon_pet(player2)
|
|
_assert(player2["dungeon_attack_pct"] == 0, "Dungeon buff cleared after unsummon")
|
|
|
|
func _test_follow_and_teleport_ai() -> void:
|
|
print("\n--- Test 4: Follow AI (Idle, Follow, Teleport) ---")
|
|
var sys := ClassicPetSystem.new()
|
|
var dummy_stats := {}
|
|
sys.summon_pet({"vnum": ClassicPetSystem.SEAL_BABY_BOAR}, dummy_stats, false, Vector2(0, 0))
|
|
|
|
# Owner is at (1, 0) -> dist is 1.5 - 1.0 = 0.5 (< 2.0 MIN_FOLLOW_DIST)
|
|
var ai_idle = sys.update_follow_ai(Vector2(1.0, 0.0), 0.1)
|
|
_assert(ai_idle["action"] == "idle", "AI stays idle when within 2.0m")
|
|
|
|
# Owner walks away to (8, 0) -> dist > 2.0m
|
|
var ai_follow = sys.update_follow_ai(Vector2(8.0, 0.0), 0.5)
|
|
_assert(ai_follow["action"] == "follow", "AI follows owner when distance > 2.0m")
|
|
|
|
# Owner warps/runs far away to (100, 100) -> dist > 15.0m
|
|
var move_sig := {"fired": false, "teleport": false}
|
|
sys.pet_moved.connect(func(_pos: Vector2, is_tele: bool):
|
|
move_sig["fired"] = true
|
|
move_sig["teleport"] = is_tele
|
|
)
|
|
var ai_tele = sys.update_follow_ai(Vector2(100.0, 100.0), 0.1)
|
|
_assert(ai_tele["action"] == "teleport", "AI teleports when distance > 15.0m")
|
|
_assert(move_sig["teleport"] == true, "pet_moved signal reported teleport")
|
|
|
|
func _test_serialization() -> void:
|
|
print("\n--- Test 5: State Serialization & Restoration ---")
|
|
var sys1 := ClassicPetSystem.new()
|
|
var dummy_stats := {"max_hp": 5000}
|
|
sys1.summon_pet({"vnum": ClassicPetSystem.SEAL_BABY_FIREDRAKE}, dummy_stats, false, Vector2(10, 20))
|
|
|
|
var data = sys1.serialize()
|
|
_assert(data["is_summoned"] == true, "Serialized is_summoned")
|
|
_assert(data["active_mob_vnum"] == ClassicPetSystem.MOB_BABY_FIREDRAKE, "Serialized mob vnum")
|
|
_assert(data["active_seal_vnum"] == ClassicPetSystem.SEAL_BABY_FIREDRAKE, "Serialized seal vnum")
|
|
|
|
var sys2 := ClassicPetSystem.new()
|
|
sys2.deserialize(data)
|
|
_assert(sys2.is_summoned == true, "Restored is_summoned")
|
|
_assert(sys2.active_mob_vnum == ClassicPetSystem.MOB_BABY_FIREDRAKE, "Restored mob vnum")
|
|
_assert(sys2.active_pet_name == "幼火龙", "Restored pet name")
|
|
|
|
func _finish() -> void:
|
|
print("\n=======================================================")
|
|
print("Classic Pet System Parity Tests: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("=======================================================\n")
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|