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

224 lines
7.8 KiB
GDScript

# test_player_trade_exchange_parity.gd —— 40250 面对面交易与防调包双向锁定系统回归测试
extends SceneTree
const SystemClass = preload("res://player_trade_exchange_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_player_trade_exchange_parity (Batch 26 Direction 3) ===")
_test_trade_session_initialization()
_test_untradeable_items_rejection()
_test_anti_swap_lock_reset_security()
_test_gold_offer_validation()
_test_successful_atomic_exchange()
_test_insufficient_space_rejection()
_test_cancel_trade()
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 _create_player(id: int, name: String, gold: int, inv_size: int = 10) -> Dictionary:
var inv: Array = []
for i in range(inv_size):
inv.append(null)
return {
"id": id,
"name": name,
"gold": gold,
"inv": inv
}
func _test_trade_session_initialization() -> void:
print("\n--- Test 1: Trade Session Initialization & Distance ---")
var trade = SystemClass.new()
var p1 = _create_player(101, "Alice", 500000)
var p2 = _create_player(102, "Bob", 300000)
# 1. Reject distance > 1000
var r_dist = trade.start_exchange(p1, p2, 1200.0)
_assert(r_dist["error_code"] == "TOO_FAR", "Reject trade if distance > 1000")
# 2. Reject self trade
var r_self = trade.start_exchange(p1, p1, 100.0)
_assert(r_self["error_code"] == "CANNOT_TRADE_SELF", "Reject trade with self")
# 3. Successful start
var r_ok = trade.start_exchange(p1, p2, 100.0)
_assert(r_ok["success"] == true, "Trade session opened successfully")
_assert(trade.is_active == true, "Trade is active")
func _test_untradeable_items_rejection() -> void:
print("\n--- Test 2: Untradeable Items Rejection ---")
var trade = SystemClass.new()
var p1 = _create_player(101, "Alice", 500000)
var p2 = _create_player(102, "Bob", 300000)
p1["inv"][0] = { "vnum": 1, "name": "Normal Item", "anti_give": false, "is_soulbound": false }
p1["inv"][1] = { "vnum": 2, "name": "Bound Item", "anti_give": false, "is_soulbound": true }
p1["inv"][2] = { "vnum": 3, "name": "Anti-Give Item", "anti_give": true, "is_soulbound": false }
p1["inv"][3] = { "vnum": 4, "name": "Equipped Item", "is_equipped": true }
trade.start_exchange(p1, p2)
# Soulbound rejected
var r_bound = trade.add_item(101, 1, 0)
_assert(r_bound["error_code"] == "ITEM_UNTRADEABLE", "Soulbound item rejected from trade")
# Anti-give rejected
var r_antigive = trade.add_item(101, 2, 0)
_assert(r_antigive["error_code"] == "ITEM_UNTRADEABLE", "Anti-give flag item rejected from trade")
# Equipped rejected
var r_equip = trade.add_item(101, 3, 0)
_assert(r_equip["error_code"] == "ITEM_UNTRADEABLE", "Equipped item rejected from trade")
# Normal item accepted
var r_norm = trade.add_item(101, 0, 0)
_assert(r_norm["success"] == true, "Tradeable item successfully added to slot 0")
func _test_anti_swap_lock_reset_security() -> void:
print("\n--- Test 3: 40250 Anti-Swap Security (Lock Reset on Modification) ---")
var trade = SystemClass.new()
var p1 = _create_player(101, "Alice", 500000)
var p2 = _create_player(102, "Bob", 300000)
p1["inv"][0] = { "vnum": 10, "name": "Sword" }
p2["inv"][0] = { "vnum": 20, "name": "Shield" }
p2["inv"][1] = { "vnum": 30, "name": "Dagger" }
trade.start_exchange(p1, p2)
trade.add_item(101, 0, 0)
trade.add_item(102, 0, 0)
# Alice locks ready
trade.set_ready(101, true)
_assert(trade.side_a.is_ready == true, "Alice locked ready")
# Bob attempts to add another item (or swap item)
trade.add_item(102, 1, 1)
_assert(trade.side_a.is_ready == false, "Alice's ready lock instantly revoked on Bob item add")
_assert(trade.side_b.is_ready == false, "Bob's ready lock also reset")
# Alice locks ready again
trade.set_ready(101, true)
_assert(trade.side_a.is_ready == true, "Alice locked ready again")
# Bob modifies gold
trade.set_gold(102, 10000)
_assert(trade.side_a.is_ready == false, "Alice's ready lock instantly revoked on Bob gold change")
# Alice locks ready again
trade.set_ready(101, true)
# Bob removes item
trade.remove_item(102, 1)
_assert(trade.side_a.is_ready == false, "Alice's ready lock instantly revoked on Bob item remove")
func _test_gold_offer_validation() -> void:
print("\n--- Test 4: Gold Offer Validation ---")
var trade = SystemClass.new()
var p1 = _create_player(101, "Alice", 100000)
var p2 = _create_player(102, "Bob", 50000)
trade.start_exchange(p1, p2)
# Reject offer exceeding gold
var r_fail = trade.set_gold(101, 150000)
_assert(r_fail["error_code"] == "INSUFFICIENT_GOLD", "Reject gold offer exceeding owned gold")
# Accept valid offer
var r_ok = trade.set_gold(101, 80000)
_assert(r_ok["success"] == true, "Valid gold offer accepted")
_assert(trade.side_a.offered_gold == 80000, "Alice offered 80,000 gold")
func _test_successful_atomic_exchange() -> void:
print("\n--- Test 5: Successful Atomic Trade Execution ---")
var trade = SystemClass.new()
var p1 = _create_player(101, "Alice", 500000, 5)
var p2 = _create_player(102, "Bob", 200000, 5)
p1["inv"][0] = { "vnum": 101, "name": "Dragon Sword+9" }
p2["inv"][0] = { "vnum": 202, "name": "Black Steel Armor+9" }
trade.start_exchange(p1, p2)
# Alice puts Dragon Sword and 100,000 gold
trade.add_item(101, 0, 0)
trade.set_gold(101, 100000)
# Bob puts Black Steel Armor and 40,000 gold
trade.add_item(102, 0, 0)
trade.set_gold(102, 40000)
# Both confirm
var r_ready_a = trade.set_ready(101, true)
_assert(r_ready_a["trade_completed"] == false, "Trade not completed after only Alice ready")
var r_ready_b = trade.set_ready(102, true)
_assert(r_ready_b["trade_completed"] == true, "Trade atomically completed when both ready")
_assert(trade.is_active == false, "Trade session closed")
# Check Gold:
# Alice: 500,000 - 100,000 + 40,000 = 440,000
_assert(p1["gold"] == 440000, "Alice gold correctly calculated (440,000)")
# Bob: 200,000 - 40,000 + 100,000 = 260,000
_assert(p2["gold"] == 260000, "Bob gold correctly calculated (260,000)")
# Check Items:
# Alice now has Black Steel Armor
var alice_has_armor := false
for it in p1["inv"]:
if it != null and it.get("vnum") == 202:
alice_has_armor = true
_assert(alice_has_armor == true, "Alice received Bob's Black Steel Armor")
# Bob now has Dragon Sword
var bob_has_sword := false
for it in p2["inv"]:
if it != null and it.get("vnum") == 101:
bob_has_sword = true
_assert(bob_has_sword == true, "Bob received Alice's Dragon Sword")
func _test_insufficient_space_rejection() -> void:
print("\n--- Test 6: CheckSpace Rejection (Inventory Full) ---")
var trade = SystemClass.new()
# Bob's inventory has only 1 slot, and it is full with an item he is NOT trading
var p1 = _create_player(101, "Alice", 500000, 5)
var p2 = _create_player(102, "Bob", 200000, 1)
p1["inv"][0] = { "vnum": 101, "name": "Item A" }
p2["inv"][0] = { "vnum": 999, "name": "Untraded Item (Full)" } # Not trading this
trade.start_exchange(p1, p2)
trade.add_item(101, 0, 0) # Alice offers Item A
trade.set_ready(101, true)
var res = trade.set_ready(102, true)
_assert(res["trade_completed"] == false, "Trade aborted due to space check")
_assert(res["error_code"] == "PLAYER_B_INSUFFICIENT_SPACE", "Detected Bob has insufficient inventory space")
func _test_cancel_trade() -> void:
print("\n--- Test 7: Trade Cancellation ---")
var trade = SystemClass.new()
var p1 = _create_player(101, "Alice", 500000, 5)
var p2 = _create_player(102, "Bob", 200000, 5)
trade.start_exchange(p1, p2)
_assert(trade.is_active == true, "Trade started")
trade.cancel_trade()
_assert(trade.is_active == false, "Trade canceled and inactive")