using System.Collections.Generic; using GameCore; using System.Linq; using asap.core; using UnityEngine; using cfg; using System.Text; using game; namespace OfferTreasureAscent { public class Manager { private int _status = 0, _eventId, _redirectId; private List _failedPurchaseRewardIndices; private PlayerItemData _playerItemData => GContext.container.Resolve(); private cfg.Tables _tables => GContext.container.Resolve(); public int EventId => _eventId; public int RoundCount { get { return FtUtils.PopCount(_status); } } public int Status => _status; #region RedPoint public bool DoNeedRedPoint() { return GetIapId() <= 0; } public readonly string RedPointKey = "offer_treasure_ascent"; #endregion #region DataStorage public const string PfKey = "OfferTreasureAscent"; public static void InitEvent(FishingEvent e) { var manager = GContext.container.Resolve(); if (manager == null) { GContext.container.Register().AsSingleton(); manager = GContext.container.Resolve(); } var loadRes = manager.Load(); if (!loadRes || e.ID != manager.EventId) { manager.Init(e.ID); manager.Save(); } UITypes.OfferTreasureAscentMainPanel.SetType(manager.GetUiData()[0]); UITypes.OfferTreasureAscentBoxPanel.SetType(manager.GetUiData()[2]); } public void InitTest() { _eventId = 90400139; _redirectId = 1001; _status = 0; _failedPurchaseRewardIndices = new List(); } public void Reset() { _status = 0; _failedPurchaseRewardIndices = new List(); Save(); } public void Save() { var sb = new StringBuilder(); sb.AppendFormat("{0},{1},", _eventId, _status); if (_failedPurchaseRewardIndices != null && _failedPurchaseRewardIndices.Count > 0) foreach (var i in _failedPurchaseRewardIndices) sb.AppendFormat("{0},", i); PlayFabMgr.Instance.UpdateUserDataValue(PfKey, sb.ToString()); } public bool Load() { try { var s = PlayFabMgr.Instance.GetLocalData(PfKey); if (s == null || s == "") return false; var tokens = s.Trim(',').Split(','); if (tokens.Length < 2) return false; _eventId = int.Parse(tokens[0]); _status = int.Parse(tokens[1]); _failedPurchaseRewardIndices = new List(); for (int i = 2; i < tokens.Length; i++) _failedPurchaseRewardIndices.Add(int.Parse(tokens[i])); _redirectId = _tables.TbFishingEvent[_eventId].RedirectID; return true; } catch (System.Exception e) { Debug.LogWarning(e); return false; } } public void Init(int eventId) { _eventId = eventId; _redirectId = _tables.TbFishingEvent[_eventId].RedirectID; _status = 0; _failedPurchaseRewardIndices = new List(); Save(); } #endregion #region TableContext private EventOfferTreasureAscent TbMain { get { return _tables.TbEventOfferTreasureAscent[_redirectId]; } } public List GetRandomizedRewardList() { // warp the ordered list to randomize the order, with player playfab id as seed so that player got consistent result. var id = GContext.container.Resolve().UserId; // treat playfab id as an ordinary string instead of a hexadicimal number, and roll it into a hash. Consistant for each player. int seed = id.Aggregate(17, (hash,c)=> hash * 31 + c); var rng = new System.Random(seed); // var rng = new System.Random((int)System.Convert.ToUInt64(id, 16)); var res = _tables.TbEventOfferTreasureAscentRewards.DataList.ToList(); FtUtils.ShuffleList(res, rng); return res; } /// /// Get item data from reward index. Will return null if no table data found. Note that the ID of the item, which is not used here, is not the same as the index. /// /// Index of rewards. /// The ItemData, after transition and stuff. Will return null if not found. public ItemData GetItemFromIndex(int idx) { // var tbRewards = _tables.TbEventOfferTreasureAscentRewards.DataList; var tbRewards = GetRandomizedRewardList(); idx = Mathf.Clamp(idx, 0, tbRewards.Count - 1); var reward = tbRewards[idx]; var res = new ItemData(reward.Item, reward.Number); _playerItemData.ItemTransition(res); _playerItemData.LureInflation(res, null); return res; } /// /// Pick random reward index. /// /// Current round count that limits the option. /// Reward idx. -1 if not found. public int PickRandomAvailableRewardIdx(int currentRound) { // var tbRewards = _tables.TbEventOfferTreasureAscentRewards.DataList; var tbRewards = GetRandomizedRewardList(); var availableIdxList = tbRewards .Select((r, i) => (r, i)) .Where(ri => ri.r.LimitConfig - 1 <= currentRound && !IsClaimed(ri.i)) .Select(ri => ri.i) .ToList(); if (availableIdxList.Count <= 0) { Debug.LogError($"[OfferTreasureAscent] No available reward found in round {currentRound}. This is not supposed to happen."); return -1; } var weightList = availableIdxList.Select(i => tbRewards[i].Weight).ToList(); return FtUtils.PickRandomItemWithWeight(availableIdxList, i => weightList[i]); } public int PickRandomAvailableRewardIdx() { return PickRandomAvailableRewardIdx(RoundCount); } public int GetRewardCount() { return _tables.TbEventOfferTreasureAscentRewards.DataList.Count; } public async System.Threading.Tasks.Task<(int normalRewardIdx, List finalRewards, int iap_id)> GrantRandomReward() { var iapId = GetIapId(); var pickedRewardIdx = PickRandomAvailableRewardIdx(); List finalRewards = null; if (iapId > 0) { GContext.container.Resolve().ResolveIapId(iapId, out var iapItemList, out _); var buyTypeData = new ShopBuyTypeData { type = ShopBuyType.EventPack, ID = EventId, NoShow = true }; var rewardItem = GetItemFromIndex(pickedRewardIdx); if (pickedRewardIdx < 0 || rewardItem == null) { Debug.LogError("[OfferTreasureAscent] No reward to claim."); return (-1, null, iapId); } // Drop id required here is fill with 0, because it is a legacy feature and is no longer used. var res = await GContext.container.Resolve() .OnBuy(0, buyTypeData, iapItemList, new List() { rewardItem }); if (res) { Claim(pickedRewardIdx); // Debug.Log($"[OfferTreasureAscent] Grant paid {pickedRewardIdx}, ID {pickedRewardIdx + 1}"); finalRewards = TryGrantFinalReward(); } else { RecordFailedRewardIdx(pickedRewardIdx); return (-1, null, iapId); } } else { if (pickedRewardIdx >= 0) { Claim(pickedRewardIdx); TryGrantReward(pickedRewardIdx); finalRewards = TryGrantFinalReward(); } } return (pickedRewardIdx, finalRewards, iapId); } public List GetRewards() { var res = new List(); for (int i = 0; i < GetRewardCount(); i++) res.Add(GetItemFromIndex(i)); return res; } /// /// Get IAP ID list. Fill in 0s for free chances. /// /// IAP ID public List GetIapIdList() { var res = new List(); for (int i = 0; i < TbMain.FreeChance; i++) { res.Add(0); } res.AddRange(TbMain.IapList); return res; } /// /// Get IAP ID. Get 0 for free chance. /// /// Round count. Starts with 0. /// IAP ID, get 0 for free chance. public int GetIapId(int currentRound) { var iapIdList = GetIapIdList(); currentRound = Mathf.Clamp(currentRound, 0, iapIdList.Count - 1); return iapIdList[currentRound]; } /// /// Get IAP ID. Get 0 for free chance. For current round. /// /// IAP ID of this round. 0 for free chance. public int GetIapId() { return GetIapId(RoundCount); } #endregion #region UI data /// /// Get UI Data. /// /// First is main panel, second is the grand reward showing animation panel. public string[] GetUiData() { return new string[] { TbMain.UIPanel, TbMain.Icon, TbMain.BoxOpenPrefab}; } /// /// Get Entrance button data. /// /// public OfferTreasureAscentEntranceData GetEntranceData() { var res = new OfferTreasureAscentEntranceData(); res.IconUrl = TbMain.Icon; (var startTime, var endTime) = FtUtils.GetEventTimeLimit(EventId); res.TimerData = new TimerContext { StartTime = startTime, ExpiryTime = endTime }; return res; } public GeneralProbabilityData[] GetProbabilityData() { var res = new GeneralProbabilityData[GetRewardCount()]; var tbRewards = GetRandomizedRewardList(); var totalWeight = tbRewards.Where((r, i) => !IsClaimed(i)).Sum(r => r.Weight); for (int i = 0; i < GetRewardCount(); i++) { res[i] = new GeneralProbabilityData { Item = GetItemFromIndex(i), Probability = tbRewards[i].Weight / (float)totalWeight, IsClaimed = IsClaimed(i) }; } return res; } #endregion #region System public void Claim(int idx) { _status |= 1 << idx; Save(); } public bool IsClaimed(int idx) { return (_status & (1 << idx)) != 0; } public ItemData GetGrandReward() { return new ItemData(TbMain.ChestReward, 1); } public int GetGrandRewardItemId() { return TbMain.ChestReward; } /// /// Check if all normal rewards are claimed /// /// True is all is claimed. public bool IsNormalRewardsDepleted { get { int rewardCount = GetRewardCount(); if (rewardCount <= 0) return true; if (rewardCount > 31) { UnityEngine.Debug.LogError("[OfferTreasureAscent] Reward count is too large for int: " + rewardCount); return true; } int expectedMask = (1 << rewardCount) - 1; return (_status & expectedMask) == expectedMask; } } public bool IsFinalRewardClaimed { get { return IsNormalRewardsDepleted && IsClaimed(GetRewardCount()); } } /// /// Grant reward according to reward index, and try grant final reward if all rewards are claimed. /// /// The index of the target reward. public void TryGrantReward(int rewardIdx) { Debug.Log($"[OfferTreasureAscent] Grant free {rewardIdx}, ID {rewardIdx + 1}"); var rewardItem = GetItemFromIndex(rewardIdx); if (rewardItem == null) { Debug.LogError("[OfferTreasureAscent] Reward item is null. This is not supposed to happen."); return; } _playerItemData.AddItem(rewardItem, null); GContext.Publish(new ShowData(rewardItem)); } public List TryGrantFinalReward() { // Debug.Log("[OfferTreasureAscent]Try Grant Final..."); if (!IsNormalRewardsDepleted || IsFinalRewardClaimed) return null; // var grandReward = GetGrandReward(); // _playerItemData.AddItem(grandReward, null); var grandRewardId = GetGrandRewardItemId(); var items = FtUtils.ResolveChestContent(grandRewardId); if (items.Count <= 0) { Debug.LogError("[OfferTreasureAscent] Grand reward is empty."); return null; } _playerItemData.AddItem(items, null); Claim(GetRewardCount()); // Debug.Log("[OfferTreasureAscent] Grant Final."); return items; } public void OnBuySuccess() { if (_failedPurchaseRewardIndices is { Count: > 0 }) { foreach (var idx in _failedPurchaseRewardIndices) Claim(idx); _failedPurchaseRewardIndices.Clear(); Save(); } TryGrantFinalReward(); } public void RecordFailedRewardIdx(int idx) { _failedPurchaseRewardIndices.Add(idx); Save(); } #endregion } }