using System.Collections.Generic; using Definition.Enum; using Event; using UI; using UnityEngine; using UnityGameFramework.Runtime; namespace CustomComponent { [DisallowMultipleComponent] public class StoryDirectorComponent : GameFrameworkComponent { [SerializeField] [Tooltip("按顺序执行。每个元素是一个 StoryDirectiveAsset 资产引用。")] private List _directives = new List(); [SerializeField] private bool _allowRepeatTrigger = false; [SerializeField] private bool _verboseLog = true; [SerializeField] private string _backgroundAssetNamePrefix = string.Empty; private readonly HashSet _consumedDirectiveTokens = new HashSet(); private BgFormController _bgFormController; private int _bgRequestVersion; private bool _isExecuting; private void Start() { GameEntry.Event.Subscribe(DialogCompletedEventArgs.EventId, OnDialogCompleted); GameEntry.Event.Subscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted); } private void OnDestroy() { GameEntry.Event.Unsubscribe(DialogCompletedEventArgs.EventId, OnDialogCompleted); GameEntry.Event.Unsubscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted); _bgFormController?.CloseUI(); _bgFormController = null; } public void ResetConsumedDirectives() { _consumedDirectiveTokens.Clear(); } private void OnDialogCompleted(object sender, GameFramework.Event.GameEventArgs e) { if (!(e is DialogCompletedEventArgs args)) { return; } ExecuteDirectives(StoryTriggerType.DialogCompleted, args.DialogId); } private void OnCombineCompleted(object sender, GameFramework.Event.GameEventArgs e) { if (!(e is CombineCompletedEventArgs)) { return; } ExecuteDirectives(StoryTriggerType.CombineCompleted, 0); } private void ExecuteDirectives(StoryTriggerType triggerType, int triggerId) { if (_isExecuting) { return; } _isExecuting = true; try { for (int i = 0; i < _directives.Count; i++) { StoryDirectiveAsset directive = _directives[i]; if (directive == null || !directive.IsMatch(triggerType, triggerId)) { continue; } string directiveToken = BuildDirectiveToken(directive, i); if (!_allowRepeatTrigger && _consumedDirectiveTokens.Contains(directiveToken)) { continue; } ExecuteDirective(directive, triggerType, triggerId); if (!_allowRepeatTrigger) { _consumedDirectiveTokens.Add(directiveToken); } } } finally { _isExecuting = false; } } private static string BuildDirectiveToken(StoryDirectiveAsset directive, int directiveIndex) { return $"{directive.GetInstanceID()}:{directiveIndex}"; } private void ExecuteDirective(StoryDirectiveAsset directive, StoryTriggerType triggerType, int triggerId) { directive.Execute(this); if (_verboseLog) { Log.Info("StoryDirector executed action '{0}' by trigger '{1}' ({2}).", directive.ActionName, triggerType.ToString(), triggerId.ToString()); } } public void ExecuteStartDialog(int dialogId) { if (dialogId <= 0) { Log.Warning("StoryDirector start dialog skipped. dialogId must be positive."); return; } if (GameEntry.Dialog == null) { Log.Warning("StoryDirector start dialog failed. Dialog component is missing."); return; } GameEntry.Dialog.StartDialog(dialogId); } public void ExecuteStartCombine(StoryCombineConfig config) { if (GameEntry.Combine == null) { Log.Warning("StoryDirector start combine failed. Combine component is missing."); return; } CombineFormContext context = BuildCombineFormContext(config); int? formSerialId = GameEntry.Combine.StartLevel(context); if (!formSerialId.HasValue) { Log.Warning("StoryDirector start combine failed. Open combine UI failed."); return; } } public void ExecuteChangeBackground(string backgroundAssetName) { string assetName = string.IsNullOrWhiteSpace(backgroundAssetName) ? string.Empty : backgroundAssetName.Trim(); if (string.IsNullOrEmpty(assetName)) { Log.Warning("StoryDirector change background skipped. assetName is empty."); return; } if (GameEntry.SpriteCache == null) { Log.Warning("StoryDirector change background failed. SpriteCache component is missing."); return; } int requestVersion = ++_bgRequestVersion; GameEntry.SpriteCache.GetSprite(_backgroundAssetNamePrefix + assetName, sprite => { OnBackgroundLoaded(requestVersion, assetName, sprite); }); } private void OnBackgroundLoaded(int requestVersion, string assetName, Sprite sprite) { if (requestVersion != _bgRequestVersion) { return; } if (sprite == null) { Log.Warning("StoryDirector change background failed. Loaded sprite is null for '{0}'.", assetName); return; } if (_bgFormController == null) { _bgFormController = new BgFormController(); } _bgFormController.OpenUI(new BgFormContext { Sprite = sprite }); } public void ExecuteEndChapter(int chapterId) { int finalChapterId = chapterId > 0 ? chapterId : (GameEntry.Dialog != null ? GameEntry.Dialog.CurrentChapterId : 0); GameEntry.Event.Fire(this, StoryChapterEndedEventArgs.Create(finalChapterId)); } private static CombineFormContext BuildCombineFormContext(StoryCombineConfig config) { if (config != null && config.Slots != null && config.Slots.Count > 0) { return new CombineFormContext { Slots = CloneSlots(config.Slots), Parts = CloneParts(config.Parts), AutoStart = config.AutoStart }; } return BuildDefaultCombineContext(); } private static List CloneSlots(List source) { var result = new List(); if (source == null) { return result; } for (int i = 0; i < source.Count; i++) { CombineSlotContext slot = source[i]; if (slot == null) { continue; } result.Add(new CombineSlotContext { RequiredPartType = slot.RequiredPartType, BuildOrder = slot.BuildOrder, RequireStrictOrder = slot.RequireStrictOrder, AnchoredPosition = slot.AnchoredPosition, SizeDelta = slot.SizeDelta, MechanicsExplanation = slot.MechanicsExplanation, MismatchHint = slot.MismatchHint }); } return result; } private static List CloneParts(List source) { var result = new List(); if (source == null) { return result; } for (int i = 0; i < source.Count; i++) { CombinePartContext part = source[i]; if (part == null) { continue; } result.Add(new CombinePartContext { PartType = part.PartType, PartDisplayName = part.PartDisplayName, MechanicsExplanation = part.MechanicsExplanation, LockAfterPlaced = part.LockAfterPlaced }); } return result; } private static CombineFormContext BuildDefaultCombineContext() { List slots = new List { new CombineSlotContext { RequiredPartType = CombinePartType.Dou, BuildOrder = 0, RequireStrictOrder = true, AnchoredPosition = new Vector2(-320f, -160f), SizeDelta = new Vector2(120f, 120f), MechanicsExplanation = "Dou transfers upper load and works as the base node." }, new CombineSlotContext { RequiredPartType = CombinePartType.Sheng, BuildOrder = 1, RequireStrictOrder = true, AnchoredPosition = new Vector2(-320f, -20f), SizeDelta = new Vector2(120f, 120f), MechanicsExplanation = "Sheng raises layer height to form the bracket hierarchy." }, new CombineSlotContext { RequiredPartType = CombinePartType.Gong, BuildOrder = 2, RequireStrictOrder = true, AnchoredPosition = new Vector2(-320f, 120f), SizeDelta = new Vector2(120f, 120f), MechanicsExplanation = "Gong spreads force laterally through overhang." }, new CombineSlotContext { RequiredPartType = CombinePartType.Qiao, BuildOrder = 3, RequireStrictOrder = true, AnchoredPosition = new Vector2(-160f, 120f), SizeDelta = new Vector2(120f, 120f), MechanicsExplanation = "Qiao continues force transfer to the outer side." }, new CombineSlotContext { RequiredPartType = CombinePartType.Ang, BuildOrder = 4, RequireStrictOrder = true, AnchoredPosition = new Vector2(0f, 120f), SizeDelta = new Vector2(120f, 120f), MechanicsExplanation = "Ang uses leverage to redirect eave load inward." } }; List parts = new List { new CombinePartContext { PartType = CombinePartType.Dou, PartDisplayName = "Dou", LockAfterPlaced = true }, new CombinePartContext { PartType = CombinePartType.Sheng, PartDisplayName = "Sheng", LockAfterPlaced = true }, new CombinePartContext { PartType = CombinePartType.Gong, PartDisplayName = "Gong", LockAfterPlaced = true }, new CombinePartContext { PartType = CombinePartType.Qiao, PartDisplayName = "Qiao", LockAfterPlaced = true }, new CombinePartContext { PartType = CombinePartType.Ang, PartDisplayName = "Ang", LockAfterPlaced = true } }; return new CombineFormContext { Slots = slots, Parts = parts, AutoStart = true }; } } }