// Copyright Epic Games, Inc. All Rights Reserved. #include "GameFeaturePluginTemplate.h" #include "AssetRegistry/ARFilter.h" #include "AssetRegistry/IAssetRegistry.h" #include "AssetToolsModule.h" #include "Dom/JsonValue.h" #include "Editor.h" #include "GameFeaturesSubsystem.h" #include "GameFeaturesSubsystemSettings.h" #include "HAL/FileManager.h" #include "Interfaces/IPluginManager.h" #include "Misc/Paths.h" #include "Subsystems/AssetEditorSubsystem.h" #define LOCTEXT_NAMESPACE "GameFeatures" FGameFeaturePluginTemplateDescription::FGameFeaturePluginTemplateDescription(FText InName, FText InDescription, FString InOnDiskPath, FString InDefaultSubfolder, FString InDefaultPluginName, TSubclassOf GameFeatureDataClassOverride, FString GameFeatureDataNameOverride, EPluginEnabledByDefault InEnabledByDefault) : FPluginTemplateDescription(InName, InDescription, InOnDiskPath, /*bCanContainContent=*/ true, EHostType::Runtime) { SortPriority = 10; bCanBePlacedInEngine = false; DefaultSubfolder = InDefaultSubfolder; DefaultPluginName = InDefaultPluginName; GameFeatureDataName = !GameFeatureDataNameOverride.IsEmpty() ? GameFeatureDataNameOverride : FString(); GameFeatureDataClass = GameFeatureDataClassOverride != nullptr ? GameFeatureDataClassOverride : TSubclassOf(UGameFeatureData::StaticClass()); PluginEnabledByDefault = InEnabledByDefault; } bool FGameFeaturePluginTemplateDescription::ValidatePathForPlugin(const FString& ProposedAbsolutePluginPath, FText& OutErrorMessage) { if (!IsRootedInGameFeaturesRoot(ProposedAbsolutePluginPath)) { OutErrorMessage = LOCTEXT("InvalidPathForGameFeaturePlugin", "Game features must be inside the Plugins/GameFeatures folder"); return false; } OutErrorMessage = FText::GetEmpty(); return true; } void FGameFeaturePluginTemplateDescription::UpdatePathWhenTemplateSelected(FString& InOutPath) { if (!IsRootedInGameFeaturesRoot(InOutPath)) { InOutPath = GetGameFeatureRoot(); } } void FGameFeaturePluginTemplateDescription::UpdatePathWhenTemplateUnselected(FString& InOutPath) { InOutPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*FPaths::ProjectPluginsDir()); FPaths::MakePlatformFilename(InOutPath); } void FGameFeaturePluginTemplateDescription::UpdatePluginNameTextWhenTemplateSelected(FText& OutPluginNameText) { OutPluginNameText = FText::FromString(DefaultPluginName); } void FGameFeaturePluginTemplateDescription::UpdatePluginNameTextWhenTemplateUnselected(FText& OutPluginNameText) { OutPluginNameText = FText::GetEmpty(); } void FGameFeaturePluginTemplateDescription::CustomizeDescriptorBeforeCreation(FPluginDescriptor& Descriptor) { Descriptor.bExplicitlyLoaded = true; Descriptor.AdditionalFieldsToWrite.FindOrAdd(TEXT("BuiltInInitialFeatureState")) = MakeShared(TEXT("Active")); Descriptor.Category = TEXT("Game Features"); // Game features should not be enabled by default if the game wants to strictly manage default settings in the target settings Descriptor.EnabledByDefault = PluginEnabledByDefault; if (Descriptor.Modules.Num() > 0) { Descriptor.Modules[0].Name = FName(*(Descriptor.Modules[0].Name.ToString() + TEXT("Runtime"))); } } void FGameFeaturePluginTemplateDescription::OnPluginCreated(TSharedPtr NewPlugin) { // If the template includes an existing game feature data, do not create a new one. TArray ObjectList; FARFilter AssetFilter; AssetFilter.ClassPaths.Add(UGameFeatureData::StaticClass()->GetClassPathName()); AssetFilter.PackagePaths.Add(FName(NewPlugin->GetMountedAssetPath())); AssetFilter.bRecursiveClasses = true; AssetFilter.bRecursivePaths = true; IAssetRegistry::GetChecked().GetAssets(AssetFilter, ObjectList); UObject* GameFeatureDataAsset = nullptr; if (ObjectList.Num() <= 0) { // Create the game feature data asset FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); FString const& AssetName = !GameFeatureDataName.IsEmpty() ? GameFeatureDataName : NewPlugin->GetName(); GameFeatureDataAsset = AssetToolsModule.Get().CreateAsset(AssetName, NewPlugin->GetMountedAssetPath(), GameFeatureDataClass, /*Factory=*/ nullptr); } else { GameFeatureDataAsset = ObjectList[0].GetAsset(); } // Activate the new game feature plugin auto AdditionalFilter = [](const FString&, const FGameFeaturePluginDetails&, FBuiltInGameFeaturePluginBehaviorOptions&) -> bool { return true; }; UGameFeaturesSubsystem::Get().LoadBuiltInGameFeaturePlugin(NewPlugin.ToSharedRef(), AdditionalFilter, FGameFeaturePluginLoadComplete::CreateLambda([GameFeatureDataAsset](const UE::GameFeatures::FResult&) { // Edit the new game feature data if (GameFeatureDataAsset != nullptr) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(GameFeatureDataAsset); } })); } FString FGameFeaturePluginTemplateDescription::GetGameFeatureRoot() const { FString Result = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*(FPaths::ProjectPluginsDir() / TEXT("GameFeatures/"))); // Append the optional subfolder if specified. if (!DefaultSubfolder.IsEmpty()) { Result /= DefaultSubfolder + TEXT("/"); } FPaths::MakePlatformFilename(Result); return Result; } bool FGameFeaturePluginTemplateDescription::IsRootedInGameFeaturesRoot(const FString& InStr) const { const FString ConvertedPath = FPaths::ConvertRelativePathToFull(FPaths::CreateStandardFilename(InStr / TEXT("test.uplugin"))); return GetDefault()->IsValidGameFeaturePlugin(ConvertedPath); } #undef LOCTEXT_NAMESPACE