using System; using System.Collections; using BaseGames.Core; using BaseGames.Core.Events; using UnityEngine; namespace BaseGames.EventChain { // ===================================================================== // EventChainSO —— 世界事件链数据(架构 14_NarrativeModule §9) // ===================================================================== /// /// 世界事件链:描述"当全部 conditions 满足时,依序执行 actions"。 /// 策划纯数据配置,无需程序员介入。 /// 资产路径:Assets/ScriptableObjects/EventChains/Chain_{Context}.asset /// [CreateAssetMenu(menuName = "BaseGames/EventChain/EventChain")] public class EventChainSO : ScriptableObject { [Header("基础")] public string chainId; // 全局唯一,如 "Chain_BossForest_Defeated" public bool repeatable; // false = 只触发一次(触发后存档记录) [Min(0f)] public float actionDelay = 0f; // 各 action 之间的延迟(秒),0 = 紧接着执行 [Header("触发条件(全部满足才触发)")] [Tooltip("旧版条件列表(隐式 And 逻辑)。新配置推荐改用 conditionGroups。\n" + "conditionGroups 非空时,此字段被忽略。")] public ChainCondition[] conditions; [Tooltip("条件组列表(新版)。每组内部支持 And / Or 逻辑,多组之间为 And 关系(全部组满足才触发)。\n" + "此字段非空时,旧版 conditions 字段将被忽略。")] public ConditionGroup[] conditionGroups; [Header("执行动作(顺序执行)")] public ChainAction[] actions; } // ===================================================================== // ChainCondition 抽象基类 + 内置实现 // ===================================================================== /// /// 事件链触发条件抽象基类(Strategy + Observer 混合)。 /// Register/Unregister 向 EventChainManager 的中继 C# 事件挂钩, /// IsMet() 被 EvaluateAll() 调用以检验是否满足触发条件。 /// public abstract class ChainCondition : ScriptableObject { public abstract void Register(EventChainManager manager); public abstract void Unregister(EventChainManager manager); public abstract bool IsMet(); /// /// 重置运行时瞬态状态(每次 EventChainManager.OnEnable 时调用)。 /// ScriptableObject 是资产,_met 等字段会跨 PlayMode 会话残留; /// 显式重置确保每次进入游戏/切换场景时条件均从初始状态开始评估。 /// public virtual void ResetState() { } /// /// 声明此条件关心哪类运行时事件。 /// EventChainManager 在构建链桶时使用此掩码,使评估仅在相关事件到来时触发, /// 跳过无关事件,降低 EvaluateAll 的无效迭代次数。 /// 默认返回 Any(适配自定义条件:任何事件均触发评估)。 /// public virtual ChainEventMask RelevantEvents => ChainEventMask.Any; } /// /// 位掩码:标识事件链条件关心的运行时事件类别。 /// 用于 EventChainManager 构建懒评估桶,减少每次事件触发时的无关链扫描。 /// [System.Flags] public enum ChainEventMask { None = 0, Boss = 1 << 0, Collectible = 1 << 1, Ability = 1 << 2, Room = 1 << 3, Dialogue = 1 << 4, FlagChanged = 1 << 5, Chain = 1 << 6, /// 不区分事件类别;任何事件均触发评估(自定义条件的默认值)。 Any = -1, } // ─── 内置条件 ────────────────────────────────────────────────────────── [CreateAssetMenu(menuName = "BaseGames/EventChain/Condition/BossDefeated")] public class BossDefeatedCondition : ChainCondition { public string bossId; [System.NonSerialized] private bool _met; public override void Register(EventChainManager m) => m.OnBossDefeated += Check; public override void Unregister(EventChainManager m) => m.OnBossDefeated -= Check; public override bool IsMet() => _met; public override void ResetState() => _met = false; public override ChainEventMask RelevantEvents => ChainEventMask.Boss; private void Check(string id) { if (id == bossId) _met = true; } } [CreateAssetMenu(menuName = "BaseGames/EventChain/Condition/FlagSet")] public class FlagSetCondition : ChainCondition { public string flagId; [System.NonSerialized] private bool _met; [System.NonSerialized] private bool _initialized; // 延迟初始化标记 public override void Register(EventChainManager m) { // 订阅事件;实际标志值延迟到首次 IsMet() 调用时从 SaveService 读取, // 避免 Register 早于 SaveService 注册时读取失败 m.OnWorldFlagChanged += OnFlagChanged; } public override void Unregister(EventChainManager m) { m.OnWorldFlagChanged -= OnFlagChanged; } public override bool IsMet() { if (!_initialized) { var sm = ServiceLocator.GetOrDefault(); if (sm != null) { _met = sm.GetFlag(flagId); _initialized = true; } #if UNITY_EDITOR || DEVELOPMENT_BUILD else Debug.LogWarning( $"[FlagSetCondition] 标志 '{flagId}' 首次 IsMet() 时 ISaveService 尚未注册," + "返回 false(将在 SaveService 注册后通过 OnWorldFlagChanged 更新)。"); #endif } return _met; } public override void ResetState() { _met = false; _initialized = false; } public override ChainEventMask RelevantEvents => ChainEventMask.FlagChanged; private void OnFlagChanged(string changedFlagId) { if (changedFlagId != flagId) return; var sm = ServiceLocator.GetOrDefault(); if (sm != null) { _met = sm.GetFlag(flagId); _initialized = true; } } } [CreateAssetMenu(menuName = "BaseGames/EventChain/Condition/AbilityUnlocked")] public class AbilityUnlockedCondition : ChainCondition { public string abilityId; [System.NonSerialized] private bool _met; public override void Register(EventChainManager m) => m.OnAbilityUnlocked += Check; public override void Unregister(EventChainManager m) => m.OnAbilityUnlocked -= Check; public override bool IsMet() => _met; public override void ResetState() => _met = false; public override ChainEventMask RelevantEvents => ChainEventMask.Ability; private void Check(string id) { if (id == abilityId) _met = true; } } [CreateAssetMenu(menuName = "BaseGames/EventChain/Condition/CollectibleCollected")] public class CollectibleCollectedCondition : ChainCondition { public string itemId; [System.NonSerialized] private bool _met; public override void Register(EventChainManager m) => m.OnCollectiblePickedUp += Check; public override void Unregister(EventChainManager m) => m.OnCollectiblePickedUp -= Check; public override bool IsMet() => _met; public override void ResetState() => _met = false; public override ChainEventMask RelevantEvents => ChainEventMask.Collectible; private void Check(string id) { if (id == itemId) _met = true; } } [CreateAssetMenu(menuName = "BaseGames/EventChain/Condition/RoomEntered")] public class RoomEnteredCondition : ChainCondition { public string sceneName; [System.NonSerialized] private bool _met; public override void Register(EventChainManager m) => m.OnRoomEntered += Check; public override void Unregister(EventChainManager m) => m.OnRoomEntered -= Check; public override bool IsMet() => _met; public override void ResetState() => _met = false; public override ChainEventMask RelevantEvents => ChainEventMask.Room; private void Check(string id) { if (id == sceneName) _met = true; } } [CreateAssetMenu(menuName = "BaseGames/EventChain/Condition/DialogueCompleted")] public class DialogueCompletedCondition : ChainCondition { public string npcId; [System.NonSerialized] private bool _met; public override void Register(EventChainManager m) => m.OnDialogueCompleted += Check; public override void Unregister(EventChainManager m) => m.OnDialogueCompleted -= Check; public override bool IsMet() => _met; public override void ResetState() => _met = false; public override ChainEventMask RelevantEvents => ChainEventMask.Dialogue; private void Check(string id) { if (id == npcId) _met = true; } } [CreateAssetMenu(menuName = "BaseGames/EventChain/Condition/ChainCompleted")] public class ChainCompletedCondition : ChainCondition { public string chainId; [System.NonSerialized] private bool _met; public override void Register(EventChainManager m) => m.OnChainCompleted += Check; public override void Unregister(EventChainManager m) => m.OnChainCompleted -= Check; public override bool IsMet() => _met; public override void ResetState() => _met = false; public override ChainEventMask RelevantEvents => ChainEventMask.Chain; private void Check(string id) { if (id == chainId) _met = true; } } // ===================================================================== // ConditionGroup ── 支持 And/Or 逻辑的条件分组 // ===================================================================== /// /// 条件组:将多个 以 And 或 Or 逻辑组合。 /// 多个条件组之间始终为 And 关系(所有组均须满足)。 /// 在 中配置,替代旧版隐式 And 的 conditions[]。 /// [System.Serializable] public class ConditionGroup { [Tooltip("组内条件的逻辑关系:\n And(默认)= 全部条件均须满足\n Or = 任意一个条件满足即可触发本组通过")] public WorldStateFlagLogic logic = WorldStateFlagLogic.And; [Tooltip("本组的条件列表。")] public ChainCondition[] conditions; } // ===================================================================== // ChainAction 抽象基类 + 内置实现 // ===================================================================== /// /// 事件链执行动作抽象基类。ExecuteAsync 可即时返回或协程等待。 /// public abstract class ChainAction : ScriptableObject { public abstract IEnumerator ExecuteAsync(MonoBehaviour runner); } // ─── 内置动作 ────────────────────────────────────────────────────────── /// 打开门(发布 EVT_DoorOpened 事件)。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/OpenDoor")] public class OpenDoorAction : ChainAction { public string doorId; [SerializeField] private StringEventChannelSO _onDoorOpened; // EVT_DoorOpened public override IEnumerator ExecuteAsync(MonoBehaviour runner) { _onDoorOpened?.Raise(doorId); yield break; } } /// 设置/清除存档标志。设置后通知 EventChainManager 触发条件重评估。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/SetFlag")] public class SetFlagAction : ChainAction { [Tooltip("世界状态标志 Key(字符串),由 ISaveService 持久化。")] public string flagId; public bool value = true; #if UNITY_EDITOR private void OnValidate() { if (string.IsNullOrWhiteSpace(flagId)) Debug.LogWarning( $"[SetFlagAction] '{name}': flagId 为空,执行时将静默失败。请填写有效的 flagId。", this); } #endif public override IEnumerator ExecuteAsync(MonoBehaviour runner) { var saveService = ServiceLocator.GetOrDefault(); if (saveService == null) { Debug.LogError($"[SetFlagAction] ISaveService 未注册,标志 '{flagId}' 无法持久化。", this); yield break; } saveService.SetFlag(flagId, value); // 通知 EventChainManager 标志已变更,触发 FlagSetCondition 重新评估(事件驱动,无需轮询) if (runner is EventChainManager ecm) ecm.NotifyFlagChanged(flagId); yield break; } } /// 通知地图显示/标记区域(通过事件频道解耦,无直接 MapManager 依赖)。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/UpdateMap")] public class UpdateMapAction : ChainAction { public string regionId; [SerializeField] private StringEventChannelSO _onRevealRegion; // EVT_RevealRegion → MapManager 订阅 public override IEnumerator ExecuteAsync(MonoBehaviour runner) { _onRevealRegion?.Raise(regionId); yield break; } } /// 播放过场动画,等待其结束再继续链。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/PlayCutscene")] public class PlayCutsceneAction : ChainAction { public string cutsceneId; [SerializeField] private StringEventChannelSO _onPlayCutscene; // → CutsceneManager.PlayById [SerializeField] private VoidEventChannelSO _onCutsceneEnded; // ← CutsceneManager 播完后 Raise [Tooltip("等待过场动画结束的超时时间(秒)。超时后记录错误并继续执行事件链,避免链永久挂起。\n0 = 永不超时(不推荐,可能导致链死锁)。")] [Min(0f)] [SerializeField] private float timeoutSeconds = 60f; public override IEnumerator ExecuteAsync(MonoBehaviour runner) { bool done = false; // 用 try/finally 确保即使协程被强制停止(StopAllCoroutines)时也能正确退订 var sub = _onCutsceneEnded != null ? _onCutsceneEnded.Subscribe(() => done = true) : default(EventSubscription); try { _onPlayCutscene?.Raise(cutsceneId); if (timeoutSeconds > 0f) { // 超时保护:防止 CutsceneManager 未触发 Ended 事件导致链永久挂起 float elapsed = 0f; while (!done && elapsed < timeoutSeconds) { elapsed += UnityEngine.Time.deltaTime; yield return null; } if (!done) Debug.LogError( $"[PlayCutsceneAction] 过场动画 '{cutsceneId}' 等待超时({timeoutSeconds}s)。" + "请确认 CutsceneManager 在动画结束后调用了 _onCutsceneEnded.Raise()。"); } else { yield return new WaitUntil(() => done); } } finally { sub.Dispose(); } } } /// 切换 NPC 对话(通过 EVT_NPCDialogueChange 强类型事件广播,NPC 自行响应)。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/ChangeNPCDialogue")] public class ChangeNPCDialogueAction : ChainAction { [Tooltip("目标 NPC 的唯一 ID(对应 NpcSO.npcId)。")] public string npcId; [Tooltip("要切换到的对话序列 ID(对应 DialogueSequenceSO.sequenceId)。")] public string newSequenceId; /// /// 强类型事件频道(NpcDialogueChangeEventChannelSO)。 /// NPC 组件订阅后根据 npcId 字段过滤,无需 Split 字符串。 /// 资产:Assets/ScriptableObjects/Events/EVT_NpcDialogueChange.asset /// [SerializeField] private BaseGames.Core.Events.NpcDialogueChangeEventChannelSO _onNPCDialogueChange; public override IEnumerator ExecuteAsync(MonoBehaviour runner) { _onNPCDialogueChange?.Raise(new BaseGames.Core.Events.NpcDialogueChangeEvent { npcId = npcId, newSequenceId = newSequenceId }); yield break; } } /// 在场景中生成一个预制体。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/SpawnObject")] public class SpawnObjectAction : ChainAction { public GameObject prefab; public Vector3 position; public override IEnumerator ExecuteAsync(MonoBehaviour runner) { if (prefab != null) UnityEngine.Object.Instantiate(prefab, position, Quaternion.identity); yield break; } } /// 等待指定秒数。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/Wait")] public class WaitAction : ChainAction { [Min(0f)] public float seconds; public override IEnumerator ExecuteAsync(MonoBehaviour runner) { yield return new WaitForSeconds(seconds); } } /// 发布一个无负载 Void 事件频道。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/RaiseEvent")] public class RaiseEventAction : ChainAction { [SerializeField] private VoidEventChannelSO _eventChannel; public override IEnumerator ExecuteAsync(MonoBehaviour runner) { _eventChannel?.Raise(); yield break; } } /// 解锁能力(通过 EVT_AbilityUnlocked 事件广播,PlayerStats 响应)。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/UnlockAbility")] public class UnlockAbilityAction : ChainAction { public string abilityId; [SerializeField] private StringEventChannelSO _onAbilityUnlock; // EVT_AbilityUnlocked public override IEnumerator ExecuteAsync(MonoBehaviour runner) { _onAbilityUnlock?.Raise(abilityId); yield break; } } /// 切换背景音乐(通过 EVT_PlayBGM 事件广播)。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/PlayAudio")] public class PlayAudioAction : ChainAction { public string bgmKey; [SerializeField] private StringEventChannelSO _onPlayBGM; // EVT_PlayBGM public override IEnumerator ExecuteAsync(MonoBehaviour runner) { _onPlayBGM?.Raise(bgmKey); yield break; } } }