Create a simple dialogue authoring system, tied into our items #10

Merged
tschesky merged 9 commits from dialogue_graph into main 2025-09-29 09:34:15 +00:00
72 changed files with 6319 additions and 173 deletions
Showing only changes of commit cee5d575b1 - Show all commits

View File

@@ -44,6 +44,18 @@ namespace Core
// Args: slot's itemData (the slot definition), then the slotted item data
public event Action<PickupItemData, PickupItemData> 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<PickupItemData, PickupItemData> 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<PickupItemData, PickupItemData> OnForbiddenItemSlotted;
// Broadcasts when any registered ItemSlot is cleared (item removed)
// Args: the item data that was removed
public event Action<PickupItemData> OnItemSlotCleared;
// Broadcasts when any two items are successfully combined
// Args: first item data, second item data, result item data
public event Action<PickupItemData, PickupItemData, PickupItemData> 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);
}
/// <summary>
/// Unsubscribe all pickup/slot event handlers and clear registries and manager events.
/// </summary>
@@ -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);
}
/// <summary>
/// Checks if a specific item has been created through item combination.
/// </summary>

View File

@@ -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<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];
}
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<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;
}
// 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;
}

View File

@@ -31,11 +31,21 @@ namespace Interactions
public UnityEvent onItemSlotted;
public UnityEvent onItemSlotRemoved;
// Native C# event alternative for code-only subscribers
public event Action<PickupItemData> OnItemSlotRemoved;
public UnityEvent onCorrectItemSlotted;
// Native C# event alternative to the UnityEvent for code-only subscribers
public event Action<PickupItemData, PickupItemData> OnCorrectItemSlotted;
public UnityEvent onIncorrectItemSlotted;
// Native C# event alternative for code-only subscribers
public event Action<PickupItemData, PickupItemData> OnIncorrectItemSlotted;
public UnityEvent onForbiddenItemSlotted;
// Native C# event alternative for code-only subscribers
public event Action<PickupItemData, PickupItemData> 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);