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

145 lines
5.6 KiB
GDScript

# test_class_specialization_parity.gd —— 40250 角色 30 级转职与无损洗髓 1:1 回归测试套件
extends SceneTree
const ClassSpecializationSystem = preload("res://class_specialization_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_class_specialization_parity (Direction 3: Class Specialization 1:1) ===")
_test_spec_tree_configuration()
_test_level_30_requirement()
_test_choosing_specialization()
_test_skill_point_allocation()
_test_reset_specialization_lossless()
_finish()
func _test_spec_tree_configuration() -> void:
print("\n--- Test 1: 4 Classes x 2 Specializations Tree ---")
var sys := ClassSpecializationSystem.new()
for j in [0, 1, 2, 3]:
var specs = sys.get_available_specializations(j)
_assert(specs.size() == 2, "Job %d has exactly 2 specialization branches" % j)
_assert(specs.has(1) and specs.has(2), "Branches are 1 and 2")
_assert(specs[1]["skills"].size() == 5, "Branch 1 has 5 skills")
_assert(specs[2]["skills"].size() == 5, "Branch 2 has 5 skills")
func _test_level_30_requirement() -> void:
print("\n--- Test 2: Level 30 Requirement ---")
var sys := ClassSpecializationSystem.new()
var low_lvl_player := {"job": 0, "level": 25, "skill_group": 0}
var fail_res = sys.choose_specialization(1, low_lvl_player)
_assert(fail_res["ok"] == false and fail_res["reason"] == "LEVEL_TOO_LOW", "Level 25 rejected")
func _test_choosing_specialization() -> void:
print("\n--- Test 3: Choosing Specialization (Warrior Body) ---")
var sys := ClassSpecializationSystem.new()
var player := {"job": 0, "level": 35, "skill_group": 0, "skill_points": 35}
var spec_sig := {"fired": false, "job": -1, "spec_id": 0}
sys.specialization_chosen.connect(func(j: int, sid: int, _name: String):
spec_sig["fired"] = true
spec_sig["job"] = j
spec_sig["spec_id"] = sid
)
# Choose Arahan (Body Warrior)
var res = sys.choose_specialization(1, player)
_assert(res["ok"] == true, "Specialization chosen")
_assert(player["skill_group"] == 1, "skill_group set to 1")
_assert(player["skills"].size() == 5, "5 skills initialized")
_assert(player["skills"].has(4) and player["skills"][4]["name"] == "剑气", "Has Aura of Sword (4)")
_assert(spec_sig["fired"] == true and spec_sig["spec_id"] == 1, "specialization_chosen signal emitted")
# Duplicate choice without reset must fail
var fail_dup = sys.choose_specialization(2, player)
_assert(fail_dup["ok"] == false and fail_dup["reason"] == "ALREADY_SPECIALIZED", "Cannot re-choose without reset")
func _test_skill_point_allocation() -> void:
print("\n--- Test 4: Skill Point Allocation ---")
var sys := ClassSpecializationSystem.new()
var player := {"job": 0, "level": 35, "skill_group": 1, "skill_points": 5, "skills": {
4: {"name": "剑气", "level": 0}
}}
var alloc_sig := {"fired": false, "vnum": 0, "lvl": 0}
sys.skill_point_allocated.connect(func(v: int, l: int, _r: int):
alloc_sig["fired"] = true
alloc_sig["vnum"] = v
alloc_sig["lvl"] = l
)
# Allocate 3 points into Aura of Sword
sys.allocate_skill_point(4, player)
sys.allocate_skill_point(4, player)
sys.allocate_skill_point(4, player)
_assert(player["skills"][4]["level"] == 3, "Aura of Sword leveled to 3")
_assert(player["skill_points"] == 2, "Remaining points 5 - 3 = 2")
_assert(alloc_sig["fired"] == true and alloc_sig["lvl"] == 3, "skill_point_allocated emitted")
# Cap test: level 20 is max normal level
player["skills"][4]["level"] = 20
var fail_cap = sys.allocate_skill_point(4, player)
_assert(fail_cap["ok"] == false and fail_cap["reason"] == "MAX_NORMAL_LEVEL", "Cannot allocate past level 20")
func _test_reset_specialization_lossless() -> void:
print("\n--- Test 5: Lossless Reset with Scroll (71100) & Branch Switch ---")
var sys := ClassSpecializationSystem.new()
var player := {
"job": 0,
"level": 40,
"skill_group": 1, # Body
"skill_points": 5,
"skills": {
1: {"name": "三连斩", "level": 10},
4: {"name": "剑气", "level": 20}
}
}
# Total points invested: 10 + 20 = 30. Remaining: 5. After refund: 35.
var inv: Array = [
{"vnum": ClassSpecializationSystem.VNUM_RESET_SCROLL, "name": "洗髓卷轴", "count": 1}
]
var reset_sig := {"fired": false, "pts": 0}
sys.specialization_reset.connect(func(pts: int):
reset_sig["fired"] = true
reset_sig["pts"] = pts
)
var res = sys.reset_specialization(inv, player, false)
_assert(res["ok"] == true, "Reset succeeded")
_assert(inv[0] == null, "Reset scroll consumed")
_assert(player["skill_group"] == 0, "skill_group reset to 0")
_assert(player["skills"].is_empty(), "Old skills cleared")
_assert(player["skill_points"] == 35, "All 30 points refunded (5 + 30 = 35)")
_assert(reset_sig["fired"] == true and reset_sig["pts"] == 30, "specialization_reset emitted with 30 pts")
# Now player can choose the other branch: Partisan (Mental Warrior)
var switch_res = sys.choose_specialization(2, player)
_assert(switch_res["ok"] == true, "Successfully switched to Mental Warrior (Partisan)")
_assert(player["skill_group"] == 2, "skill_group is now 2")
_assert(player["skills"].has(16) and player["skills"][16]["name"] == "强身术", "Has Strong Body (16)")
func _finish() -> void:
print("\n==========================================")
print("Class Specialization 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 Class Specialization parity tests passed!")
quit(0)