using System.Linq; using GameCore; using UnityEngine; using System.Collections.Generic; using UnityEngine.Assertions; using asap.core; using game; using System.Text; using cfg; using System; public class EventLuckMagicModel : IHasChainPack, ILuckyTaskGetData, ILuckyTaskUIGetData { private int _eventId, _ticketCount, _chainProgress, _roundCount; /// /// Position of selection GameObject, or null if hidden. /// public Vector2? SelectionState; private readonly List _rings = new List(); public EventLuckMagicRing CurrentRing { get; set; } public EventLuckMagicRing OuterRing => _rings[0]; public EventLuckMagicRing MiddleRing => _rings[1]; public EventLuckMagicRing InnerRing => _rings[2]; public EventLuckMagicHigherRingData HigherRingData; public Dictionary RingMap; public int TicketCount => _ticketCount; public Dictionary TaskProgress; public const int TaskProgressCount = 3; public bool IsGameDepleted => _rings.All(x => x.IsDepleted); public bool DoesSkipAnimation = false; private DateTime _expiryTime, _startTime; public TimeSpan RemainingTime => _expiryTime - ZZTimeHelper.UtcNow(); public bool IsActive => RemainingTime.TotalSeconds > 0; public int EventId => _eventId; public int RoundCount => _roundCount; public DateTime ExpiryTime => _expiryTime; public EventLuckMagicParamsCtrl ParamsCtrl; public void InitNew(int eventId) { _eventId = eventId; _rings.Clear(); EventLuckMagicAct.TableContext.GetRingsInfo(out int[] costs, out int[] roadSignIds); for (int i = 0; i < costs.Length; i++) _rings.Add(EventLuckMagicRing.CreateEmpty(costs[i], roadSignIds[i], (EEventLuckMagicRingType)(i + 1))); EventLuckMagicAct.TableContext.GetBlocksInfo(out var infoList); RingMap = new Dictionary() { {EEventLuckMagicRingType.OuterRing, OuterRing}, {EEventLuckMagicRingType.MiddleRing, MiddleRing}, {EEventLuckMagicRingType.InnerRing, InnerRing} }; foreach (var info in infoList) { RingMap[info.RingType].AddBlock(new EventLuckMagicBlock(info, RingMap[info.RingType])); } RingMap[EEventLuckMagicRingType.OuterRing].ImportBitWiseBlockState(0); RingMap[EEventLuckMagicRingType.MiddleRing].ImportBitWiseBlockState(0); RingMap[EEventLuckMagicRingType.InnerRing].ImportBitWiseBlockState(0); HigherRingData = EventLuckMagicHigherRingData.CreateEmpty(); LinkRings(); _ticketCount = EventLuckMagicAct.TableContext.GetWelcomeGift(); _chainProgress = 0; TaskProgress = GetTaskProgress(); CurrentRing = _rings.FirstOrDefault(r => !r.IsDepleted) ?? _rings.Last(); SelectionState = null; DoesSkipAnimation = false; (_startTime, _expiryTime) = EventLuckMagicAct.TableContext.GetEventStartTimeAndEndTime(); _roundCount = 0; _taskData?.Dispose(); _dicTasks = new Dictionary(); _taskData = new LuckyTaskData(this); _taskData.InitDicTasks(EventLuckMagicAct.TableContext.GetFirstLuckyTaskIdList()); } public void Load(EventLuckMagicPlayfabData pfData, EventLuckMagicPlayerPreferenceData ppData) { _eventId = pfData.EventId; EventLuckMagicAct.TableContext.GetRingsInfo(out int[] costs, out int[] roadSignIds); for (int i = 0; i < costs.Length; i++) _rings.Add(EventLuckMagicRing.CreateEmpty(costs[i], roadSignIds[i], (EEventLuckMagicRingType)(i + 1))); EventLuckMagicAct.TableContext.GetBlocksInfo(out var infoList); RingMap = new Dictionary() { {EEventLuckMagicRingType.OuterRing, OuterRing}, {EEventLuckMagicRingType.MiddleRing, MiddleRing}, {EEventLuckMagicRingType.InnerRing, InnerRing} }; foreach (var info in infoList) { RingMap[info.RingType].AddBlock(new EventLuckMagicBlock(info, RingMap[info.RingType])); } RingMap[EEventLuckMagicRingType.OuterRing].ImportBitWiseBlockState(pfData.OuterState); RingMap[EEventLuckMagicRingType.MiddleRing].ImportBitWiseBlockState(pfData.MiddleState); RingMap[EEventLuckMagicRingType.InnerRing].ImportBitWiseBlockState(pfData.InnerState); HigherRingData = EventLuckMagicHigherRingData.Create(RingMap[pfData.HigherRingType], pfData.HigherRingExpireTime.Value); LinkRings(); _ticketCount = pfData.TicketCount; _chainProgress = pfData.ChainProgress; _roundCount = pfData.RoundCount; TaskProgress = GetTaskProgress(); CurrentRing = _rings.FirstOrDefault(r => !r.IsDepleted) ?? _rings.Last(); ppData ??= EventLuckMagicPlayerPreferenceData.CreateEmpty(); SelectionState = ppData.SelectionPosition; DoesSkipAnimation = ppData.DoesSkipAnimation; (_startTime, _expiryTime) = EventLuckMagicAct.TableContext.GetEventStartTimeAndEndTime(); _dicTasks = pfData.DicTasks; _taskData = new LuckyTaskData(this); } public void AddTicket(int num) { if (_ticketCount + num < 0) { Debug.Log($"[EventLuckMagic] Try to add {num} ticket(s) failed. Having {_ticketCount} ticket(s) now."); return; } _ticketCount += num; GContext.container.Resolve().SaveTransitionData(_eventId, _ticketCount); ToPfData().Save(); EventLuckMagicAct.EventAggregator.Publish(new EventLuckMagicTicketChange()); } public void LinkRings() { for (int i = 0; i < _rings.Count - 1; i++) _rings[i].HigherRing = _rings[i + 1]; CurrentRing = _rings.FirstOrDefault(r => !r.IsDepleted); if (CurrentRing == null) { Debug.Log($"[EventLuckMagic] Cannot find any available ring. Seems that all rings are depleted."); return; } var r = CurrentRing; // The Higher ring of r is the next ring that is not depleted. while (r != null && r.HigherRing != null) { while (r.HigherRing != null && r.HigherRing.IsDepleted) { r.HigherRing = r.HigherRing.HigherRing; } r = r.HigherRing; } } public void UpdateTaskProgress(int targetId) { TaskProgress[targetId]++; // Debug.Log($"[EventLuckMagic] Task progress of {targetId} is {TaskProgress[targetId]}."); ToPfData().Save(); } public bool IsTaskCompleted(int targetId) { return TaskProgress[targetId] >= TaskProgressCount; } public void Reset() { for (int i = 0; i < _rings.Count; i++) _rings[i].Reset(); HigherRingData.SetEmpty(); LinkRings(); HigherRingData.PointTo(CurrentRing.HigherRing); HigherRingData.ExpireTime = System.DateTime.MinValue; TaskProgress = EventLuckMagicAct.TableContext.GetTaskInfo(GContext.container.Resolve().RoundCount) .Keys.ToDictionary(x => x, x => 0); CurrentRing = _rings.FirstOrDefault(r => !r.IsDepleted) ?? _rings.Last(); _taskData.InitDicTasks(EventLuckMagicAct.TableContext.GetLastLuckyTaskIdList()); ToPfData().Save(); } public void SetChainProgress(int p) { Debug.Log($"[EventLuckMagic]SetProgress {p}"); _chainProgress = p; } public int GetChainProgress() { return _chainProgress; } public void Save() { ToPfData().Save(); } public EventLuckMagicPlayerPreferenceData ToPpData() { return new EventLuckMagicPlayerPreferenceData { SelectionPosition = SelectionState, DoesSkipAnimation = DoesSkipAnimation }; } public EventLuckMagicPlayfabData ToPfData() { var expireTime = HigherRingData.DoesExist ? HigherRingData.ExpireTime : DateTime.MinValue; // var expireTime = HigherRingData.DoesExist ? HigherRingData.ExpireTime : ZZTimeHelper.UtcNow().AddSeconds(-1); var ringType = HigherRingData.DoesExist ? HigherRingData.Ring.RingType : EEventLuckMagicRingType.OuterRing; return new EventLuckMagicPlayfabData { EventId = _eventId, TicketCount = TicketCount, ChainProgress = _chainProgress, InnerState = InnerRing.GetBitWiseBlockState(), MiddleState = MiddleRing.GetBitWiseBlockState(), OuterState = OuterRing.GetBitWiseBlockState(), HigherRingExpireTime = expireTime, HigherRingType = ringType, RoundCount = _roundCount, DicTasks = _dicTasks }; } private Dictionary GetTaskProgress() { var taskInfo = EventLuckMagicAct.TableContext.GetTaskInfo(RoundCount); var res = taskInfo.ToDictionary( kv => kv.Key, kv => _rings.Sum(ring => ring.Blocks.Count(x => x.Reward.id == kv.Key && x.IsTaken)) ); return res; } public EventLuckMagicEntranceData ToEntranceData() { return new EventLuckMagicEntranceData { TicketCount = _ticketCount, ExpiryTime = _expiryTime, Icon = EventLuckMagicAct.TableContext.GetIcon(), StartTime = _startTime, }; } public GenericChainPackData ToChainPackData() { return GenericChainPackData.Create( this, EventLuckMagicAct.TableContext.GetChainPackInfo()); } public void AddRoundCount() { _roundCount++; Save(); } public void SaveTaskData() { ToPfData().Save(); } #region Lucky Task private Dictionary _dicTasks; private LuckyTaskData _taskData; public string LuckyTaskRedPointKey => "eventluckmagic.entrance.luckytask"; public Dictionary DicTasks { get => _dicTasks; set => _dicTasks = value; } public string TaskPanelName => "EventLuckyMagicTaskPopupPanel"; public bool OnGetTaskReward(int id) { return _taskData.OnGetTaskReward(id, EventLuckMagicAct.TableContext.GetLastLuckyTaskIdList()); } public int GetLuckyPackID() { return EventLuckMagicAct.TableContext.GetPackId(); } public void DisposeLuckyTaskSubscription() { _taskData.Dispose(); } #endregion } public class EventLuckMagicRing { public readonly int Cost, RoadSignId; public List Blocks; public bool IsDepleted => Blocks.All(x => x.IsTaken || EventLuckMagicAct.TableContext.IsRewardRoadSign(x.Reward.id, out _, out _, out _)); public readonly EEventLuckMagicRingType RingType; public EventLuckMagicRing HigherRing { get; set; } public int[] CornerIndices = new int[4]; private int BlockCount => Blocks.Count; public int AvailableBlockCount => Blocks.Count(x => !x.IsTaken); private EventLuckMagicRing(int cost, int roadSignId, EEventLuckMagicRingType ringType) { Blocks = new List(); Cost = cost; RoadSignId = roadSignId; RingType = ringType; HigherRing = null; } public static EventLuckMagicRing CreateEmpty(int cost, int roadSignId, EEventLuckMagicRingType ringType) { return new EventLuckMagicRing(cost, roadSignId, ringType); } public void AddBlock(EventLuckMagicBlock block) { Blocks.Add(block); } public void Reset() { Blocks.ForEach(x => x.Reset()); } public void GetLoopAnimationInfo(int targetIdx, int extraLoopCount, out int startIdx, out int totalMovementCount) { int idx = 0, gap = BlockCount / 4; startIdx = 0; while (idx < BlockCount) { idx += gap; if (GetShortestDistance(idx, targetIdx) > GetShortestDistance(startIdx, targetIdx)) startIdx = idx; } totalMovementCount = GetClockWiseDistance(startIdx, targetIdx) + extraLoopCount * BlockCount; } private int GetClockWiseDistance(int startIdx, int endIdx) { Assert.IsTrue(startIdx >= 0 && startIdx < BlockCount && endIdx >= 0 && endIdx < BlockCount); return (endIdx - startIdx + BlockCount) % BlockCount; } private int GetShortestDistance(int startIdx, int endIdx) { int d = Mathf.Abs(startIdx - endIdx); d = d > BlockCount / 2 ? BlockCount - d : d; return d; } public int GetBitWiseBlockState() { var res = 0; for (int i = 0; i < BlockCount; i++) { if (Blocks[i].IsTaken) res |= 1 << i; } return res; } public void ImportBitWiseBlockState(int state) { for (int i = 0; i < BlockCount; i++) { if ((state & (1 << i)) != 0) Blocks[i].Take(); else Blocks[i].Reset(); } } } public class EventLuckMagicBlock { public int TableId; public readonly ItemData Reward; private bool _isTaken; public bool IsTaken { get { if (EventLuckMagicAct.TableContext.IsRewardRoadSign(Reward.id, out _, out _, out _)) return ParentRing == null || ParentRing.HigherRing == null || ParentRing.HigherRing.IsDepleted; return _isTaken; } } private int _weight; public int Weight { get { if (EventLuckMagicAct.TableContext.IsRewardRoadSign(Reward.id, out _, out _, out var weightConfig)) { if (weightConfig.Length != 2) return _weight; return ParentRing.AvailableBlockCount <= weightConfig[0] ? weightConfig[1] : _weight; } return _weight; } set { _weight = value; } } public EEventLuckMagicBlockType BlockType; /// /// The ring this block belongs to. /// public EventLuckMagicRing ParentRing { get; } public EventLuckMagicBlock(EventLuckMagicBlockInfo info, EventLuckMagicRing parentRing) { Reward = info.Reward; Weight = info.Weight; BlockType = GetBlockType(info.RingType, info.Reward.id); ParentRing = parentRing; TableId = info.TableId; // _isTaken = isTaken; } public void Take() { _isTaken = true; } private static EEventLuckMagicBlockType GetBlockType(EEventLuckMagicRingType ringType, int rewardId) { if (EventLuckMagicAct.TableContext.IsRewardRoadSign(ringType, rewardId)) return EEventLuckMagicBlockType.RoadSign; else return (EEventLuckMagicBlockType)(ringType - 1); } public void Reset() { _isTaken = false; } } public class EventLuckMagicHigherRingData { public EventLuckMagicRing Ring { get; set; } public System.DateTime ExpireTime; public System.TimeSpan RemainingTime => ExpireTime - ZZTimeHelper.UtcNow(); public bool DoesExist => Ring != null && !Ring.IsDepleted && RemainingTime.TotalSeconds > 0; private EventLuckMagicHigherRingData() { } public void PointTo(EventLuckMagicRing ring) { Ring = ring; } public void Activate(int expireDuration) { ExpireTime = ZZTimeHelper.UtcNow().AddSeconds(expireDuration); } public void SetEmpty() { Ring = null; ExpireTime = ZZTimeHelper.UtcNow().AddSeconds(-5); } public static EventLuckMagicHigherRingData CreateEmpty() { var res = new EventLuckMagicHigherRingData(); res.SetEmpty(); return res; } public static EventLuckMagicHigherRingData Create(EventLuckMagicRing ring, System.DateTime? expireTime) { var res = new EventLuckMagicHigherRingData(); res.PointTo(ring); if (expireTime != null) res.ExpireTime = expireTime.Value; else res.ExpireTime = ZZTimeHelper.UtcNow().AddSeconds(-4); return res; } } public class EventLuckMagicPanelData { public EventLuckMagicTableContext TableContext; public EventLuckMagicModel Model; public EventLuckMagicSystem System; public EventLuckMagicParamsCtrl ParamsCtrl; public IEventAggregator EventAggregator; public Vector2? SelectionPosition; } public class EventLuckMagicSpinParams { public float MaxInterval; public float MinInterval; public int ExtraLoopCount; public AnimationCurve IntervalLerpCurve; } public class EventLuckMagicSpinCtx { public EventLuckMagicRing Ring; public int Index; public ItemData Reward; public bool doesHideButton; public int RoundCount; } public class EventRewardFlyStashRequest { public ItemData Reward; public bool DoesPlayOpen; // public Vector2 Position; // public float SourceIconSize; public RectTransform SourceIconRt; public RectTransform SourceTextRt; public EventRewardFlyStashRequest(ItemData reward, RectTransform sourceIconRt, bool doesPlayOpen = true, RectTransform sourceTextRt = null) { Reward = reward; DoesPlayOpen = doesPlayOpen; SourceIconRt = sourceIconRt; SourceTextRt = sourceTextRt; } } public class EventLuckMagicPlayfabData { public int EventId; public int TicketCount; public int ChainProgress; public int InnerState, MiddleState, OuterState; public int RoundCount; public System.DateTime? HigherRingExpireTime; public EEventLuckMagicRingType HigherRingType; public Dictionary DicTasks; public const string Key = "EventLuckMagicPlayfabData"; private const string Splitter = "|", Comma = ","; private const int Count = 11; public string Serialize() { var sb = new StringBuilder(); sb.Append(EventId).Append(Splitter); sb.Append(TicketCount).Append(Splitter); sb.Append(ChainProgress).Append(Splitter); sb.Append(InnerState).Append(Splitter); sb.Append(MiddleState).Append(Splitter); sb.Append(OuterState).Append(Splitter); sb.Append(HigherRingExpireTime).Append(Splitter); sb.Append((int)HigherRingType).Append(Splitter); sb.Append(RoundCount).Append(Splitter); if (DicTasks != null) { foreach (var kv in DicTasks) sb.Append(kv.Key).Append(Comma); sb.Append(Splitter); foreach (var kv in DicTasks) sb.Append(kv.Value).Append(Comma); } return sb.ToString(); } public static EventLuckMagicPlayfabData Deserialize(string s) { try { if (s == null || s == "") return null; var tokens = s.Trim(Splitter.ToCharArray()).Split(Splitter); if (tokens.Length != Count) { var e = new Exception($"[EventLuckMagic]Wrong amount of parameters. Expect {Count}, but got {tokens.Length} in \"{s}\"."); Debug.LogError(e); return null; } // Debug.Log($"[EventLuckMagic] time string:{tokens[6]}"); var keyList = tokens[9].Trim(Comma[0]).Split(Comma).Select(int.Parse).ToList(); var valueList = tokens[10].Trim(Comma[0]).Split(Comma).Select(int.Parse).ToList(); if (keyList == null || valueList == null || keyList.Count != valueList.Count) { var e = new Exception($"[EventLuckMagic]Dictionary Error."); Debug.LogError(e); return null; } var dicTasks = new Dictionary(); for (int i = 0; i < keyList.Count; i++) dicTasks.Add(keyList[i], valueList[i]); return new EventLuckMagicPlayfabData { EventId = int.Parse(tokens[0]), TicketCount = int.Parse(tokens[1]), ChainProgress = int.Parse(tokens[2]), InnerState = int.Parse(tokens[3]), MiddleState = int.Parse(tokens[4]), OuterState = int.Parse(tokens[5]), HigherRingExpireTime = ParseTime(tokens[6]), HigherRingType = (EEventLuckMagicRingType)int.Parse(tokens[7]), RoundCount = int.Parse(tokens[8]), DicTasks = dicTasks }; } catch (Exception e) { Debug.Log($"[EventLuckMagic] Deserialize error: "); Debug.LogError(e); return null; } } private static System.DateTime ParseTime(string timeString) { if (timeString == "") return System.DateTime.MinValue; else try { return System.DateTime.Parse(timeString); } catch { Debug.LogError($"[EventLuckMagic] Failed to parse time string \"{timeString}\""); return System.DateTime.MinValue; } } public void Save() { var s = Serialize(); // Debug.Log($"[EventLuckMagic] Save playfab data : \n" + s); PlayFabMgr.Instance.UpdateUserDataValue(Key, s); } } public class EventLuckMagicPlayerPreferenceData { public Vector2? SelectionPosition; public bool DoesSkipAnimation; public static string Key => "EventLuckMagic" + GContext.container.Resolve().UserId; private const char Splitter = '|', Comma = ','; public static EventLuckMagicPlayerPreferenceData CreateEmpty() { var res = new EventLuckMagicPlayerPreferenceData(); res.SelectionPosition = null; res.DoesSkipAnimation = false; return res; } public string Serialize() { var sb = new StringBuilder(); sb.Append(DoesSkipAnimation).Append(Splitter); if (SelectionPosition != null) sb.Append(SelectionPosition.Value.x).Append(Comma).Append(SelectionPosition.Value.y).Append(Splitter); return sb.ToString(); } public static EventLuckMagicPlayerPreferenceData Deserialize(string s) { var res = new EventLuckMagicPlayerPreferenceData(); if (s == null || s == "") return res; try { var tokens = s.Trim(Splitter).Split(Splitter); res.DoesSkipAnimation = bool.Parse(tokens[0]); if (tokens.Length > 1) { var posTokens = tokens[1].Split(Comma); res.SelectionPosition = new Vector2(float.Parse(posTokens[0]), float.Parse(posTokens[1])); } else res.SelectionPosition = null; } catch (System.Exception e) { Debug.LogError($"[EventLuckMagic] Failed to deserialize:"); Debug.LogError(e); } return res; } public void Save() { var s = Serialize(); PlayerPrefs.SetString(Key, s); } } public class EventLuckMagicEntranceData { public int TicketCount; public DateTime ExpiryTime; public DateTime StartTime; public TimeSpan RemainingTime => ExpiryTime - ZZTimeHelper.UtcNow(); public bool IsActive => ZZTimeHelper.UtcNow() >= StartTime && ZZTimeHelper.UtcNow() < ExpiryTime; public string Icon; }