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

118 lines
5.9 KiB
GDScript

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