Round Restart Help

Hello,

I’m trying to implement functionality to restart the round. This includes restoring all of the objects in the current level to their original state. I would like to accomplish this without having to call ServerTravel, or anything that requires the engine to reload the entire map. That, in my opinion, is a bad solution.

I’m using version 4.7.6.

Here’s what I have so far:

-In my GameMode header, I keep an array of AActors:



UPROPERTY()
TArray<AActor *> currentRoundActors;


These are the actors that are present at the beginning of the round.

-When the BeginPlay is called, I populate the array:



void ANimModGameMode::BeginPlay()
{
	Super::BeginPlay();

	for (FActorIterator It(GetWorld()); It; ++It)
	{
		AActor* A = *It;
		if (A && A != this && !A->IsA<AController>() && ShouldReset(A))
		{
			currentRoundActors.Add(A);
		}
	}
}


-I have a trigger volume that, when triggered by a certain player, will:
-freeze all of the players in the game.
-start a timer

-When the timer expires, it will call “RestartRound” on the current GameMode:



#define ISSERVER (GEngine->GetNetMode(GetWorld()) < NM_Client)




void ANimModGameMode::RestartRound()
{
	if (!ISSERVER)
		return;
	//Players should already be frozen
	//FreezePlayers();

	// Reset all actors (except controllers, the GameMode, and any other actors specified by ShouldReset())
	//TODO: This doesn't handle actors that are in currentRoundActors that have been destroyed before this point.
	//We need to find a way to respawn those, as well.
	TArray<AActor *> destroyActors;
	TArray<AActor *> respawnActors;
	for (FActorIterator It(GetWorld()); It; ++It)
	{
		AActor* A = *It;
		if (A && A != this && !A->IsA<AController>() && ShouldReset(A))
		{
			if (currentRoundActors.Contains(A))
			{
				respawnActors.Add(A);
				currentRoundActors.Remove(A);
			}
			else
				destroyActors.Add(A);
		}
	}

	//Whatever is left is something that is pending being deleted, or is deleted.
	//Respawn what we can.
	for (AActor *A : currentRoundActors)
	{
		if (A == nullptr)
		{
			destroyActors.Add(A);
			continue;
		}

		if (ShouldReset(A))
		{
			respawnActors.Add(A);
		}
	}

	//Respawn the ones that we do want.
	while (respawnActors.Num() > 0)
	{
		AActor *A = respawnActors[0];
		if (A == nullptr)
                {
                        respawnActors.RemoveAt(0);
			continue;
                }

		//Add a new actor as a copy of the 'reloaded' one, since the reloaded one is disappearing.
		FActorSpawnParameters spawnParams;
		spawnParams.Template = A;
		/*spawnParams.bNoFail = true;
		spawnParams.bNoCollisionFail = true;
		spawnParams.Name = NAME_None;*/
		//A->SetReplicates(true);
		FVector orig = A->GetActorLocation();
		FRotator rot = A->GetActorRotation();
		AActor *newActor = GetWorld()->SpawnActor
		(
			A->GetClass(),
			&orig,
			&rot,
			spawnParams
		);
		if (newActor == nullptr)
			continue;

		//newActor->SetOwner(A->GetOwner());

		//Add the new one
		currentRoundActors.Add(newActor);
		if (currentRoundActors.Contains(A))
			currentRoundActors.Remove(A);
		//Remove the old one
		destroyActors.Add(A);

		respawnActors.RemoveAt(0);
	}

	//Destroy all of the actors we don't want.
	while (destroyActors.Num() > 0)
	{
		AActor *actor = destroyActors[0];
		destroyActors.RemoveAt(0);
		//This can (theoretically happen) if we added something that has already been deleted.
		if (actor == nullptr)
			continue;

		actor->Destroy(true, true);
		actor = nullptr;
	}

	//GetWorld()->ForceGarbageCollection(true); //...Maybe?

	// reset the GameMode...Which does ****-all.
	//Reset();

	// Notify the level script that the level has been reset
	ALevelScriptActor* LevelScript = GetWorld()->GetLevelScriptActor();
	if (LevelScript)
	{
		LevelScript->LevelReset();
	}

	//'Respawn' the players
	for (FConstControllerIterator Iterator = GetWorld()->GetControllerIterator(); Iterator; ++Iterator)
	{
		AController* Controller = *Iterator;
		ANimModPlayerController* PlayerController = Cast<ANimModPlayerController>(Controller);
		if (PlayerController)
		{
			RestartPlayer(PlayerController);
			//PlayerController->ClientRestartRound();
			//PlayerController->ServerRestartPlayer();
		}
		else
			Controller->Reset();
	}

	//Unfreeze the players.
	UnfreezePlayers();
}


Just in case someone wants to see what my “ShouldReset” looks like:



bool ANimModGameMode::ShouldReset(AActor* ActorToReset)
{
	UClass *actorClass = ActorToReset->GetActorClass();
	AGameNetworkManager *networkManager = Cast<AGameNetworkManager>(ActorToReset);
	//AParticleEventManager *particleEventManager = Cast<AParticleEventManager>(ActorToReset);
	if
	(
		actorClass->IsChildOf(UWorld::StaticClass()) ||
		actorClass->IsChildOf(APlayerController::StaticClass()) ||
		actorClass->IsChildOf(ACharacter::StaticClass()) ||
		actorClass->IsChildOf(AGameMode::StaticClass()) ||
		actorClass->IsChildOf(APlayerCameraManager::StaticClass()) ||
		actorClass->IsChildOf(APlayerStart::StaticClass()) ||
		actorClass->IsChildOf(AHUD::StaticClass()) ||
		actorClass->IsChildOf(UGameViewportClient::StaticClass()) ||
		actorClass->IsChildOf(AGameSession::StaticClass()) ||
		/*actorClass->IsChildOf(AGameNetworkManager::StaticClass()) ||*/
		actorClass->IsChildOf(APlayerState::StaticClass()) ||
		actorClass->IsChildOf(AWorldSettings::StaticClass()) ||
		actorClass->IsChildOf(AGameState::StaticClass()) ||
		actorClass->IsChildOf(AVIPTrigger::StaticClass()) ||
		actorClass->IsChildOf(ULevel::StaticClass()) ||
		actorClass->IsChildOf(ALight::StaticClass()) ||
		actorClass->IsChildOf(ALevelScriptActor::StaticClass()) ||
		actorClass->IsChildOf(ALightmassImportanceVolume::StaticClass()) ||
		actorClass->IsChildOf(APostProcessVolume::StaticClass()) ||
		actorClass->IsChildOf(ANavigationData::StaticClass()) ||
		actorClass->IsChildOf(AAtmosphericFog::StaticClass()) ||
		actorClass->IsChildOf(ASpectatorPawn::StaticClass()) ||
/*#ifdef DEBUG*/
		actorClass->IsChildOf(AGameplayDebuggingReplicator::StaticClass()) ||
/*#endif*/
		/*particleEventManager != nullptr ||*/
		(AActor *)GetWorld()->MyParticleEventManager == ActorToReset ||
		networkManager != nullptr
	)
		return false;

	if (currentRoundActors.Contains(ActorToReset) && ActorToReset->IsPendingKill())
		return true;

	if (ActorToReset->IsRootComponentStatic())
		return false; //I...think...
	
	return true;
}


Now, this code works(ish) when I’m testing in with 1 player in a non-dedicated server. If I test with two players in a dedicated server, the objects stay the way they are. Sometimes I can get them to disappear from the clients, altogether, but it’s clear that they respawned on the server, because I “collide with” empty air where the objects are supposed to be.

I also tried using “FReloadObjectArc”. Instead of storing a pointer to the Actors at when BeginPlay was called, I was serializing them in to their own FReloadObjectArc, then trying to overwrite them in “RestartRound”. That didn’t do anything.

I’m close (I think). I just need to know why the objects are respawning on the clients. And why static mesh actors aren’t respawning at all.

Thoughts?

When testing with a dedicated server and two or more players, I see these errors in the Output Log:

Hey, figured I’d add the reply to your PM here instead as it might be useful to others:

“Hm interesting problem…I think you’d only care to reset specific dynamic objects. You could make all of those into blueprints with an interface that has a Reset() function to implement for every blueprint type that does something dynamic during gameplay (eg. physically simulate, or get damaged and break, turn light on/off) Now on a new round you simply iterate ALL actors, do a “has interface” check so you can see on which actors to call the Reset. A quick glance over your list of actors to reset reveals that perhaps you’re trying to start with every feature at once? Do you have a specific reason to ever reset APlayerStart ? or a generic ALight. I think you’re better off thinking of practical applications and turning those into reset(able) blueprints.”

Cheers,

Tom

Thanks for the reply, Tom. My problem with the “Reset Interface” approach is two-fold:

  1. It doesn’t solve my problem of reloading an actor from the saved map file. I want the actor to go back to its default state. And I have no idea how to accomplish this. As you can see, I’ve tried a few different approaches. I’ve tried forcing a copy of the AActor to spawn and deleting the original, overwriting the AActor with the FReloadObjectArc, using level streaming to load a copy of the level and setting that to the UWorld’s CurrentLevel. I’ve hit a dead-end here.

  2. I don’t want my artist/mapper (or anyone, really) to have to do this for every object that needs to be reset. If I can take that out of their hands, I’d like to.

I think you may have misread the “ShouldReset” method. If the actor is a APlayerStart or a generic ALight, it will return false; Meaning that that particular actor should **not ** be reset.