- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
180 lines
6.7 KiB
GDScript
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)
|