829 lines
33 KiB
C#
829 lines
33 KiB
C#
using System.Collections.Generic;
|
|
using Core;
|
|
using Core.Lifecycle;
|
|
using Interactions;
|
|
using UnityEngine;
|
|
using PuzzleS;
|
|
using UnityEngine.Audio;
|
|
|
|
namespace Dialogue
|
|
{
|
|
[AddComponentMenu("AppleHills/Dialogue/Dialogue Component")]
|
|
[RequireComponent(typeof(AppleAudioSource))]
|
|
public class DialogueComponent : ManagedBehaviour
|
|
{
|
|
[SerializeField] private RuntimeDialogueGraph dialogueGraph;
|
|
|
|
private RuntimeDialogueNode currentNode;
|
|
private int currentLineIndex;
|
|
private bool initialized = false;
|
|
private SpeechBubble speechBubble;
|
|
private AppleAudioSource appleAudioSource;
|
|
|
|
// Flag to track when a condition has been met but dialogue hasn't advanced yet
|
|
private bool _conditionSatisfiedPendingAdvance = false;
|
|
|
|
// Track the current slot state for WaitOnSlot nodes
|
|
private ItemSlotState _currentSlotState = ItemSlotState.None;
|
|
private PickupItemData _lastSlottedItem;
|
|
|
|
// Properties
|
|
public bool IsActive { get; private set; }
|
|
public bool IsCompleted { get; private set; }
|
|
public string CurrentSpeakerName => dialogueGraph?.speakerName;
|
|
|
|
|
|
public override int ManagedAwakePriority => 150; // Dialogue systems
|
|
|
|
internal override void OnManagedStart()
|
|
{
|
|
// Get required components
|
|
appleAudioSource = GetComponent<AppleAudioSource>();
|
|
speechBubble = GetComponentInChildren<SpeechBubble>();
|
|
|
|
if (speechBubble == null)
|
|
{
|
|
Debug.LogError("SpeechBubble component is missing on Dialogue Component");
|
|
}
|
|
|
|
var interactable = GetComponent<InteractableBase>();
|
|
if (interactable != null)
|
|
{
|
|
interactable.characterArrived.AddListener(OnCharacterArrived);
|
|
}
|
|
|
|
// Update bubble visibility based on whether we have lines
|
|
if (speechBubble != null)
|
|
{
|
|
speechBubble.UpdatePromptVisibility(HasAnyLines());
|
|
}
|
|
|
|
// Register for global events
|
|
PuzzleManager.Instance.OnStepCompleted += OnAnyPuzzleStepCompleted;
|
|
ItemManager.Instance.OnItemPickedUp += OnAnyItemPickedUp;
|
|
ItemManager.Instance.OnCorrectItemSlotted += OnAnyItemSlotted;
|
|
ItemManager.Instance.OnIncorrectItemSlotted += OnAnyIncorrectItemSlotted;
|
|
ItemManager.Instance.OnForbiddenItemSlotted += OnAnyForbiddenItemSlotted;
|
|
ItemManager.Instance.OnItemSlotCleared += OnAnyItemSlotCleared;
|
|
ItemManager.Instance.OnItemsCombined += OnAnyItemsCombined;
|
|
}
|
|
|
|
private void OnCharacterArrived()
|
|
{
|
|
if (speechBubble == null || !HasAnyLines()) return;
|
|
|
|
// Advance the dialogue state to move to the next content
|
|
AdvanceDialogueState();
|
|
|
|
// Check if we have DialogueContent available (prioritizing the new content system)
|
|
DialogueContent content = GetCurrentDialogueContent();
|
|
|
|
if (content != null)
|
|
{
|
|
// Display the content with the method that handles both text and images
|
|
// and pass whether there are more lines available for prompt display
|
|
speechBubble.DisplayDialogueContent(content, HasAnyLines());
|
|
|
|
// Play audio if available
|
|
PlayDialogueAudio(content.Audio);
|
|
|
|
// Log the content type for debugging
|
|
Logging.Debug($"Displaying content type: {content.ContentType} - {(content.ContentType == DialogueContentType.Text ? content.Text : content.Image?.name)}");
|
|
}
|
|
else
|
|
{
|
|
// Fall back to legacy text-only method if no DialogueContent is available
|
|
string line = GetCurrentDialogueLine();
|
|
speechBubble.DisplayDialogueLine(line, HasAnyLines());
|
|
|
|
// Log for debugging
|
|
Logging.Debug($"Displaying legacy text: {line}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Play the audio clip for the current dialogue content
|
|
/// </summary>
|
|
/// <param name="clip">Audio clip to play</param>
|
|
private void PlayDialogueAudio(AudioResource clip)
|
|
{
|
|
// Stop any currently playing audio
|
|
if (appleAudioSource.audioSource.isPlaying)
|
|
{
|
|
appleAudioSource.Stop();
|
|
}
|
|
|
|
// Play the new clip if it exists
|
|
if (clip != null)
|
|
{
|
|
appleAudioSource.audioSource.resource = clip;
|
|
appleAudioSource.Play(1);
|
|
Logging.Debug($"Playing dialogue audio: {clip.name}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current dialogue content (text or image)
|
|
/// </summary>
|
|
/// <returns>DialogueContent or null if only legacy text content is available</returns>
|
|
private DialogueContent GetCurrentDialogueContent()
|
|
{
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
if (!IsActive || IsCompleted || currentNode == null)
|
|
return null;
|
|
|
|
// Check if we have DialogueContent available
|
|
if (currentNode.dialogueContent != null && currentNode.dialogueContent.Count > 0)
|
|
{
|
|
// For WaitOnSlot nodes, use the appropriate content based on slot state
|
|
if (currentNode.nodeType == RuntimeDialogueNodeType.WaitOnSlot)
|
|
{
|
|
// Choose the appropriate content collection based on the current slot state
|
|
List<DialogueContent> contentForState = currentNode.dialogueContent; // Default content
|
|
|
|
switch (_currentSlotState)
|
|
{
|
|
case ItemSlotState.Incorrect:
|
|
// Use incorrect item content if available
|
|
if (currentNode.incorrectItemContent != null && currentNode.incorrectItemContent.Count > 0)
|
|
contentForState = currentNode.incorrectItemContent;
|
|
break;
|
|
|
|
case ItemSlotState.Forbidden:
|
|
// Use forbidden item content if available
|
|
if (currentNode.forbiddenItemContent != null && currentNode.forbiddenItemContent.Count > 0)
|
|
contentForState = currentNode.forbiddenItemContent;
|
|
break;
|
|
}
|
|
|
|
// If we have content for this state, return the current one
|
|
if (contentForState != null && contentForState.Count > 0)
|
|
{
|
|
// Make sure index is within bounds
|
|
int index = Mathf.Clamp(currentLineIndex, 0, contentForState.Count - 1);
|
|
return contentForState[index];
|
|
}
|
|
return null; // No content for this slot state
|
|
}
|
|
else
|
|
{
|
|
// For other node types, use the default dialogueContent
|
|
if (currentLineIndex >= 0 && currentLineIndex < currentNode.dialogueContent.Count)
|
|
{
|
|
return currentNode.dialogueContent[currentLineIndex];
|
|
}
|
|
}
|
|
}
|
|
|
|
// No DialogueContent available, will fall back to legacy text handling
|
|
return null;
|
|
}
|
|
|
|
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;
|
|
ItemManager.Instance.OnIncorrectItemSlotted -= OnAnyIncorrectItemSlotted;
|
|
ItemManager.Instance.OnForbiddenItemSlotted -= OnAnyForbiddenItemSlotted;
|
|
ItemManager.Instance.OnItemSlotCleared -= OnAnyItemSlotCleared;
|
|
ItemManager.Instance.OnItemsCombined -= OnAnyItemsCombined;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start the dialogue from the beginning
|
|
/// </summary>
|
|
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();
|
|
|
|
// Update bubble visibility based on whether we have lines
|
|
if (speechBubble != null)
|
|
{
|
|
speechBubble.UpdatePromptVisibility(HasAnyLines());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current dialogue line and advance to the next line or node if appropriate
|
|
/// Each call represents one interaction with the NPC
|
|
/// </summary>
|
|
public string GetCurrentDialogueLine()
|
|
{
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
if (!IsActive || IsCompleted || currentNode == null)
|
|
return string.Empty;
|
|
|
|
// For WaitOnSlot nodes, use the appropriate line type based on slot state
|
|
if (currentNode.nodeType == RuntimeDialogueNodeType.WaitOnSlot)
|
|
{
|
|
// Choose the appropriate line collection based on the current slot state
|
|
List<string> linesForState = currentNode.dialogueLines; // Default lines
|
|
|
|
switch (_currentSlotState)
|
|
{
|
|
case ItemSlotState.Incorrect:
|
|
// Use incorrect item lines if available, otherwise fall back to default lines
|
|
if (currentNode.incorrectItemLines != null && currentNode.incorrectItemLines.Count > 0)
|
|
linesForState = currentNode.incorrectItemLines;
|
|
break;
|
|
|
|
case ItemSlotState.Forbidden:
|
|
// Use forbidden item lines if available, otherwise fall back to default lines
|
|
if (currentNode.forbiddenItemLines != null && currentNode.forbiddenItemLines.Count > 0)
|
|
linesForState = currentNode.forbiddenItemLines;
|
|
break;
|
|
|
|
// For None or Correct state, use the default lines
|
|
default:
|
|
linesForState = currentNode.dialogueLines;
|
|
break;
|
|
}
|
|
|
|
// If we have lines for this state, return the current one
|
|
if (linesForState != null && linesForState.Count > 0)
|
|
{
|
|
// Make sure index is within bounds
|
|
int index = Mathf.Clamp(currentLineIndex, 0, linesForState.Count - 1);
|
|
return linesForState[index];
|
|
}
|
|
}
|
|
|
|
// For other node types, use the default dialogueLines
|
|
if (currentNode.dialogueLines == 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];
|
|
}
|
|
|
|
return currentLine;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Advance dialogue state for the next interaction
|
|
/// </summary>
|
|
private void AdvanceDialogueState()
|
|
{
|
|
if (!IsActive || IsCompleted || currentNode == null)
|
|
return;
|
|
|
|
// If the condition was satisfied earlier, move to the next node immediately
|
|
if (_conditionSatisfiedPendingAdvance)
|
|
{
|
|
_conditionSatisfiedPendingAdvance = false; // Reset flag
|
|
MoveToNextNode();
|
|
return;
|
|
}
|
|
|
|
// First check if we have any dialogueContent to process
|
|
bool hasDialogueContent = currentNode.dialogueContent != null && currentNode.dialogueContent.Count > 0;
|
|
|
|
if (hasDialogueContent)
|
|
{
|
|
// If we have dialogueContent and there are more entries, advance to the next one
|
|
if (currentLineIndex < currentNode.dialogueContent.Count - 1)
|
|
{
|
|
currentLineIndex++;
|
|
return;
|
|
}
|
|
|
|
// If we should loop through content, reset the index
|
|
if (currentNode.loopThroughLines && currentNode.dialogueContent.Count > 0)
|
|
{
|
|
currentLineIndex = 0;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fall back to legacy dialogueLines
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// If we're at a node that doesn't have a next node, we're done
|
|
if (string.IsNullOrEmpty(currentNode.nextNodeID))
|
|
{
|
|
IsActive = false;
|
|
IsCompleted = true;
|
|
return;
|
|
}
|
|
|
|
// Move to the next node only if no conditions to wait for
|
|
if (!IsWaitingForCondition())
|
|
{
|
|
MoveToNextNode();
|
|
}
|
|
}
|
|
|
|
private void MoveToNextNode()
|
|
{
|
|
Logging.Debug("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.WaitOnCombination:
|
|
// Check if the result item is already created through combination
|
|
if (IsResultItemCreated(currentNode.combinationResultItemID))
|
|
{
|
|
// If it's already created, 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)
|
|
{
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// Set the flag that condition is satisfied
|
|
_conditionSatisfiedPendingAdvance = true;
|
|
|
|
UpdateDialogueVisibilityOnItemEvent();
|
|
}
|
|
}
|
|
|
|
private void OnAnyItemPickedUp(PickupItemData item)
|
|
{
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// Set the flag that condition is satisfied
|
|
_conditionSatisfiedPendingAdvance = true;
|
|
|
|
UpdateDialogueVisibilityOnItemEvent();
|
|
}
|
|
}
|
|
|
|
private void OnAnyItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem)
|
|
{
|
|
Logging.Debug("[DialogueComponent] OnAnyItemSlotted");
|
|
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// Set the flag that condition is satisfied
|
|
_conditionSatisfiedPendingAdvance = true;
|
|
|
|
UpdateDialogueVisibilityOnItemEvent();
|
|
}
|
|
}
|
|
|
|
private void OnAnyItemsCombined(PickupItemData itemA, PickupItemData itemB, PickupItemData resultItem)
|
|
{
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
// Only react if we're active and waiting on a combination
|
|
if (!IsActive || IsCompleted || currentNode == null ||
|
|
currentNode.nodeType != RuntimeDialogueNodeType.WaitOnCombination)
|
|
return;
|
|
|
|
// Check if this is the result item we're waiting for
|
|
if (resultItem.itemId == currentNode.combinationResultItemID)
|
|
{
|
|
// Set the flag that condition is satisfied
|
|
_conditionSatisfiedPendingAdvance = true;
|
|
|
|
UpdateDialogueVisibilityOnItemEvent();
|
|
}
|
|
}
|
|
|
|
private void OnAnyIncorrectItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem)
|
|
{
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
// Update the slot state for displaying the correct dialogue lines
|
|
if (!IsActive || IsCompleted || currentNode == null)
|
|
return;
|
|
|
|
// Only update state if we're actively waiting on this slot
|
|
if (currentNode.nodeType == RuntimeDialogueNodeType.WaitOnSlot &&
|
|
slotDefinition.itemId == currentNode.slotItemID)
|
|
{
|
|
_currentSlotState = ItemSlotState.Incorrect;
|
|
_lastSlottedItem = slottedItem;
|
|
|
|
UpdateDialogueVisibilityOnItemEvent();
|
|
}
|
|
}
|
|
|
|
private void OnAnyForbiddenItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem)
|
|
{
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
// Update the slot state for displaying the correct dialogue lines
|
|
if (!IsActive || IsCompleted || currentNode == null)
|
|
return;
|
|
|
|
// Only update state if we're actively waiting on this slot
|
|
if (currentNode.nodeType == RuntimeDialogueNodeType.WaitOnSlot &&
|
|
slotDefinition.itemId == currentNode.slotItemID)
|
|
{
|
|
_currentSlotState = ItemSlotState.Forbidden;
|
|
_lastSlottedItem = slottedItem;
|
|
|
|
UpdateDialogueVisibilityOnItemEvent();
|
|
}
|
|
}
|
|
|
|
private void OnAnyItemSlotCleared(PickupItemData removedItem)
|
|
{
|
|
// Initialize if needed
|
|
if (!initialized)
|
|
{
|
|
StartDialogue();
|
|
}
|
|
|
|
// Update the slot state when an item is removed
|
|
if (!IsActive || IsCompleted || currentNode == null)
|
|
return;
|
|
|
|
// Reset slot state if we were tracking this item
|
|
if (_lastSlottedItem != null && _lastSlottedItem == removedItem)
|
|
{
|
|
_currentSlotState = ItemSlotState.None;
|
|
_lastSlottedItem = null;
|
|
|
|
UpdateDialogueVisibilityOnItemEvent();
|
|
}
|
|
}
|
|
|
|
private void UpdateDialogueVisibilityOnItemEvent()
|
|
{
|
|
// If auto-play is enabled, immediately display dialogue
|
|
if (currentNode.shouldAutoPlay)
|
|
{
|
|
OnCharacterArrived();
|
|
}
|
|
else
|
|
{
|
|
// Manual mode: just update bubble visibility to show interaction prompt
|
|
if (speechBubble != null)
|
|
{
|
|
speechBubble.UpdatePromptVisibility(HasAnyLines());
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
case RuntimeDialogueNodeType.WaitOnCombination:
|
|
return !IsResultItemCreated(currentNode.combinationResultItemID);
|
|
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;
|
|
}
|
|
|
|
private bool IsResultItemCreated(string resultItemId)
|
|
{
|
|
if (ItemManager.Instance == null) return false;
|
|
|
|
// Use the ItemManager's tracking of items created through combination
|
|
return ItemManager.Instance.WasItemCreatedThroughCombination(resultItemId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the dialogue component has any lines available to serve
|
|
/// </summary>
|
|
/// <returns>True if there are lines available, false otherwise</returns>
|
|
public bool HasAnyLines()
|
|
{
|
|
if (!initialized)
|
|
{
|
|
// If not initialized yet but has a dialogue graph, it will have lines when initialized
|
|
return dialogueGraph != null;
|
|
}
|
|
|
|
// No lines if dialogue is not active or is completed
|
|
if (!IsActive || IsCompleted || currentNode == null)
|
|
return false;
|
|
|
|
// Special case: if condition has been satisfied but not yet advanced, we should show lines
|
|
if (_conditionSatisfiedPendingAdvance && !string.IsNullOrEmpty(currentNode.nextNodeID))
|
|
{
|
|
// Check if the next node would have lines or content
|
|
RuntimeDialogueNode nextNode = dialogueGraph.GetNodeByID(currentNode.nextNodeID);
|
|
return nextNode != null &&
|
|
((nextNode.dialogueLines != null && nextNode.dialogueLines.Count > 0) ||
|
|
(nextNode.dialogueContent != null && nextNode.dialogueContent.Count > 0) ||
|
|
nextNode.nodeType != RuntimeDialogueNodeType.End);
|
|
}
|
|
|
|
// For WaitOnSlot nodes, check for lines or content based on current slot state
|
|
if (currentNode.nodeType == RuntimeDialogueNodeType.WaitOnSlot)
|
|
{
|
|
// First check for DialogueContent
|
|
if (currentNode.dialogueContent != null && currentNode.dialogueContent.Count > 0)
|
|
{
|
|
// Choose the appropriate content collection based on the current slot state
|
|
List<DialogueContent> contentForState = currentNode.dialogueContent;
|
|
|
|
switch (_currentSlotState)
|
|
{
|
|
case ItemSlotState.Incorrect:
|
|
if (currentNode.incorrectItemContent != null && currentNode.incorrectItemContent.Count > 0)
|
|
contentForState = currentNode.incorrectItemContent;
|
|
break;
|
|
|
|
case ItemSlotState.Forbidden:
|
|
if (currentNode.forbiddenItemContent != null && currentNode.forbiddenItemContent.Count > 0)
|
|
contentForState = currentNode.forbiddenItemContent;
|
|
break;
|
|
}
|
|
|
|
if (contentForState.Count > 0)
|
|
{
|
|
if (currentLineIndex < contentForState.Count - 1 || currentNode.loopThroughLines)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fall back to legacy text lines
|
|
List<string> linesForState = currentNode.dialogueLines;
|
|
|
|
switch (_currentSlotState)
|
|
{
|
|
case ItemSlotState.Incorrect:
|
|
if (currentNode.incorrectItemLines != null && currentNode.incorrectItemLines.Count > 0)
|
|
linesForState = currentNode.incorrectItemLines;
|
|
break;
|
|
|
|
case ItemSlotState.Forbidden:
|
|
if (currentNode.forbiddenItemLines != null && currentNode.forbiddenItemLines.Count > 0)
|
|
linesForState = currentNode.forbiddenItemLines;
|
|
break;
|
|
}
|
|
|
|
if (linesForState != null && linesForState.Count > 0)
|
|
{
|
|
if (currentLineIndex < linesForState.Count - 1 || currentNode.loopThroughLines)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// For other node types, check for DialogueContent first, then fall back to legacy text
|
|
else
|
|
{
|
|
// Check for DialogueContent
|
|
if (currentNode.dialogueContent != null && currentNode.dialogueContent.Count > 0)
|
|
{
|
|
if (currentLineIndex < currentNode.dialogueContent.Count - 1 || currentNode.loopThroughLines)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If we're at the end of content but not waiting for a condition and have a next node
|
|
if (!IsWaitingForCondition() && !string.IsNullOrEmpty(currentNode.nextNodeID))
|
|
{
|
|
RuntimeDialogueNode nextNode = dialogueGraph.GetNodeByID(currentNode.nextNodeID);
|
|
return nextNode != null &&
|
|
((nextNode.dialogueContent != null && nextNode.dialogueContent.Count > 0) ||
|
|
(nextNode.dialogueLines != null && nextNode.dialogueLines.Count > 0) ||
|
|
nextNode.nodeType != RuntimeDialogueNodeType.End);
|
|
}
|
|
}
|
|
|
|
// Fall back to legacy text lines
|
|
if (currentNode.dialogueLines != null && currentNode.dialogueLines.Count > 0)
|
|
{
|
|
if (currentLineIndex < currentNode.dialogueLines.Count - 1 || currentNode.loopThroughLines)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If we're at the end of lines but not waiting for a condition and have a next node
|
|
if (!IsWaitingForCondition() && !string.IsNullOrEmpty(currentNode.nextNodeID))
|
|
{
|
|
RuntimeDialogueNode nextNode = dialogueGraph.GetNodeByID(currentNode.nextNodeID);
|
|
return nextNode != null &&
|
|
((nextNode.dialogueContent != null && nextNode.dialogueContent.Count > 0) ||
|
|
(nextNode.dialogueLines != null && nextNode.dialogueLines.Count > 0) ||
|
|
nextNode.nodeType != RuntimeDialogueNodeType.End);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Editor functionality
|
|
public void SetDialogueGraph(RuntimeDialogueGraph graph)
|
|
{
|
|
dialogueGraph = graph;
|
|
}
|
|
}
|
|
}
|