# test_status_skill_reset_parity.gd —— 40250 属性重修卷轴与忘却之书洗点 1:1 回归测试套件 extends SceneTree const StatusSkillResetSystem = preload("res://status_skill_reset_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_status_skill_reset_parity (Direction 2: Status & Skill Reset 1:1) ===") _test_single_status_reset() _test_all_status_reset() _test_book_of_oblivion() _test_all_skills_reset() _finish() func _test_single_status_reset() -> void: print("\n--- Test 1: Single Status Redistribution (71002) ---") var sys := StatusSkillResetSystem.new() var player := { "job": "warrior", "vit": 16, "int": 3, "str": 50, # Base is 15 -> 35 points added "dex": 5, "unused_status_points": 0 } var inv: Array = [ {"vnum": StatusSkillResetSystem.VNUM_STATUS_REDIST_SCROLL, "name": "属性重修卷轴", "count": 2} ] var reset_sig := {"fired": false, "stat": "", "refunded": 0} sys.status_reset.connect(func(s: String, r: int): reset_sig["fired"] = true reset_sig["stat"] = s reset_sig["refunded"] = r ) # Reset STR var res_str = sys.reset_single_status("str", player, inv, 0) _assert(res_str["ok"] == true, "STR reset successfully") _assert(player["str"] == 15, "STR returned to warrior base 15") _assert(player["unused_status_points"] == 35, "35 points refunded to unused pool") _assert(inv[0]["count"] == 1, "Scroll count decremented 2 -> 1") _assert(reset_sig["fired"] == true, "status_reset signal emitted") _assert(reset_sig["stat"] == "str" and reset_sig["refunded"] == 35, "Signal reported correct stat & points") # Try reset again when already at base var res_base = sys.reset_single_status("str", player, inv, 0) _assert(res_base["ok"] == false, "Reset rejected when already at base") _assert(res_base["reason"] == "STAT_ALREADY_AT_BASE", "Reason is STAT_ALREADY_AT_BASE") func _test_all_status_reset() -> void: print("\n--- Test 2: All Status Reset (Blood Pill 71003) ---") var sys := StatusSkillResetSystem.new() var player := { "job": "warrior", "vit": 36, # Base 16 (+20) "int": 3, # Base 3 (+0) "str": 40, # Base 15 (+25) "dex": 15, # Base 5 (+10) "unused_status_points": 5 # total extra = 20 + 25 + 10 = 55 } var inv: Array = [ {"vnum": StatusSkillResetSystem.VNUM_ALL_STATUS_RESET_SCROLL, "name": "全属性重置卷轴", "count": 1} ] var all_sig := {"fired": false, "refunded": 0} sys.all_status_reset.connect(func(r: int): all_sig["fired"] = true all_sig["refunded"] = r ) var res_all = sys.reset_all_status(player, inv, 0) _assert(res_all["ok"] == true, "All status reset succeeded") _assert(player["vit"] == 16, "VIT back to base 16") _assert(player["str"] == 15, "STR back to base 15") _assert(player["dex"] == 5, "DEX back to base 5") _assert(player["unused_status_points"] == 60, "Refunded 55 points (5 existing + 55 = 60 total)") _assert(inv[0] == null, "Scroll consumed from slot 0") _assert(all_sig["fired"] == true, "all_status_reset signal emitted") _assert(all_sig["refunded"] == 55, "Reported 55 refunded points") func _test_book_of_oblivion() -> void: print("\n--- Test 3: Book of Oblivion (50311~50320) Single Skill Point Reset ---") var sys := StatusSkillResetSystem.new() var player_skills := { 1: 16, # 三连斩 Lv 16 2: 0, # 旋风斩 Lv 0 3: 20 # 剑气 Master (M1) } var player_data := {"unused_skill_points": 0} var inv: Array = [ {"vnum": 50311, "name": "忘却之书(三连斩)", "target_skill": 1, "count": 2}, {"vnum": 50312, "name": "忘却之书(旋风斩)", "target_skill": 2, "count": 1}, {"vnum": 50313, "name": "忘却之书(剑气)", "target_skill": 3, "count": 1} ] var sk_sig := {"fired": false, "id": 0, "new_lv": 0} sys.skill_point_reset.connect(func(id: int, nlv: int): sk_sig["fired"] = true sk_sig["id"] = id sk_sig["new_lv"] = nlv ) # 1. Reset Skill 1 (16 -> 15) var res1 = sys.reset_single_skill_point(1, player_skills, player_data, inv, 0) _assert(res1["ok"] == true, "Skill 1 point reset succeeded") _assert(player_skills[1] == 15, "Skill 1 level reduced 16 -> 15") _assert(player_data["unused_skill_points"] == 1, "1 skill point refunded") _assert(inv[0]["count"] == 1, "Book count decremented 2 -> 1") _assert(sk_sig["fired"] == true, "skill_point_reset signal emitted") _assert(sk_sig["id"] == 1 and sk_sig["new_lv"] == 15, "Signal reported skill 1 lv 15") # 2. Skill level 0 cannot be reset var res_zero = sys.reset_single_skill_point(2, player_skills, player_data, inv, 1) _assert(res_zero["ok"] == false, "Skill lv 0 rejected") _assert(res_zero["reason"] == "SKILL_LEVEL_ZERO", "Reason is SKILL_LEVEL_ZERO") # 3. Master skill (>= 20) cannot be downgraded by Book of Oblivion (40250 rule) var res_master = sys.reset_single_skill_point(3, player_skills, player_data, inv, 2) _assert(res_master["ok"] == false, "Master skill rejected") _assert(res_master["reason"] == "SKILL_IS_MASTER", "Reason is SKILL_IS_MASTER") func _test_all_skills_reset() -> void: print("\n--- Test 4: All Skills Reset Scroll (71001) ---") var sys := StatusSkillResetSystem.new() var player_skills := { 1: 15, 2: 18, 3: 7 } # Total invested: 15 + 18 + 7 = 40 var player_data := {"unused_skill_points": 2} var inv: Array = [ {"vnum": StatusSkillResetSystem.VNUM_SKILL_RESET_SCROLL, "name": "技能重置卷轴", "count": 1} ] var all_sk_sig := {"fired": false, "refunded": 0} sys.all_skills_reset.connect(func(r: int): all_sk_sig["fired"] = true all_sk_sig["refunded"] = r ) var res_all = sys.reset_all_skills(player_skills, player_data, inv, 0) _assert(res_all["ok"] == true, "All skills reset succeeded") _assert(player_skills[1] == 0 and player_skills[2] == 0 and player_skills[3] == 0, "All skills cleared to 0") _assert(player_data["unused_skill_points"] == 42, "Refunded 40 points (2 existing + 40 = 42 total)") _assert(inv[0] == null, "Scroll consumed") _assert(all_sk_sig["fired"] == true, "all_skills_reset signal emitted") _assert(all_sk_sig["refunded"] == 40, "Signal reported 40 refunded points") func _finish() -> void: print("\n=======================================================") print("Status & Skill Reset Parity Tests: %d Passed, %d Failed" % [_passed, _failed]) print("=======================================================\n") if _failed > 0: quit(1) else: quit(0)