using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.Events; namespace BulletHellTemplate { /// /// Manages the application (equip/unequip) of purchased items and runes to specific character slots. /// Uses arrays of pre-instantiated slots for items and runes and opens separate popups for each type. /// Also provides UnityEvents for Add/Remove operations. /// public class UIItemsApply : MonoBehaviour { [Header("Item Slots")] [Tooltip("Array of already-instantiated slots for regular items (e.g. weapon, armor).")] public ItemSlotEntry[] itemsSlots; [Header("Rune Slots")] [Tooltip("Array of already-instantiated slots for runes (category 'Runes').")] public ItemSlotEntry[] runesSlots; [Header("Popups")] [Tooltip("Popup GameObject that displays the list of available items (normal items).")] public GameObject itemPopup; [Tooltip("Popup GameObject that displays the list of available runes.")] public GameObject runePopup; [Tooltip("Optional UI placeholder to show if no rune is selected (not mandatory).")] public GameObject runeNotSelected; [Header("UI Containers (Instantiated Entries)")] [Tooltip("Container within the item popup that holds ItemApplyEntry instances for normal items.")] public Transform itemContainer; [Tooltip("Container within the rune popup that holds ItemApplyEntry instances for runes.")] public Transform runeContainer; [Header("Prefabs for Entry")] [Tooltip("Prefab for the item entries in the item popup.")] public ItemApplyEntry itemPrefab; [Tooltip("Prefab for the rune entries in the rune popup.")] public ItemApplyEntry runePrefab; [Header("Rune Category Filter")] [Tooltip("Category name used to identify runes (e.g., 'Runes').")] public string runeCategoryFilter = "Runes"; [Header("Events")] [Tooltip("Invoked when an item or rune is successfully equipped (added).")] public UnityEvent OnAddItem; [Tooltip("Invoked when an item or rune is removed (unequipped).")] public UnityEvent OnRemoveItem; private CharacterData currentCharacter; /// /// Stores the equipped items or runes for quick reference. /// Key: slot name, Value: uniqueItemGuid of the equipped item or rune. /// private Dictionary equippedItems = new Dictionary(); /// /// Holds a reference to the currently selected slot entry, so we can deselect it on popup close. /// private ItemSlotEntry currentSelectedSlotEntry = null; private void OnEnable() { // Called when this GameObject becomes enabled } /// /// Initializes both item slots and rune slots for the currently selected character. /// Activates or deactivates slots based on the character's available slot arrays. /// public void SetupSlotsForCurrentCharacter(int _currentSelectedCharacter) { int selectedCharacterId = _currentSelectedCharacter; CharacterData selectedCharacter = FindCharacterData(selectedCharacterId); if (selectedCharacter == null) { Debug.LogError($"No CharacterData found for ID {selectedCharacterId}."); return; } currentCharacter = selectedCharacter; equippedItems.Clear(); // Setup normal item slots ConfigureItemSlots(selectedCharacter); } /// /// Configures the array of item slots (itemsSlots) based on the character's available itemSlots array. /// Each slot is activated or deactivated accordingly and the equipped icon is shown if any. /// private void ConfigureItemSlots(CharacterData charData) { if (charData.itemSlots == null) return; for (int i = 0; i < itemsSlots.Length; i++) { if (i < charData.itemSlots.Length) { itemsSlots[i].gameObject.SetActive(true); string slotName = charData.itemSlots[i]; itemsSlots[i].Setup(slotName, this, false); // 'false' = normal item // Check which item is equipped in this slot string uniqueGuid = InventorySave.GetEquippedItemForSlot(charData.characterId, slotName); if (!string.IsNullOrEmpty(uniqueGuid)) { equippedItems[slotName] = uniqueGuid; // Update icon var purchasedItem = PlayerSave.GetInventoryItems() .Find(pi => pi.uniqueItemGuid == uniqueGuid); if (purchasedItem != null) { var soItem = FindItemById(purchasedItem.itemId); if (soItem != null) itemsSlots[i].SetItemIcon(soItem.itemIcon, soItem.rarity); } else { itemsSlots[i].SetItemIcon(null); } } else { itemsSlots[i].SetItemIcon(null); } } else { itemsSlots[i].gameObject.SetActive(false); } } } /// /// Configures the array of rune slots (runesSlots) based on the character's rune slots array. /// Each slot is activated or deactivated accordingly and the equipped icon is shown if any. /// public void ConfigureRuneSlots() { if (currentCharacter == null) return; if (currentCharacter.runeSlots == null) return; for (int i = 0; i < runesSlots.Length; i++) { if (i < currentCharacter.runeSlots.Length) { runesSlots[i].gameObject.SetActive(true); string slotName = currentCharacter.runeSlots[i]; runesSlots[i].Setup(slotName, this, true); // 'true' = is rune // Check which rune is equipped string uniqueGuid = InventorySave.GetEquippedItemForSlot(currentCharacter.characterId, slotName); if (!string.IsNullOrEmpty(uniqueGuid)) { equippedItems[slotName] = uniqueGuid; var purchasedItem = PlayerSave.GetInventoryItems() .Find(pi => pi.uniqueItemGuid == uniqueGuid); if (purchasedItem != null) { var soRune = FindItemById(purchasedItem.itemId); if (soRune != null) runesSlots[i].SetItemIcon(soRune.itemIcon, soRune.rarity); } else { runesSlots[i].SetItemIcon(null); } } else { runesSlots[i].SetItemIcon(null); } } else { runesSlots[i].gameObject.SetActive(false); } } } /// /// Finds the CharacterData for the given characterId. /// private CharacterData FindCharacterData(int charId) { if (GameInstance.Singleton == null || GameInstance.Singleton.characterData == null) return null; foreach (var cd in GameInstance.Singleton.characterData) { if (cd.characterId == charId) return cd; } return null; } /// /// Finds the scriptable InventoryItem by itemId. /// private InventoryItem FindItemById(string itemId) { if (GameInstance.Singleton == null || GameInstance.Singleton.inventoryItems == null) return null; foreach (InventoryItem it in GameInstance.Singleton.inventoryItems) { if (it.itemId == itemId) return it; } return null; } /// /// Called by ItemSlotEntry when clicking the slot. Decides which popup (item or rune) to open. /// Also stores a reference to the clicked slot entry. /// /// The slot name to equip an item/rune in. /// If true, open rune popup; otherwise item popup. /// The slot entry that was clicked. public void OpenSlotPopup(string slotName, bool isRune, ItemSlotEntry clickedSlotEntry) { currentSelectedSlotEntry = clickedSlotEntry; if (isRune) { if (runePopup != null) runePopup.SetActive(true); if (runeContainer != null) { foreach (Transform child in runeContainer) Destroy(child.gameObject); } ShowRunesInPopup(slotName); } else { if (itemPopup != null) itemPopup.SetActive(true); if (itemContainer != null) { foreach (Transform child in itemContainer) Destroy(child.gameObject); } ShowItemsInPopup(slotName); } } /// /// Populates the item popup with valid purchased items that match the given slotName (excluding runes). /// private void ShowItemsInPopup(string slotName) { var purchasedItems = PlayerSave.GetInventoryItems(); foreach (var pi in purchasedItems) { var soItem = FindItemById(pi.itemId); if (soItem == null) continue; if (soItem.category == runeCategoryFilter) continue; // skip runes if (soItem.slot != slotName) continue; bool equipElsewhere = IsEquippedByAnotherCharacter(pi.uniqueItemGuid, currentCharacter.characterId, slotName); if (!equipElsewhere) { var entry = Instantiate(itemPrefab, itemContainer); entry.Setup(soItem, pi.uniqueItemGuid, slotName, this, false); string eqHere = InventorySave.GetEquippedItemForSlot(currentCharacter.characterId, slotName); entry.ShowAsEquipped(eqHere == pi.uniqueItemGuid); } } } /// /// Populates the rune popup with valid purchased runes that match the given slotName (matching runeCategoryFilter). /// private void ShowRunesInPopup(string slotName) { if (runeNotSelected != null) runeNotSelected.gameObject.SetActive(false); var purchasedItems = PlayerSave.GetInventoryItems(); foreach (var pi in purchasedItems) { var soItem = FindItemById(pi.itemId); if (soItem == null) continue; if (soItem.category != runeCategoryFilter) continue; if (soItem.slot != slotName) continue; bool equipElsewhere = IsEquippedByAnotherCharacter(pi.uniqueItemGuid, currentCharacter.characterId, slotName); if (!equipElsewhere) { var entry = Instantiate(runePrefab, runeContainer); entry.Setup(soItem, pi.uniqueItemGuid, slotName, this, true); string eqHere = InventorySave.GetEquippedItemForSlot(currentCharacter.characterId, slotName); entry.ShowAsEquipped(eqHere == pi.uniqueItemGuid); } } } /// /// Checks if an item/rune is equipped by a different character in the same slot. /// private bool IsEquippedByAnotherCharacter(string uniqueItemGuid, int currentCharId, string slotName) { if (GameInstance.Singleton == null || GameInstance.Singleton.characterData == null) return false; foreach (var cd in GameInstance.Singleton.characterData) { if (cd.characterId == currentCharId) continue; string eq = InventorySave.GetEquippedItemForSlot(cd.characterId, slotName); if (eq == uniqueItemGuid) return true; } return false; } /// /// Equips or unequips an item/rune in the specified slot. If already equipped, unequips. Otherwise, replaces any existing item. /// Invokes OnAddItem or OnRemoveItem events accordingly. /// /// The purchased item/rune GUID. /// The slot name (e.g., "WeaponSlot" or "DefenseRuneSlot"). /// If true, indicates a rune slot. public async void ApplyItemToSlot(string uniqueItemGuid, string slotName, bool isRune) {; RequestResult r; if (equippedItems.ContainsKey(slotName) && equippedItems[slotName] == uniqueItemGuid) { r = await InventorySave.SetEquippedItemForSlotAsync( currentCharacter.characterId, slotName, ""); if (r.Success) equippedItems.Remove(slotName); OnRemoveItem?.Invoke(); } else { if (equippedItems.ContainsKey(slotName)) await InventorySave.SetEquippedItemForSlotAsync( currentCharacter.characterId, slotName, ""); r = await InventorySave.SetEquippedItemForSlotAsync( currentCharacter.characterId, slotName, uniqueItemGuid); if (r.Success) equippedItems[slotName] = uniqueItemGuid; OnAddItem?.Invoke(); } if (!r.Success) Debug.LogWarning($"Equip failed: {r.Reason}"); ClosePopup(isRune); RefreshSlotIcon(slotName, isRune); UICharacterMenu.Singleton.UpdateDetailPanel(currentCharacter); } /// /// Closes the appropriate popup (item or rune) after the operation completes, /// and deselects the current slot. /// public void ClosePopup(bool isRune) { if (currentSelectedSlotEntry != null) { currentSelectedSlotEntry.DeselectSlot(); currentSelectedSlotEntry = null; } if (runeNotSelected != null) runeNotSelected.gameObject.SetActive(true); if (isRune && runePopup != null) runePopup.SetActive(false); if (!isRune && itemPopup != null) itemPopup.SetActive(false); } /// /// Refreshes the slot icon for the given slot after equipping or unequipping. /// private void RefreshSlotIcon(string slotName, bool isRune) { string guidEquipped = equippedItems.ContainsKey(slotName) ? equippedItems[slotName] : ""; InventoryItem soItem = null; if (!string.IsNullOrEmpty(guidEquipped)) { var purchased = PlayerSave.GetInventoryItems() .Find(pi => pi.uniqueItemGuid == guidEquipped); if (purchased != null) soItem = FindItemById(purchased.itemId); } ItemSlotEntry[] targetSlots = isRune ? runesSlots : itemsSlots; for (int i = 0; i < targetSlots.Length; i++) { if (!targetSlots[i].gameObject.activeSelf) continue; if (targetSlots[i].GetSlotName() == slotName) { if (soItem == null) targetSlots[i].SetItemIcon(null); else targetSlots[i].SetItemIcon(soItem.itemIcon, soItem.rarity); break; } } } } }