Files
ichni_Official/Assets/Scripts/Story/Dialog/DialogManager.cs
SoulliesOfficial 7580c4d87c 大更
2026-03-14 03:13:10 -04:00

447 lines
16 KiB
C#

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<TextAsset> dialogTextAssets;
public bool isPlayingDialog;
public bool isPlayingChoice;
public string currentDialog;
public int currentDialogSentenceIndex;
public string currentFinalType;
public Dictionary<string, List<string>> functionDictionary;
public Dictionary<string, List<DialogSentence>> dialogDictionary;
public Dictionary<string, ChoiceGroup> choiceDictionary;
public Dictionary<string, List<Condition>> conditionDictionary;
public List<UnityAction> 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<TextAsset>("Story/" + chapter + "/Dialogs/" + dialogName);
dialogUIPage.dialogContentFrame.ClearAllSentences();
SetDialog(new List<TextAsset> { dialog });
}
public void SetDialog(List<TextAsset> dialogFiles, string dialogParagraphName = "")
{
dialogUIPage.FadeIn();
isPlayingDialog = true;
currentDialog = "NULL";
dialogEndActions = new List<UnityAction>();
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<string> 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<TextAsset> dialogFiles, out string firstHeader)
{
ClearDictionaries();
firstHeader = string.Empty;
dialogTextAssets = dialogFiles;
List<string> dialogLines = new List<string>();
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);
}
/// <summary>
/// 从原始大文本中提取所有以 '$' 开头的有效片段,忽略以 '#' 开头的注释片段。
/// 拆分依据:每当遇到 '$' 或 '#' 字符,即视为一个新片段的起始。
/// </summary>
/// <param name="inputText">未分割的完整文本(可能包含任意换行或连续内容)。</param>
/// <returns>剥离首 '$' 后的有效文本列表。</returns>
public static List<string> ExtractValidFragments(string inputText)
{
if (inputText == null)
{
throw new ArgumentNullException(nameof(inputText));
}
// 正则:(?<prefix>[$#]) // 片段起始前缀
// (?<content>.*? ) // 非贪婪捕获所有内容
// (?=(?:[$#])|\z) // 直到下一个 '$'、'#' 或文末
const string pattern = @"(?<prefix>[$#])(?<content>.*?)(?=(?:[$#])|\z)";
MatchCollection matches = Regex.Matches(inputText, pattern, RegexOptions.Singleline);
var result = new List<string>(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<DialogSentence>());
//choiceDictionary.Add(currentLoadingDialog, new ChoiceGroup("Error"));
conditionDictionary.Add(currentLoadingDialog, new List<Condition>());
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<string> 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;
}
/// <summary>
/// 解析条件模块
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
private bool ParseConditionModule(string line)
{
//$Condition{
//conditionSentence0->[nextDialogName0];
//conditionSentence1->[nextDialogName1];
//}
if (line.Contains("Condition"))
{
string[] conditionModuleData = line.Split('{');
List<Condition> conditions = new List<Condition>();
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;
}
}
}