Subobject's field replication problem

Hi guys,

I have problem because one of my fields is not updating on client side (on server works well).
Ok in my GameState class (AAPOGameState) I have TArray of UQuest.
UQuest class has some basic fields like name and TArray of UQuestKillObjective.
In UQuestKillObjective I have 3 important values: mobId, numberOfMobsToKill, numberOfKilledMobs.
UQuest has method which passes events to all objectives in quest.

And here is my problem when I enter into trigger which generates event value are updated on server side but not on clients.
Base on my research only one field isn’t updated (numberOfKilledMobs). Is looks like this field was replicated only once when I’m adding quest to quest log.

A LOT OF CODE :slight_smile:

GameState.h:

class APO_API AAPOGameState : public AGameState
{
	GENERATED_BODY()
protected:
	UPROPERTY(Replicated, EditDefaultsOnly, BlueprintReadWrite, Category = "Quest", Meta = (BlueprintProtected = "true"))
		FString actualQuestTitle;
	UPROPERTY(Replicated, EditDefaultsOnly, BlueprintReadWrite, Category = "Quest", Meta = (BlueprintProtected = "true"))
		FString actualQuestObjectives;

	UPROPERTY(Replicated, EditDefaultsOnly, BlueprintReadWrite, Category = "Quest", Meta = (BlueprintProtected = "true"))
		TArray<UQuest*> questLog;

public:

	virtual void PostInitializeComponents() override;

	UFUNCTION(BlueprintCallable, Category = "Quest")
		FORCEINLINE FString getActualQuestTitle() const { return actualQuestTitle; }
	UFUNCTION(BlueprintCallable, Category = "Quest")
		FORCEINLINE FString getActualQuestObjectives() const { return actualQuestObjectives; }
	UFUNCTION(BlueprintCallable, Category = "Quest")
		TArray<UQuest*> getQuestLog();
	

	UFUNCTION(BlueprintCallable, Category = "Quest")
		void setActualQuestTitle(FString _actualQuestTitle);
	UFUNCTION(BlueprintCallable, Category = "Quest")
		void setActualQuestObjectives(FString _actualQuestObjectives);
	UFUNCTION(BlueprintCallable, Category = "Quest")
		void setQuestLog(TArray<UQuest*> _questLog);


	UFUNCTION(BlueprintCallable, Category = GameState, Server, Reliable, WithValidation)
		void Server_ExecuteQuestEvent(UQuestEventAttr* eAttr);

	//Replication
	virtual bool ReplicateSubobjects(UActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags) override;

public:
	int selectedQuestId;

GameState.cpp

	void AAPOGameState::PostInitializeComponents() {
        	Super::PostInitializeComponents();
        
        	this->selectedQuestId = 0;
        }
        
        void AAPOGameState::setQuestLog(TArray<UQuest*> _questLog) {
        	this->questLog.Empty();
        	this->questLog += _questLog;
        }
        TArray<UQuest*> AAPOGameState::getQuestLog() {
        	return questLog;
        }
        
        void AAPOGameState::Server_ExecuteQuestEvent_Implementation(UQuestEventAttr* eAttr)
        {
            //just walkaround to make code simple
        	this->questLog[0]->execEvent(eAttr);
        }
    
        void AAPOGameState::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
        {
        	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
        	DOREPLIFETIME(AAPOGameState, actualQuestTitle);
        	DOREPLIFETIME(AAPOGameState, actualQuestObjectives);
        	DOREPLIFETIME(AAPOGameState, questLog);
        }
    
        bool AAPOGameState::ReplicateSubobjects(UActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
        {
        	bool wroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
        	for (UQuest * myObject : questLog)
        	{
        		if (myObject)
        		{			
        			Channel->ReplicateSubobject(myObject, *Bunch, *RepFlags);
        			myObject->ReplicateSubobjects(Channel, Bunch, RepFlags);
        		}
        	}
        	return wroteSomething;
        }

Quest.h:

class APO_API UQuest : public UObject
{
	GENERATED_UCLASS_BODY()
private:
	UPROPERTY(Replicated)
	int id;
	UPROPERTY(Replicated)
	FString name;
	UPROPERTY(Replicated)
	FString discription;
	UPROPERTY(Replicated)
	TArray<UQuestKillObjective*> objectiveList;
public:
	void init(int _id, FString _name,FString _discription, TArray<UQuestKillObjective*> _objectiveList);

	UFUNCTION(BlueprintPure, Category = "Quest")
		int getId() { return id; }
	UFUNCTION(BlueprintPure, Category = "Quest")
		FString getName() { return name; }
	UFUNCTION(BlueprintPure, Category = "Quest")
		FString getDiscription();

	void execEvent(UQuestEventAttr *attr);
	void isComplate(void);


	//REPLICATION
	virtual bool IsSupportedForNetworking() const override
	{
		return true;
	}
	virtual bool ReplicateSubobjects(UActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags);

};

Quest.cpp:

void UQuest::execEvent(UQuestEventAttr *attr) {
	for (int32 Index = 0; Index != this->objectiveList.Num(); ++Index)
	{
		if(this->objectiveList[Index]->isCompleate())continue;
		this->objectiveList[Index]->exec(attr);
	}
}

FString UQuest::getDiscription() {
	FString objectives = FString(TEXT(""));
	for (int32 Index = 0; Index != this->objectiveList.Num(); ++Index)
	{
		objectives += this->objectiveList[Index]->toString();
		objectives += TEXT("\n");
	}
	return objectives;
}

//REPLICATION
void UQuest::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(UQuest, id);
	DOREPLIFETIME(UQuest, name);
	DOREPLIFETIME(UQuest, discription);
	DOREPLIFETIME(UQuest, objectiveList);
}

bool UQuest::ReplicateSubobjects(UActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
{
	for (UQuestKillObjective * myObject : objectiveList)
	{
		if (myObject)
		{
			Channel->ReplicateSubobject(myObject, *Bunch, *RepFlags);
			myObject->ReplicateSubobjects(Channel, Bunch, RepFlags);
		}
	}
	return false;
}

QuestKillObjective.h

class APO_API UQuestKillObjective: public UObject
{
	GENERATED_UCLASS_BODY()
public:
	UQuestKillObjective* Init(int _mobId, int _numberOfMobsToKill);
	FString toString();
	void exec(UQuestEventAttr *attr) ;
	bool isCompleate();
	//REPLICATION
	virtual bool ReplicateSubobjects(UActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags);
	virtual bool IsSupportedForNetworking() const override
	{
		return true;
	}
private:
	UPROPERTY(Replicated)
	int mobId;
	UPROPERTY(Replicated)
	int numberOfMobsToKill;
	UPROPERTY(Replicated)
	int numberOfKilledMobs;
};

QuestKillObjective.cpp

UQuestKillObjective::UQuestKillObjective(const class FObjectInitializer& PCIP)
	: Super(PCIP)
{
	numberOfKilledMobs = 0;
}

UQuestKillObjective* UQuestKillObjective::Init(int _mobId, int _numberOfMobsToKill)
{
	this->mobId = _mobId;
	this->numberOfMobsToKill = _numberOfMobsToKill;
	return this;
}

FString UQuestKillObjective::toString() {
	const FString obj = FString::Printf(TEXT("Kill %i / %i"), numberOfKilledMobs, numberOfMobsToKill);
	return obj;
}
void UQuestKillObjective::exec(UQuestEventAttr *attr) {
	if (attr->getType().Equals(FString(TEXT("KILL"))) && this->mobId == attr->getMobId()) {
		this->numberOfKilledMobs++;
	}
}
bool UQuestKillObjective::isCompleate() {
	if (numberOfKilledMobs < numberOfMobsToKill) return false;
	else return true;	
}

//REPLICATION
void UQuestKillObjective::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(UQuestKillObjective, mobId);
	DOREPLIFETIME(UQuestKillObjective, numberOfMobsToKill);
	DOREPLIFETIME(UQuestKillObjective, numberOfKilledMobs);
}

bool UQuestKillObjective::ReplicateSubobjects(UActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
{
	return false;
}