Skip to content
Snippets Groups Projects
Select Git revision
  • 0453b94c5b05bf7726fc6ac2aef2d44a904fc61e
  • master default protected
2 results

string_tools.test.cpp

Blame
  • SFGameInstance.cpp 29.35 KiB
    // Fill out your copyright notice in the Description page of Project Settings.
    
    
    #include "SFGameInstance.h"
    
    #include "Engine/World.h"
    #include "IUniversalLogging.h"
    #include "HUD/SFFadeHandler.h"
    #include "HUD/SFMasterHUD.h"
    
    #include "Help/SFUtils.h"
    #include "Logging/SFLoggingBPLibrary.h"
    #include "Logging/SFLoggingUtils.h"
    
    USFGameInstance* USFGameInstance::Instance = nullptr;
    
    // ****************************************************************** // 
    // ******* Initialization ******************************************* //
    // ****************************************************************** //
    
    void USFGameInstance::Init()
    {
    	Super::Init();
    
    	FSFLoggingUtils::Log("USFGameInstance::Init");
    
    	IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
    	if (ClusterManager && !ClusterEventListenerDelegate.IsBound())
    	{
    		ClusterEventListenerDelegate = FOnClusterEventJsonListener::CreateUObject(
    			this, &USFGameInstance::HandleClusterEvent);
    		ClusterManager->AddClusterEventJsonListener(ClusterEventListenerDelegate);
    	}
    
    
    	Instance = this;
    	LogObject = NewObject<USFLogObject>();
    
    	if (ConditionIndexToStartAtInit != -1)
    	{
    		FSFLoggingUtils::Log(
    			"Started on a map that was part of the last study, so start the study run for debug reasons from Init()");
    		RestoreLastParticipantForDebugStart(ConditionIndexToStartAtInit);
    	}
    
    	if (StudySetup)
    	{
    		InitFadeHandler(StudySetup->FadeConfig);
    	}
    
    	// Register delegate for ticker callback
    	TickDelegateHandle = FTSTicker::GetCoreTicker().AddTicker(
    		FTickerDelegate::CreateUObject(this, &USFGameInstance::LogTick));
    	LogObject->Initialize();
    
    	bStudyStarted = false;
    	bStudyEnded = false;
    
    	FWorldDelegates::OnPostWorldCreation.AddUObject(this, &USFGameInstance::OnPostWorldCreation);
    }
    
    void USFGameInstance::Shutdown()
    {
    	IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
    	if (ClusterManager && ClusterEventListenerDelegate.IsBound())
    	{
    		ClusterManager->RemoveClusterEventJsonListener(ClusterEventListenerDelegate);
    	}
    
    
    	if (ExperimenterWindow)
    	{
    		ExperimenterWindow->DestroyWindow();
    	}
    	Instance = nullptr;
    	// Unregister ticker delegate
    	FTSTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle);
    
    	FWorldDelegates::OnPostWorldCreation.RemoveAll(this);
    	Super::Shutdown();
    }
    
    void USFGameInstance::OnWorldStart()
    {
    	UWorld* NewWorld = GetWorld();
    	//when unloading map, NewWorld==nullptr, when loading new map OldWorld==nullptr
    	if (NewWorld == nullptr)
    	{
    		return;
    	}
    
    	if (bStudyStarted)
    	{
    		//everything is already up and running, so nothing to do
    		return;
    	}
    
    	FSFLoggingUtils::Log("USFGameInstance::OnWorldStart for " + NewWorld->GetName());
    
    	// so we have loaded a new world and the study is not running, so check whether this is a map in one of the conditions
    	TArray<USFCondition*> LastConditions = USFParticipant::GetLastParticipantsConditions();
    	int FirstMapConditionIndex = -1;
    	for (int i = 0; i < LastConditions.Num(); i++)
    	{
    		if (FPaths::GetBaseFilename(LastConditions[i]->Map).Equals(NewWorld->GetName()))
    		{
    			FirstMapConditionIndex = i;
    			break;
    		}
    	}
    
    	if (FirstMapConditionIndex != -1)
    	{
    		if (!IsInitialized())
    		{
    			//this is potentially called before init was called
    			ConditionIndexToStartAtInit = FirstMapConditionIndex;
    		}
    		else
    		{
    			FSFLoggingUtils::Log(
    				"[USFGameInstance::OnWorldChanged] Started on a map that was part of the last study, so start the study run for debug reasons.");
    			RestoreLastParticipantForDebugStart(FirstMapConditionIndex);
    			LogToHUD("Start map " + NewWorld->GetName() + " for debugging!");
    		}
    		return;
    	}
    
    	// otherwise check whether a setup is present
    	TArray<AActor*> StudySetups;
    	UGameplayStatics::GetAllActorsOfClass(NewWorld, ASFStudySetup::StaticClass(), StudySetups);
    
    	FSFLoggingUtils::Log("Found " + FString::FromInt(StudySetups.Num()) + " ASFStudySetup actors on this map.");
    
    	if (StudySetups.Num() == 1)
    	{
    		PrepareWithStudySetup(Cast<ASFStudySetup>(StudySetups[0]));
    		return;
    	}
    
    	// else we started on an unrelated map and just disable the HUD, so people can test their stuff
    	bStartedOnUnrelatedMap = true;
    	StudySetup = NewObject<ASFStudySetup>(this, TEXT("DefaultStudySetupForUnrelatedMap"));
    	InitFadeHandler(FFadeConfig());
    	FadeHandler->FadeIn();
    	FSFLoggingUtils::Log(
    		"[USFGameInstance::OnWorldChanged] world started that neither contains exactly one SFStudySetup actor, nor is a level that is part of one of the conditions from the last study run!",
    		false);
    }
    
    void USFGameInstance::OnPostWorldCreation(UWorld* World)
    {
    	//this is actually called before BeginPlay but after EndPlay of the world before, so actually switch the current condition just here
    	if(Participant)
    		Participant->ApplyNextCondition();
    }
    
    void USFGameInstance::RestoreLastParticipantForDebugStart(int InStartConditionIndex)
    {
    	bInDebugMode = true;
    	const int ParticipantSequenceNumber = USFParticipant::GetLastParticipantSequenceNumber();
    	const FString ParticipantID = USFParticipant::GetLastParticipantID();
    	Participant = NewObject<USFParticipant>(this,
    	                                        FName(TEXT("Participant_") + ParticipantID));
    	StudySetup = USFParticipant::GetLastParticipantSetup();
    	StartConditionIndex = InStartConditionIndex;
    	Participant->Initialize(ParticipantSequenceNumber, ParticipantID);
    	Participant->Conditions = Participant->GetLastParticipantsConditions();
    	Participant->LoadLastParticipantsIndependentVariables();
    
    	Participant->RecoverStudyResultsOfFinishedConditions();
    	USFCondition* StartCondition = Participant->Conditions[StartConditionIndex];
    	//also delete any data of the condition that is now restarted (StartCondition)
    	Participant->DeleteStoredDataForConditionFromLongTable(StartCondition);
    	for (USFDependentVariable* DV : StartCondition->DependentVariables)
    	{
    		if (USFMultipleTrialDependentVariable* MTDV = Cast<USFMultipleTrialDependentVariable>(DV))
    		{
    			Participant->DeleteStoredTrialDataForCondition(StartCondition, MTDV);
    		}
    	}
    
    	InitFadeHandler(StudySetup->FadeConfig);
    	StartStudy();
    
    	//cannot directly fade in here, since Init might not be done, so we wait a second for safety
    	GetTimerManager().SetTimer(StartFadingTimerHandle, this, &USFGameInstance::StartFadingIn, 1.0f);
    }
    
    void USFGameInstance::StartFadingIn()
    {
    	FadeHandler->FadeIn();
    	GetTimerManager().ClearTimer(StartFadingTimerHandle);
    }
    
    
    USFGameInstance* USFGameInstance::Get()
    {
    	if (Instance == nullptr)
    	{
    		FSFUtils::OpenMessageBox("GameInstance is not set to USFGameInstance, Study Framework will not work", true);
    	}
    	return Instance;
    }
    
    bool USFGameInstance::IsInitialized()
    {
    	return Instance != nullptr;
    }
    
    void USFGameInstance::InitFadeHandler(FFadeConfig FadeConfig)
    {
    	if (FadeHandler == nullptr)
    	{
    		FadeHandler = NewObject<USFFadeHandler>(GetTransientPackage(), "SFFadeHandler");
    		FadeHandler->AddToRoot();
    	}
    	FadeHandler->SetFadeConfig(FadeConfig);
    
    	if (bStartedOnUnrelatedMap)
    	{
    		StudySetup->ExperimenterViewConfig.bShowHUD = false;
    	}
    	if (StudySetup->ExperimenterViewConfig.bShowExperimenterViewInSecondWindow)
    	{
    		ExperimenterWindow = NewObject<USFExperimenterWindow>(GetTransientPackage(), "ExperimenterWindow");
    		ExperimenterWindow->CreateWindow(StudySetup->ExperimenterViewConfig);
    	}
    }
    
    // When starting on the setup map, i.e. not for debugging purposes
    void USFGameInstance::PrepareWithStudySetup(ASFStudySetup* Setup)
    {
    	StudySetup = DuplicateObject(Setup, this);
    
    	int ParticipantSequenceNumber = USFParticipant::GetLastParticipantSequenceNumber();
    	FString LastParticipantID = USFParticipant::GetLastParticipantID();
    	bool bRecoverParticipantData = false;
    
    
    	if (FSFUtils::IsPrimary())
    	{
    		if (USFParticipant::GetLastParticipantFinished())
    		{
    			ParticipantSequenceNumber++;
    		}
    		else
    		{
    			const FString MessageText = FString("The last participant did not finish the study run. Would you like to:")
    				+
    				"\n[Continue Participant] Continue last participant (Participant ID: " +
    				LastParticipantID + ", SequenceNumber: " + FString::FromInt(ParticipantSequenceNumber) +
    				") where he/she left off in Condition " +
    				FString::FromInt(USFParticipant::GetLastParticipantLastConditionStarted() + 1) + "/" + FString::FromInt(
    					USFParticipant::GetLastParticipantsConditions().Num()) +
    				"\n[Next Participant] Continue with the next participant (ParticipantSequenceNumber: " +
    				FString::FromInt(ParticipantSequenceNumber + 1) +
    				")\n[Restart Study] Restart the entire study anew (ParticipantSequenceNumber: 0)";
    			const FString MessageTitle = "WARNING: Unfinished study run detected";
    			TArray<FString> Buttons = {
    				"Continue Participant",
    				"Next Participant",
    				"Restart Study"
    			};
    
    			bool bValidDecision = false;
    			while (!bValidDecision)
    			{
    				int Answer = FSFUtils::OpenCustomDialog(MessageTitle, MessageText, Buttons);
    				const FString ModeSelection = Buttons[Answer];
    
    				if (Answer < 0)
    				{
    					FSFLoggingUtils::Log(
    						"[USFGameInstance::PrepareWithStudySetup] The window for deciding on how to recover from an unfinished run was closed without giving an answer, repeat question!",
    						false);
    					continue;
    				}
    
    				switch (Answer)
    				{
    				case 2:
    					FSFLoggingUtils::Log("[USFGameInstance::PrepareWithStudySetup]: Restart entire study");
    					ParticipantSequenceNumber = 0;
    				//clear data (but only on primary to avoid data races)
    					USFParticipant::ClearLogData();
    					bValidDecision = true;
    					break;
    				case 0:
    					FSFLoggingUtils::Log("[USFGameInstance::PrepareWithStudySetup]: Continue last participant");
    					bRecoverParticipantData = true;
    					bValidDecision = true;
    					break;
    				case 1:
    					FSFLoggingUtils::Log(
    						"[USFGameInstance::PrepareWithStudySetup]: Continue with the next participant");
    					ParticipantSequenceNumber++;
    					bValidDecision = true;
    					break;
    				default:
    					FSFLoggingUtils::Log(
    						"[USFGameInstance::PrepareWithStudySetup]: Unknown UnfinishedRunDetectedDialog answer: " +
    						FString::FromInt(Answer), true);
    					break;
    				}
    			}
    		}
    
    		// Participant
    		FString ParticipantID = "";
    		if (StudySetup->bUseCustomParticipantIDs)
    		{
    			//if we are recovering, restore it:
    			if (bRecoverParticipantData)
    			{
    				ParticipantID = USFParticipant::GetLastParticipantID();
    			}
    			else
    			{
    				//ask for it:
    				bool bValidIDEntered = false;
    				while (!bValidIDEntered)
    				{
    					FString IDGiven;
    					int Result = FSFUtils::OpenCustomDialogText("Participant ID", "Please type in your ID:", "",
    					                                            IDGiven);
    					if (Result < 0)
    					{
    						FSFLoggingUtils::Log(
    							"[USFGameInstance::PrepareWithStudySetup] The window for entering the participant ID was closed without giving an answer, repeat question!",
    							false);
    						continue;
    					}
    					//check whether it was used before
    					if (USFParticipant::WasParticipantIdAlreadyUsed(IDGiven))
    					{
    						FSFUtils::OpenMessageBox(
    							"Participant ID \"" + IDGiven + "\"was already used, you have to choose another one!");
    					}
    					else if (IDGiven.IsEmpty())
    					{
    						FSFUtils::OpenMessageBox("Participant ID given is empty, you have to choose one!");
    					}
    					else
    					{
    						ParticipantID = IDGiven;
    						bValidIDEntered = true;
    					}
    				}
    			}
    		}
    		else
    		{
    			//otherwise just use sequence number:
    			ParticipantID = FString::FromInt(ParticipantSequenceNumber);
    		}
    
    
    		// Primary Node in cluster mode: Continue preparation by event so it is done on all nodes
    		if (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
    		{
    			IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr();
    			FDisplayClusterClusterEventJson Event;
    			TMap<FString, FString> Params;
    			Params.Add("ParticipantSequenceNumber", FString::FromInt(ParticipantSequenceNumber));
    			Params.Add("ParticipantID", ParticipantID);
    			Params.Add("bRecoverParticipantData", bRecoverParticipantData ? "true" : "false");
    			Event.Type = "SFGameInstanceEvent";
    			Event.Name = "PrepareWithStudySetupAllNodes";
    			Event.Parameters = Params;
    			Manager->EmitClusterEventJson(Event, true);
    		}
    		// Not in cluster mode: Directly call prep
    		else
    		{
    			PrepareWithStudySetupAllNodes(ParticipantSequenceNumber, ParticipantID, bRecoverParticipantData);
    		}
    	}
    
    	//this needs to be done directly to not have one frame at the beginning which is not faded out!
    	if (IsInitialized())
    	{
    		InitFadeHandler(StudySetup->FadeConfig);
    	}
    	UpdateHUD("Wait for Start");
    }
    
    //Continue / Next Participant / Restart Study
    void USFGameInstance::PrepareWithStudySetupAllNodes(int NewParticipantSequenceNumber, FString NewParticipantID,
                                                        bool bRestoreLast)
    {
    	FSFLoggingUtils::Log(FString("[USFGameInstance::PrepareWithStudySetupAllNodes]:") +
    		"  Participant#: " + FString::FromInt(NewParticipantSequenceNumber) +
    		"  ParticipantID: " + NewParticipantID +
    		"  RecoverLast: " + (bRestoreLast ? "true" : "false"));
    	TArray<USFCondition*> Conditions;
    
    	if (bRestoreLast)
    	{
    		Conditions = USFParticipant::GetLastParticipantsConditions();
    		StartConditionIndex = USFParticipant::GetLastParticipantLastConditionStarted();
    	}
    	else
    	{
    		Conditions = StudySetup->GetAllConditionsForRun(NewParticipantSequenceNumber);
    	}
    
    	if (StudySetup->bPreloadAllMapsOnStart)
    	{
    		PreloadAllMaps(Conditions);
    	}
    
    
    	Participant = NewObject<USFParticipant>(this, FName(TEXT("Participant_") + NewParticipantID));
    	Participant->Initialize(NewParticipantSequenceNumber, NewParticipantID);
    	if (bRestoreLast)
    	{
    		Participant->LoadLastParticipantsIndependentVariables();
    	}
    	else
    	{
    		Participant->SetIndependentVariablesFromStudySetup(StudySetup);
    		Participant->StoreInIndependentVarLongTable();
    	}
    	Participant->SetStudyConditions(Conditions);
    
    	if (bRestoreLast)
    	{
    		Participant->RecoverStudyResultsOfFinishedConditions();
    		if (FSFUtils::IsPrimary())
    		{
    			//also delete any data of the condition that is now restarted (StartCondition), only on primary to avoid data races
    			Participant->DeleteStoredDataForConditionFromLongTable(Participant->Conditions[StartConditionIndex]);
    			for (USFDependentVariable* DV : Participant->Conditions[StartConditionIndex]->DependentVariables)
    			{
    				if (USFMultipleTrialDependentVariable* MTDV = Cast<USFMultipleTrialDependentVariable>(DV))
    				{
    					Participant->DeleteStoredTrialDataForCondition(Participant->Conditions[StartConditionIndex], MTDV);
    				}
    			}
    		}
    	}
    	
    }
    
    void USFGameInstance::PreloadAllMaps(const TArray<USFCondition*>& Conditions)
    {
    	TArray<FString> Maps;
    	for (const USFCondition* Condition : Conditions)
    	{
    		if (!Maps.Contains(Condition->Map))
    		{
    			Maps.Add(Condition->Map);
    		}
    	}
    
    	for (FString Map : Maps)
    	{
    		LoadPackage(nullptr, *Map, LOAD_None);
    	}
    	FSFLoggingUtils::Log("Sucessfully preloaded all maps.");
    }
    
    
    // ****************************************************************** // 
    // ******* Control Study ******************************************** //
    // ****************************************************************** //
    
    bool USFGameInstance::StartStudy()
    {
    	if (bStudyStarted)
    	{
    		FSFLoggingUtils::Log("[USFGameInstance::StartStudy()]: Study already started.", true);
    		return false;
    	}
    
    	if (!bInDebugMode)
    	{
    		//we are actually doing a real start and not just a "debug-start"
    		if (!Participant->StartStudy())
    		{
    			FSFLoggingUtils::Log("[USFGameInstance::StartStudy()]: unable to start study.", true);
    			return false;
    		}
    	}
    
    	if (GetWorld()->GetFirstPlayerController() && Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD()))
    	{
    		ASFMasterHUD* HUD = Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());
    		HUD->SetStartStudyButtonVisibility(ESlateVisibility::Collapsed);
    		HUD->SetNextConditionButtonVisibility(ESlateVisibility::Visible);
    	}
    
    	if (StartConditionIndex == -1)
    	{
    		NextCondition();
    	}
    	else
    	{
    		GoToCondition(StartConditionIndex);
    	}
    
    	//check whether we want to use the eye tracker
    	if (StudySetup && StudySetup->UseGazeTracker != EGazeTrackerMode::NotTracking)
    	{
    		GazeTracker = NewObject<USFGazeTracker>(this, TEXT("GazeTracker"));
    		GazeTracker->Init(StudySetup->UseGazeTracker, StudySetup->bIgnoreNonGazeTargetActors,
    		                  StudySetup->EyeTrackingFrameRate, StudySetup->GazeTrackingBackend);
    	}
    
    
    	return true;
    }
    
    void USFGameInstance::EndStudy()
    {
    	USFCondition* LastCondition = Participant->GetCurrentCondition();
    	if (!LastCondition || !LastCondition->WasStarted() || LastCondition->IsFinished())
    	{
    		return;
    	}
    
    	LastCondition->End();
    	Participant->EndStudy();
    
    	UpdateHUD("Study ended");
    	if (GetHUD())
    	{
    		GetHUD()->SetNextConditionButtonVisibility(ESlateVisibility::Collapsed);
    	}
    
    	bStudyEnded = true;
    	FadeHandler->FadeOut();
    }
    
    
    bool USFGameInstance::NextCondition(bool bForced /*=false*/,
                                        EFadeBetweenCondition Fade /*= EFadeBetweenCondition::AsDefault*/)
    {
    	// Check if is already fading
    	if (FadeHandler->GetIsFading() && !FadeHandler->GetIsFadedOutWaitingForLevel())
    	{
    		FSFLoggingUtils::Log("[USFGameInstance::NextCondition()]: Already Fading between levels", true);
    		return false;
    	}
    
    	TOptional<int> NextConditionIdx = Participant->GetNextConditionIndex();
    	if (!NextConditionIdx.IsSet())
    	{
    		FSFLoggingUtils::Log("[SFGameInstance::NextCondition]: All conditions already ran, no NextCondition", false);
    		EndStudy();
    		return false;
    	}
    	return GoToCondition(NextConditionIdx.GetValue(), bForced, Fade);
    }
    
    bool USFGameInstance::GoToCondition(const int NewConditionIndex, bool bForced /*=false*/,
                                        EFadeBetweenCondition Fade /*= EFadeBetweenCondition::AsDefault*/)
    {
    	// Check if is already fading
    	if (FadeHandler->GetIsFading() && !FadeHandler->GetIsFadedOutWaitingForLevel())
    	{
    		FSFLoggingUtils::Log("[USFGameInstance::GoToCondition()]: Already Fading between levels", true);
    		return false;
    	}
    
    	if (NewConditionIndex < 0 || NewConditionIndex >= GetParticipant()->GetAllConditions().Num())
    	{
    		FSFLoggingUtils::Log("[USFGameInstance::GoToCondition()]: Could not load next condition.", true);
    		return false;
    	}
    
    	if (!bStudyStarted && !bIsStarting)
    	{
    		StartConditionIndex = NewConditionIndex; //Start at this condition
    		bIsStarting = true; //To avoid infinite loops, when StartStudy calls GoToCondition(StartCondition)
    		return StartStudy();
    	}
    	GoToConditionSynced(NewConditionIndex, bForced, Fade);
    	return true;
    }
    
    void USFGameInstance::GoToConditionSynced(int ConditionIndex, bool bForced, EFadeBetweenCondition Fade)
    {
    	const EDisplayClusterOperationMode OperationMode = IDisplayCluster::Get().GetOperationMode();
    	if (OperationMode != EDisplayClusterOperationMode::Cluster)
    	{
    		HandleGoToConditionSynced(ConditionIndex, bForced, Fade);
    	}
    	else
    	{
    		IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
    		check(ClusterManager != nullptr);
    
    		FDisplayClusterClusterEventJson ClusterEvent;
    		ClusterEvent.Type = "SFGameInstanceEvent";
    		ClusterEvent.Name = "GoToConditionSynced";
    		TMap<FString, FString> Params;
    		Params.Add("ConditionIndex", FString::FromInt(ConditionIndex));
    		Params.Add("bForced", bForced ? "true" : "false");
    		Params.Add("Fade", (Fade == EFadeBetweenCondition::AsDefault
    			                    ? "AsDefault"
    			                    : (Fade == EFadeBetweenCondition::ForceNoFade ? "ForceNoFade" : "ForceFade")));
    		ClusterEvent.Parameters = Params;
    		ClusterEvent.bShouldDiscardOnRepeat = true;
    
    		ClusterManager->EmitClusterEventJson(ClusterEvent, true);
    	}
    }
    
    
    void USFGameInstance::HandleClusterEvent(const FDisplayClusterClusterEventJson& Event)
    {
    	if (Event.Type == "SFGameInstanceEvent")
    	{
    		if (Event.Name == "PrepareWithStudySetupAllNodes")
    		{
    			int NewParticipantSequenceNumber = FCString::Atoi(*Event.Parameters["ParticipantSequenceNumber"]);
    			FString NewParticipantID = Event.Parameters["ParticipantID"];
    			bool bRecoverParticipantData = Event.Parameters["bRecoverParticipantData"] == "true";
    			PrepareWithStudySetupAllNodes(NewParticipantSequenceNumber, NewParticipantID, bRecoverParticipantData);
    		}
    		//now we actually react on all cluster nodes:
    		if (Event.Name == "GoToConditionSynced")
    		{
    			EFadeBetweenCondition Fade = EFadeBetweenCondition::AsDefault;
    			if (Event.Parameters["Fade"] != "AsDefault")
    			{
    				if (Event.Parameters["Fade"] == "ForceNoFade")
    				{
    					Fade = EFadeBetweenCondition::ForceNoFade;
    				}
    				else if (Event.Parameters["Fade"] == "ForceFade")
    				{
    					Fade = EFadeBetweenCondition::ForceFade;
    				}
    				else
    				{
    					FSFLoggingUtils::Log(
    						"[USFGameInstance::HandleClusterEvent] Unknown Fade value: " + Event.Parameters["Fade"], true);
    				}
    			}
    			HandleGoToConditionSynced(FCString::Atoi(*Event.Parameters["ConditionIndex"]),
    			                          Event.Parameters["bForced"] == "true", Fade);
    		}
    	}
    }
    
    
    void USFGameInstance::HandleGoToConditionSynced(int ConditionIndex, bool bForced, EFadeBetweenCondition Fade)
    {
    	USFCondition* NextCondition = Participant->GetAllConditions()[ConditionIndex];
    	USFCondition* LastCondition = Participant->GetCurrentCondition();
    
    	if (NextCondition->WasStarted() && bForced)
    	{
    		LogComment(
    			"Restarting Condition " + NextCondition->GetPrettyName() +
    			", so we delete all gathered data for that condition.");
    
    		FString BackUpFolderName = "Restart_" + NextCondition->GetShortName() + "_BackUp-" + FDateTime::Now().
    			ToString();
    		Participant->SetCurrentBackUpFolderName(BackUpFolderName);
    		//so remove already stored data!
    		Participant->DeleteStoredDataForConditionFromLongTable(NextCondition);
    		for (USFDependentVariable* DV : NextCondition->DependentVariables)
    		{
    			if (USFMultipleTrialDependentVariable* MTDV = Cast<USFMultipleTrialDependentVariable>(DV))
    			{
    				Participant->DeleteStoredTrialDataForCondition(NextCondition, MTDV);
    			}
    		}
    	}
    	else if (LastCondition && LastCondition->WasStarted())
    	{
    		TArray<FString> UnfinishedVars = LastCondition->End();
    		if (UnfinishedVars.Num() != 0)
    		{
    			FString Missing = "Missing data for: {";
    			for (FString UnfinishedVar : UnfinishedVars)
    			{
    				Missing += UnfinishedVar + " ";
    			}
    			Missing += "}";
    			if (bForced)
    			{
    				FSFLoggingUtils::Log(
    					"[USFGameInstance::GoToCondition()]: Forced to go to next condition, but current one is not finished! "
    					+ Missing,
    					true);
    			}
    			else
    			{
    				FSFLoggingUtils::Log(
    					"[USFGameInstance::GoToCondition()]: Cannot go to next condition, since current one is not finished! "
    					+ Missing,
    					true);
    				return;
    			}
    		}
    	}
    
    	const bool bConditionPresent = Participant->SetNextCondition(ConditionIndex);
    	if (!bConditionPresent)
    	{
    		FSFLoggingUtils::Log("[USFGameInstance::GoToCondition()]: Requested GoTo Condition seems not to exist.", true);
    		return;
    	}
    
    	// Fade to next Level
    	if (IsInitialized())
    	{
    		if (FadeHandler->GetIsFadedOutWaitingForLevel())
    		{
    			FadeHandler->SetLevelToLoad(NextCondition->Map);
    		}
    		else
    		{
    			FadeHandler->FadeToLevel(NextCondition->Map, Fade);
    		}
    		UpdateHUD("Fading out");
    	}
    
    	bStudyStarted = true;
    }
    
    bool USFGameInstance::IsStarted() const
    {
    	return bStudyStarted;
    }
    
    bool USFGameInstance::IsInDebugMode() const
    {
    	return bInDebugMode;
    }
    
    bool USFGameInstance::IsContinueStudyStart() const
    {
    	return !bInDebugMode && (StartConditionIndex != -1);
    }
    
    bool USFGameInstance::HasEnded() const
    {
    	return bStudyEnded;
    }
    
    float USFGameInstance::GetCurrentTime() const
    {
    	if (GetParticipant() && IsStarted())
    	{
    		return GetParticipant()->GetCurrentTime();
    	}
    	return 0.0f;
    }
    
    FString USFGameInstance::GetFactorLevel(FString FactorName) const
    {
    	if (!Participant)
    	{
    		return "ParticipantNotSet";
    	}
    	if (Participant->GetCurrentCondition()->FactorLevels.Contains(FactorName))
    	{
    		return Participant->GetCurrentCondition()->FactorLevels[FactorName];
    	}
    
    	FSFLoggingUtils::Log("[USFGameInstance::GetFactorLevel()]: Factor " + FactorName + " seems not to exist!", true);
    	return "FactorNotPresent";
    }
    
    bool USFGameInstance::HasFactor(FString FactorName) const
    {
    	if (Participant && Participant->GetCurrentCondition()->FactorLevels.Contains(FactorName))
    	{
    		return true;
    	}
    	return false;
    }
    
    FString USFGameInstance::GetIndependentVariableValue(FString VariableName) const
    {
    	if (!GetParticipant())
    	{
    		return "";
    	}
    	return GetParticipant()->GetIndependentVariable(VariableName).Value;
    }
    
    FString USFGameInstance::GetCurrentPhase() const
    {
    	if (!Participant)
    	{
    		return "ParticipantNotSet";
    	}
    
    	if (!Participant->GetCurrentCondition())
    	{
    		return "NoConditionActive";
    	}
    	return Participant->GetCurrentCondition()->PhaseName;
    }
    
    int USFGameInstance::GetCurrentConditionsSequenceNumber() const
    {
    	if (!Participant)
    	{
    		return -1;
    	}
    	return Participant->GetCurrentConditionNumber() + 1;
    }
    
    USFCondition* USFGameInstance::GetCurrentCondition() const
    {
    	if (!Participant)
    	{
    		return nullptr;
    	}
    	return Participant->GetCurrentCondition();
    }
    
    USFCondition* USFGameInstance::GetNextCondition() const
    {
    	if (!Participant || !Participant->GetNextConditionIndex().IsSet())
    	{
    		return nullptr;
    	}
    	return Participant->GetAllConditions()[Participant->GetNextConditionIndex().GetValue()];
    }
    
    void USFGameInstance::LogToHUD(FString Text)
    {
    	if (GetWorld()->GetFirstPlayerController() && Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())
    		&&
    		Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())->IsWidgetPresent())
    	{
    		Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())->AddLogMessage(Text);
    	}
    	else
    	{
    		HUDSavedData.LogMessages.Add(Text);
    	}
    	//by default we also log this into the debug stream, so you can also check on older messages
    	FSFLoggingUtils::Log("[Logged to HUD] " + Text);
    }
    
    void USFGameInstance::UpdateHUD(FString Status)
    {
    	if (GetHUD())
    	{
    		GetHUD()->UpdateHUD(Participant, Status);
    	}
    	else
    	{
    		HUDSavedData.Status = Status;
    		if (Participant)
    		{
    			HUDSavedData.Participant = Participant->GetID();
    		}
    	}
    }
    
    ASFMasterHUD* USFGameInstance::GetHUD()
    {
    	if (GetWorld() && GetWorld()->GetFirstPlayerController())
    	{
    		return Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());
    	}
    	return nullptr;
    }
    
    USFGazeTracker* USFGameInstance::GetGazeTracker() const
    {
    	return GazeTracker;
    }
    
    FExperimenterViewConfig USFGameInstance::GetExperimenterViewConfig() const
    {
    	if (StudySetup)
    	{
    		return StudySetup->ExperimenterViewConfig;
    	}
    	FSFLoggingUtils::Log(
    		"[USFGameInstance::GetExperimenterViewConfig] No StudySetup present, returning default ExperimenterViewConfig.",
    		true);
    	return FExperimenterViewConfig();
    }
    
    USFExperimenterWindow* USFGameInstance::GetExperimenterWindow() const
    {
    	return ExperimenterWindow;
    }
    
    void USFGameInstance::SetHUDVisible(bool bVisible)
    {
    	if (GetHUD())
    	{
    		GetHUD()->SetHUDVisible(bVisible);
    	}
    }
    
    USFFadeHandler* USFGameInstance::GetFadeHandler()
    {
    	return FadeHandler;
    }
    
    ASFStudySetup* USFGameInstance::GetStudySetup()
    {
    	return StudySetup;
    }
    
    
    // ****************************************************************** // 
    // ******* Executing Study ****************************************** //
    // ****************************************************************** //
    
    void USFGameInstance::OnLevelLoaded()
    {
    	//do we need to do something here?
    	UpdateHUD("Fading In");
    }
    
    void USFGameInstance::OnFadedIn()
    {
    	if (Participant && Participant->GetCurrentCondition())
    	{
    		//this should be logged first
    		if (bInDebugMode)
    		{
    			USFLoggingBPLibrary::LogComment("Running in DEBUG MODE");
    		}
    		FString NumberStr = FString::FromInt(GetCurrentConditionsSequenceNumber()) + "/" + FString::FromInt(
    			Participant->GetAllConditions().Num());
    		USFLoggingBPLibrary::LogComment(
    			"Start Condition " + NumberStr + ": " + Participant->GetCurrentCondition()->GetPrettyName());
    	}
    
    	OnFadedInDelegate.Broadcast();
    
    	if (bStartedOnUnrelatedMap)
    	{
    		return;
    	}
    
    	Participant->GetCurrentCondition()->Begin();
    
    	UpdateHUD("Condition " + FString::FromInt(GetCurrentConditionsSequenceNumber()) + "/" + FString::FromInt(
    		Participant->GetAllConditions().Num()));
    }
    
    USFParticipant* USFGameInstance::GetParticipant() const
    {
    	return Participant;
    }
    
    // ****************************************************************** // 
    // *******    Logging    ******************************************** //
    // ****************************************************************** //
    
    bool USFGameInstance::LogTick(float DeltaSeconds)
    {
    	if (LogObject->GetLoggingLoopsActive())
    	{
    		LogObject->WritePositionLogToFile();
    		LogObject->WriteGazeTrackingLogToFile();
    	}
    	return true;
    }
    
    USFLogObject* USFGameInstance::GetLogObject()
    {
    	return LogObject;
    }
    
    void USFGameInstance::LogComment(const FString& Comment, bool bAlsoLogToHUD /*= true*/)
    {
    	FSFLoggingUtils::Log("Logged Comment: " + Comment);
    	if (!GetParticipant())
    	{
    		return;
    	}
    	UniLog.Log("#" + GetParticipant()->GetCurrentTimeAsString() + ": " + Comment, "ParticipantLog");
    	if (bAlsoLogToHUD)
    	{
    		LogToHUD(Comment);
    	}
    }
    
    void USFGameInstance::LogData(const FString& DependentVariableName, const FString& Value, bool bAlsoLogToHUD /*= true*/)
    {
    	if (!GetParticipant())
    	{
    		return;
    	}
    	USFCondition* CurrCondition = GetParticipant()->GetCurrentCondition();
    	if (!CurrCondition->StoreDependentVariableData(DependentVariableName, Value))
    	{
    		return;
    	}
    
    	LogComment("Recorded " + DependentVariableName + ": " + Value, bAlsoLogToHUD);
    
    
    	//the data is stored in the phase long table on SetCondition() or EndStudy()
    }
    
    void USFGameInstance::LogTrialData(const FString& DependentVariableName, const TArray<FString>& Values,
                                       bool bAlsoLogToHUD /*= true*/)
    {
    	if (!GetParticipant())
    	{
    		return;
    	}
    	USFCondition* CurrCondition = Get()->GetParticipant()->GetCurrentCondition();
    	if (!CurrCondition->StoreMultipleTrialDependentVariableData(DependentVariableName, Values))
    	{
    		return;
    	}
    
    
    	FString Data = "";
    	for (const FString& Value : Values)
    	{
    		Data += (Data.IsEmpty() ? "{" : ",") + Value;
    	}
    	Data += "}";
    	LogComment("Recorded " + DependentVariableName + ": " + Data, bAlsoLogToHUD);
    }