- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
122 lines
5.0 KiB
GDScript
122 lines
5.0 KiB
GDScript
# test_item_split_parity.gd —— 40250 物品精准拆分与快捷操作 1:1 纯净回归测试套件
|
|
extends SceneTree
|
|
|
|
const ItemSplitSystem = preload("res://item_split_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_item_split_parity (Direction 3: Item Split & Quick Sell 1:1) ===")
|
|
_test_item_stack_splitting()
|
|
_test_quick_sell_to_shop()
|
|
_test_drop_safety_confirmation()
|
|
_finish()
|
|
|
|
func _test_item_stack_splitting() -> void:
|
|
print("\n--- Test 1: Stack Splitting (Shift + Left Click) ---")
|
|
var iss := ItemSplitSystem.new()
|
|
var inv: Array = [
|
|
{"vnum": 27003, "name": "红药水(大)", "count": 200}, # Slot 0
|
|
{"vnum": 10, "name": "木剑+0", "count": 1}, # Slot 1 (single)
|
|
null, # Slot 2 (empty)
|
|
null # Slot 3 (empty)
|
|
]
|
|
|
|
# 1. Split single count item
|
|
var r_single = iss.split_item(1, 1, 2, inv)
|
|
_assert(r_single["ok"] == false and r_single["reason"] == "NOT_SPLITTABLE", "Single count item rejected for split")
|
|
|
|
# 2. Split count >= total count
|
|
var r_overflow = iss.split_item(0, 200, 2, inv)
|
|
_assert(r_overflow["ok"] == false and r_overflow["reason"] == "INVALID_SPLIT_COUNT", "Split count >= total rejected")
|
|
|
|
# 3. Valid split: Split 50 from 200 into slot 2
|
|
var split_signal := {"fired": false, "src": -1, "dst": -1, "cnt": 0}
|
|
iss.item_split.connect(func(s: int, d: int, c: int):
|
|
split_signal["fired"] = true
|
|
split_signal["src"] = s
|
|
split_signal["dst"] = d
|
|
split_signal["cnt"] = c
|
|
)
|
|
|
|
var r_split = iss.split_item(0, 50, 2, inv)
|
|
_assert(r_split["ok"] == true, "Split 50 potions succeeded")
|
|
_assert(inv[0]["count"] == 150, "Source slot 0 has 150 remaining")
|
|
_assert(inv[2] != null and inv[2]["count"] == 50, "Destination slot 2 has 50")
|
|
_assert(split_signal["fired"] == true and split_signal["cnt"] == 50, "item_split signal fired")
|
|
|
|
# 4. Auto-find empty slot when target_slot is -1
|
|
var r_auto = iss.split_item(0, 30, -1, inv)
|
|
_assert(r_auto["ok"] == true, "Auto-find split succeeded")
|
|
_assert(r_auto["target_slot"] == 3, "Auto-placed into slot 3")
|
|
_assert(inv[3] != null and inv[3]["count"] == 30, "Slot 3 has 30 count")
|
|
_assert(inv[0]["count"] == 120, "Slot 0 has 120 remaining")
|
|
|
|
func _test_quick_sell_to_shop() -> void:
|
|
print("\n--- Test 2: Quick Sell to NPC Shop (Ctrl + Right Click) ---")
|
|
var iss := ItemSplitSystem.new()
|
|
var player_data := {"gold": 1000}
|
|
var inv: Array = [
|
|
{"vnum": 10, "name": "木剑+0", "count": 1, "sale_price": 50, "is_locked": true}, # Locked
|
|
{"vnum": 27001, "name": "小红药水", "count": 20, "sale_price": 100} # 20 x 100 = 2000
|
|
]
|
|
|
|
# 1. Locked item cannot be sold
|
|
var r_lock = iss.quick_sell_item(0, inv, player_data)
|
|
_assert(r_lock["ok"] == false and r_lock["reason"] == "ITEM_LOCKED", "Locked item quick sell blocked")
|
|
|
|
# 2. Valid quick sell
|
|
var sell_signal := {"fired": false, "gold": 0}
|
|
iss.item_quick_sold.connect(func(_s: int, g: int, _n: String):
|
|
sell_signal["fired"] = true
|
|
sell_signal["gold"] = g
|
|
)
|
|
|
|
var r_sell = iss.quick_sell_item(1, inv, player_data)
|
|
_assert(r_sell["ok"] == true, "Quick sell succeeded")
|
|
_assert(inv[1] == null, "Sold slot cleared to null")
|
|
_assert(player_data["gold"] == 3000, "Earned 2000 Yang (1000 -> 3000)")
|
|
_assert(sell_signal["fired"] == true and sell_signal["gold"] == 2000, "item_quick_sold signal fired")
|
|
|
|
func _test_drop_safety_confirmation() -> void:
|
|
print("\n--- Test 3: Drop Safety Check for Valuable Equipment ---")
|
|
var iss := ItemSplitSystem.new()
|
|
|
|
# 1. Locked item
|
|
var locked_item := {"vnum": 169, "name": "战神之剑+9", "is_locked": true}
|
|
var r1 = iss.check_drop_safety(locked_item)
|
|
_assert(r1["can_drop"] == false and r1["reason"] == "ITEM_LOCKED", "Locked item drop blocked")
|
|
|
|
# 2. +9 equipment requires confirmation
|
|
var high_refine := {"vnum": 169, "name": "战神之剑+9", "refine_level": 9, "is_locked": false}
|
|
var r2 = iss.check_drop_safety(high_refine)
|
|
_assert(r2["can_drop"] == true and r2["require_confirm"] == true, "+9 weapon requires confirmation")
|
|
|
|
# 3. Item with bonus attributes requires confirmation
|
|
var bonus_item := {"vnum": 10, "name": "木剑+0", "attributes": [{"type": 1, "value": 500}], "is_locked": false}
|
|
var r3 = iss.check_drop_safety(bonus_item)
|
|
_assert(r3["can_drop"] == true and r3["require_confirm"] == true, "Item with bonuses requires confirmation")
|
|
|
|
# 4. Plain +0 item requires no confirmation
|
|
var plain_item := {"vnum": 10, "name": "木剑+0", "refine_level": 0, "is_locked": false}
|
|
var r4 = iss.check_drop_safety(plain_item)
|
|
_assert(r4["can_drop"] == true and r4["require_confirm"] == false, "Plain item drops without confirmation")
|
|
|
|
func _finish() -> void:
|
|
print("\n==========================================")
|
|
print("Item Split Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("==========================================")
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|