395 lines
16 KiB
C#
395 lines
16 KiB
C#
using System.Collections.Generic;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
|
|
namespace BulletHellTemplate
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Stores the equipped items or runes for quick reference.
|
|
/// Key: slot name, Value: uniqueItemGuid of the equipped item or rune.
|
|
/// </summary>
|
|
private Dictionary<string, string> equippedItems = new Dictionary<string, string>();
|
|
|
|
/// <summary>
|
|
/// Holds a reference to the currently selected slot entry, so we can deselect it on popup close.
|
|
/// </summary>
|
|
private ItemSlotEntry currentSelectedSlotEntry = null;
|
|
|
|
private void OnEnable()
|
|
{
|
|
// Called when this GameObject becomes enabled
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes both item slots and rune slots for the currently selected character.
|
|
/// Activates or deactivates slots based on the character's available slot arrays.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the CharacterData for the given characterId.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the scriptable InventoryItem by itemId.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by ItemSlotEntry when clicking the slot. Decides which popup (item or rune) to open.
|
|
/// Also stores a reference to the clicked slot entry.
|
|
/// </summary>
|
|
/// <param name="slotName">The slot name to equip an item/rune in.</param>
|
|
/// <param name="isRune">If true, open rune popup; otherwise item popup.</param>
|
|
/// <param name="clickedSlotEntry">The slot entry that was clicked.</param>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates the item popup with valid purchased items that match the given slotName (excluding runes).
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates the rune popup with valid purchased runes that match the given slotName (matching runeCategoryFilter).
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if an item/rune is equipped by a different character in the same slot.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="uniqueItemGuid">The purchased item/rune GUID.</param>
|
|
/// <param name="slotName">The slot name (e.g., "WeaponSlot" or "DefenseRuneSlot").</param>
|
|
/// <param name="isRune">If true, indicates a rune slot.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes the appropriate popup (item or rune) after the operation completes,
|
|
/// and deselects the current slot.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes the slot icon for the given slot after equipping or unequipping.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|