2022-05-23 18:41:30 +00:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
#include "LyraFrontendStateComponent.h"
|
2022-09-13 07:18:28 +00:00
|
|
|
|
|
|
|
#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"
|
2022-05-23 18:41:30 +00:00
|
|
|
#include "GameFramework/GameModeBase.h"
|
2022-09-13 07:18:28 +00:00
|
|
|
#include "GameFramework/GameStateBase.h"
|
2022-05-23 18:41:30 +00:00
|
|
|
#include "GameModes/LyraExperienceManagerComponent.h"
|
2022-09-13 07:18:28 +00:00
|
|
|
#include "Internationalization/Text.h"
|
2022-05-23 18:41:30 +00:00
|
|
|
#include "Kismet/GameplayStatics.h"
|
2022-09-13 07:18:28 +00:00
|
|
|
#include "Misc/AssertionMacros.h"
|
|
|
|
#include "Misc/Optional.h"
|
|
|
|
#include "NativeGameplayTags.h"
|
2022-05-23 18:41:30 +00:00
|
|
|
#include "PrimaryGameLayout.h"
|
2022-09-13 07:18:28 +00:00
|
|
|
#include "Templates/Casts.h"
|
|
|
|
#include "UObject/NameTypes.h"
|
2022-05-23 18:41:30 +00:00
|
|
|
|
|
|
|
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)
|
2022-09-13 07:18:28 +00:00
|
|
|
.QueueStep(TEXT("Try Join Requested Session"), this, &ThisClass::FlowStep_TryJoinRequestedSession)
|
2022-05-23 18:41:30 +00:00
|
|
|
.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())
|
|
|
|
{
|
2022-09-13 07:18:28 +00:00
|
|
|
// Start the auto login process, this should finish quickly and will use the default input device id
|
2022-05-23 18:41:30 +00:00
|
|
|
InProgressPressStartScreen = SubFlow;
|
|
|
|
UserSubsystem->OnUserInitializeComplete.AddDynamic(this, &ULyraFrontendStateComponent::OnUserInitialized);
|
2022-09-13 07:18:28 +00:00
|
|
|
UserSubsystem->TryToInitializeForLocalPlay(0, FInputDeviceId(), false);
|
2022-05-23 18:41:30 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-13 07:18:28 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2022-05-23 18:41:30 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|