Switching player controller in seamless travel does not update world list of player controllers

So, after breakpointing through the code it becomes apparent that the bug to do with switching player controllers exists in the UWorld object not the actual switching of them.

Breakpointing through the engine AGameMode::HandleSeamlessTravelPlayer(AController*& C) successfully switches old controllers to the new ones for travelling to a different game mode. However, if I then use

UWorld* ThisWorld = GEngine->GetWorldFromContextObject(WorldContextObject);

	if (!ThisWorld)
	{
		return nullptr;
	}

	auto controller = Cast<APlayerCraftController>(ThisWorld->GetFirstPlayerController());
	return controller->IsLocalPlayerController() ? controller : nullptr;

ThisWorld->GetFirstPlayerController() always returns the old player controller, not the new one, even though during the seamlessTravel function which happens before this point they get switched out successfully. The strange thing however, if I breakpoint at line 824 GameMode.cpp

	UE_LOG(LogGameMode, Log, TEXT(">> GameMode::HandleSeamlessTravelPlayer: %s "), *C->GetName());

it works perfectly fine and updates the UWorld list. without the breakpoint it fails every time but the log suggests that its running the AGameMode::HandleSeamlessTravelPlayer function before we’re trying to use our snippet that fails.

Hey GotSomePills-

Can you explain exactly what the bug is that you’re experiencing? Does the controller become unresponsive after travel or does it assume the wrong controller connection (player 2 controller controls player 3 instead)? Are you able to reproduce this in a new project? If so, can you list the steps that reproduce the issue so that I can investigate the problem locally? Let me know if there is any additional information you can provide.

Cheers

I don’t have the time this weekend to try it with a fresh project i’m afraid but this is the set up:

From the main menu, the player travels absolute (non seamless) to the lobby level and creates a session. Once all players are ready they host may click start game. Then all connected players travel to the designated map/game mode.

in the eventTick of the game mode we have a check (nested inside a state check) for if(numTravellingPlayers==0){
doStuff();
}

that do stuff links to the function quoted above. very rarely it won’t trigger a breakpoint however most of the time using ThisWorld->GetFirstPlayerController() returns the player controller from the previous game mode even though the output log shows that the handleSeamlessTravel function has actually switched the controllers out. so its either a case of we’re running the call too early (which shouldn’t really be the case if there are no travelling players left in the new gamemodes tick function) OR the code in GameMode.cpp may have something that fails sometimes. The strange thing is that between the times it works and times it doesn’t, nothing changes.

So essentially:

LobbyGameMode:
    Player1 - LobbyController_0
    Player2 - LobbyController_1

- travel to new map

use UWorld::GetFirstPlayerController();
RaceGameMode:
    Player1 - LobbyController_0 - should be RaceController
    Player2 - LobbyController_0

although if i check the actual player state through breakpointing at this point i can see that it has the correct controller. it appears that UWorld::ControllerList (not sure on the correct variable name, can’t check it right now) doesn’t appear to get updated at the same time the controllers get switched out during AGameMode::HandleSeamlessTravelPlayer

the code works correctly every single time I breakpoint through the code to check the variable values through (debugGame). Which is the strangest part about it all.

OKAY!

By using this:

if(numTravellingPlayers==0){                        
             bool hello = true;
    			for (auto iter = GetWorld()->GetPlayerControllerIterator(); iter; ++iter)
    			{
    				auto player = Cast<APlayerCraftController>(*iter);
    				if (player == nullptr)
    				{
    					hello = false;
    				}
    			}
    			if(hello)
    			AllPlayersConnected();
}

in my GameMode tick function where i’m checking if everybody is ready (nested inside if(numTravellingPlayers==0) works fine every time.

So i believe the bug is that game mode tick functions run during a time where another thread (i’m guessing) is attempting to switch out the controllers and somewhere else updates the list inside of UWorld::PlayerControllers

my suggestion would be to have that list updated when the controllers are switched.

Hello,

I’m having the same issue in a Blueprint project. I use the Seamless travel to keep my players connected when transitioning from the a Lobby map to the game, and once inside the new map, my new PlayerController is creating a widget on BeginPlay. Then, that widget is doing a Get Player Controller>Cast to PlayerController on Construct, and it fails because the GePlayerController is actually returning the Lobby PlayerController, not the new one.

My workaround in the scenario is to retry every 0.2s. It eventually success, but that’s a dirty solution to the underlying problem. And it’s not an ideal one as I have multiple others PlayerController query when the game starts.

The easiest way to repro it is to simply put an EventTick in the new PlayerController, and plug it to a Print GetPlayerController. You should see the Lobby PlayerController several times before switching to the new one.

Ps: it seems to not happen when there is only one player, so make sure to have at least one client.