# test_skill_book_parity.gd —— 技能书研读与进阶突破系统 1:1 回归测试套件 # 运行方式: # /opt/homebrew/bin/godot --headless --path metin2-client/project --script test_skill_book_parity.gd extends SceneTree const SkillBookSystem = preload("res://skill_book_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_skill_book_parity (Direction 2: Skill Books & M->G->P 1:1) ===") _test_need_books_formula() _test_preconditions() _test_reading_flow_and_scrolls() _test_breakthrough_to_grandmaster() _test_soul_stone_training() _finish() func _test_need_books_formula() -> void: print("\n--- Test 1: 40250 Need Books Formula (need_bookcount = level - 20) ---") _assert(SkillBookSystem.get_need_books(21) == 1, "M1 (Lv 21) requires 1 book") _assert(SkillBookSystem.get_need_books(22) == 2, "M2 (Lv 22) requires 2 books") _assert(SkillBookSystem.get_need_books(25) == 5, "M5 (Lv 25) requires 5 books") _assert(SkillBookSystem.get_need_books(30) == 10, "M10 (Lv 30) requires 10 books") func _test_preconditions() -> void: print("\n--- Test 2: Preconditions (EXP, Tier, Cooldown) ---") var now := 100000 # 1. Reject if normal tier (< 20) var r1 := SkillBookSystem.read_skill_book(1, 15, SkillBookSystem.MASTER_NORMAL, 0, 50000, 0, now, false, false) _assert(r1["result"] == SkillBookSystem.Result.ERR_NOT_MASTER_TIER, "Reject normal tier (< 20) skill") # 2. Reject if EXP < 20,000 var r2 := SkillBookSystem.read_skill_book(1, 21, SkillBookSystem.MASTER_M, 0, 19999, 0, now, false, false) _assert(r2["result"] == SkillBookSystem.Result.ERR_INSUFFICIENT_EXP, "Reject if player EXP < 20,000") # 3. Reject if on cooldown var r3 := SkillBookSystem.read_skill_book(1, 21, SkillBookSystem.MASTER_M, 0, 50000, now + 3600, now, false, false) _assert(r3["result"] == SkillBookSystem.Result.ERR_COOLDOWN, "Reject if on 24h cooldown") # 4. Exorcism scroll (71001) bypasses cooldown var r4 := SkillBookSystem.read_skill_book(1, 21, SkillBookSystem.MASTER_M, 0, 50000, now + 3600, now, true, false, 90) _assert(r4["result"] == SkillBookSystem.Result.SUCCESS_LEVEL_UP, "Exorcism scroll bypasses cooldown") _assert(r4["consumed_exorcism"] == true, "Exorcism scroll consumed") # 5. Reject if already Grandmaster (G1) var r5 := SkillBookSystem.read_skill_book(1, 31, SkillBookSystem.MASTER_G, 0, 50000, 0, now, false, false) _assert(r5["result"] == SkillBookSystem.Result.ERR_ALREADY_G_OR_P, "Reject if already Grandmaster (G1)") func _test_reading_flow_and_scrolls() -> void: print("\n--- Test 3: Reading Probabilities & Hermit's Advice (71094) ---") var now := 100000 # Normal roll 50 (<= 65) -> Failure var fail_res := SkillBookSystem.read_skill_book(1, 22, SkillBookSystem.MASTER_M, 0, 50000, 0, now, false, false, 50) _assert(fail_res["result"] == SkillBookSystem.Result.FAIL_READ, "Roll 50 fails (base fail percent 65)") _assert(fail_res["exp_consumed"] == 20000, "20,000 EXP consumed on failure") _assert(fail_res["read_count"] == 0, "read_count not incremented on failure") _assert(fail_res["next_read_time"] == now + 86400, "24h cooldown applied on failure") # Hermit's Advice roll 10 -> Guaranteed Success (fail_percent 0) var hermit_res := SkillBookSystem.read_skill_book(1, 22, SkillBookSystem.MASTER_M, 0, 50000, 0, now, false, true, 10) _assert(hermit_res["result"] == SkillBookSystem.Result.SUCCESS_READ_ONCE, "Hermit advice guarantees success even on roll 10") _assert(hermit_res["consumed_hermit"] == true, "Hermit advice consumed") _assert(hermit_res["read_count"] == 1, "M2 (needs 2) read_count becomes 1") # Read second book to complete M2 -> M3 var up_res := SkillBookSystem.read_skill_book(1, 22, SkillBookSystem.MASTER_M, 1, 50000, 0, now, true, true, 10) _assert(up_res["result"] == SkillBookSystem.Result.SUCCESS_LEVEL_UP, "Second book triggers level up") _assert(up_res["level"] == 23, "Level upgraded to 23 (M3)") _assert(up_res["read_count"] == 0, "read_count reset to 0 upon level up") func _test_breakthrough_to_grandmaster() -> void: print("\n--- Test 4: Breakthrough from M10 (Lv 29/30) to Grandmaster (G1) ---") var now := 100000 # M10 (level 29), already read 9 books, needs 9 (level - 20 = 9) var g_res := SkillBookSystem.read_skill_book(1, 29, SkillBookSystem.MASTER_M, 8, 50000, 0, now, true, true, 10) _assert(g_res["result"] == SkillBookSystem.Result.SUCCESS_LEVEL_UP, "M10 completes final book") _assert(g_res["level"] == 30, "Skill level advances to 30") _assert(g_res["master_type"] == SkillBookSystem.MASTER_G, "Skill grade transforms into Grandmaster (MASTER_G)") func _test_soul_stone_training() -> void: print("\n--- Test 5: Soul Stone (50513) G1 -> P Progression ---") # G1 (Lv 30) -> G2, karma 5000 (positive) var s1 := SkillBookSystem.soul_stone_train(1, 30, SkillBookSystem.MASTER_G, 5000, 20) _assert(s1["success"] == true, "Soul stone training succeeds on roll 20 (<= 30%)") _assert(s1["level"] == 31, "Advances to G2 (Lv 31)") _assert(s1["karma_consumed"] == 1000, "G1 training consumed 1,000 karma") # Evil character (karma -2000): karma cost doubled! var s2 := SkillBookSystem.soul_stone_train(1, 30, SkillBookSystem.MASTER_G, -2000, 20) _assert(s2["karma_consumed"] == 2000, "Negative karma doubles consumption (2,000 karma)") # G10 (Lv 39) -> P (Lv 40) var s_p := SkillBookSystem.soul_stone_train(1, 39, SkillBookSystem.MASTER_G, 10000, 15) _assert(s_p["success"] == true, "G10 succeeds into Perfect Master") _assert(s_p["level"] == 40, "Skill level reaches 40") _assert(s_p["master_type"] == SkillBookSystem.MASTER_P, "Skill grade transforms into Perfect Master (MASTER_P / 'P')") func _finish() -> void: print("\n=== SUMMARY: %d passed, %d failed ===" % [_passed, _failed]) if _failed == 0: print("ALL TESTS PASSED!\n") quit(0) else: printerr("SOME TESTS FAILED!\n") quit(1)