using System.Collections.Generic;
using System.Reflection;
using Ichni.Story.UI;
using UnityEngine;
using UnityEngine.Events;
using Yarn.Unity;
using Yarn.Unity.UnityLocalization;
namespace Ichni.Story.Dialogue
{
///
/// 故事树 block 与 Yarn 之间的桥接控制器。
/// 点击文本块 → 启动对应 Yarn 节点;对话结束 → 持久化剧情变量、标记 block 完成、
/// 重算解锁并存档、依次执行结束回调(如歌曲解锁提示,阶段 4 使用)。
///
public class StoryDialogueController : MonoBehaviour
{
public static StoryDialogueController instance;
[Header("References")]
[Tooltip("驱动 Yarn 对话的 DialogueRunner。")]
public DialogueRunner dialogueRunner;
[Tooltip("故事树页面:对话进行时淡出、结束后淡入(可选)。")]
public StoryUIPage storyUIPage;
///
/// 对话结束后依次执行并清空的回调队列。供 unlock_song 等 Yarn 命令(阶段 4)
/// 把"解锁提示"排队到对话结束后弹出。
///
[System.NonSerialized] public List dialogueEndActions = new List();
// 当前正在播放对话的 block id;对话结束时据此标记完成
private string _activeBlockId;
private void Awake()
{
instance = this;
EnsureUnityLocalisedLineProvider();
}
private void OnEnable()
{
if (dialogueRunner != null && dialogueRunner.onDialogueComplete != null)
dialogueRunner.onDialogueComplete.AddListener(HandleDialogueComplete);
}
private void OnDisable()
{
if (dialogueRunner != null && dialogueRunner.onDialogueComplete != null)
dialogueRunner.onDialogueComplete.RemoveListener(HandleDialogueComplete);
}
///
/// 播放某文本块对应的 Yarn 节点。若对话已在进行、引用缺失或节点名为空则忽略。
///
public void PlayBlock(TextBlockView block)
{
if (block == null) return;
if (dialogueRunner == null)
{
Debug.LogError("[StoryDialogueController] 未配置 DialogueRunner,无法开始对话。");
return;
}
if (dialogueRunner.IsDialogueRunning)
{
Debug.LogWarning("[StoryDialogueController] 已有对话在进行中,忽略本次点击。");
return;
}
if (string.IsNullOrEmpty(block.YarnNodeName))
{
Debug.LogWarning($"[StoryDialogueController] block '{block.blockId}' 未配置 yarnNodeName,无法开始对话。");
return;
}
// 将 DialogueRunner 的 YarnProject 对齐到当前章节(支持多章节共用一个 Runner)
EnsureProjectForCurrentChapter();
_activeBlockId = block.blockId;
if (storyUIPage != null)
storyUIPage.FadeOut();
// StartDialogue 返回 YarnTask;此处即发即忘,完成由 onDialogueComplete 处理
dialogueRunner.StartDialogue(block.YarnNodeName).Forget();
}
// 将 DialogueRunner 的 YarnProject 切换为当前已构建章节的 StoryData.yarnProject(若不同且未在运行)
private void EnsureProjectForCurrentChapter()
{
StoryTreeController tree = StoryManager.instance != null ? StoryManager.instance.treeController : null;
StoryData data = tree != null ? tree.ActiveStoryData : null;
if (data == null || data.yarnProject == null) return;
if (dialogueRunner.YarnProject != data.yarnProject && !dialogueRunner.IsDialogueRunning)
dialogueRunner.SetProject(data.yarnProject);
}
///
/// DialogueRunner.lineProvider 为 [SerializeReference] internal 字段。
/// Inspector 的 "Use Unity Localised Line Provider" 按钮有时无法将 MonoBehaviour
/// 引用正确持久化到该字段;此方法在运行时用反射补救,确保使用正确的 Provider。
///
private void EnsureUnityLocalisedLineProvider()
{
if (dialogueRunner == null) return;
FieldInfo field = typeof(DialogueRunner).GetField(
"lineProvider",
BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null) return;
// 已经是 UnityLocalisedLineProvider 则无需处理
if (field.GetValue(dialogueRunner) is UnityLocalisedLineProvider) return;
UnityLocalisedLineProvider provider =
dialogueRunner.GetComponent();
if (provider != null)
{
field.SetValue(dialogueRunner, provider);
Debug.Log("[StoryDialogueController] Assigned UnityLocalisedLineProvider to DialogueRunner.lineProvider.");
}
else
{
Debug.LogWarning("[StoryDialogueController] UnityLocalisedLineProvider not found on DialogueRunner's GameObject. Localized line text will be unavailable.");
}
}
private void HandleDialogueComplete()
{
// 持久化对话过程中写入的全局剧情变量
if (GameSaveManager.instance != null && GameSaveManager.instance.StorySaveModule != null)
GameSaveManager.instance.StorySaveModule.SaveVariables();
// 标记 block 完成 → 重算解锁 → 存档(OnBlockCompleted 内部已去重并保存章节进度)
if (!string.IsNullOrEmpty(_activeBlockId) &&
StoryManager.instance != null && StoryManager.instance.treeController != null)
{
StoryManager.instance.treeController.OnBlockCompleted(_activeBlockId);
}
_activeBlockId = null;
// 依次执行并清空结束回调(如歌曲解锁提示)
if (dialogueEndActions.Count > 0)
{
List pending = new List(dialogueEndActions);
dialogueEndActions.Clear();
foreach (UnityAction action in pending)
action?.Invoke();
}
if (storyUIPage != null)
storyUIPage.FadeIn();
}
}
}