# mail_system.gd —— Metin2 40250 游戏内邮箱系统与附件寄送提取 1:1 # 对照 40250 服务端 mailbox.cpp, input_db.cpp, uimailbox.py class_name MailSystem extends RefCounted signal mail_sent(mail_id: int, sender: String, recipient: String) signal mail_received(mail_id: int, recipient: String, title: String) signal unread_count_changed(recipient: String, count: int) signal attachment_claimed(mail_id: int) const POSTAGE_FEE := 1000 # 寄件固定邮资 1000 金币 const MAX_TITLE_LENGTH := 25 # 标题最长字符 const MAX_BODY_LENGTH := 100 # 正文最长字符 const MAIL_EXPIRATION_SECONDS := 2592000.0 # 30 天有效期 (30 * 86400) var mails: Array = [] var next_mail_id: int = 1 # 寄送邮件 func send_mail( sender: String, recipient: String, title: String, body: String, player_data: Dictionary, inventory: Array, attach_slot: int = -1, attach_gold: int = 0, current_time: float = 0.0 ) -> Dictionary: if recipient.strip_edges().is_empty(): return {"ok": false, "reason": "EMPTY_RECIPIENT", "msg": "收件人不能为空!"} if title.length() > MAX_TITLE_LENGTH: return {"ok": false, "reason": "TITLE_TOO_LONG", "msg": "邮件标题过长!"} if body.length() > MAX_BODY_LENGTH: return {"ok": false, "reason": "BODY_TOO_LONG", "msg": "邮件正文过长!"} if attach_gold < 0: return {"ok": false, "reason": "INVALID_GOLD", "msg": "附加金币数额无效!"} # 检查玩家金币 (邮资 1000 + 附加金币) var total_gold_cost := POSTAGE_FEE + attach_gold var player_gold: int = int(player_data.get("gold", 0)) if player_gold < total_gold_cost: return { "ok": false, "reason": "INSUFFICIENT_YANG", "required": total_gold_cost, "current": player_gold, "msg": "金币不足以支付邮资与附件!需要 %d 金币。" % total_gold_cost } # 检查附加物品 var attached_item = null if attach_slot >= 0: if attach_slot >= inventory.size() or inventory[attach_slot] == null: return {"ok": false, "reason": "INVALID_ATTACH_ITEM", "msg": "所选格子没有物品!"} var item = inventory[attach_slot] if item.get("is_locked", false): return {"ok": false, "reason": "ITEM_LOCKED", "msg": "已加锁的物品无法邮寄!"} attached_item = item.duplicate(true) inventory[attach_slot] = null # 扣除金币 player_data["gold"] = player_gold - total_gold_cost # 创建邮件 var mail_id := next_mail_id next_mail_id += 1 var mail_entry: Dictionary = { "id": mail_id, "sender": sender, "recipient": recipient, "title": title, "body": body, "send_time": current_time, "expire_time": current_time + MAIL_EXPIRATION_SECONDS, "is_read": false, "attached_gold": attach_gold, "attached_item": attached_item, "is_gold_claimed": (attach_gold == 0), "is_item_claimed": (attached_item == null) } mails.append(mail_entry) mail_sent.emit(mail_id, sender, recipient) mail_received.emit(mail_id, recipient, title) unread_count_changed.emit(recipient, get_unread_count(recipient)) return { "ok": true, "mail_id": mail_id, "msg": "邮件已成功寄出!" } # 获取指定收件人的收件箱 func get_inbox(recipient: String) -> Array: var list: Array = [] for m in mails: if m["recipient"] == recipient: list.append(m) return list # 查阅邮件 func read_mail(mail_id: int, current_player: String) -> Dictionary: for m in mails: if m["id"] == mail_id and m["recipient"] == current_player: if not m["is_read"]: m["is_read"] = true unread_count_changed.emit(current_player, get_unread_count(current_player)) return {"ok": true, "mail": m.duplicate(true)} return {"ok": false, "reason": "MAIL_NOT_FOUND", "msg": "未找到该邮件!"} # 提取邮件附件 (金币与物品) func claim_attachments( mail_id: int, current_player: String, player_data: Dictionary, inventory: Array ) -> Dictionary: var target_mail = null for m in mails: if m["id"] == mail_id and m["recipient"] == current_player: target_mail = m break if target_mail == null: return {"ok": false, "reason": "MAIL_NOT_FOUND", "msg": "未找到指定邮件!"} var claimed_gold: int = 0 var claimed_item = null # 提取金币 if not target_mail["is_gold_claimed"] and target_mail["attached_gold"] > 0: claimed_gold = target_mail["attached_gold"] player_data["gold"] = int(player_data.get("gold", 0)) + claimed_gold target_mail["attached_gold"] = 0 target_mail["is_gold_claimed"] = true # 提取物品 if not target_mail["is_item_claimed"] and target_mail["attached_item"] != null: # 寻找背包空位 var empty_slot := -1 for i in range(inventory.size()): if inventory[i] == null: empty_slot = i break if empty_slot == -1: return { "ok": false, "reason": "INVENTORY_FULL", "claimed_gold": claimed_gold, "msg": "背包已满,无法提取物品附件!" } claimed_item = target_mail["attached_item"].duplicate(true) inventory[empty_slot] = claimed_item target_mail["attached_item"] = null target_mail["is_item_claimed"] = true attachment_claimed.emit(mail_id) return { "ok": true, "claimed_gold": claimed_gold, "claimed_item": claimed_item, "msg": "附件提取成功!" } # 删除邮件 func delete_mail(mail_id: int, current_player: String) -> Dictionary: for i in range(mails.size()): var m = mails[i] if m["id"] == mail_id and m["recipient"] == current_player: # 检查是否有未提取的附件 if not m["is_gold_claimed"] or not m["is_item_claimed"]: return { "ok": false, "reason": "HAS_UNCLAIMED_ATTACHMENTS", "msg": "邮件内包含尚未提取的附件,无法删除!" } mails.remove_at(i) unread_count_changed.emit(current_player, get_unread_count(current_player)) return {"ok": true, "msg": "邮件已删除。"} return {"ok": false, "reason": "MAIL_NOT_FOUND", "msg": "未找到指定邮件!"} # 获取未读邮件数量 func get_unread_count(recipient: String) -> int: var cnt := 0 for m in mails: if m["recipient"] == recipient and not m["is_read"]: cnt += 1 return cnt # 过期邮件清理 (30天自动销毁) func cleanup_expired_mails(current_time: float) -> int: var removed := 0 var i := mails.size() - 1 while i >= 0: if current_time >= mails[i]["expire_time"]: var recipient: String = mails[i]["recipient"] mails.remove_at(i) removed += 1 unread_count_changed.emit(recipient, get_unread_count(recipient)) i -= 1 return removed