using System; using System.Collections; using System.Collections.Generic; 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 initialized = false; // Properties public bool IsActive { get; private set; } public bool IsCompleted { get; private set; } public string CurrentSpeakerName => dialogueGraph?.speakerName; // Event for UI updates if needed public event Action OnDialogueChanged; private void Start() { // Register for global events if (PuzzleManager.Instance != null) PuzzleManager.Instance.OnStepCompleted += OnAnyPuzzleStepCompleted; if (ItemManager.Instance != null) { ItemManager.Instance.OnItemPickedUp += OnAnyItemPickedUp; ItemManager.Instance.OnCorrectItemSlotted += OnAnyItemSlotted; } // Auto-start the dialogue StartDialogue(); } private void OnDestroy() { // Unregister from events if (PuzzleManager.Instance != null) PuzzleManager.Instance.OnStepCompleted -= OnAnyPuzzleStepCompleted; if (ItemManager.Instance != null) { ItemManager.Instance.OnItemPickedUp -= OnAnyItemPickedUp; ItemManager.Instance.OnCorrectItemSlotted -= OnAnyItemSlotted; } } /// /// Start the dialogue from the beginning /// public void StartDialogue() { if (dialogueGraph == null) { Debug.LogError("DialogueComponent: No dialogue graph assigned!"); return; } // Reset state IsActive = true; IsCompleted = false; currentLineIndex = 0; initialized = true; // Set to entry node currentNode = dialogueGraph.GetNodeByID(dialogueGraph.entryNodeID); // Process the node ProcessCurrentNode(); } /// /// Get the current dialogue line and advance to the next line or node if appropriate /// Each call represents one interaction with the NPC /// public string GetCurrentDialogueLine() { // Initialize if needed if (!initialized) { StartDialogue(); } if (!IsActive || IsCompleted || currentNode == null || currentNode.dialogueLines.Count == 0) return string.Empty; // Get current line string currentLine = string.Empty; if (currentLineIndex >= 0 && currentLineIndex < currentNode.dialogueLines.Count) { currentLine = currentNode.dialogueLines[currentLineIndex]; } // Advance dialogue state for next interaction AdvanceDialogueState(); // Return the current line return currentLine; } /// /// Advance dialogue state for the next interaction /// private void AdvanceDialogueState() { if (!IsActive || IsCompleted || currentNode == null) return; // If we're on a conditional node, we can't advance past it until condition is met if (IsWaitingForCondition()) return; // If we have more lines in the current node, advance to the next line if (currentLineIndex < currentNode.dialogueLines.Count - 1) { currentLineIndex++; return; } // If we should loop through lines, reset the index if (currentNode.loopThroughLines && currentNode.dialogueLines.Count > 0) { currentLineIndex = 0; return; } // Otherwise, move to the next node MoveToNextNode(); } private void MoveToNextNode() { // If there's no next node, complete the dialogue if (string.IsNullOrEmpty(currentNode.nextNodeID)) { IsActive = false; IsCompleted = true; return; } // Move to the next node currentNode = dialogueGraph.GetNodeByID(currentNode.nextNodeID); currentLineIndex = 0; // 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: // Regular dialogue node, nothing special to do break; case RuntimeDialogueNodeType.WaitOnPuzzleStep: // Check if the puzzle step is already completed if (IsPuzzleStepComplete(currentNode.puzzleStepID)) { // If it's already complete, move past this node automatically MoveToNextNode(); } break; case RuntimeDialogueNodeType.WaitOnPickup: // Check if the item is already picked up if (IsItemPickedUp(currentNode.pickupItemID)) { // If it's already picked up, move past this node automatically MoveToNextNode(); } break; case RuntimeDialogueNodeType.WaitOnSlot: // Check if the item is already slotted if (IsItemSlotted(currentNode.slotItemID)) { // If it's already slotted, move past this node automatically MoveToNextNode(); } break; case RuntimeDialogueNodeType.End: // End node, complete the dialogue IsActive = false; IsCompleted = true; break; default: Debug.LogError($"DialogueComponent: Unknown node type {currentNode.nodeType}"); break; } } // Global event handlers private void OnAnyPuzzleStepCompleted(PuzzleStepSO step) { // Only react if we're active and waiting on a puzzle step if (!IsActive || IsCompleted || currentNode == null || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnPuzzleStep) return; // Check if this is the step we're waiting for if (step.stepId == currentNode.puzzleStepID) { // Move to next node automatically when condition is met MoveToNextNode(); OnDialogueChanged?.Invoke(GetCurrentDialogueLine()); } } private void OnAnyItemPickedUp(PickupItemData item) { // Only react if we're active and waiting on an item pickup if (!IsActive || IsCompleted || currentNode == null || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnPickup) return; // Check if this is the item we're waiting for if (item.itemId == currentNode.pickupItemID) { // Move to next node automatically when condition is met MoveToNextNode(); OnDialogueChanged?.Invoke(GetCurrentDialogueLine()); } } private void OnAnyItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem) { // Only react if we're active and waiting on a slot if (!IsActive || IsCompleted || currentNode == null || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnSlot) return; // Check if this is the slot we're waiting for if (slotDefinition.itemId == currentNode.slotItemID) { // Move to next node automatically when condition is met MoveToNextNode(); OnDialogueChanged?.Invoke(GetCurrentDialogueLine()); } } // Helper methods private bool IsWaitingForCondition() { if (currentNode == null) return false; switch (currentNode.nodeType) { case RuntimeDialogueNodeType.WaitOnPuzzleStep: return !IsPuzzleStepComplete(currentNode.puzzleStepID); case RuntimeDialogueNodeType.WaitOnPickup: return !IsItemPickedUp(currentNode.pickupItemID); case RuntimeDialogueNodeType.WaitOnSlot: return !IsItemSlotted(currentNode.slotItemID); default: return false; } } private bool IsPuzzleStepComplete(string stepID) { return PuzzleManager.Instance != null && PuzzleManager.Instance.IsPuzzleStepCompleted(stepID); } private bool IsItemPickedUp(string itemID) { if (ItemManager.Instance == null) return false; // Check all pickups for the given ID foreach (var pickup in ItemManager.Instance.Pickups) { if (pickup.isPickedUp && pickup.itemData != null && pickup.itemData.itemId == itemID) { return true; } } return false; } private bool IsItemSlotted(string slotID) { if (ItemManager.Instance == null) return false; // Check if any slot with this ID has the correct item foreach (var slot in ItemManager.Instance.ItemSlots) { if (slot.itemData != null && slot.itemData.itemId == slotID && slot.CurrentSlottedState == ItemSlotState.Correct) { return true; } } return false; } // Editor functionality public void SetDialogueGraph(RuntimeDialogueGraph graph) { dialogueGraph = graph; } } }