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

711 lines
23 KiB
C#

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;
/// <summary>
/// Position of selection GameObject, or null if hidden.
/// </summary>
public Vector2? SelectionState;
private readonly List<EventLuckMagicRing> _rings = new List<EventLuckMagicRing>();
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<EEventLuckMagicRingType, EventLuckMagicRing> RingMap;
public int TicketCount => _ticketCount;
public Dictionary<int, int> 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, EventLuckMagicRing>()
{
{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<int, int>();
_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, EventLuckMagicRing>()
{
{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<FishingEventData>().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<EventLuckMagicModel>().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<int, int> 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<EventLuckMagicModel> ToChainPackData()
{
return GenericChainPackData<EventLuckMagicModel>.Create(
this, EventLuckMagicAct.TableContext.GetChainPackInfo());
}
public void AddRoundCount()
{
_roundCount++;
Save();
}
public void SaveTaskData()
{
ToPfData().Save();
}
#region Lucky Task
private Dictionary<int, int> _dicTasks;
private LuckyTaskData _taskData;
public string LuckyTaskRedPointKey => "eventluckmagic.entrance.luckytask";
public Dictionary<int, int> 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<EventLuckMagicBlock> 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<EventLuckMagicBlock>();
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;
/// <summary>
/// The ring this block belongs to.
/// </summary>
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<int, int> 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<int, int>();
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<IUserService>().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;
}