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

180 lines
6.7 KiB
GDScript

# test_mail_system_parity.gd —— 40250 游戏内邮箱系统 1:1 纯净回归测试套件
extends SceneTree
const MailSystem = preload("res://mail_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_mail_system_parity (Direction 4: In-Game Mailbox 1:1) ===")
_test_mail_sending_validation()
_test_mail_reading_and_unread_count()
_test_attachment_claiming()
_test_delete_protection()
_test_mail_expiry_cleanup()
_finish()
func _test_mail_sending_validation() -> void:
print("\n--- Test 1: Mail Sending Validation & Postage (1000 Yang) ---")
var ms := MailSystem.new()
var player_data := {"gold": 500} # Less than postage 1000
var inv: Array = [null]
# Empty recipient
var r1 = ms.send_mail("PlayerA", "", "Title", "Body", player_data, inv)
_assert(r1["ok"] == false and r1["reason"] == "EMPTY_RECIPIENT", "Empty recipient rejected")
# Insufficient Yang for postage
var r2 = ms.send_mail("PlayerA", "PlayerB", "Title", "Body", player_data, inv)
_assert(r2["ok"] == false and r2["reason"] == "INSUFFICIENT_YANG", "Blocked when gold < postage fee")
# Locked item cannot be mailed
player_data["gold"] = 10000
inv[0] = {"vnum": 10, "name": "木剑", "is_locked": true}
var r3 = ms.send_mail("PlayerA", "PlayerB", "Title", "Body", player_data, inv, 0)
_assert(r3["ok"] == false and r3["reason"] == "ITEM_LOCKED", "Locked item rejected for mailing")
# Valid sending without attachment
var send_data := {"fired": false, "id": 0}
ms.mail_sent.connect(func(mid: int, _s: String, _r: String):
send_data["fired"] = true
send_data["id"] = mid
)
var r4 = ms.send_mail("PlayerA", "PlayerB", "问候信", "你好,这是测试邮件!", player_data, inv)
_assert(r4["ok"] == true, "Mail sent successfully")
_assert(player_data["gold"] == 9000, "1000 Yang postage deducted (10000 -> 9000)")
_assert(send_data["fired"] == true and send_data["id"] == 1, "mail_sent signal fired with id 1")
_assert(ms.get_unread_count("PlayerB") == 1, "PlayerB has 1 unread mail")
func _test_mail_reading_and_unread_count() -> void:
print("\n--- Test 2: Mail Reading & Unread Counter ---")
var ms := MailSystem.new()
var player_data := {"gold": 10000}
var inv: Array = [null]
ms.send_mail("Alice", "Bob", "Hello", "World", player_data, inv)
_assert(ms.get_unread_count("Bob") == 1, "Initial unread count is 1")
var unread_signal := {"fired": false, "count": -1}
ms.unread_count_changed.connect(func(_rec: String, cnt: int):
unread_signal["fired"] = true
unread_signal["count"] = cnt
)
# Read mail
var res = ms.read_mail(1, "Bob")
_assert(res["ok"] == true, "Mail read successfully")
_assert(res["mail"]["is_read"] == true, "is_read is true")
_assert(ms.get_unread_count("Bob") == 0, "Unread count updated to 0")
_assert(unread_signal["fired"] == true and unread_signal["count"] == 0, "unread_count_changed signal fired with 0")
# Reading non-existent mail
var r_fail = ms.read_mail(999, "Bob")
_assert(r_fail["ok"] == false and r_fail["reason"] == "MAIL_NOT_FOUND", "Non-existent mail returns error")
func _test_attachment_claiming() -> void:
print("\n--- Test 3: Gold & Item Attachment Sealing and Claiming ---")
var ms := MailSystem.new()
var sender_data := {"gold": 100000}
var sender_inv: Array = [
{"vnum": 11299, "name": "黑钢甲+9", "count": 1},
null
]
# Send mail with 50,000 Yang and armor in slot 0
var send_res = ms.send_mail(
"Sender",
"Receiver",
"珍贵赠礼",
"请收下这件黑钢甲和金币!",
sender_data,
sender_inv,
0, # attach slot 0
50000 # attach 50,000 gold
)
_assert(send_res["ok"] == true, "Mail with attachments sent")
_assert(sender_data["gold"] == 49000, "51,000 Yang deducted (50k gold + 1k postage)")
_assert(sender_inv[0] == null, "Attached item removed from sender inventory")
# Receiver inbox
var receiver_data := {"gold": 10000}
var receiver_inv: Array = [null, null]
var claim_signal := {"fired": false, "id": 0}
ms.attachment_claimed.connect(func(mid: int):
claim_signal["fired"] = true
claim_signal["id"] = mid
)
# Claim attachments
var claim_res = ms.claim_attachments(1, "Receiver", receiver_data, receiver_inv)
_assert(claim_res["ok"] == true, "Attachments claimed successfully")
_assert(claim_signal["fired"] == true and claim_signal["id"] == 1, "attachment_claimed signal fired")
_assert(receiver_data["gold"] == 60000, "Receiver gold increased by 50,000 (10k -> 60k)")
_assert(receiver_inv[0] != null and receiver_inv[0]["vnum"] == 11299, "Attached armor received into inventory")
# Double claiming yields nothing
var claim_again = ms.claim_attachments(1, "Receiver", receiver_data, receiver_inv)
_assert(claim_again["claimed_gold"] == 0 and claim_again["claimed_item"] == null, "No attachments left to claim")
func _test_delete_protection() -> void:
print("\n--- Test 4: Delete Protection on Unclaimed Attachments ---")
var ms := MailSystem.new()
var s_data := {"gold": 50000}
var s_inv: Array = [{"vnum": 10, "name": "木剑", "count": 1}]
ms.send_mail("A", "B", "Item Mail", "Body", s_data, s_inv, 0, 10000)
# Trying to delete before claiming attachments
var del_blocked = ms.delete_mail(1, "B")
_assert(del_blocked["ok"] == false and del_blocked["reason"] == "HAS_UNCLAIMED_ATTACHMENTS", "Deletion blocked while attachments unclaimed")
# Claim attachments
var b_data := {"gold": 0}
var b_inv: Array = [null]
ms.claim_attachments(1, "B", b_data, b_inv)
# Now deletion succeeds
var del_ok = ms.delete_mail(1, "B")
_assert(del_ok["ok"] == true, "Mail deleted after attachments claimed")
_assert(ms.get_inbox("B").is_empty(), "B inbox is now empty")
func _test_mail_expiry_cleanup() -> void:
print("\n--- Test 5: 30-Day Expiry Cleanup ---")
var ms := MailSystem.new()
var s_data := {"gold": 10000}
var s_inv: Array = [null]
# Send mail at time 0
ms.send_mail("A", "B", "Old Mail", "Body", s_data, s_inv, -1, 0, 0.0)
_assert(ms.get_inbox("B").size() == 1, "1 mail exists")
# Clean up at day 15 (15 * 86400 = 1296000) -> not expired
var c1 = ms.cleanup_expired_mails(1296000.0)
_assert(c1 == 0, "0 mails cleaned at 15 days")
_assert(ms.get_inbox("B").size() == 1, "Mail still intact")
# Clean up at day 31 (31 * 86400 = 2678400) -> expired!
var c2 = ms.cleanup_expired_mails(2678400.0)
_assert(c2 == 1, "1 mail cleaned after 30 days")
_assert(ms.get_inbox("B").size() == 0, "Expired mail was removed")
func _finish() -> void:
print("\n==========================================")
print("Mail System Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
print("==========================================")
if _failed > 0:
quit(1)
else:
quit(0)