Update slot items to simplified slotting logic + reveal renderers
This commit is contained in:
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Interactions;
|
||||
using Core.Lifecycle;
|
||||
using Core.SaveLoad;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
@@ -31,18 +30,10 @@ namespace Core
|
||||
// Broadcasts when any registered ItemSlot reports a correct item slotted
|
||||
// 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 a wrong item is attempted (not slotted, just attempted)
|
||||
// Args: slot's itemData (the slot definition), then the attempted item data
|
||||
public event Action<PickupItemData, PickupItemData> OnWrongItemAttempted;
|
||||
|
||||
// Broadcasts when any two items are successfully combined
|
||||
// Args: first item data, second item data, result item data
|
||||
@@ -82,17 +73,11 @@ namespace Core
|
||||
{
|
||||
OnCorrectItemSlotted?.Invoke(slotData, slottedItem);
|
||||
}
|
||||
|
||||
// Handler that forwards incorrect-slot events
|
||||
private void ItemSlot_OnIncorrectItemSlotted(PickupItemData slotData, PickupItemData slottedItem)
|
||||
|
||||
// Handler that forwards wrong-item-attempted events
|
||||
private void ItemSlot_OnWrongItemAttempted(PickupItemData slotData, PickupItemData attemptedItem)
|
||||
{
|
||||
OnIncorrectItemSlotted?.Invoke(slotData, slottedItem);
|
||||
}
|
||||
|
||||
// Handler that forwards forbidden-slot events
|
||||
private void ItemSlot_OnForbiddenItemSlotted(PickupItemData slotData, PickupItemData slottedItem)
|
||||
{
|
||||
OnForbiddenItemSlotted?.Invoke(slotData, slottedItem);
|
||||
OnWrongItemAttempted?.Invoke(slotData, attemptedItem);
|
||||
}
|
||||
|
||||
// Handler that forwards item combination events
|
||||
@@ -107,12 +92,6 @@ namespace Core
|
||||
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>
|
||||
@@ -137,8 +116,7 @@ namespace Core
|
||||
if (s != null)
|
||||
{
|
||||
s.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted;
|
||||
s.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted;
|
||||
s.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved;
|
||||
s.OnWrongItemAttempted -= ItemSlot_OnWrongItemAttempted;
|
||||
}
|
||||
}
|
||||
_itemSlots.Clear();
|
||||
@@ -149,9 +127,7 @@ namespace Core
|
||||
// Clear manager-level event subscribers
|
||||
OnItemPickedUp = null;
|
||||
OnCorrectItemSlotted = null;
|
||||
OnIncorrectItemSlotted = null;
|
||||
OnForbiddenItemSlotted = null;
|
||||
OnItemSlotCleared = null;
|
||||
OnWrongItemAttempted = null;
|
||||
OnItemsCombined = null;
|
||||
}
|
||||
|
||||
@@ -181,10 +157,9 @@ namespace Core
|
||||
if (slot == null) return;
|
||||
if (_itemSlots.Add(slot))
|
||||
{
|
||||
// Subscribe to all slot events
|
||||
// Subscribe to active slot events only
|
||||
slot.OnCorrectItemSlotted += ItemSlot_OnCorrectItemSlotted;
|
||||
slot.OnIncorrectItemSlotted += ItemSlot_OnIncorrectItemSlotted;
|
||||
slot.OnItemSlotRemoved += ItemSlot_OnItemSlotRemoved;
|
||||
slot.OnWrongItemAttempted += ItemSlot_OnWrongItemAttempted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,10 +168,9 @@ namespace Core
|
||||
if (slot == null) return;
|
||||
if (_itemSlots.Remove(slot))
|
||||
{
|
||||
// Unsubscribe from all slot events
|
||||
// Unsubscribe from active slot events
|
||||
slot.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted;
|
||||
slot.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted;
|
||||
slot.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved;
|
||||
slot.OnWrongItemAttempted -= ItemSlot_OnWrongItemAttempted;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,7 @@ namespace Dialogue
|
||||
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.OnWrongItemAttempted += OnAnyWrongItemAttempted;
|
||||
ItemManager.Instance.OnItemsCombined += OnAnyItemsCombined;
|
||||
}
|
||||
|
||||
@@ -191,9 +189,7 @@ namespace Dialogue
|
||||
{
|
||||
ItemManager.Instance.OnItemPickedUp -= OnAnyItemPickedUp;
|
||||
ItemManager.Instance.OnCorrectItemSlotted -= OnAnyItemSlotted;
|
||||
ItemManager.Instance.OnIncorrectItemSlotted -= OnAnyIncorrectItemSlotted;
|
||||
ItemManager.Instance.OnForbiddenItemSlotted -= OnAnyForbiddenItemSlotted;
|
||||
ItemManager.Instance.OnItemSlotCleared -= OnAnyItemSlotCleared;
|
||||
ItemManager.Instance.OnWrongItemAttempted -= OnAnyWrongItemAttempted;
|
||||
ItemManager.Instance.OnItemsCombined -= OnAnyItemsCombined;
|
||||
}
|
||||
}
|
||||
@@ -538,7 +534,7 @@ namespace Dialogue
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnyIncorrectItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem)
|
||||
private void OnAnyWrongItemAttempted(PickupItemData slotDefinition, PickupItemData attemptedItem)
|
||||
{
|
||||
// Initialize if needed
|
||||
if (!initialized)
|
||||
@@ -554,53 +550,10 @@ namespace Dialogue
|
||||
if (currentNode.nodeType == RuntimeDialogueNodeType.WaitOnSlot &&
|
||||
slotDefinition.itemId == currentNode.slotItemID)
|
||||
{
|
||||
// Treat as Incorrect for dialogue purposes
|
||||
_currentSlotState = ItemSlotState.Incorrect;
|
||||
_lastSlottedItem = slottedItem;
|
||||
_lastSlottedItem = attemptedItem;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq; // for Count() on List<bool>
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System; // for Action<T>
|
||||
using Core; // register with ItemManager
|
||||
using AppleHills.Core.Settings;
|
||||
using Core.Settings; // Added for IInteractionSettings
|
||||
|
||||
namespace Interactions
|
||||
@@ -18,6 +16,16 @@ namespace Interactions
|
||||
Forbidden
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display behavior for slot renderers
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public enum SlotRendererDisplayType
|
||||
{
|
||||
UseConfig = 0, // Assign sprite from slotted item's config (default)
|
||||
RevealExisting = 1 // Just make the renderer visible (sprite already on renderer)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a sprite renderer to an optional item assignment
|
||||
/// </summary>
|
||||
@@ -27,6 +35,8 @@ namespace Interactions
|
||||
public SpriteRenderer renderer;
|
||||
[Tooltip("Optional: If set, this renderer slot is dedicated to this specific item. Leave null for flexible slots.")]
|
||||
public PickupItemData assignedItem;
|
||||
[Tooltip("Display behavior: UseConfig assigns sprite from item config, RevealExisting just makes renderer visible")]
|
||||
public SlotRendererDisplayType displayType = SlotRendererDisplayType.UseConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,8 +52,9 @@ namespace Interactions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interaction that allows slotting, swapping, or picking up items in a slot.
|
||||
/// Interaction that allows slotting items in a slot.
|
||||
/// ItemSlot is a CONTAINER, not a Pickup itself.
|
||||
/// Items cannot be removed or swapped once slotted (except via combinations).
|
||||
/// </summary>
|
||||
public class ItemSlot : SaveableInteractable
|
||||
{
|
||||
@@ -51,11 +62,10 @@ namespace Interactions
|
||||
public PickupItemData itemData;
|
||||
public SpriteRenderer iconRenderer;
|
||||
|
||||
// Multi-slot item tracking
|
||||
// Multi-slot item tracking - now only stores correct items (dense arrays)
|
||||
private List<PickupItemData> slottedItemsData = new List<PickupItemData>();
|
||||
public SlotRendererMapping[] slottedItemRenderers; // Array of renderers with optional item assignments
|
||||
private List<GameObject> slottedItemObjects = new List<GameObject>();
|
||||
private List<bool> slottedItemCorrectness = new List<bool>(); // Track which items are correct
|
||||
|
||||
// Tracks the current state of the slotted item(s)
|
||||
private ItemSlotState currentState = ItemSlotState.None;
|
||||
@@ -72,26 +82,14 @@ namespace Interactions
|
||||
public ItemSlotState CurrentSlottedState => currentState;
|
||||
|
||||
/// <summary>
|
||||
/// Number of items currently slotted (correct or incorrect)
|
||||
/// Number of items currently slotted (all are correct in new system)
|
||||
/// </summary>
|
||||
public int CurrentSlottedCount => slottedItemObjects.Count(obj => obj != null);
|
||||
public int CurrentSlottedCount => slottedItemObjects.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Number of CORRECT items currently slotted
|
||||
/// Number of CORRECT items currently slotted (all items are correct now)
|
||||
/// </summary>
|
||||
public int CurrentCorrectCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < slottedItemCorrectness.Count; i++)
|
||||
{
|
||||
if (i < slottedItemObjects.Count && slottedItemObjects[i] != null && slottedItemCorrectness[i])
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
public int CurrentCorrectCount => slottedItemObjects.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Number of items required to complete this slot
|
||||
@@ -125,21 +123,15 @@ namespace Interactions
|
||||
/// </summary>
|
||||
private bool IsMultiSlot => slottedItemRenderers != null && slottedItemRenderers.Length > 1;
|
||||
|
||||
// Event dispatchers (only 3 events needed)
|
||||
public UnityEvent onItemSlotted;
|
||||
public UnityEvent onItemSlotRemoved;
|
||||
// Native C# event alternatives for code-only subscribers
|
||||
public event Action<PickupItemData, PickupItemData> OnItemSlotted; // (slotData, slottedItemData)
|
||||
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;
|
||||
public event Action<PickupItemData, PickupItemData> OnCorrectItemSlotted; // (slotData, slottedItemData)
|
||||
|
||||
public UnityEvent onWrongItemAttempted;
|
||||
public event Action<PickupItemData, PickupItemData> OnWrongItemAttempted; // (slotData, attemptedItem)
|
||||
|
||||
/// <summary>
|
||||
/// Get the first (or only) slotted object - for backward compatibility
|
||||
@@ -180,6 +172,19 @@ namespace Interactions
|
||||
|
||||
// Initialize settings references
|
||||
interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
|
||||
|
||||
// Hide all RevealExisting renderers at start
|
||||
if (slottedItemRenderers != null)
|
||||
{
|
||||
foreach (var mapping in slottedItemRenderers)
|
||||
{
|
||||
if (mapping != null && mapping.renderer != null &&
|
||||
mapping.displayType == SlotRendererDisplayType.RevealExisting)
|
||||
{
|
||||
mapping.renderer.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
@@ -213,6 +218,8 @@ namespace Interactions
|
||||
|
||||
/// <summary>
|
||||
/// Validation: Check if interaction can proceed based on held item and slot state.
|
||||
/// Now allows all items to proceed - wrong items will trigger reactions without slotting.
|
||||
/// No unslotting or swapping allowed.
|
||||
/// </summary>
|
||||
protected override (bool canProceed, string errorMessage) CanProceedWithInteraction()
|
||||
{
|
||||
@@ -231,32 +238,21 @@ namespace Interactions
|
||||
if (heldItem == null && CurrentSlottedCount == 0)
|
||||
return (false, "This requires an item.");
|
||||
|
||||
// If holding an item and slot is full but not complete, allow swap
|
||||
// Scenario: Nothing held + Has items = Can't remove (no unslotting)
|
||||
if (heldItem == null && CurrentSlottedCount > 0)
|
||||
return (false, "I can't remove these items.");
|
||||
|
||||
// Scenario: Holding item + Slot full = Error (no swapping)
|
||||
if (heldItem != null && !HasSpace)
|
||||
{
|
||||
// Allow swap for fixing mistakes (single-slot or multi-slot not complete)
|
||||
if (!IsMultiSlot || !IsComplete)
|
||||
return (true, null); // Allow swap
|
||||
|
||||
// Multi-slot is complete - can't swap
|
||||
return (false, "This slot is full.");
|
||||
}
|
||||
|
||||
// Check forbidden items if trying to slot
|
||||
if (heldItem != null)
|
||||
{
|
||||
var config = interactionSettings?.GetSlotItemConfig(itemData);
|
||||
var forbidden = config?.forbiddenItems ?? new List<PickupItemData>();
|
||||
|
||||
if (PickupItemData.ListContainsEquivalent(forbidden, heldItem))
|
||||
return (false, "Can't place that here.");
|
||||
}
|
||||
|
||||
// All other cases proceed (holding item + has space)
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main interaction logic: Slot, pickup, swap, or combine items.
|
||||
/// Main interaction logic: Slot correct items or trigger reactions for wrong items.
|
||||
/// No swapping, unslotting, or combinations allowed - items stay once slotted.
|
||||
/// Returns true only if correct item was slotted AND slot is now complete.
|
||||
/// </summary>
|
||||
protected override bool DoInteraction()
|
||||
@@ -266,221 +262,60 @@ namespace Interactions
|
||||
var heldItemData = FollowerController.CurrentlyHeldItemData;
|
||||
var heldItemObj = FollowerController.GetHeldPickupObject();
|
||||
|
||||
// Scenario 1: Held item + Has space = Slot it
|
||||
// Only scenario: Held item + Has space = Check if allowed, then slot or react
|
||||
if (heldItemData != null && HasSpace)
|
||||
{
|
||||
SlotItem(heldItemObj, heldItemData);
|
||||
FollowerController.ClearHeldItem(); // Clear follower's hand after slotting
|
||||
var config = interactionSettings?.GetSlotItemConfig(itemData);
|
||||
var allowed = config?.allowedItems ?? new List<PickupItemData>();
|
||||
|
||||
// Check if we completed the slot
|
||||
if (IsComplete)
|
||||
// Check if item is allowed
|
||||
if (PickupItemData.ListContainsEquivalent(allowed, heldItemData))
|
||||
{
|
||||
isLockedAfterCompletion = true;
|
||||
currentState = ItemSlotState.Correct;
|
||||
return true; // Completed!
|
||||
}
|
||||
|
||||
return false; // Slotted but not complete yet
|
||||
}
|
||||
|
||||
// Scenario 2 & 3: Slot is full
|
||||
if (CurrentSlottedCount > 0)
|
||||
{
|
||||
// Try combination if both items present (only for single slots)
|
||||
if (heldItemData != null && !IsMultiSlot)
|
||||
{
|
||||
var slottedPickup = slottedItemObjects[0].GetComponent<Pickup>();
|
||||
if (slottedPickup != null)
|
||||
// CORRECT ITEM - Slot it
|
||||
SlotItem(heldItemObj, heldItemData);
|
||||
FollowerController.ClearHeldItem(); // Clear follower's hand after slotting
|
||||
|
||||
// Check if we completed the slot
|
||||
if (IsComplete)
|
||||
{
|
||||
var comboResult = FollowerController.TryCombineItems(slottedPickup, out _);
|
||||
|
||||
if (comboResult == FollowerController.CombinationResult.Successful)
|
||||
{
|
||||
// Combination succeeded - clear slot and return false (not a "slot success")
|
||||
ClearSlot();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Swap behavior when slot is full (single slots OR multi-slots that aren't complete)
|
||||
if (heldItemData != null && !HasSpace)
|
||||
{
|
||||
// For single slots: always allow swap
|
||||
// For multi-slots: only allow swap if not complete yet (allows fixing mistakes)
|
||||
if (!IsMultiSlot || !IsComplete)
|
||||
{
|
||||
// Determine target index for the new item
|
||||
int targetIndex = GetTargetSlotIndexForItem(heldItemData);
|
||||
|
||||
int indexToSwap;
|
||||
if (targetIndex >= 0 && targetIndex < slottedItemObjects.Count && slottedItemObjects[targetIndex] != null)
|
||||
{
|
||||
// Swap with the item in the assigned slot
|
||||
indexToSwap = targetIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// LIFO swap - swap with the last non-null item
|
||||
indexToSwap = -1;
|
||||
for (int i = slottedItemObjects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (slottedItemObjects[i] != null)
|
||||
{
|
||||
indexToSwap = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (indexToSwap < 0)
|
||||
{
|
||||
// No items to swap (shouldn't happen, but fallback)
|
||||
SlotItem(heldItemObj, heldItemData);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var itemToReturn = slottedItemObjects[indexToSwap];
|
||||
var itemDataToReturn = slottedItemsData[indexToSwap];
|
||||
|
||||
// Step 1: Give old item to follower
|
||||
FollowerController.TryPickupItem(itemToReturn, itemDataToReturn, dropItem: false);
|
||||
|
||||
// Step 2: Remove old item from slot
|
||||
RemoveItemAtIndex(indexToSwap);
|
||||
|
||||
// Step 3: Slot the new item
|
||||
SlotItem(heldItemObj, heldItemData);
|
||||
|
||||
// Check if we completed the slot with this swap
|
||||
if (IsComplete)
|
||||
{
|
||||
isLockedAfterCompletion = true;
|
||||
currentState = ItemSlotState.Correct;
|
||||
return true; // Completed!
|
||||
}
|
||||
|
||||
return false; // Swapped but not complete
|
||||
}
|
||||
}
|
||||
|
||||
// Pickup from slot (empty hands) - LIFO removal
|
||||
if (heldItemData == null)
|
||||
{
|
||||
// Find last non-null item
|
||||
int lastIndex = -1;
|
||||
for (int i = slottedItemObjects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (slottedItemObjects[i] != null)
|
||||
{
|
||||
lastIndex = i;
|
||||
break;
|
||||
}
|
||||
isLockedAfterCompletion = true;
|
||||
currentState = ItemSlotState.Correct;
|
||||
return true; // Completed!
|
||||
}
|
||||
|
||||
if (lastIndex >= 0)
|
||||
{
|
||||
var itemToPickup = slottedItemObjects[lastIndex];
|
||||
var itemDataToPickup = slottedItemsData[lastIndex];
|
||||
|
||||
// Try to give item to follower
|
||||
FollowerController.TryPickupItem(itemToPickup, itemDataToPickup, dropItem: false);
|
||||
|
||||
// Remove from slot
|
||||
RemoveItemAtIndex(lastIndex);
|
||||
}
|
||||
|
||||
// Just picked up from slot - not a success
|
||||
return false; // Slotted but not complete yet
|
||||
}
|
||||
else
|
||||
{
|
||||
// WRONG ITEM - Trigger reaction, don't slot, item stays in hand
|
||||
TriggerWrongItemReaction(heldItemData);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't reach here (validation prevents empty + no held)
|
||||
// No other scenarios
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper: Clear the slot and fire removal events.
|
||||
/// Triggers a "wrong item" reaction without slotting.
|
||||
/// Integrates with DialogueComponent for NPC feedback.
|
||||
/// Item stays in player's hand.
|
||||
/// </summary>
|
||||
private void ClearSlot()
|
||||
private void TriggerWrongItemReaction(PickupItemData wrongItem)
|
||||
{
|
||||
// Find first non-null data for event
|
||||
PickupItemData previousData = null;
|
||||
foreach (var data in slottedItemsData)
|
||||
{
|
||||
if (data != null)
|
||||
{
|
||||
previousData = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
DebugUIMessage.Show($"{wrongItem.itemName} doesn't belong here.", Color.yellow);
|
||||
|
||||
// Clear all pickup's OwningSlot references
|
||||
foreach (var itemObj in slottedItemObjects)
|
||||
{
|
||||
if (itemObj != null)
|
||||
{
|
||||
var pickup = itemObj.GetComponent<Pickup>();
|
||||
if (pickup != null)
|
||||
{
|
||||
pickup.OwningSlot = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fire event for DialogueComponent integration
|
||||
onWrongItemAttempted?.Invoke();
|
||||
OnWrongItemAttempted?.Invoke(itemData, wrongItem);
|
||||
|
||||
slottedItemObjects.Clear();
|
||||
slottedItemsData.Clear();
|
||||
slottedItemCorrectness.Clear(); // Also clear correctness tracking
|
||||
currentState = ItemSlotState.None;
|
||||
isLockedAfterCompletion = false;
|
||||
UpdateSlottedSprite();
|
||||
|
||||
// Fire removal events
|
||||
onItemSlotRemoved?.Invoke();
|
||||
OnItemSlotRemoved?.Invoke(previousData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper: Remove a specific item from the slot by index.
|
||||
/// </summary>
|
||||
private void RemoveItemAtIndex(int index)
|
||||
{
|
||||
if (index < 0 || index >= slottedItemObjects.Count)
|
||||
return;
|
||||
|
||||
var itemObj = slottedItemObjects[index];
|
||||
var removedItemData = slottedItemsData[index];
|
||||
|
||||
// Clear the pickup's OwningSlot reference
|
||||
if (itemObj != null)
|
||||
{
|
||||
var pickup = itemObj.GetComponent<Pickup>();
|
||||
if (pickup != null)
|
||||
{
|
||||
pickup.OwningSlot = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Set to null instead of removing to maintain sparse storage
|
||||
slottedItemObjects[index] = null;
|
||||
slottedItemsData[index] = null;
|
||||
slottedItemCorrectness[index] = false;
|
||||
|
||||
if (CurrentSlottedCount == 0)
|
||||
{
|
||||
currentState = ItemSlotState.None;
|
||||
isLockedAfterCompletion = false;
|
||||
}
|
||||
|
||||
UpdateSlottedSprite();
|
||||
|
||||
// Fire removal events
|
||||
onItemSlotRemoved?.Invoke();
|
||||
OnItemSlotRemoved?.Invoke(removedItemData);
|
||||
// Item stays in player's hand - no state change
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Visual Updates
|
||||
@@ -488,11 +323,12 @@ namespace Interactions
|
||||
/// <summary>
|
||||
/// Determines the best slot index for an item based on assignments and availability.
|
||||
/// Returns -1 if no mappings are configured (uses append behavior).
|
||||
/// Simplified for dense arrays.
|
||||
/// </summary>
|
||||
private int GetTargetSlotIndexForItem(PickupItemData itemData)
|
||||
{
|
||||
if (slottedItemRenderers == null || slottedItemRenderers.Length == 0)
|
||||
return -1;
|
||||
return -1; // Append behavior
|
||||
|
||||
// Check if any renderer has assignments configured
|
||||
bool hasAnyAssignments = false;
|
||||
@@ -509,7 +345,7 @@ namespace Interactions
|
||||
if (!hasAnyAssignments)
|
||||
return -1;
|
||||
|
||||
// Step 1: Check if this item has a dedicated assigned slot
|
||||
// Check if this item has a dedicated assigned slot
|
||||
for (int i = 0; i < slottedItemRenderers.Length; i++)
|
||||
{
|
||||
var mapping = slottedItemRenderers[i];
|
||||
@@ -520,41 +356,16 @@ namespace Interactions
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Item is not assigned to a specific slot - find best available slot
|
||||
// Ensure lists are large enough to check
|
||||
while (slottedItemObjects.Count < slottedItemRenderers.Length)
|
||||
{
|
||||
slottedItemObjects.Add(null);
|
||||
slottedItemsData.Add(null);
|
||||
slottedItemCorrectness.Add(false);
|
||||
}
|
||||
|
||||
// Prefer unassigned empty slots first
|
||||
for (int i = 0; i < slottedItemRenderers.Length; i++)
|
||||
{
|
||||
var mapping = slottedItemRenderers[i];
|
||||
if (mapping?.assignedItem == null && slottedItemObjects[i] == null)
|
||||
{
|
||||
return i; // Empty unassigned slot
|
||||
}
|
||||
}
|
||||
|
||||
// Then try assigned but empty slots
|
||||
for (int i = 0; i < slottedItemRenderers.Length; i++)
|
||||
{
|
||||
var mapping = slottedItemRenderers[i];
|
||||
if (mapping?.assignedItem != null && slottedItemObjects[i] == null)
|
||||
{
|
||||
return i; // Empty assigned slot
|
||||
}
|
||||
}
|
||||
|
||||
// All slots full - return -1 to trigger swap logic
|
||||
return -1;
|
||||
// Item is not assigned to a specific slot - return next available index
|
||||
// Dense arrays: just return current count if there's space
|
||||
return slottedItemObjects.Count < slottedItemRenderers.Length
|
||||
? slottedItemObjects.Count
|
||||
: -1; // No space, use append (will trigger swap logic)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the sprite and scale for all slotted items.
|
||||
/// Handles both UseConfig (assign sprite) and RevealExisting (just enable renderer) display modes.
|
||||
/// </summary>
|
||||
private void UpdateSlottedSprite()
|
||||
{
|
||||
@@ -574,31 +385,51 @@ namespace Interactions
|
||||
if (i < slottedItemsData.Count && slottedItemsData[i] != null)
|
||||
{
|
||||
var slottedData = slottedItemsData[i];
|
||||
if (slottedData.mapSprite != null)
|
||||
|
||||
// Handle display mode
|
||||
if (mapping.displayType == SlotRendererDisplayType.RevealExisting)
|
||||
{
|
||||
slotRenderer.sprite = slottedData.mapSprite;
|
||||
|
||||
// Scale sprite to desired height, preserve aspect ratio, compensate for parent scale
|
||||
var configs = GameManager.GetSettingsObject<IPlayerMovementConfigs>();
|
||||
float desiredHeight = configs?.FollowerMovement?.HeldIconDisplayHeight ?? 2.0f;
|
||||
var sprite = slottedData.mapSprite;
|
||||
float spriteHeight = sprite.bounds.size.y;
|
||||
Vector3 parentScale = slotRenderer.transform.parent != null
|
||||
? slotRenderer.transform.parent.localScale
|
||||
: Vector3.one;
|
||||
|
||||
if (spriteHeight > 0f)
|
||||
// RevealExisting: Just make the renderer visible
|
||||
slotRenderer.enabled = true;
|
||||
}
|
||||
else // UseConfig (default)
|
||||
{
|
||||
// UseConfig: Assign sprite from item config
|
||||
if (slottedData.mapSprite != null)
|
||||
{
|
||||
float uniformScale = desiredHeight / spriteHeight;
|
||||
float scale = uniformScale / Mathf.Max(parentScale.x, parentScale.y);
|
||||
slotRenderer.transform.localScale = new Vector3(scale, scale, 1f);
|
||||
slotRenderer.sprite = slottedData.mapSprite;
|
||||
|
||||
// Scale sprite to desired height, preserve aspect ratio, compensate for parent scale
|
||||
var configs = GameManager.GetSettingsObject<IPlayerMovementConfigs>();
|
||||
float desiredHeight = configs?.FollowerMovement?.HeldIconDisplayHeight ?? 2.0f;
|
||||
var sprite = slottedData.mapSprite;
|
||||
float spriteHeight = sprite.bounds.size.y;
|
||||
Vector3 parentScale = slotRenderer.transform.parent != null
|
||||
? slotRenderer.transform.parent.localScale
|
||||
: Vector3.one;
|
||||
|
||||
if (spriteHeight > 0f)
|
||||
{
|
||||
float uniformScale = desiredHeight / spriteHeight;
|
||||
float scale = uniformScale / Mathf.Max(parentScale.x, parentScale.y);
|
||||
slotRenderer.transform.localScale = new Vector3(scale, scale, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear renderer if no item at this index
|
||||
slotRenderer.sprite = null;
|
||||
// No item at this index - clear/hide renderer
|
||||
if (mapping.displayType == SlotRendererDisplayType.RevealExisting)
|
||||
{
|
||||
// RevealExisting: Hide the renderer
|
||||
slotRenderer.enabled = false;
|
||||
}
|
||||
else // UseConfig (default)
|
||||
{
|
||||
// UseConfig: Clear sprite
|
||||
slotRenderer.sprite = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -767,15 +598,10 @@ namespace Interactions
|
||||
|
||||
// Add to slotted items list (no events, no interaction completion)
|
||||
// Follower state is managed separately during save/load restoration
|
||||
// All saved items are correct (wrong items never get slotted)
|
||||
slottedItemObjects.Add(slottedObject);
|
||||
slottedItemsData.Add(slottedData);
|
||||
|
||||
// Determine if this item is correct for correctness tracking
|
||||
var config = interactionSettings?.GetSlotItemConfig(itemData);
|
||||
var allowed = config?.allowedItems ?? new List<PickupItemData>();
|
||||
bool isCorrectItem = PickupItemData.ListContainsEquivalent(allowed, slottedData);
|
||||
slottedItemCorrectness.Add(isCorrectItem);
|
||||
|
||||
// Deactivate the item and set pickup state
|
||||
slottedObject.SetActive(false);
|
||||
if (pickup != null)
|
||||
@@ -784,12 +610,13 @@ namespace Interactions
|
||||
pickup.OwningSlot = this;
|
||||
}
|
||||
|
||||
Logging.Debug($"[ItemSlot] Successfully restored slotted item: {slottedData.itemName} (itemId: {slottedData.itemId}, correct: {isCorrectItem})");
|
||||
Logging.Debug($"[ItemSlot] Successfully restored slotted item: {slottedData.itemName} (itemId: {slottedData.itemId})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public API for slotting items during gameplay.
|
||||
/// Adds item to the slot (multi-slot support with positional awareness).
|
||||
/// Only called for correct items - wrong items trigger reactions instead.
|
||||
/// Caller is responsible for managing follower's held item state.
|
||||
/// </summary>
|
||||
public void SlotItem(GameObject itemToSlot, PickupItemData itemToSlotData)
|
||||
@@ -800,35 +627,22 @@ namespace Interactions
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if this item is correct (allowed)
|
||||
var config = interactionSettings?.GetSlotItemConfig(itemData);
|
||||
var allowed = config?.allowedItems ?? new List<PickupItemData>();
|
||||
bool isCorrectItem = PickupItemData.ListContainsEquivalent(allowed, itemToSlotData);
|
||||
// NOTE: Only called for CORRECT items now (validation done in DoInteraction)
|
||||
|
||||
// Determine target slot index
|
||||
int targetIndex = GetTargetSlotIndexForItem(itemToSlotData);
|
||||
|
||||
if (targetIndex >= 0)
|
||||
if (targetIndex >= 0 && targetIndex < slottedItemObjects.Count)
|
||||
{
|
||||
// Positional slotting - ensure lists are large enough
|
||||
while (slottedItemObjects.Count <= targetIndex)
|
||||
{
|
||||
slottedItemObjects.Add(null);
|
||||
slottedItemsData.Add(null);
|
||||
slottedItemCorrectness.Add(false);
|
||||
}
|
||||
|
||||
// Place at specific index
|
||||
slottedItemObjects[targetIndex] = itemToSlot;
|
||||
slottedItemsData[targetIndex] = itemToSlotData;
|
||||
slottedItemCorrectness[targetIndex] = isCorrectItem;
|
||||
// Insert at specific position (shifts items if needed)
|
||||
slottedItemObjects.Insert(targetIndex, itemToSlot);
|
||||
slottedItemsData.Insert(targetIndex, itemToSlotData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append behavior (backward compatible or when all slots full)
|
||||
// Append to end (backward compatible or when all slots full)
|
||||
slottedItemObjects.Add(itemToSlot);
|
||||
slottedItemsData.Add(itemToSlotData);
|
||||
slottedItemCorrectness.Add(isCorrectItem);
|
||||
}
|
||||
|
||||
// Deactivate item and set pickup state
|
||||
@@ -845,32 +659,18 @@ namespace Interactions
|
||||
// Update visuals
|
||||
UpdateSlottedSprite();
|
||||
|
||||
// Fire events based on correctness
|
||||
if (isCorrectItem)
|
||||
// Fire success events (all items are correct now)
|
||||
DebugUIMessage.Show($"Slotted {itemToSlotData.itemName}", Color.green);
|
||||
onItemSlotted?.Invoke();
|
||||
OnItemSlotted?.Invoke(itemData, itemToSlotData);
|
||||
|
||||
// Check if slot is complete
|
||||
if (IsComplete)
|
||||
{
|
||||
DebugUIMessage.Show($"You slotted {itemToSlotData.itemName} into: {itemData.itemName}", Color.green);
|
||||
|
||||
// Fire generic slot event
|
||||
onItemSlotted?.Invoke();
|
||||
OnItemSlotted?.Invoke(itemData, itemToSlotData);
|
||||
|
||||
// Only fire correct completion event if ALL required CORRECT items are now slotted
|
||||
if (IsComplete)
|
||||
{
|
||||
currentState = ItemSlotState.Correct;
|
||||
DebugUIMessage.Show($"Completed: {itemData.itemName}", Color.green);
|
||||
onCorrectItemSlotted?.Invoke();
|
||||
OnCorrectItemSlotted?.Invoke(itemData, itemToSlotData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Incorrect item slotted
|
||||
DebugUIMessage.Show($"Slotted {itemToSlotData.itemName}, but it might not be right...", Color.yellow);
|
||||
onItemSlotted?.Invoke(); // Still fire generic event
|
||||
OnItemSlotted?.Invoke(itemData, itemToSlotData);
|
||||
onIncorrectItemSlotted?.Invoke();
|
||||
OnIncorrectItemSlotted?.Invoke(itemData, itemToSlotData);
|
||||
currentState = ItemSlotState.Correct;
|
||||
DebugUIMessage.Show($"Completed: {itemData.itemName}", Color.green);
|
||||
onCorrectItemSlotted?.Invoke();
|
||||
OnCorrectItemSlotted?.Invoke(itemData, itemToSlotData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -894,21 +694,15 @@ namespace Interactions
|
||||
// Note: We don't have easy access to the expected SaveId here, so we just accept it
|
||||
// The Pickup's bilateral restoration ensures it only claims the correct slot
|
||||
|
||||
// Add the item to lists
|
||||
// Add the item to lists (all items are correct in new system)
|
||||
slottedItemObjects.Add(pickup.gameObject);
|
||||
slottedItemsData.Add(pickup.itemData);
|
||||
|
||||
// Determine correctness for tracking
|
||||
var config = interactionSettings?.GetSlotItemConfig(itemData);
|
||||
var allowed = config?.allowedItems ?? new List<PickupItemData>();
|
||||
bool isCorrectItem = PickupItemData.ListContainsEquivalent(allowed, pickup.itemData);
|
||||
slottedItemCorrectness.Add(isCorrectItem);
|
||||
|
||||
pickup.gameObject.SetActive(false);
|
||||
pickup.IsPickedUp = true;
|
||||
pickup.OwningSlot = this;
|
||||
|
||||
Logging.Debug($"[ItemSlot] Successfully claimed slotted item: {pickup.itemData?.itemName} (correct: {isCorrectItem})");
|
||||
Logging.Debug($"[ItemSlot] Successfully claimed slotted item: {pickup.itemData?.itemName}");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user