Files
ft/Client/Assets/Scripts/EventSmash/Manager/EventSmashManager.cs
2026-06-29 21:18:33 +08:00

686 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<EventSmashData>, IActivityEventRedPoint<EventSmashData>
{
public EventSmashManager()
{
GContext.OnEvent<MiniBattlePassRedPointChangedEvent>()
.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<GuideDataCenter>().ClearGuide(GroupName.Smash01.ToString());
var smashPotData = (EventSmashData)data;
var welcomeGift = GContext.container.Resolve<FishingEventData>().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
/// <summary>
/// 生成关卡奖励
/// 1. 第1关StageItemProb1 保底收集第2关StageItemProb2 保底第3关起无保底
/// 2. 剩余格子从 EventSmashReward 表按权重随机抽取(包含收集道具和普通奖励)
/// </summary>
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<int> 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);
}
/// <summary>
/// 生成所有格子的奖励 ItemData 映射
/// item &lt; 0 → 收集道具,从 Collection 表的 DropCount 随机数量(不做过关膨胀)
/// item &gt; 0 → 普通物品:表内 item+count 经 <see cref="PlayerItemData.LureInflation"/> 后写入(与当前活动膨胀系数快照一致)
/// </summary>
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<PlayerItemData>();
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<ItemData> { rewardItemData }, data.InflationRate);
if (rewardItemData.id == 1002)
{
rewardItemData.count = playerItem.GetExtraCoinMag(rewardItemData.count);
}
}
data.GridRewardMap[i] = rewardItemData;
}
}
/// <summary>
/// 根据权重随机选择奖励,返回 (item, count) 列表
/// </summary>
private List<(int item, int count)> SelectRewardsByWeight(List<EventSmashReward> 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<T>(List<T> 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
/// <summary>
/// 砸单个罐子
/// </summary>
/// <param name="stageJustCompleted">本格砸完后是否触发九格全开并已进入下一关(与异步动效回调配合,不依赖 Manager 上的延迟标志位)。</param>
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<int> { gridIndex }, false);
FinalizeSmash(data, out stageJustCompleted);
SaveData(data);
NotifyMiniBattlePassProgress(data, 1);
SetRedPoint(data);
return true;
}
/// <summary>
/// 一键全砸
/// </summary>
/// <param name="stageJustCompleted">是否在本轮操作中完成九格全开并已进入下一关。</param>
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
/// <summary>
/// 验证参数并获取配置
/// </summary>
private bool TryValidateSmash(EventSmashData data, out EventSmashMain mainConfig)
{
mainConfig = null;
if (data == null || !data.IsActive) return false;
mainConfig = data.MainConfig;
return mainConfig != null;
}
/// <summary>
/// 检查并扣除代币
/// </summary>
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);
}
/// <summary>
/// 完成砸取:检查九格是否全开并进入下一关(代币与合并红点由 <see cref="SmashSingle"/> / <see cref="SmashAll"/> 在战令上报后统一 <see cref="SetRedPoint"/>)。
/// </summary>
private void FinalizeSmash(EventSmashData data, out bool stageJustCompleted)
{
stageJustCompleted = CheckStageComplete(data);
}
#endregion
/// <summary>
/// 检查关卡是否完成
/// </summary>
private bool CheckStageComplete(EventSmashData data)
{
if (data.OpenedGrids.Count < 9) return false;
// 关卡完成
data.CompletedStageCount++;
data.OpenedGrids.Clear();
data.GridRewardMap?.Clear();
// 生成下一关奖励
GenerateStageRewards(data);
return true;
}
/// <summary>
/// 与 Bingo 等小战令一致:仅当战令表 <see cref="MiniBattlePass.TaskType"/> 为 <see cref="ConditionType.None"/> 时,
/// 在此按砸罐次数手动累加;若表配了事件型 TaskType进度由 <see cref="MiniBattlePassDataModel"/> 内事件订阅上报,此处不再调用以免重复。
/// </summary>
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<MiniBattlePassDataProvider>().UpdateTaskData(MiniBattlePassType.Smash, count);
}
#endregion Smash
#region Collection
/// <summary>
/// 当前轮收集进度是否已达成本轮领奖条件(仅判断,不改数据)
/// </summary>
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;
}
/// <summary>
/// 当前进度可连续领取的档位数(不改数据,用于先播满 N 次表现再批量领奖)。
/// </summary>
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;
}
/// <summary>
/// 领取一轮收集奖励:先扣进度并 <see cref="SaveData"/>,再写入 <see cref="DeferredRewardStashService"/>
/// 由面板整段表现结束后 <see cref="IDeferredRewardStashService.Flush"/> 合并弹窗。
/// </summary>
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 <see cref="FinalizeSmash"/> <see cref="EventSmashData.GridRewardMap"/>
/// <summary>
/// 基于当前数据(已 <see cref="GrantGridReward"/>、尚未 <see cref="ClaimOneCollectionTierToStash"/>)推算本刻可连续领取的各档,与 Claim 规则一致,仅用于打点。
/// </summary>
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<int> openedGridIndices)
{
var parts = new List<string>();
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) : "";
}
/// <summary>单开或全开成功扣费、处理格子后立刻打点,不依赖面板/动效。</summary>
private void ReportEventSmashScrapAgg(EventSmashData data, int itemConsume, List<int> 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
/// <summary>
/// 获取收集进度
/// </summary>
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());
}
/// <summary>砸罐入口红点数量:代币数 + Smash 小战令 <see cref="RedPointManager.GetRedPointCount"/>。</summary>
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));
}
/// <summary>
/// Smash 小战令已写入 <see cref="RedPointManager"/><c>MiniBPSmash</c>)后调用,刷新入口 <c>eventSmashPot.red</c> 的合并状态/数量,避免入口仍显示合并前的缓存。
/// </summary>
public void RefreshMergedEntranceRedPoint()
{
var data = LoadData();
if (data != null && data.IsActive)
{
SetRedPoint(data);
}
}
void IActivityEventRedPoint<EventSmashData>.SetRedPoint(EventSmashData data) => SetRedPoint(data);
#endregion RedPoint
#region Grid Reward
/// <summary>
/// 发放格子奖励
/// </summary>
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);
}
}
/// <summary>
/// 非收集类格子奖励:直接入包、不经过临时背包,不单独为该项弹通用领奖窗。
/// 膨胀已在 <see cref="GenerateGridRewardMap"/> 写入 <see cref="EventSmashData.GridRewardMap"/> 时完成,此处不再 <see cref="PlayerItemData.LureInflation"/>,避免二次膨胀。
/// </summary>
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<PlayerItemData>().AddItem(rewardItemData, changeSource);
}
/// <summary>
/// 获取格子奖励(用于显示)
/// </summary>
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
}
}