using System; using System.Collections.Generic; using System.Linq; using LitJson; using UnityEngine; public class TimingGiftManager : GameSystemManager { private int m_selectTabIndex; public int selectTabIndex { get { return m_selectTabIndex; } set { if (m_selectTabIndex == value) return; m_selectTabIndex = value; OnSelectTabIndexChangeEvent?.Invoke(); } } public int selectTabGiftId; public event Action OnSelectTabIndexChangeEvent; private int m_selectCtgIdIndex; public int selectCtgIdIndex { get { return m_selectCtgIdIndex; } set { if (m_selectCtgIdIndex == value) return; m_selectCtgIdIndex = value; OnSelectCtgIdIndexChangeEvent?.Invoke(); } } public int selectCtgId; public int[] selectCtgIds; public int[][] selectGainItemList; public int limitPopCnt; public int limitPopCd; public event Action OnSelectCtgIdIndexChangeEvent; public bool isLogShow = true; public override void Init() { DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin += OnBeforePlayerDataInitializeEventOnRelogin; DTC0403_tagPlayerLoginLoadOK.playerLoginOkEvent += OnPlayerLoginOk; GlobalTimeEvent.Instance.secondEvent += OnSecondEvent; var config = FuncConfigConfig.Get("TimingGift"); limitPopCnt = int.Parse(config.Numerical1); limitPopCd = int.Parse(config.Numerical2); } public override void Release() { DTC0102_tagCDBPlayer.beforePlayerDataInitializeEventOnRelogin -= OnBeforePlayerDataInitializeEventOnRelogin; DTC0403_tagPlayerLoginLoadOK.playerLoginOkEvent -= OnPlayerLoginOk; GlobalTimeEvent.Instance.secondEvent -= OnSecondEvent; } private void OnBeforePlayerDataInitializeEventOnRelogin() { showGiftIdList.Clear(); lastTriggerTimeDict.Clear(); } private void OnPlayerLoginOk() { LoadData(); } private void OnSecondEvent() { RemoveExpired(); } public Color32 GetColor32(int[] colorArr) { if (colorArr == null || colorArr.Length < 3) return Color.white; return new Color32() { r = (byte)(colorArr.Length > 0 ? colorArr[0] : 0), g = (byte)(colorArr.Length > 1 ? colorArr[1] : 0), b = (byte)(colorArr.Length > 2 ? colorArr[2] : 0), a = (byte)(colorArr.Length > 3 ? colorArr[3] : 255), }; } public static bool TryGetTimingGiftTypeConfig(int giftType, out TimingGiftTypeConfig config) { config = null; if (!TimingGiftTypeConfig.HasKey(giftType)) return false; config = TimingGiftTypeConfig.Get(giftType); return true; } public bool IsBuy(int ctgId) { if (!CTGConfig.HasKey(ctgId)) return false; CTGConfig ctgConfig = CTGConfig.Get(ctgId); bool hasRechargeCount = RechargeManager.Instance.TryGetRechargeCount(ctgId, out RechargeCount _rechargeCount); bool isBuy = hasRechargeCount && _rechargeCount.todayCount >= ctgConfig.DailyBuyCount; return isBuy; } public bool IsListHasIndex(int index, List list) { return list != null && index >= 0 && index < list.Count; } public int GetListValue(int index, List list) { return IsListHasIndex(index, list) ? list[index] : 0; } public bool IsArrHasIndex(int index, int[] arr) { return arr != null && index >= 0 && index < arr.Length; } public int GetArrValue(int index, int[] arr) { return IsArrHasIndex(index, arr) ? arr[index] : 0; } public void OpenTimingGiftWin(int type) { if (!UIManager.Instance.IsOpened()) { UIManager.Instance.OpenWindow(type); } } List showGiftIdList = new List(); public event Action OnShowGiftIdListAddEvent; public bool IsShowGiftIdListHasType(int type) { if (showGiftIdList.IsNullOrEmpty()) return false; for (int i = 0; i < showGiftIdList.Count; i++) { int id = showGiftIdList[i]; if (TimingGiftConfig.TryGetTimingGiftConfig(id, out TimingGiftConfig config) && config.GiftType == type) return true; } return false; } public List GetCurrectTimingGiftIdList(bool isSort = false) { if (isSort) { // 按剩余时间少的排序靠前 showGiftIdList.Sort((a, b) => { int remainingSecondsA = GetRemainingSeconds(a); int remainingSecondsB = GetRemainingSeconds(b); return remainingSecondsA.CompareTo(remainingSecondsB); }); } return showGiftIdList; } HashSet typeHashSet; int GetPopCnt() { if (typeHashSet == null) typeHashSet = new HashSet(); typeHashSet.Clear(); if (showGiftIdList.IsNullOrEmpty()) return 0; for (int i = 0; i < showGiftIdList.Count; i++) { int id = showGiftIdList[i]; if (!TimingGiftConfig.TryGetTimingGiftConfig(id, out var config)) continue; typeHashSet.Add(config.GiftType); } return typeHashSet.Count; } bool IsInLimitCd() { if (lastTriggerTimeDict.IsNullOrEmpty()) return false; int lastTime = lastTriggerTimeDict.Values.Max(); return TimeUtility.AllSeconds - lastTime <= limitPopCd; } public void TryAdd(int type) { if (NewBieCenter.Instance.inGuiding) { #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] TryPop:触发Return,正在引导中"); } #endif return; } if (!FuncOpen.Instance.IsFuncOpen((int)FuncOpenEnum.TimingGift)) { #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] TryPop:触发Return,时机礼包功能未开启"); } #endif return; } if (!TimingGiftConfig.TryGetTypeToGiftIdList(type, out List giftIdList)) { #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] TryPop:触发Return,表格中没有找到{type}类型的礼包ID"); } #endif return; } int popCount = GetPopCnt(); if (limitPopCnt <= popCount) { #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] TryPop:触发Return,今日已达到限制次数"); } #endif return; } bool isInLimitCd = IsInLimitCd(); if (isInLimitCd) { #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] TryPop:触发Return,礼包触发冷却时间中"); } #endif return; } if (!IsTodayUnlimited(type)) { #if UNITY_EDITOR if (isLogShow) { int lastTriggerTime = lastTriggerTimeDict[type]; Debug.Log($"[TimingGift] TryPop:触发Return,今天已弹出过{type}类型的礼包,该类型上次弹出时间为{TimeUtility.GetTime((uint)lastTriggerTime):yyyy-MM-dd HH:mm:ss}"); } #endif return; } bool isChange = false; for (int i = 0; i < giftIdList.Count; i++) { int id = giftIdList[i]; if (!TimingGiftConfig.TryGetTimingGiftConfig(id, out TimingGiftConfig config)) continue; if (showGiftIdList.Contains(id)) continue; isChange = true; showGiftIdList.Add(id); lastTriggerTimeDict[id] = TimeUtility.AllSeconds; } if (isChange) { SaveData(); OpenTimingGiftWin(type); OnShowGiftIdListAddEvent?.Invoke(); } } public event Action OnRemoveExpiredEvent; public void RemoveExpired() { if (showGiftIdList.IsNullOrEmpty()) return; bool isChange = false; // 使用倒序遍历,避免移除元素时索引错位 for (int i = showGiftIdList.Count - 1; i >= 0; i--) { int id = showGiftIdList[i]; // 已经过期的移除 if (IsExpired(id)) { isChange = true; showGiftIdList.RemoveAt(i); } } if (isChange) { #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] RemoveExpired:触发移除过期礼包,当前有时效的giftID列表为[{string.Join(", ", showGiftIdList)}]"); } #endif OnRemoveExpiredEvent?.Invoke(); } } //触发类型 上次触发时间 Dictionary lastTriggerTimeDict = new Dictionary(); public bool TryGetLastTriggerTime(int type, out int lastTriggerTime) { return lastTriggerTimeDict.TryGetValue(type, out lastTriggerTime); } public bool IsExpired(int id) { return GetRemainingSeconds(id) <= 0; } public int GetRemainingSecondsByType(int type) { if (!TryGetLastTriggerTime(type, out int lastTriggerTime)) return 0; if (!TryGetTimingGiftTypeConfig(type, out TimingGiftTypeConfig typeConfig)) return 0; // 计算剩余秒数 DateTime lastTime = TimeUtility.GetTime((uint)lastTriggerTime); DateTime nowTime = TimeUtility.ServerNow; double elapsedSeconds = (nowTime - lastTime).TotalSeconds; int remainingSeconds = (int)(typeConfig.Duration - elapsedSeconds); return remainingSeconds; } public int GetRemainingSeconds(int id) { if (!TimingGiftConfig.TryGetTimingGiftConfig(id, out TimingGiftConfig config)) return 0; int type = config.GiftType; return GetRemainingSecondsByType(type); } // 判断指定type的礼包今日是否不限弹出 // true:今日不限弹出 false:今日已弹出过 public bool IsTodayUnlimited(int type) { // 没有触发记录,说明还没弹出过 if (!TryGetLastTriggerTime(type, out int lastTriggerTime)) return true; // 如果上次触发时间在今天0点之前,说明今天还没弹出过 int todayStartTick = TimeUtility.GetTodayStartTick(); return lastTriggerTime < todayStartTick; } private bool IsTriggle(int type) { if (!TryGetTimingGiftTypeConfig(type, out TimingGiftTypeConfig config)) { #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] IsTriggle:未找到{type}类型的TriggleChangeRate配置"); } #endif return false; } int rangeNum = UnityEngine.Random.Range(0, 100); bool res = rangeNum < config.TriggleChangeRate; #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] IsTriggle:判定固定概率,结果={res}, type={type}, rangeNum={rangeNum}, TriggleChangeRate={config.TriggleChangeRate}"); } #endif return res; } #region 礼包弹出条件 /// /// 使用完广告次数时弹出 /// public bool TryAddWhenAllAdsUsed(int type, int adId) { if (!ADAwardConfig.HasKey(adId)) return false; ADAwardConfig config = ADAwardConfig.Get(adId); int maxCount = config.ADCntMax; int haveCount = AdsManager.Instance.GetADCntByADID(adId) + 1; TryAddWhenAllAdsUsed(type, haveCount, maxCount); return true; } /// /// 使用完广告次数时弹出 /// public bool TryAddWhenAllAdsUsed(int type, int haveCount, int maxCount) { if (maxCount == haveCount) { TryAdd(type); return true; } return false; } /// /// 数量不足时必弹出 /// public bool TryAddWhenInsufficient(int type, PackType packType, int itemId, long needCount) { var haveCount = PackManager.Instance.GetItemCountByID(packType, itemId); return TryAddWhenInsufficient(type, haveCount, needCount); } /// /// 数量不足时必弹出 /// public bool TryAddWhenInsufficient(int type, long haveCount, long needCount) { // 需要0个时必弹 if (needCount <= 0) { TryAdd(type); return true; } bool isEnough = haveCount >= needCount; // 不足时必弹 if (!isEnough) { TryAdd(type); return true; } return false; } /// /// 数量正好消耗完时必弹出 /// public bool TryAddWhenExactConsumption(int type, PackType packType, int itemId, long needCount) { var haveCount = PackManager.Instance.GetItemCountByID(packType, itemId); return TryAddWhenExactConsumption(type, haveCount, needCount); } /// /// 数量正好消耗完时必弹出 /// public bool TryAddWhenExactConsumption(int type, long haveCount, long needCount) { // 需要0个时必弹 if (needCount <= 0) { TryAdd(type); return true; } //正好消耗完x时必弹 if (haveCount - needCount == 0) { TryAdd(type); return true; } return false; } /// /// 充足时固定概率弹出 /// public bool TryAddWithFixedProbabilityWhenSufficient(int type, PackType packType, int itemId, long needCount) { var haveCount = PackManager.Instance.GetItemCountByID(packType, itemId); return TryAddWithFixedProbabilityWhenSufficient(type, haveCount, needCount); } /// /// 充足时固定概率弹出 /// public bool TryAddWithFixedProbabilityWhenSufficient(int type, long haveCount, long needCount) { bool isEnough = haveCount >= needCount; if (!isEnough) { return false; } bool isTriggle = IsTriggle(type); if (isTriggle) { TryAdd(type); return true; } return false; } #endregion #region 数据保存 private void LoadData() { lastTriggerTimeDict.Clear(); if (!SettingDataManager.Instance.TryGetSettingData((int)SettingDataKeyNum.TimingGiftType, out string data)) return; // 使用 LocalSave 读取 JSON 字符串 if (string.IsNullOrEmpty(data) || data == "{}") return; Dictionary loadDict = ConfigParse.ParseIntDict(data); if (loadDict == null) return; lastTriggerTimeDict = loadDict; InitCurrectTimingGiftIdList(new List(loadDict.Keys)); #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] LoadData:当前有时效的giftID列表为: [{string.Join(", ", showGiftIdList)}]\n当前触发时间字典内容:\n{string.Join("\n", lastTriggerTimeDict.Select(kv => $" 类型{kv.Key}: {TimeUtility.GetTime((uint)kv.Value):yyyy-MM-dd HH:mm:ss}"))}"); } #endif } private void SaveData() { string jsonStr = JsonMapper.ToJson(lastTriggerTimeDict); SettingDataManager.Instance.SendSettingData(SettingDataKeyNum.TimingGiftType, jsonStr); #if UNITY_EDITOR if (isLogShow) { Debug.Log($"[TimingGift] SaveData:当前有时效的giftID列表为: [{string.Join(", ", showGiftIdList)}]\n保存的触发时间字典内容:\n{string.Join("\n", lastTriggerTimeDict.Select(kv => $" 类型{kv.Key}: {TimeUtility.GetTime((uint)kv.Value):yyyy-MM-dd HH:mm:ss}"))}"); } #endif } public void InitCurrectTimingGiftIdList(List list) { if (list == null) return; for (int i = 0; i < list.Count; i++) { int type = list[i]; if (!TimingGiftConfig.TryGetTypeToGiftIdList(type, out List giftIdList)) continue; for (int j = 0; j < giftIdList.Count; j++) { // 表中没有的不处理 if (!TimingGiftConfig.TryGetTimingGiftConfig(type, out TimingGiftConfig config)) continue; // 已经过期的不处理 if (IsExpired(type)) continue; // 重复的不处理 if (showGiftIdList.Contains(type)) continue; showGiftIdList.Add(type); } } } #endregion }