582 lines
19 KiB
C#
582 lines
19 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using GameCore;
|
|
using System;
|
|
using UnityEngine;
|
|
using asap.core;
|
|
using game;
|
|
using cfg;
|
|
|
|
public class EventBingoModel
|
|
{
|
|
public readonly static int BoardSize = 5;
|
|
public readonly static int NumberCount = BoardSize * BoardSize;
|
|
public readonly static string SfxItemIn = "audio_ui_eventbingobaseball_in", SfxItemOut = "audio_ui_eventbingobaseball_out";
|
|
public const int RecordLength = 4;
|
|
public const char Splitter = '|', Comma = ',';
|
|
private int _stepInRound, _ticketCount, _roundCount, _eventId, _chainProgress, _chainTaskTokenProgress;
|
|
private EventBingoRoundData _roundData;
|
|
private EventBingoProgressTaskData _taskData;
|
|
private EventBingoTableContext _ctx;
|
|
private bool _isFirstTime;
|
|
// This is actually used as a dequeue.
|
|
public LinkedList<EventBingoNumberRollerItemData> NumberRollerDataList;
|
|
public int[] BoardNumbers => _roundData.BoardNumbers;
|
|
public EventBingoStepState StepState => _roundData[_stepInRound];
|
|
public int BoardState => StepState.BoardState;
|
|
public int TicketCount => _ticketCount;
|
|
public int RoundCount => _roundCount;
|
|
public EventBingoProgressTaskData TaskData => _taskData;
|
|
public int EventId => _eventId;
|
|
public int StepInRound => _stepInRound;
|
|
public bool IsActivated
|
|
{
|
|
get
|
|
{
|
|
if (_ctx == null)
|
|
return false;
|
|
var timeLimit = _ctx.GetTimeLimit();
|
|
return timeLimit.Item1 <= ZZTimeHelper.UtcNow() && ZZTimeHelper.UtcNow() < timeLimit.Item2;
|
|
}
|
|
}
|
|
public bool IsFirstTime => _isFirstTime;
|
|
public EventBingoTableContext Ctx => _ctx;
|
|
public int PackId => _ctx.GetPackId();
|
|
|
|
public void Init(FishingEvent e)
|
|
{
|
|
_eventId = e.ID;
|
|
RefreshTableContext();
|
|
_roundCount = 0;
|
|
var seed = _ctx.PickSeed(_roundCount);
|
|
_roundData = new EventBingoRoundData(seed);
|
|
_stepInRound = 0;
|
|
NumberRollerDataList = new LinkedList<EventBingoNumberRollerItemData>();
|
|
_taskData = _ctx.GetTaskData();
|
|
_ticketCount = 0;
|
|
AddTicket(_ctx.GetWelcomeGift());
|
|
SetUiPanel(_ctx.GetUiData());
|
|
_isFirstTime = true;
|
|
ToPlayfabData().Save();
|
|
}
|
|
|
|
public void Load(EventBingoPlayfabData pfData, EventBingoPlayerPreferenceData ppData, FishingEvent e)
|
|
{
|
|
_eventId = pfData.EventId;
|
|
RefreshTableContext();
|
|
_ticketCount = pfData.TicketCount;
|
|
_roundCount = pfData.RoundCount;
|
|
_stepInRound = pfData.StepInRound;
|
|
_roundData = new EventBingoRoundData(pfData.Seed);
|
|
if (!_roundData.ValidateStepIndex(_stepInRound))
|
|
{
|
|
Debug.LogWarning($"[EventBingo] Wrong data detected. Step count out of range. Reset round data.");
|
|
_stepInRound = 0;
|
|
var seed = _ctx.PickSeed(_roundCount);
|
|
_roundData = new EventBingoRoundData(seed);
|
|
}
|
|
_taskData = _ctx.GetTaskData(pfData.TaskId, pfData.TaskProgress);
|
|
if (ppData == null || ppData.RollerDataList == null)
|
|
NumberRollerDataList = new LinkedList<EventBingoNumberRollerItemData>();
|
|
else
|
|
NumberRollerDataList = new LinkedList<EventBingoNumberRollerItemData>(ppData.RollerDataList);
|
|
SetUiPanel(_ctx.GetUiData());
|
|
_isFirstTime = ppData == null ? true : ppData.IsFirstTime;
|
|
}
|
|
|
|
public void InitTest()
|
|
{
|
|
Debug.Log("[EventBingo] Abandon.");
|
|
}
|
|
|
|
public void NextStep()
|
|
{
|
|
_stepInRound++;
|
|
var taskUpdate = _taskData.AddProgress(1);
|
|
ToPlayfabData().Save();
|
|
// Debug.Log($"[EventBingo] Step{_stepInRound}: {StepState.Number}.");
|
|
}
|
|
|
|
public void NextRound()
|
|
{
|
|
_roundCount++;
|
|
var seed = _ctx.PickSeed(_roundCount);
|
|
_roundData = new EventBingoRoundData(seed);
|
|
_stepInRound = 0;
|
|
NumberRollerDataList.Clear();
|
|
ToPlayfabData().Save();
|
|
ToPlayerPreferenceData().Save();
|
|
}
|
|
|
|
public bool CheckIfTaken(int index)
|
|
{
|
|
var flag = 1 << index;
|
|
return (BoardState & flag) != 0;
|
|
}
|
|
|
|
public List<EventBingoBlockViewData> GetBoardInitiationData()
|
|
{
|
|
var res = new List<EventBingoBlockViewData>();
|
|
for (int i = 0; i < NumberCount; i++)
|
|
{
|
|
_roundData.MinorRewards.TryGetValue(i, out var minorReward);
|
|
res.Add(new EventBingoBlockViewData
|
|
{
|
|
number = _roundData.BoardNumbers[i],
|
|
reward = minorReward,
|
|
haveItem = CheckIfTaken(i)
|
|
});
|
|
}
|
|
return res;
|
|
}
|
|
|
|
public void AddNumberRecord(int number)
|
|
{
|
|
_ctx.GetRandomItemIcon(out int iconIdx, out string iconUrl);
|
|
NumberRollerDataList.AddLast(new EventBingoNumberRollerItemData
|
|
{
|
|
number = number,
|
|
iconIdx = iconIdx,
|
|
});
|
|
while (NumberRollerDataList.Count > RecordLength)
|
|
NumberRollerDataList.RemoveFirst();
|
|
ToPlayerPreferenceData().Save();
|
|
}
|
|
|
|
public bool AddTicket(int count)
|
|
{
|
|
_ticketCount += count;
|
|
if (_ticketCount < 0)
|
|
{
|
|
_ticketCount -= count;
|
|
return false;
|
|
}
|
|
EventBingoAct.EventAggregator.Publish(new EventBingoTicketUpdateEvent());
|
|
ToPlayfabData().Save();
|
|
GContext.container.Resolve<FishingEventData>().SaveTransitionData(_eventId, _ticketCount);
|
|
return true;
|
|
}
|
|
|
|
public bool DeliverMinorReward(int index, out ItemData reward)
|
|
{
|
|
bool haveReward = _roundData.MinorRewards.TryGetValue(index, out reward);
|
|
if (haveReward)
|
|
{
|
|
EventBingoSystem.GrantReward(reward);
|
|
}
|
|
else
|
|
{
|
|
// Debug.Log($"[EventBingo] No reward for idx {index}");
|
|
}
|
|
return haveReward;
|
|
}
|
|
|
|
public void RefreshTableContext()
|
|
{
|
|
_ctx = new EventBingoTableContext(_eventId);
|
|
}
|
|
|
|
public EventBingoPlayfabData ToPlayfabData()
|
|
{
|
|
return new EventBingoPlayfabData(
|
|
_eventId, _ticketCount, _roundCount, _stepInRound, _roundData.Seed,
|
|
TaskData.TaskId, TaskData.Progress, _chainProgress,
|
|
_chainTaskTokenProgress);
|
|
}
|
|
|
|
public EventBingoEntranceData ToEntranceData()
|
|
{
|
|
(var startTime, var expiryTime) = _ctx.GetTimeLimit();
|
|
bool needRedPoint = _ticketCount >= _ctx.GetRedPointThreshold();
|
|
return new EventBingoEntranceData
|
|
{
|
|
NeedRedPoint = needRedPoint,
|
|
ExpiryTime = expiryTime,
|
|
StartTime = startTime,
|
|
Icon = _ctx.GetEntranceIcon(),
|
|
};
|
|
}
|
|
|
|
public EventBingoPlayerPreferenceData ToPlayerPreferenceData()
|
|
{
|
|
return new EventBingoPlayerPreferenceData
|
|
{
|
|
RollerDataList = NumberRollerDataList.ToList(),
|
|
IsFirstTime = _isFirstTime
|
|
};
|
|
|
|
}
|
|
|
|
#region ChainPack
|
|
public int GetChainProgress()
|
|
{
|
|
return _chainProgress;
|
|
}
|
|
|
|
public void SetChainProgress(int p)
|
|
{
|
|
_chainProgress = p;
|
|
}
|
|
|
|
public int GetTaskProgress()
|
|
{
|
|
return _chainTaskTokenProgress;
|
|
}
|
|
|
|
public void SetTaskProgress(int p)
|
|
{
|
|
_chainTaskTokenProgress = p;
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
ToPlayfabData().Save();
|
|
}
|
|
#endregion
|
|
|
|
private void SetUiPanel(EventBingoUiData panelData)
|
|
{
|
|
UITypes.EventBingoInfoPanel.SetType(panelData.InfoPanel);
|
|
UITypes.EventBingoChainPackPanel.SetType(panelData.ChainPackPanel);
|
|
}
|
|
|
|
public bool IsHit(int number)
|
|
{
|
|
foreach (var n in BoardNumbers)
|
|
{
|
|
if (n == number)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void SetFirstTimeFlag()
|
|
{
|
|
_isFirstTime = false;
|
|
ToPlayerPreferenceData().Save();
|
|
}
|
|
|
|
public void ActivateInstantBingo()
|
|
{
|
|
_stepInRound = _roundData.StepCount - 2;
|
|
}
|
|
}
|
|
|
|
public class EventBingoRoundData
|
|
{
|
|
private int _seed;
|
|
public int[] BoardNumbers;
|
|
private List<int> _playerNumberSequence;
|
|
private List<int> _stateSequence;
|
|
private List<int> _playerIdxSequence;
|
|
public Dictionary<int, ItemData> MinorRewards;
|
|
public int Seed => _seed;
|
|
public int StepCount => _stateSequence.Count;
|
|
|
|
public EventBingoStepState this[int idx]
|
|
{
|
|
get
|
|
{
|
|
if (idx >= _stateSequence.Count || idx >= _playerNumberSequence.Count)
|
|
{
|
|
var actualIdx = Mathf.Min(_stateSequence.Count - 2, _playerNumberSequence.Count - 2);
|
|
return new EventBingoStepState
|
|
{
|
|
Number = _playerNumberSequence[actualIdx],
|
|
BoardState = _stateSequence[actualIdx],
|
|
Index = _playerIdxSequence[actualIdx],
|
|
};
|
|
throw new IndexOutOfRangeException(
|
|
$"[EventBingo] Index {idx} out of range. Expected range: [0, {Math.Min(_stateSequence.Count, _playerNumberSequence.Count)}]");
|
|
}
|
|
return new EventBingoStepState
|
|
{
|
|
Number = _playerNumberSequence[idx],
|
|
BoardState = _stateSequence[idx],
|
|
Index = _playerIdxSequence[idx]
|
|
};
|
|
}
|
|
}
|
|
|
|
public EventBingoRoundData(int seed)
|
|
{
|
|
_playerNumberSequence = new List<int>();
|
|
_playerIdxSequence = new List<int>();
|
|
_stateSequence = new List<int>();
|
|
UpdateWithSeed(seed);
|
|
}
|
|
|
|
public void UpdateWithSeed(int seed)
|
|
{
|
|
var model = GContext.container.Resolve<EventBingoModel>();
|
|
_seed = seed;
|
|
var rng = new System.Random(_seed);
|
|
BoardNumbers = EventBingoSystem.GenerateBoardNumbers(rng).ToArray();
|
|
_playerNumberSequence.Clear();
|
|
_playerIdxSequence.Clear();
|
|
_stateSequence.Clear();
|
|
_playerNumberSequence.Add(-1);
|
|
_playerIdxSequence.Add(-1);
|
|
_stateSequence.Add(0b0);
|
|
var numberPool = Enumerable.Range(1, 99).ToList();
|
|
int state = 0b0;
|
|
var pickedIndexSet = new HashSet<int>();
|
|
while (numberPool.Count > 0 && EventBingoSystem.CheckBingo(state, out _) <= 0)
|
|
{
|
|
var pickedNumber = EventBingoSystem.PickRandomNumberFromList(numberPool, rng);
|
|
var pickedIdx = Array.IndexOf(BoardNumbers, pickedNumber);
|
|
if (pickedIdx > -1)
|
|
{
|
|
pickedIndexSet.Add(pickedIdx);
|
|
state |= 1 << pickedIdx;
|
|
}
|
|
_playerIdxSequence.Add(pickedIdx);
|
|
_playerNumberSequence.Add(pickedNumber);
|
|
_stateSequence.Add(state);
|
|
}
|
|
MinorRewards = new Dictionary<int, ItemData>();
|
|
model.Ctx.GetMinorDropData(rng, out var availableRewards, out var displayRewards);
|
|
|
|
var allIndices = Enumerable.Range(0, EventBingoModel.NumberCount).ToList();
|
|
FtMathUtils.ShuffleList(allIndices, rng);
|
|
var pickedIndices = pickedIndexSet.ToList();
|
|
FtMathUtils.ShuffleList(pickedIndices, rng);
|
|
var unpickedIndices = allIndices.Except(pickedIndexSet).ToList();
|
|
FtMathUtils.ShuffleList(unpickedIndices, rng);
|
|
// Assign rewards to picked positions
|
|
for (int i = 0; i < availableRewards.Count && i < pickedIndices.Count; i++)
|
|
{
|
|
MinorRewards[pickedIndices[i]] = availableRewards[i];
|
|
Debug.Log($"[EventBingo] Available MiniDrop: item {availableRewards[i].id} @ idx{pickedIndices[i]}");
|
|
}
|
|
// Assign display rewards to unpicked positions
|
|
for (int i = 0; i < displayRewards.Count && i < unpickedIndices.Count; i++)
|
|
{
|
|
MinorRewards[unpickedIndices[i]] = displayRewards[i];
|
|
Debug.Log($"[EventBingo] Unavailable MiniDrop: item {displayRewards[i].id} @ idx {unpickedIndices[i]}");
|
|
}
|
|
// var leftOverIndices = Enumerable.Range(0, EventBingoModel.NumberCount)
|
|
// .Where(x => !pickedIndexSet.Contains(x))
|
|
// .ToHashSet()
|
|
// .ToList()
|
|
// .OrderBy(x => rng.Next())
|
|
// .ToList();
|
|
// var pickedIndexList = pickedIndexSet.ToList().OrderBy(x => rng.Next()).ToList();
|
|
// for (int i = 0; i < availableRewards.Count && i < pickedIndexList.Count; i++)
|
|
// {
|
|
// var idx = pickedIndexList.First();
|
|
// MinorRewards[idx] = availableRewards[i];
|
|
// pickedIndexList.Remove(idx);
|
|
// }
|
|
// for (int i = 0; i < displayRewards.Count && i < leftOverIndices.Count; i++)
|
|
// {
|
|
// var idx = leftOverIndices.First();
|
|
// MinorRewards[idx] = displayRewards[i];
|
|
// leftOverIndices.Remove(idx);
|
|
// }
|
|
}
|
|
|
|
public int[] GetNumberRollerDataList(int step)
|
|
{
|
|
var startIdx = Mathf.Max(1, step - EventBingoModel.RecordLength + 1);
|
|
var count = Mathf.Min(EventBingoModel.RecordLength, step);
|
|
return _playerNumberSequence.GetRange(startIdx, count).ToArray();
|
|
}
|
|
|
|
public bool ValidateStepIndex(int idx)
|
|
{
|
|
return idx < _playerNumberSequence.Count && idx < _playerIdxSequence.Count && idx < _stateSequence.Count;
|
|
}
|
|
}
|
|
|
|
public class EventBingoStepState
|
|
{
|
|
public int BoardState;
|
|
public int Number;
|
|
public int Index;
|
|
}
|
|
|
|
public class EventBingoProgressTaskData
|
|
{
|
|
public int TaskId;
|
|
public int Progress;
|
|
public ItemData Reward;
|
|
public int TargetProgress;
|
|
public int NextTaskId;
|
|
|
|
/// <summary>
|
|
/// Increase progress and check if task is completed.
|
|
/// </summary>
|
|
/// <param name="count">Usually 1 in this event.</param>
|
|
/// <returns>True is the old task is completed.</returns>
|
|
public bool AddProgress(int count)
|
|
{
|
|
Progress += count;
|
|
if (Progress >= TargetProgress)
|
|
{
|
|
EventBingoSystem.GrantReward(Reward);
|
|
MoveToNextTask();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void MoveToNextTask()
|
|
{
|
|
var nextTask = GContext.container.Resolve<EventBingoModel>().Ctx.GetTaskData(NextTaskId);
|
|
TaskId = NextTaskId;
|
|
Progress = 0;
|
|
Reward = nextTask.Reward;
|
|
TargetProgress = nextTask.TargetProgress;
|
|
NextTaskId = nextTask.NextTaskId;
|
|
}
|
|
|
|
}
|
|
|
|
public class EventBingoPlayfabData
|
|
{
|
|
public int EventId, TicketCount, RoundCount, StepInRound, Seed, TaskId, TaskProgress, ChainProgress, ChainTaskTokenProgress;
|
|
private const int TokenCount = 9;
|
|
private const string Key = "EventBingo";
|
|
private string Serialize()
|
|
{
|
|
var sb = new System.Text.StringBuilder();
|
|
sb.Append(EventId).Append(EventBingoModel.Splitter);
|
|
sb.Append(TicketCount).Append(EventBingoModel.Splitter);
|
|
sb.Append(RoundCount).Append(EventBingoModel.Splitter);
|
|
sb.Append(StepInRound).Append(EventBingoModel.Splitter);
|
|
sb.Append(Seed).Append(EventBingoModel.Splitter);
|
|
sb.Append(TaskId).Append(EventBingoModel.Splitter);
|
|
sb.Append(TaskProgress).Append(EventBingoModel.Splitter);
|
|
sb.Append(ChainProgress).Append(EventBingoModel.Splitter);
|
|
sb.Append(ChainTaskTokenProgress).Append(EventBingoModel.Splitter);
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static EventBingoPlayfabData Deserialize(string s)
|
|
{
|
|
if (s == null || s == "")
|
|
return null;
|
|
var tokens = s.Trim(EventBingoModel.Splitter).Split(EventBingoModel.Splitter);
|
|
if (tokens.Length != TokenCount)
|
|
{
|
|
Debug.LogError($"[EventBingo]Wrong amount of parameters. Expect {TokenCount}, but got {tokens.Length} in \"{s}\".");
|
|
return null;
|
|
}
|
|
var eventId = int.Parse(tokens[0]);
|
|
var ticketCount = int.Parse(tokens[1]);
|
|
var roundCount = int.Parse(tokens[2]);
|
|
var stepInRound = int.Parse(tokens[3]);
|
|
var seed = int.Parse(tokens[4]);
|
|
var taskId = int.Parse(tokens[5]);
|
|
var taskProgress = int.Parse(tokens[6]);
|
|
var chainProgress = int.Parse(tokens[7]);
|
|
var chainTaskTokenProgress = int.Parse(tokens[8]);
|
|
return new EventBingoPlayfabData(eventId, ticketCount, roundCount,
|
|
stepInRound, seed, taskId, taskProgress, chainProgress, chainTaskTokenProgress);
|
|
}
|
|
|
|
public EventBingoPlayfabData(int eventId, int ticketCount, int roundCount, int stepInRound,
|
|
int seed, int taskId, int taskProgress, int chainProgress, int chainTaskTokenProgress)
|
|
{
|
|
EventId = eventId;
|
|
TicketCount = ticketCount;
|
|
RoundCount = roundCount;
|
|
StepInRound = stepInRound;
|
|
Seed = seed;
|
|
TaskId = taskId;
|
|
TaskProgress = taskProgress;
|
|
ChainProgress = chainProgress;
|
|
ChainTaskTokenProgress = chainTaskTokenProgress;
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
PlayFabMgr.Instance.UpdateUserDataValue(Key, Serialize());
|
|
}
|
|
|
|
public static EventBingoPlayfabData Load()
|
|
{
|
|
var s = PlayFabMgr.Instance.GetLocalData(Key);
|
|
return Deserialize(s);
|
|
}
|
|
}
|
|
|
|
public class EventBingoEntranceData
|
|
{
|
|
public bool NeedRedPoint;
|
|
public DateTime ExpiryTime;
|
|
public DateTime StartTime;
|
|
public string Icon;
|
|
}
|
|
|
|
public class EventBingoPlayerPreferenceData
|
|
{
|
|
public List<EventBingoNumberRollerItemData> RollerDataList;
|
|
public bool IsFirstTime = true;
|
|
private readonly static string Key = "EventBingo" + GContext.container.Resolve<IUserService>().UserId;
|
|
|
|
private string Serialize()
|
|
{
|
|
var sb = new System.Text.StringBuilder();
|
|
sb.Append(IsFirstTime ? "1" : "0").Append(EventBingoModel.Splitter);
|
|
foreach (var data in RollerDataList)
|
|
sb.Append(data.number).Append(EventBingoModel.Comma).Append(data.iconIdx).Append(EventBingoModel.Splitter);
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static EventBingoPlayerPreferenceData Deserialize(string s)
|
|
{
|
|
var tokens = s.Trim(EventBingoModel.Splitter).Split(EventBingoModel.Splitter);
|
|
var rollerData = new List<EventBingoNumberRollerItemData>();
|
|
bool isFirstTime;
|
|
if (tokens.Length < 1)
|
|
{
|
|
throw new Exception("[EventBingo] Invalid serialized data, length < 1");
|
|
}
|
|
isFirstTime = tokens[0] == "1";
|
|
for (int i = 1; i < tokens.Length; i++)
|
|
{
|
|
var token = tokens[i];
|
|
var tokenTokens = token.Split(EventBingoModel.Comma);
|
|
if (tokenTokens.Length != 2)
|
|
throw new Exception("[EventBingo] Wrong amount of parameters in " + token);
|
|
rollerData.Add(new EventBingoNumberRollerItemData
|
|
{
|
|
number = int.Parse(tokenTokens[0]),
|
|
iconIdx = int.Parse(tokenTokens[1])
|
|
});
|
|
}
|
|
return new EventBingoPlayerPreferenceData { RollerDataList = rollerData, IsFirstTime = isFirstTime };
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
PlayerPrefs.SetString(Key, Serialize());
|
|
}
|
|
|
|
public static EventBingoPlayerPreferenceData Load()
|
|
{
|
|
var s = PlayerPrefs.GetString(Key);
|
|
if (s == null || s == "")
|
|
return null;
|
|
try
|
|
{
|
|
var res = Deserialize(s);
|
|
return res;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class EventBingoTicketUpdateEvent { }
|
|
|
|
public class EventBingoUiData
|
|
{
|
|
public string InfoPanel { get; set; }
|
|
public string ChainPackPanel { get; set; }
|
|
} |