Calling Client RPC in order to assign a Team value to clients. How to check the Role properly?

Hi there

I am trying to make an Air Hockey style game. I want to give the ‘Listen Server/Host’ the team value ‘0’ and the client the value ‘1’.

I am writing this function inside BeginPlay() on an Actor I created specially for managing the ‘Match Stats’. I have dragged this actor into my ‘Game’ map. (The process so far for clients joining the game is MAIN_MENU>LOBBY>GAME).

Anyway, I am REALLY struggling with calling a Client RPC in order to set the Team value on the client. Here is the function as I have it now, but I have tried many variants of this.

Basically my problem here stems from mallet->GetActorRole(), it seems it always returns ROLE_AUTHORITY. So in the following example, the else part never fires (neither on the Listen-Server instance or the Client instance).

I am clearly understanding it wrong, I figured the mallet-> part would mean it was accessing a property in that particular mallet. So I figured the ROLE_AUTHORITY would be different for the Host and for the Client.

// Called when the game starts or when spawned
void AMatchManager::BeginPlay()
{
	Super::BeginPlay();
	SetReplicates(true);

	UWorld* World=GetWorld();
	if(!ensure(World!=nullptr)) return;

	if(GetWorld()->IsServer())
	{
		UE_LOG(LogTemp, Warning, TEXT("Match Manager BeginPlay (IS SERVER)"));
		if(HasAuthority())
		{
			SpawnPuck();

			AGameStateBase* gamestate=GetWorld()->GetGameState();
			if(gamestate!=nullptr)
			{
				
				for(int i=0; i<gamestate->PlayerArray.Num(); i++)
				{
					APlayerMallet* mallet=(APlayerMallet*)gamestate->PlayerArray[i]->GetPawn();
					if(mallet!=nullptr)
					{
						UE_LOG(LogTemp, Warning, TEXT("setting team from MatchManager BeginPlay (SERVER AUTH) team will be assigned as:%d"), (i%2));
						if(mallet->GetLocalRole() == ROLE_Authority)
						{
							UE_LOG(LogTemp, Warning, TEXT("ROLE_AUTH, so it is the listen server"));
							mallet->Team=0;
						}
						else
						{
							UE_LOG(LogTemp, Warning, TEXT("This player is client... Calling RPC"));
							mallet->Client_SetTeam(i%2);
						}
					}
				}
			}
		}

	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Match Manager BeginPlay (NOT SERVER)"));
	}
	
	
}

So only one mallet can be the host but it seems that neither != ROLE_AUTHORITY

1 Like

If you add a delay on the Client the role probably is no longer ROLE_Authority it just isn’t updated on BeginPlay.

Instead of calling SetReplicates(true) at BeginPlay you should do bReplicates = true in the constructor. SetReplicates(true) is used when you dynamically want to turn Replication on and off but the constructor should be used if you want replication to be on from the start.

Thank you for this information, however I did already notice this in the output log. I had put the SetReplicates(true) line in as part of me trial-and-error fix method. Sadly I was still struggling with this same problem.

I have been trying so many combos (eg. HasAuth && world->IsServer && ROLE = ROLE_AUTHORITY etc etc) that I got myself quite confused about what I have and have not tried.

This evening I am going to have another go from scratch to test it out.

But at least knowing I am on the right path is at least a good start, like my function listed above is a decent enough approach for doing such thing as setting a Team or other match based info about the players ??

I would probably just use the GameMode class to Assign teams. GameMode only exist on the Host/Server so no danger of calling it on the Clients by mistake.

I would make a OnRep Variable on the PlayerState for the Team.

At the end of GameModeBase::PostLogin I would set the Team using the PlayerController reference and Controller::GetPlayerState for getting the associated PlayerState and then set the Team variable.

Thank you I’ll give this a try

The ‘OnRep’ variable is what I have been missing I think. Going to learn more about it now. Thanks for your time in helping me.

Hi again Garner

Sorry to bother you but I have been trying to implement it the way you said. I tried many things since last writing. But now I have written a custom PlayerController class so that I can have the ‘Team’ variable on that. The problem is it seems to get reset to the default values after using Seamless Travel from the Lobby map to the Main Game map.

void APuzzlePlatformsGameMode::PostLogin(APlayerController* NewPlayer)
{
	Super::PostLogin(NewPlayer);
	UE_LOG(LogTemp, Warning, TEXT("GMode: Game mode POSTLOGIN called"));

	int team=(2-++NumberOfPlayers)%2;
	APuzzPlatPlayerController* pCont=(APuzzPlatPlayerController*)NewPlayer;
	if(pCont!=nullptr)
	{
		pCont->Team=5;
	}


	if(NumberOfPlayers>=2)
	{
		GetWorldTimerManager().SetTimer(GameStartTimer, this, &APuzzlePlatformsGameMode::StartGame, 2);
	}
}

As a test I have these log messages in Tick for the PlayerMallet (ACharacter)

void APlayerMallet::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if(GetWorld()==nullptr)
		return;
	
	APuzzPlatPlayerController* pCont = (APuzzPlatPlayerController*)GetWorld()->GetFirstPlayerController();
	if(pCont!=nullptr)
	{
		int team=pCont->Team;
		UE_LOG(LogTemp, Warning, TEXT("pCont (1st PCont) Team=%d"), team);
	}
	APuzzPlatPlayerController* pContLocal=(APuzzPlatPlayerController*)GetWorld()->GetFirstLocalPlayerFromController();
	if(pContLocal!=nullptr)
	{
		int teamlocal=pContLocal->Team;
		UE_LOG(LogTemp, Warning, TEXT("pCont (1st Local PCont) Team=%d"), teamlocal);
	}
}

But the results are wrong for both. As you can see I tried to hard-code 5 as the Team, but whenever the Main game scene begins all those values are back at default 111. Also the first of the two logs always gives 0.

Here is the full project on Github. Its fairly small still in size i think. If you can be kind enough to sometime download it and see what I mean that would be awesome (I have truly spent hundreds of hours stuck on this one problem and really cannot think of any other way to workaround the problem)

c0de-m0nk3y/AirHockeySteam (github.com)

All the best and thanks for the help you have already given.

(A final edit: I also tried using a custom PlayerState as you suggest. But the same thing is happening where the values get reset back and also had problem setting the replication up on that object- the Github link now shows the project with this method setup but is still not working :frowning:

You’re welcome.

If you instead save the variables on the PlayerState then you can use the function CopyProperties to transfer the variables to the new PlayerState being spawned in the next. Look at the existing function PlayerState:CopyProperties and do the same for your own variables.

Remember to call the Super::CopyProperties to not lose the existing code when overriding the function.

Ah I did not know about any of that function :smiley:

Here is how I did that, and now the Host/ListenServer does indeed remain correct. However the value is not replicated for the client.

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

    DOREPLIFETIME(APuzzPlatPlayerState, CurrentTeam); //WHY DOES THIS GIVE ERROR?
}

void APuzzPlatPlayerState::CopyProperties(APlayerState* PlayerState)
{
    Super::CopyProperties(PlayerState);

    UE_LOG(LogTemp, Warning, TEXT("Copying Properties YEAHBOIIIII"));
    APuzzPlatPlayerState* pState=Cast<APuzzPlatPlayerState>(PlayerState);
    pState->CurrentTeam=CurrentTeam;
}

As you can see I override the GetLifetimeReplicatedProps function, but the line where I try to use the DOREPLIFETIME macro on my CurrentTeam variable always gives me an error (it says “Type name is not allowed” for my first argument, and also it tells me DOREPLIFETIME is undefined.

In the header I have put

public:

	UPROPERTY(replicated)
		int CurrentTeam = 111;

protected:
	void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override;

The error log reads like this:

1>C:\Users\Documents\Unreal Projects\3_Steam_Multiplayer-master\UnrealProject\PuzzlePlatforms\Source\PuzzlePlatforms\PuzzPlatPlayerState.cpp(10): error C2275: 'APuzzPlatPlayerState': illegal use of this type as an expression
1>C:\Users\Documents\Unreal Projects\3_Steam_Multiplayer-master\UnrealProject\PuzzlePlatforms\Source\PuzzlePlatforms\PuzzPlatPlayerState.cpp(10): error C3861: 'DOREPLIFETIME': identifier not found

I have undated the Github project in case you wish to look at the whole project: c0de-m0nk3y/AirHockeySteam (github.com)

(I truly think this one last thing ie. the replication of that one variable CurrentTeam will be enough for me to make massive strides after I figure it out. I will leave the code alone for a little while as I ran out of ideas and don’t want to remove correct code or add any more incorrect code.)

You are missing the UnrealNetwork include in the cpp file

#include "Net/UnrealNetwork.h"

Wow thank you so much!!!

Hi Garner. Just one last message to say thanks again. You have been incredible help to me here and I now have a pretty good understanding how it all works.

Just in case you fancy a peek, here is the working code project on Github which assigns a Team and Colour material to each player when they start the game. Its very basic right now and needs work before it can be a proper game, but this is quite a nice starting point.

c0de-m0nk3y/AirHockeySteam (github.com)

Much love and respect for the helping you are giving people here.

Glad I could help.

Feel free to ask more questions and perhaps I know the answer although I am also still learning this great engine. My main area is multiplayer.