RealtimeStyleTransferRuntime/Source/LyraGame/UI/Frontend/LyraFrontendStateComponent.cpp

214 lines
7.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LyraFrontendStateComponent.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/GameModeBase.h"
#include "GameFeaturesSubsystem.h"
#include "System/LyraAssetManager.h"
#include "GameFeatureAction.h"
#include "GameFeaturesSubsystemSettings.h"
#include "GameModes/LyraExperienceManagerComponent.h"
#include "GameModes/LyraExperienceDefinition.h"
#include "TimerManager.h"
#include "NativeGameplayTags.h"
#include "ControlFlowManager.h"
#include "CommonUIExtensions.h"
#include "Kismet/GameplayStatics.h"
#include "PrimaryGameLayout.h"
#include "ICommonUIModule.h"
#include "CommonUISettings.h"
#include "CommonUserSubsystem.h"
#include "CommonSessionSubsystem.h"
#include "Engine/GameInstance.h"
namespace FrontendTags
{
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_PLATFORM_TRAIT_SINGLEONLINEUSER, "Platform.Trait.SingleOnlineUser");
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_UI_LAYER_MENU, "UI.Layer.Menu");
}
ULyraFrontendStateComponent::ULyraFrontendStateComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void ULyraFrontendStateComponent::BeginPlay()
{
Super::BeginPlay();
// Listen for the experience load to complete
AGameStateBase* GameState = GetGameStateChecked<AGameStateBase>();
ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<ULyraExperienceManagerComponent>();
check(ExperienceComponent);
// This delegate is on a component with the same lifetime as this one, so no need to unhook it in
ExperienceComponent->CallOrRegister_OnExperienceLoaded_HighPriority(FOnLyraExperienceLoaded::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded));
}
void ULyraFrontendStateComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
}
bool ULyraFrontendStateComponent::ShouldShowLoadingScreen(FString& OutReason) const
{
if (bShouldShowLoadingScreen)
{
OutReason = TEXT("Frontend Flow Pending...");
if (FrontEndFlow.IsValid())
{
const TOptional<FString> StepDebugName = FrontEndFlow->GetCurrentStepDebugName();
if (StepDebugName.IsSet())
{
OutReason = StepDebugName.GetValue();
}
}
return true;
}
return false;
}
void ULyraFrontendStateComponent::OnExperienceLoaded(const ULyraExperienceDefinition* Experience)
{
FControlFlow& Flow = FControlFlowStatics::Create(this, TEXT("FrontendFlow"))
.QueueStep(TEXT("Wait For User Initialization"), this, &ThisClass::FlowStep_WaitForUserInitialization)
.QueueStep(TEXT("Try Show Press Start Screen"), this, &ThisClass::FlowStep_TryShowPressStartScreen)
.QueueStep(TEXT("Try Show Main Screen"), this, &ThisClass::FlowStep_TryShowMainScreen);
Flow.ExecuteFlow();
FrontEndFlow = Flow.AsShared();
}
void ULyraFrontendStateComponent::FlowStep_WaitForUserInitialization(FControlFlowNodeRef SubFlow)
{
// If this was a hard disconnect, explicitly destroy all user and session state
// TODO: Refactor the engine disconnect flow so it is more explicit about why it happened
bool bWasHardDisconnect = false;
AGameModeBase* GameMode = GetWorld()->GetAuthGameMode<AGameModeBase>();
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this);
if (ensure(GameMode) && UGameplayStatics::HasOption(GameMode->OptionsString, TEXT("closed")))
{
bWasHardDisconnect = true;
}
// Only reset users on hard disconnect
UCommonUserSubsystem* UserSubsystem = GameInstance->GetSubsystem<UCommonUserSubsystem>();
if (ensure(UserSubsystem) && bWasHardDisconnect)
{
UserSubsystem->ResetUserState();
}
// Always reset sessions
UCommonSessionSubsystem* SessionSubsystem = GameInstance->GetSubsystem<UCommonSessionSubsystem>();
if (ensure(SessionSubsystem))
{
SessionSubsystem->CleanUpSessions();
}
SubFlow->ContinueFlow();
}
void ULyraFrontendStateComponent::FlowStep_TryShowPressStartScreen(FControlFlowNodeRef SubFlow)
{
const UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this);
UCommonUserSubsystem* UserSubsystem = GameInstance->GetSubsystem<UCommonUserSubsystem>();
// Check to see if the first player is already logged in, if they are, we can skip the press start screen.
if (const UCommonUserInfo* FirstUser = UserSubsystem->GetUserInfoForLocalPlayerIndex(0))
{
if (FirstUser->InitializationState == ECommonUserInitializationState::LoggedInLocalOnly ||
FirstUser->InitializationState == ECommonUserInitializationState::LoggedInOnline)
{
SubFlow->ContinueFlow();
return;
}
}
// Check to see if the platform actually requires a 'Press Start' screen. This is only
// required on platforms where there can be multiple online users where depending on what player's
// controller presses 'Start' establishes the player to actually login to the game with.
if (!UserSubsystem->ShouldWaitForStartInput())
{
// Start the auto login process, this should finish quickly
InProgressPressStartScreen = SubFlow;
UserSubsystem->OnUserInitializeComplete.AddDynamic(this, &ULyraFrontendStateComponent::OnUserInitialized);
UserSubsystem->TryToInitializeForLocalPlay(0, 0, false);
return;
}
// Add the Press Start screen, move to the next flow when it deactivates.
if (UPrimaryGameLayout* RootLayout = UPrimaryGameLayout::GetPrimaryGameLayoutForPrimaryPlayer(this))
{
constexpr bool bSuspendInputUntilComplete = true;
RootLayout->PushWidgetToLayerStackAsync<UCommonActivatableWidget>(FrontendTags::TAG_UI_LAYER_MENU, bSuspendInputUntilComplete, PressStartScreenClass,
[this, SubFlow](EAsyncWidgetLayerState State, UCommonActivatableWidget* Screen) {
switch (State)
{
case EAsyncWidgetLayerState::AfterPush:
bShouldShowLoadingScreen = false;
Screen->OnDeactivated().AddWeakLambda(this, [this, SubFlow]() {
SubFlow->ContinueFlow();
});
break;
case EAsyncWidgetLayerState::Canceled:
bShouldShowLoadingScreen = false;
SubFlow->ContinueFlow();
return;
}
});
}
}
void ULyraFrontendStateComponent::OnUserInitialized(const UCommonUserInfo* UserInfo, bool bSuccess, FText Error, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext OnlineContext)
{
FControlFlowNodePtr FlowToContinue = InProgressPressStartScreen;
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this);
UCommonUserSubsystem* UserSubsystem = GameInstance->GetSubsystem<UCommonUserSubsystem>();
if (ensure(FlowToContinue.IsValid() && UserSubsystem))
{
UserSubsystem->OnUserInitializeComplete.RemoveDynamic(this, &ULyraFrontendStateComponent::OnUserInitialized);
InProgressPressStartScreen.Reset();
if (bSuccess)
{
// On success continue flow normally
FlowToContinue->ContinueFlow();
}
else
{
// TODO: Just continue for now, could go to some sort of error screen
FlowToContinue->ContinueFlow();
}
}
}
void ULyraFrontendStateComponent::FlowStep_TryShowMainScreen(FControlFlowNodeRef SubFlow)
{
if (UPrimaryGameLayout* RootLayout = UPrimaryGameLayout::GetPrimaryGameLayoutForPrimaryPlayer(this))
{
constexpr bool bSuspendInputUntilComplete = true;
RootLayout->PushWidgetToLayerStackAsync<UCommonActivatableWidget>(FrontendTags::TAG_UI_LAYER_MENU, bSuspendInputUntilComplete, MainScreenClass,
[this, SubFlow](EAsyncWidgetLayerState State, UCommonActivatableWidget* Screen) {
switch (State)
{
case EAsyncWidgetLayerState::AfterPush:
bShouldShowLoadingScreen = false;
SubFlow->ContinueFlow();
return;
case EAsyncWidgetLayerState::Canceled:
bShouldShowLoadingScreen = false;
SubFlow->ContinueFlow();
return;
}
});
}
}