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

255 lines
9.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LyraFrontendStateComponent.h"
#include "CommonActivatableWidget.h"
#include "CommonGameInstance.h"
#include "CommonSessionSubsystem.h"
#include "CommonUserSubsystem.h"
#include "CommonUserTypes.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "ControlFlow.h"
#include "ControlFlowManager.h"
#include "Delegates/Delegate.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "GameFramework/GameModeBase.h"
#include "GameFramework/GameStateBase.h"
#include "GameModes/LyraExperienceManagerComponent.h"
#include "Internationalization/Text.h"
#include "Kismet/GameplayStatics.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Optional.h"
#include "NativeGameplayTags.h"
#include "PrimaryGameLayout.h"
#include "Templates/Casts.h"
#include "UObject/NameTypes.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 Join Requested Session"), this, &ThisClass::FlowStep_TryJoinRequestedSession)
.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 and will use the default input device id
InProgressPressStartScreen = SubFlow;
UserSubsystem->OnUserInitializeComplete.AddDynamic(this, &ULyraFrontendStateComponent::OnUserInitialized);
UserSubsystem->TryToInitializeForLocalPlay(0, FInputDeviceId(), 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_TryJoinRequestedSession(FControlFlowNodeRef SubFlow)
{
UCommonGameInstance* GameInstance = Cast<UCommonGameInstance>(UGameplayStatics::GetGameInstance(this));
if (GameInstance->GetRequestedSession() != nullptr && GameInstance->CanJoinRequestedSession())
{
UCommonSessionSubsystem* SessionSubsystem = GameInstance->GetSubsystem<UCommonSessionSubsystem>();
if (ensure(SessionSubsystem))
{
// Bind to session join completion to continue or cancel the flow
// TODO: Need to ensure that after session join completes, the server travel completes.
OnJoinSessionCompleteEventHandle = SessionSubsystem->OnJoinSessionCompleteEvent.AddWeakLambda(this, [this, SubFlow, SessionSubsystem](const FOnlineResultInformation& Result)
{
// Unbind delegate. SessionSubsystem is the object triggering this event, so it must still be valid.
SessionSubsystem->OnJoinSessionCompleteEvent.Remove(OnJoinSessionCompleteEventHandle);
OnJoinSessionCompleteEventHandle.Reset();
if (Result.bWasSuccessful)
{
// No longer transitioning to the main menu
SubFlow->CancelFlow();
}
else
{
// Proceed to the main menu
SubFlow->ContinueFlow();
return;
}
});
GameInstance->JoinRequestedSession();
return;
}
}
// Skip this step if we didn't start requesting a session join
SubFlow->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;
}
});
}
}