2025-09-29 09:34:15 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using Interactions;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Core
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Central registry for pickups and item slots.
|
|
|
|
|
|
/// Mirrors the singleton pattern used by PuzzleManager.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class ItemManager : MonoBehaviour
|
|
|
|
|
|
{
|
|
|
|
|
|
private static ItemManager _instance;
|
|
|
|
|
|
private static bool _isQuitting;
|
|
|
|
|
|
|
|
|
|
|
|
public static ItemManager Instance
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_instance == null && Application.isPlaying && !_isQuitting)
|
|
|
|
|
|
{
|
|
|
|
|
|
_instance = FindAnyObjectByType<ItemManager>();
|
|
|
|
|
|
if (_instance == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var go = new GameObject("ItemManager");
|
|
|
|
|
|
_instance = go.AddComponent<ItemManager>();
|
|
|
|
|
|
// DontDestroyOnLoad(go);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return _instance;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private readonly HashSet<Pickup> _pickups = new HashSet<Pickup>();
|
|
|
|
|
|
private readonly HashSet<ItemSlot> _itemSlots = new HashSet<ItemSlot>();
|
|
|
|
|
|
private readonly HashSet<string> _itemsCreatedThroughCombination = new HashSet<string>();
|
|
|
|
|
|
|
|
|
|
|
|
// Central events forwarded from registered pickups/slots
|
|
|
|
|
|
// Broadcasts when any registered pickup was picked up (passes the picked item data)
|
|
|
|
|
|
public event Action<PickupItemData> OnItemPickedUp;
|
|
|
|
|
|
|
|
|
|
|
|
// 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 any two items are successfully combined
|
|
|
|
|
|
// Args: first item data, second item data, result item data
|
|
|
|
|
|
public event Action<PickupItemData, PickupItemData, PickupItemData> OnItemsCombined;
|
|
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
_instance = this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Subscribe to scene load completed so we can clear registrations when scenes change
|
|
|
|
|
|
// Access Instance directly to ensure the service is initialized and we get the event hookup.
|
2025-09-29 13:25:22 +02:00
|
|
|
|
SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted;
|
2025-09-29 09:34:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnDestroy()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Unsubscribe from SceneManagerService
|
|
|
|
|
|
if (SceneManagerService.Instance != null)
|
2025-09-29 13:25:22 +02:00
|
|
|
|
SceneManagerService.Instance.SceneLoadStarted -= OnSceneLoadStarted;
|
2025-09-29 09:34:15 +00:00
|
|
|
|
|
|
|
|
|
|
// Ensure we clean up any subscriptions from registered items when the manager is destroyed
|
|
|
|
|
|
ClearAllRegistrations();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnApplicationQuit()
|
|
|
|
|
|
{
|
|
|
|
|
|
_isQuitting = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-29 13:25:22 +02:00
|
|
|
|
private void OnSceneLoadStarted(string sceneName)
|
2025-09-29 09:34:15 +00:00
|
|
|
|
{
|
|
|
|
|
|
// Clear all registrations when a new scene is loaded, so no stale references persist
|
|
|
|
|
|
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>
|
|
|
|
|
|
private void ClearAllRegistrations()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Unsubscribe pickup handlers
|
|
|
|
|
|
var pickupsCopy = new List<Pickup>(_pickups);
|
|
|
|
|
|
foreach (var p in pickupsCopy)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (p != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
p.OnItemPickedUp -= Pickup_OnItemPickedUp;
|
|
|
|
|
|
p.OnItemsCombined -= Pickup_OnItemsCombined;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_pickups.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
// Unsubscribe slot handlers
|
|
|
|
|
|
var slotsCopy = new List<ItemSlot>(_itemSlots);
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
|
|
// Clear item tracking
|
|
|
|
|
|
_itemsCreatedThroughCombination.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
// Clear manager-level event subscribers
|
|
|
|
|
|
OnItemPickedUp = null;
|
|
|
|
|
|
OnCorrectItemSlotted = null;
|
|
|
|
|
|
OnIncorrectItemSlotted = null;
|
|
|
|
|
|
OnForbiddenItemSlotted = null;
|
|
|
|
|
|
OnItemSlotCleared = null;
|
|
|
|
|
|
OnItemsCombined = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void RegisterPickup(Pickup pickup)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (pickup == null) return;
|
|
|
|
|
|
// only subscribe if newly added to avoid duplicate subscriptions
|
|
|
|
|
|
if (_pickups.Add(pickup))
|
|
|
|
|
|
{
|
|
|
|
|
|
pickup.OnItemPickedUp += Pickup_OnItemPickedUp;
|
|
|
|
|
|
pickup.OnItemsCombined += Pickup_OnItemsCombined;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void UnregisterPickup(Pickup pickup)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (pickup == null) return;
|
|
|
|
|
|
if (_pickups.Remove(pickup))
|
|
|
|
|
|
{
|
|
|
|
|
|
pickup.OnItemPickedUp -= Pickup_OnItemPickedUp;
|
|
|
|
|
|
pickup.OnItemsCombined -= Pickup_OnItemsCombined;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void RegisterItemSlot(ItemSlot slot)
|
|
|
|
|
|
{
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void UnregisterItemSlot(ItemSlot slot)
|
|
|
|
|
|
{
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Checks if a specific item has been created through item combination.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="itemId">The ID of the item to check.</param>
|
|
|
|
|
|
/// <returns>True if the item has been created through combination, false otherwise.</returns>
|
|
|
|
|
|
public bool WasItemCreatedThroughCombination(string itemId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return !string.IsNullOrEmpty(itemId) && _itemsCreatedThroughCombination.Contains(itemId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Returns the current slot state for the given item data by searching registered slots.
|
|
|
|
|
|
/// If the item is currently slotted in a slot, returns that slot's state; otherwise returns ItemSlotState.None.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ItemSlotState GetSlotStatusForItem(PickupItemData itemData)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (itemData == null) return ItemSlotState.None;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var slot in _itemSlots)
|
|
|
|
|
|
{
|
|
|
|
|
|
var slottedObj = slot.GetSlottedObject();
|
|
|
|
|
|
if (slottedObj == null) continue;
|
|
|
|
|
|
var pickup = slottedObj.GetComponent<Pickup>();
|
|
|
|
|
|
if (pickup == null) continue;
|
|
|
|
|
|
if (pickup.itemData == itemData)
|
|
|
|
|
|
{
|
|
|
|
|
|
return slot.CurrentSlottedState;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ItemSlotState.None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IEnumerable<Pickup> Pickups => _pickups;
|
|
|
|
|
|
public IEnumerable<ItemSlot> ItemSlots => _itemSlots;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|