using System; using System.Collections.Generic; using UnityEngine; using Interactions; using Core.Lifecycle; using Core.SaveLoad; namespace Core { /// /// Central registry for pickups and item slots. /// Mirrors the singleton pattern used by PuzzleManager. /// public class ItemManager : ManagedBehaviour { 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; public override int ManagedAwakePriority => 75; // Item registry internal override void OnManagedAwake() { // Set instance immediately (early initialization) _instance = this; } internal override void OnManagedStart() { Logging.Debug("[ItemManager] Initialized"); } internal override void OnSceneReady() { // Replaces SceneLoadStarted subscription for clearing registrations ClearAllRegistrations(); } protected override void OnDestroy() { base.OnDestroy(); // Ensure we clean up any subscriptions from registered items when the manager is destroyed 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.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.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.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; /// /// Gets all registered pickups. Used by save/load system to find items by save ID. /// public IEnumerable GetAllPickups() => _pickups; /// /// Gets all registered item slots. Used by save/load system. /// public IEnumerable GetAllItemSlots() => _itemSlots; /// /// Finds a pickup by its save ID. Used by save/load system to restore item references. /// /// The save ID to search for /// The pickup's GameObject if found, null otherwise public GameObject FindPickupBySaveId(string saveId) { if (string.IsNullOrEmpty(saveId)) return null; // Search through all registered pickups foreach (var pickup in _pickups) { if (pickup is SaveableInteractable saveable && saveable.SaveId == saveId) { return pickup.gameObject; } } return null; } } }