// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #include "GameSaving/AwsGameKitGameSavingExamples.h" // GameKit #include "AwsGameKitCore.h" #include "AwsGameKitEditor.h" #include "AwsGameKitRuntime.h" #include "AwsGameKitStyleSet.h" #include "FeatureResourceManager.h" #include "Core/AwsGameKitErrors.h" #include "Identity/AwsGameKitIdentity.h" #include "Models/AwsGameKitGameSavingModels.h" #include "Utils/Blueprints/UAwsGameKitFileUtils.h" // Unreal #include "Modules/ModuleManager.h" #include "Styling/SlateStyle.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SMultiLineEditableTextBox.h" #include "Widgets/Layout/SExpandableArea.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "AwsGameKitGameSavingExamples" #define result_row(label, text) \ + SVerticalBox::Slot() \ .AutoHeight() \ [ \ SNew(SHorizontalBox) \ + SHorizontalBox::Slot() \ .Padding(25, 5, 5, 5) \ .FillWidth(1) \ [ \ SNew(STextBlock) \ .Text(FText::FromString(label)) \ ] \ + SHorizontalBox::Slot() \ .Padding(5) \ .FillWidth(3) \ [ \ SNew(SEditableTextBox) \ .IsReadOnly(true) \ .Text(FText::FromString(text)) \ ] \ ] \ InitializationStatus AAwsGameKitGameSavingExamples::gameSavingInitializationStatus = InitializationStatus::NOT_STARTED; FGameSavingSlots AAwsGameKitGameSavingExamples::cachedSlotsCopy = FGameSavingSlots(); /** * Set the default values for this actor's properties. */ AAwsGameKitGameSavingExamples::AAwsGameKitGameSavingExamples() { savePopoutOpen = false; loadPopoutOpen = false; gameSavingInitializationStatus = InitializationStatus::NOT_STARTED; } void AAwsGameKitGameSavingExamples::Destroyed() { if (savePopoutOpen) { SaveSlotWindow.Get()->RequestDestroyWindow(); } if (loadPopoutOpen) { LoadSlotWindow.Get()->RequestDestroyWindow(); } Super::Destroyed(); } bool AAwsGameKitGameSavingExamples::IsEditorOnly() const { return true; } bool AAwsGameKitGameSavingExamples::IsIdentityDeployed() const { return IsFeatureDeployed(FeatureType::Identity, "Identity/Authentication"); } bool AAwsGameKitGameSavingExamples::IsGameSavingDeployed() const { return IsFeatureDeployed(FeatureType::GameStateCloudSaving, "Game Saving"); } bool AAwsGameKitGameSavingExamples::IsFeatureDeployed(FeatureType featureType, FString featureName) const { /* * This check is only meant for the examples. * This is to ensure the feature has been deployed before running any of the sample code. */ if (!ReloadSettings(featureType)) { FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("This example requires an AWS GameKit backend service for " + featureName + "." " See Edit > Project Settings > Plugins > AWS GameKit to create the " + featureName + " backend.")); return false; } return true; } bool AAwsGameKitGameSavingExamples::ReloadSettings(FeatureType featureType) const { FAwsGameKitRuntimeModule* runtimeModule = FModuleManager::GetModulePtr("AwsGameKitRuntime"); if (runtimeModule->AreFeatureSettingsLoaded(featureType)) { return true; } /************* // In order to call ReloadConfigFile() outside of AwsGameKitEditor module, use the lines below: FAwsGameKitRuntimeModule* runtimeModule = FModuleManager::GetModulePtr("AwsGameKitRuntime"); SessionManagerLibrary sessionManagerLibrary = runtimeModule->GetSessionManagerLibrary(); sessionManagerLibrary.SessionManagerWrapper->ReloadConfig(sessionManagerLibrary.SessionManagerInstanceHandle); return runtimeModule->AreFeatureSettingsLoaded(FeatureType::GameStateCloudSaving); *************/ FAwsGameKitEditorModule* editorModule = AWSGAMEKIT_EDITOR_MODULE_INSTANCE(); return runtimeModule->ReloadConfigFile(editorModule->GetFeatureResourceManager()->GetClientConfigSubdirectory()); } // This initializer is called from the Details Panel APIs. void AAwsGameKitGameSavingExamples::InitializeGameSavingLibrary(const OnButtonClickFromDetailsPanel postInitCallback, FString* postInitStatusCode) { const TFunction postInitCallbackWrapper = [this, postInitCallback]() { (this->* (postInitCallback))(); }; InitializeGameSavingLibrary(postInitCallbackWrapper, postInitStatusCode); } // This initializer is called from the Popout Window APIs. void AAwsGameKitGameSavingExamples::InitializeGameSavingLibrary(const OnButtonClickFromPopupWindow postInitCallback, FString* postInitStatusCode) { const TFunction postInitCallbackWrapper = [this, postInitCallback]() { (this->* (postInitCallback))(); }; InitializeGameSavingLibrary(postInitCallbackWrapper, postInitStatusCode); } /** * This function must be called exactly once before using any of the Game Saving APIs in this example. * * The Game Saving library needs to be initialized exactly once by calling AddLocalSlots() followed by GetAllSlotSyncStatuses(). * It's important to call these two APIs first (before any other Game Saving APIs) and to call them in this order. * It's also important to only call AddLocalSlots() once, regardless of how many separate Actors use Game Saving in a single game session. * * AddLocalSlots() ensures Game Saving knows about local saves on the device that exist from previous times the game was played. * * GetAllSlotSyncStatuses() ensures Game Saving has the latest information about the cloud saves, knows which local saves are synchronized * with the cloud, and which saves should be uploaded, downloaded, or need manual conflict resolution. * * @param postInitCallback The function to execute after the Game Saving library has finished initializing. This is the Game Saving API the user clicked. * @param postInitStatusCode The status code to report the initialization statuses to. This should correspond to the Game Saving API the user clicked. */ void AAwsGameKitGameSavingExamples::InitializeGameSavingLibrary(const TFunction postInitCallback, FString* postInitStatusCode) { UE_LOG(LogAwsGameKit, Display, TEXT("AAwsGameKitGameSavingExamples::InitializeGameSavingLibrary()")); if (gameSavingInitializationStatus == InitializationStatus::SUCCESSFUL) { UE_LOG(LogAwsGameKit, Display, TEXT("AAwsGameKitGameSavingExamples::InitializeGameSavingLibrary() Game Saving is already initialized. Exiting early. Game Saving should only be initialized once.")); postInitCallback(); return; } if (gameSavingInitializationStatus == InitializationStatus::IN_PROGRESS) { UE_LOG(LogAwsGameKit, Display, TEXT("AAwsGameKitGameSavingExamples::Initialize() Game Saving is already being initialized. Exiting early.")); *postInitStatusCode = "Try again after initialization is complete."; return; } gameSavingInitializationStatus = InitializationStatus::IN_PROGRESS; // Record which function the user originally clicked. We'll call it back after initialization is complete: postInitializationCallback = postInitCallback; postInitializationStatusCode = postInitStatusCode; // Let user know the Game Saving library is being initialized, because initialization is asynchronous & long-running: *postInitStatusCode = "Initializing Game Saving library - Adding local saves and syncing status with cloud ..."; // Begin actual initialization: /* * Find all SaveInfo.json files on the device. * * In this example, the SaveInfo.json files are saved to UAwsGameKitFileUtils::GetFeatureSaveDirectory(). * * In your own game, you should store each SaveInfo.json file alongside its corresponding save file. * This will help other developers and players to understand both files go together. */ FFilePaths saveInfoFilePaths; const FString searchDirectory = UAwsGameKitFileUtils::GetFeatureSaveDirectory(FeatureType_E::GameStateCloudSaving); const FString fileExtension = AwsGameKitGameSaving::GetSaveInfoFileExtension(); UAwsGameKitFileUtils::GetFilesInDirectory(saveInfoFilePaths, searchDirectory, fileExtension); // Call AddLocalSlots(): const auto ResultDelegate = MakeAwsGameKitDelegate(this, &AAwsGameKitGameSavingExamples::OnAddLocalSlotsComplete); AwsGameKitGameSaving::AddLocalSlots(saveInfoFilePaths, ResultDelegate); // GetAllSlotSyncStatuses() is called in the ResultDelegate for AddLocalSlots() // because it should be called *after* AddLocalSlots() has completed. } void AAwsGameKitGameSavingExamples::OnAddLocalSlotsComplete(const IntResult& result) { UE_LOG(LogAwsGameKit, Display, TEXT("AAwsGameKitGameSavingExamples::OnAddLocalSlotsComplete()")); if (result.Result != GameKit::GAMEKIT_SUCCESS) { // Update UI with the error message: *postInitializationStatusCode = GetResultMessage(result.Result); gameSavingInitializationStatus = InitializationStatus::FAILED; return; } // Call GetAllSlotSyncStatuses(): const auto ResultDelegate = MakeAwsGameKitDelegate(this, &AAwsGameKitGameSavingExamples::OnGetAllSlotSyncStatusesForInitializationComplete); AwsGameKitGameSaving::GetAllSlotSyncStatuses(ResultDelegate); } void AAwsGameKitGameSavingExamples::OnGetAllSlotSyncStatusesForInitializationComplete(const IntResult& result, const TArray& cachedSlots) { UE_LOG(LogAwsGameKit, Display, TEXT("AAwsGameKitGameSavingExamples::OnGetAllSlotSyncStatusesForInitializationComplete()")); if (result.Result != GameKit::GAMEKIT_SUCCESS) { // Update UI with the error message: *postInitializationStatusCode = GetResultMessage(result.Result); gameSavingInitializationStatus = InitializationStatus::FAILED; return; } gameSavingInitializationStatus = InitializationStatus::SUCCESSFUL; // Copy the cached slots: // This is an optimization for the LoadSlot() API. See OnLoadGameButtonClicked() for details. cachedSlotsCopy = FGameSavingSlots(); cachedSlotsCopy.Slots = cachedSlots; /* * Check for sync conflicts: * * In your own game, when you call GetAllSlotSyncStatuses() at the game's startup, * you would want to loop through the "cachedSlots" parameter and look for any slots with SlotSyncStatus == IN_CONFLICT. * * If any slots are in conflict, you'd likely want to present this conflict to the player and let them decide which file to keep: the local save or the cloud save. * The Blueprint Game Saving example has a UI to demonstrate this. */ UE_LOG(LogAwsGameKit, Display, TEXT("AAwsGameKitGameSavingExamples::OnGetAllSlotSyncStatusesForInitializationComplete() Game Saving library successfully initialized.")); // Call the function the user originally clicked: postInitializationCallback(); } void AAwsGameKitGameSavingExamples::CallLoginApi() { if (!IsIdentityDeployed()) { return; } // Log the user inputs: UE_LOG(LogAwsGameKit, Display, TEXT("AwsGameKitIdentity::Login() called with parameters: UserName=%s, Password=