using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Core; using Interactions; using UnityEngine; using PuzzleS; namespace Dialogue { [AddComponentMenu("Apple Hills/Dialogue/Dialogue Component")] public class DialogueComponent : MonoBehaviour { [SerializeField] private RuntimeDialogueGraph dialogueGraph; private RuntimeDialogueNode currentNode; private int currentLineIndex; private bool isWaitingForCondition; // Events public event Action OnDialogueLineChanged; // speaker name, dialogue text public event Action OnDialogueCompleted; public event Action OnConditionStatusChanged; // Whether the condition is now met // References to managers private ItemManager itemManager; private PuzzleManager puzzleManager; // Properties public bool IsActive { get; private set; } public bool IsCompleted { get; private set; } public string CurrentSpeakerName => dialogueGraph?.speakerName; private void Awake() { // Auto-injection of managers itemManager = FindFirstObjectByType(); puzzleManager = FindFirstObjectByType(); if (itemManager == null) Debug.LogWarning("DialogueComponent: ItemManager not found in scene!"); if (puzzleManager == null) Debug.LogWarning("DialogueComponent: PuzzleManager not found in scene!"); } public void StartDialogue() { if (dialogueGraph == null) { Debug.LogError("DialogueComponent: No dialogue graph assigned!"); return; } // Reset state IsActive = true; IsCompleted = false; isWaitingForCondition = false; currentLineIndex = 0; // Set to entry node currentNode = dialogueGraph.GetNodeByID(dialogueGraph.entryNodeID); // Register for events based on current node RegisterForEvents(); // Try to process the current node ProcessCurrentNode(); } public bool CanAdvance() { // Can't advance if dialogue is not active or is completed if (!IsActive || IsCompleted) return false; // Check if we're waiting for a condition if (isWaitingForCondition) return false; // Check if we have more lines in the current node if (currentLineIndex < currentNode.dialogueLines.Count - 1 || (currentNode.loopThroughLines && currentNode.dialogueLines.Count > 0)) return true; // Check if we have a next node return !string.IsNullOrEmpty(currentNode.nextNodeID); } public void Advance() { if (!CanAdvance()) return; // If we have more lines in the current node, advance to the next line if (currentLineIndex < currentNode.dialogueLines.Count - 1) { currentLineIndex++; OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); return; } // If we should loop through lines, reset the index if (currentNode.loopThroughLines && currentNode.dialogueLines.Count > 0) { currentLineIndex = 0; OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); return; } // Otherwise, move to the next node MoveToNextNode(); } public void AdvanceToNextNode() { if (!IsActive || IsCompleted) return; // Force move to the next node, regardless of current line index MoveToNextNode(); } public string GetCurrentDialogueLine() { if (currentNode == null || currentNode.dialogueLines.Count == 0) return string.Empty; if (currentLineIndex < 0 || currentLineIndex >= currentNode.dialogueLines.Count) return string.Empty; return currentNode.dialogueLines[currentLineIndex]; } // Methods to handle dialogue responses for item slots public void HandleItemSlotInteraction(string slotID, string itemID, bool isForbiddenItem) { if (!IsActive || IsCompleted || currentNode == null || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnSlot) return; // If this is the slot we're waiting for if (currentNode.slotItemID == slotID) { // If correct item is slotted, move to next node if (itemID == slotID) { MoveToNextNode(); } // If it's a forbidden item, show the forbidden dialogue else if (isForbiddenItem && currentNode.forbiddenItemLines.Count > 0) { ShowResponseLines(currentNode.forbiddenItemLines, currentNode.loopThroughForbiddenLines); } // Otherwise show incorrect item dialogue else if (currentNode.incorrectItemLines.Count > 0) { ShowResponseLines(currentNode.incorrectItemLines, currentNode.loopThroughIncorrectLines); } } } private void ShowResponseLines(List lines, bool loopThrough) { StartCoroutine(ShowResponseRoutine(lines, loopThrough)); } private IEnumerator ShowResponseRoutine(List lines, bool loopThrough) { // Store original node and line index var originalNode = currentNode; var originalLineIndex = currentLineIndex; // Show each response line for (int i = 0; i < lines.Count; i++) { // Break if dialogue state has changed if (currentNode != originalNode || !IsActive || IsCompleted) break; OnDialogueLineChanged?.Invoke(CurrentSpeakerName, lines[i]); // Wait for input to continue yield return new WaitForSeconds(2f); // Wait time between lines, can be adjusted // If we should loop and we're at the end, start over if (loopThrough && i == lines.Count - 1) i = -1; // Break after first iteration if not looping if (!loopThrough && i == 0) break; } // Restore original dialogue line if (currentNode == originalNode && IsActive && !IsCompleted) { OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); } } private void MoveToNextNode() { // Unregister from events based on current node UnregisterFromEvents(); // If there's no next node, complete the dialogue if (string.IsNullOrEmpty(currentNode.nextNodeID)) { IsActive = false; IsCompleted = true; OnDialogueCompleted?.Invoke(); return; } // Move to the next node currentNode = dialogueGraph.GetNodeByID(currentNode.nextNodeID); currentLineIndex = 0; // Register for events based on new node RegisterForEvents(); // Process the new node ProcessCurrentNode(); } private void ProcessCurrentNode() { if (currentNode == null) { Debug.LogError("DialogueComponent: Current node is null!"); return; } // Handle different node types switch (currentNode.nodeType) { case RuntimeDialogueNodeType.Dialogue: isWaitingForCondition = false; OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); break; case RuntimeDialogueNodeType.WaitOnPuzzleStep: HandlePuzzleStepNode(); break; case RuntimeDialogueNodeType.WaitOnPickup: HandlePickupNode(); break; case RuntimeDialogueNodeType.WaitOnSlot: HandleSlotNode(); break; case RuntimeDialogueNodeType.End: IsActive = false; IsCompleted = true; OnDialogueCompleted?.Invoke(); break; default: Debug.LogError($"DialogueComponent: Unknown node type {currentNode.nodeType}"); break; } } private void HandlePuzzleStepNode() { if (puzzleManager == null) { Debug.LogError("DialogueComponent: PuzzleManager is required for WaitOnPuzzleStep nodes!"); MoveToNextNode(); return; } // Check if the puzzle step is already completed if (IsPuzzleStepComplete(currentNode.puzzleStepID)) { isWaitingForCondition = false; OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); return; } // Otherwise, wait for the puzzle step isWaitingForCondition = true; OnConditionStatusChanged?.Invoke(false); OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); } private void HandlePickupNode() { if (itemManager == null) { Debug.LogError("DialogueComponent: ItemManager is required for WaitOnPickup nodes!"); MoveToNextNode(); return; } // Check if the item is already picked up if (IsItemPickedUp(currentNode.pickupItemID)) { isWaitingForCondition = false; OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); return; } // Otherwise, wait for the item pickup isWaitingForCondition = true; OnConditionStatusChanged?.Invoke(false); OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); } private void HandleSlotNode() { if (itemManager == null) { Debug.LogError("DialogueComponent: ItemManager is required for WaitOnSlot nodes!"); MoveToNextNode(); return; } // Check if the slot already has the correct item if (IsItemSlotted(currentNode.slotItemID)) { isWaitingForCondition = false; OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); MoveToNextNode(); return; } // Otherwise, wait for the correct item to be slotted isWaitingForCondition = true; OnConditionStatusChanged?.Invoke(false); OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine()); } private void RegisterForEvents() { if (currentNode == null) return; switch (currentNode.nodeType) { case RuntimeDialogueNodeType.WaitOnPuzzleStep: if (puzzleManager != null) puzzleManager.OnStepCompleted += OnStepCompleted; break; case RuntimeDialogueNodeType.WaitOnPickup: if (itemManager != null) itemManager.OnItemPickedUp += OnItemPickedUp; break; case RuntimeDialogueNodeType.WaitOnSlot: if (itemManager != null) itemManager.OnCorrectItemSlotted += OnCorrectItemSlotted; break; } } private void UnregisterFromEvents() { if (currentNode == null) return; switch (currentNode.nodeType) { case RuntimeDialogueNodeType.WaitOnPuzzleStep: if (puzzleManager != null) puzzleManager.OnStepCompleted -= OnStepCompleted; break; case RuntimeDialogueNodeType.WaitOnPickup: if (itemManager != null) itemManager.OnItemPickedUp -= OnItemPickedUp; break; case RuntimeDialogueNodeType.WaitOnSlot: if (itemManager != null) itemManager.OnCorrectItemSlotted -= OnCorrectItemSlotted; break; } } // Event handlers for PuzzleManager private void OnStepCompleted(PuzzleStepSO step) { if (!IsActive || !isWaitingForCondition || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnPuzzleStep) return; if (step.stepId == currentNode.puzzleStepID) { isWaitingForCondition = false; OnConditionStatusChanged?.Invoke(true); MoveToNextNode(); } } // Event handlers for ItemManager private void OnItemPickedUp(PickupItemData item) { if (!IsActive || !isWaitingForCondition || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnPickup) return; if (item.itemId == currentNode.pickupItemID) { isWaitingForCondition = false; OnConditionStatusChanged?.Invoke(true); MoveToNextNode(); } } private void OnCorrectItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem) { if (!IsActive || !isWaitingForCondition || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnSlot) return; if (slotDefinition.itemId == currentNode.slotItemID) { isWaitingForCondition = false; OnConditionStatusChanged?.Invoke(true); MoveToNextNode(); } } // Helper methods private bool IsPuzzleStepComplete(string stepID) { if (puzzleManager == null) return false; // Use the public method instead of accessing the private field return puzzleManager.IsPuzzleStepCompleted(stepID); } private bool IsItemPickedUp(string itemID) { if (itemManager == null) return false; // Check if any picked up item has this ID foreach (var pickup in itemManager.Pickups) { if (pickup.isPickedUp && pickup.itemData != null && pickup.itemData.itemId == itemID) { return true; } } return false; } private bool IsItemSlotted(string slotID) { if (itemManager == null) return false; // Check if any slot has the correct item with this ID foreach (var slot in itemManager.ItemSlots) { if (slot.CurrentSlottedState == ItemSlotState.Correct && slot.itemData != null && slot.itemData.itemId == slotID) { return true; } } return false; } // Editor functionality public void SetDialogueGraph(RuntimeDialogueGraph graph) { dialogueGraph = graph; } } }