Update slot items to simplified slotting logic + reveal renderers

This commit is contained in:
Michal Pikulski
2025-12-16 11:08:29 +01:00
parent 2807292cd7
commit ef709f5725
4 changed files with 232 additions and 1829 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using UnityEngine;
using Interactions;
using Core.Lifecycle;
using Core.SaveLoad;
namespace Core
{
@@ -32,17 +31,9 @@ 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 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
@@ -83,16 +74,10 @@ 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;
}
}

View File

@@ -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();
}

View File

@@ -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 event Action<PickupItemData, PickupItemData> OnCorrectItemSlotted; // (slotData, slottedItemData)
public UnityEvent onIncorrectItemSlotted;
// Native C# event alternative for code-only subscribers
public event Action<PickupItemData, PickupItemData> OnIncorrectItemSlotted;
public UnityEvent onForbiddenItemSlotted;
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,220 +262,59 @@ 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!
// CORRECT ITEM - Slot it
SlotItem(heldItemObj, heldItemData);
FollowerController.ClearHeldItem(); // Clear follower's hand after slotting
// Check if we completed the slot
if (IsComplete)
{
isLockedAfterCompletion = true;
currentState = ItemSlotState.Correct;
return true; // Completed!
}
return false; // Slotted but not complete yet
}
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)
else
{
var slottedPickup = slottedItemObjects[0].GetComponent<Pickup>();
if (slottedPickup != null)
{
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;
}
}
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
// 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);
// Item stays in player's hand - no state change
}
/// <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);
}
#endregion
@@ -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)
{
DebugUIMessage.Show($"You slotted {itemToSlotData.itemName} into: {itemData.itemName}", Color.green);
// Fire success events (all items are correct now)
DebugUIMessage.Show($"Slotted {itemToSlotData.itemName}", Color.green);
onItemSlotted?.Invoke();
OnItemSlotted?.Invoke(itemData, itemToSlotData);
// 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
// Check if slot is complete
if (IsComplete)
{
// 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;
}