Is there no way to persist Player information across servertravel?

Running a bit on wits-end here, so apologies if the tone of this post is a bit harsh. Onto the problem…

We have a multiplayer game where N number of clients (players) connect into a "lobby, " configure their weapons etc, and the server then proceeds “in-game” when ready via a button click (which does a servertravel command).

We can see that the clients and the server successfully move to the new map. However, we are unable to figure out a “good” way to persist Unique player information from the lobby to in-game. Let me explain the problem with more detail:

Assuming both the lobby map L and the in-game map IG use the SAME PlayerController type:

  1. lobby step
    Player 1 (host) chooses Weapon W1
    Player 2 (client) chooses Weapon W2

we store this “somewhere”, I will get to the “somewheres” we have tried and what has happened.

  1. move from L to IG

when ready, Player 1 does a servertravel to IG

  1. load in-game assets and settings

upon map transition, the game mode / player controller / whatever should Spawn Actor / create the actual in-game assets and Weapon settings (player 1 → W1, player 2 → W2) for the associated player.

Sounds pretty basic. Here’s the problem: There does not seem to be any way to persist data-ownership information in any meaningful way across server level loads.

Here’s what we’ve tried and what has happened:

  1. store the data in the server’s GameInstanceBP since this should persist, and use a unique ID to identify the player controller in the in-game map and his associated weapon information

This doesn’t work because even though we are using the same PlayerController TYPE, the INSTANCES in L and IG are different. Therefore, any ID we set from the server in the lobby gets wiped out and reset in the client after servertravel. In other words, we loose all data-connectivity information.

  1. store the data directly as replicated into the client PlayerController
    We were under the understanding (based on what we read on the web) that playerController persists accross levels. This turned out to be wrong. As seen in (1), the instances get wiped out and re-initialized, so we loose all the data.

What are we missing for this BASIC, BASIC multiplayer functionality to work? We just need some good way to either ensure that DESPITE level changes, there is a unique ID per client that we can depend on to re-associate data, OR persist the actual data across level changes. Is there a flag we are missing?

TL;DR
There doesn’t seem to be any good way to persist player information across multiplayer servertravel. This is a HUGE problem for multiplayer games.

I’m a little shocked at how poor documented / supported this basic thing for multiplayer games seems to be in UE4. I can’t find any documentation about say, being able to get my own IP (which we could use as a unique ID), or if there is anyone has any info they can provide, good documentation or examples would be really really appreciated.

To work around this problem, here’s what we are doing now:

  1. Upon connection to the server, the server generates a unique ID (UID) and fires it to the client via RPC. it also stores the UID locally in the server-side GameInstance

  2. The client takes the UID and stores it locally in its GameInstance. Since GameInstances are not replicated, each client has a UID which is generated by the server it connected to, lasting the life time of the GameInstance (or until the server sends it a new one)

  3. When servertravel occurs, the new client PlayerController checks for its UID from the local GameInstance, and uses that with its back and forth with the server.

This works, but it does feel a bit convoluted and it certainly feels like there should be a better way to do this. Ideally I’d like to be able to queue-up the PlayerControllers that need to be “prepared” for game play in the lobby level, and simply start preparing them in order when the game starts…but at the moment that isn’t possible…and it feels like a massive hole in UE4’s basic multiplayer capabilities.

There is always the PlayerState->PlayerId variable. But I get where you’re coming from, we’re struggling with the same basic functionality.

I am dealing with the exact same issue. PlayerId would work as the identifier, but when you load in to a new level all the Player States get a new PlayerId so that one doesn’t work either. I’m gonna try the suggestion above, even though it does seem extremely hackish to get a simple functionality for multiplayer games.

We ended up having a separate PlayerData struct which gets stored in the client, and then also a master list in the server. Upon new map load, the postLogin / preLogin steps in the GameMode would verify the data to be correct (via RPCs, etc)

I’ve the same problem explained here Transfer PlayerState data during seamless travel - Multiplayer & Networking - Epic Developer Community Forums

Accordning to other posts it should work to store Everything in the playerstate and then implement the method CopyProperties:

void AMyPlayerState::CopyProperties(class APlayerState* PlayerState)
{

	APlayerState::CopyProperties(PlayerState);

	AMyPlayerState* MyPlayerState = Cast<AMyPlayerState>(PlayerState);
	
	if (MyPlayerState)
	{
		MyPlayerState->TestName = TestName;		
	}
	
}

Have any1 got that to work?

The documentation is poor and the Epic network ppl is very absent on theese matters on answerhub. I could do a similair solution as u guys and store it in the GameInstance in a TMAP with a unique client id and FDataStruct. (as u said it doesn’t feel good)

But is the CopyProperties solution supose to work or not?

Ok, after a second look at ShooterGame i solved the problem, apperently you must set the variable definition as below (apply to seamless travel):

UPROPERTY(BlueprintReadOnly, Replicated, Category = “Preplan”)
TArray<UMinionDataWrapper*> MinionDataArray;

void AShooterPlayerState::CopyProperties(class APlayerState* PlayerState)
{
APlayerState::CopyProperties(PlayerState);

if (PlayerState != nullptr)
{
	AShooterPlayerState* ShooterPlayerState = Cast<AShooterPlayerState>(PlayerState);		
	if (ShooterPlayerState)
	{			
		
		ShooterPlayerState->MinionDataArray = MinionDataArray;			
		
	}
	
}

}

Think the BlueprintReadOnly is the key here, hope some1else can get helped by this finding,

cheers!