364 lines
12 KiB
C#
364 lines
12 KiB
C#
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
|
||
/// 活动切换时 RedirectID(eventId)会变化
|
||
/// </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
|
||
}
|
||
}
|