using System; using System.Collections.Generic; using System.Linq; using asap.core; using cfg; using game; using GameCore; using UniRx; using UnityEngine; using IEventData = Activity.IEventData; using Activity; using Random = UnityEngine.Random; namespace EventSmash.Manager { public class EventSmashManager : Activity.AEventDataManager, IActivityEventRedPoint { public EventSmashManager() { GContext.OnEvent() .Subscribe(OnMiniBattlePassRedPointChangedForMergedEntrance) .AddTo(CompositeDisposable); } private void OnMiniBattlePassRedPointChangedForMergedEntrance(MiniBattlePassRedPointChangedEvent e) { if (e.Type != MiniBattlePassType.Smash) { return; } try { RefreshMergedEntranceRedPoint(); } catch { // 数据未就绪等 } } #region data protected override void OnDataInitialized(IEventData data, FishingEvent eventConfig) { base.OnDataInitialized(data, eventConfig); // 新一期活动(EventID 变化)时重置引导组 Smash01,与主面板 TriggerGuide(..., false) 配合每期仅触发一次 GContext.container.Resolve().ClearGuide(GroupName.Smash01.ToString()); var smashPotData = (EventSmashData)data; var welcomeGift = GContext.container.Resolve().GetInitWelcomeGift(eventConfig.ID); AddToken(smashPotData, welcomeGift); GenerateStageRewards(smashPotData); } #endregion data #region Token public int GetCurrentTokenCount(EventSmashData data) { if (data == null) return 0; return GetTokenCount(data); } #endregion Token #region Stage Rewards Generation /// /// 生成关卡奖励 /// 1. 第1关:StageItemProb1 保底收集;第2关:StageItemProb2 保底;第3关起无保底 /// 2. 剩余格子从 EventSmashReward 表按权重随机抽取(包含收集道具和普通奖励) /// private void GenerateStageRewards(EventSmashData data) { if (data == null || !data.IsActive) return; var mainConfig = data.MainConfig; if (mainConfig == null) return; const int totalGrids = 9; var rewards = new List<(int item, int count)>(); var collectionConfigs = data.GetAllCollectionConfigs(); var rewardConfigs = data.GetAllRewardConfigs(); if (collectionConfigs == null || rewardConfigs == null) return; // CompletedStageCount:已完成关卡数。生成当前关奖励时 0=第1关、1=第2关、≥2=第3关及以后 List stageItemProbs = null; if (data.CompletedStageCount == 0) stageItemProbs = mainConfig.StageItemProb1; else if (data.CompletedStageCount == 1) stageItemProbs = mainConfig.StageItemProb2; if (stageItemProbs != null) { for (int i = 0; i < collectionConfigs.Count && i < stageItemProbs.Count; i++) { int guaranteedCount = stageItemProbs[i]; for (int j = 0; j < guaranteedCount; j++) { rewards.Add((collectionConfigs[i].ID, 0)); } } } if (rewards.Count > totalGrids) rewards.RemoveRange(totalGrids, rewards.Count - totalGrids); int remainingCount = totalGrids - rewards.Count; if (remainingCount > 0) { var randomRewards = SelectRewardsByWeight(rewardConfigs, remainingCount); rewards.AddRange(randomRewards); } Shuffle(rewards); GenerateGridRewardMap(data, rewards); SaveData(data); } /// /// 生成所有格子的奖励 ItemData 映射 /// item < 0 → 收集道具,从 Collection 表的 DropCount 随机数量(不做过关膨胀) /// item > 0 → 普通物品:表内 item+count 经 后写入(与当前活动膨胀系数快照一致) /// private void GenerateGridRewardMap(EventSmashData data, List<(int item, int count)> rewards) { if (data == null || rewards == null) return; data.GridRewardMap.Clear(); var playerItem = GContext.container.Resolve(); for (int i = 0; i < rewards.Count; i++) { var (item, count) = rewards[i]; ItemData rewardItemData; if (item < 0) { var collectionConfig = data.Tables.TbEventSmashCollection.GetOrDefault(item); if (collectionConfig?.DropCount == null || collectionConfig.DropCount.Count == 0) continue; var countIndex = Random.Range(0, collectionConfig.DropCount.Count); var addAmount = collectionConfig.DropCount[countIndex]; rewardItemData = new ItemData { id = item, count = addAmount, }; } else { rewardItemData = new ItemData { id = item, count = count, changeSource = new ChangeSource(data.EventID, "Event_Minigame2", "Event_Minigame2_Smash", "DirectReward") }; playerItem.LureInflation(new List { rewardItemData }, data.InflationRate); if (rewardItemData.id == 1002) { rewardItemData.count = playerItem.GetExtraCoinMag(rewardItemData.count); } } data.GridRewardMap[i] = rewardItemData; } } /// /// 根据权重随机选择奖励,返回 (item, count) 列表 /// private List<(int item, int count)> SelectRewardsByWeight(List rewardConfigs, int count) { var result = new List<(int item, int count)>(); if (rewardConfigs == null || rewardConfigs.Count == 0) return result; int totalWeight = 0; foreach (var r in rewardConfigs) { totalWeight += r.Weight; } for (int i = 0; i < count; i++) { int randomValue = Random.Range(0, totalWeight); int currentWeight = 0; foreach (var reward in rewardConfigs) { currentWeight += reward.Weight; if (randomValue < currentWeight) { result.Add((reward.Item, reward.Count)); break; } } } return result; } private void Shuffle(List list) { for (int i = list.Count - 1; i > 0; i--) { int j = Random.Range(0, i + 1); (list[i], list[j]) = (list[j], list[i]); } } #endregion Stage Rewards Generation #region Smash /// /// 砸单个罐子 /// /// 本格砸完后是否触发九格全开并已进入下一关(与异步动效回调配合,不依赖 Manager 上的延迟标志位)。 public bool SmashSingle(EventSmashData data, int gridIndex, out bool stageJustCompleted) { stageJustCompleted = false; if (!TryValidateSmash(data, out _)) return false; if (data.OpenedGrids.Contains(gridIndex)) return false; if (!TryConsumeToken(data, 1)) return false; ProcessGrid(data, gridIndex); ReportEventSmashScrapAgg(data, 1, new List { gridIndex }, false); FinalizeSmash(data, out stageJustCompleted); SaveData(data); NotifyMiniBattlePassProgress(data, 1); SetRedPoint(data); return true; } /// /// 一键全砸 /// /// 是否在本轮操作中完成九格全开并已进入下一关。 public bool SmashAll(EventSmashData data, out bool stageJustCompleted) { stageJustCompleted = false; if (!TryValidateSmash(data, out _)) return false; int unopenedCount = 9 - data.OpenedGrids.Count; if (unopenedCount <= 0) return false; int totalCost = unopenedCount; if (!TryConsumeToken(data, totalCost)) return false; var toOpen = Enumerable.Range(0, 9).Where(i => !data.OpenedGrids.Contains(i)).ToList(); foreach (var i in toOpen) ProcessGrid(data, i); ReportEventSmashScrapAgg(data, toOpen.Count, toOpen, true); FinalizeSmash(data, out stageJustCompleted); SaveData(data); NotifyMiniBattlePassProgress(data, unopenedCount); SetRedPoint(data); return true; } #region 公共提取方法 /// /// 验证参数并获取配置 /// private bool TryValidateSmash(EventSmashData data, out EventSmashMain mainConfig) { mainConfig = null; if (data == null || !data.IsActive) return false; mainConfig = data.MainConfig; return mainConfig != null; } /// /// 检查并扣除代币 /// private bool TryConsumeToken(EventSmashData data, int cost) { int tokenCount = GetTokenCount(data); if (tokenCount < cost) { ShowFestPackPanel(data); return false; } RemoveToken(data, cost); return true; } private void ProcessGrid(EventSmashData data, int gridIndex) { data.OpenedGrids.Add(gridIndex); GrantGridReward(data, gridIndex); } /// /// 完成砸取:检查九格是否全开并进入下一关(代币与合并红点由 / 在战令上报后统一 )。 /// private void FinalizeSmash(EventSmashData data, out bool stageJustCompleted) { stageJustCompleted = CheckStageComplete(data); } #endregion /// /// 检查关卡是否完成 /// private bool CheckStageComplete(EventSmashData data) { if (data.OpenedGrids.Count < 9) return false; // 关卡完成 data.CompletedStageCount++; data.OpenedGrids.Clear(); data.GridRewardMap?.Clear(); // 生成下一关奖励 GenerateStageRewards(data); return true; } /// /// 与 Bingo 等小战令一致:仅当战令表 时, /// 在此按砸罐次数手动累加;若表配了事件型 TaskType,进度由 内事件订阅上报,此处不再调用以免重复。 /// private void NotifyMiniBattlePassProgress(EventSmashData data, int count) { if (data?.MainConfig == null || data.MainConfig.BattlePassId <= 0 || count <= 0) return; var miniBattlePassTable = Tables.TbMiniBattlePass.GetOrDefault(data.MainConfig.BattlePassId); if (miniBattlePassTable == null || miniBattlePassTable.TaskType != ConditionType.None) { return; } GContext.container.Resolve().UpdateTaskData(MiniBattlePassType.Smash, count); } #endregion Smash #region Collection /// /// 当前轮收集进度是否已达成本轮领奖条件(仅判断,不改数据) /// public bool CanClaimCollectionTier(EventSmashData data, int collectionId) { var collectionConfig = Tables.TbEventSmashCollection.GetOrDefault(collectionId); if (collectionConfig == null || collectionConfig.CollectingRequirement.Count == 0) return false; int amount = data.GetCollectionData(collectionId); int round = data.CollectionProgressPairs.TryGetValue(collectionId, out var pair) ? pair.round : 0; int reqIndex = round % collectionConfig.CollectingRequirement.Count; int requirement = collectionConfig.CollectingRequirement[reqIndex]; return amount >= requirement; } /// /// 当前进度可连续领取的档位数(不改数据,用于先播满 N 次表现再批量领奖)。 /// public int GetClaimableCollectionTierCount(EventSmashData data, int collectionId) { var collectionConfig = Tables.TbEventSmashCollection.GetOrDefault(collectionId); if (collectionConfig == null || collectionConfig.CollectingRequirement.Count == 0) return 0; int amount = data.GetCollectionData(collectionId); int round = data.CollectionProgressPairs.TryGetValue(collectionId, out var pair) ? pair.round : 0; int count = 0; while (true) { int reqIndex = round % collectionConfig.CollectingRequirement.Count; int requirement = collectionConfig.CollectingRequirement[reqIndex]; if (amount < requirement) break; amount -= requirement; round++; count++; } return count; } /// /// 领取一轮收集奖励:先扣进度并 ,再写入 , /// 由面板整段表现结束后 合并弹窗。 /// public void ClaimOneCollectionTierToStash(EventSmashData data, int collectionId) { var collectionConfig = Tables.TbEventSmashCollection.GetOrDefault(collectionId); if (collectionConfig == null || collectionConfig.CollectingRequirement.Count == 0) return; int amount = data.GetCollectionData(collectionId); int round = data.CollectionProgressPairs.TryGetValue(collectionId, out var pair) ? pair.round : 0; int reqIndex = round % collectionConfig.CollectingRequirement.Count; int requirement = collectionConfig.CollectingRequirement[reqIndex]; if (amount < requirement) return; int configCount = collectionConfig.CollectingRewardList != null ? collectionConfig.CollectingRewardList.Count : 0; int rewardDropId = configCount > 0 ? collectionConfig.CollectingRewardList[round % configCount] : 0; amount -= requirement; round++; data.SetCollectionData(collectionId, amount, round); SaveData(data); if (rewardDropId > 0) { ChangeSource changeSource = new ChangeSource(data.EventID, "Event_Minigame2", "Event_Minigame2_Smash", "TaskReward"); GContext.Publish(new DeferredRewardStashService.EventStashDrop { DropId = rewardDropId, Inflate = data.InflationRate, changeSource = changeSource }); } } #region Agg(砸罐:与表现无关,单开/全开逻辑在 Manager 内落盘后立刻上报;须在 前调用以保留本关 ) /// /// 基于当前数据(已 、尚未 )推算本刻可连续领取的各档,与 Claim 规则一致,仅用于打点。 /// private static List<(int collectionId, int requirement, int rewardDropId)> BuildSmashAggCollectClaimsSnapshot(EventSmashData data) { var result = new List<(int, int, int)>(); var list = data?.Tables?.TbEventSmashCollection?.DataList; if (list == null) return result; foreach (var collectionConfig in list) { if (collectionConfig == null || collectionConfig.CollectingRequirement == null || collectionConfig.CollectingRequirement.Count == 0) continue; int collectionId = collectionConfig.ID; int amount = data.GetCollectionData(collectionId); int round = data.CollectionProgressPairs.TryGetValue(collectionId, out var pair) ? pair.round : 0; while (true) { int reqIndex = round % collectionConfig.CollectingRequirement.Count; int requirement = collectionConfig.CollectingRequirement[reqIndex]; if (amount < requirement) break; int configCount = collectionConfig.CollectingRewardList != null ? collectionConfig.CollectingRewardList.Count : 0; int rewardDropId = configCount > 0 ? collectionConfig.CollectingRewardList[round % configCount] : 0; result.Add((collectionId, requirement, rewardDropId)); amount -= requirement; round++; } } return result; } private static string BuildSmashAggRewardList(EventSmashData data, List openedGridIndices) { var parts = new List(); if (data?.GridRewardMap != null && openedGridIndices != null) { foreach (var gi in openedGridIndices.OrderBy(x => x)) { if (!data.GridRewardMap.TryGetValue(gi, out var item) || item == null || item.id <= 0) continue; parts.Add($"{item.id},{item.count}"); } } return parts.Count > 0 ? string.Join(",", parts) : ""; } /// 单开或全开成功扣费、处理格子后立刻打点,不依赖面板/动效。 private void ReportEventSmashScrapAgg(EventSmashData data, int itemConsume, List openedGridIndices, bool isSmashAll) { if (data == null || openedGridIndices == null) return; string rewardList = BuildSmashAggRewardList(data, openedGridIndices); var claims = BuildSmashAggCollectClaimsSnapshot(data); var collectionRows = data.Tables?.TbEventSmashCollection?.DataList; string CtAt(int indexInTable) { if (collectionRows == null || indexInTable < 0 || indexInTable >= collectionRows.Count) return "0"; int cid = collectionRows[indexInTable].ID; return BuildSmashAggCollectTargetString(claims, cid); } string ct1 = CtAt(0); string ct2 = CtAt(1); string ct3 = CtAt(2); #if UNITY_EDITOR Debug.Log( $"[EventSmash][AGG] item_consume={itemConsume} reward_list={rewardList} " + $"collect_target1={ct1} collect_target2={ct2} collect_target3={ct3} smash_all={isSmashAll}"); #endif #if AGG using (var e = GEvent.GameEvent("event_smash_scrap")) { e.AddContent("item_consume", itemConsume) .AddContent("reward_list", rewardList) .AddContent("collect_target1", ct1) .AddContent("collect_target2", ct2) .AddContent("collect_target3", ct3); } #endif } private static string BuildSmashAggCollectTargetString( List<(int collectionId, int requirement, int rewardDropId)> claims, int collectionId) { var parts = claims .Where(c => c.collectionId == collectionId) .Select(c => $"{c.requirement},{c.rewardDropId}"); var s = string.Join(";", parts); return string.IsNullOrEmpty(s) ? "0" : s; } #endregion /// /// 获取收集进度 /// public (int current, int target) GetCollectionProgress(EventSmashData data, int collectionId, out EventSmashCollection collectionConfig) { collectionConfig = null; if (data == null) return (0, 0); collectionConfig = Tables.TbEventSmashCollection.GetOrDefault(collectionId); if (collectionConfig == null || collectionConfig.CollectingRequirement.Count == 0) return (0, 0); var amount = data.GetCollectionData(collectionId); var round = data.CollectionProgressPairs.TryGetValue(collectionId, out var pair) ? pair.round : 0; int reqIndex = round % collectionConfig.CollectingRequirement.Count; int requirement = collectionConfig.CollectingRequirement[reqIndex]; return (amount, requirement); } #endregion Collection #region FestPack (Gift Pack) public async void ShowFestPackPanel(EventSmashData data) { try { if (data?.MainConfig == null) return; var packData = new EventNeoNormalPackData { EventId = data.EventID, PackId = data.MainConfig.PackID, Panel=data.MainConfig.PackPanel }; await EventNeoNormalPackPanel.TryShowAsync(packData); } catch (Exception e) { Debug.LogError($"ShowFestPackPanel error: {e}"); } } #endregion FestPack #region RedPoint private string GetRedPointKey() { return "eventSmashPot.red"; } private static string GetSmashMiniBattlePassRedKey() { return HomeBtnMiniBP.redKey+MiniBattlePassType.Smash; } private bool GetRedPointState(EventSmashData data) { if (!data.IsActive) return false; if (GetTokenCount(data) > 0) return true; var rpm = RedPointManager.Instance; return rpm != null && rpm.GetRedPointState(GetSmashMiniBattlePassRedKey()); } /// 砸罐入口红点数量:代币数 + Smash 小战令 private int GetRedPointCount(EventSmashData data) { if (!data.IsActive) return 0; var rpm = RedPointManager.Instance; int bp = rpm != null ? rpm.GetRedPointCount(GetSmashMiniBattlePassRedKey()) : 0; return GetTokenCount(data) + bp; } private void SetRedPoint(EventSmashData data) { var rpm = RedPointManager.Instance; if (rpm == null) return; rpm.SetRedPointState(GetRedPointKey(), GetRedPointState(data), GetRedPointCount(data)); } /// /// Smash 小战令已写入 MiniBPSmash)后调用,刷新入口 eventSmashPot.red 的合并状态/数量,避免入口仍显示合并前的缓存。 /// public void RefreshMergedEntranceRedPoint() { var data = LoadData(); if (data != null && data.IsActive) { SetRedPoint(data); } } void IActivityEventRedPoint.SetRedPoint(EventSmashData data) => SetRedPoint(data); #endregion RedPoint #region Grid Reward /// /// 发放格子奖励 /// private void GrantGridReward(EventSmashData data, int gridIndex) { if (!data.GridRewardMap.TryGetValue(gridIndex, out var rewardItemData)) { return; } if (rewardItemData.id < 0) { int collectionId = rewardItemData.id; var addAmount = rewardItemData.count; var currentAmount = data.GetCollectionData(collectionId); data.SetCollectionData(collectionId, currentAmount + addAmount); } else { GrantNonCollectionGridRewardDirectToBag(data, rewardItemData); } } /// /// 非收集类格子奖励:直接入包、不经过临时背包,不单独为该项弹通用领奖窗。 /// 膨胀已在 写入 时完成,此处不再 ,避免二次膨胀。 /// private void GrantNonCollectionGridRewardDirectToBag(EventSmashData data, ItemData rewardItemData) { if (rewardItemData == null || data == null) return; var changeSource = new ChangeSource(data.EventID, "Event_Minigame2", "Event_Minigame2_Smash", "GridReward"); rewardItemData.changeSource = changeSource; GContext.container.Resolve().AddItem(rewardItemData, changeSource); } /// /// 获取格子奖励(用于显示) /// public ItemData GetGridReward(EventSmashData data, int gridIndex) { if (data == null || gridIndex < 0) return null; return data.GridRewardMap.TryGetValue(gridIndex, out var item) ? item : null; } #endregion Grid Reward } }