using System; using System.Collections.Generic; using System.Linq; using asap.core; using Castle.Core.Internal; using cfg; using GameCore; using UnityEngine; using UnityEngine.Assertions; using Random = UnityEngine.Random; using game; using Newtonsoft.Json; using ScrollViewItemInfo = EventPartnerScrollViewItem.ScrollViewItemInfo; using UniRx; public class EventPartnerData { private bool _lock = false, _whole = false; private MetaData _data; private readonly Tables _tables = GContext.container.Resolve(); private readonly IUserService _userService = GContext.container.Resolve(); private readonly IRTService _rtService = GContext.container.Resolve(); private readonly FriendService _friendService = GContext.container.Resolve(); private readonly ICustomServerMgr _customServerMgr = GContext.container.Resolve(); public List Components; public int EventId => _data.EventId; public int RedirectId => _data.RedirectId; public PlayerPreferenceCache Cache; public bool IsWhole => _whole; /// /// In degrees. /// public float LastWheelRotation => _data.WheelRotation; private CompositeDisposable _disposables = null; public DateTime? LastDetailRequestFromEntranceButton = null; public int DetailRequestIntervalInSeconds = 15; public bool IsLastTimeDetailRequestFromEntranceButtonValid = false; public int TicketCount { get { if (_data == null) { Debug.Log($"[EventPartner] data is null!"); return 0; } return _data.TicketCount; } } public DateTime EndTime { get { return DateTime.Parse((_tables.TbFishingEvent[_data.EventId].TimeDefinition as LimitedTime)?.EndTime); } } public TimeSpan RemainingTime => EndTime - ZZTimeHelper.UtcNow(); public bool IsWithinEventTime { get { var et = DateTime.Parse((_tables.TbFishingEvent[_data.EventId].TimeDefinition as LimitedTime).EndTime); var st = DateTime.Parse((_tables.TbFishingEvent[_data.EventId].TimeDefinition as LimitedTime).StartTime); return ZZTimeHelper.UtcNow() >= st && ZZTimeHelper.UtcNow() < et; } } public bool IsEventActivated { get { // if (DebugFlag) // return true; return _data is not null && _tables.TbFishingEvent.DataMap.ContainsKey(_data.EventId) && _tables.TbFishingEvent[_data.EventId].Type == 4 && _tables.TbFishingEvent[_data.EventId].SubType == 3 && (_tables.TbEventPartnerMain.DataMap.ContainsKey(_data.RedirectId) || _tables.TbEventPartnerMain2.DataMap.ContainsKey(_data.RedirectId)) && IsWithinEventTime && DateTime.Parse((_tables.TbFishingEvent[_data.EventId].TimeDefinition as LimitedTime)?.StartTime) < ZZTimeHelper.UtcNow(); } } public bool IsBuildingCompleted => Components.All(component => component.IsFinished); public bool IsBuildingRewardCollected { get => _data.IsBuildingRewardCollected; set => _data.IsBuildingRewardCollected = value; } public bool DoNeedRedPoint { get { if (!_whole) return false; if (_data.IsFirstOpen) { _data.IsFirstOpen = false; return true; } if (Components.Any(component => component.LastProgressReceived < component.GetProgressIdx(component.Score))) return true; // Debug.Log(_data.EventId); if (IsBuildingCompleted) return !IsBuildingRewardCollected; if (TicketCount >= GContext.container.Resolve().GetRedDot(_data.EventId) && Components.Any(c => c.IsMatched && !c.IsFinished)) return true; var chainPackData = GContext.container.Resolve().ChainPackWithProgressMigrationData; if (chainPackData != null && chainPackData.DoNeedPackRedPoint) return true; return !IsFullyMatched && !RequestPartners.IsNullOrEmpty(); } } public string Icon => _tables.TbEventPartnerMain[_data.RedirectId].Icon; public string MainPanelUrl => _tables.TbEventPartnerMain[_data.RedirectId].UIPanel; public string InfoPanelUrl => _tables.TbEventPartnerMain[_data.RedirectId].InfoPanel; public string AddressableLabel => _tables.TbEventPartnerMain[_data.RedirectId].Addressable; public string AtlasUrl => _tables.TbEventPartnerMain[_data.RedirectId].Atlas; public string BgmUrl => _tables.TbEventPartnerMain[_data.RedirectId].Bgm; public string MatchBgmUrl => "audio_ui_partner_space_radar"; public int ActiveFriendCountThreshold => _tables.TbEventPartnerConfig.FriendRecommendParam1[0]; public int RecommendCount => _tables.TbEventPartnerConfig.FriendRecommendParam2[_friendService.FriendList.Count < ActiveFriendCountThreshold ? 0 : 1]; // //Invited Partners // public List InvitePartners { get; private set; } // //Received Invitations // public List RequestPartners { get; private set; } // //Refused Invitations // public List RefusePartners { get; private set; } public bool IsFullyMatched => Components.All(c => c.IsMatched); public int InvitationCount => _data.InvitationCount; public bool DoRecommendBot => !IsFullyMatched && _data.InvitationCount >= _tables.TbEventPartnerConfig.RobotRecommendTriggerCount; public async void LoadData(FishingEvent e) { try { await LoadDataAsync(e); } catch (Exception ex) { Debug.LogError(ex.Message); } } public async System.Threading.Tasks.Task LoadDataAsync(FishingEvent e) { if (_lock) { Debug.Log($"[EventPartner] Data locked, abandon."); return; } try { _lock = true; _playerPreferenceKey = "EventPartnerRecommendCache" + _userService.UserId; //Load Player Preferences var cacheString = PlayerPrefs.GetString(_playerPreferenceKey, ""); if (cacheString == "") { Cache = new PlayerPreferenceCache(); Cache.Recommend = null; Cache.DisplayScore = new Dictionary(); Cache.ListInvitedBy = new List(); Cache.ActionQ = new Dictionary>(); } else { Debug.Log($"[EventPartner]Cache: {cacheString}"); try { Cache = JsonConvert.DeserializeObject(cacheString); } catch (Exception ex) { throw ex; } Debug.Log($"Cache Done"); } var pfString = PlayFabMgr.Instance.GetLocalData(EventPartnerPlayfabData.Key); EventPartnerPlayfabData pfData = EventPartnerPlayfabData.FromString(pfString); Debug.Log($"[EventPartner] Try get response in load data:"); var detailRequest = new EventBuildDetailRequest() { eventId = e.ID }; var response = await GContext.container.Resolve() .EventPartnerRequest(DetailUrl, detailRequest); if (response == null) { response = new EventBuildDetailResp(); response.State = EEventBuildState.Failed; response.Message = "Response is null"; Debug.Log($"[EventPartner] Response is Null."); } else { Debug.Log($"[EventPartner] Response: {response}"); Debug.Log($"[EventPartner] ResponseState: {response.State}"); Debug.Log($"[EventPartner] ResponseMessage: {response.Message}"); } switch (response.State) { case EEventBuildState.Failed: Debug.Log($"[EventPartner] Load data error from server: {response.Message} "); ResolveEventOnly(e); break; case EEventBuildState.NoActiveEvent: Debug.Log($"[EventPartner] Event partner not active in server: {response.Message}, but this is not supposed to happen any more."); ResolveEventOnly(e); break; case EEventBuildState.Success: ResolveData(e, response, Cache, pfData); SubscribeEvent(); break; default: Debug.Log($"[EventPartner] Unexpected status: {response.State}./nMessage: {response.Message} "); ResolveEventOnly(e); break; } UITypes.EventPartnerPanel.SetType(MainPanelUrl); UITypes.EventPartnerInfoPopupPanel.SetType(InfoPanelUrl); var chainPackData = GContext.container.Resolve().ChainPackWithProgressMigrationData; var packId = GContext.container.Resolve().TbEventPartnerMain.GetOrDefault(e.RedirectID).PackId; if (chainPackData.EventId != e.ID) chainPackData.UpdateData(e.ID, packId); Debug.Log($"[EventPartner]Load Data Event Thrown"); GContext.Publish(new EventPartnerDataLoaded()); Unlock(); } catch (Exception ex) { Unlock(); Debug.Log($"[EventPartner]Load Data Error"); throw ex; } } /// /// Resolve data from server and local Player Preference. /// /// fishing event /// server response for detail /// Player Preference private void ResolveData(FishingEvent e, EventBuildDetailResp r, PlayerPreferenceCache p, EventPartnerPlayfabData d) { Debug.Log($"[EventPartner]Resolve data: "); _data = new MetaData(); _data.EventId = e.ID; _data.RedirectId = e.RedirectID; _data.IsFirstOpen = p.IsFirstTimeOpen; _data.IsBuildingRewardCollected = d.IsBuildingRewardCollected; _data.TicketCount = d.TicketCount; _data.InvitationCount = d.InvitationCount; _data.Components = new ComponentData[4]; Components = new List(); r.Scores ??= new Dictionary>(); InvitePartners = r.Info.InvitePartners; RequestPartners = r.Info.RequestPartners; RefusePartners = r.Info.RefusePartners; if (e.ID != d.EventId) { _data.TicketCount = GContext.container.Resolve().GetInitWelcomeGift(e.ID); ToPlayfabData().Save(); Debug.Log($"[EventPartner]New Player. Reward give."); } InvitePartners ??= new List(); RequestPartners ??= new List(); RefusePartners ??= new List(); // Debug.Log($"[EventPartner] DataHash from Initiation: {GetHashCode()}"); for (int idx = 0; idx < 4; idx++) { _data.Components[idx] = new ComponentData { ComponentIndex = idx, ComponentId = _tables.TbEventPartnerMain[RedirectId].ComponentList[idx] }; var componentData = _data.Components[idx]; if (r.Info.Partners is null || !r.Info.Partners.TryGetValue(idx, out var partner)) { componentData.PartnerId = ""; continue; } // Get display info if (partner.EndsWith(EventPartnerBot.RobotIdentifier)) { componentData.PartnerId = partner; EventPartnerBot.GetBotDisplayInfo(partner, out componentData.AvatarUrl, out componentData.DisplayName); var botRes = d.Bots.TryGetValue(idx, out var bot); if (botRes) { componentData.BotTargetScore = bot.TargetScore; componentData.WakeStartTime = bot.WakeStartTime; componentData.WakingSeconds = bot.WakingSeconds; componentData.BotGrade = bot.Grade; } else { Debug.Log("[EventPartner]Bot not found. Schedule a new bot."); componentData.BotTargetScore = 0; componentData.WakeStartTime = ZZTimeHelper.UtcNow(); componentData.WakingSeconds = Random.Range( GContext.container.Resolve().TbEventPartnerConfig.RobotDailyActiveTimeWindow[0], GContext.container.Resolve().TbEventPartnerConfig.RobotDailyActiveTimeWindow[1]); componentData.BotGrade = FtMathUtils.GetRandomIdxFromWeightList( GContext.container.Resolve().TbEventPartnerRobot.DataList.Select(r => r.Weight)); } // componentData.BotTargetScore = d.Bots[idx].TargetScore; // componentData.WakeStartTime = d.Bots[idx].WakeStartTime; // componentData.WakingSeconds = d.Bots[idx].WakingSeconds; // componentData.BotGrade = d.Bots[idx].Grade; } else { componentData.PartnerId = partner; var partnerInfo = _userService.GetPlayInfo(componentData.PartnerId); componentData.AvatarUrl = partnerInfo.avatarUrl; componentData.DisplayName = partnerInfo.name; } // Get actual score if (partner.EndsWith(EventPartnerBot.RobotIdentifier)) { var botRes = d.Bots.TryGetValue(idx, out var bot); if (botRes) { componentData.ScoreSelfActual = bot.ScorePlayer; componentData.ScorePartnerActual = bot.ScoreBot; } else { componentData.ScoreSelfActual = 0; componentData.ScorePartnerActual = 0; } } else if (r.Scores.TryGetValue(idx, out var scoreData)) { if (!scoreData.TryGetValue(_userService.UserId, out componentData.ScoreSelfActual)) { Debug.Log($"[EventPartner]Fail to get self score. Id:{componentData}."); componentData.ScoreSelfActual = 0; } if (!scoreData.TryGetValue(componentData.PartnerId, out componentData.ScorePartnerActual)) { Debug.Log($"[EventPartner]Fail to get partner score. Id:{componentData}."); componentData.ScorePartnerActual = 0; } } else { componentData.ScoreSelfActual = 0; componentData.ScorePartnerActual = 0; } // Get display score. if (p.DisplayScore.TryGetValue(idx, out var displayScore)) { componentData.ScoreSelfDisplay = displayScore.Item1 <= componentData.ScoreSelfActual ? displayScore.Item1 : componentData.ScoreSelfActual; componentData.ScorePartnerDisplay = displayScore.Item2 <= componentData.ScorePartnerActual ? displayScore.Item2 : componentData.ScorePartnerActual; } else // Cache does not have this component index, create a new one from server data. { componentData.ScoreSelfDisplay = componentData.ScoreSelfActual; componentData.ScorePartnerDisplay = componentData.ScorePartnerActual; p.DisplayScore.Add(idx, (componentData.ScoreSelfDisplay, componentData.ScorePartnerDisplay)); } if (d.LastProgressReceived.IsNullOrEmpty() || !d.LastProgressReceived.TryGetValue(idx, out var lastProgressReceived)) componentData.LastProgressReceived = 0; else componentData.LastProgressReceived = lastProgressReceived; } foreach (var c in _data.Components) Components.Add(new Component(c)); Debug.Log($"[EventPartner]Resolve Data Event Thrown"); GContext.Publish(new EventPartnerDataLoaded()); _whole = true; } private void ResolveEventOnly(FishingEvent e) { Debug.Log($"[EventPartner]Resolve event only: "); _data = new MetaData(); _data.EventId = e.ID; _data.RedirectId = e.RedirectID; _data.Components = new ComponentData[4]; Components = new List(); InvitePartners ??= new List(); RequestPartners ??= new List(); RefusePartners ??= new List(); for (int idx = 0; idx < 4; idx++) { _data.Components[idx] = new ComponentData { ComponentIndex = idx, ComponentId = _tables.TbEventPartnerMain[RedirectId].ComponentList[idx] }; } foreach (var c in _data.Components) Components.Add(new Component(c)); Debug.Log($"[EventPartner]Resolve Only Event Data Event Thrown"); GContext.Publish(new EventPartnerDataLoaded()); } public void ResolveAllDataButEvent(EventBuildDetailResp r, PlayerPreferenceCache p, EventPartnerPlayfabData d) { _data.IsFirstOpen = p.IsFirstTimeOpen; _data.IsBuildingRewardCollected = d.IsBuildingRewardCollected; _data.TicketCount = d.TicketCount; _data.InvitationCount = d.InvitationCount; r.Scores ??= new Dictionary>(); InvitePartners = r.Info.InvitePartners; RequestPartners = r.Info.RequestPartners; RefusePartners = r.Info.RefusePartners; InvitePartners ??= new List(); RequestPartners ??= new List(); RefusePartners ??= new List(); if (_data.EventId != d.EventId) { _data.TicketCount = GContext.container.Resolve().GetInitWelcomeGift(_data.EventId); ToPlayfabData().Save(); Debug.Log($"[EventPartner]New Player. Reward give."); } for (int idx = 0; idx < 4; idx++) { var componentData = _data.Components[idx]; if (r.Info.Partners is null || !r.Info.Partners.TryGetValue(idx, out var partner)) { componentData.PartnerId = ""; continue; } // Get display info if (partner.EndsWith(EventPartnerBot.RobotIdentifier)) { componentData.PartnerId = partner; EventPartnerBot.GetBotDisplayInfo(partner, out componentData.AvatarUrl, out componentData.DisplayName); var botRes = d.Bots.TryGetValue(idx, out var bot); if (botRes) { componentData.BotTargetScore = bot.TargetScore; componentData.WakeStartTime = bot.WakeStartTime; componentData.WakingSeconds = bot.WakingSeconds; componentData.BotGrade = bot.Grade; } else { Debug.Log("[EventPartner]Bot not found. Schedule a new bot."); componentData.BotTargetScore = 0; componentData.WakeStartTime = ZZTimeHelper.UtcNow(); componentData.WakingSeconds = Random.Range( GContext.container.Resolve().TbEventPartnerConfig.RobotDailyActiveTimeWindow[0], GContext.container.Resolve().TbEventPartnerConfig.RobotDailyActiveTimeWindow[1]); componentData.BotGrade = FtMathUtils.GetRandomIdxFromWeightList( GContext.container.Resolve().TbEventPartnerRobot.DataList.Select(r => r.Weight)); } } else { componentData.PartnerId = partner; var partnerInfo = _userService.GetPlayInfo(componentData.PartnerId); componentData.AvatarUrl = partnerInfo.avatarUrl; componentData.DisplayName = partnerInfo.name; } // Get actual score if (partner.EndsWith(EventPartnerBot.RobotIdentifier)) { EventPartnerBot.GetBotDisplayInfo(partner, out componentData.AvatarUrl, out componentData.DisplayName); var botRes = d.Bots.TryGetValue(idx, out var bot); if (botRes) { componentData.ScoreSelfActual = bot.ScorePlayer; componentData.ScorePartnerActual = bot.ScoreBot; } else { componentData.ScoreSelfActual = 0; componentData.ScorePartnerActual = 0; } } else if (r.Scores.TryGetValue(idx, out var scoreData)) { if (!scoreData.TryGetValue(_userService.UserId, out componentData.ScoreSelfActual)) { Debug.Log($"[EventPartner]Fail to get self score. Id:{componentData}."); componentData.ScoreSelfActual = 0; } if (!scoreData.TryGetValue(componentData.PartnerId, out componentData.ScorePartnerActual)) { Debug.Log($"[EventPartner]Fail to get partner score. Id:{componentData}."); componentData.ScorePartnerActual = 0; } } else { componentData.ScoreSelfActual = 0; componentData.ScorePartnerActual = 0; } // Get display score. if (p.DisplayScore.TryGetValue(idx, out var displayScore)) { componentData.ScoreSelfDisplay = displayScore.Item1 <= componentData.ScoreSelfActual ? displayScore.Item1 : componentData.ScoreSelfActual; componentData.ScorePartnerDisplay = displayScore.Item2 <= componentData.ScorePartnerActual ? displayScore.Item2 : componentData.ScorePartnerActual; } else // Cache does not have this component index, create a new one from server data. { componentData.ScoreSelfDisplay = componentData.ScoreSelfActual; componentData.ScorePartnerDisplay = componentData.ScorePartnerActual; p.DisplayScore.Add(idx, (componentData.ScoreSelfDisplay, componentData.ScorePartnerDisplay)); } if (d.LastProgressReceived.IsNullOrEmpty() || !d.LastProgressReceived.TryGetValue(idx, out var lastProgressReceived)) componentData.LastProgressReceived = 0; else componentData.LastProgressReceived = lastProgressReceived; } _whole = true; // If there is a error and scheduled a new bot. ToPlayfabData().Save(); } public void ActivateRobotRecommend() { _data.InvitationCount = 200; } #if UNITY_EDITOR public void ResetData() { // _data.EventId = 0; int componentCount = 0; componentCount = GetComponentCount(); _data.Components = new ComponentData[componentCount]; for (int i = 0; i < componentCount; i++) { _data.Components[i] = new ComponentData { ComponentIndex = i, ComponentId = _tables.TbEventPartnerMain[RedirectId].ComponentList[i] }; Components.Add(new Component(_data.Components[i])); } _data.TicketCount = int.MaxValue / 4096; SavePlayerPreferenceData(); } #endif public void SavePlayerPreferenceData() { for (int i = 0; i < 4; i++) Cache.DisplayScore[i] = (Components[i].ScoreSelfDisplay, Components[i].ScorePartnerDisplay); string cache = JsonConvert.SerializeObject(Cache); // Debug.Log($"[EventPartner]Cache saved: {cache}"); PlayerPrefs.SetString(_playerPreferenceKey, cache); } private int GetComponentIndexByPartnerId(string partnerId) { foreach (var c in Components.Where(c => c.PartnerId == partnerId)) return c.ComponentIndex; return -10086; } public int GetComponentCount() { return _tables.TbEventPartnerMain.DataMap.ContainsKey(_data.RedirectId) ? _tables.TbEventPartnerMain[_data.RedirectId].ComponentList.Count : 0; } public bool ContainsPartnerId(string id) { return Components.Any(c => c.IsMatched && c.PartnerId == id); } // public void AddPartnerTicketForce(int n) // { // _data.TicketCount += n; // } public bool AddTicket(int n) { _data.TicketCount += n; if (_data.TicketCount < 0) { _data.TicketCount -= n; return false; } GContext.container.Resolve().SaveTransitionData(_data.EventId, _data.TicketCount); GContext.Publish(new EventPartnerTicketChangedEvent()); ToPlayfabData().Save(); return true; } public bool HasEnoughTicket(int cost) { return _data.TicketCount >= cost; } public void RecordWheelRotation(float rotation) { _data.WheelRotation = rotation; } public void AddInvitationCount() { _data.InvitationCount++; } public void Unlock() { _lock = false; Debug.Log("[EventPartner] Unlock."); } public class Component { private static readonly IUserService UserService = GContext.container.Resolve(); private readonly ComponentData _data; // private PartnerData _partnerData; private readonly Tables _tables = GContext.container.Resolve(); private readonly int _redirectId; public bool IsMatched => _data.PartnerId != ""; public int Score => _data.ScoreSelfActual + _data.ScorePartnerActual; public int ScoreSelf => _data.ScoreSelfActual; public int ScorePartner => _data.ScorePartnerActual; public bool IsPartnerRobot => _data.PartnerId.EndsWith(EventPartnerBot.RobotIdentifier); public int BotIndex => EventPartnerBot.Id2Idx(_data.PartnerId); public int BotTargetScore => _data.BotTargetScore; /// Strength of bot adding score. public int BotGrade { get => _data.BotGrade; set => _data.BotGrade = value; } public Queue BotActionQueue { get { EventPartnerAct.Ctx.Data.Cache.ActionQ ??= new Dictionary>(); if (EventPartnerAct.Ctx.Data.Cache.ActionQ.TryGetValue(ComponentIndex, out var q)) return q; q = new Queue(); EventPartnerAct.Ctx.Data.Cache.ActionQ.Add(ComponentIndex, q); return q; } } public string PartnerId { get => _data.PartnerId; set => _data.PartnerId = value; } /// /// This component idx starts from 0 and has nothing to do with the component id in table EventPartnerComponent. /// public int ComponentIndex => _data.ComponentIndex; public int ComponentId => _data.ComponentId; public int ScoreDisplay => _data.ScoreSelfDisplay + _data.ScorePartnerDisplay; public int ScorePartnerDisplay => _data.ScorePartnerDisplay; public int ScoreSelfDisplay => _data.ScoreSelfDisplay; public int DisplayStageProgress => GetProgressIdx(ScoreDisplay); public int RedirectID => _redirectId; public int LastProgressReceived { get => _data.LastProgressReceived; set => _data.LastProgressReceived = value; } public bool IsFinished => _data.LastProgressReceived >= _tables.TbEventPartnerMain[_redirectId].StagePoint.Count; public bool IsBuilt => Score >= MaxScore; public Component(ComponentData data) { _data = data; _redirectId = GContext.container.Resolve().RedirectId; } public int MaxScore => _tables.TbEventPartnerMain[RedirectID].StagePoint[^1]; public string GetComponentName() { var componentTable = _tables.TbEventPartnerComponent; return componentTable.DataMap.ContainsKey(_data.ComponentId) ? LocalizationMgr.GetText(componentTable[_data.ComponentId].Text_l10n_key) : ""; } public string PartnerAvatarUrl => _data.AvatarUrl; public string PartnerDisplayName => _data.DisplayName; public DateTime BotWakeTime { get => _data.WakeStartTime; set => _data.WakeStartTime = value; } public int BotWakingSeconds { get => _data.WakingSeconds; set => _data.WakingSeconds = value; } public void SetComponent(string playfabId) { _data.PartnerId = playfabId; var info = UserService.GetPlayInfo(playfabId); _data.AvatarUrl = info.avatarUrl; _data.DisplayName = info.name; } public void SetComponent(ScrollViewItemInfo info) { _data.PartnerId = info.PlayfabId; _data.AvatarUrl = info.AvatarUrl; _data.DisplayName = info.DisplayName; } /// /// Get progress index according to score input. Result starts at 0, which means no upgrade accomplished, no /// reward has been claimed yet. /// /// Score to look up. /// public int GetProgressIdx(int score) { var d = _tables.TbEventPartnerMain; Assert.IsTrue(d.DataMap.ContainsKey(_redirectId), $"RedirectId {_redirectId} not found in table."); var scoreList = d[_redirectId].StagePoint; int i = 0; while (i < scoreList.Count && score >= scoreList[i]) i++; // if (i == ScoreList.Count) return ScoreList.Count - 1; return i; } /// /// Called when a component matched to a bot got upgraded. /// public void ScheduleBotScoreAction() { var newActionQ = new Queue(); var currentProgress = GetProgressIdx(Score); if (_data.BotGrade >= _tables.TbEventPartnerRobot.DataList.Count) { Debug.Log($"[EventPartner]Grade {_data.BotGrade} out of range {_tables.TbEventPartnerRobot.DataList.Count}"); return; } var robotData = _tables.TbEventPartnerRobot.DataList[_data.BotGrade]; var percentage = Random.Range(robotData.PointsRange[0], robotData.PointsRange[1]) / 100; int newBotTargetScore = (int)(percentage * _tables.TbEventPartnerMain[RedirectID] .StagePoint[currentProgress]); if (newBotTargetScore < _data.BotTargetScore) { Debug.Log($"[EventPartner]New bot target {newBotTargetScore} is smaller than old target {_data.BotTargetScore}"); return; } var startTime = _data.WakeStartTime + TimeSpan.FromSeconds( Random.Range(_tables.TbEventPartnerConfig.RobotDailyActiveTimeWindow[0], _tables.TbEventPartnerConfig.RobotDailyActiveTimeWindow[1])); while (startTime < ZZTimeHelper.UtcNow()) { startTime += TimeSpan.FromDays(1); } var scheduleDuration = Random.Range( _tables.TbEventPartnerConfig.RobotSingleAddPointsTimeWindow[0], _tables.TbEventPartnerConfig.RobotSingleAddPointsTimeWindow[1]); var scheduleAmount = Random.Range( _tables.TbEventPartnerConfig.RobotSingleAddPointsOperations[0], _tables.TbEventPartnerConfig.RobotSingleAddPointsOperations[1]); var timeSet = new HashSet(); while (timeSet.Count < scheduleAmount) { timeSet.Add(Random.Range(1, scheduleDuration)); } var timeList = timeSet.OrderBy(t => t).ToList(); _data.BotTargetScore = newBotTargetScore; var botScore = _data.ScorePartnerActual; var gap = newBotTargetScore - botScore; if (!FtMathUtils.RandomSplit(gap, scheduleAmount, out var scoreList)) Debug.Log($"[EventPartner]Split {gap} into {scheduleAmount} @ {ComponentIndex}."); Debug.Log($"[EventPartner]WakeTime:{_data.WakeStartTime}, StartTime: {startTime}, Duration: {scheduleDuration}s, Add {gap} points in {scheduleAmount} times."); Debug.Log($"[EventPartner]Activation Delay: {startTime - _data.WakeStartTime}, BotAddScoreStrengthGrade: {_data.BotGrade}"); int minWheelNumber = _tables.TbEventPartnerMain[_redirectId].SpinPoint[0]; for (int i = 1; i < _tables.TbEventPartnerMain[_redirectId].SpinPoint.Count; i++) if (minWheelNumber > _tables.TbEventPartnerMain[_redirectId].SpinPoint[i]) minWheelNumber = _tables.TbEventPartnerMain[_redirectId].SpinPoint[i]; for (int i = 0; i < scheduleAmount; i++) { FtMathUtils.ScaleByBase(scoreList[i], minWheelNumber, out var score); newActionQ.Enqueue(new EventBuildBotAddScoreAction { Score = score, Time = startTime + TimeSpan.FromSeconds(timeList[i]) }); } EventPartnerAct.Ctx.Data.Cache.ActionQ[ComponentIndex] = newActionQ; EventPartnerAct.Ctx.Data.SavePlayerPreferenceData(); EventPartnerAct.Ctx.Data.ToPlayfabData().Save(); foreach (var a in newActionQ) { Debug.Log($" [EventPartner]Add {a.Score} @ {a.Time} in slot {ComponentIndex}."); } } public int GetNextRewardId(int score) { var d = _tables.TbEventPartnerMain; Assert.IsTrue(d.DataMap.ContainsKey(_redirectId), $"RedirectId {_redirectId} not found in table."); int i = GetProgressIdx(score); if (i == d[_redirectId].StagePoint.Count) i--; return d[_redirectId].StageReward[i]; } public float GetProgress(int score) { var d = _tables.TbEventPartnerMain; Assert.IsTrue(d.DataMap.ContainsKey(_redirectId), $"RedirectId {_redirectId} not found in table."); var scoreList = d[_redirectId].StagePoint; int idx = 0, count = scoreList.Count; float progress = 0; while (idx < count) { if (score < scoreList[idx]) { if (idx == 0) progress += (float)score / scoreList[0]; else progress += (float)(score - scoreList[idx - 1]) / (scoreList[idx] - scoreList[idx - 1]); break; } progress += 1f; idx++; } return progress / count; } public float GetPartialProgress(int score) { var d = _tables.TbEventPartnerMain; Assert.IsTrue(d.DataMap.ContainsKey(_redirectId), $"RedirectId {_redirectId} not found in table."); var scoreList = d[_redirectId].StagePoint; int idx = 0; while (idx < scoreList.Count) { if (score < scoreList[idx]) { if (idx == 0) return (float)score / scoreList[0]; return (float)(score - scoreList[idx - 1]) / (scoreList[idx] - scoreList[idx - 1]); } if (score == MaxScore) return 1; if (score == scoreList[idx]) { return _data.LastProgressReceived >= idx + 1 ? 0 : 1; } idx++; } Debug.LogWarning($"Score {score} did not found a match."); return 1; } public void AddRobotScore() { if (!BotActionQueue.TryPeek(out var action) || ZZTimeHelper.UtcNow() < action.Time) { return; } Debug.Log($"Score added by bot in slot{ComponentIndex} at {ZZTimeHelper.UtcNow()}"); _data.ScorePartnerActual += action.Score; BotActionQueue.Dequeue(); EventPartnerAct.Ctx.Data.SavePlayerPreferenceData(); EventPartnerAct.Ctx.Data.ToPlayfabData().Save(); EventPartnerAct.Ctx.EventAggregator.Publish(new EventPartnerPartnerAddScore()); } public void SyncRobotScore() { EventPartnerAct.Ctx.Data.ToPlayfabData().Save(); } /// /// Update partner's score. /// /// The target score. /// False if the new score is actually smaller than the current score. True if succeeded. public bool UpdatePartnerScore(int newScore) { if (_data.ScorePartnerActual >= newScore) return false; _data.ScorePartnerActual = newScore; return true; } public int GetComponentScoreGap(int score) { int stageProgress = GetProgressIdx(score); //Score reaches top prize. Return random non-zero value. if (stageProgress == _tables.TbEventPartnerMain[_redirectId].StageReward.Count) return 1; int currentStagePoint = _tables.TbEventPartnerMain[_redirectId].StagePoint[stageProgress]; int previousStagePoint = stageProgress == 0 ? 0 : _tables.TbEventPartnerMain[_redirectId].StagePoint[stageProgress - 1]; return currentStagePoint - previousStagePoint; } public void SyncDisplayScore() { _data.ScorePartnerDisplay = _data.ScorePartnerActual; _data.ScoreSelfDisplay = _data.ScoreSelfActual; EventPartnerAct.Ctx.Data.Cache.DisplayScore[ComponentIndex] = (_data.ScoreSelfActual, _data.ScorePartnerActual); } public void AddSelfScore(int n) { _data.ScoreSelfActual += n; if (Score > MaxScore) _data.ScoreSelfActual -= Score - MaxScore; } public async System.Threading.Tasks.Task AddSelfScoreAsync(int n) { var newScore = n + _data.ScoreSelfActual; if (newScore + _data.ScorePartnerActual > MaxScore) newScore -= newScore + _data.ScorePartnerActual - MaxScore; var request = new EventBuildChangeScoreRequest { partnerId = PartnerId, score = newScore, slotId = ComponentIndex, eventId = _redirectId }; var res = await GContext.container.Resolve() .EventPartnerRequest(UpdateScoreUrl, request); // if (res.State == EEventBuildState.Success) _data.ScorePartnerActual = newScore; } public static int GetRandomIdxFromWeightList(IEnumerable weightList) { var accWeightList = new List(); int tmp = 0; foreach (int w in weightList) { tmp += w; accWeightList.Add(tmp); } tmp = Random.Range(0, accWeightList[^1]); for (int i = 0; i < accWeightList.Count; i++) { if (tmp < accWeightList[i]) return i; } return accWeightList.Count - 1; } } public class ComponentData { public int ScoreSelfActual, ScorePartnerActual, ComponentId, ScorePartnerDisplay, ScoreSelfDisplay, ComponentIndex, LastProgressReceived; public string PartnerId, AvatarUrl, DisplayName; #region BotData // public int BotTargetScore, BotScoreGap; public int BotGrade, BotIndex, BotTargetScore, WakingSeconds; // public Queue BotActionQueue; public DateTime WakeStartTime; #endregion } public class MetaData { public ComponentData[] Components; public int EventId, RedirectId, TicketCount, InvitationCount; /// /// In degrees. /// public float WheelRotation; public bool IsBuildingRewardCollected, IsFirstOpen; } #region Cache private string _playerPreferenceKey; public class PlayerPreferenceCache { public DateTime? RecommendListExpireTime { get; set; } public float WheelRotation = 0f; public List Recommend { get; set; } public bool IsFirstTimeOpen = true; /// /// Score for display. Key: component index. value: (my display score, partner display score) /// public Dictionary DisplayScore { get; set; } /// /// List of players that invited 'me' /// public List ListInvitedBy { get; set; } public Dictionary> ActionQ { get; set; } /// /// List of players that was invited by 'me' /// public List ListInvitation { get; set; } } #endregion #region HttpRequestModel public const string RecommendUrl = "recommend", DetailUrl = "detail", InviteUrl = "invite", IgnoreUrl = "refuse", AcceptUrl = "accept", UpdateScoreUrl = "score", MatchRecentPlayerInfoUrl = "recent-player-info", PlayerDisplayInfoUrl = "player-display-info", BuildUrl = "build"; private Dictionary _buildEventBuffer = new Dictionary(); private async void OnPartnerBuild(EventBuildScoreEvt e) { await Awaiters.NextFrame; int componentIdx = GetComponentIndexByPartnerId(e.partnerId); Debug.Log($"[EventPartner]Partner Build score {e.score} at slot {componentIdx}"); if (componentIdx is < 0 or >= 4) { Debug.Log($"Error: Component index of partner{e.partnerId} is {componentIdx}"); return; } if (Components[componentIdx].UpdatePartnerScore(e.score)) EventPartnerAct.Ctx.EventAggregator.Publish(new EventPartnerPartnerAddScore()); } private async void OnPartnerChange(EventBuildPartnerEvt e) { await Awaiters.NextFrame; switch (e.evtType) { case 0: //Invited RequestPartners.Add(e.partnerId); EventPartnerAct.Ctx.EventAggregator.Publish(new EventPartnerOnInvited()); break; case 1: //Accepted _data.Components[e.slotId].ComponentIndex = e.slotId; _data.Components[e.slotId].ComponentId = _tables.TbEventPartnerMain[RedirectId].ComponentList[e.slotId]; _data.Components[e.slotId].PartnerId = e.partnerId; InvitePartners.Remove(e.partnerId); var info = GContext.container.Resolve().GetPlayInfo(e.partnerId); EventPartnerAct.Ctx.EventAggregator.Publish(new EventPartnerMatchSuccess { NamePartner = info.name, IconPartner = info.avatarUrl, NameComponent = Components[e.slotId].GetComponentName(), SlotId = e.slotId, PartnerId = e.partnerId }); break; case -1: //Refused RefusePartners.Add(e.partnerId); InvitePartners.Remove(e.partnerId); break; } } public void SubscribeEvent() { _disposables?.Dispose(); _disposables = new CompositeDisposable() { _rtService.OnEvent(OnPartnerChange), _rtService.OnEvent(OnPartnerBuild) }; } public enum EEventBuildState : byte { Success = 0, Failed = 1, InviteNotFound = 2, AlreadyExist = 3, NoActiveEvent = 4, Full = 5 } public enum EEventBuildTokenState : byte { Success = 0, InsufficientToken = 1, SaveInfoFailure = 2, BadRequest = 3, } public enum EEventBuildUpdateProgressState : byte { Success = 0, RedisSaveError = 1, ProgressRewind = 2, SlotIdOutofRange = 3, EventBuildInfoNotFound = 4 } public enum EEventBuildBuildState { Success = 0, InsufficientToken = 1, ScoreError = 2, PlayerInfoNotFound = 3, Failed = 4 } public class EventBuildInfo { public Dictionary Partners { get; set; } // //Invited Partners // public List InvitePartners { get; set; } // //Received Invitations // public List RequestPartners { get; set; } // //Refused Invitations // public List RefusePartners { get; set; } } public class EventBuildBotInfo { public string Id { get; set; } // public int Level { get; set; } public int ScorePlayer { get; set; } public int ScoreBot { get; set; } [JsonIgnore] public EventBuildBotAddScoreAction[] ActionQueue { get; set; } /// /// Score grade, 1-4 /// public int Grade { get; set; } public int TargetScore { get; set; } public DateTime WakeStartTime { get; set; } public int WakingSeconds { get; set; } } public class EventBuildBotAddScoreAction { public DateTime? Time { get; set; } public int Score { get; set; } } public class EventBuildDetailRequest { public int eventId { get; set; } } public class EventBuildDetailResp { public EEventBuildState State { get; set; } public string Message { get; set; } public EventBuildInfo Info { get; set; } public Dictionary> Scores { get; set; } } public class EventBuildOperationResp { public EEventBuildState State { get; set; } public int? SlotId { get; set; } public string ErrorMessage { get; set; } } public class EventBuildRecommendPlayerRequest { public List Exclude { get; set; } public int? MaxLevel { get; set; } public int? MinLevel { get; set; } public int Count = 10; } public class EventBuildRecommendPlayerResp { public EEventBuildState State { get; set; } public string ErrorMessage { get; set; } public List Players { get; set; } } public class EventBuildChangeScoreRequest { public string partnerId { get; set; } public int slotId { get; set; } public int score { get; set; } public int eventId { get; set; } } public class EventBuildPartnerRequest { public string partnerId { get; set; } public int eventId { get; set; } } public class EventBuildPartnerEvt { // 0 = invite 1 = accept -1 = reject public int evtType { get; set; } public string partnerId { get; set; } public int slotId { get; set; } } public class EventBuildScoreEvt { public string partnerId { get; set; } public int score { get; set; } } // public class EventBuildRewardStatusResp // { // public EEventBuildState State { get; set; } // public string Message { get; set; } // public bool IsRewardCollected { get; set; } // } // public class EventBuildUpdateProgressRequest // { // public int SlotId { get; set; } // public int NewProgress { get; set; } // } public class PlayerInfo { public string PlayFabId { get; set; } public string AvatarUrl { get; set; } public string DisplayName { get; set; } public int Level { get; set; } public DateTime? lastLoginTime { get; set; } public DateTime? createTime { get; set; } } public class EventBuildRecentPlayerInfoRequest { public string[] playerPrefabs { get; set; } } public class EventBuildRecentPlayerInfoResponse { public EEventBuildState State { get; set; } public string Message { get; set; } public List playerInfos { get; set; } public string[] leftOvers { get; set; } } public class EventBuildPlayerDisplayInfoRequest { public string[] PlayfabIds { get; set; } } public class EventBuildPlayerDisplayInfoResp { public EEventBuildState State { get; set; } public string Message { get; set; } public List PlayerInfos { get; set; } } public class EventBuildBuildRequest { public string PartnerId { get; set; } public int NewScore { get; set; } public int EventId { get; set; } } public class EventBuildBuildResponse { public EEventBuildBuildState State { get; set; } public string Message { get; set; } } #endregion public class EventPartnerPlayfabData { [JsonIgnore] public const string Key = "EventPartnerData"; public int EventId { get; set; } public int TicketCount { get; set; } public int InvitationCount { get; set; } public bool IsBuildingRewardCollected { get; set; } public Dictionary LastProgressReceived { get; set; } public Dictionary Bots { get; set; } public void Save() { var s = JsonConvert.SerializeObject(this); PlayFabMgr.Instance.UpdateUserDataValue("EventPartnerData", s); } public static EventPartnerPlayfabData FromString(string s) { try { if (s == null || s == "") { Debug.Log($"[EventPartner] Empty incoming data."); return new EventPartnerPlayfabData(); } var data = JsonConvert.DeserializeObject(s); return data; } catch (Exception e) { Debug.LogError("[EventPartner] Failed to parse EventPartnerPlayfabData"); Debug.LogError(e); return new EventPartnerPlayfabData(); } } } public EventPartnerPlayfabData ToPlayfabData() { var data = new EventPartnerPlayfabData { EventId = EventId, TicketCount = TicketCount, InvitationCount = InvitationCount, IsBuildingRewardCollected = IsBuildingRewardCollected, LastProgressReceived = Components.ToDictionary(c => c.ComponentIndex, c => c.LastProgressReceived), Bots = Components.Where(c => c.PartnerId.EndsWith(EventPartnerBot.RobotIdentifier)).ToDictionary(c => c.ComponentIndex, c => new EventBuildBotInfo { Id = c.PartnerId, ScoreBot = c.ScorePartner, ScorePlayer = c.ScoreSelf, WakeStartTime = c.BotWakeTime, WakingSeconds = c.BotWakingSeconds, Grade = c.BotGrade, TargetScore = c.BotTargetScore, }), }; return data; } } public class EventPartnerTicketChangedEvent { }