using game.PlayerDataStorage; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using asap.core; using game; using GameCore; using Newtonsoft.Json; using UniRx; public class FetchMailRequest { public DateTime? lastSyncTime; } public class FetchMailResp { public bool success; public List mails; public string error; } public class MailStateInfo { public string Id { get; set; } public EMailState state { get; set; } public MailStateInfo(GameMail mail, EMailState newState) { Id = mail.Id.ToString(); state = mail.State | newState; } } public class UpdateMailStateRequest { public MailStateInfo[] mailStates; } public class UpdateMailStateResp { public bool success; public MailStateInfo[] states; } public class FetchMailEvt { DateTime TimeStamp; } public class MailInfo { public string Id { get; set; } public EMailType Type { get; set; } public int expireDays { get; set; } public string Title { get; set; } public string From { get; set; } public string Content { get; set; } public int[] DropIds { get; set; } } public class SendMailRequest { public MailInfo[] mailInfos; } public class SendMailResp { public bool success; public List mails; public string error; } public class AutoMailData { public string Id; public int levelMin; public int levelMax; public DateTime accountCreateTime; public DateTime loginTimeMin; public DateTime loginTimeMax; public int expireDays; public int DropID; public AutoMailData(cfg.AutoDeliveryMail mail) { Id = mail.MailID.ToString(); levelMin = mail.AccountLevelRange[0]; levelMax = mail.AccountLevelRange[1] == -1 ? int.MaxValue : mail.AccountLevelRange[1]; accountCreateTime = DateTime.Parse(mail.AccountCreateTimestamp); loginTimeMin = DateTime.Parse(mail.LoginTimestampRange[0]); loginTimeMax = DateTime.Parse(mail.LoginTimestampRange[1]); DropID = mail.DropID; expireDays = mail.ExpireDay; } public bool IsSatisfy(DateTime loginTime, DateTime CreateTime, int level) { return loginTime >= loginTimeMin && loginTime <= loginTimeMax && CreateTime < accountCreateTime && level >= levelMin && level <= levelMax; } public MailInfo ToMailInfo() { return new MailInfo { Id = Id.ToString(), Type = EMailType.AutoDelivery, DropIds = new[] { DropID }, expireDays = expireDays }; } } public static class MailUrls { public const string Fetch = "mail/fetch"; public const string UpdateState = "mail/updateState"; public const string Send = "mail/send"; } public class InGameMailService : IInGameMailService { private MailCache _mailCache; private BaseMailHandler _handler; readonly IUserService userService; private List _autoMailDatas; private CompositeDisposable _disposables; private bool _forceFetch = false; //private const int Fetch_Interval = 3; public bool IsUnlock { get; set; } public InGameMailService(ICustomServerMgr customServerMgr, cfg.Tables tables, IUserService userService, IPlayerSaveStore saveStore) { this._mailCache = new MailCache(tables.TbAutoDeliveryMail); this.userService = userService; this._handler = new BaseMailHandler(_mailCache, customServerMgr, userService, saveStore); _autoMailDatas = tables.TbAutoDeliveryMail.DataList .Select(x => new AutoMailData(x)).ToList(); } public void Init() { if (!userService.IsAuthorized) throw new InvalidOperationException("Mail cache requires an authenticated user."); _mailCache.Load(userService.UserId); _disposables = new CompositeDisposable(); var minActiveLevel = _autoMailDatas.Min(x => x.levelMin) - 1; _ = _handler.FetchMail(); GContext.OnEvent() .Skip(1) .Where(x => x.lvl >= minActiveLevel) .Subscribe(OnLevelChange) .AddTo(_disposables); //GContext.OnEvent() //.Skip(1) //.Subscribe(OnHomeUIShow) //.AddTo(_disposables); var rtService = GContext.container.Resolve(); rtService.OnEvent(async evt => await _handler.FetchMail()) .AddTo(_disposables); } public void Reset() { _handler.Close(); _mailCache?.Unload(); _disposables?.Dispose(); _disposables = null; } public async Task FetchMail() { await _handler.FetchMail(); } public IList GetAllMail() { return _mailCache.GetAll(); } public async Task MarkRead(GameMail mail) { return await _handler.MarkAsRead(mail); } public async Task MarkRead(GameMail[] mails = null) { return await _handler.MarkAsRead(mails); } public async Task DelMail(GameMail mail) { return await _handler.DeleteMail(mail); } public async Task DelMail(GameMail[] mails = null) { return await _handler.DeleteMail(mails); } public async Task TakeReward(GameMail mail) { return await _handler.TakeReward(mail); } public async Task TakeReward(GameMail[] mails) { return await _handler.TakeReward(mails); } private void OnLevelChange(OnLvChangeEvent evt) { #if UNITY_EDITOR UnityEngine.Debug.Log("[InGameMailService] OnLevelChange"); #endif GenAutoDeliveryMail(); } //private async void OnHomeUIShow(RestartShowHomeUIEvent evt) //{ //#if UNITY_EDITOR //var deltaTime = _mailCache.LastSyncTime == null ? 0 : (ZZTimeHelper.UtcNow() - _mailCache.LastSyncTime.Value).TotalSeconds; //UnityEngine.Debug.Log($"[InGameMailService] OnHomeUIShow deltaTime {deltaTime}"); //#endif //if(_mailCache.LastSyncTime == null //|| _forceFetch //|| ZZTimeHelper.UtcNow() - _mailCache.LastSyncTime.Value > TimeSpan.FromMinutes(Fetch_Interval)) //{ //if(await _handler.FetchMail()) //{ //_forceFetch = false; //} //} //} private async void GenAutoDeliveryMail() { var playerData = GContext.container.Resolve(); var userData = userService; if (!_handler.IsCurrent) return; var mailsToSend = _autoMailDatas.Where(m => !_mailCache.HasMail(m.Id) && m.IsSatisfy(userData.LoginTime, userData.CreateTime, playerData.lv)) .Select(m => m.ToMailInfo()) .ToArray(); if (mailsToSend.Length > 0) { if (await _handler.SendMail(mailsToSend) && _handler.IsCurrent) { _forceFetch = true; } } #if UNITY_EDITOR var mailids = mailsToSend.Select(m => m.Id); var mailidsStr = string.Join(',', mailids); UnityEngine.Debug.Log($"[InGameMailService] GenAutoDeliveryMail {mailidsStr}"); #endif } public void PrintCacheMail() { UnityEngine.Debug.Log(_mailCache.PrintCacheMails()); } } public class BaseMailHandler : IMailHandler { protected MailCache _cache; protected ICustomServerMgr _customServerMgr; readonly IUserService userService; readonly IPlayerSaveStore saveStore; bool closed, rewardPending; public bool IsCurrent => !closed && saveStore.IsCurrent && !userService.LoginLifetime.IsCancellationRequested; public void Close() { closed = true; } public BaseMailHandler(MailCache cache, ICustomServerMgr customServerMgr, IUserService userService, IPlayerSaveStore saveStore) { this.userService = userService; this.saveStore = saveStore; this._cache = cache; this._customServerMgr = customServerMgr; } public virtual async Task FetchMail() { try { if (!IsCurrent) return false; await Awaiters.NextFrame; if (!IsCurrent) return false; #if UNITY_EDITOR UnityEngine.Debug.Log($"[InGameMailService] Start Fetch Mails"); #endif var req = new FetchMailRequest() { lastSyncTime = _cache.LastSyncTime }; if (!IsCurrent) return false; var result = await _customServerMgr.CustomServerPost(MailUrls.Fetch, JsonConvert.SerializeObject(req)); if (!IsCurrent) return false; if (result != null) { var resp = JsonConvert.DeserializeObject(result); if (resp.success) { if (resp.mails != null && resp.mails.Count > 0) { _cache.Cache(resp.mails); _cache.Save(); var UnreadMailCount = _cache.UnreadMailCount; RedPointManager.Instance.SetRedPointState(RedPointName.Unread_Mail, UnreadMailCount > 0, UnreadMailCount); #if UNITY_EDITOR UnityEngine.Debug.Log($"[InGameMailService] FetchMail {UnreadMailCount} LastSyncTime = {_cache.LastSyncTime}"); #endif } else { _cache.UpdateSyncTime(); #if UNITY_EDITOR UnityEngine.Debug.Log($"[InGameMailService] No Mails Fetch Fetched LastSyncTime = {_cache.LastSyncTime}"); #endif } return true; } else { UnityEngine.Debug.LogError($"[InGameMailService] FetchMail Failed {resp.error} "); } } } catch (Exception e) { UnityEngine.Debug.LogException(e); } UnityEngine.Debug.LogWarning("[InGameMailService] FetchMail Failed"); return false; } public async Task SendMail(MailInfo[] mailInfos) { try { var req = new SendMailRequest() { mailInfos = mailInfos }; if (!IsCurrent) return false; var result = await _customServerMgr.CustomServerPost(MailUrls.Send, JsonConvert.SerializeObject(req)); if (!IsCurrent) return false; if (result != null) { var resp = JsonConvert.DeserializeObject(result); if (!resp.success) { UnityEngine.Debug.LogError($"[InGameMailService] SendMail Failed {resp.error} "); } return resp.success; } } catch (Exception e) { UnityEngine.Debug.LogError(e); } return false; } protected virtual async Task UpdateMailState(GameMail[] mails, EMailState state) { var req = new UpdateMailStateRequest() { mailStates = mails.Select(m => new MailStateInfo(m, state)).ToArray() }; if (!IsCurrent) return false; var result = await _customServerMgr.CustomServerPost(MailUrls.UpdateState, JsonConvert.SerializeObject(req)); if (!IsCurrent) return false; if (result != null) { var resp = JsonConvert.DeserializeObject(result); if (resp.success) { foreach (var s in req.mailStates) { var mail = _cache.GetMail(s.Id); if (mail != null) mail.State = s.state; } return true; } } return false; } public virtual async Task MarkAsRead(GameMail mail) { if (!IsCurrent || mail.IsRead) return false; var result = await UpdateMailState(new[] { mail }, EMailState.HasRead); if (result) _cache.Save(); return result; } public virtual async Task MarkAsRead(GameMail[] mails = null) { if (!IsCurrent) return false; if (mails == null) { mails = _cache.GetAll() .Where(m => !m.IsRead) .ToArray(); } return await UpdateMailState(mails, EMailState.HasRead); } public Task TakeReward(GameMail mail) => TakeReward(new[] { mail }); public async Task TakeReward(GameMail[] mails = null) { if (!IsCurrent || rewardPending) return false; if (saveStore is KVPlayerSaveStore && !saveStore.Read("NewPlayerDataDic").Success) return false; var eligible = (mails ?? _cache.GetAll().ToArray()) .Where(m => m != null && m.HasDrops && !m.HasReward) .GroupBy(m => m.Id).Select(g => g.First()).ToArray(); if (eligible.Length == 0) return false; // Freeze eligibility and the personal-mail inflation rule before changing server state. var rewards = eligible.Select(m => new { m.Type, Drops = m.DropIds.ToList() }).ToArray(); var items = GContext.container.Resolve(); var player = GContext.container.Resolve(); rewardPending = true; try { if (!await UpdateMailState(eligible, EMailState.RewardTaken) || !IsCurrent) return false; _cache.Save(); await PlayerSaveAction.RunAsync(saveStore, "mail-reward", () => { foreach (var reward in rewards) { if (reward.Type == EMailType.Personal) items.AddItemByDropListLureInflation(null, reward.Drops, null); else items.AddItemByDropList(reward.Drops, null); } PlayerSaveAction.Present(() => GContext.Publish(new ShowData())); }, () => player.SavePlayerData(), userService.LoginLifetime); if (!IsCurrent) return false; rewardPending = false; return true; } catch (Exception e) { UnityEngine.Debug.LogException(e); return false; } // On failure keep the claim blocked in this login. Server state and KV saves are not atomic. } public async Task DeleteMail(GameMail mail) { if (!IsCurrent || mail.IsDeleted) return false; var result = await UpdateMailState(new[] { mail }, EMailState.HasDeleted); if (result) _cache.Save(); return result; } public async Task DeleteMail(GameMail[] mails = null) { if (!IsCurrent) return false; if (mails == null) { mails = _cache.GetAll() .Where(m => m.IsRead && m.HasReward) .ToArray(); } else { mails = mails.Where(m => !m.IsDeleted) .ToArray(); } if (mails.Length == 0) return false; var result = await UpdateMailState(mails, EMailState.HasDeleted); if (result) _cache.Save(); return result; } }