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

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; }
}