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

161 lines
6.6 KiB
GDScript

# test_pet_companion_parity.gd —— 40250 伴随宠物系统 1:1 纯净回归测试套件
extends SceneTree
const PetCompanionSystem = preload("res://pet_companion_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_pet_companion_parity (Direction 2: Pet Companion System 1:1) ===")
_test_pet_table_and_validity()
_test_summon_and_bonuses()
_test_dismiss_and_toggle()
_test_3d_movement_and_states()
_test_time_expiry()
_finish()
func _test_pet_table_and_validity() -> void:
print("\n--- Test 1: Pet Table Configuration & Seal Validation ---")
var pet_sys := PetCompanionSystem.new()
# Invalid seal item
var invalid_res = pet_sys.toggle_pet_seal({"vnum": 99999}, Vector3.ZERO)
_assert(invalid_res["ok"] == false and invalid_res["reason"] == "NOT_A_PET_SEAL", "Invalid seal rejected")
# Check configured pets in PET_TABLE
_assert(PetCompanionSystem.PET_TABLE.has(53001), "Fire Phoenix (53001) in table")
_assert(PetCompanionSystem.PET_TABLE.has(53002), "Reindeer (53002) in table")
_assert(PetCompanionSystem.PET_TABLE.has(53003), "Ice Phoenix (53003) in table")
_assert(PetCompanionSystem.PET_TABLE.has(53005), "Bao Bao (53005) in table")
_assert(PetCompanionSystem.PET_TABLE.has(53006), "Leonidas (53006) in table")
var f_phoenix = PetCompanionSystem.PET_TABLE[53001]
_assert(f_phoenix["mob_vnum"] == 34001, "Fire Phoenix mob vnum is 34001")
_assert(f_phoenix["bonuses"]["max_hp"] == 1500, "Fire Phoenix bonus Max HP is 1500")
_assert(f_phoenix["bonuses"]["monster_dmg_pct"] == 15, "Fire Phoenix bonus monster dmg is 15%")
func _test_summon_and_bonuses() -> void:
print("\n--- Test 2: Summoning & Stat Injection ---")
var pet_sys := PetCompanionSystem.new()
var signal_data := {
"fired": false,
"name": "",
"vnum": 0
}
pet_sys.pet_summoned.connect(func(pname: String, vnum: int):
signal_data["fired"] = true
signal_data["name"] = pname
signal_data["vnum"] = vnum
)
var player_pos := Vector3(100.0, 0.0, 200.0)
var res = pet_sys.toggle_pet_seal({"vnum": 53001, "duration": 3600.0}, player_pos)
_assert(res["ok"] == true, "Fire Phoenix summon succeeded")
_assert(res["action"] == "summon", "Action is summon")
_assert(pet_sys.is_summoned == true, "is_summoned is true")
_assert(pet_sys.current_seal_vnum == 53001, "current_seal_vnum is 53001")
_assert(signal_data["fired"] == true, "pet_summoned signal emitted")
_assert(signal_data["name"] == "火凤凰", "Summoned pet name is 火凤凰")
_assert(signal_data["vnum"] == 53001, "Summoned pet vnum is 53001")
var bonuses = pet_sys.get_active_bonuses()
_assert(bonuses["max_hp"] == 1500, "Active bonus max_hp is 1500")
_assert(bonuses["monster_dmg_pct"] == 15, "Active bonus monster_dmg_pct is 15%")
func _test_dismiss_and_toggle() -> void:
print("\n--- Test 3: Dismiss & Switch Pets ---")
var pet_sys := PetCompanionSystem.new()
var dismiss_data := {
"fired": false,
"name": ""
}
pet_sys.pet_dismissed.connect(func(pname: String):
dismiss_data["fired"] = true
dismiss_data["name"] = pname
)
var player_pos := Vector3.ZERO
# Summon 53005 (Bao Bao)
pet_sys.toggle_pet_seal({"vnum": 53005}, player_pos)
_assert(pet_sys.is_summoned == true, "Bao Bao summoned")
_assert(pet_sys.get_active_bonuses()["critical_pct"] == 15, "Bao Bao grants 15% crit")
# Toggle 53005 again -> dismiss
var res = pet_sys.toggle_pet_seal({"vnum": 53005}, player_pos)
_assert(res["ok"] == true and res["action"] == "dismiss", "Toggling same seal dismisses pet")
_assert(pet_sys.is_summoned == false, "Pet is no longer summoned")
_assert(dismiss_data["fired"] == true, "pet_dismissed signal emitted")
_assert(dismiss_data["name"] == "小胖熊猫", "Dismissed name matches")
_assert(pet_sys.get_active_bonuses().is_empty(), "No bonuses active when pet dismissed")
# Summon 53006 (Leonidas) and then summon 53003 (Ice Phoenix) to switch
pet_sys.toggle_pet_seal({"vnum": 53006}, player_pos)
_assert(pet_sys.current_seal_vnum == 53006, "Leonidas active")
_assert(pet_sys.get_active_bonuses()["att_pct"] == 10, "Leonidas grants 10% att")
# Switch directly to 53003
var switch_res = pet_sys.toggle_pet_seal({"vnum": 53003}, player_pos)
_assert(switch_res["ok"] == true and switch_res["action"] == "summon", "Direct switch to Ice Phoenix succeeded")
_assert(pet_sys.current_seal_vnum == 53003, "Active pet is now Ice Phoenix")
_assert(pet_sys.get_active_bonuses()["def_pct"] == 15, "Ice Phoenix grants 15% def")
func _test_3d_movement_and_states() -> void:
print("\n--- Test 4: 3D Movement & State Machine ---")
var pet_sys := PetCompanionSystem.new()
var player_pos := Vector3(0.0, 0.0, 0.0)
pet_sys.toggle_pet_seal({"vnum": 53001}, player_pos)
# 1. When close (dist <= 2.0), state is wait
pet_sys.pet_position = Vector3(1.0, 0.0, 0.0) # dist = 1.0m <= 2.0m
pet_sys.update_movement(player_pos, 0.1)
_assert(pet_sys.pet_state == "wait", "Pet waits within 2.0m")
# 2. When player moves to (10, 0, 0), dist is 9.0m (> 2.0m)
player_pos = Vector3(10.0, 0.0, 0.0)
var prev_pos = pet_sys.pet_position
pet_sys.update_movement(player_pos, 0.5) # dt = 0.5s -> move speed 4.5m/s -> move 2.25m towards player
_assert(pet_sys.pet_state == "run", "Pet state is run when following player")
_assert(pet_sys.pet_position.x > prev_pos.x, "Pet moved towards player")
# 3. When player teleports far away (> 25.0m)
player_pos = Vector3(100.0, 0.0, 100.0)
pet_sys.update_movement(player_pos, 0.1)
var new_dist = pet_sys.pet_position.distance_to(player_pos)
_assert(new_dist < 5.0, "Pet instantly teleports near player when dist >= 25m")
_assert(pet_sys.pet_state == "wait", "Pet returns to wait state after teleport")
func _test_time_expiry() -> void:
print("\n--- Test 5: Pet Duration Expiration ---")
var pet_sys := PetCompanionSystem.new()
pet_sys.toggle_pet_seal({"vnum": 53001, "duration": 10.0}, Vector3.ZERO)
_assert(pet_sys.is_summoned == true, "Pet summoned with 10s duration")
pet_sys.update(4.0)
_assert(pet_sys.is_summoned == true, "Pet still active at 4s elapsed")
_assert(is_equal_approx(pet_sys.remaining_time, 6.0), "6s remaining")
pet_sys.update(7.0) # Exceeds remaining
_assert(pet_sys.is_summoned == false, "Pet dismissed automatically when duration expires")
_assert(pet_sys.get_active_bonuses().is_empty(), "No bonuses after expiration")
func _finish() -> void:
print("\n==========================================")
print("Pet Companion Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
print("==========================================")
if _failed > 0:
quit(1)
else:
quit(0)