From cee5d575b1359c5f8d1a1d460bd37955c0e9f941 Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Mon, 29 Sep 2025 11:15:37 +0200 Subject: [PATCH] Working slotting items --- Assets/Scripts/Core/ItemManager.cs | 94 ++++++-- Assets/Scripts/Dialogue/DialogueComponent.cs | 237 ++++++++++++++----- Assets/Scripts/Interactions/ItemSlot.cs | 24 ++ 3 files changed, 274 insertions(+), 81 deletions(-) diff --git a/Assets/Scripts/Core/ItemManager.cs b/Assets/Scripts/Core/ItemManager.cs index e00162dc..aee51ecc 100644 --- a/Assets/Scripts/Core/ItemManager.cs +++ b/Assets/Scripts/Core/ItemManager.cs @@ -44,6 +44,18 @@ namespace Core // Args: slot's itemData (the slot definition), then the slotted item data public event Action OnCorrectItemSlotted; + // Broadcasts when any registered ItemSlot reports an incorrect item slotted + // Args: slot's itemData (the slot definition), then the slotted item data + public event Action OnIncorrectItemSlotted; + + // Broadcasts when any registered ItemSlot reports a forbidden item slotted + // Args: slot's itemData (the slot definition), then the slotted item data + public event Action OnForbiddenItemSlotted; + + // Broadcasts when any registered ItemSlot is cleared (item removed) + // Args: the item data that was removed + public event Action OnItemSlotCleared; + // Broadcasts when any two items are successfully combined // Args: first item data, second item data, result item data public event Action OnItemsCombined; @@ -81,6 +93,48 @@ namespace Core ClearAllRegistrations(); } + // Handler that forwards pickup events + private void Pickup_OnItemPickedUp(PickupItemData data) + { + OnItemPickedUp?.Invoke(data); + } + + // Handler that forwards correct-slot events + private void ItemSlot_OnCorrectItemSlotted(PickupItemData slotData, PickupItemData slottedItem) + { + OnCorrectItemSlotted?.Invoke(slotData, slottedItem); + } + + // Handler that forwards incorrect-slot events + private void ItemSlot_OnIncorrectItemSlotted(PickupItemData slotData, PickupItemData slottedItem) + { + OnIncorrectItemSlotted?.Invoke(slotData, slottedItem); + } + + // Handler that forwards forbidden-slot events + private void ItemSlot_OnForbiddenItemSlotted(PickupItemData slotData, PickupItemData slottedItem) + { + OnForbiddenItemSlotted?.Invoke(slotData, slottedItem); + } + + // Handler that forwards item combination events + private void Pickup_OnItemsCombined(PickupItemData itemA, PickupItemData itemB, PickupItemData resultItem) + { + // Track the created item + if (resultItem != null) + { + _itemsCreatedThroughCombination.Add(resultItem.itemId); + } + + OnItemsCombined?.Invoke(itemA, itemB, resultItem); + } + + // Handler that forwards slot-removed events + private void ItemSlot_OnItemSlotRemoved(PickupItemData removedItem) + { + OnItemSlotCleared?.Invoke(removedItem); + } + /// /// Unsubscribe all pickup/slot event handlers and clear registries and manager events. /// @@ -103,7 +157,12 @@ namespace Core foreach (var s in slotsCopy) { if (s != null) + { s.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted; + s.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted; + s.OnForbiddenItemSlotted -= ItemSlot_OnForbiddenItemSlotted; + s.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved; + } } _itemSlots.Clear(); @@ -113,6 +172,9 @@ namespace Core // Clear manager-level event subscribers OnItemPickedUp = null; OnCorrectItemSlotted = null; + OnIncorrectItemSlotted = null; + OnForbiddenItemSlotted = null; + OnItemSlotCleared = null; OnItemsCombined = null; } @@ -142,7 +204,11 @@ namespace Core if (slot == null) return; if (_itemSlots.Add(slot)) { + // Subscribe to all slot events slot.OnCorrectItemSlotted += ItemSlot_OnCorrectItemSlotted; + slot.OnIncorrectItemSlotted += ItemSlot_OnIncorrectItemSlotted; + slot.OnForbiddenItemSlotted += ItemSlot_OnForbiddenItemSlotted; + slot.OnItemSlotRemoved += ItemSlot_OnItemSlotRemoved; } } @@ -151,34 +217,14 @@ namespace Core if (slot == null) return; if (_itemSlots.Remove(slot)) { + // Unsubscribe from all slot events slot.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted; + slot.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted; + slot.OnForbiddenItemSlotted -= ItemSlot_OnForbiddenItemSlotted; + slot.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved; } } - // Handler that forwards pickup events - private void Pickup_OnItemPickedUp(PickupItemData data) - { - OnItemPickedUp?.Invoke(data); - } - - // Handler that forwards correct-slot events - private void ItemSlot_OnCorrectItemSlotted(PickupItemData slotData, PickupItemData slottedItem) - { - OnCorrectItemSlotted?.Invoke(slotData, slottedItem); - } - - // Handler that forwards item combination events - private void Pickup_OnItemsCombined(PickupItemData itemA, PickupItemData itemB, PickupItemData resultItem) - { - // Track the created item - if (resultItem != null) - { - _itemsCreatedThroughCombination.Add(resultItem.itemId); - } - - OnItemsCombined?.Invoke(itemA, itemB, resultItem); - } - /// /// Checks if a specific item has been created through item combination. /// diff --git a/Assets/Scripts/Dialogue/DialogueComponent.cs b/Assets/Scripts/Dialogue/DialogueComponent.cs index fb20143b..d66c3d10 100644 --- a/Assets/Scripts/Dialogue/DialogueComponent.cs +++ b/Assets/Scripts/Dialogue/DialogueComponent.cs @@ -18,6 +18,13 @@ namespace Dialogue private bool initialized = false; private SpeechBubble speechBubble; + // 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; } @@ -36,6 +43,9 @@ namespace Dialogue { ItemManager.Instance.OnItemPickedUp += OnAnyItemPickedUp; ItemManager.Instance.OnCorrectItemSlotted += OnAnyItemSlotted; + ItemManager.Instance.OnIncorrectItemSlotted += OnAnyIncorrectItemSlotted; + ItemManager.Instance.OnForbiddenItemSlotted += OnAnyForbiddenItemSlotted; + ItemManager.Instance.OnItemSlotCleared += OnAnyItemSlotCleared; ItemManager.Instance.OnItemsCombined += OnAnyItemsCombined; } @@ -87,6 +97,9 @@ namespace Dialogue { ItemManager.Instance.OnItemPickedUp -= OnAnyItemPickedUp; ItemManager.Instance.OnCorrectItemSlotted -= OnAnyItemSlotted; + ItemManager.Instance.OnIncorrectItemSlotted -= OnAnyIncorrectItemSlotted; + ItemManager.Instance.OnForbiddenItemSlotted -= OnAnyForbiddenItemSlotted; + ItemManager.Instance.OnItemSlotCleared -= OnAnyItemSlotCleared; ItemManager.Instance.OnItemsCombined -= OnAnyItemsCombined; } } @@ -133,17 +146,55 @@ namespace Dialogue StartDialogue(); } - if (!IsActive || IsCompleted || currentNode == null || currentNode.dialogueLines.Count == 0) + 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 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]; } - Debug.Log("Returning line: " + currentLine); - // Return the current line + return currentLine; } @@ -155,6 +206,14 @@ namespace Dialogue 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; + } + // If we have more lines in the current node, advance to the next line if (currentLineIndex < currentNode.dialogueLines.Count - 1) { @@ -186,6 +245,8 @@ namespace Dialogue private void MoveToNextNode() { + Debug.Log("MoveToNextNode"); + // If there's no next node, complete the dialogue if (string.IsNullOrEmpty(currentNode.nextNodeID)) { @@ -276,14 +337,10 @@ namespace Dialogue // 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(); + // Instead of immediately moving to the next node, set the flag + _conditionSatisfiedPendingAdvance = true; - // Notify any listeners about the dialogue change - // string line = GetCurrentDialogueLine(); - // OnDialogueChanged?.Invoke(line); - - // Update bubble visibility after state change + // Update bubble visibility after state change to show interaction prompt if (speechBubble != null) { speechBubble.UpdatePromptVisibility(HasAnyLines()); @@ -301,25 +358,15 @@ namespace Dialogue // 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(); + // Instead of immediately moving to the next node, set the flag + _conditionSatisfiedPendingAdvance = true; - // Notify any listeners about the dialogue change - // string line = GetCurrentDialogueLine(); - // OnDialogueChanged?.Invoke(line); - - // Update bubble visibility after state change + // Update bubble visibility after state change to show interaction prompt if (speechBubble != null) { speechBubble.UpdatePromptVisibility(HasAnyLines()); } } - - // Always check if any dialogue was unblocked by this pickup - // if (speechBubble != null) - // { - // speechBubble.UpdatePromptVisibility(HasAnyLines()); - // } } private void OnAnyItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem) @@ -334,25 +381,15 @@ namespace Dialogue // 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(); + // Instead of immediately moving to the next node, set the flag + _conditionSatisfiedPendingAdvance = true; - // Notify any listeners about the dialogue change - // string line = GetCurrentDialogueLine(); - // OnDialogueChanged?.Invoke(line); - - // Update bubble visibility after state change + // Update bubble visibility after state change to show interaction prompt if (speechBubble != null) { speechBubble.UpdatePromptVisibility(HasAnyLines()); } } - - // Always check if any dialogue was unblocked by this slotting - // if (speechBubble != null) - // { - // speechBubble.UpdatePromptVisibility(HasAnyLines()); - // } } private void OnAnyItemsCombined(PickupItemData itemA, PickupItemData itemB, PickupItemData resultItem) @@ -365,25 +402,77 @@ namespace Dialogue // Check if this is the result item we're waiting for if (resultItem.itemId == currentNode.combinationResultItemID) { - // Move to next node automatically when condition is met - MoveToNextNode(); + // Instead of immediately moving to the next node, set the flag + _conditionSatisfiedPendingAdvance = true; - // Notify any listeners about the dialogue change - // string line = GetCurrentDialogueLine(); - // OnDialogueChanged?.Invoke(line); - - // Update bubble visibility after state change + // Update bubble visibility after state change to show interaction prompt + if (speechBubble != null) + { + speechBubble.UpdatePromptVisibility(HasAnyLines()); + } + } + } + + private void OnAnyIncorrectItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem) + { + // 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; + + // Trigger dialogue update + if (speechBubble != null) + { + speechBubble.UpdatePromptVisibility(HasAnyLines()); + } + } + } + + private void OnAnyForbiddenItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem) + { + // 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; + + // Trigger dialogue update + if (speechBubble != null) + { + speechBubble.UpdatePromptVisibility(HasAnyLines()); + } + } + } + + private void OnAnyItemSlotCleared(PickupItemData removedItem) + { + // 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; + + // Trigger dialogue update if (speechBubble != null) { speechBubble.UpdatePromptVisibility(HasAnyLines()); } } - - // Always check if any dialogue was unblocked by this combination - // if (speechBubble != null) - // { - // speechBubble.UpdatePromptVisibility(HasAnyLines()); - // } } // Helper methods @@ -468,8 +557,47 @@ namespace Dialogue if (!IsActive || IsCompleted || currentNode == null) return false; - // Check if the current node has any lines - if (currentNode.dialogueLines.Count > 0) + // 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 + RuntimeDialogueNode nextNode = dialogueGraph.GetNodeByID(currentNode.nextNodeID); + return nextNode != null && (nextNode.dialogueLines.Count > 0 || nextNode.nodeType != RuntimeDialogueNodeType.End); + } + + // For WaitOnSlot nodes, check for lines based on current slot state + if (currentNode.nodeType == RuntimeDialogueNodeType.WaitOnSlot) + { + // Choose the appropriate line collection based on the current slot state + List 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; + } + + // Check if we have any lines for the current state + if (linesForState != null && linesForState.Count > 0) + { + // If we're not at the end of the lines or we loop through them + if (currentLineIndex < linesForState.Count - 1 || currentNode.loopThroughLines) + { + return true; + } + } + } + // For other node types, use the standard check + else if (currentNode.dialogueLines.Count > 0) { // If we're not at the end of the lines or we loop through them if (currentLineIndex < currentNode.dialogueLines.Count - 1 || currentNode.loopThroughLines) @@ -486,12 +614,7 @@ namespace Dialogue } } - // Special case for conditional nodes waiting on conditions - // if (IsWaitingForCondition()) - // { - // return currentNode.dialogueLines.Count > 0; - // } - + return false; } diff --git a/Assets/Scripts/Interactions/ItemSlot.cs b/Assets/Scripts/Interactions/ItemSlot.cs index 23e637ff..409b9edf 100644 --- a/Assets/Scripts/Interactions/ItemSlot.cs +++ b/Assets/Scripts/Interactions/ItemSlot.cs @@ -31,11 +31,21 @@ namespace Interactions public UnityEvent onItemSlotted; public UnityEvent onItemSlotRemoved; + // Native C# event alternative for code-only subscribers + public event Action OnItemSlotRemoved; + public UnityEvent onCorrectItemSlotted; // Native C# event alternative to the UnityEvent for code-only subscribers public event Action OnCorrectItemSlotted; + public UnityEvent onIncorrectItemSlotted; + // Native C# event alternative for code-only subscribers + public event Action OnIncorrectItemSlotted; + public UnityEvent onForbiddenItemSlotted; + // Native C# event alternative for code-only subscribers + public event Action OnForbiddenItemSlotted; + private PickupItemData _currentlySlottedItemData; public SpriteRenderer slottedItemRenderer; private GameObject _currentlySlottedItemObject = null; @@ -71,6 +81,7 @@ namespace Interactions { DebugUIMessage.Show("Can't place that here.", Color.red); onForbiddenItemSlotted?.Invoke(); + OnForbiddenItemSlotted?.Invoke(itemData, heldItemData); _currentState = ItemSlotState.Forbidden; Interactable.BroadcastInteractionComplete(false); return; @@ -86,6 +97,7 @@ namespace Interactions { FollowerController.TryPickupItem(_currentlySlottedItemObject, _currentlySlottedItemData, false); onItemSlotRemoved?.Invoke(); + OnItemSlotRemoved?.Invoke(_currentlySlottedItemData); _currentState = ItemSlotState.None; SlotItem(heldItemObj, heldItemData, _currentlySlottedItemObject == null); return; @@ -130,12 +142,22 @@ namespace Interactions public void SlotItem(GameObject itemToSlot, PickupItemData itemToSlotData, bool clearFollowerHeldItem = true) { + // Cache the previous item data before clearing, needed for OnItemSlotRemoved event + var previousItemData = _currentlySlottedItemData; + bool wasSlotCleared = _currentlySlottedItemObject != null && itemToSlot == null; + if (itemToSlot == null) { _currentlySlottedItemObject = null; _currentlySlottedItemData = null; // Clear state when no item is slotted _currentState = ItemSlotState.None; + + // Fire native event for slot clearing + if (wasSlotCleared) + { + OnItemSlotRemoved?.Invoke(previousItemData); + } } else { @@ -144,6 +166,7 @@ namespace Interactions SetSlottedObject(itemToSlot); _currentlySlottedItemData = itemToSlotData; } + if (clearFollowerHeldItem) { FollowerController.ClearHeldItem(); @@ -172,6 +195,7 @@ namespace Interactions { DebugUIMessage.Show("I'm not sure this works.", Color.yellow); onIncorrectItemSlotted?.Invoke(); + OnIncorrectItemSlotted?.Invoke(itemData, _currentlySlottedItemData); _currentState = ItemSlotState.Incorrect; } Interactable.BroadcastInteractionComplete(false);