Having an issue with my replicated inventory, need to explain

To start off, I’m gonna share 2 GIF’s so you can see what’s going on in-game.
This is when it works:

And this when it doesn’t

You probably noticed what’s the difference, when it doesn’t work I first searched the contents of these “Pants” which opened my inventory UI showing me the pants’ contents (left side) with 4 empty slots, I closed the inventory, went ahead and picked up the pants, and as you saw, my inventory UI recognized I had the pants, but didn’t render it’s icon nor it’s contents.

So what’s happening under the hood when I search the pants contents:

  • Assign the inventory component reference of the pants pickup item (the little bag actor you see) to a replicated property of the inventory component of my character, like this:
void ACppProjectCharacter::GetItemContents_Implementation(AItem* item) {
    if (item) {
        Inventory->SearchedItemContent = item->Inventory;
    }
}

AItem extends from AActor, and Inventory is an UInventoryComponent
SearchedItemContent is a UInventoryComponent replicated property and calls a repnotifies function:

UPROPERTY(ReplicatedUsing = OnRep_SearchedItemContent, VisibleAnywhere, BlueprintReadOnly, Category = "Inventory")
UInventoryComponent* SearchedItemContent;

// The repnotifies function that opens the player inventory when SearchedItemContent is pointing to another inventory
void UInventoryComponent::OnRep_SearchedItemContent()
{
    if (SearchedItemContent) {
        Cast<ACppProjectCharacter>(GetOwner())->OpenInventory();
    }
}

After this the BP and UI BP take over by rendering both my player inventory and the pickup item inventory, they just read the data presented to them I don’t manipulate inventories data through BP

This is how I replicate these properties:

void UInventoryComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    DOREPLIFETIME(UInventoryComponent, Storage);
    DOREPLIFETIME_CONDITION(UInventoryComponent, SearchedItemContent, COND_OwnerOnly);
}

bool UInventoryComponent::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags) {
    bool bWroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
        // Note storage is a UStorageItemBase property, it's just an ItemBase that can hold other items in it, so this is the typical backpack or pants in a dropped pickup item
    if (Storage) {
        bWroteSomething |= Channel->ReplicateSubobject(Storage, *Bunch, *RepFlags);

        if (!Storage->Items.IsEmpty()) {
            bWroteSomething |= Channel->ReplicateSubobjectList(Storage->Items, *Bunch, *RepFlags);
        }
    }

    if (SearchedItemContent) {
        bWroteSomething |= Channel->ReplicateSubobject(SearchedItemContent, *Bunch, *RepFlags);

        if (SearchedItemContent->Storage) {
            bWroteSomething |= Channel->ReplicateSubobject(SearchedItemContent->Storage, *Bunch, *RepFlags);

            if (!SearchedItemContent->Storage->Items.IsEmpty()) {
                bWroteSomething |= Channel->ReplicateSubobjectList(SearchedItemContent->Storage->Items, *Bunch, *RepFlags);
            }
        }
    }

    return bWroteSomething;
}

Storage also has a repnotifies that updates the inventory UI accordingly:

void UInventoryComponent::OnRep_Storage()
{
    if (Storage && Storage->InventoryOwner) {
        if (ACppProjectCharacter* AsPlayer = Cast<ACppProjectCharacter>(GetWorld()->GetGameInstance()->GetFirstLocalPlayerController()->GetPawn())) {
            AsPlayer->Inventory->OnStorageUpdated.Broadcast(Storage, FText::FromString("Storage"));
        }
    }
}

NOW, UStorageItemBase has an array of UItemBase items of course, same deal, it updates the UI:

UPROPERTY(ReplicatedUsing = OnRep_Storage, VisibleAnywhere, BlueprintReadOnly, Category = "StorageItemBase")
TArray<class UItemBase*> Items;

void UStorageItemBase::OnRep_Storage()
{
    if (InventoryOwner) {
        if (IsStorageValid()) {
            if (ACppProjectCharacter* AsPlayer = Cast<ACppProjectCharacter>(World->GetGameInstance()->GetFirstLocalPlayerController()->GetPawn())) {
                AsPlayer->Inventory->OnStorageUpdated.Broadcast(this, FText::FromString(""));
            }
        }
    }
}

And finally this is how’s replicated:

void UStorageItemBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    DOREPLIFETIME_CONDITION(UStorageItemBase, Items, COND_OwnerOnly);
}

And that’s it.

Just more context though, when I move items from one inventory to another I call the function Rename on the UItemBase UObjects, so their OuterPrivate is changed to the current owner correctly, and since I have all these COND_OwnerOnly conditions for all the repnotifies (because I don’t want player clients to be aware of other inventories unless they open them.

So, what happens when I pick up the item in the debugger is that Rename works in Storage (AKA the UStorageItemBase or the Pants item I picked up), it’s OuterPrivate becomes my character, and the item is on my inventory (otherwise you couldn’t see the Pants section that’s empty in the GIF, that only shows up If the Pants property has an actual item) but the items inside it (so the Items property of Storage) appears as empty, the array is completely empty on my client unlike the server version of that item. Why? I don’t really know

Remember, this just happens when I open the contents of the item, so when my character Inventory SearchedItemContent property points to the inventory of the item pickup actor being searched on.
I thought maybe it was something with the conditions I’m setting to replicate the different properties, so I removed the condition from all the DOREPLIFETIME calls I have and tried again, unfortunately it didn’t fix anything.
Also tried with forcing a client GC each time the inventory UI is closed (and SearchedItemContent becomer nullptr on the client) and nothing.

I noticed another thing, when I move items from one inventory to another, calling Rename works fine on the server, but that doesn’t happen on client, so the client still has OuterPrivate with their original owners, so I tried calling rename on client in one of my RepNotifies as well:

void UItemBase::OnRep_Owner_Change()
{
	this->Rename(nullptr, InventoryOwner->GetOuter());
}

And, even if client finally showed the correct OuterPrivate with this change, that didn’t fix the issue at hand either

Finally, have in mind that each time I close my inventory UI, SearchedItemContent becomes nullptr and so does the client version because, again, it replicates, any reference to it should be gone.

And also I can confirm that no matter if player 1 interacts with the pickup item and even moves items into it, if a player 2 never opens the pants contents and just picks it up, it’ll work just fine for player 2, so yeah, something fishy is happening with the client of player 1. Hopefully this is the full insight of what I’m doing and you can help me find a solution for it.

Thanks in advance

Any help please? If I can’t fix this, the inventory is basically unusable