using System; using Invector; using Invector.vItemManager; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using System.Linq; namespace Beyond { [vClassHeader("Equip Area", openClose = false)] public class bEquipArea : vMonoBehaviour { public delegate void OnPickUpItem(bEquipArea area, bItemSlot slot); private const int powerChargesMaxValue = 100; public OnPickUpItem onPickUpItemCallBack; public bInventory inventory; public bItemManager itemManager; public bItemWindow itemPicker; [Tooltip("Set current equiped slot when submit an slot of this area")] public bool setEquipSlotWhenSubmit; [Tooltip("Skip empty slots when switching between slots")] public bool skipEmptySlots; public List equipSlots; public string equipPointName; public Text displayNameText; public Text displayTypeText; public Text displayAmountText; public Text displayDescriptionText; public Text displayAttributesText; [vHelpBox("You can ignore display Attributes using this property")] public List ignoreAttributes; // --- FIX 1: Initialize Events inline to prevent NullReferenceException --- public UnityEngine.Events.UnityEvent onInitPickUpItem = new UnityEngine.Events.UnityEvent(); public UnityEngine.Events.UnityEvent onFinishPickUpItem = new UnityEngine.Events.UnityEvent(); public InputField.OnChangeEvent onChangeName; public InputField.OnChangeEvent onChangeType; public InputField.OnChangeEvent onChangeAmount; public InputField.OnChangeEvent onChangeDescription; public InputField.OnChangeEvent onChangeAttributes; public OnChangeEquipmentEvent onEquipItem = new OnChangeEquipmentEvent(); public OnChangeEquipmentEvent onUnequipItem = new OnChangeEquipmentEvent(); public OnSelectEquipArea onSelectEquipArea = new OnSelectEquipArea(); // ------------------------------------------------------------------------ public event Action OnItemUsed; public UnityEngine.UI.Toggle.ToggleEvent onSetLockToEquip = new UnityEngine.UI.Toggle.ToggleEvent(); [HideInInspector] public bEquipSlot currentSelectedSlot; public bEquipSlot lastSelectedSlot; [HideInInspector] public int indexOfEquippedItem; public bItem lastEquipedItem; protected bool _isLockedToEquip; [vHelpBox("Select what ItemType this EquipSlot will equip", vHelpBoxAttribute.MessageType.Warning)] public List itemTypes; private List items = new(); private List usedIndexes = new(); private List gemableWeapons = new(); private bItem selectedPowerableItem; private int selectedGemableItemIndex; public bool isLockedToEquip { get { return _isLockedToEquip; } set { if (_isLockedToEquip != value) onSetLockToEquip.Invoke(value); _isLockedToEquip = value; } } public bool ignoreEquipEvents; internal bool isInit; public void Init() { if (!isInit) Start(); } private void Start() { if (!isInit) { isInit = true; // Dependencies if (inventory == null) inventory = GetComponentInParent(); if (inventory == null && Player.Instance != null && Player.Instance.ItemManager != null) inventory = Player.Instance.ItemManager.inventory; itemManager = Player.Instance != null ? Player.Instance.GetComponent() : null; if (equipSlots.Count == 0) { var equipSlotsArray = GetComponentsInChildren(true); equipSlots = equipSlotsArray.vToList(); } foreach (bEquipSlot slot in equipSlots) { slot.SetInventory(this.inventory); // Explicitly set inventory slot.onSubmitSlotCallBack = OnSubmitSlot; slot.onSelectSlotCallBack = OnSelectSlot; slot.onDeselectSlotCallBack = OnDeselect; onSetLockToEquip.AddListener(slot.SetLockToEquip); if (slot.displayAmountText) slot.displayAmountText.text = ""; slot.onChangeAmount.Invoke(""); } } if (itemPicker) { itemPicker.onSelectSlot.AddListener(SetPowerableWeaponImage); } } public void SetEquipmentWindow() { itemPicker.CreateEquipmentWindow(inventory.items, itemTypes, equipSlots[0].item, OnPickItem); } public void SetNewItemWindow(bItemWindow newItemPicker) { if (itemPicker) DisableItemWindow(); itemPicker = newItemPicker; EnableItemWindow(); } public void DisableItemWindow() => itemPicker.gameObject.SetActive(false); public void EnableItemWindow() => itemPicker.gameObject.SetActive(true); public void SetEquipmentwindowWithFilter(List types) { itemPicker.CreateEquipmentWindow(inventory.items, types, null, OnPickItem); } public void SetEquipmentWindowsUsableWithFilter(List types) { itemPicker.CreateEquipmentWindowUsable(inventory.items, types, equipSlots[0].item, OnPickItem); } private void SetOccupiedIndexes() { usedIndexes.Clear(); for (int i = 0; i < equipSlots.Count; i++) { if (equipSlots[i].isOcupad()) usedIndexes.Add(i); } } public bool IsAnyItemEquipped() { for (int i = 0; i < equipSlots.Count; i++) if (equipSlots[i].isOcupad()) return true; return false; } public bEquipSlot currentEquippedSlot => equipSlots[indexOfEquippedItem]; public bItem currentEquippedItem { get { var validEquipSlots = ValidSlots; if (validEquipSlots.Count > 0 && indexOfEquippedItem >= 0 && indexOfEquippedItem < validEquipSlots.Count) return validEquipSlots[indexOfEquippedItem].item; return null; } } public List ValidSlots => equipSlots.FindAll(slot => slot.isValid && (!skipEmptySlots || slot.item != null)); public bool ContainsItem(vItem item) => ValidSlots.Find(slot => slot.item == item) != null; private void UseItemInternal(bItem item) { if (item.type == bItemType.Gemstones) { if (gemableWeapons.Count < 1 || gemableWeapons[selectedGemableItemIndex] == null) { if (bItemCollectionDisplay.Instance && item.type == bItemType.Gemstones && item) { Player.Instance.PlayFullyChargedSound(); if (PopupMenuController.Instance != null) PopupMenuController.Instance.TryToShowPopupMesssage("No proper weapons available"); return; } } else { bItemAttribute powerAttribute = gemableWeapons[selectedGemableItemIndex].attributes.First(attribute => attribute.name == bItemAttributes.Power); if (powerAttribute.value >= powerChargesMaxValue) { Player.Instance.PlayFullyChargedSound(); if (PopupMenuController.Instance != null) PopupMenuController.Instance.TryToShowPopupMesssage(gemableWeapons[selectedGemableItemIndex].name + " is already fully charged"); return; } powerAttribute.value += item.GetItemAttribute(bItemAttributes.Power).value; if (powerAttribute.value > powerChargesMaxValue) powerAttribute.value = powerChargesMaxValue; } } inventory.OnUseItem(item); } public void UseItem() { if (itemManager.UsingItem) return; UseItemInternal(itemPicker.currentSelectedSlot.item); } public void UseItem(bEquipSlot equipSlot) { bItem itemBeingUsed = equipSlot.item; FindGemableWeapons(); UseItemInternal(itemBeingUsed); } public void UseEquippedItem() { inventory.OnUseItem(currentEquippedItem); } public void OnSubmitSlot(bItemSlot slot) { lastSelectedSlot = currentSelectedSlot; if (itemPicker != null) { currentSelectedSlot = slot as bEquipSlot; if (setEquipSlotWhenSubmit) { SetEquipSlot(equipSlots.IndexOf(currentSelectedSlot)); } itemPicker.gameObject.SetActive(true); itemPicker.onCancelSlot.RemoveAllListeners(); itemPicker.onCancelSlot.AddListener(CancelCurrentSlot); itemPicker.CreateEquipmentWindow(inventory.items, currentSelectedSlot.itemType, slot.item, OnPickItem); onInitPickUpItem.Invoke(); } } public void CancelCurrentSlot() { if (currentSelectedSlot == null) currentSelectedSlot = lastSelectedSlot; if (currentSelectedSlot != null) currentSelectedSlot.OnCancel(); onFinishPickUpItem.Invoke(); } public void UnequipItem(bEquipSlot slot) { if (slot) { bItem item = slot.item; if (ValidSlots[indexOfEquippedItem].item == item) lastEquipedItem = item; slot.RemoveItem(); onUnequipItem.Invoke(this, item); } } public void UnequipItem(bItem item) { var slot = ValidSlots.Find(_slot => _slot.item == item); if (slot) { if (ValidSlots[indexOfEquippedItem].item == item) lastEquipedItem = item; slot.RemoveItem(); onUnequipItem.Invoke(this, item); } } public void UnequipCurrentItem() { if (currentSelectedSlot && currentSelectedSlot.item) { var _item = currentSelectedSlot.item; if (ValidSlots[indexOfEquippedItem].item == _item) lastEquipedItem = _item; currentSelectedSlot.RemoveItem(); onUnequipItem.Invoke(this, _item); } } public void OnSelectSlot(bItemSlot slot) { if (equipSlots.Contains(slot as bEquipSlot)) currentSelectedSlot = slot as bEquipSlot; else currentSelectedSlot = null; onSelectEquipArea.Invoke(this); CreateFullItemDescription(slot); } public void OnDeselect(bItemSlot slot) { if (equipSlots.Contains(slot as bEquipSlot)) currentSelectedSlot = null; } protected virtual void CreateFullItemDescription(bItemSlot slot) { var _name = slot.item ? slot.item.name : ""; var _type = slot.item ? slot.item.ItemTypeText() : ""; var _amount = slot.item ? slot.item.amount.ToString() : ""; var _description = slot.item ? slot.item.description : ""; var _attributes = slot.item ? slot.item.GetItemAttributesText(ignoreAttributes) : ""; if (displayNameText) displayNameText.text = _name; onChangeName.Invoke(_name); if (displayTypeText) displayTypeText.text = _type; onChangeType.Invoke(_type); if (displayAmountText) displayAmountText.text = _amount; onChangeAmount.Invoke(_amount); if (displayDescriptionText) displayDescriptionText.text = _description; onChangeDescription.Invoke(_description); if (displayAttributesText) displayAttributesText.text = _attributes; onChangeAttributes.Invoke(_attributes); } // --- FIX 2: Safer OnPickItem implementation --- public void OnPickItem(bItemSlot slot) { if (usedIndexes.Count == 0) SetOccupiedIndexes(); // 1. Unequip if checked if (slot.isChecked) { bEquipSlot occupiedSlot = equipSlots.Find(eSlot => eSlot.item == slot.item); if (occupiedSlot != null) // Check if slot was actually found { usedIndexes.Remove(equipSlots.IndexOf(occupiedSlot)); occupiedSlot.RemoveItem(); onUnequipItem?.Invoke(this, slot.item); // Safe Invoke } onFinishPickUpItem?.Invoke(); // Safe Invoke return; } // 2. Fill first free slot bEquipSlot freeSlot = equipSlots.Find(eslot => !eslot.isOcupad() && eslot.isValid && eslot.itemType.Contains(slot.item.type)); if (freeSlot) { onPickUpItemCallBack?.Invoke(this, slot); freeSlot.AddItem(slot.item); if (!ignoreEquipEvents) { onEquipItem?.Invoke(this, slot.item); } onFinishPickUpItem?.Invoke(); usedIndexes.Add(equipSlots.IndexOf(freeSlot)); return; } // 3. Swap with oldest (FIFO) if full if (usedIndexes.Count > 0) { int slotIndexToTake = -1; // Iterate through used indexes to find a compatible slot for (int i = 0; i < usedIndexes.Count; i++) { int idx = usedIndexes[i]; if (equipSlots[idx].itemType.Contains(slot.item.type)) { slotIndexToTake = idx; usedIndexes.RemoveAt(i); usedIndexes.Add(idx); break; } } // If we found a compatible slot to swap if (slotIndexToTake != -1) { bEquipSlot slotToBeTaken = equipSlots[slotIndexToTake]; if (slotToBeTaken.isOcupad()) { onUnequipItem?.Invoke(this, slotToBeTaken.item); slotToBeTaken.RemoveItem(); } onPickUpItemCallBack?.Invoke(this, slot); slotToBeTaken.AddItem(slot.item); if (!ignoreEquipEvents) { onEquipItem?.Invoke(this, slot.item); } } } onFinishPickUpItem?.Invoke(); } // ---------------------------------------------- void FindGemableWeapons() { List weaponTypes = new List() { bItemType.Swords, bItemType.Axes }; gemableWeapons = inventory.items.FindAll(item => weaponTypes.Contains(item.type) && item.GetItemAttribute(bItemAttributes.Power) != null); selectedGemableItemIndex = 0; } private void SetPowerableWeaponImage(bItemSlot itemSelected) { if (itemSelected.item.type == bItemType.Gemstones) { FindGemableWeapons(); if (gemableWeapons.Count > 0) { selectedPowerableItem = gemableWeapons[selectedGemableItemIndex]; itemPicker.SetPowerableSwitchSwordImage(selectedPowerableItem); } } } public void SwitchPowerableWeapon() { if (gemableWeapons.Count < 1) { itemPicker.SetPowerableSwitchSwordImage(null); return; } selectedGemableItemIndex++; if (selectedGemableItemIndex >= gemableWeapons.Count) { selectedGemableItemIndex = 0; } selectedPowerableItem = gemableWeapons[selectedGemableItemIndex]; itemPicker.SetPowerableSwitchSwordImage(selectedPowerableItem); } public void NextEquipSlot() { if (equipSlots == null || equipSlots.Count == 0) return; lastEquipedItem = currentEquippedItem; var validEquipSlots = ValidSlots; if (indexOfEquippedItem + 1 < validEquipSlots.Count) indexOfEquippedItem++; else indexOfEquippedItem = 0; if (currentEquippedItem != null && !ignoreEquipEvents) onEquipItem?.Invoke(this, currentEquippedItem); onUnequipItem?.Invoke(this, lastEquipedItem); } public void PreviousEquipSlot() { if (equipSlots == null || equipSlots.Count == 0) return; lastEquipedItem = currentEquippedItem; var validEquipSlots = ValidSlots; if (indexOfEquippedItem - 1 >= 0) indexOfEquippedItem--; else indexOfEquippedItem = validEquipSlots.Count - 1; if (currentEquippedItem != null && !ignoreEquipEvents) onEquipItem?.Invoke(this, currentEquippedItem); onUnequipItem?.Invoke(this, lastEquipedItem); } public void SetEquipSlot(int indexOfSlot) { if (equipSlots == null || equipSlots.Count == 0) return; if (indexOfSlot < equipSlots.Count && indexOfSlot >= 0) { lastEquipedItem = currentEquippedItem; indexOfEquippedItem = indexOfSlot; if (currentEquippedItem != null && !ignoreEquipEvents) { onEquipItem?.Invoke(this, currentEquippedItem); } if (currentEquippedItem != lastEquipedItem) onUnequipItem?.Invoke(this, lastEquipedItem); } } public void EquipCurrentSlot() { if (!currentEquippedSlot || (currentEquippedSlot.item != null && currentEquippedSlot.item.isEquiped)) return; if (currentEquippedItem) onEquipItem?.Invoke(this, currentEquippedItem); else if (lastEquipedItem) onUnequipItem?.Invoke(this, lastEquipedItem); } public void AddItemToEquipSlot(bItemSlot slot, bItem item, bool autoEquip = false) { if (slot is bEquipSlot && equipSlots.Contains(slot as bEquipSlot)) { AddItemToEquipSlot(equipSlots.IndexOf(slot as bEquipSlot), item, autoEquip); } } public void AddItemToEquipSlot(int indexOfSlot, bItem item, bool autoEquip = false) { if (indexOfSlot < equipSlots.Count && item != null && item.canBeUsed) { var slot = equipSlots[indexOfSlot]; if (slot != null && slot.isValid && slot.itemType.Contains(item.type)) { var sameSlot = equipSlots.Find(s => s.item == item && s != slot); if (sameSlot != null) RemoveItemOfEquipSlot(equipSlots.IndexOf(sameSlot)); if (slot.item != null && slot.item != item) { if (currentEquippedItem == slot.item) lastEquipedItem = slot.item; slot.item.isInEquipArea = false; onUnequipItem?.Invoke(this, slot.item); } item.checkColor = slot.checkColor; item.isInEquipArea = true; slot.AddItem(item); if (autoEquip) SetEquipSlot(indexOfSlot); else if (!ignoreEquipEvents) onEquipItem?.Invoke(this, item); } } } public void RemoveItemOfEquipSlot(bItemSlot slot) { if (slot is bEquipSlot && equipSlots.Contains(slot as bEquipSlot)) { RemoveItemOfEquipSlot(equipSlots.IndexOf(slot as bEquipSlot)); } } public void RemoveItemOfEquipSlot(int indexOfSlot) { if (indexOfSlot < equipSlots.Count) { var slot = equipSlots[indexOfSlot]; if (slot != null && slot.item != null) { var item = slot.item; item.isInEquipArea = false; if (currentEquippedItem == item) lastEquipedItem = currentEquippedItem; slot.RemoveItem(); onUnequipItem?.Invoke(this, item); } } } public void AddCurrentItem(bItem item) { if (indexOfEquippedItem < equipSlots.Count) { var slot = equipSlots[indexOfEquippedItem]; if (slot.item != null && item != slot.item) { if (currentEquippedItem == slot.item) lastEquipedItem = slot.item; slot.item.isInEquipArea = false; onUnequipItem?.Invoke(this, currentSelectedSlot.item); } slot.AddItem(item); if (!ignoreEquipEvents) onEquipItem?.Invoke(this, item); } } public void RemoveCurrentItem() { if (!currentEquippedItem) return; lastEquipedItem = currentEquippedItem; ValidSlots[indexOfEquippedItem].RemoveItem(); onUnequipItem?.Invoke(this, lastEquipedItem); } } }