using System; using System.Collections; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Dialogue; 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("触发条件(全部满足才触发)")] public ChainCondition[] conditions; [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() { } } // ─── 内置条件 ────────────────────────────────────────────────────────── [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; private void Check(string id) { if (id == bossId) _met = true; } } [CreateAssetMenu(menuName = "BaseGames/EventChain/Condition/FlagSet")] public class FlagSetCondition : ChainCondition { public string flagId; public override void Register(EventChainManager m) { } // 持续轮询,无需订阅 public override void Unregister(EventChainManager m) { } public override bool IsMet() { var sm = ServiceLocator.GetOrDefault(); return sm != null && sm.GetFlag(flagId); } } [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; 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; 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; 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; 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; private void Check(string id) { if (id == chainId) _met = true; } } // ===================================================================== // 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; } } /// 设置/清除存档标志。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/SetFlag")] public class SetFlagAction : ChainAction { [WorldStateFlag] public string flagId; public bool value = true; 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); 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 public override IEnumerator ExecuteAsync(MonoBehaviour runner) { bool done = false; var sub = _onCutsceneEnded != null ? _onCutsceneEnded.Subscribe(() => done = true) : default(EventSubscription); _onPlayCutscene?.Raise(cutsceneId); yield return new WaitUntil(() => done); sub.Dispose(); } } /// 切换 NPC 对话(通过 EVT_NPCDialogueChange 广播,NPC 自行响应)。 [CreateAssetMenu(menuName = "BaseGames/EventChain/Action/ChangeNPCDialogue")] public class ChangeNPCDialogueAction : ChainAction { public string npcId; public string newSequenceId; [SerializeField] private StringEventChannelSO _onNPCDialogueChange; // EVT_NPCDialogueChange → NPC 订阅 public override IEnumerator ExecuteAsync(MonoBehaviour runner) { _onNPCDialogueChange?.Raise($"{npcId}:{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; } } }