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

168 lines
6.4 KiB
GDScript

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