using System; using System.Collections.Generic; using UnityEngine; using Interactions; using Bootstrap; namespace Core { /// /// Central registry for pickups and item slots. /// Mirrors the singleton pattern used by PuzzleManager. /// public class ItemManager : MonoBehaviour { private static ItemManager _instance; /// /// Singleton instance of the ItemManager. No longer creates an instance if one doesn't exist. /// public static ItemManager Instance => _instance; private readonly HashSet _pickups = new HashSet(); private readonly HashSet _itemSlots = new HashSet(); private readonly HashSet _itemsCreatedThroughCombination = new HashSet(); // Central events forwarded from registered pickups/slots // Broadcasts when any registered pickup was picked up (passes the picked item data) public event Action 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 OnCorrectItemSlotted; // Broadcasts when any registered ItemSlot reports an incorrect item slotted // Args: slot's itemData (the slot definition), then the slotted item data public event Action OnIncorrectItemSlotted; // Broadcasts when any registered ItemSlot reports a forbidden item slotted // Args: slot's itemData (the slot definition), then the slotted item data public event Action OnForbiddenItemSlotted; // Broadcasts when any registered ItemSlot is cleared (item removed) // Args: the item data that was removed public event Action OnItemSlotCleared; // Broadcasts when any two items are successfully combined // Args: first item data, second item data, result item data public event Action OnItemsCombined; void Awake() { _instance = this; // Register for post-boot initialization BootCompletionService.RegisterInitAction(InitializePostBoot); } private void InitializePostBoot() { // Subscribe to scene load completed so we can clear registrations when scenes change SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted; Logging.Debug("[ItemManager] Subscribed to SceneManagerService events"); } void OnDestroy() { // Unsubscribe from SceneManagerService if (SceneManagerService.Instance != null) SceneManagerService.Instance.SceneLoadStarted -= OnSceneLoadStarted; // Ensure we clean up any subscriptions from registered items when the manager is destroyed ClearAllRegistrations(); } private void OnSceneLoadStarted(string sceneName) { // 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); } /// /// Unsubscribe all pickup/slot event handlers and clear registries and manager events. /// private void ClearAllRegistrations() { // Unsubscribe pickup handlers var pickupsCopy = new List(_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(_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; } } /// /// Checks if a specific item has been created through item combination. /// /// The ID of the item to check. /// True if the item has been created through combination, false otherwise. public bool WasItemCreatedThroughCombination(string itemId) { return !string.IsNullOrEmpty(itemId) && _itemsCreatedThroughCombination.Contains(itemId); } /// /// 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. /// 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(); if (pickup == null) continue; if (pickup.itemData == itemData) { return slot.CurrentSlottedState; } } return ItemSlotState.None; } public IEnumerable Pickups => _pickups; public IEnumerable ItemSlots => _itemSlots; } }