using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Ichni.Story.UI; using Sirenix.OdinInspector; using UnityEngine; 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; private string currentLoadingDialog; [Header("Test")] public List testTextAssets; public DialogUIPage dialogUIPage; private void Awake() { instance = this; } public void SetDialog(string dialogName) { TextAsset dialog = Resources.Load("Dialogs/" + dialogName); SetDialog(new List { dialog }, "Entry"); } public void SetDialog(List dialogFiles, string dialogParagraphName) { dialogUIPage.FadeIn(); currentDialog = "NULL"; isPlayingDialog = true; LoadDialog(dialogFiles); if (!string.IsNullOrEmpty(dialogParagraphName)) { currentDialog = dialogParagraphName; } PlayNextDialogParagraph(currentDialog); } public void PlayNextDialogParagraph(string nextDialog) { currentDialog = nextDialog; currentDialogSentenceIndex = 0; if (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"; } PlayDialog(); } [Button("Test Play")] public void PlayDialog() { if(currentDialog == "NULL") { throw new Exception("Current dialog is NULL"); } /*if (dialogInterface.dialogTextFrame.isPlayingSentence) { dialogInterface.dialogTextFrame.FinishSentence(); return; }*/ if (dialogDictionary[currentDialog].Count > 0 && currentDialogSentenceIndex < dialogDictionary[currentDialog].Count) { DialogSentence currentSentence = dialogDictionary[currentDialog][currentDialogSentenceIndex]; string interpretedContent = currentSentence.GetInterpretedContent(); dialogUIPage.textFrame.PlaySentence(currentSentence.characterName, interpretedContent); currentDialogSentenceIndex++; if (currentDialogSentenceIndex <= dialogDictionary[currentDialog].Count) { return; } } if(currentDialogSentenceIndex >= dialogDictionary[currentDialog].Count) { if (currentFinalType == "Choice") { isPlayingChoice = true; dialogUIPage.choiceFrame.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) { dialogUIPage.FadeOut(); dialogUIPage.choiceFrame.gameObject.SetActive(false); //currentDialogNPC.priorStoryTexts.Remove(dialogTextAsset); //currentDialogNPC = null; isPlayingDialog = false; } } } public partial class DialogManager { public void LoadDialog(List dialogFiles) { ClearDictionaries(); 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 (var line in from line in dialogLines where !ParseHeader(line) where !ParseChoiceModule(line) where !ParseConditionModule(line) where !ParseDialogSentence(line) select line) { throw new Exception($"Invalid dialog line: {line}"); // 抛出异常,提示不合法的对话行 } //dialogDictionary.RemoveWhere((header, sentences) => sentences == null || sentences.Count == 0); choiceDictionary.RemoveWhere((header, choices) => choices == null || 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 List()); 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(emotion):sentence string[] sentenceData; if (line.Contains(":")) { sentenceData = line.Split(":", 2); } else { return false; } string character = sentenceData[0]; string speakerName = character; string emotion = "Default"; if (character.Contains("(")) { emotion = character.Split("(")[1].Replace(")", ""); speakerName = character.Split("(")[0]; } else if (character.Contains("(")) { emotion = character.Split("(")[1].Replace(")", ""); speakerName = character.Split("(")[0]; } DialogSentence dialogSentence = new DialogSentence { characterName = speakerName, characterEmotion = emotion, content = sentenceData[1] }; dialogDictionary[currentLoadingDialog].Add(dialogSentence); return true; } public bool ParseChoiceModule(string line) { //$Choice{ //choiceText0(Hint0)->[nextDialogName0]; //choiceText1(Hint1)->[nextDialogName1]; //} line = line.Trim(); if (line.Contains("Choice")) { string[] choiceModuleData = line.Split('{'); List choices = new List(); string[] choiceData = choiceModuleData[1].Split(';'); for (var index = 0; index < choiceData.Length - 1; index++) { Choice choice = new Choice { choiceText = choiceData[index].Split("->")[0].Split("(")[0].Trim(), hint = choiceData[index].Split("->")[0].Split("(")[1].Replace(")", "").Trim(), nextDialogName = choiceData[index].Split("->[")[1].Replace("]", "").Trim(), }; choices.Add(choice); } choiceDictionary[currentLoadingDialog] = choices; 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; } } }