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

173 lines
6.8 KiB
GDScript

# test_offline_bazaar_market_parity.gd —— 40250 单人单机广场离线摆摊与虚拟交易 1:1 回归测试套件
extends SceneTree
const OfflineBazaarMarketSystem = preload("res://offline_bazaar_market_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_offline_bazaar_market_parity (Direction 1: Offline Bazaar Market 1:1) ===")
_test_npc_stalls_browse_and_buy()
_test_buy_validation_errors()
_test_player_stall_open()
_test_offline_sales_and_revenue()
_test_stall_close_and_item_return()
_finish()
func _test_npc_stalls_browse_and_buy() -> void:
print("\n--- Test 1: NPC Stalls Browsing & Purchasing ---")
var mkt := OfflineBazaarMarketSystem.new()
var stalls = mkt.get_all_stalls()
_assert(stalls.size() == 3, "Found 3 default NPC stalls in village square")
# Browse Stall 1 (Materials)
var b_res = mkt.browse_stall(1)
_assert(b_res["ok"] == true and b_res["owner"] == "阿隆", "Browsed Stall 1 (阿隆)")
_assert(b_res["items"].size() == 4, "Stall 1 has 4 goods")
# Buy Pearl (27993) from Stall 1
var player := {"gold": 2000000}
var inv: Array = []
inv.resize(10)
for i in range(10):
inv[i] = null
var buy_sig := {"fired": false, "name": "", "cost": 0}
mkt.item_bought_from_stall.connect(func(_sid: int, iname: String, _c: int, cost: int):
buy_sig["fired"] = true
buy_sig["name"] = iname
buy_sig["cost"] = cost
)
# Buy 2 pearls: 500,000 * 2 = 1,000,000 gold
var buy_res = mkt.buy_from_stall(1, 0, 2, player, inv)
_assert(buy_res["ok"] == true, "Purchased 2 pearls")
_assert(player["gold"] == 1000000, "Gold deducted 2M -> 1M")
_assert(inv[0]["vnum"] == 27993 and inv[0]["count"] == 2, "Pearls placed in slot 0")
_assert(mkt.npc_stalls[1]["items"][0]["stock"] == 3, "Stock decreased 5 -> 3")
_assert(buy_sig["fired"] == true and buy_sig["name"] == "珍珠", "item_bought_from_stall signal fired")
func _test_buy_validation_errors() -> void:
print("\n--- Test 2: Purchase Error Validations ---")
var mkt := OfflineBazaarMarketSystem.new()
var player := {"gold": 100} # Very poor
var inv: Array = [null]
# Not enough gold
var fail_gold = mkt.buy_from_stall(1, 0, 1, player, inv)
_assert(fail_gold["ok"] == false and fail_gold["reason"] == "NOT_ENOUGH_GOLD", "Rejected: not enough gold")
# Out of stock
player["gold"] = 999999999
var fail_stock = mkt.buy_from_stall(1, 0, 999, player, inv)
_assert(fail_stock["ok"] == false and fail_stock["reason"] == "OUT_OF_STOCK", "Rejected: out of stock")
# Full inventory
var full_inv := [{"vnum": 1}]
var fail_inv = mkt.buy_from_stall(1, 0, 1, player, full_inv)
_assert(fail_inv["ok"] == false and fail_inv["reason"] == "INVENTORY_FULL", "Rejected: inventory full")
func _test_player_stall_open() -> void:
print("\n--- Test 3: Opening Private Player Stall (50200 Bundle) ---")
var mkt := OfflineBazaarMarketSystem.new()
var inv: Array = [
{"vnum": OfflineBazaarMarketSystem.ITEM_BUNDLE_VNUM, "name": "摆摊包袱", "count": 1},
{"vnum": 10, "name": "巨剑+9", "count": 1},
{"vnum": 27003, "name": "红色药水(特大)", "count": 100}
]
var open_sig := {"fired": false, "title": "", "count": 0}
mkt.player_stall_opened.connect(func(t: String, c: int):
open_sig["fired"] = true
open_sig["title"] = t
open_sig["count"] = c
)
# Try open without bundle
var no_bundle_inv: Array = [{"vnum": 10, "count": 1}]
var fail_bundle = mkt.open_player_stall("我的摊位", [{"slot": 0, "price": 1000}], no_bundle_inv, true)
_assert(fail_bundle["ok"] == false and fail_bundle["reason"] == "NEED_BUNDLE_ITEM", "Rejected without bundle")
# Open stall with bundle and 2 items
var items_to_list := [
{"slot": 1, "price": 500000, "count": 1},
{"slot": 2, "price": 200, "count": 50} # sell 50 out of 100
]
var res = mkt.open_player_stall("【东方极速特惠商铺】", items_to_list, inv, true)
_assert(res["ok"] == true, "Player stall opened")
_assert(inv[0] == null, "Bundle item (50200) consumed")
_assert(inv[1] == null, "Listed Sword removed from inventory slot 1")
_assert(inv[2]["count"] == 50, "Potion count halved in inventory (100 - 50 = 50)")
_assert(mkt.player_stall["is_open"] == true, "Stall is open")
_assert(mkt.player_stall["items"].size() == 2, "2 goods listed in stall")
_assert(open_sig["fired"] == true and open_sig["count"] == 2, "player_stall_opened signal fired")
func _test_offline_sales_and_revenue() -> void:
print("\n--- Test 4: Simulated Offline Sales & Revenue Collection ---")
var mkt := OfflineBazaarMarketSystem.new()
var inv: Array = [
{"vnum": 50200, "count": 1},
{"vnum": 28430, "name": "战士灵石+4", "count": 1}
]
mkt.open_player_stall("【灵石专卖】", [{"slot": 1, "price": 1000000, "count": 1}], inv, true)
_assert(mkt.player_stall["items"].size() == 1, "1 item listed")
# Simulate 30 minutes of offline time
var sim = mkt.simulate_offline_sales(30.0)
_assert(sim["sold_count"] == 1, "Stone sold during simulated offline time")
_assert(mkt.player_stall["revenue"] == 1000000, "1M gold earned in stall revenue")
_assert(mkt.player_stall["items"].is_empty(), "Stall inventory now empty")
# Collect revenue
var player := {"gold": 50000}
var col_res = mkt.collect_stall_revenue(player)
_assert(col_res["ok"] == true and player["gold"] == 1050000, "Collected revenue into player wallet")
_assert(mkt.player_stall["revenue"] == 0, "Stall revenue reset to 0")
func _test_stall_close_and_item_return() -> void:
print("\n--- Test 5: Closing Stall & Returning Unsold Items ---")
var mkt := OfflineBazaarMarketSystem.new()
var inv: Array = [
{"vnum": 50200, "count": 1},
{"vnum": 11299, "name": "黑铁战甲+9", "count": 1}
]
mkt.open_player_stall("【神装铺】", [{"slot": 1, "price": 10000000, "count": 1}], inv, true)
var close_sig := {"fired": false, "rev": -1}
mkt.player_stall_closed.connect(func(rev: int):
close_sig["fired"] = true
close_sig["rev"] = rev
)
# Close stall immediately without sales
var player := {"gold": 0}
var close_res = mkt.close_player_stall(inv, player)
_assert(close_res["ok"] == true, "Stall closed successfully")
_assert(mkt.player_stall["is_open"] == false, "Stall is no longer open")
_assert(close_sig["fired"] == true and close_sig["rev"] == 0, "player_stall_closed signal fired")
_assert(inv[0]["vnum"] == 11299, "Unsold armor safely returned to first empty inventory slot")
func _finish() -> void:
print("\n==========================================")
print("Offline Bazaar Market Parity Tests Finished:")
print("Passed: %d, Failed: %d" % [_passed, _failed])
print("==========================================")
if _failed > 0:
printerr("FAILED: Some tests did not pass.")
quit(1)
else:
print("SUCCESS: All Offline Bazaar Market parity tests passed!")
quit(0)