How to differentiate multiple clients / keep them apart ?

Hey once more,

I have a 4 player setup with 3 clients and a server, and replication takes care of most game logic by now. However,
I need to restrict certain actions on objects depending on the clients, eg. client #1 can only play his cards, not the other ones,
whereas client#2 can play other cards, but not those. Basically, I need a way to say:



if(handIndex == clientIndex) PlayCard();


All of my gamelogic is happening on the server, and the client correctly sends clicks with the object’s id, which allows
me to move the correct card on the server and therefore for everyone else. I now need a way to send along the client’s id,
which then allows me to differentiate between them.

I have asked for help on answerhub, but
iterating through playercontroller’s (as suggested) doesn’t work, there seems to be only one playerController for all 3 other clients, and I cannot reach it from
the server by iterating.

The other suggesting to use PlayerId inside the Playerstate is nice, but since my 40 card pawns aren’t possessed by a controller,
PlayerState is always null. Also, if I iterate for a playercontroller on the client, and use playercontroller->playerstate->playerid on that,
it will be again the same for all 3 clients. ( and also again, a playerId that I cannot find by iterating on the server)

please help me out, I’m already taking desperate approaches as well :smiley:

The advice exi gave is accurate in what you’ll need to do in your situation. The playerid should be available after RegisterPlayer(), and comes before anything related to the pawn.


void AGameSession::RegisterPlayer(APlayerController* NewPlayer, const TSharedPtr<FUniqueNetId>& UniqueId, bool bWasFromInvite)
{
	if (NewPlayer != NULL)
	{
		// Set the player's ID.
		check(NewPlayer->PlayerState);
		NewPlayer->PlayerState->PlayerId = GetNextPlayerID();
		NewPlayer->PlayerState->SetUniqueId(UniqueId);
		NewPlayer->PlayerState->RegisterPlayerWithSession(bWasFromInvite);
	}
}

You can manually assign a playerid from within Login or postlogin based on the parameter passed into clienttravel when connecting to a server map as well. You don’t need to possess any cards they can be left as an abstract class.

As you want, the game is handled on the server. You need to pass over the player id for the player who is doing the click event over to the server. When cards are dealt they will be given the same id number of the owner playercontroller’s playerstate id and that playercontroller will be given the values of those cards that they can spawn on their client side. Just make sure that you send over the playerid or playername from the client to the server as well as the object id being manipulated.

This is great, but I tried overriding PostLogin in my GameMode



void APuzzleBlankGameMode::PostLogin(APlayerController * NewPlayer){

	Super::PostLogin(NewPlayer);

	APuzzleBlankPlayerController* pc = Cast<APuzzleBlankPlayerController>(NewPlayer);
	pc->netid = incrementId;
	incrementId++;
        UE_LOG(LogTemp, Warning, TEXT("netid to pc:%d "), pc->netid);
}


and logged the netid from the PlayerController when clicking on a card



void APuzzleBlankBlock::BlockClicked(UPrimitiveComponent* ClickedComp)
{
	
	if (PlayerController) UE_LOG(LogTemp, Warning, TEXT("netid of card:%d "), PlayerController->netid);
}


and it works great for the server but the clients’ PlayerController always returns -1 ( my default value, which means they never get set to any other number ). However, the Log in Postlogin turns up 3 times in the console. So it is being called, but I don’t know where those playercontrollers end up.

############## EDIT:

I thought maybe using the PlayerId itself and not casting might change something:



void APuzzleBlankGameMode::PostLogin(APlayerController * NewPlayer){

	Super::PostLogin(NewPlayer);
	UE_LOG(LogTemp, Warning, TEXT("Gamemode postlogin"));
	
	NewPlayer->PlayerState->PlayerId = incrementId;
	incrementId++;

	UE_LOG(LogTemp, Warning, TEXT("PlayerController Playerid%d"), NewPlayer->PlayerState->PlayerId);
}


This now sets Id’s from 0 to 3, with the server being 0 and all other 3 clients always being 3. How can they all have the same PlayerController? I’m in the PIE btw, I feel like this might be a problem somehow.

I have now worked on this a full day and it honestly seems to me like all 3 clients are sharing a single PlayerController. That can’t be right.

EDIT:

Ok one more question:
When I test my game in PIE with 3 clients and 1 listen server, the clienttravel function doesn’t concern me at all. Do I need to call it anyway?

1 Like

Most likely what is happening is that the state is changing to inactive on the playercontroller when it can’t find a pawn. Either override the RestartPlayer() functionality or assign a pawn without any graphical representation and movement control.


void AController::FailedToSpawnPawn()
{
	ChangeState(NAME_Inactive);
}

You can check out a blueprint example I made here that displays the Player ID assigned to each client : Content.zip
If you’re reaching 3 then you’re creating 4 playercontrollers. If you want to determine if all players are controlling the same thing then create a default pawn for each player controller and all client windows would move the same player pawn.

From the server, after your incrementID reaches 3, you can manually assign an id by calling a function that will loop through all playercontrollers and assign a local id number to them.


	id = 0;
	foreach WorldInfo.AllControllers(class'Your_PlayerController', PC)
	{

		PC.SetID(id);
		id++;

	}


If still none of this works you can provide a link to your code and I’ll check it out.

Actually I did a override of the FailedToSpawn now:



void APuzzleBlankPlayerController::FailedToSpawnPawn(){
	UE_LOG(LogTemp, Warning, TEXT("State of PlayerController is %s"), *GetStateName().ToString());
}


And the State for all 4 PlayerControllers is “Spectating”. This makes sense, as it comes from the original “Puzzle” Tutorial Setup that I started out with. Now I know that the PlayerControllers only exist on the Server side ( all 4 of them ) as they are not replicated. So that’s why I cannot simply iterate through them and give them values, since those values never reach the client. Of course I can give them IDs, but I still don’t have a way of knowing which Client clicked a card, as their PlayerController will never get an id.

Going through PlayerState is what everyone keeps recommending, since those are replicated, but all of my 3 clients have the exact same PlayerState->PlayerId. The server always has 0, I can log all players’ ids and get something like (0,201,202,203) but then if I log on the clients all I get is 203 for all three of them.

I want to thank you for your help, I don’t want to be any work for you :slight_smile: Here’s a link to my source code.

Ok, so my time is running out, I need to get this done, so this is my hack (i know this is terrible):

On BeginPlay in my Card, I get a reference to the Player Controller, and call a server RPC on that PlayerController. That RPC gets a global field integer, sets my card’s “networkID” int to this value, and increments the global field. This way, all cards are numbered in ascending order, from 0 - 40 for player 1, 41-80 player2, etc… However, for some reason these 2 phenomenons appear:

  1. This doesn’t work for the last client#3, which has its networkID at -1 ( as constructed ).
  2. Server, Client#1 and Client#2 randomly switch places everytime I start the game, meaning id 0 - 40,41-80,81-120 are randomly distributed to those 3.

The dirrty fix:
I divide the networkId by 40, giving me the IDs of -1, 0, 1 , 2 on Client#3 and server,client#1,client#2 ( the last 3 in randomized order ), add +1 to it, and
then not care about which client is which player for the rest of the game.

I’m apologized to myself in a comment somewhere.

I’m checking out your code, I’ve gotten several hard crashes today from Unreal so I think my fan is dying. It was my brothers birthday yesterday so we’re celebrating tonight. If I don’t finish right now I’ll finish soon after.

Functionality

File: Download

My graphics card is dying and having constant crashes sorry I don’t have the patience to write it in c++, although the code is simple to migrate over. Currently each player id is being set based at the time of login starting at 0 from PostLogin. Game becomes active when 4 players connect, this game rule can be defined at the scene before connecting to server map. An improvement to the code would be to wait before the client interface has been created before trying to access it… sorry I would have done this but since today Unreal can’t be open longer than 15 minutes before it crashes. Don’t overwrite any of your other classes, the code is based on an empty project with starter content.

Here is the blueprint diagram… functions are nearly identical if you convert to c++. It’s good you found a solution and you really don’t have to worry about going any further. When cards are dealt, you can assign the card values to each of the players in the same fashion that PlayerId or isPlayerTurn is being manually assigned in the sample. This could be as simple as setting an array of integers on the playercontroller for the cards that the player holds. When a player decides to place a card they can notify the server of the card value being placed and remove/deactivate that card from the local array on the player controller.

Wow thank you so much for your effort. I have to say some things do look a lot clearer in blueprint than they do in code. I’m getting around on my hack, but this has been really enlighting! I did really miss a solid tutorial on how clients / server communication works other than “replicated just magically does things”. Thanks again :wink:

For what it’s worth, this is how I’ve done it:

In my custom GameMode:

GameModeCombat.h



public:

	int32 getUniquePlayerID();

private:
	int32 nextUniquePlayerID;


GameModeCombat.cpp




AGameModeCombat::AGameModeCombat(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{

	nextUniquePlayerID = 0;
}

int AGameModeCombat::getUniquePlayerID(){ return nextUniquePlayerID++; }


ACustomPlayerController.h



        UPROPERTY(ReplicatedUsing = OnRep_CustomPlayerIDSet)
	int32	CustomPlayerID;

	UFUNCTION()
	virtual void OnRep_CustomPlayerIDSet();

ACustomPlayerController.cpp



/** Event when play begins for this actor. */
void ACustomPlayerController::BeginPlay()
{
	Super::BeginPlay();
	if (Role == ROLE_Authority)	// then running on the server
	{
		AGameModeCombat* gm = (AGameModeCombat*)GetWorld()->GetAuthGameMode();
		CustomPlayerID = gm->getUniquePlayerID();	
	}
}

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

	// Replicate to everyone
	DOREPLIFETIME(ACustomPlayerController, CustomPlayerID);
	

}

void ACustomPlayerController::OnRep_CustomPlayerIDSet()
{
	GEngine->AddOnScreenDebugMessage(-1,100.0f, FColor::Green, FString("CustomPlayerID UPDATED TO = ") + FString::FromInt(CustomPlayerID) + FString("ROLE = ") + FString::FromInt(Role));

}


So basically when the playercontroller is created on the server (Role == ROLE_Authority), the server calls a function to get a unique ID for that controller. That unique id is then replicated to the other version of that playercontroller on the clients, and they are notified. Note if you have 2 players connected via a dedicated server, there are 4 player controllers in the world. 2 on the server, and a replicated version of those on each client.