ReceivedSpectatorClass has wrong class parameter and client stuck as spectator

The function parameter currently is:
void APlayerController::ReceivedSpectatorClass(TSubclassOf SpectatorClass)

but I am pretty sure it should be:
void APlayerController::ReceivedSpectatorClass(TSubclassOf SpectatorClass)

The function parameters are fixed in 4.9 preview 2.

Also, the spectator replication and instancing can come out of order, causing the client pawn to not get properly initialized and IIRC being stuck as spectator pawn. We solved this by waiting with calling SafeServerUpdateSpectatorState() until replication for other things have been made (like PlayerReplication).

Hi Markus Arvidsson,

I was a bit confused by your post, but I think I just realized what happened. It looks like AnswerHub cut out a little bit of your post because it was not marked as being code. Would you be able to edit your post to add back in the template class you were referring to, and put the function names and parameters inside code formatting? For a single line of code, you would use ` at the beginning and end of the line.

Hey yes, the current code is:

virtual void ReceivedSpectatorClass(TSubclassOf<class AGameMode> SpectatorClass);

But it should most likely be:

virtual void ReceivedSpectatorClass(TSubclassOf<class ASpectatorPawn> SpectatorClass);

The serious problem is/was that the client can end up in a permanent spectator state if the replication happens in a certain order. I can’t really give instructions exactly how to replicate it, but in our case it was solved by waiting to call SafeServerUpdateSpectatorState until replication was setup. The “arbitrary” order of replication is as you know something that one has to be careful about, and this spectator problem is one way the problem surfaced.

Hi Markus,

I have filed a report about the parameter for the ReceivedSpectatorClass function (UE-18286) in order to have that investigated further.

I am still trying to reproduce the issue that you described where a client gets stuck in a spectator state. Were you seeing this in PIE, or in a packaged game?

This was in standalone game but not cooked. So UE4Editor.exe with the -game commandline for client and -server for the server.

I got an update on this now for 4.9 preview 2 and a more detailed description of the problem.

First of all, the parameter problem for ReceivedSpectatorClass is now fixed in 4.9 preview 2.

The stuck as spectator still happens. In my case I detected it on a listen server but I don’t see why it would not happen on a dedicated server, too.

Client connects to a server.
APlayerController::ReceivedPlayer on the client is called and that in turns calls APlayerController::BeginSpectatingState. APlayerController::BeginSpectatingState spawns a pawn of class SpectatorPawn.
If ReceivedSpectatorClass then is called/received in a particular order in relation to other reliable functions or replicated variables it will call BeginSpectatingState and Unpossess the correct Pawn, causing the client to be stuck with a SpectatorPawn. This might be because the AcknowledgedPawn on the server is correct so the server does not resend SafeRetryClientRestart or it might be that the state is set to NAME_Playing on the client even if the Pawn is the SpectatorPawn.

I got a 100% reproductive case when running both the listen server and the client without debugger. But with debugger and breakpoints it normally works, indicating this is a timing or replication order issue.

Proposed solution:
Do not spawn any SpectatorPawn before SpectatorClass has been received or just remove the network functionality for sending SpectatorClass and have it predetermined/hardcoded by the game mode.

We have solved this locally by overriding ReceivedSpectatorClass and BeginSpectatingState so both functions are empty.

Hi Markus,

Do you happen to have a small test project that demonstrates this behavior? There is likely some small detail that I am missing since I have not yet found myself getting stuck in a spectator state.

We don’t have a small project I am afraid but I can try to get it down in size.

We made some other small changes as well. I don’t know if these changes are necessary but they felt right to us to make sure the pawn is not spawned before PlayerState has been replicated. ClientRetryClientRestart I don’t think can happen with NewPawn == NULL but since there is a check for it I figured it is possible so I added a check for PlayerState too.

ClientRestart_Implementation will now return and the server will resend it until the PlayerState has been replicated to the Pawn.

In APlayerController::ClientRetryClientRestart_Implementation

Change

if (NewPawn == NULL) to

if (NewPawn == NULL || PlayerState == NULL || NewPawn->PlayerState == NULL)

And in APlayerController::ClientRestart_Implementation

Change

if ( GetPawn() == NULL ) to

if ( GetPawn() == NULL || PlayerState == NULL || GetPawn()->PlayerState == NULL )

Hi Markus,

I wanted to provide a quick update for you. I have still not been able to repro the issue where a client gets stuck in spectator state when joining a server. However, I did check in with our networking team and they said that this process is still somewhat fragile. I have entered another ticket for this issue (UE-20856) to allow us to better track and investigate it.

If you do have the opportunity to put together a sample project that demonstrates this behavior, it would be a tremendous help.

Hi, yeah I will try get you that as soon as I can. I had a 100% repro but only when running through the debugger or if it was the other way around. So I will try to recreate that.