Controller variable not replicating

Hello, I have a quick replication question.
I set up the following function inside my gamemode, It generates a new APRT_Loadout type variable and sets the value of CurrentLoadout on every player controller

void APRT_GameMode::StartNewRound()
{
	Super::StartNewRound();

	//get a loadout and set it on controller
	APRT_Loadout* ChosenLoadout = LoadoutGenerator->PickLoadout();
		
	if(ChosenLoadout)
	{
		for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
		{
			APRT_PlayerController* Controller = Cast<APRT_PlayerController>(*Iterator);
			if(Controller) Controller->SetCurrentLoadout(ChosenLoadout);
		}
	}
}
void APRT_PlayerController::SetCurrentLoadout(APRT_Loadout* New)
{
	CurrentLoadout = New;
}

The CurrentLoadout inside the playercontroller is replicated using a rep notifier:

UPROPERTY(ReplicatedUsing=OnRep_CurrentLoadout)
APRT_Loadout* CurrentLoadout;
void APRT_PlayerController::OnRep_CurrentLoadout()
{
	if (GetShooterHUD()) GetShooterHUD()->LoadoutSpawned(CurrentLoadout);
}

However, the variable never replicates back to the client, the rep notifier of course doesn’t fire as well.
My GetLifetimeReplicatedProps is all set too:

void APRT_PlayerController::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(APRT_PlayerController, CurrentLoadout);
}

Any ideas why this happens? Thanks!

EDIT: I changed my DOREPLIFETIME for:
DOREPLIFETIME_CONDITION_NOTIFY(APRT_PlayerController, CurrentLoadout, COND_None, REPNOTIFY_Always);

and the rep notify happens but it replicates a nullptr

Is the loadout actor itself also replicated? The pointer is probably resolving to nullptr because that actor is not replicated, and therefore doesn’t exist client-side.

hello TheJamsh, thanks for your reply.
yes, the loadout actor is also replicated.
I’ve tried multiple setups (CurrentLoadout inside character, inside the controller, etc)

Right now, I’ve put the CurrentLoadout variable inside the character, just to change how it is setup to see if it works, since I had no other ideas on how to resolve this:

The gamemode picks a new loadout and iterates over the controllers on the world, getting their character and setting the CurrentLoadout var inside them:

void APRT_GameMode_Wingman::StartNewRound()
{
	Super::StartNewRound();

	//get a loadout and set it on controller
	APRT_Loadout* ChosenLoadout = LoadoutGenerator->PickLoadout();
		
	if(ChosenLoadout)
	{
		for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
		{
			APRT_PlayerController* Controller = Cast<APRT_PlayerController>(*Iterator);
			APRT_Character* Char = Controller->GetPrtCharacter();
			if(Char) Char->SetCurrentLoadout(ChosenLoadout);
		}
	}
}
void APRT_Character::SetCurrentLoadout(APRT_Loadout* New)
{
	CurrentLoadout = New;
}

The CurrentLoadout variable inside the character is all set to replicate:

	UPROPERTY(VisibleAnywhere, Category=Character, ReplicatedUsing=OnRep_CurrentLoadout)
	APRT_Loadout* CurrentLoadout;
void APRT_Character::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(APRT_Character, CurrentItemInHand);
	DOREPLIFETIME(APRT_Character, CurrentLoadout);
	DOREPLIFETIME(APRT_Character, Health);
	DOREPLIFETIME(APRT_Character, PitchReplicated);
	DOREPLIFETIME_CONDITION(APRT_Character, Inventory, COND_OwnerOnly);
	DOREPLIFETIME_CONDITION(APRT_Character, bWantsToRun, COND_SkipOwner);
	DOREPLIFETIME_CONDITION(APRT_Character, bIsTargeting, COND_SkipOwner);
	DOREPLIFETIME_CONDITION(APRT_Character, LastTakeHitInfo, COND_Custom);
}

the APRT_Loadout class is also replicated:

APRT_Loadout::APRT_Loadout()
{
	bReplicates = true;
}

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

CurrentLoadout is set server-side, but it is not replicated back to the clients.
I need it to replicate so I can perform a HUD update with the OnRep event:

void APRT_Character::OnRep_CurrentLoadout() const
{
	APRT_PlayerController* PlrController = Cast<APRT_PlayerController>(Controller);

	if(PlrController && PlrController->GetShooterHUD())
	{
		PlrController->GetShooterHUD()->LoadoutSpawned(CurrentLoadout);
	}
}

I have also created an Exec method so I can generate a new CurrentLoadout value for every character anytime during the match using the console, it does set the variable server-side but does not replicate it back.

any ideas? thanks again!

And what about network relevancy? Where in in the game-world the loadout actor being spawned?

Best guess right now is that it’s an actor with no components and is just being spawned at the world origin. Players who are too far from that origin are not receiving it.

It is being spawned at the world origin but all the other actors are very close to it, relevancy is not really used on the project because the level is pretty small.
Loadout has no components but does have other variables, all of them are replicated as well

UPROPERTY(BlueprintReadOnly, Replicated)
	TSubclassOf<APRT_Weapon> Primary;
	
	UPROPERTY(BlueprintReadOnly, Replicated)
	TArray<TSubclassOf<APRT_BaseAttachment>> PrimaryAttachments;
	
	UPROPERTY(BlueprintReadOnly, Replicated)
	TSubclassOf<APRT_Weapon> Secondary;
	
	UPROPERTY(BlueprintReadOnly, Replicated)
	TArray<TSubclassOf<APRT_BaseAttachment>> SecondaryAttachments;
void APRT_Loadout::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(APRT_Loadout, Primary)
	DOREPLIFETIME(APRT_Loadout, PrimaryAttachments)
	DOREPLIFETIME(APRT_Loadout, Secondary)
	DOREPLIFETIME(APRT_Loadout, SecondaryAttachments)
}

The LoadoutGenerator object, which returns the CurrentLoadout value, is spawned by the gamemode on beginplay and only exists on the server, the gamemode then gets a value from it and sets the value of CurrentLoadout on every character.
The value of CurrentLoadout is replicated but the object which generated it only exists server-side.
Maybe that’s the problem? should I remove the loadout generator from the gamemode and bring it to the gamestate, setting it to replicate?

The way I wanted to set it up was: LoadoutGenerator only exists on the server, it generates a new loadout and sets the value of CurrentLoadout on every character server-side, the value of currentloadout is replicated, so clients will know it has been updated and will trigger the rep notify

1 Like

I can’t believe it, the problem was the Loadout actor was being spawned by the NewObject function and not the SpawnActor function inside UWorld. I don’t know why, but when an actor is created using the NewObject function it does not replicate, it needs to be spawned by the SpawnActor method.
@Jambax thanks for replying to my topic and trying to help me, much appreciated.

EDIT: Actually, after more testing, I can actually replicate the TSubClassOf array, however I was having trouble because I didn’t set the array to be a const reference, and that was confusing me a lot and I made false assumptions. You can ignore what I asked below!

Hello again,
I’ve been trying to wrap my head around replication for months and when I think I know how it works, a new question appears.
So I’ve been playing around with some values after the problem I faced above, and I have a question:

Suppose I have a TArray<TSubClassOf> inside my GameMode. Imagine I fill up this array beforehand by setting it to EditDefaultsOnly and adding new values inside the GameMode BP.

Also, Imagine I have a TArray<TSubClassOf> inside my player controller, and that one replicates.

Eventually during the gameplay, the GameMode sets the array of the player controller to be = its own array.

So I tested this and the array replicates back but with nothing inside it.

Does that happen because TSubClassOf is not replicated (since it’s not an AActor)?

Or does this happen because the TArray the player controller refers to is inside the gamemode and therefore only exists on the server?

I think the first question is the answer, and the second one is just some confusion I’m making, but I just need a confirmation.

Sorry if this sounds confusing but replication is a complex subject. Thanks!

1 Like