| using System; | 
| using System.Collections.Generic; | 
| using System.Linq; | 
| using UnityEngine; | 
| using LitJson; | 
|   | 
|   | 
| public class MailManager : GameSystemManager<MailManager> | 
| { | 
|     /// <summary> | 
|     /// 邮件数据字典,存储所有邮件详情 | 
|     /// Key: 邮件GUID(唯一标识) | 
|     /// Value: 邮件详细数据(MailData结构体) | 
|     /// </summary> | 
|     Dictionary<string, MailData> mailDataDict = new Dictionary<string, MailData>(); | 
|     public MailCategory nowMailCategory = MailCategory.Personal; | 
|     public Redpoint parentRedpoint = new Redpoint(MainRedDot.MailRepoint); | 
|     public Redpoint tabRedpoint0; | 
|     public Redpoint tabRedpoint1; | 
|     public event Action OnUpdateMailListEvent;// 更新邮件列表数据 | 
|     public event Action OnUpdateMailStateChangeEvent;// 更新邮件状态变更 | 
|     public readonly string dateFormat = "yyyy.MM.dd"; | 
|     public string nowUuid = string.Empty; | 
|     public int personalMailMaxLimitCount; | 
|   | 
|   | 
|     public override void Init() | 
|     { | 
|         tabRedpoint0 = new Redpoint(MainRedDot.MailRepoint, GetTabRedpointId(MailCategory.Personal)); | 
|         tabRedpoint1 = new Redpoint(MainRedDot.MailRepoint, GetTabRedpointId(MailCategory.Global)); | 
|         DTC0102_tagCDBPlayer.beforePlayerDataInitializeEvent += OnBeforePlayerDataInitializeEvent; | 
|         personalMailMaxLimitCount = int.Parse(FuncConfigConfig.Get("PersonalMail").Numerical1); | 
|     } | 
|   | 
|     public override void Release() | 
|     { | 
|         DTC0102_tagCDBPlayer.beforePlayerDataInitializeEvent -= OnBeforePlayerDataInitializeEvent; | 
|     } | 
|   | 
|     public void OnBeforePlayerDataInitializeEvent() | 
|     { | 
|         mailDataDict.Clear(); | 
|     } | 
|   | 
|     public bool HasNoReadMail(MailCategory category) | 
|     { | 
|         var list = GetMailList(category); | 
|         if (list.IsNullOrEmpty()) | 
|             return false; | 
|   | 
|         foreach (var guid in list) | 
|         { | 
|             if (mailDataDict.TryGetValue(guid, out var mailData) && mailData.MailState == 1) | 
|                 return true; | 
|         } | 
|         return false; | 
|     } | 
|   | 
|     public int GetTabRedpointId(MailCategory type) | 
|     { | 
|         return MainRedDot.MailRepoint * 10 + (int)type; | 
|     } | 
|   | 
|     public void UpdatePersonalRedPoint() | 
|     { | 
|         if (tabRedpoint0 == null) | 
|             return; | 
|         tabRedpoint0.state = RedPointState.None; | 
|         if (HasNoReadMail(MailCategory.Personal)) | 
|         { | 
|             tabRedpoint0.state = RedPointState.Simple; | 
|         } | 
|     } | 
|   | 
|     public void UpdateGlobalRedPoint() | 
|     { | 
|         if (tabRedpoint1 == null) return; | 
|         tabRedpoint1.state = RedPointState.None; | 
|         if (HasNoReadMail(MailCategory.Global)) | 
|         { | 
|             tabRedpoint1.state = RedPointState.Simple; | 
|         } | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 更新邮件红点提示状态 | 
|     /// </summary> | 
|     public void UpdateRedPoint() | 
|     { | 
|         UpdatePersonalRedPoint(); | 
|         UpdateGlobalRedPoint(); | 
|     } | 
|   | 
|     public bool TryGetMailData(string uuid, out MailData mailData) | 
|     { | 
|         return mailDataDict.TryGetValue(uuid, out mailData); | 
|     } | 
|     public string FormatCreateMailTime(DateTime createDateTime) | 
|     { | 
|         TimeSpan diff = TimeUtility.ServerNow - createDateTime; | 
|         if (diff.TotalHours <= 24) | 
|         { | 
|             int hours = (int)Math.Floor(diff.TotalHours); | 
|             return Language.Get("Mail05", hours); | 
|         } | 
|         else | 
|         { | 
|             return createDateTime.ToString(dateFormat); | 
|         } | 
|     } | 
|   | 
|     // 返回有效期天数(正数=剩余天数,负数=已过期天数) | 
|     public float GetMailExpiryDays(DateTime createDateTime, int limitDays) | 
|     { | 
|         // 计算到期日当天的0点时间 | 
|         DateTime expiryDate = createDateTime.Date.AddDays(limitDays); | 
|         DateTime serverNow = TimeUtility.ServerNow; | 
|         TimeSpan remainingTime = expiryDate - serverNow; | 
|         return (float)remainingTime.TotalDays; | 
|     } | 
|   | 
|     public string FormatMailExpiryDays(DateTime createDateTime, int limitDays) | 
|     { | 
|         string result = string.Empty; | 
|         float days = GetMailExpiryDays(createDateTime, limitDays); | 
|         // 防护:处理因客户端与服务器时间戳的微小差异,导致剩余时间略大于有效期上限的情况。 | 
|         // 将计算出的剩余天数限制在邮件的有效期天数之内。 | 
|         if (days > limitDays) | 
|         { | 
|             days = limitDays; | 
|         } | 
|   | 
|         if (days > 1.0f) | 
|         { | 
|             // 大于1天:按向上取整的天数显示。 | 
|             int daysRounded = (int)MathF.Ceiling(days); | 
|             result = Language.Get("Mail07", daysRounded); | 
|         } | 
|         else if (days > 0.0f) | 
|         { | 
|             // 小于1天(但未过期):按精确的小时/分钟显示。 | 
|             TimeSpan t = TimeSpan.FromDays(days); | 
|             result = Language.Get("Mail12", StringUtility.Contact(" ", Language.Get("Mail13", t.Hours, t.Minutes))); | 
|         } | 
|         else | 
|         { | 
|             // (预留)已过期:按负数向下取整(向更小的负数取整,以获得已过期天数的整数绝对值) | 
|             // MathF.Floor(-1.01f) -> -2.0f | 
|             // MathF.Floor(-0.5f)  -> -1.0f | 
|             // float expiredDaysFloor = MathF.Floor(days); | 
|             // int expiredDays = (int)expiredDaysFloor; | 
|   | 
|             //直接显示已过期 | 
|             result = Language.Get("Mail11"); ; | 
|         } | 
|         return result; | 
|     } | 
|   | 
|   | 
|     public List<string> GetMailList(MailCategory mailCategory) | 
|     { | 
|         var result = new List<string>(); | 
|         foreach (var kvp in mailDataDict) | 
|         { | 
|             if (kvp.Value.Category == mailCategory) | 
|             { | 
|                 result.Add(kvp.Key); | 
|             } | 
|         } | 
|         return result; | 
|     } | 
|   | 
|     public int GetMailCount(MailCategory mailCategory) | 
|     { | 
|         var result = new List<string>(); | 
|         foreach (var kvp in mailDataDict) | 
|         { | 
|             if (kvp.Value.Category == mailCategory) | 
|             { | 
|                 result.Add(kvp.Key); | 
|             } | 
|         } | 
|         return result.Count; | 
|     } | 
|   | 
|   | 
|     public List<string> GetSortMailScrList(MailCategory mailCategory) | 
|     { | 
|         List<string> resList = GetMailList(mailCategory); | 
|         return resList.OrderByDescending(guid => mailDataDict[guid].CreateDateTime).ToList(); | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 更新邮件列表数据 | 
|     /// </summary> | 
|     /// <param name="vNetData">从服务器接收的邮件列表数据包</param> | 
|     public void UpdateMailList(HA362_tagMCMailList vNetData) | 
|     { | 
|         if (vNetData == null) return; | 
|   | 
|         // 更新邮件数据字典 | 
|         foreach (var mail in vNetData.MailList) | 
|         { | 
|             var category = vNetData.IsServerMail == 1 ? MailCategory.Global : MailCategory.Personal; | 
|             var mailData = new MailData(); | 
|   | 
|             mailData.Category = category; | 
|             mailData.GUID = mail.GUID; | 
|             mailData.Type = mail.Type; | 
|             mailData.CreateTime = mail.CreateTime; | 
|             mailData.CreateDateTime = Convert.ToDateTime(UIHelper.GetTime(mail.CreateTime)); | 
|             mailData.LimitDays = mail.LimitDays; | 
|             mailData.Title = mail.Title; | 
|             mailData.Text = mail.Text; | 
|             mailData.MailState = mail.MailState; | 
|   | 
|             mailData.Items = mail.Items?.Select(i => new MailItemData | 
|             { | 
|                 ItemID = i.ItemID, | 
|                 Count = i.Count, | 
|                 IsBind = i.IsBind, | 
|                 UserData = i.UserData | 
|             }).ToList(); | 
|   | 
|             if (!mailData.Items.IsNullOrEmpty()) | 
|             { | 
|                 mailData.Items = mailData.Items | 
|                     .OrderByDescending(item => ItemConfig.HasKey((int)item.ItemID) ? ItemConfig.Get((int)item.ItemID).ItemColor : 0) // 按品质降序,未知物品排在最后 | 
|                     .ThenBy(item => item.Count) // 品质相同按数量升序 | 
|                     .ToList(); | 
|             } | 
|   | 
|             mailDataDict[mail.GUID] = mailData; | 
|         } | 
|   | 
|         UpdateRedPoint(); | 
|         OnUpdateMailListEvent?.Invoke(); | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 更新邮件状态变更 | 
|     /// </summary> | 
|     /// <param name="vNetData">从服务器接收的邮件状态变更数据包</param> | 
|     public void UpdateMailStateChange(HA363_tagMCMailStateChange vNetData) | 
|     { | 
|         if (vNetData == null || vNetData.MailList == null) return; | 
|   | 
|         foreach (var stateChange in vNetData.MailList) | 
|         { | 
|             if (mailDataDict.TryGetValue(stateChange.GUID, out var mailData)) | 
|             { | 
|                 if (stateChange.MailState == 4) | 
|                 { | 
|                     mailDataDict.Remove(stateChange.GUID); | 
|                     continue; | 
|                 } | 
|                 mailData.MailState = stateChange.MailState; | 
|                 mailDataDict[stateChange.GUID] = mailData; | 
|             } | 
|         } | 
|   | 
|         UpdateRedPoint(); | 
|         OnUpdateMailStateChangeEvent?.Invoke(); | 
|     } | 
|   | 
|   | 
|     /// <summary> | 
|     /// 阅读邮件 | 
|     /// </summary> | 
|     /// <param name="GUID">邮件GUID</param> | 
|     public void ReadMail(string GUID) | 
|     { | 
|         if (string.IsNullOrEmpty(GUID)) | 
|         { | 
|             Debug.LogError("阅读邮件GUID不能为空"); | 
|             return; | 
|         } | 
|         SendRequestMail(GUID, 0); | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 领取邮件 | 
|     /// </summary> | 
|     /// <param name="GUID">邮件GUID,为空时批量领取所有邮件</param> | 
|     public void ClaimMail(string GUID = null) | 
|     { | 
|         SendRequestMail(GUID, 1); | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 删除邮件 | 
|     /// </summary> | 
|     /// <param name="GUID">邮件GUID,为空时批量删除已领取或无物品的已读邮件</param> | 
|     public void DeleteMail(string GUID = null) | 
|     { | 
|         SendRequestMail(GUID, 2); | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 发送邮件请求到服务器 | 
|     /// </summary> | 
|     /// <param name="GUID">邮件GUID</param> | 
|     /// <param name="ReqType">请求类型 0-设置已读,1-领取邮件,2-删除邮件</param> | 
|     public void SendRequestMail(string GUID, byte ReqType) | 
|     { | 
|         CA537_tagCMRequestMail pack = new CA537_tagCMRequestMail(); | 
|         pack.GUID = GUID; | 
|         pack.ReqType = ReqType; | 
|         GameNetSystem.Instance.SendInfo(pack); | 
|     } | 
|   | 
|     //没有一个可领取的个人邮件 | 
|     public bool IsCanHaveMail() | 
|     { | 
|         var personalMails = GetMailList(MailCategory.Personal); | 
|         return personalMails.Any(guid => | 
|             mailDataDict.TryGetValue(guid, out var mail) && | 
|             mail.MailState != 3 && | 
|             mail.Items != null && | 
|             mail.Items.Count > 0 | 
|         ); | 
|     } | 
| } | 
|   | 
| public enum MailCategory | 
| { | 
|     Personal = 0,  // 个人邮件 | 
|     Global = 1     // 全服邮件 | 
| } | 
|   | 
| public class MailItemData | 
| { | 
|     public uint ItemID;        //物品ID | 
|     public uint Count;        //数量 | 
|     public byte IsBind;        //是否绑定 | 
|     public string UserData;        //自定义数据     | 
| } | 
|   | 
| public class MailData | 
| { | 
|     public MailCategory Category; | 
|     public string GUID;        //邮件GUID | 
|     public byte Type;        //邮件类型,暂时默认0 | 
|     public string CreateTime;        //创建时间 | 
|     public DateTime CreateDateTime;//创建时间 | 
|     public byte LimitDays;        //有效天数 | 
|     public string Title;        //标题 | 
|     public string Text;        //内容 | 
|     public byte MailState;        //邮件状态: 0-未知;1-未读;2-已读;3-已领; | 
|     public List<MailItemData> Items;        //物品信息 | 
|   | 
|     public bool HasAward() | 
|     { | 
|         return Items != null && Items.Count > 0; | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 判断是否为模板类型邮件 | 
|     /// </summary> | 
|     /// <returns>true-模板邮件, false-普通邮件</returns> | 
|     public bool IsTemplateMail() | 
|     { | 
|         if (string.IsNullOrEmpty(Title)) | 
|             return false; | 
|   | 
|         return Title.StartsWith("<T>") && Title.EndsWith("</T>"); | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 获取模板邮件的模板key | 
|     /// </summary> | 
|     /// <returns>模板key,如果不是模板邮件返回空字符串</returns> | 
|     public string GetTemplateKey() | 
|     { | 
|         if (!IsTemplateMail()) | 
|             return string.Empty; | 
|   | 
|         // 模板格式: <T>key</T> | 
|         // 跳过开头的"<T>" (3字符) | 
|         // 取中间部分,总长度减去"<T>"和"</T>"共7字符 | 
|         const int prefixLength = 3; // "<T>"长度 | 
|         const int suffixLength = 4; // "</T>"长度 | 
|         return Title.Substring(prefixLength, Title.Length - (prefixLength + suffixLength)); | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 获取模板邮件的参数列表 | 
|     /// </summary> | 
|     /// <returns>参数列表,如果不是模板邮件或参数为空则返回空列表</returns> | 
|     public List<string> GetTemplateParams() | 
|     { | 
|         if (!IsTemplateMail() || string.IsNullOrEmpty(Text)) | 
|             return new List<string>(); | 
|   | 
|         try | 
|         { | 
|             List<string> result = JsonMapper.ToObject<List<string>>(Text); | 
|             // 如果解析结果为null(例如Text的内容是 "null" 字符串),则返回一个空列表以防后续出错 | 
|             return result ?? new List<string>(); | 
|         } | 
|         catch (Exception ex) | 
|         { | 
|             Debug.LogError($"使用 LitJson 解析邮件参数失败。 Text: {Text}, 错误: {ex.Message}"); | 
|             return new List<string>(); | 
|         } | 
|     } | 
| } |