Files
ft/Client/Assets/Scripts/Activity/ActivityManager.cs
2026-06-29 21:18:33 +08:00

364 lines
12 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 Activity.Condition;
using Activity.EventSubscriber;
using asap.core;
using cfg;
using TimeManager;
using UnityEngine;
namespace Activity
{
/// <summary>
/// 活动管理器 - 负责监听事件、判断条件、调用Sentry执行开关
/// </summary>
public class ActivityManager : IDisposable
{
public ActivityManager(Tables tables,ActivityScopeManager scopeManager)
{
_tables = tables;
_scopeManager = scopeManager;
}
private readonly Tables _tables;
private readonly ActivityScopeManager _scopeManager;
private readonly Dictionary<(int EventType,int EventSubType), IActivitySentry> _sentryMap = new();
private readonly Dictionary<Type, HashSet<FishingEvent>> _dataTypeIndex = new();
#region ManagerInit
private void InitAllManage()
{
// 注册 EventDataRepository 为单例
GContext.container.Register<Data.IEventDataRepository,Data.EventDataRepository>().AsSingleton();
Debug.Log("[ActivityManager] EventDataRepository registered as singleton.");
}
#endregion
#region Sentry
private void InitializeAllSentries()
{
// 按 (Type, SubType) 分组,每个类型只创建一次 Sentry
var eventGroups = _tables.TbFishingEvent.DataList
.GroupBy(e => (e.Type, e.SubType));
foreach (var group in eventGroups)
{
var events = group.ToList();
var sentry = GetOrCreateSentry(events);
if (sentry != null)
BuildDataTypeIndexForGroup(events);
else
_scopeManager.DestroyActivityScope((events[0].Type, events[0].SubType));
}
Debug.Log($"[ActivityManager] Initialized {_sentryMap.Count} sentries, indexed {_dataTypeIndex.Count} data types.");
}
private IActivitySentry GetOrCreateSentry(List<FishingEvent> events)
{
if (events == null || events.Count == 0) return null;
var typeKey = (events[0].Type, events[0].SubType);
// 如果已经创建过,直接返回
if (_sentryMap.TryGetValue(typeKey, out var createSentry))
{
return createSentry;
}
var sentryType = ActivitySentryManager.GetActivitySentryType(events[0]);
if (sentryType == null)
{
#if UNITY_EDITOR
Debug.LogWarning($"[ActivityManager] No sentry for event {events[0].ID}");
#endif
return null;
}
var scope = _scopeManager.GetActivityScope(typeKey);
var instance = scope.GetInstance((typeof(IActivitySentry), sentryType.Name));
if (instance is IActivitySentry sentry)
{
//注入依赖
sentry.InjectDependencies(_scopeManager);
sentry.CurrentEventConfig = null;
sentry.RelatedEvents = events;
_sentryMap[typeKey] = sentry;
Debug.Log($"[ActivityManager] Created sentry {sentryType.Name} for Type:{events[0].Type} SubType:{events[0].SubType}");
return sentry;
}
Debug.LogError($"[ActivityManager] Failed to create {sentryType.Name}");
return null;
}
/// <summary>
/// 为一组活动构建数据类型索引(同一类型的活动共享相同的检查器)
/// </summary>
private void BuildDataTypeIndexForGroup(List<FishingEvent> allEvents)
{
if (allEvents == null || allEvents.Count == 0) return;
var dataTypeToEvents = new Dictionary<Type, HashSet<FishingEvent>>();
foreach (var evt in allEvents)
{
var checkers = evt.GetConditionCheckers();
if (checkers == null) continue;
foreach (var checker in checkers)
{
if (checker.CareDataChangeWithType == null) continue;
foreach (var dataType in checker.CareDataChangeWithType)
{
if (!dataTypeToEvents.ContainsKey(dataType))
dataTypeToEvents[dataType] = new HashSet<FishingEvent>();
dataTypeToEvents[dataType].Add(evt);
}
}
}
foreach (var (dataType, events) in dataTypeToEvents)
{
if (!_dataTypeIndex.ContainsKey(dataType))
_dataTypeIndex[dataType] = new HashSet<FishingEvent>();
_dataTypeIndex[dataType].UnionWith(events);
}
}
#endregion
#region
private void SubscribeGlobalEvents()
{
foreach (var subscriber in ActivitySubscriberManager.ActivityEventSubscribers)
subscriber.Subscribe(this);
Debug.Log("[ActivityManager] Global events subscribed.");
}
/// <summary>
/// 将活动代币余额变更转发给各 Sentry由 <see cref="EventSubscriber.ActivityTokenBalanceRedPointSubscriber"/> 订阅总线后调用)。
/// </summary>
public void DispatchActivityTokenBalanceChanged(int eventId)
{
foreach (var sentry in _sentryMap.Values)
sentry.OnActivityTokenBalanceChanged(eventId);
}
#endregion
#region
/// <summary>
/// 当数据变化时调用 - 通用处理方法
/// 直接通过数据类型找到所有需要检查的活动,并从活动中获取该数据类型的检查器
/// </summary>
public void OnDataChanged<T>(T data)
{
// 查找关心该数据类型的所有活动
if (!_dataTypeIndex.TryGetValue(typeof(T), out var events)) return;
foreach (var fishingEvent in events)
OnActivitySingleEvent(fishingEvent);
}
/// <summary>
/// 当活动特定事件发生时调用(收集奖励、购买礼包等)
/// </summary>
public void OnActivitySingleEvent(FishingEvent fishingEvent)
{
if (fishingEvent == null) return;
var typeKey = (fishingEvent.Type, fishingEvent.SubType);
CheckActivityStatus(typeKey);
}
public void OnActivityEvents(List<FishingEvent> fishingEvents)
{
foreach (var fishingEvent in fishingEvents)
{
OnActivitySingleEvent(fishingEvent);
}
}
#endregion
#region
/// <summary>
/// 检查所有活动的状态
/// </summary>
public void CheckAllActivities()
{
foreach (var typeKey in _sentryMap.Keys)
{
CheckActivityStatus(typeKey);
}
}
/// <summary>
/// 检查指定类型的活动状态
/// </summary>
private void CheckActivityStatus((int, int) typeKey)
{
if (!_sentryMap.TryGetValue(typeKey, out var sentry)) return;
// 从 Sentry 中获取该类型的所有活动
var relevantEvents = sentry.RelatedEvents;
if (relevantEvents == null || relevantEvents.Count == 0) return;
foreach (var fishingEvent in relevantEvents)
{
IsOpenActivity(fishingEvent, sentry );
}
}
private void IsOpenActivity( FishingEvent fishingEvent, IActivitySentry sentry)
{
if(fishingEvent==null) return;
// 判断该活动是否已经开启
var isAlreadyOpen = sentry.CurrentEventConfig?.ID == fishingEvent.ID;
var isOpenActivity = ShouldActivityOpen(fishingEvent, isAlreadyOpen);
if (isOpenActivity && !isAlreadyOpen)
// 如果应该开启且未开启,则开启
OpenActivity(sentry, fishingEvent);
else if (!isOpenActivity && isAlreadyOpen)
// 只有应该关闭且已经开启时才关闭
CloseActivity(sentry, fishingEvent);
}
/// <summary>
/// 判断活动是否应该开启
/// </summary>
/// <param name="fishingEvent">活动配置</param>
/// <param name="isAlreadyOpen">活动是否已经开启</param>
private bool ShouldActivityOpen(FishingEvent fishingEvent, bool isAlreadyOpen)
{
var checkers = fishingEvent.GetConditionCheckers();
if (checkers == null) return false;
// 如果活动已经开启,只检查需要持续监听的条件(单向条件无需重复检查)
if (isAlreadyOpen)
{
return checkers
.Where(checker => checker.NeedContinueListenAfterSatisfied)
.All(checker => checker.CanOpen(fishingEvent));
}
// 活动未开启,检查所有条件
return checkers.All(checker => checker.CanOpen(fishingEvent));
}
#endregion
#region
/// <summary>
/// 开启活动
/// </summary>
private void OpenActivity(IActivitySentry sentry, FishingEvent fishingEvent)
{
// 开启新活动
sentry.Open(fishingEvent);
}
/// <summary>
/// 关闭活动
/// </summary>
private void CloseActivity(IActivitySentry sentry, FishingEvent fishingEvent)
{
sentry.Close(fishingEvent);
}
#endregion
#region API
public void Dispose()
{
// 释放所有 Sentry
foreach (var sentry in _sentryMap.Values)
sentry?.Dispose();
// 取消所有事件订阅
if(ActivitySubscriberManager.ActivityEventSubscribers!=null)
foreach (var subscription in ActivitySubscriberManager.ActivityEventSubscribers)
subscription?.Unsubscribe();
_sentryMap.Clear();
_dataTypeIndex.Clear();
_scopeManager?.Dispose();
}
public void Init()
{
// 显式拉起 TimeRefreshManager订阅 MinuteTick、跨天检测须在本 Init 内任意入口订阅 SecondTick 之前执行
GContext.container.Resolve<TimeRefreshManager>();
ConditionCheckerFactory.Initialize();
InitAllManage();
InitializeAllSentries();
SubscribeGlobalEvents();
CheckAllActivities();
}
/// <summary>
/// 根据类型键获取对应的 Sentry 实例
/// </summary>
public IActivitySentry GetSentry((int EventType, int EventSubType) typeKey)
{
return _sentryMap.GetValueOrDefault(typeKey);
}
public IActivitySentry GetSentry(FishingEvent fishingEvent)
{
if (fishingEvent == null) return null;
return GetSentry((fishingEvent.Type, fishingEvent.SubType));
}
public TM Resolve<TM>() where TM : AEventDataManager
{
if (!_scopeManager.IsRegistered<TM>()) return null;
// 从 _scopeManager 获取 typeKey
var typeKey = _scopeManager.GetRegisteredTypeKey<TM>();
if (typeKey == null) return null;
if (!_scopeManager.HasActiveScope(typeKey.Value)) return null;
return _scopeManager.GetOrRegisterInstance<TM>(typeKey.Value);
}
#endregion
#region
/// <summary>
/// 通过配置表反向查询物品对应的活动代币数量
/// 注意: 配置表中多个 FishingEventCycleItem 使用同一个 ItemId
/// 活动切换时 RedirectIDeventId会变化
/// </summary>
/// <returns>代币数量,如果活动未开启或未找到返回 null</returns>
public int? GetTokenCountByItemId(FishingEventCycleItem cycleItem)
{
if (cycleItem == null) return null;
// 1. 获取活动哨兵
var sentry = GetSentry((cycleItem.Type, cycleItem.SubType));
if (sentry?.CurrentEventConfig == null) return null;
// 2. 查询代币数量
var tokenService = GContext.container.Resolve<Services.IEventTokenService>();
return tokenService?.GetTokenCount(sentry.CurrentEventConfig.ID);
}
#endregion
}
}