using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Ichni.Story.UI; using Sirenix.OdinInspector; using SLSUtilities.General; using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; namespace Ichni.Story { public partial class DialogManager : SerializedMonoBehaviour { public static DialogManager instance; public List dialogTextAssets; public bool isPlayingDialog; public bool isPlayingChoice; public string currentDialog; public int currentDialogSentenceIndex; public string currentFinalType; public Dictionary> functionDictionary; public Dictionary> dialogDictionary; public Dictionary choiceDictionary; public Dictionary> conditionDictionary; public List dialogEndActions; private string currentLoadingDialog; public DialogUIPage dialogUIPage; private void Awake() { instance = this; } public void SetDialog(string dialogName) { string chapter = ChapterSelectionManager.instance.currentChapter.chapterIndex; TextAsset dialog = Resources.Load("Story/" + chapter + "/Dialogs/" + dialogName); dialogUIPage.dialogContentFrame.ClearAllSentences(); SetDialog(new List { dialog }); } public void SetDialog(List dialogFiles, string dialogParagraphName = "") { dialogUIPage.FadeIn(); isPlayingDialog = true; currentDialog = "NULL"; dialogEndActions = new List(); LoadDialog(dialogFiles, out string firstHeader); currentDialog = dialogParagraphName == "" ? firstHeader : dialogParagraphName; } public void PlayNextDialogParagraph(string nextDialog, bool invokeFunctions = true) { currentDialog = nextDialog; currentDialogSentenceIndex = 0; if (invokeFunctions && functionDictionary.TryGetValue(currentDialog, out List functionList)) { functionList.ForEach(x => StoryInterpreters.FunctionInterpreter.Eval(x)); } if (choiceDictionary.ContainsKey(currentDialog)) { currentFinalType = "Choice"; } else if (conditionDictionary.ContainsKey(currentDialog)) { currentFinalType = "Condition"; } else { currentFinalType = "None"; } } public void PlayDialog() { if(currentDialog == "NULL") { throw new Exception("Current dialog is NULL"); } if (isPlayingChoice) { return; } if (dialogDictionary[currentDialog].Count > 0 && currentDialogSentenceIndex < dialogDictionary[currentDialog].Count) { DialogSentence currentSentence = dialogDictionary[currentDialog][currentDialogSentenceIndex]; string interpretedContent = currentSentence.GetInterpretedContent(); dialogUIPage.dialogContentFrame.PlaySentence(currentSentence.characterName, interpretedContent); currentDialogSentenceIndex++; if (currentDialogSentenceIndex <= dialogDictionary[currentDialog].Count) { return; } } if(currentDialogSentenceIndex >= dialogDictionary[currentDialog].Count) { if (currentFinalType == "Choice") { isPlayingChoice = true; dialogUIPage.dialogContentFrame.PlayChoice(choiceDictionary[currentDialog]); return; } if (currentFinalType == "Condition") { foreach (var condition in conditionDictionary[currentDialog]) { if (condition.GetConditionResult()) { PlayNextDialogParagraph(condition.nextDialogName); return; } } } } if (currentFinalType == "None" && currentDialogSentenceIndex >= dialogDictionary[currentDialog].Count) { isPlayingDialog = false; dialogUIPage.FadeOut(); if (StoryManager.instance.storyline.currentBlock.state == StoryBlockState.Current) { StoryManager.instance.storyline.currentBlock.state = StoryBlockState.Completed; StoryManager.instance.storyUIPage.messageBox.Clear(); dialogEndActions.ForEach(action => action.Invoke()); StoryManager.instance.storyUIPage.messageBox.SetUp(); StoryManager.instance.storyline.SaveStoryline(ChapterSelectionManager.instance.currentChapter.chapterIndex); Debug.Log("Dialog completed, setting block state to Completed."); } } } public void RevealDialog() { string finalType; int max = 0; Debug.Log($"Revealing dialog: {currentDialog}, currentFinalType: {currentFinalType}"); do { finalType = currentFinalType; currentDialogSentenceIndex = 0; foreach (DialogSentence sentence in dialogDictionary[currentDialog]) { string interpretedContent = sentence.GetInterpretedContent(); dialogUIPage.dialogContentFrame.PlaySentence(sentence.characterName, interpretedContent); currentDialogSentenceIndex++; } if (finalType == "Choice") { ChoiceGroup choiceGroup = choiceDictionary[currentDialog]; int choiceIndex = GameSaveManager.instance.StorySaveModule.selectedChoices[choiceGroup.choiceName]; dialogUIPage.dialogContentFrame.SelectChoice(choiceGroup, choiceIndex); } if (finalType == "Condition") { foreach (var condition in conditionDictionary[currentDialog]) { if (condition.GetConditionResult()) { PlayNextDialogParagraph(condition.nextDialogName, false); } } } max++; if (max > 1024) { throw new Exception("An infinite loop may detected in dialog parsing. Please check the dialog structure."); } } while (finalType != "None"); } } public partial class DialogManager { public void LoadDialog(List dialogFiles, out string firstHeader) { ClearDictionaries(); firstHeader = string.Empty; dialogTextAssets = dialogFiles; List dialogLines = new List(); foreach (TextAsset textAsset in dialogTextAssets) { dialogLines.AddRange(ExtractValidFragments(textAsset.text)); } dialogLines.RemoveAll(line => line.Trim() == ""); //dialogLines.ForEach(Debug.Log); foreach (string line in dialogLines) { if (!ParseHeader(line)) { if (!ParseChoiceModule(line)) { if (!ParseConditionModule(line)) { if (!ParseDialogSentence(line)) { throw new Exception($"Invalid dialog line: {line}"); // 抛出异常,提示不合法的对话行 } } } } else { if (firstHeader == string.Empty) { firstHeader = currentDialog; } } } //dialogDictionary.RemoveWhere((header, sentences) => sentences == null || sentences.Count == 0); choiceDictionary.RemoveWhere((header, choices) => choices == null || choices.choices.Count == 0); conditionDictionary.RemoveWhere((header, conditions) => conditions == null || conditions.Count == 0); } /// /// 从原始大文本中提取所有以 '$' 开头的有效片段,忽略以 '#' 开头的注释片段。 /// 拆分依据:每当遇到 '$' 或 '#' 字符,即视为一个新片段的起始。 /// /// 未分割的完整文本(可能包含任意换行或连续内容)。 /// 剥离首 '$' 后的有效文本列表。 public static List ExtractValidFragments(string inputText) { if (inputText == null) { throw new ArgumentNullException(nameof(inputText)); } // 正则:(?[$#]) // 片段起始前缀 // (?.*? ) // 非贪婪捕获所有内容 // (?=(?:[$#])|\z) // 直到下一个 '$'、'#' 或文末 const string pattern = @"(?[$#])(?.*?)(?=(?:[$#])|\z)"; MatchCollection matches = Regex.Matches(inputText, pattern, RegexOptions.Singleline); var result = new List(matches.Count); foreach (Match m in matches) { char prefix = m.Groups["prefix"].Value[0]; string content = m.Groups["content"].Value; if (prefix == '$') { result.Add(content.Trim()); } // prefix == '#' 时自动忽略 } return result; } } public partial class DialogManager { public void ClearDictionaries() { dialogDictionary.Clear(); choiceDictionary.Clear(); conditionDictionary.Clear(); functionDictionary.Clear(); } public bool ParseHeader(string line) { //格式:[currentLoadingDialog]{Function0();Function1();Function2();} line = line.Trim(); string dialogTitle = line.Split("{")[0]; if (dialogTitle[0] != '[' || dialogTitle[^1] != ']') { return false; } currentLoadingDialog = dialogTitle.Replace("[", "").Replace("]", ""); dialogDictionary.Add(currentLoadingDialog, new List()); //choiceDictionary.Add(currentLoadingDialog, new ChoiceGroup("Error")); conditionDictionary.Add(currentLoadingDialog, new List()); if (currentDialog == "NULL") { currentDialog = currentLoadingDialog; } if (!line.Contains("{")) // 这个Header没有函数需要执行 { return true; } string functions = line.Split("{")[1]; if (functions.Contains("}")) { functions = functions.Split("}")[0]; } else { throw new System.Exception("Dialog header's function list must be enclosed in {}."); } functions = functions.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim(); //忽略空格,换行 List functionList = functions.Split(';').ToList(); //分割函数 functionList = functionList.Where(x => !string.IsNullOrEmpty(x)).ToList(); //去除空函数 functionDictionary.Add(currentLoadingDialog, functionList); return true; } public bool ParseDialogSentence(string line) { //speakerName:sentence string[] sentenceData; if (line.Contains(":")) { sentenceData = line.Split(":", 2); } else { return false; } string character = sentenceData[0]; string speakerName = character; DialogSentence dialogSentence = new DialogSentence { characterName = speakerName.Trim(), content = sentenceData[1].Trim() }; dialogDictionary[currentLoadingDialog].Add(dialogSentence); return true; } public bool ParseChoiceModule(string line) { //$Choice(ChoiceName){ //choiceText0->[nextDialogName0]; //choiceText1->[nextDialogName1]; //} line = line.Trim(); if (line.Contains("Choice")) { string[] choiceModuleData = line.Split('{'); string choiceName = choiceModuleData[0].Split('(')[1].Replace(")", "").Trim(); ChoiceGroup choiceGroup = new ChoiceGroup(choiceName); string[] choiceData = choiceModuleData[1].Split(';'); for (var index = 0; index < choiceData.Length - 1; index++) { choiceData[index] = choiceData[index].Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim(); string choiceText = choiceData[index].Split("->[")[0].Trim(); string nextDialogName = choiceData[index].Split("->[")[1].Replace("]", "").Trim(); choiceGroup.choices.Add(new Choice(choiceText, nextDialogName)); } choiceDictionary[currentLoadingDialog] = choiceGroup; return true; } return false; } /// /// 解析条件模块 /// /// /// private bool ParseConditionModule(string line) { //$Condition{ //conditionSentence0->[nextDialogName0]; //conditionSentence1->[nextDialogName1]; //} if (line.Contains("Condition")) { string[] conditionModuleData = line.Split('{'); List conditions = new List(); string[] conditionData = conditionModuleData[1].Split(';'); for (var index = 0; index < conditionData.Length - 1; index++) { conditionData[index] = conditionData[index].Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim(); Condition condition = new Condition { conditionSentence = conditionData[index].Split("->[")[0].Trim(), nextDialogName = conditionData[index].Split("->[")[1].Replace("]", "").Trim(), }; conditions.Add(condition); } conditionDictionary[currentLoadingDialog] = conditions; return true; } return false; } } }