Files
2026-05-26 16:15:54 +08:00

1411 lines
53 KiB
C#

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<Tables>();
private readonly IUserService _userService = GContext.container.Resolve<IUserService>();
private readonly IRTService _rtService = GContext.container.Resolve<IRTService>();
private readonly FriendService _friendService = GContext.container.Resolve<FriendService>();
private readonly ICustomServerMgr _customServerMgr = GContext.container.Resolve<ICustomServerMgr>();
public List<Component> Components;
public int EventId => _data.EventId;
public int RedirectId => _data.RedirectId;
public PlayerPreferenceCache Cache;
public bool IsWhole => _whole;
/// <summary>
/// In degrees.
/// </summary>
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<FishingEventData>().GetRedDot(_data.EventId) && Components.Any(c => c.IsMatched && !c.IsFinished))
return true;
var chainPackData = GContext.container.Resolve<FishingEventData>().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];
//<summary>
//Invited Partners
//</summary>
public List<string> InvitePartners { get; private set; }
//<summary>
//Received Invitations
//</summary>
public List<string> RequestPartners { get; private set; }
//<summary>
//Refused Invitations
//</summary>
public List<string> 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<int, (int, int)>();
Cache.ListInvitedBy = new List<ScrollViewItemInfo>();
Cache.ActionQ = new Dictionary<int, Queue<EventBuildBotAddScoreAction>>();
}
else
{
Debug.Log($"<color=#ffc700>[EventPartner]Cache: {cacheString}</color>");
try
{
Cache = JsonConvert.DeserializeObject<PlayerPreferenceCache>(cacheString);
}
catch (Exception ex)
{
throw ex;
}
Debug.Log($"Cache Done");
}
var pfString = PlayFabMgr.Instance.GetLocalData(EventPartnerPlayfabData.Key);
EventPartnerPlayfabData pfData = EventPartnerPlayfabData.FromString(pfString);
Debug.Log($"<color=#2d7cee>[EventPartner] Try get response in load data:</color>");
var detailRequest = new EventBuildDetailRequest() { eventId = e.ID };
var response = await GContext.container.Resolve<ICustomServerMgr>()
.EventPartnerRequest<EventBuildDetailResp>(DetailUrl, detailRequest);
if (response == null)
{
response = new EventBuildDetailResp();
response.State = EEventBuildState.Failed;
response.Message = "Response is null";
Debug.Log($"<color=#2d7cee>[EventPartner] Response is Null.</color>");
}
else
{
Debug.Log($"<color=#2d7cee>[EventPartner] Response: {response}</color>");
Debug.Log($"<color=#2d7cee>[EventPartner] ResponseState: {response.State}</color>");
Debug.Log($"<color=#2d7cee>[EventPartner] ResponseMessage: {response.Message}</color>");
}
switch (response.State)
{
case EEventBuildState.Failed:
Debug.Log($"<color=red>[EventPartner] Load data error from server: {response.Message} </color>");
ResolveEventOnly(e);
break;
case EEventBuildState.NoActiveEvent:
Debug.Log($"<color=red>[EventPartner] Event partner not active in server: {response.Message}, but this is not supposed to happen any more.</color>");
ResolveEventOnly(e);
break;
case EEventBuildState.Success:
ResolveData(e, response, Cache, pfData);
SubscribeEvent();
break;
default:
Debug.Log($"<color=red>[EventPartner] Unexpected status: {response.State}./nMessage: {response.Message} </color>");
ResolveEventOnly(e);
break;
}
UITypes.EventPartnerPanel.SetType(MainPanelUrl);
UITypes.EventPartnerInfoPopupPanel.SetType(InfoPanelUrl);
var chainPackData = GContext.container.Resolve<FishingEventData>().ChainPackWithProgressMigrationData;
var packId = GContext.container.Resolve<Tables>().TbEventPartnerMain.GetOrDefault(e.RedirectID).PackId;
if (chainPackData.EventId != e.ID)
chainPackData.UpdateData(e.ID, packId);
Debug.Log($"<color=#2d7cee>[EventPartner]Load Data Event Thrown</color>");
GContext.Publish(new EventPartnerDataLoaded());
Unlock();
}
catch (Exception ex)
{
Unlock();
Debug.Log($"<color=red>[EventPartner]Load Data Error</color>");
throw ex;
}
}
/// <summary>
/// Resolve data from server and local Player Preference.
/// </summary>
/// <param name="e">fishing event</param>
/// <param name="r">server response for detail</param>
/// <param name="p">Player Preference</param>
private void ResolveData(FishingEvent e, EventBuildDetailResp r, PlayerPreferenceCache p, EventPartnerPlayfabData d)
{
Debug.Log($"<color=#2d7cee>[EventPartner]Resolve data: </color>");
_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<Component>();
r.Scores ??= new Dictionary<int, Dictionary<string, int>>();
InvitePartners = r.Info.InvitePartners;
RequestPartners = r.Info.RequestPartners;
RefusePartners = r.Info.RefusePartners;
if (e.ID != d.EventId)
{
_data.TicketCount = GContext.container.Resolve<FishingEventData>().GetInitWelcomeGift(e.ID);
ToPlayfabData().Save();
Debug.Log($"<color=#2d7cee>[EventPartner]New Player. Reward give.</color>");
}
InvitePartners ??= new List<string>();
RequestPartners ??= new List<string>();
RefusePartners ??= new List<string>();
// Debug.Log($"<color=#ffc700>[EventPartner] DataHash from Initiation: {GetHashCode()}</color>");
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("<color=#2d7cee>[EventPartner]Bot not found. Schedule a new bot.</color>");
componentData.BotTargetScore = 0;
componentData.WakeStartTime = ZZTimeHelper.UtcNow();
componentData.WakingSeconds = Random.Range(
GContext.container.Resolve<Tables>().TbEventPartnerConfig.RobotDailyActiveTimeWindow[0],
GContext.container.Resolve<Tables>().TbEventPartnerConfig.RobotDailyActiveTimeWindow[1]);
componentData.BotGrade = FtMathUtils.GetRandomIdxFromWeightList(
GContext.container.Resolve<Tables>().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($"<color=#2d7cee>[EventPartner]Fail to get self score. Id:{componentData}.</color>");
componentData.ScoreSelfActual = 0;
}
if (!scoreData.TryGetValue(componentData.PartnerId, out componentData.ScorePartnerActual))
{
Debug.Log($"<color=#2d7cee>[EventPartner]Fail to get partner score. Id:{componentData}.</color>");
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($"<color=#2d7cee>[EventPartner]Resolve Data Event Thrown</color>");
GContext.Publish(new EventPartnerDataLoaded());
_whole = true;
}
private void ResolveEventOnly(FishingEvent e)
{
Debug.Log($"<color=#2d7cee>[EventPartner]Resolve event only: </color>");
_data = new MetaData();
_data.EventId = e.ID;
_data.RedirectId = e.RedirectID;
_data.Components = new ComponentData[4];
Components = new List<Component>();
InvitePartners ??= new List<string>();
RequestPartners ??= new List<string>();
RefusePartners ??= new List<string>();
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($"<color=#2d7cee>[EventPartner]Resolve Only Event Data Event Thrown</color>");
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<int, Dictionary<string, int>>();
InvitePartners = r.Info.InvitePartners;
RequestPartners = r.Info.RequestPartners;
RefusePartners = r.Info.RefusePartners;
InvitePartners ??= new List<string>();
RequestPartners ??= new List<string>();
RefusePartners ??= new List<string>();
if (_data.EventId != d.EventId)
{
_data.TicketCount = GContext.container.Resolve<FishingEventData>().GetInitWelcomeGift(_data.EventId);
ToPlayfabData().Save();
Debug.Log($"<color=#2d7cee>[EventPartner]New Player. Reward give.</color>");
}
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("<color=#2d7cee>[EventPartner]Bot not found. Schedule a new bot.</color>");
componentData.BotTargetScore = 0;
componentData.WakeStartTime = ZZTimeHelper.UtcNow();
componentData.WakingSeconds = Random.Range(
GContext.container.Resolve<Tables>().TbEventPartnerConfig.RobotDailyActiveTimeWindow[0],
GContext.container.Resolve<Tables>().TbEventPartnerConfig.RobotDailyActiveTimeWindow[1]);
componentData.BotGrade = FtMathUtils.GetRandomIdxFromWeightList(
GContext.container.Resolve<Tables>().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($"<color=#2d7cee>[EventPartner]Fail to get self score. Id:{componentData}.</color>");
componentData.ScoreSelfActual = 0;
}
if (!scoreData.TryGetValue(componentData.PartnerId,
out componentData.ScorePartnerActual))
{
Debug.Log($"<color=#2d7cee>[EventPartner]Fail to get partner score. Id:{componentData}.</color>");
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($"<color=#c191ff>[EventPartner]Cache saved: {cache}</color>");
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<FishingEventData>().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<IUserService>();
private readonly ComponentData _data;
// private PartnerData _partnerData;
private readonly Tables _tables = GContext.container.Resolve<Tables>();
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<EventBuildBotAddScoreAction> BotActionQueue
{
get
{
EventPartnerAct.Ctx.Data.Cache.ActionQ ??= new Dictionary<int, Queue<EventBuildBotAddScoreAction>>();
if (EventPartnerAct.Ctx.Data.Cache.ActionQ.TryGetValue(ComponentIndex, out var q))
return q;
q = new Queue<EventBuildBotAddScoreAction>();
EventPartnerAct.Ctx.Data.Cache.ActionQ.Add(ComponentIndex, q);
return q;
}
}
public string PartnerId
{
get => _data.PartnerId;
set => _data.PartnerId = value;
}
/// <summary>
/// This component idx starts from 0 and has nothing to do with the component id in table EventPartnerComponent.
/// </summary>
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<EventPartnerData>().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;
}
/// <summary>
/// Get progress index according to score input. Result starts at 0, which means no upgrade accomplished, no
/// reward has been claimed yet.
/// </summary>
/// <param name="score">Score to look up.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Called when a component matched to a bot got upgraded.
/// </summary>
public void ScheduleBotScoreAction()
{
var newActionQ = new Queue<EventBuildBotAddScoreAction>();
var currentProgress = GetProgressIdx(Score);
if (_data.BotGrade >= _tables.TbEventPartnerRobot.DataList.Count)
{
Debug.Log($"<color=#c191ff>[EventPartner]Grade {_data.BotGrade} out of range {_tables.TbEventPartnerRobot.DataList.Count}</color>");
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($"<color=#c191ff>[EventPartner]New bot target {newBotTargetScore} is smaller than old target {_data.BotTargetScore}</color>");
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<int>();
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($"<color=#c191ff>[EventPartner]Split {gap} into {scheduleAmount} @ {ComponentIndex}.</color>");
Debug.Log($"<color=#c191ff>[EventPartner]WakeTime:{_data.WakeStartTime}, StartTime: {startTime}, Duration: {scheduleDuration}s, Add {gap} points in {scheduleAmount} times.</color>");
Debug.Log($"<color=#c191ff>[EventPartner]Activation Delay: {startTime - _data.WakeStartTime}, BotAddScoreStrengthGrade: {_data.BotGrade}</color>");
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($"<color=#c191ff> [EventPartner]Add {a.Score} @ {a.Time} in slot {ComponentIndex}.</color>");
}
}
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($"<color=#ff4408>Score added by bot in slot{ComponentIndex} at {ZZTimeHelper.UtcNow()}</color>");
_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();
}
/// <summary>
/// Update partner's score.
/// </summary>
/// <param name="newScore">The target score.</param>
/// <returns>False if the new score is actually smaller than the current score. True if succeeded.</returns>
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<ICustomServerMgr>()
.EventPartnerRequest<EventBuildOperationResp>(UpdateScoreUrl, request);
// if (res.State == EEventBuildState.Success)
_data.ScorePartnerActual = newScore;
}
public static int GetRandomIdxFromWeightList(IEnumerable<int> weightList)
{
var accWeightList = new List<int>();
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<EventBuildBotAddScoreAction> BotActionQueue;
public DateTime WakeStartTime;
#endregion
}
public class MetaData
{
public ComponentData[] Components;
public int EventId, RedirectId, TicketCount, InvitationCount;
/// <summary>
/// In degrees.
/// </summary>
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<ScrollViewItemInfo> Recommend { get; set; }
public bool IsFirstTimeOpen = true;
/// <summary>
/// Score for display. Key: component index. value: (my display score, partner display score)
/// </summary>
public Dictionary<int, (int, int)> DisplayScore { get; set; }
/// <summary>
/// List of players that invited 'me'
/// </summary>
public List<ScrollViewItemInfo> ListInvitedBy { get; set; }
public Dictionary<int, Queue<EventBuildBotAddScoreAction>> ActionQ { get; set; }
/// <summary>
/// List of players that was invited by 'me'
/// </summary>
public List<ScrollViewItemInfo> 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<string, int> _buildEventBuffer = new Dictionary<string, int>();
private async void OnPartnerBuild(EventBuildScoreEvt e)
{
await Awaiters.NextFrame;
int componentIdx = GetComponentIndexByPartnerId(e.partnerId);
Debug.Log($"<color=#c191ff>[EventPartner]Partner Build score {e.score} at slot {componentIdx}</color>");
if (componentIdx is < 0 or >= 4)
{
Debug.Log($"<color=#c191ff>Error: Component index of partner{e.partnerId} is {componentIdx}</color>");
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<IUserService>().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<EventBuildPartnerEvt>(OnPartnerChange),
_rtService.OnEvent<EventBuildScoreEvt>(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<int, string> Partners { get; set; }
//<summary>
//Invited Partners
//</summary>
public List<string> InvitePartners { get; set; }
//<summary>
//Received Invitations
//</summary>
public List<string> RequestPartners { get; set; }
//<summary>
//Refused Invitations
//</summary>
public List<string> 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; }
/// <summary>
/// Score grade, 1-4
/// </summary>
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<int, Dictionary<string, int>> 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<string> 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<PlayerInfo> 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<PlayerInfo> 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<PlayerInfo> 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<int, int> LastProgressReceived { get; set; }
public Dictionary<int, EventBuildBotInfo> 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($"<color=#2d7cee>[EventPartner] Empty incoming data.</color>");
return new EventPartnerPlayfabData();
}
var data = JsonConvert.DeserializeObject<EventPartnerPlayfabData>(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 { }