Seamless Travel to Same Level Problems

Hi,

Using 4.13.2 with the ShooterGame demo for testing I’ve noticed that if I use seamless ServerTravel to reload the same level twice then things go bad. AGameMode::PostSeamlessTravel seems to call HandleSeamlessTravelPlayer immediately for remote players who have not started to travel, since the ClientWorldPackageName is the same both before and after the transition.

This means that APlayerController’s LastCompletedSeamlessTravelCount and SeamlessTravelCount are equal already for remote clients when the server’s PostLoadMap event triggers.

Normally for ShooterGame this doesn’t seem to be an issue but I am writing a plugin that spawns an entity from the PostMapLoad event and doing that in this situation completely breaks the remote clients’ replication, but works perfectly otherwise.

First seamless travel, looks good and works fine:

[OnPostLoadMap] Client: 000002AE98DEAB00 LCSTC: 0 STC: 1

Seamless travel again to the same level, the remote client is stuck and replication doesn’t work:

[OnPostLoadMap] Client: 000002AE98DEAB00 LCSTC: 2 STC: 2
 LogPlayerController:Verbose: APlayerController::ServerNotifyLoadedWorld_Implementation: Client loaded /Temp/Untitled_3
 LogNet: Server connection received: ActorChannelFailure
 LogNet: Server connection received: ActorChannelFailure
 LogPlayerController:Verbose: APlayerController::ServerNotifyLoadedWorld_Implementation: Client loaded /Game/Maps/Highrise

Relevant engine code:

void AGameMode::PostSeamlessTravel()
{
	if ( GameSession != NULL )
	{
		GameSession->PostSeamlessTravel();
	}

	// We have to make a copy of the controller list, since the code after this will destroy
	// and create new controllers in the world's list
	TArray<TAutoWeakObjectPtr<class AController> >	OldControllerList;
	for (auto It = GetWorld()->GetControllerIterator(); It; ++It)
	{
		OldControllerList.Add(*It);
	}

	// handle players that are already loaded
	for( FConstControllerIterator Iterator = OldControllerList.CreateConstIterator(); Iterator; ++Iterator )
	{
		AController* Controller = *Iterator;
		if (Controller->PlayerState)
		{
			APlayerController* PlayerController = Cast<APlayerController>(Controller);
			if (PlayerController == NULL)
			{
				HandleSeamlessTravelPlayer(Controller);
			}
			else
			{
				if (Controller->PlayerState->bOnlySpectator)
				{
					// The spectator count must be incremented here, instead of in HandleSeamlessTravelPlayer,
					// as otherwise spectators can 'hide' from player counters, by making HasClientLoadedCurrentWorld return false
					NumSpectators++;
				}
				else
				{
					NumTravellingPlayers++;
				}
				if (PlayerController->HasClientLoadedCurrentWorld())
				{
					HandleSeamlessTravelPlayer(Controller);
				}
			}
		}
	}
}

bool APlayerController::HasClientLoadedCurrentWorld()
{
	UNetConnection* Connection = Cast<UNetConnection>(Player);
	if (Connection == NULL && UNetConnection::GNetConnectionBeingCleanedUp != NULL && UNetConnection::GNetConnectionBeingCleanedUp->PlayerController == this)
	{
		Connection = UNetConnection::GNetConnectionBeingCleanedUp;
	}
	if (Connection != NULL)
	{
		// NOTE: To prevent exploits, child connections must not use the parent connections ClientWorldPackageName value at all.

		return (Connection->ClientWorldPackageName == GetWorld()->GetOutermost()->GetFName());
	}
	else
	{
		// if we have no client connection, we're local, so we always have the current world
		return true;
	}
}

Thanks!

Why do you want to load the same level mutiple times ? I think Unreal provide you other methods to re-initialize a level. Maybe check the “OnReset” event, something like that :slight_smile:

Unfortunately as a plugin author I don’t have control over game code, I need to support every reasonable use case. I think traveling to the same level is a common and reasonable way to entirely reset, especially if the next level is chosen by player vote or something like that. If seamless traveling to the same level is not supported then I think it should be documented and the travel function should reject attempts to do it rather than calling functions out of order and subtly breaking things :slight_smile:

Hey rdeist,

I’ve tested this issue on my end using Shooter Game as well as a clean test project, and I’m not seeing the same results.

What exactly is broken when you perform the second travel?

Do you have any steps we can follow to reproduce the issue on our end, or a simplified test project that showcases the issue?

Hello,

I am marking this topic as resolved for tracking purposes, as we have not heard from you in a few days. If this issue persists, feel free to respond to this thread. For any new issues, please create a new Answerhub topic.

Have a great day

OP clearly states

“Normally for ShooterGame this doesn’t seem to be an issue but I am writing a plugin that spawns an entity from the
PostMapLoad event and doing that in this situation completely breaks the remote clients’ replication, but works perfectly
otherwise.”

I’m noticing Epic devs are really trigger happy to close issues without even reading. OP did all the work for you, right down to the the problematic line of code. It’s easy to see why it breaks it.

@rdeist Did you ever find a workaround for this issue? I find myself in the same situation.

I am in the same situation too, in 4.17.2.I want to change the gamemode on the same map.I got “ActorChannelFailure” randomly in every seamless travel.

I run clients and server on the same machine.Check the logs below, I find that server called HandleSeamlessTravelPlayer in AGameMode::PostSeamlessTravel before remote clients start seamless travel.The client finally crashed in “check( !ObjectNetGUID.IsDefault() && ObjectNetGUID.IsValid() )” of FObjectReplicator::StartReplicating
ClientLog:
link text

ServerLog:
link text

Finally, I find the bug.See the debug picture as below

My project has no TransitionMap, so there is no chance to check the condition “CurrentMapName == DestinationMapName” to do “Connection->ClientWorldPackageName = NAME_None;”
So there are two solutions:
1. Add a Transition map to the project
2. modify the “FSeamlessTravelHandler::StartTravel” in World.cpp

@Sean L :diamonds::diamonds: Is this a engine bug?

You can try my solutions

@superman_tb Thank you! I’ve moved on already but I’ll be sure to give this a try in the future!

Seamless traveling to the same level is supported now, if you make sure that transition map is not empty.