- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
163 lines
6.4 KiB
GDScript
163 lines
6.4 KiB
GDScript
# test_biologist_chaegirab_research_parity.gd —— 40250 生物学者车齿木研究链 1:1 回归测试套件
|
|
extends SceneTree
|
|
|
|
const BiologistChaegirabResearchSystem = preload("res://biologist_chaegirab_research_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_biologist_chaegirab_research_parity (Direction 1: Biologist Research 1:1) ===")
|
|
_test_stage_progression_and_sample_validation()
|
|
_test_sample_completion_and_soul_stone_capstone()
|
|
_test_subsequent_stages_and_stat_stacking()
|
|
_test_serialization()
|
|
_finish()
|
|
|
|
func _test_stage_progression_and_sample_validation() -> void:
|
|
print("\n--- Test 1: Stage Requirements & Sample Item Validations ---")
|
|
var sys := BiologistChaegirabResearchSystem.new()
|
|
var inv: Array = [
|
|
{"vnum": 10, "name": "木剑", "count": 1},
|
|
{"vnum": 30006, "name": "兽人牙齿", "count": 5}
|
|
]
|
|
|
|
# 1. Level too low (< 30)
|
|
var res_low = sys.submit_sample(inv, 1, 25)
|
|
_assert(res_low["ok"] == false, "Rejected: level 25 < 30")
|
|
_assert(res_low["reason"] == "PLAYER_LEVEL_TOO_LOW", "Reason is PLAYER_LEVEL_TOO_LOW")
|
|
|
|
# 2. Wrong sample item
|
|
var res_wrong = sys.submit_sample(inv, 0, 35)
|
|
_assert(res_wrong["ok"] == false, "Rejected: wrong item (Wood Sword)")
|
|
_assert(res_wrong["reason"] == "WRONG_SAMPLE_ITEM", "Reason is WRONG_SAMPLE_ITEM")
|
|
|
|
# 3. Valid sample submission (force success)
|
|
var sub_sig := {"fired": false, "cur": 0}
|
|
sys.sample_submitted.connect(func(_n: String, _s: bool, c: int, _t: int):
|
|
sub_sig["fired"] = true
|
|
sub_sig["cur"] = c
|
|
)
|
|
|
|
var res_succ = sys.submit_sample(inv, 1, 35, true)
|
|
_assert(res_succ["ok"] == true, "Orc Tooth submitted successfully")
|
|
_assert(res_succ["current"] == 1, "Progress is 1/10")
|
|
_assert(inv[1]["count"] == 4, "Orc tooth count decremented 5 -> 4")
|
|
_assert(sub_sig["fired"] == true, "sample_submitted signal emitted")
|
|
|
|
func _test_sample_completion_and_soul_stone_capstone() -> void:
|
|
print("\n--- Test 2: Sample Completion & Capstone Soul Stone Delivery ---")
|
|
var sys := BiologistChaegirabResearchSystem.new()
|
|
var player_stats := {"move_speed": 100}
|
|
var inv: Array = [
|
|
{"vnum": 30006, "name": "兽人牙齿", "count": 10},
|
|
{"vnum": 30220, "name": "兽人灵魂石", "count": 1}
|
|
]
|
|
|
|
# Submit remaining 9 teeth to hit 10/10
|
|
for i in range(10):
|
|
sys.submit_sample(inv, 0, 35, true)
|
|
|
|
_assert(sys.current_samples == 10, "Collected all 10 Orc Teeth")
|
|
_assert(sys.waiting_for_soul_stone == true, "Now waiting for soul stone")
|
|
|
|
# Cannot submit more teeth
|
|
inv[0] = {"vnum": 30006, "name": "兽人牙齿", "count": 1}
|
|
var res_excess = sys.submit_sample(inv, 0, 35, true)
|
|
_assert(res_excess["ok"] == false, "Excess sample submission blocked")
|
|
_assert(res_excess["reason"] == "WAITING_FOR_SOUL_STONE", "Reason is WAITING_FOR_SOUL_STONE")
|
|
|
|
var cap_sig := {"fired": false, "stone": ""}
|
|
sys.capstone_submitted.connect(func(sn: String, _st: String):
|
|
cap_sig["fired"] = true
|
|
cap_sig["stone"] = sn
|
|
)
|
|
|
|
# Submit wrong soul stone
|
|
inv[0] = {"vnum": 30221, "name": "诅咒灵魂石", "count": 1}
|
|
var res_wrong_stone = sys.submit_soul_stone(inv, 0, player_stats)
|
|
_assert(res_wrong_stone["ok"] == false, "Wrong soul stone rejected")
|
|
_assert(res_wrong_stone["reason"] == "WRONG_SOUL_STONE", "Reason is WRONG_SOUL_STONE")
|
|
|
|
# Submit valid Orc Soul Stone (slot 1)
|
|
var res_stone_succ = sys.submit_soul_stone(inv, 1, player_stats)
|
|
_assert(res_stone_succ["ok"] == true, "Orc Soul Stone submitted")
|
|
_assert(player_stats["move_speed"] == 110, "Permanent move speed +10 applied (100 -> 110)")
|
|
_assert(sys.permanent_bonuses["move_speed"] == 10, "Permanent bonus recorded")
|
|
_assert(sys.current_stage_index == 1, "Advanced to Stage 2 (Curse Book)")
|
|
_assert(sys.current_samples == 0, "Current samples reset to 0")
|
|
_assert(sys.waiting_for_soul_stone == false, "No longer waiting for soul stone")
|
|
_assert(cap_sig["fired"] == true, "capstone_submitted signal emitted")
|
|
|
|
func _test_subsequent_stages_and_stat_stacking() -> void:
|
|
print("\n--- Test 3: Subsequent Stages (Curse Book & Demon Keepsake) Stacking ---")
|
|
var sys := BiologistChaegirabResearchSystem.new()
|
|
var player_stats := {"move_speed": 100, "cast_speed_pct": 0, "defense": 200}
|
|
|
|
# Stage 1: Complete Orc Tooth (+10 move speed)
|
|
var inv1: Array = [
|
|
{"vnum": 30006, "name": "兽人牙齿", "count": 10},
|
|
{"vnum": 30220, "name": "兽人灵魂石", "count": 1}
|
|
]
|
|
for i in range(10):
|
|
sys.submit_sample(inv1, 0, 30, true)
|
|
sys.submit_soul_stone(inv1, 1, player_stats)
|
|
|
|
# Stage 2: Complete Curse Book (+5% cast speed)
|
|
var inv2: Array = [
|
|
{"vnum": 30047, "name": "诅咒之书", "count": 15},
|
|
{"vnum": 30221, "name": "诅咒灵魂石", "count": 1}
|
|
]
|
|
for i in range(15):
|
|
sys.submit_sample(inv2, 0, 40, true)
|
|
sys.submit_soul_stone(inv2, 1, player_stats)
|
|
|
|
# Stage 3: Complete Demon Keepsake (+60 def)
|
|
var inv3: Array = [
|
|
{"vnum": 30015, "name": "恶魔纪念物", "count": 15},
|
|
{"vnum": 30222, "name": "恶魔灵魂石", "count": 1}
|
|
]
|
|
for i in range(15):
|
|
sys.submit_sample(inv3, 0, 50, true)
|
|
sys.submit_soul_stone(inv3, 1, player_stats)
|
|
|
|
_assert(player_stats["move_speed"] == 110, "Stage 1 bonus active (+10)")
|
|
_assert(player_stats["cast_speed_pct"] == 5, "Stage 2 bonus active (+5%)")
|
|
_assert(player_stats["defense"] == 260, "Stage 3 bonus active (+60)")
|
|
_assert(sys.completed_stages.size() == 3, "3 stages completed in total")
|
|
|
|
func _test_serialization() -> void:
|
|
print("\n--- Test 4: Serialization & Restoration ---")
|
|
var sys1 := BiologistChaegirabResearchSystem.new()
|
|
sys1.current_stage_index = 4 # Stage 5: Zelkova Branch
|
|
sys1.current_samples = 18
|
|
sys1.waiting_for_soul_stone = false
|
|
sys1.completed_stages = [1, 2, 3, 4]
|
|
sys1.permanent_bonuses = {"move_speed": 10, "cast_speed_pct": 5, "defense": 60, "attack_power": 50}
|
|
|
|
var data = sys1.serialize()
|
|
var sys2 := BiologistChaegirabResearchSystem.new()
|
|
sys2.deserialize(data)
|
|
|
|
_assert(sys2.current_stage_index == 4, "Restored stage index 4")
|
|
_assert(sys2.current_samples == 18, "Restored samples 18")
|
|
_assert(sys2.completed_stages.size() == 4, "Restored 4 completed stages")
|
|
_assert(sys2.permanent_bonuses["attack_power"] == 50, "Restored attack bonus 50")
|
|
|
|
func _finish() -> void:
|
|
print("\n=======================================================")
|
|
print("Biologist Research Parity Tests: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("=======================================================\n")
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|