Files
ichni_Official/Assets/Scripts/Story/Dialog/DialogManager.cs
SoulliesOfficial db4d131192 1
2025-06-06 10:14:55 -04:00

400 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<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, List<Choice>> choiceDictionary;
public Dictionary<string, List<Condition>> conditionDictionary;
private string currentLoadingDialog;
[Header("Test")]
public List<TextAsset> testTextAssets;
public DialogUIPage dialogUIPage;
private void Awake()
{
instance = this;
}
public void SetDialog(string dialogName)
{
TextAsset dialog = Resources.Load<TextAsset>("Dialogs/" + dialogName);
SetDialog(new List<TextAsset> { dialog }, "Entry");
}
public void SetDialog(List<TextAsset> 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<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";
}
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<TextAsset> dialogFiles)
{
ClearDictionaries();
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 (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);
}
/// <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 List<Choice>());
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(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<Choice> choices = new List<Choice>();
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;
}
/// <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;
}
}
}