- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
118 lines
5.9 KiB
GDScript
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)
|