// DialogueGraph.cs
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "DialogueGraph", menuName = "Dialogue/DialogueGraph")]
public class DialogueGraph : ScriptableObject
{
    public List<DialogueNodeData> nodes = new List<DialogueNodeData>();
    public List<DialogueConnection> connections = new List<DialogueConnection>();
}

// DialogueNodeData.cs
using System;
using UnityEngine;

[Serializable]
public abstract class DialogueNodeData
{
    public string id = Guid.NewGuid().ToString();
    public Vector2 position;
}

// StartNodeData.cs
using System;

[Serializable]
public class StartNodeData : DialogueNodeData
{
}

// DialogueNodeDataWithContent.cs
using System;
using UnityEngine;

[Serializable]
public class DialogueNodeDataWithContent : DialogueNodeData
{
    public string text;
    public AudioClip audioClip;
    public List<TextEffectData> textEffects = new List<TextEffectData>();
    public List<ActionData> actions = new List<ActionData>();
}

[Serializable]
public class TextEffectData
{
    public TextEffectType effectType;
    public EffectParameters parameters;
}

[Serializable]
public class EffectParameters
{
    // Общие параметры для эффектов
    public float duration = 1f;
    public float intensity = 1f;

    // Специфичные параметры для некоторых эффектов
    public float speed = 1f;
    public Color color = Color.white;
    public Vector3 direction = Vector3.up;
    public string animationName;
}

// ConditionNodeData.cs
using System;

[Serializable]
public class ConditionNodeData : DialogueNodeData
{
    public ConditionType conditionType;
    public List<Condition> conditions = new List<Condition>();
    public LogicalOperator logicalOperator;
}

[Serializable]
public class Condition
{
    public string key;
    public bool value;
}

public enum ConditionType
{
    CheckFlag,
    CompareVariable
}

public enum LogicalOperator
{
    AND,
    OR,
    NOT
}

// ActionNodeData.cs
using System;

[Serializable]
public class ActionNodeData : DialogueNodeData
{
    public ActionType actionType;
    public ActionParameters parameters;
}

[Serializable]
public class ActionParameters
{
    public string animationName;
    public GameObject targetObject;
    public string cutsceneName;
}

public enum ActionType
{
    PlayAnimation,
    TriggerCutscene,
    SetFlag,
    ModifyVariable
}

// EndNodeData.cs
using System;

[Serializable]
public class EndNodeData : DialogueNodeData
{
}

// DialogueConnection.cs
using System;

[Serializable]
public class DialogueConnection
{
    public string fromNodeId;
    public string toNodeId;
}

// DialogueEditorWindow.cs
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;

public class DialogueEditorWindow : EditorWindow
{
    private DialogueGraph currentGraph;
    private VisualElement nodeContainer;
    private double saveInterval = 30000;
    private double lastSaveTime = 0;
    private string graphPath = "Assets/DialogueGraphs/NewDialogueGraph.asset";

    [MenuItem("Window/Диалоговый Редактор")]
    public static void ShowWindow()
    {
        GetWindow<DialogueEditorWindow>("Диалоговый Редактор");
    }

    private void OnEnable()
    {
        nodeContainer = new VisualElement();
        nodeContainer.style.flexGrow = 1;
        rootVisualElement.Add(CreateToolbar());
        rootVisualElement.Add(new ScrollView { contentContainer = nodeContainer });
        LoadOrCreateGraph();
        EditorApplication.update += EditorUpdate;
    }

    private void OnDisable()
    {
        EditorApplication.update -= EditorUpdate;
    }

    private VisualElement CreateToolbar()
    {
        var toolbar = new Toolbar();

        var newButton = new Button(() => CreateNewGraph()) { text = "Создать" };
        var loadButton = new Button(() => LoadGraph()) { text = "Загрузить" };
        var saveButton = new Button(() => SaveGraph()) { text = "Сохранить" };
        var addNodeButton = new Button(() => AddNode()) { text = "Добавить Узел" };

        toolbar.Add(newButton);
        toolbar.Add(loadButton);
        toolbar.Add(saveButton);
        toolbar.Add(addNodeButton);

        return toolbar;
    }

    private void CreateNewGraph()
    {
        currentGraph = CreateInstance<DialogueGraph>();
        AssetDatabase.CreateAsset(currentGraph, graphPath);
        AssetDatabase.SaveAssets();
        RefreshEditor();
    }

    private void LoadGraph()
    {
        string path = EditorUtility.OpenFilePanel("Загрузить Граф Диалога", "Assets", "asset");
        if (!string.IsNullOrEmpty(path))
        {
            path = "Assets" + path.Substring(Application.dataPath.Length);
            DialogueGraph loadedGraph = AssetDatabase.LoadAssetAtPath<DialogueGraph>(path);
            if (loadedGraph != null)
            {
                currentGraph = loadedGraph;
                graphPath = path;
                RefreshEditor();
            }
            else
            {
                EditorUtility.DisplayDialog("Ошибка", "Не удалось загрузить граф диалога.", "OK");
            }
        }
    }

    private void SaveGraph()
    {
        if (currentGraph != null)
        {
            EditorUtility.SetDirty(currentGraph);
            AssetDatabase.SaveAssets();
        }
    }

    private void EditorUpdate()
    {
        if (currentGraph == null) return;
        if (EditorApplication.timeSinceStartup - lastSaveTime > saveInterval / 1000)
        {
            SaveGraph();
            lastSaveTime = EditorApplication.timeSinceStartup;
        }
    }

    private void LoadOrCreateGraph()
    {
        currentGraph = AssetDatabase.LoadAssetAtPath<DialogueGraph>(graphPath);
        if (currentGraph == null)
        {
            CreateNewGraph();
        }
        RefreshEditor();
    }

    private void RefreshEditor()
    {
        nodeContainer.Clear();
        foreach(var node in currentGraph.nodes)
        {
            CreateNodeElement(node);
        }
        DrawConnections();
        ValidateGraph();
    }

    private void CreateNodeElement(DialogueNodeData node)
    {
        VisualElement nodeElement = null;
        if(node is StartNodeData)
            nodeElement = new StartNodeElement(node, currentGraph);
        else if(node is DialogueNodeDataWithContent)
            nodeElement = new DialogueNodeElement(node, currentGraph);
        else if(node is ConditionNodeData)
            nodeElement = new ConditionNodeElement(node, currentGraph);
        else if(node is ActionNodeData)
            nodeElement = new ActionNodeElement(node, currentGraph);
        else if(node is EndNodeData)
            nodeElement = new EndNodeElement(node, currentGraph);

        if(nodeElement != null)
            nodeContainer.Add(nodeElement);
    }

    private void AddNode()
    {
        GenericMenu menu = new GenericMenu();
        menu.AddItem(new GUIContent("Диалог"), false, () => AddDialogueNode());
        menu.AddItem(new GUIContent("Условие"), false, () => AddConditionNode());
        menu.AddItem(new GUIContent("Действие"), false, () => AddActionNode());
        menu.AddItem(new GUIContent("Конец"), false, () => AddEndNode());
        menu.ShowAsContext();
    }

    private void AddDialogueNode()
    {
        var node = new DialogueNodeDataWithContent();
        node.position = new Vector2(100, 100);
        currentGraph.nodes.Add(node);
        RefreshEditor();
    }

    private void AddConditionNode()
    {
        var node = new ConditionNodeData();
        node.position = new Vector2(300, 100);
        currentGraph.nodes.Add(node);
        RefreshEditor();
    }

    private void AddActionNode()
    {
        var node = new ActionNodeData();
        node.position = new Vector2(500, 100);
        currentGraph.nodes.Add(node);
        RefreshEditor();
    }

    private void AddEndNode()
    {
        var node = new EndNodeData();
        node.position = new Vector2(700, 100);
        currentGraph.nodes.Add(node);
        RefreshEditor();
    }

    private void DrawConnections()
    {
        // Реализация отрисовки соединений между узлами
    }

    private void ValidateGraph()
    {
        // Реализация валидации графа
    }
}

// DialogueNodeElement.cs
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor;

public class DialogueNodeElement : VisualElement
{
    private DialogueNodeData nodeData;
    private DialogueGraph graph;
    private TextField textField;
    private ObjectField audioField;
    private EnumField effectTypeField;
    private Button saveButton;

    public DialogueNodeElement(DialogueNodeData data, DialogueGraph dialogueGraph)
    {
        nodeData = data;
        graph = dialogueGraph;
        style.position = Position.Absolute;
        style.left = nodeData.position.x;
        style.top = nodeData.position.y;
        style.width = 250;
        style.height = 200;
        style.backgroundColor = new Color(0.8f, 0.8f, 0.8f);
        style.borderBottomColor = Color.black;
        style.borderTopColor = Color.black;
        style.borderLeftColor = Color.black;
        style.borderRightColor = Color.black;
        style.borderWidth = 1;

        Label title = new Label("Диалог Узел");
        title.style.unityFontStyleAndWeight = FontStyle.Bold;
        title.style.unityTextAlign = TextAnchor.MiddleCenter;
        title.style.flexGrow = 0;
        Add(title);

        textField = new TextField("Текст");
        textField.value = ((DialogueNodeDataWithContent)nodeData).text;
        textField.RegisterValueChangedCallback(evt =>
        {
            ((DialogueNodeDataWithContent)nodeData).text = evt.newValue;
        });
        Add(textField);

        audioField = new ObjectField("Аудио")
        {
            objectType = typeof(AudioClip)
        };
        audioField.value = ((DialogueNodeDataWithContent)nodeData).audioClip;
        audioField.RegisterValueChangedCallback(evt =>
        {
            ((DialogueNodeDataWithContent)nodeData).audioClip = evt.newValue as AudioClip;
        });
        Add(audioField);

        // Добавление эффектов
        foreach(var effect in ((DialogueNodeDataWithContent)nodeData).textEffects)
        {
            AddEffectField(effect);
        }

        Button addEffectButton = new Button(() =>
        {
            var newEffect = new TextEffectData();
            ((DialogueNodeDataWithContent)nodeData).textEffects.Add(newEffect);
            AddEffectField(newEffect);
        })
        {
            text = "Добавить Эффект"
        };
        Add(addEffectButton);

        this.RegisterCallback<MouseDownEvent>(OnMouseDown);
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
    }

    private void AddEffectField(TextEffectData effect)
    {
        EnumField effectType = new EnumField("Тип Эффекта", effect.effectType);
        effectType.RegisterValueChangedCallback(evt =>
        {
            effect.effectType = (TextEffectType)evt.newValue;
        });
        Add(effectType);

        // Добавление параметров эффекта в зависимости от типа
        // Пример для некоторых эффектов
        switch(effect.effectType)
        {
            case TextEffectType.Typewriter:
                FloatField speedField = new FloatField("Скорость");
                speedField.value = effect.parameters.speed;
                speedField.RegisterValueChangedCallback(evt =>
                {
                    effect.parameters.speed = evt.newValue;
                });
                Add(speedField);
                break;
            case TextEffectType.FadeIn:
                FloatField durationField = new FloatField("Длительность");
                durationField.value = effect.parameters.duration;
                durationField.RegisterValueChangedCallback(evt =>
                {
                    effect.parameters.duration = evt.newValue;
                });
                Add(durationField);
                break;
            // Добавить остальные эффекты и их параметры
        }
    }

    private Vector2 mouseOffset;

    private void OnMouseDown(MouseDownEvent evt)
    {
        if (evt.button == 0)
        {
            mouseOffset = nodeData.position - evt.localMousePosition;
            this.CaptureMouse();
        }
    }

    private void OnGeometryChanged(GeometryChangedEvent evt)
    {
        if (this.HasMouseCapture())
        {
            Vector2 newPos = evt.newRect.position + (Vector2)GUIUtility.GUIToScreenPoint(Event.current.mousePosition) + mouseOffset;
            nodeData.position = newPos;
            style.left = nodeData.position.x;
            style.top = nodeData.position.y;
            evt.StopPropagation();
        }
    }
}

// ConditionNodeElement.cs
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor;

public class ConditionNodeElement : VisualElement
{
    private ConditionNodeData nodeData;
    private DialogueGraph graph;
    private EnumField conditionTypeField;
    private EnumField logicalOperatorField;

    public ConditionNodeElement(DialogueNodeData data, DialogueGraph dialogueGraph)
    {
        nodeData = data as ConditionNodeData;
        graph = dialogueGraph;
        style.position = Position.Absolute;
        style.left = nodeData.position.x;
        style.top = nodeData.position.y;
        style.width = 250;
        style.height = 200;
        style.backgroundColor = new Color(0.9f, 0.7f, 0.7f);
        style.borderBottomColor = Color.black;
        style.borderTopColor = Color.black;
        style.borderLeftColor = Color.black;
        style.borderRightColor = Color.black;
        style.borderWidth = 1;

        Label title = new Label("Условие Узел");
        title.style.unityFontStyleAndWeight = FontStyle.Bold;
        title.style.unityTextAlign = TextAnchor.MiddleCenter;
        title.style.flexGrow = 0;
        Add(title);

        conditionTypeField = new EnumField("Тип Условия", nodeData.conditionType);
        conditionTypeField.RegisterValueChangedCallback(evt =>
        {
            nodeData.conditionType = (ConditionType)evt.newValue;
        });
        Add(conditionTypeField);

        logicalOperatorField = new EnumField("Логический Оператор", nodeData.logicalOperator);
        logicalOperatorField.RegisterValueChangedCallback(evt =>
        {
            nodeData.logicalOperator = (LogicalOperator)evt.newValue;
        });
        Add(logicalOperatorField);

        foreach(var condition in nodeData.conditions)
        {
            AddConditionField(condition);
        }

        Button addConditionButton = new Button(() =>
        {
            var newCondition = new Condition();
            nodeData.conditions.Add(newCondition);
            AddConditionField(newCondition);
        })
        {
            text = "Добавить Условие"
        };
        Add(addConditionButton);

        this.RegisterCallback<MouseDownEvent>(OnMouseDown);
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
    }

    private void AddConditionField(Condition condition)
    {
        TextField keyField = new TextField("Ключ");
        keyField.value = condition.key;
        keyField.RegisterValueChangedCallback(evt =>
        {
            condition.key = evt.newValue;
        });
        Add(keyField);

        Toggle valueToggle = new Toggle("Значение");
        valueToggle.value = condition.value;
        valueToggle.RegisterValueChangedCallback(evt =>
        {
            condition.value = evt.newValue;
        });
        Add(valueToggle);
    }

    private Vector2 mouseOffset;

    private void OnMouseDown(MouseDownEvent evt)
    {
        if (evt.button == 0)
        {
            mouseOffset = nodeData.position - evt.localMousePosition;
            this.CaptureMouse();
        }
    }

    private void OnGeometryChanged(GeometryChangedEvent evt)
    {
        if (this.HasMouseCapture())
        {
            Vector2 newPos = evt.newRect.position + (Vector2)GUIUtility.GUIToScreenPoint(Event.current.mousePosition) + mouseOffset;
            nodeData.position = newPos;
            style.left = nodeData.position.x;
            style.top = nodeData.position.y;
            evt.StopPropagation();
        }
    }
}

// ActionNodeElement.cs
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor;

public class ActionNodeElement : VisualElement
{
    private ActionNodeData nodeData;
    private DialogueGraph graph;
    private EnumField actionTypeField;
    private TextField animationNameField;
    private ObjectField targetObjectField;
    private TextField cutsceneNameField;

    public ActionNodeElement(DialogueNodeData data, DialogueGraph dialogueGraph)
    {
        nodeData = data as ActionNodeData;
        graph = dialogueGraph;
        style.position = Position.Absolute;
        style.left = nodeData.position.x;
        style.top = nodeData.position.y;
        style.width = 250;
        style.height = 200;
        style.backgroundColor = new Color(0.7f, 0.9f, 0.7f);
        style.borderBottomColor = Color.black;
        style.borderTopColor = Color.black;
        style.borderLeftColor = Color.black;
        style.borderRightColor = Color.black;
        style.borderWidth = 1;

        Label title = new Label("Действие Узел");
        title.style.unityFontStyleAndWeight = FontStyle.Bold;
        title.style.unityTextAlign = TextAnchor.MiddleCenter;
        title.style.flexGrow = 0;
        Add(title);

        actionTypeField = new EnumField("Тип Действия", nodeData.actionType);
        actionTypeField.RegisterValueChangedCallback(evt =>
        {
            nodeData.actionType = (ActionType)evt.newValue;
            UpdateFields();
        });
        Add(actionTypeField);

        animationNameField = new TextField("Имя Анимации");
        animationNameField.value = nodeData.parameters.animationName;
        animationNameField.RegisterValueChangedCallback(evt =>
        {
            nodeData.parameters.animationName = evt.newValue;
        });
        Add(animationNameField);

        targetObjectField = new ObjectField("Целевой Объект")
        {
            objectType = typeof(GameObject)
        };
        targetObjectField.value = nodeData.parameters.targetObject;
        targetObjectField.RegisterValueChangedCallback(evt =>
        {
            nodeData.parameters.targetObject = evt.newValue as GameObject;
        });
        Add(targetObjectField);

        cutsceneNameField = new TextField("Имя Катсцены");
        cutsceneNameField.value = nodeData.parameters.cutsceneName;
        cutsceneNameField.RegisterValueChangedCallback(evt =>
        {
            nodeData.parameters.cutsceneName = evt.newValue;
        });
        Add(cutsceneNameField);

        UpdateFields();

        this.RegisterCallback<MouseDownEvent>(OnMouseDown);
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
    }

    private void UpdateFields()
    {
        switch(nodeData.actionType)
        {
            case ActionType.PlayAnimation:
                animationNameField.style.display = DisplayStyle.Flex;
                targetObjectField.style.display = DisplayStyle.Flex;
                cutsceneNameField.style.display = DisplayStyle.None;
                break;
            case ActionType.TriggerCutscene:
                animationNameField.style.display = DisplayStyle.None;
                targetObjectField.style.display = DisplayStyle.None;
                cutsceneNameField.style.display = DisplayStyle.Flex;
                break;
            case ActionType.SetFlag:
            case ActionType.ModifyVariable:
                animationNameField.style.display = DisplayStyle.None;
                targetObjectField.style.display = DisplayStyle.None;
                cutsceneNameField.style.display = DisplayStyle.None;
                break;
        }
    }

    private Vector2 mouseOffset;

    private void OnMouseDown(MouseDownEvent evt)
    {
        if (evt.button == 0)
        {
            mouseOffset = nodeData.position - evt.localMousePosition;
            this.CaptureMouse();
        }
    }

    private void OnGeometryChanged(GeometryChangedEvent evt)
    {
        if (this.HasMouseCapture())
        {
            Vector2 newPos = evt.newRect.position + (Vector2)GUIUtility.GUIToScreenPoint(Event.current.mousePosition) + mouseOffset;
            nodeData.position = newPos;
            style.left = nodeData.position.x;
            style.top = nodeData.position.y;
            evt.StopPropagation();
        }
    }
}

// StartNodeElement.cs
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor;

public class StartNodeElement : VisualElement
{
    private StartNodeData nodeData;
    private DialogueGraph graph;

    public StartNodeElement(DialogueNodeData data, DialogueGraph dialogueGraph)
    {
        nodeData = data as StartNodeData;
        graph = dialogueGraph;
        style.position = Position.Absolute;
        style.left = nodeData.position.x;
        style.top = nodeData.position.y;
        style.width = 200;
        style.height = 100;
        style.backgroundColor = new Color(0.7f, 0.7f, 0.9f);
        style.borderBottomColor = Color.black;
        style.borderTopColor = Color.black;
        style.borderLeftColor = Color.black;
        style.borderRightColor = Color.black;
        style.borderWidth = 1;

        Label title = new Label("Начало Диалога");
        title.style.unityFontStyleAndWeight = FontStyle.Bold;
        title.style.unityTextAlign = TextAnchor.MiddleCenter;
        title.style.flexGrow = 1;
        Add(title);

        this.RegisterCallback<MouseDownEvent>(OnMouseDown);
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
    }

    private Vector2 mouseOffset;

    private void OnMouseDown(MouseDownEvent evt)
    {
        if (evt.button == 0)
        {
            mouseOffset = nodeData.position - evt.localMousePosition;
            this.CaptureMouse();
        }
    }

    private void OnGeometryChanged(GeometryChangedEvent evt)
    {
        if (this.HasMouseCapture())
        {
            Vector2 newPos = evt.newRect.position + (Vector2)GUIUtility.GUIToScreenPoint(Event.current.mousePosition) + mouseOffset;
            nodeData.position = newPos;
            style.left = nodeData.position.x;
            style.top = nodeData.position.y;
            evt.StopPropagation();
        }
    }
}

// EndNodeElement.cs
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor;

public class EndNodeElement : VisualElement
{
    private EndNodeData nodeData;
    private DialogueGraph graph;

    public EndNodeElement(DialogueNodeData data, DialogueGraph dialogueGraph)
    {
        nodeData = data as EndNodeData;
        graph = dialogueGraph;
        style.position = Position.Absolute;
        style.left = nodeData.position.x;
        style.top = nodeData.position.y;
        style.width = 200;
        style.height = 100;
        style.backgroundColor = new Color(0.9f, 0.7f, 0.7f);
        style.borderBottomColor = Color.black;
        style.borderTopColor = Color.black;
        style.borderLeftColor = Color.black;
        style.borderRightColor = Color.black;
        style.borderWidth = 1;

        Label title = new Label("Конец Диалога");
        title.style.unityFontStyleAndWeight = FontStyle.Bold;
        title.style.unityTextAlign = TextAnchor.MiddleCenter;
        title.style.flexGrow = 1;
        Add(title);

        this.RegisterCallback<MouseDownEvent>(OnMouseDown);
        this.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
    }

    private Vector2 mouseOffset;

    private void OnMouseDown(MouseDownEvent evt)
    {
        if (evt.button == 0)
        {
            mouseOffset = nodeData.position - evt.localMousePosition;
            this.CaptureMouse();
        }
    }

    private void OnGeometryChanged(GeometryChangedEvent evt)
    {
        if (this.HasMouseCapture())
        {
            Vector2 newPos = evt.newRect.position + (Vector2)GUIUtility.GUIToScreenPoint(Event.current.mousePosition) + mouseOffset;
            nodeData.position = newPos;
            style.left = nodeData.position.x;
            style.top = nodeData.position.y;
            evt.StopPropagation();
        }
    }
}

// DialogueManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class DialogueManager : MonoBehaviour
{
    public DialogueGraph graph;
    public TextMeshProUGUI dialogueText;
    public AudioSource audioSource;
    public float textDisplayTime = 2f;
    public float waitTime = 1f;
    private Queue<DialogueNodeDataWithContent> dialogueQueue = new Queue<DialogueNodeDataWithContent>();
    private bool isPlaying = false;

    public void PlayDialogue()
    {
        if (graph == null || isPlaying) return;
        var startNode = graph.nodes.Find(n => n is StartNodeData);
        if(startNode == null) return;
        TraverseDialogue(startNode);
        StartCoroutine(DisplayDialogue());
    }

    private void TraverseDialogue(DialogueNodeData node)
    {
        if(node is DialogueNodeDataWithContent dialogueNode)
        {
            dialogueQueue.Enqueue(dialogueNode);
            foreach(var conn in graph.connections)
            {
                if(conn.fromNodeId == node.id)
                {
                    var nextNode = graph.nodes.Find(n => n.id == conn.toNodeId);
                    if(nextNode != null)
                    {
                        TraverseDialogue(nextNode);
                    }
                }
            }
        }
        else if(node is ConditionNodeData conditionNode)
        {
            // Реализация условий
        }
        else if(node is ActionNodeData actionNode)
        {
            // Реализация действий
            foreach(var conn in graph.connections)
            {
                if(conn.fromNodeId == node.id)
                {
                    var nextNode = graph.nodes.Find(n => n.id == conn.toNodeId);
                    if(nextNode != null)
                    {
                        TraverseDialogue(nextNode);
                    }
                }
            }
        }
        else if(node is EndNodeData)
        {
        }
    }

    private IEnumerator DisplayDialogue()
    {
        isPlaying = true;
        while(dialogueQueue.Count > 0)
        {
            var node = dialogueQueue.Dequeue();
            if(node.audioClip != null)
            {
                audioSource.PlayOneShot(node.audioClip);
            }
            yield return StartCoroutine(ApplyTextEffects(node.text, node.textEffects));
            yield return new WaitForSeconds(waitTime);
        }
        isPlaying = false;
    }

    private IEnumerator ApplyTextEffects(string text, List<TextEffectData> effects)
    {
        if(effects == null || effects.Count == 0)
        {
            dialogueText.text = text;
            yield return new WaitForSeconds(textDisplayTime);
            yield break;
        }

        foreach(var effect in effects)
        {
            switch(effect.effectType)
            {
                case TextEffectType.Typewriter:
                    yield return StartCoroutine(TypewriterEffect(text, effect.parameters));
                    break;
                case TextEffectType.FadeIn:
                    yield return StartCoroutine(FadeInEffect(text, effect.parameters));
                    break;
                case TextEffectType.Shake:
                    yield return StartCoroutine(ShakeEffect(text, effect.parameters));
                    break;
                case TextEffectType.Rainbow:
                    yield return StartCoroutine(RainbowEffect(text, effect.parameters));
                    break;
                case TextEffectType.Wobble:
                    yield return StartCoroutine(WobbleEffect(text, effect.parameters));
                    break;
                case TextEffectType.Scroll:
                    yield return StartCoroutine(ScrollEffect(text, effect.parameters));
                    break;
                case TextEffectType.Explode:
                    yield return StartCoroutine(ExplodeEffect(text, effect.parameters));
                    break;
                case TextEffectType.Glitch:
                    yield return StartCoroutine(GlitchEffect(text, effect.parameters));
                    break;
                case TextEffectType.Pulse:
                    yield return StartCoroutine(PulseEffect(text, effect.parameters));
                    break;
                case TextEffectType.Expand:
                    yield return StartCoroutine(ExpandEffect(text, effect.parameters));
                    break;
                // Добавьте дополнительные эффекты здесь
            }
        }
    }

    // Примеры некоторых эффектов

    private IEnumerator TypewriterEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = "";
        foreach(char c in text)
        {
            dialogueText.text += c;
            yield return new WaitForSeconds(0.05f / parameters.speed);
        }
        yield return null;
    }

    private IEnumerator FadeInEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        dialogueText.alpha = 0;
        float elapsed = 0f;
        while(elapsed < parameters.duration)
        {
            dialogueText.alpha = Mathf.Lerp(0, 1, elapsed / parameters.duration);
            elapsed += Time.deltaTime;
            yield return null;
        }
        dialogueText.alpha = 1;
    }

    private IEnumerator ShakeEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        Vector3 originalPos = dialogueText.rectTransform.localPosition;
        float elapsed = 0f;
        while(elapsed < parameters.duration)
        {
            dialogueText.rectTransform.localPosition = originalPos + Random.insideUnitSphere * parameters.intensity;
            elapsed += Time.deltaTime;
            yield return null;
        }
        dialogueText.rectTransform.localPosition = originalPos;
    }

    private IEnumerator RainbowEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        dialogueText.ForceMeshUpdate();
        var textInfo = dialogueText.textInfo;
        float elapsed = 0f;
        while(elapsed < parameters.duration)
        {
            for(int i =0;i<textInfo.characterCount;i++)
            {
                var charInfo = textInfo.characterInfo[i];
                if(!charInfo.isVisible) continue;
                int vertexIndex = charInfo.vertexIndex;
                int materialIndex = charInfo.materialReferenceIndex;
                var colors = textInfo.meshInfo[materialIndex].colors32;
                Color c = Color.HSVToRGB((elapsed * parameters.speed + i * 0.05f) %1,1,1);
                colors[vertexIndex +0] = c;
                colors[vertexIndex +1] = c;
                colors[vertexIndex +2] = c;
                colors[vertexIndex +3] = c;
            }
            dialogueText.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32);
            elapsed += Time.deltaTime;
            yield return null;
        }
    }

    private IEnumerator WobbleEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        Vector3 originalPos = dialogueText.rectTransform.localPosition;
        float elapsed = 0f;
        while(elapsed < parameters.duration)
        {
            dialogueText.rectTransform.localPosition = originalPos + new Vector3(Mathf.Sin(elapsed * parameters.speed) * parameters.intensity, 0, 0);
            elapsed += Time.deltaTime;
            yield return null;
        }
        dialogueText.rectTransform.localPosition = originalPos;
    }

    private IEnumerator ScrollEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        Vector2 originalPos = dialogueText.rectTransform.anchoredPosition;
        float elapsed = 0f;
        float targetY = originalPos.y + 100;
        while(elapsed < parameters.duration)
        {
            dialogueText.rectTransform.anchoredPosition = Vector2.Lerp(originalPos, new Vector2(originalPos.x, targetY), elapsed / parameters.duration);
            elapsed += Time.deltaTime;
            yield return null;
        }
        dialogueText.rectTransform.anchoredPosition = new Vector2(originalPos.x, targetY);
    }

    private IEnumerator ExplodeEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        // Реализация эффекта Explode
        yield return null;
    }

    private IEnumerator GlitchEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        // Реализация эффекта Glitch
        yield return null;
    }

    private IEnumerator PulseEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        float elapsed = 0f;
        while(elapsed < parameters.duration)
        {
            dialogueText.fontSize = 36 + Mathf.PingPong(elapsed * parameters.speed, 10);
            elapsed += Time.deltaTime;
            yield return null;
        }
        dialogueText.fontSize = 36;
    }

    private IEnumerator ExpandEffect(string text, EffectParameters parameters)
    {
        dialogueText.text = text;
        Vector3 originalScale = dialogueText.rectTransform.localScale;
        float elapsed = 0f;
        while(elapsed < parameters.duration)
        {
            dialogueText.rectTransform.localScale = Vector3.Lerp(originalScale, originalScale * 1.5f, elapsed / parameters.duration);
            elapsed += Time.deltaTime;
            yield return null;
        }
        dialogueText.rectTransform.localScale = originalScale;
    }

    // Добавьте дополнительные методы эффектов здесь
}

// DialoguePlaybackController.cs
using UnityEngine;

public class DialoguePlaybackController : MonoBehaviour
{
    public DialogueManager dialogueManager;

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.Space))
        {
            dialogueManager.PlayDialogue();
        }
    }
}