Hey guys,
i am at a point where i don’t know what to do anymore. I want to create an Inventory for each Player in a Multiplayer Game that works seamlessly with Chests and other things.
So, after talking to someone on skype, i tried to do this with an ActorComponent that i give to every Actor which needs some kind of an Inventory.
For now, the PlayerState and the Chest got one.
PlayerState.h
#pragma once
#include "GameFramework/PlayerState.h"
#include "UnknownProjectPlayerState.generated.h"
/**
*
*/
UCLASS()
class UNKNOWNPROJECT_API AUnknownProjectPlayerState : public APlayerState
{
GENERATED_BODY()
/// Variables ///
public:
UPROPERTY(Replicated, BlueprintReadOnly, VisibleAnywhere, Category = "Inventory")
class UInventoryComponent* Inventory;
/// Functions ///
public:
/// Constructors ///
AUnknownProjectPlayerState(const FObjectInitializer& ObjectInitializer);
/// Function Override ///
virtual void BeginPlay() override;
/// Network Function to Replicate Variables ///
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const;
};
PlayerState.cpp
#include "UnknownProject.h"
#include "UnknownProjectPlayerState.h"
AUnknownProjectPlayerState::AUnknownProjectPlayerState(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bReplicates = true;
Inventory = CreateDefaultSubobject<UInventoryComponent>(FName("Inventory"));
Inventory->SetIsReplicated(true);
}
/// Function Override ///
void AUnknownProjectPlayerState::BeginPlay()
{
Super::BeginPlay();
}
/// Network Function to Replicate Variables ///
void AUnknownProjectPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AUnknownProjectPlayerState, Inventory)
}
And the ChestCode looks similar, but let’s only focus on the PlayerState, because this is already not working like i want.
Inside of my UInventoryComponent (code below), i have a replicated Array of Items (that gets replicated using an OnRep function), the inventory size, a delegate that should be bind to a function
inside the widget to update it and 2 functions that should help editing the Array for the beginning. Both of these functions have a normal and a Client->Server version.
Let’s only concentrate on the AddItemToInventory function. It is called from a linetrace that hits an item. Then it searches for other stacks in the inventory, or adds
the item to an empty place. Since the client calls it, i call the server version if the OwnerRole is not authority. The Server then adds the Item to the Array
and now the OnRep should get called + the Delegate. But the Delegate is only working on Client 1 and Client 2 is just doing nothing.
The function it self works, since i printed the arrays. And if i update the Widget by hand, i see my items. But the delegate is not working.
I could remove the “return” after the server call and move it to the end of the function, to have a Client version of the array for displaying and then updating it later
from serverside, but that doesn’t solve the problem of the delegate not firing. If my client cheats and changes the inventory, the server would fix it but the widget won’t
get updated correctly. I could also create some kind of a C++ version of a widget, but why should i if i can bind a delegate to the update event.
Or is this not working in Network Classes? Is the bind getting overridden or something like that?
I am also not happy with the “OwnerOnly” flag. This InventoryComponent should work on a chest without changing anything.
The Inventory should be visible for all clients looking into the chest. So it should be replicated to everyone.
I also created a reference of the InventoryComponent on my Widgets that gets passed when the widget is created. So i can pass the component to the second function.
Although this somewhat seems to work, the component is not getting replicated correctly. I also tried to replicated the InventoryComponent Variable in the PlayerState,
but i still had problems getting the Array in my Widgets. Accessing the Array directly from the PlayerState gives me the correct array, but passing the Component from the PlayerState
to a widget and getting the Array gives me an empty array -.-
InventoryComponent.h
#pragma once
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FUpdateWidgetInventory);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UNKNOWNPROJECT_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
/// Variables ///
public:
// Inventory Properties //
UPROPERTY(ReplicatedUsing = OnRep_Inventory, BlueprintReadWrite, EditAnywhere, Category = "Inventory")
TArray<class ABase_Item*> Inventory;
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Inventory")
int32 InventorySize;
UPROPERTY(ReplicatedUsing = OnRep_Inventory)
bool bUpdate;
// Delegates //
UPROPERTY(BlueprintAssignable, Category = "Inventory")
FUpdateWidgetInventory UpdateInventoryDelegate;
/// Functions ///
public:
// Sets default values for this component's properties
UInventoryComponent();
// Called when the game starts
virtual void InitializeComponent() override;
// Called every frame
virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;
/// Network Function to Replicate Variables ///
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const;
UFUNCTION()
void OnRep_Inventory();
UFUNCTION(BlueprintCallable, Category = "Inventory")
void AddItemToInventory(class ABase_Item* _Item);
UFUNCTION(reliable, server, WithValidation)
void Server_AddItemToInventory(class ABase_Item* _Item);
bool Server_AddItemToInventory_Validate(class ABase_Item* _Item);
void Server_AddItemToInventory_Implementation(class ABase_Item*);
UFUNCTION(BlueprintCallable, Category = "Inventory")
void MoveItem(int32 _TargetSlot, UInventoryComponent* _SourceInvCom, int32 _SourceSlot);
UFUNCTION(reliable, server, WithValidation)
void Server_MoveItem(int32 _TargetSlot, UInventoryComponent* _SourceInvCom, int32 _SourceSlot);
bool Server_MoveItem_Validate(int32 _TargetSlot, UInventoryComponent* _SourceInvCom, int32 _SourceSlot);
void Server_MoveItem_Implementation(int32, UInventoryComponent*, int32);
};
InventoryComponent.cpp
#include "UnknownProject.h"
#include "InventoryComponent.h"
// Sets default values for this component's properties
UInventoryComponent::UInventoryComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
bWantsInitializeComponent = true;
PrimaryComponentTick.bCanEverTick = true;
// ...
bReplicates = true;
}
// Called when the game starts
void UInventoryComponent::InitializeComponent()
{
Super::InitializeComponent();
Inventory.Init(nullptr, InventorySize);
for (int32 i = 0; i < InventorySize; i++)
{
Inventory* = nullptr;
}
}
// Called every frame
void UInventoryComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction )
{
Super::TickComponent( DeltaTime, TickType, ThisTickFunction );
// ...
}
/// Network Function to Replicate Variables ///
void UInventoryComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UInventoryComponent, Inventory);
DOREPLIFETIME(UInventoryComponent, bUpdate);
}
/// Network Function to change Inventory ///
// OnRepNotify when the Inventory gets changed
void UInventoryComponent::OnRep_Inventory()
{
if (GetOwnerRole() < ROLE_Authority)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Green, TEXT("Delegate RepCall"));
UpdateInventoryDelegate.Broadcast();
}
}
// Add Item to the Inventory
void UInventoryComponent::AddItemToInventory(class ABase_Item* _Item)
{
if (GetOwnerRole() < ROLE_Authority)
{
Server_AddItemToInventory(_Item);
return;
}
// Bool to keep track of destroyed item
bool bWasDestroyed = false;
/*
Make sure our Item is still Valid. If yes, check if we have a specific Slot that we want to fill.
*/
if (_Item)
{
/*
Search for an Item of that Type to Stack it
*/
if (_Item->GetCanBeStacked())
{
for (int32 i = 0; i < InventorySize; i++)
{
if (Inventory.IsValidIndex(i) && Inventory*)
{
// If we found an Item that is the same
if (Inventory*->GetClass() == _Item->GetClass())
{
if (Inventory*->Amount < Inventory*->GetMaxStacks())
{
int32 Difference = Inventory*->Amount + _Item->Amount - Inventory*->GetMaxStacks();
/*
When the difference is equal to 0, the Stacks hits exactly the MaxStacks and we
destroy the _Item we want to add.
When the difference is greater than 0, we have more Items then we can Stack, so
we just fill the Stack to max and leave the rest on the Item. We search for a
new Item to place the Rest on.
When the difference is less than 0, we can add the new amount on the old and destroy
the _Item.
*/
if (Difference == 0)
{
Inventory*->Amount = Inventory*->GetMaxStacks();
bWasDestroyed = true;
_Item->Destroy();
bUpdate = !bUpdate;
break;
}
else if (Difference > 0)
{
Inventory*->Amount = Inventory*->GetMaxStacks();
_Item->Amount = Difference;
bUpdate = !bUpdate;
}
else
{
Inventory*->Amount = Inventory*->Amount + _Item->Amount;
bWasDestroyed = true;
_Item->Destroy();
bUpdate = !bUpdate;
break;
}
}
}
}
}
}
/*
If no existing Item was found, we will search for an empty spot and add the
item there.
*/
if (_Item && !bWasDestroyed)
{
for (int32 i = 0; i < InventorySize; i++)
{
// We found an empty spot, put the Item here and set the correct ItemSlot to use later in UMG
if (Inventory.IsValidIndex(i) && !Inventory*)
{
Inventory* = _Item;
_Item->SetCurrentSlot(i);
_Item->bIsVisible = false;
// Make sure that the client updates the visibility right away
// Also the Server calls this, because he won't get the RepNotify call
_Item->MakeItemInvisible();
bUpdate = !bUpdate;
break;
}
if (i == InventorySize - 1)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Green, TEXT("Inventory full!"));
// Inventory full
}
}
}
}
if (GetOwnerRole() < ROLE_Authority)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Green, TEXT("Delegate ClientCall"));
UpdateInventoryDelegate.Broadcast();
}
}
bool UInventoryComponent::Server_AddItemToInventory_Validate(class ABase_Item* _Item)
{
return true;
}
void UInventoryComponent::Server_AddItemToInventory_Implementation(class ABase_Item* _Item)
{
if (GetOwnerRole() == ROLE_Authority)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Green, TEXT("ServerCall"));
AddItemToInventory(_Item);
}
}
// Move Item in one or two Inventories
void UInventoryComponent::MoveItem(int32 _TargetSlot, UInventoryComponent* _SourceInvCom, int32 _SourceSlot)
{
if (GetOwnerRole() < ROLE_Authority)
{
Server_MoveItem(_TargetSlot, _SourceInvCom, _SourceSlot);
}
if (!_SourceInvCom || !_SourceInvCom->Inventory[_SourceSlot])
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("InvCom or Slot not valid!"));
return;
}
if (this == _SourceInvCom && _TargetSlot == _SourceSlot)
{
return;
}
// Empty Slot? Just fill the Item and remove it from the other Inventory
if (!Inventory[_TargetSlot])
{
Inventory[_TargetSlot] = _SourceInvCom->Inventory[_SourceSlot];
_SourceInvCom->Inventory[_SourceSlot]->SetCurrentSlot(_TargetSlot);
_SourceInvCom->Inventory[_SourceSlot] = nullptr;
}
else
{
if (Inventory[_TargetSlot]->GetClass() != _SourceInvCom->Inventory[_SourceSlot]->GetClass())
{
ABase_Item* Temp_Item = Inventory[_TargetSlot];
Inventory[_TargetSlot] = _SourceInvCom->Inventory[_SourceSlot];
_SourceInvCom->Inventory[_SourceSlot] = Temp_Item;
Inventory[_TargetSlot]->SetCurrentSlot(_TargetSlot);
_SourceInvCom->Inventory[_SourceSlot]->SetCurrentSlot(_SourceSlot);
}
else
{
if (Inventory[_TargetSlot]->GetMaxStacks() >= (Inventory[_TargetSlot]->Amount + _SourceInvCom->Inventory[_SourceSlot]->Amount))
{
Inventory[_TargetSlot]->Amount += _SourceInvCom->Inventory[_SourceSlot]->Amount;
_SourceInvCom->Inventory[_SourceSlot]->Destroy();
_SourceInvCom->Inventory[_SourceSlot] = nullptr;
}
else
{
int32 Difference = (Inventory[_TargetSlot]->Amount + _SourceInvCom->Inventory[_SourceSlot]->Amount) - Inventory[_TargetSlot]->GetMaxStacks();
Inventory[_TargetSlot]->Amount = Inventory[_TargetSlot]->GetMaxStacks();
_SourceInvCom->Inventory[_SourceSlot]->Amount -= Difference;
}
}
}
if (GetOwnerRole() < ROLE_Authority)
{
UpdateInventoryDelegate.Broadcast();
_SourceInvCom->UpdateInventoryDelegate.Broadcast();
}
}
bool UInventoryComponent::Server_MoveItem_Validate(int32 _TargetSlot, UInventoryComponent* _SourceInvCom, int32 _SourceSlot)
{
return true;
}
void UInventoryComponent::Server_MoveItem_Implementation(int32 _TargetSlot, UInventoryComponent* _SourceInvCom, int32 _SourceSlot)
{
if (GetOwnerRole() == ROLE_Authority)
{
MoveItem(_TargetSlot, _SourceInvCom, _SourceSlot);
}
}