# 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)