- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
148 lines
5.9 KiB
GDScript
148 lines
5.9 KiB
GDScript
# test_inventory_expansion_parity.gd —— 40250 背包扩展凭证与阶梯解锁系统回归测试
|
|
extends SceneTree
|
|
|
|
const SystemClass = preload("res://inventory_expansion_system.gd")
|
|
|
|
var _passed: int = 0
|
|
var _failed: int = 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_inventory_expansion_parity (Batch 26 Direction 4) ===")
|
|
_test_default_unlocked_baseline()
|
|
_test_key_cost_curve()
|
|
_test_stage_by_stage_unlocking()
|
|
_test_locked_slot_item_placement_rejection()
|
|
_test_multislot_item_boundary_and_lock_check()
|
|
_test_full_180_slots_unlock_completion()
|
|
|
|
print("\n=== Result: %d Passed, %d Failed ===" % [_passed, _failed])
|
|
if _failed == 0:
|
|
print("ALL TESTS PASSED!\n")
|
|
else:
|
|
printerr("SOME TESTS FAILED!\n")
|
|
quit(0 if _failed == 0 else 1)
|
|
|
|
func _test_default_unlocked_baseline() -> void:
|
|
print("\n--- Test 1: Default Baseline (Pages 1 & 2 = 90 Slots Unlocked) ---")
|
|
var inv = SystemClass.new()
|
|
_assert(inv.get_unlocked_slot_count() == 90, "Baseline has 90 unlocked slots (2 pages)")
|
|
_assert(inv.is_slot_unlocked(0) == true, "Slot 0 (Page 1) is unlocked")
|
|
_assert(inv.is_slot_unlocked(44) == true, "Slot 44 (Page 1 end) is unlocked")
|
|
_assert(inv.is_slot_unlocked(45) == true, "Slot 45 (Page 2 start) is unlocked")
|
|
_assert(inv.is_slot_unlocked(89) == true, "Slot 89 (Page 2 end) is unlocked")
|
|
|
|
# Locked slots on Pages 3 & 4
|
|
_assert(inv.is_slot_unlocked(90) == false, "Slot 90 (Page 3 start) is locked")
|
|
_assert(inv.is_slot_unlocked(134) == false, "Slot 134 (Page 3 end) is locked")
|
|
_assert(inv.is_slot_unlocked(135) == false, "Slot 135 (Page 4 start) is locked")
|
|
_assert(inv.is_slot_unlocked(179) == false, "Slot 179 (Page 4 end) is locked")
|
|
|
|
func _test_key_cost_curve() -> void:
|
|
print("\n--- Test 2: 18-Stage Key Cost Curve (Official 40250) ---")
|
|
var inv = SystemClass.new()
|
|
_assert(inv.get_next_stage_key_cost() == 1, "Stage 1 costs 1 key")
|
|
|
|
inv.unlocked_stages = 5
|
|
_assert(inv.get_next_stage_key_cost() == 1, "Stage 6 costs 1 key")
|
|
|
|
inv.unlocked_stages = 6
|
|
_assert(inv.get_next_stage_key_cost() == 2, "Stage 7 costs 2 keys")
|
|
|
|
inv.unlocked_stages = 11
|
|
_assert(inv.get_next_stage_key_cost() == 2, "Stage 12 costs 2 keys")
|
|
|
|
inv.unlocked_stages = 12
|
|
_assert(inv.get_next_stage_key_cost() == 3, "Stage 13 costs 3 keys")
|
|
|
|
inv.unlocked_stages = 17
|
|
_assert(inv.get_next_stage_key_cost() == 4, "Stage 18 costs 4 keys")
|
|
|
|
# Sum of all stages
|
|
var total_keys := 0
|
|
for cost in SystemClass.STAGE_KEY_COSTS:
|
|
total_keys += cost
|
|
_assert(total_keys == 37, "Total keys for all 18 stages is exactly 37 keys")
|
|
|
|
func _test_stage_by_stage_unlocking() -> void:
|
|
print("\n--- Test 3: Stage-by-Stage Unlocking and Key Deduction ---")
|
|
var inv = SystemClass.new()
|
|
|
|
# Insufficient keys
|
|
var r_fail = inv.unlock_next_stage(0)
|
|
_assert(r_fail["error_code"] == "INSUFFICIENT_KEYS", "Reject unlock if keys < required")
|
|
|
|
# Unlock stage 1 (costs 1 key)
|
|
var r_stage1 = inv.unlock_next_stage(10)
|
|
_assert(r_stage1["success"] == true, "Stage 1 unlocked")
|
|
_assert(r_stage1["keys_deducted"] == 1, "1 key deducted")
|
|
_assert(r_stage1["remaining_keys"] == 9, "9 keys remain")
|
|
_assert(r_stage1["unlocked_slots"] == 95, "Unlocked slots increased to 95")
|
|
_assert(inv.is_slot_unlocked(90) == true, "Slot 90 now unlocked")
|
|
_assert(inv.is_slot_unlocked(94) == true, "Slot 94 now unlocked")
|
|
_assert(inv.is_slot_unlocked(95) == false, "Slot 95 still locked")
|
|
|
|
func _test_locked_slot_item_placement_rejection() -> void:
|
|
print("\n--- Test 4: Locked Slot Item Placement Rejection ---")
|
|
var inv = SystemClass.new()
|
|
var sword = { "vnum": 10, "name": "Sword+0" }
|
|
|
|
# Put into unlocked slot 0
|
|
var r_ok = inv.put_item(0, sword, 1)
|
|
_assert(r_ok["success"] == true, "Placed item into unlocked slot 0")
|
|
|
|
# Put into locked slot 90
|
|
var r_lock = inv.put_item(90, sword, 1)
|
|
_assert(r_lock["error_code"] == "SLOT_LOCKED", "Reject putting item into locked slot 90")
|
|
|
|
func _test_multislot_item_boundary_and_lock_check() -> void:
|
|
print("\n--- Test 5: Multi-Slot Item Boundary & Partial Lock Detection ---")
|
|
var inv = SystemClass.new()
|
|
var greatsword = { "vnum": 3000, "name": "Glaive+0" } # Height = 3
|
|
|
|
# Slot 80 is Page 2, row 7 (cells 80, 85, 90).
|
|
# But row 7 + 3 = 10 > 9 -> PAGE_OVERFLOW!
|
|
var r_overflow = inv.can_place_item_at(80, 3)
|
|
_assert(r_overflow["error_code"] == "PAGE_OVERFLOW", "Reject multi-slot item crossing page bottom")
|
|
|
|
# Slot 70 is Page 2, row 5 (cells 70, 75, 80). row 5 + 3 = 8 <= 9 -> OK!
|
|
var r_valid = inv.can_place_item_at(70, 3)
|
|
_assert(r_valid["can_place"] == true, "Can place 3-slot item safely inside page 2")
|
|
|
|
# Now unlock Stage 1 (slots 90..94 unlocked)
|
|
inv.unlock_next_stage(1)
|
|
# Try to place 2-slot item at slot 91 (needs 91 and 96; 91 is unlocked, but 96 is locked!)
|
|
var r_part_locked = inv.can_place_item_at(91, 2)
|
|
_assert(r_part_locked["error_code"] == "SLOT_LOCKED", "Reject multi-slot item when lower cell (96) is locked")
|
|
|
|
# But 1-slot item at 91 is fine
|
|
var r_single_ok = inv.can_place_item_at(91, 1)
|
|
_assert(r_single_ok["can_place"] == true, "Single slot item at 91 can be placed")
|
|
|
|
func _test_full_180_slots_unlock_completion() -> void:
|
|
print("\n--- Test 6: Full 180 Slots Unlocking (All 18 Stages) ---")
|
|
var inv = SystemClass.new()
|
|
var total_keys_available := 50
|
|
|
|
# Unlock all 18 stages
|
|
for s in range(18):
|
|
var res = inv.unlock_next_stage(total_keys_available)
|
|
_assert(res["success"] == true, "Stage %d unlocked successfully" % (s + 1))
|
|
total_keys_available = res["remaining_keys"]
|
|
|
|
_assert(inv.unlocked_stages == 18, "All 18 stages unlocked")
|
|
_assert(inv.get_unlocked_slot_count() == 180, "All 180 slots unlocked")
|
|
_assert(inv.is_slot_unlocked(179) == true, "Final slot 179 is unlocked")
|
|
_assert(total_keys_available == 50 - 37, "Remaining keys is 13 (50 - 37)")
|
|
|
|
# Attempting 19th stage
|
|
var r_max = inv.unlock_next_stage(total_keys_available)
|
|
_assert(r_max["error_code"] == "ALREADY_MAX_EXPANDED", "Reject unlocking beyond 18 stages")
|