Search Unity

Strange Inventory Swapping Bug

Discussion in 'Scripting' started by Joewa654321_, Sep 22, 2021.

  1. Joewa654321_

    Joewa654321_

    Joined:
    Apr 27, 2020
    Posts:
    32
    Hi all,

    I've encountered a strange bug and I cannot for the life of me figure out where it is originating from. I am currently creating a single / multiplayer game using Unity and Mirror that involves players having their own inventories as well as being able to interact with items and storage objects in the world. Everything works great so far, including storing items in storage objects across clients. For reference, the base inventory is the same as the Coding With Unity inventory system utilizing scriptable objects and inventory classes made up of custom slot classes, though I have added my own method of storing items as well as various other things.

    The issue is that whenever a player swaps an item from a storage objects inventory after the item has been stored, the items ID resets inside the players inventory. All other information including amount, durability, etc. is retained but the ID always resets leaving it impossible to interact with inside the UI.

    The inventory system works by assigning a copy of the storage objects associated inventory to the players temporary storage inventory which is used to display the contents of a storage object. On interaction, the interface opens up and loads all item data into the players interface. On closure of the interface, the storage objects associated inventory is updated with the players new storage inventory data which represents what was added and removed/changed during interaction.

    The data is sent initially through a client only method that calls a Command method which sends the data for each slot in the interface to the storage objects associated component and calls a ClientRpc which updates the storage object for all clients. Everything pertaining to this is working successfully, the only issue is on closure of the interface where the IDs of items that were stored and then placed in the players inventory are reset.

    The scripts involved are PlayerInventory which stores an individual players inventories on their client, PlayerInteraction which deals with detecting the storage objects among other things, and StorageObject which represents the storage object and its inventory/relevant data.

    The methods are as follows:

    1. Storage Object Interaction - Method on PlayerInteraction script

    Code (CSharp):
    1. case InteractType.Storage:
    2.                 if (_mainHand.item.Id <= -1) // Hand is empty
    3.                 {
    4.                     InventoryObject _storageInventory = interactedObject.GetComponent<StorageObject>().storageInventory;
    5.                     if (!interactedObject.GetComponent<StorageObject>().inUse)
    6.                     {
    7.                         playerInventory.LoadStorageMenu(interactedObject, _storageInventory);
    8.  
    9.                     }
    10.                 }
    11. break;
    2. Loading Storage Data - Method on PlayerInventory script

    Code (CSharp):
    1.     public void LoadStorageMenu(GameObject _storageObject, InventoryObject _inventory)
    2.     {
    3.         if (!storageOpen && !_storageObject.GetComponent<StorageObject>().inUse)
    4.         {
    5.             storageObject = _storageObject;
    6.             storageObjectId = System.Convert.ToInt32(storageObject.GetComponent<NetworkIdentity>().netId);
    7.             ToggleInterface(SwitchState.Open);
    8.             storageMenu.SetActive(true);
    9.             for (int i = 0; i < _inventory.GetSlots.Length; i++)
    10.             {
    11.                 playerStorageInventory.GetSlots[i].slotDisplay.SetActive(true);
    12.                 playerStorageInventory.GetSlots[i].UpdateSlot(_inventory.GetSlots[i].item, _inventory.GetSlots[i].amount, _inventory.GetSlots[i].durability, _inventory.GetSlots[i].storedItems, _inventory.GetSlots[i].storedAmounts, _inventory.GetSlots[i].storedDurabilities, _inventory.GetSlots[i].storedItemsSlotID, _inventory.GetSlots[i].storedItemsSlotAmount);
    13.             }
    14.             playerInventory.position = storageMenuOffset.position;
    15.             DeactivateStorage(storageObjectId);
    16.             storageOpen = true;
    17.         }
    18.     }
    3. Close Inventory Call - Method on PlayerInventory script

    Code (CSharp):
    1.     public void CloseInventory()
    2.     {
    3.         if (storageObject != null) { UpdateStorage(storageObject.GetComponent<StorageObject>().storageInventory.GetSlots.Length); }
    4.         playerCamera.ResetCameraPosition(playerCamera.interfaceCamera, playerCamera.interfaceCameraPosition, PlayerCamera.CameraType.Interface);
    5.         playerInventory.gameObject.SetActive(false);
    6.         playerInventory.position = inventoryPosition.position;
    7.         for (int i = 0; i < playerStorageInventory.GetSlots.Length; i++)
    8.         {
    9.             playerStorageInventory.GetSlots[i].slotDisplay.SetActive(false);
    10.         }
    11.         storageMenu.SetActive(false);
    12.         disableSelectionReset = false;
    13.         inventoryOpen = false;
    14.     }
    4. Update Storage Data - Method on PlayerInventory script

    Code (CSharp):
    1.     public void UpdateStorage(int slotCount)
    2.     {
    3.         storageObjectId = System.Convert.ToInt32(storageObject.GetComponent<NetworkIdentity>().netId);
    4.         StorageObject _storageObject = NetworkIdentity.spawned[System.Convert.ToUInt32(storageObjectId)].GetComponent<StorageObject>();
    5.         for (int i = 0; i < _storageObject.storageInventory.GetSlots.Length; i++)
    6.         {
    7.             DataDeconstructor(storageObjectId, i, playerStorageInventory.GetSlots[i].item.Id, playerStorageInventory.GetSlots[i].amount, playerStorageInventory.GetSlots[i].durability, playerStorageInventory.GetSlots[i].storedItems, playerStorageInventory.GetSlots[i].storedAmounts, playerStorageInventory.GetSlots[i].storedDurabilities, playerStorageInventory.GetSlots[i].storedItemsSlotID, playerStorageInventory.GetSlots[i].storedItemsSlotAmount);
    8.         }
    9.         ReactivateStorage(storageObjectId);
    10.         storageOpen = false;
    11.     }
    5. Client To Server Command - Method on PlayerInventory script

    Code (CSharp):
    1.     [Command]
    2.     public void DataDeconstructor(int netID, int slotId, int Id, int amount, int durability, int[] storedItems, int[] storedAmounts, int[] storedDurabilities, int[] storedItemsSlotID, int[] storedItemsSlotAmount)
    3.     {
    4.         NetworkIdentity.spawned[System.Convert.ToUInt32(netID)].GetComponent<StorageObject>().DataReconstructor(slotId, Id, amount, durability, storedItems, storedAmounts, storedDurabilities, storedItemsSlotID, storedItemsSlotAmount);
    5.     }
    6. Server to Clients Command - Method on StorageObject script

    Code (CSharp):
    1.     [ClientRpc]
    2.     public void DataReconstructor(int _slotID, int _Id, int _amount, int _durability, int[] _storedItems, int[] _storedAmounts, int[] _storedDurabilities, int[] _storedItemsSlotID, int[] _storedItemsSlotAmount)
    3.     {
    4.         storageInventory.GetSlots[_slotID].item.Id = _Id;
    5.         storageInventory.GetSlots[_slotID].amount = _amount;
    6.         storageInventory.GetSlots[_slotID].durability = _durability;
    7.         storageInventory.GetSlots[_slotID].storedItems = _storedItems;
    8.         storageInventory.GetSlots[_slotID].storedAmounts = _storedAmounts;
    9.         storageInventory.GetSlots[_slotID].storedDurabilities = _storedDurabilities;
    10.         storageInventory.GetSlots[_slotID].storedItemsSlotID = _storedItemsSlotID;
    11.         storageInventory.GetSlots[_slotID].storedItemsSlotAmount = _storedItemsSlotAmount;
    12.     }
    I realize that this is extremely specific. If anyone has any suggestions I would really appreciate it as I cannot find a solution to this no matter how much I look over the code.
     
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    Is it because Convert.ToInt32() on the netId is failing and returning 0? Or possibly because you use ToInt32 in some places and ToUInt32 in others?
     
  3. Joewa654321_

    Joewa654321_

    Joined:
    Apr 27, 2020
    Posts:
    32
    I use the conversion to switch from a uint to an int and back only when working with the netIDs to obtain my objects across clients as netIDs are stored as uints, I don't think that would affect my item data but I will look into an alternative method to see if this could be causing some unforeseen issue. Thanks for the reply!
     
  4. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    I truthfully don't have any experience with this at all, but the docs I read looking up how to help you gave me a hunch. I saw that netId is an instance of NetworkInstanceId, which isn't an int at all, so it's going to Convert.ToInt32() as an implicit string which is why you're not getting any warnings or errors of type mismatch. If you check the ToString() method on NetworkInstanceId, it says: "Returns a string of "NetID:value"". There's no way Convert.ToInt32() is going to know what to do with that, so compounding further is the fact that Convert.ToInt32() silently fails with a value of 0 when it can't convert a string, so you're not getting any errors or exceptions there either.
     
  5. Joewa654321_

    Joewa654321_

    Joined:
    Apr 27, 2020
    Posts:
    32
    I ended up removing the conversion entirely and representing the netID as a uint from the start. There was no reason for me to do it that way anyway so my mistake. Thanks for pointing it out, sadly my issue remains, will keep trying