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

168 lines
6.6 KiB
GDScript

# test_energy_system_parity.gd —— 40250 官方能量碎片与水晶熔炼系统 1:1 回归测试套件
extends SceneTree
const EnergySystem = preload("res://energy_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_energy_system_parity (Direction 1: Energy System 1:1) ===")
_test_fragment_extraction()
_test_crystal_fusion()
_test_crystal_usage_and_buff_lifecycle()
_test_serialization()
_finish()
func _test_fragment_extraction() -> void:
print("\n--- Test 1: Energy Fragment Extraction (40250 Quest Rules) ---")
var sys := EnergySystem.new()
var inv: Array = [
{"vnum": 10, "name": "长剑+0", "level_limit": 1},
{"vnum": 8000, "name": "木箭", "level_limit": 40, "type": 1, "subtype": 5},
{"vnum": 160, "name": "战斗剑+9", "level_limit": 65}
]
# 1. Player level too low
var res_low_player = sys.extract_energy_fragments(inv, 2, 30)
_assert(res_low_player["ok"] == false, "Extraction rejected: player lv 30 < 35")
_assert(res_low_player["reason"] == "PLAYER_LEVEL_TOO_LOW", "Reason is PLAYER_LEVEL_TOO_LOW")
# 2. Equipment level too low (< 35)
var res_low_equip = sys.extract_energy_fragments(inv, 0, 40)
_assert(res_low_equip["ok"] == false, "Extraction rejected: equip lv 1 < 35")
_assert(res_low_equip["reason"] == "EQUIP_LEVEL_TOO_LOW", "Reason is EQUIP_LEVEL_TOO_LOW")
# 3. Arrow weapon prohibited
var res_arrow = sys.extract_energy_fragments(inv, 1, 40)
_assert(res_arrow["ok"] == false, "Extraction rejected: arrow weapon not allowed")
_assert(res_arrow["reason"] == "ARROW_NOT_ALLOWED", "Reason is ARROW_NOT_ALLOWED")
var extract_sig := {"fired": false, "cnt": 0}
sys.fragments_extracted.connect(func(c: int, _n: String):
extract_sig["fired"] = true
extract_sig["cnt"] = c
)
# 4. Valid extraction of Lv 65 sword
var res_succ = sys.extract_energy_fragments(inv, 2, 70)
_assert(res_succ["ok"] == true, "Lv 65 sword extracted successfully")
_assert(inv[2] != null and inv[2]["vnum"] == EnergySystem.VNUM_ENERGY_STONE, "Slot 2 converted to Energy Stones")
_assert(inv[2]["count"] >= 3 and inv[2]["count"] <= 8, "Yield is between 3 and 8 fragments")
_assert(extract_sig["fired"] == true, "fragments_extracted signal emitted")
func _test_crystal_fusion() -> void:
print("\n--- Test 2: Energy Crystal (51002) Fusion (30 Stones + 1000 Gold) ---")
var sys := EnergySystem.new()
var player_gold := {"gold": 500} # Less than 1000
var inv: Array = [
{"vnum": EnergySystem.VNUM_ENERGY_STONE, "name": "能量碎片", "count": 25},
null
]
# 1. Gold not enough
var res_no_gold = sys.fuse_energy_crystal(inv, player_gold)
_assert(res_no_gold["ok"] == false, "Fusion rejected: gold 500 < 1000")
_assert(res_no_gold["reason"] == "NOT_ENOUGH_GOLD", "Reason is NOT_ENOUGH_GOLD")
# 2. Stones not enough (25 < 30)
player_gold["gold"] = 5000
var res_no_stone = sys.fuse_energy_crystal(inv, player_gold)
_assert(res_no_stone["ok"] == false, "Fusion rejected: stones 25 < 30")
_assert(res_no_stone["reason"] == "NOT_ENOUGH_STONES", "Reason is NOT_ENOUGH_STONES")
# 3. Valid fusion with 35 stones
inv[0]["count"] = 35
var fuse_sig := {"fired": false}
sys.crystal_fused.connect(func(_c: Dictionary):
fuse_sig["fired"] = true
)
var res_succ = sys.fuse_energy_crystal(inv, player_gold)
_assert(res_succ["ok"] == true, "Fusion succeeded")
_assert(player_gold["gold"] == 4000, "1,000 gold deducted (5000 -> 4000)")
_assert(inv[0]["count"] == 5, "Stones deducted (35 - 30 = 5 remaining)")
_assert(inv[1] != null and inv[1]["vnum"] == EnergySystem.VNUM_CHARGING_STONE, "Energy Crystal generated in slot 1")
_assert(fuse_sig["fired"] == true, "crystal_fused signal emitted")
func _test_crystal_usage_and_buff_lifecycle() -> void:
print("\n--- Test 3: Crystal Usage, 10% Attribute Buff & 2-Hour Lifecycle ---")
var sys := EnergySystem.new()
var inv: Array = [
{"vnum": EnergySystem.VNUM_CHARGING_STONE, "name": "能量水晶", "count": 1}
]
var equipped: Array = [
{"attack_power": 200, "defense": 0, "max_hp": 0},
{"attack_power": 0, "defense": 150, "max_hp": 0},
{"attack_power": 0, "defense": 0, "max_hp": 1000}
]
var player_stats := {"attack_power": 500, "defense": 300, "max_hp": 5000}
var act_sig := {"fired": false, "dur": 0.0, "pct": 0}
sys.energy_activated.connect(func(d: float, p: int):
act_sig["fired"] = true
act_sig["dur"] = d
act_sig["pct"] = p
)
var exp_sig := {"fired": false}
sys.energy_expired.connect(func():
exp_sig["fired"] = true
)
# Use crystal
var use_res = sys.use_energy_crystal(inv, 0, equipped, player_stats)
_assert(use_res["ok"] == true, "Crystal activated")
_assert(sys.is_energy_active == true, "is_energy_active is true")
_assert(inv[0] == null, "Energy crystal consumed from inventory")
_assert(act_sig["fired"] == true, "energy_activated signal emitted")
_assert(act_sig["dur"] == 7200.0, "Duration is 7200.0s (2 hours)")
# Check 10% stats:
# atk: 500 + 20 = 520
# def: 300 + 15 = 315
# hp: 5000 + 100 = 5100
_assert(player_stats["attack_power"] == 520, "Attack boosted by 10% of 200 = +20")
_assert(player_stats["defense"] == 315, "Defense boosted by 10% of 150 = +15")
_assert(player_stats["max_hp"] == 5100, "HP boosted by 10% of 1000 = +100")
# Advance time 7200s -> timeout
sys.update(7200.0, player_stats)
_assert(sys.is_energy_active == false, "Energy expired after 2 hours")
_assert(player_stats["attack_power"] == 500, "Attack cleanly reversed back to 500")
_assert(player_stats["defense"] == 300, "Defense cleanly reversed back to 300")
_assert(player_stats["max_hp"] == 5000, "HP cleanly reversed back to 5000")
_assert(exp_sig["fired"] == true, "energy_expired signal emitted")
func _test_serialization() -> void:
print("\n--- Test 4: Serialization & Restoration ---")
var sys1 := EnergySystem.new()
sys1.is_energy_active = true
sys1.energy_time_remaining = 3600.0
sys1.applied_bonuses = {"attack_power": 25, "defense": 18}
var data = sys1.serialize()
var sys2 := EnergySystem.new()
sys2.deserialize(data)
_assert(sys2.is_energy_active == true, "Restored is_energy_active")
_assert(is_equal_approx(sys2.energy_time_remaining, 3600.0), "Restored time remaining")
_assert(sys2.applied_bonuses["attack_power"] == 25, "Restored attack bonus")
func _finish() -> void:
print("\n=======================================================")
print("Energy System Parity Tests: %d Passed, %d Failed" % [_passed, _failed])
print("=======================================================\n")
if _failed > 0:
quit(1)
else:
quit(0)