diff options
Diffstat (limited to 'Plugins')
41 files changed, 2314 insertions, 0 deletions
diff --git a/Plugins/SimpleUGC/Resources/ButtonIcon_40x.png b/Plugins/SimpleUGC/Resources/ButtonIcon_40x.png Binary files differnew file mode 100644 index 0000000..2e8bbfd --- /dev/null +++ b/Plugins/SimpleUGC/Resources/ButtonIcon_40x.png diff --git a/Plugins/SimpleUGC/Resources/CreateUGC_128x.png b/Plugins/SimpleUGC/Resources/CreateUGC_128x.png Binary files differnew file mode 100644 index 0000000..5ffc3c6 --- /dev/null +++ b/Plugins/SimpleUGC/Resources/CreateUGC_128x.png diff --git a/Plugins/SimpleUGC/Resources/CreateUGC_16x.png b/Plugins/SimpleUGC/Resources/CreateUGC_16x.png Binary files differnew file mode 100644 index 0000000..f61daf0 --- /dev/null +++ b/Plugins/SimpleUGC/Resources/CreateUGC_16x.png diff --git a/Plugins/SimpleUGC/Resources/CreateUGC_48x.png b/Plugins/SimpleUGC/Resources/CreateUGC_48x.png Binary files differnew file mode 100644 index 0000000..a958056 --- /dev/null +++ b/Plugins/SimpleUGC/Resources/CreateUGC_48x.png diff --git a/Plugins/SimpleUGC/Resources/CreateUGC_64x.png b/Plugins/SimpleUGC/Resources/CreateUGC_64x.png Binary files differnew file mode 100644 index 0000000..c4278cd --- /dev/null +++ b/Plugins/SimpleUGC/Resources/CreateUGC_64x.png diff --git a/Plugins/SimpleUGC/Resources/Icon128.png b/Plugins/SimpleUGC/Resources/Icon128.png Binary files differnew file mode 100644 index 0000000..85b65e7 --- /dev/null +++ b/Plugins/SimpleUGC/Resources/Icon128.png diff --git a/Plugins/SimpleUGC/Resources/PackageUGC_128x.png b/Plugins/SimpleUGC/Resources/PackageUGC_128x.png Binary files differnew file mode 100644 index 0000000..4221113 --- /dev/null +++ b/Plugins/SimpleUGC/Resources/PackageUGC_128x.png diff --git a/Plugins/SimpleUGC/Resources/PackageUGC_16x.png b/Plugins/SimpleUGC/Resources/PackageUGC_16x.png Binary files differnew file mode 100644 index 0000000..cbe6e06 --- /dev/null +++ b/Plugins/SimpleUGC/Resources/PackageUGC_16x.png diff --git a/Plugins/SimpleUGC/Resources/PackageUGC_48x.png b/Plugins/SimpleUGC/Resources/PackageUGC_48x.png Binary files differnew file mode 100644 index 0000000..8ca1ebd --- /dev/null +++ b/Plugins/SimpleUGC/Resources/PackageUGC_48x.png diff --git a/Plugins/SimpleUGC/Resources/PackageUGC_64x.png b/Plugins/SimpleUGC/Resources/PackageUGC_64x.png Binary files differnew file mode 100644 index 0000000..0657a05 --- /dev/null +++ b/Plugins/SimpleUGC/Resources/PackageUGC_64x.png diff --git a/Plugins/SimpleUGC/SimpleUGC.uplugin b/Plugins/SimpleUGC/SimpleUGC.uplugin new file mode 100644 index 0000000..ac3cf0a --- /dev/null +++ b/Plugins/SimpleUGC/SimpleUGC.uplugin @@ -0,0 +1,34 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "SimpleUGC", + "Description": "", + "Category": "Other", + "CreatedBy": "", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "SimpleUGC", + "Type": "Runtime", + "LoadingPhase": "PreLoadingScreen" + }, + { + "Name": "SimpleUGCEditor", + "Type": "Editor", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "PluginBrowser", + "Enabled": true + } + ] +} diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Private/MakeReplaceableActorComponent.cpp b/Plugins/SimpleUGC/Source/SimpleUGC/Private/MakeReplaceableActorComponent.cpp new file mode 100644 index 0000000..3e3d638 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Private/MakeReplaceableActorComponent.cpp @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MakeReplaceableActorComponent.h" + +// Sets default values for this component's properties +UMakeReplaceableActorComponent::UMakeReplaceableActorComponent() +{ + SetAutoActivate(true); +} diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Private/ReplacementActorComponent.cpp b/Plugins/SimpleUGC/Source/SimpleUGC/Private/ReplacementActorComponent.cpp new file mode 100644 index 0000000..773cec8 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Private/ReplacementActorComponent.cpp @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ReplacementActorComponent.h" + +// Sets default values for this component's properties +UReplacementActorComponent::UReplacementActorComponent() +{ + SetAutoActivate(true); +} diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Private/SimpleUGC.cpp b/Plugins/SimpleUGC/Source/SimpleUGC/Private/SimpleUGC.cpp new file mode 100644 index 0000000..67ee730 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Private/SimpleUGC.cpp @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SimpleUGC.h" + +#define LOCTEXT_NAMESPACE "FSimpleUGCModule" +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FSimpleUGCModule, SimpleUGC) diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCBaseGameInstance.cpp b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCBaseGameInstance.cpp new file mode 100644 index 0000000..39ef343 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCBaseGameInstance.cpp @@ -0,0 +1,11 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "UGCBaseGameInstance.h" + +void UUGCBaseGameInstance::Init() +{ + // Instnatiate the registry and find mod packages + UGCRegistry = NewObject<UUGCRegistry>(this); + UGCRegistry->FindUGCPackages(); + Super::Init(); +} diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCBlueprintLibrary.cpp b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCBlueprintLibrary.cpp new file mode 100644 index 0000000..39956f9 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCBlueprintLibrary.cpp @@ -0,0 +1,13 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "UGCBlueprintLibrary.h" +#include "UGCBaseGameInstance.h" +#include "UGCRegistry.h" +#include "Kismet/GameplayStatics.h" +#include "SimpleUGC.h" + +UUGCRegistry * UUGCBlueprintLibrary::GetUGCRegistry(UObject* WorldContextObject) +{ + UUGCBaseGameInstance* GameInstance = Cast<UUGCBaseGameInstance>(UGameplayStatics::GetGameInstance(WorldContextObject)); + return (GameInstance) ? GameInstance->UGCRegistry : nullptr; +} diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp new file mode 100644 index 0000000..90680fb --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp @@ -0,0 +1,241 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "UGCRegistry.h" +#include "AssetRegistryModule.h" +#include "ARFilter.h" +#include "Interfaces/IPluginManager.h" +#include "HAL/PlatformFilemanager.h" +#include "Runtime/Json/Public/Dom/JsonObject.h" +#include "Misc/PackageName.h" +#include "Kismet/GameplayStatics.h" +#include "Misc/Paths.h" +#include "MakeReplaceableActorComponent.h" +#include "ReplacementActorComponent.h" +#include "SimpleUGC.h" + +bool UUGCRegistry::FindUGCPackages() +{ + TArray<TSharedRef<IPlugin>> EnabledPlugins = IPluginManager::Get().GetEnabledPlugins(); + for (const TSharedRef<IPlugin>& Plugin : EnabledPlugins) + { + if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Project && Plugin->GetDescriptor().Category == "UGC") + { + FUGCPackage Package; + Package.PackagePath = *Plugin->GetMountedAssetPath().LeftChop(1); + Package.EngineVersion = *Plugin->GetDescriptor().EngineVersion; + Package.Author = *Plugin->GetDescriptor().CreatedBy; + Package.Description = *Plugin->GetDescriptor().Description; + UGCPackages.Add(Package); + } + } + + return UGCPackages.Num() > 0; +} + +bool UUGCRegistry::GetAllClassesInPackage(FUGCPackage Package, TArray<UClass*> &Classes) +{ + // Load up the AssetRegistry, Filter for Blueprints + + IAssetRegistry& AssetRegistry = GetAsstRegistry(); + FARFilter ARFilter; + TArray<FAssetData> AssetList; + ARFilter.bRecursivePaths = true; + ARFilter.bIncludeOnlyOnDiskAssets = true; + ARFilter.bRecursiveClasses = true; + ARFilter.ClassNames.Add(UBlueprint::StaticClass()->GetFName()); + ARFilter.PackagePaths.Add(FName(*Package.PackagePath)); + AssetRegistry.GetAssets(ARFilter, AssetList); + + for (FAssetData Asset : AssetList) + { + FAssetDataTagMapSharedView::FFindTagResult GeneratedClassResult = Asset.TagsAndValues.FindTag("GeneratedClass"); + if (GeneratedClassResult.IsSet()) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*GeneratedClassResult.GetValue()); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + if (AssetClass) + { + Classes.Add(AssetClass); + } + } + } + + return Classes.Num() > 0; +} + +bool UUGCRegistry::GetMapsInPackage(FUGCPackage Package, TArray<FName> &Maps) +{ + // Load up the AssetRegistry, FIlter for Maps + IAssetRegistry& AssetRegistry = GetAsstRegistry(); + FARFilter ARFilter; + TArray<FAssetData> AssetList; + ARFilter.bRecursivePaths = true; + ARFilter.bIncludeOnlyOnDiskAssets = true; + ARFilter.bRecursiveClasses = true; + ARFilter.ClassNames.Add(UWorld::StaticClass()->GetFName()); + ARFilter.PackagePaths.Add(FName(*Package.PackagePath)); + AssetRegistry.GetAssets(ARFilter, AssetList); + + for (FAssetData Asset : AssetList) + { + Maps.Add(Asset.AssetName); + } + + return Maps.Num() > 0; +} + +bool UUGCRegistry::GetActorClassesWithReplacementActorComponentsInPackage(FUGCPackage Package, TArray<TSubclassOf<AActor>> &ActorClasses) +{ + // Load up the AssetRegistry, Filter for Blueprints + IAssetRegistry& AssetRegistry = GetAsstRegistry(); + FARFilter ARFilter; + TArray<FAssetData> AssetList; + ARFilter.bRecursivePaths = true; + ARFilter.bIncludeOnlyOnDiskAssets = true; + ARFilter.bRecursiveClasses = true; + ARFilter.ClassNames.Add(UBlueprint::StaticClass()->GetFName()); + ARFilter.PackagePaths.Add(FName(*Package.PackagePath)); + AssetRegistry.GetAssets(ARFilter, AssetList); + + for (FAssetData Asset : AssetList) + { + FAssetDataTagMapSharedView::FFindTagResult GeneratedClassResult = Asset.TagsAndValues.FindTag("GeneratedClass"); + if (GeneratedClassResult.IsSet()) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*GeneratedClassResult.GetValue()); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + if (AssetClass) + { + + if (UBlueprintGeneratedClass* BlueprintModClass = Cast<UBlueprintGeneratedClass>(AssetClass)) + { + // Not All Blueprint Classes have a SCS, so ensure that it has one before we get at dem nodes. + if (BlueprintModClass->SimpleConstructionScript != nullptr) + { + // Find UGCOverrideComponent + for (USCS_Node* Node : BlueprintModClass->SimpleConstructionScript->GetRootNodes()) + { + // If we found it, add to Classes + if (UReplacementActorComponent * ReplacementActorComponent = Cast<UReplacementActorComponent>(Node->ComponentTemplate)) + { + ActorClasses.Add(AssetClass); + } + } + } + } + } + } + } + return ActorClasses.Num() > 0; +} +bool UUGCRegistry::ApplyAllOverridesInPackage(FUGCPackage Package) +{ + bool bSuccess = false; + // Load up the AssetRegistry + IAssetRegistry& AssetRegistry = GetAsstRegistry(); + TArray<FAssetData> AssetList; + AssetRegistry.GetAssetsByPath(FName(*Package.PackagePath), AssetList, true); + + for (FAssetData Asset : AssetList) + { + FAssetDataTagMapSharedView::FFindTagResult GeneratedClassResult = Asset.TagsAndValues.FindTag("GeneratedClass"); + if (GeneratedClassResult.IsSet()) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*GeneratedClassResult.GetValue()); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Try to apply an override + if (AssetClass) + { + bSuccess = ApplyOverridesForActorClass(AssetClass) || bSuccess; + } + } + } + return bSuccess; +} + +bool UUGCRegistry::ApplyOverridesForActorClass(TSubclassOf<AActor> ActorClass) +{ + bool bSuccess = false; + + // Check Blueprints + if (UBlueprintGeneratedClass* BlueprintActorClass = Cast<UBlueprintGeneratedClass>(ActorClass)) + { + // Null check on SCS + if (BlueprintActorClass->SimpleConstructionScript != nullptr) + { + // Find UGCOverrideComponent so we know what to override + for (USCS_Node* Node : BlueprintActorClass->SimpleConstructionScript->GetRootNodes()) + { + // If we found it, + if (UReplacementActorComponent* ReplacementActorComponent = Cast<UReplacementActorComponent>(Node->ComponentTemplate)) + { + // Check The Classes To Override + for (UClass* ActorClassToReplace : ReplacementActorComponent->ActorClassesToReplace) + { + // Check Blueprint First.. + if (UBlueprintGeneratedClass* BlueprintClassToReplace = Cast< UBlueprintGeneratedClass>(ActorClassToReplace)) + { + if (BlueprintClassToReplace->SimpleConstructionScript != nullptr) + { + // Null check on SCS + for (USCS_Node* ActorClassToReplaceNode : BlueprintClassToReplace->SimpleConstructionScript->GetRootNodes()) + { + // Find UGCOverrideableComponent so we can ensure compatibility + if (UMakeReplaceableActorComponent* MakeReplaceableActorComponent = Cast<UMakeReplaceableActorComponent>(ActorClassToReplaceNode->ComponentTemplate)) + { + // Ensure compatibility + if (ActorClass->IsChildOf(MakeReplaceableActorComponent->CompatibleReplacement)) + { + // Register + RegisterOverrideForClass(ActorClassToReplace, ActorClass); + bSuccess = true; + } + } + } + } + } + // If doing code mods, implement check for UUGCOverrideableComponent on non BP classes here. + // else{} + } + } + } + } + } + // If doing code mods, implement check for UUGCOverrideComponent on non-BP classes here. + // else{} + + return bSuccess; +} + +void UUGCRegistry::RegisterOverrideForClass(TSubclassOf<AActor> ClassToOverride, TSubclassOf<AActor> OverrideClass) +{ + RegisteredOverrides.Emplace(ClassToOverride, OverrideClass); +} + +void UUGCRegistry::ClearOverrideForClass(TSubclassOf<AActor> ActorClass) +{ + RegisteredOverrides.Remove(ActorClass); +} + +TSubclassOf<AActor> UUGCRegistry::GetOverrideForActorClass(TSubclassOf<AActor> ActorClass) +{ + if (RegisteredOverrides.Contains(ActorClass)) + { + return *RegisteredOverrides.Find(ActorClass); + } + return ActorClass; +} + +IAssetRegistry& UUGCRegistry::GetAsstRegistry() +{ + if (!CachedAssetRegistryModule) + { + CachedAssetRegistryModule = &FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")); + } + + check(CachedAssetRegistryModule); + return CachedAssetRegistryModule->Get(); +}
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp~RF2b8431a.TMP b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp~RF2b8431a.TMP new file mode 100644 index 0000000..ab0d98e --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp~RF2b8431a.TMP @@ -0,0 +1,262 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "UGCRegistry.h" +#include "UGCKit.h" + + + +bool UUGCRegistry::FindUGCPackages() +{ + // Now let's find all new root paths in the asset registry. + TArray<FString> RootPaths; + FPackageName::QueryRootContentPaths(RootPaths); + + // Remove any plugins your game is shipping with, as well as engine and game roots. We only care about NEW paths. + RootPaths.Remove(TEXT("/Engine/")); + RootPaths.Remove(TEXT("/Game/")); + RootPaths.Remove(TEXT("/Paper2D/")); + RootPaths.Remove(TEXT("/UGCKit/")); + + // Remove any other plugins you've defined in Blueprints + for (FName Package : PackageBlacklist) + { + RootPaths.Remove(Package.ToString()); + } + + if (RootPaths.Num() <= 0) + { + return false; + } + + for (FString Path : RootPaths) + { + UGCPackages.Add(FName(*Path.LeftChop(1))); + } + + return true; + +} + +bool UUGCRegistry::GetAssetsFromPackage(FName Package, TArray<FAssetData> &Assets, bool bOnlyRegisteredModTypes) +{ + // Load up the AssetRegistry + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")); + IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + + TArray<FAssetData> AssetList; + AssetRegistry.GetAssetsByPath(Package, AssetList, true); + if (AssetList.Num() == 0) + { + return false; + } + + for (FAssetData Asset : AssetList) + { + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass) + { + if (bOnlyRegisteredModTypes) + { + for (UClass * ModClass : RegisteredModTypes) + { + if (AssetClass->IsChildOf(ModClass)) + { + Assets.Add(Asset); + break; + } + } + } + else + { + Assets.Add(Asset); + } + } + } + } + return Assets.Num() > 0; + +} + +void UUGCRegistry::ShowAllPossibleOverridesForClass(UClass * ModClass, TArray<UClass*> &Overrides) +{ + TArray<FAssetData> PackageAssets; + for (FName Package : UGCPackages) + { + GetAssetsFromPackage(Package, PackageAssets, true); + } + + for (FAssetData Asset : PackageAssets) + { + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass && AssetClass->IsChildOf(ModClass)) + { + Overrides.Add(AssetClass); + } + } + } +} + +UClass * UUGCRegistry::GetClassForAssetData(FAssetData Asset) +{ + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass) + { + return AssetClass; + } + } + return nullptr; +} + +bool UUGCRegistry::ApplyAllModsInPackage(FName Package) +{ + + bool success = false; + // Load up the AssetRegistry + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")); + IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + + TArray<FAssetData> AssetList; + AssetRegistry.GetAssetsByPath(Package, AssetList, true); + if (AssetList.Num() == 0) + { + return success; + } + + for (FAssetData Asset : AssetList) + { + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass) + { + for (UClass * ModClass : RegisteredModTypes) + { + if (AssetClass->IsChildOf(ModClass)) + { + success = ApplyOverridesForModClass(AssetClass) || success; + } + } + } + } + } + return success; +} + +bool UUGCRegistry::ApplyOverridesForModClass(UClass * ModClass) +{ + // Check Actors + AUGCBaseActor *UGCActor = Cast<AUGCBaseActor>(ModClass->GetDefaultObject()); + if (UGCActor != nullptr) + { + for (UClass * ClassToOverride : UGCActor->ClassesToOverride) + { + if (ClassToOverride->IsChildOf(AUGCBaseActor::StaticClass())) + { + AssignOverrideForBaseClass(ClassToOverride, UGCActor->GetClass()); + } + } + return true; + } + + // Check Pawns & Characters + AUGCBasePawn *UGCPawn = Cast<AUGCBasePawn>(ModClass->GetDefaultObject()); + if (UGCPawn != nullptr) + { + for (UClass * ClassToOverride : UGCPawn->ClassesToOverride) + { + if (ClassToOverride->IsChildOf(AUGCBasePawn::StaticClass()) || + ClassToOverride->IsChildOf(AUGCBaseCharacter::StaticClass())) + { + AssignOverrideForBaseClass(ClassToOverride, UGCPawn->GetClass()); + } + } + return true; + } + + // Check Characters + AUGCBaseCharacter *UGCCharacter = Cast<AUGCBaseCharacter>(ModClass->GetDefaultObject()); + if (UGCCharacter != nullptr) + { + for (UClass * ClassToOverride : UGCCharacter->ClassesToOverride) + { + if (ClassToOverride->IsChildOf(AUGCBasePawn::StaticClass()) || + ClassToOverride->IsChildOf(AUGCBaseCharacter::StaticClass())) + { + AssignOverrideForBaseClass(ClassToOverride, UGCCharacter->GetClass()); + } + } + return true; + } + return false; +} + +void UUGCRegistry::AssignOverrideForBaseClass(UClass * OriginClass, UClass * OverrideClass) +{ + // If already registered, Update Registration + for (FModOverridePairing &Pairing : RegisteredOverrides) + { + if (Pairing.Origin == OriginClass) + { + Pairing.Override = OverrideClass; + + return; + } + } + + // If not, make new + FModOverridePairing NewPairing; + NewPairing.Origin = OriginClass; + NewPairing.Override = OverrideClass; + RegisteredOverrides.Add(NewPairing); +} + +bool UUGCRegistry::ClearOverrideForClass(UClass *OriginClass) +{ + // Find Pairing, remove From Array + for (int i = 0; i < RegisteredOverrides.Num(); i++) + { + FModOverridePairing & Pairing = RegisteredOverrides[i]; + if (Pairing.Origin == OriginClass) + { + RegisteredOverrides.RemoveAt(i, 1, true); + return true; + } + } + + // Returns False if there was no pairing to begin with + return false; +} + +UClass * UUGCRegistry::GetOverrideForClass(UClass *OriginClass) +{ + for (FModOverridePairing &Pairing : RegisteredOverrides) + { + if (Pairing.Origin == OriginClass) + { + return Pairing.Override; + } + } + return OriginClass; +}
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp~RF2bf9f51.TMP b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp~RF2bf9f51.TMP new file mode 100644 index 0000000..a0c74da --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Private/UGCRegistry.cpp~RF2bf9f51.TMP @@ -0,0 +1,319 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "UGCRegistry.h" +#include "UGCKit.h" + + + +bool UUGCRegistry::FindUGCPackages() +{ + // Now let's find all new root paths in the asset registry. + TArray<FString> RootPaths; + FPackageName::QueryRootContentPaths(RootPaths); + + // Remove any plugins your game is shipping with, as well as engine and game roots. We only care about NEW paths. + RootPaths.Remove(TEXT("/Engine/")); + RootPaths.Remove(TEXT("/Game/")); + RootPaths.Remove(TEXT("/Paper2D/")); + RootPaths.Remove(TEXT("/UGCKit/")); + + // Remove any other plugins you've defined in Blueprints + for (FName Package : PackageBlacklist) + { + RootPaths.Remove(Package.ToString()); + } + + if (RootPaths.Num() <= 0) + { + return false; + } + + for (FString Path : RootPaths) + { + UGCPackages.Add(FName(*Path.LeftChop(1))); + } + + return true; + +} + +bool UUGCRegistry::GetAssetsFromPackage(FName Package, TArray<FAssetData> &Assets, bool bOnlyRegisteredModTypes) +{ + // Load up the AssetRegistry + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")); + IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + + TArray<FAssetData> AssetList; + AssetRegistry.GetAssetsByPath(Package, AssetList, true); + if (AssetList.Num() == 0) + { + return false; + } + + for (FAssetData Asset : AssetList) + { + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass) + { + if (bOnlyRegisteredModTypes) + { + for (UClass * ModClass : RegisteredModTypes) + { + if (AssetClass->IsChildOf(ModClass)) + { + Assets.Add(Asset); + break; + } + } + } + else + { + Assets.Add(Asset); + } + } + } + } + return Assets.Num() > 0; +} + +bool UUGCRegistry::GetMapsFromPackage(FName Package, TArray<FName> &Maps) +{ + // Load up the AssetRegistry + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")); + IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + + FARFilter ARFilter; + TArray<FAssetData> AssetList; + // Add any old names to the list in case things haven't been resaved + ARFilter.ClassNames.Append(OldNames); + ARFilter.bRecursivePaths = true; + ARFilter.bIncludeOnlyOnDiskAssets = true; + ARFilter.bRecursiveClasses = true; + + //////////////////////////////////////// + // Blueprint and CPP classes of our archetypes. + ARFilter.ClassNames.Add(UWorld::StaticClass()->GetFName()); + ARFilter.PackagePaths.Add(Package); + AssetRegistry.GetAssets(ARFilter, AssetList); + AssetRegistry.GetAssetsByPath(Package, AssetList, true); + if (AssetList.Num() == 0) + { + return false; + } + + for (FAssetData Asset : AssetList) + { + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass) + { + if (bOnlyRegisteredModTypes) + { + for (UClass * ModClass : RegisteredModTypes) + { + if (AssetClass->IsChildOf(ModClass)) + { + Assets.Add(Asset); + break; + } + } + } + else + { + Assets.Add(Asset); + } + } + } + } + return Assets.Num() > 0; +} + +void UUGCRegistry::ShowAllPossibleOverridesForClass(UClass * ModClass, TArray<UClass*> &Overrides) +{ + TArray<FAssetData> PackageAssets; + for (FName Package : UGCPackages) + { + GetAssetsFromPackage(Package, PackageAssets, true); + } + + for (FAssetData Asset : PackageAssets) + { + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass && AssetClass->IsChildOf(ModClass)) + { + Overrides.Add(AssetClass); + } + } + } +} + +UClass * UUGCRegistry::GetClassForAssetData(FAssetData Asset) +{ + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass) + { + return AssetClass; + } + } + return nullptr; +} + +bool UUGCRegistry::ApplyAllModsInPackage(FName Package) +{ + + bool success = false; + // Load up the AssetRegistry + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")); + IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + + TArray<FAssetData> AssetList; + AssetRegistry.GetAssetsByPath(Package, AssetList, true); + if (AssetList.Num() == 0) + { + return success; + } + + for (FAssetData Asset : AssetList) + { + const FString* ClassTextPath = Asset.TagsAndValues.Find("GeneratedClass"); + if (ClassTextPath != NULL) + { + FString ClassPath = FPackageName::ExportTextPathToObjectPath(*ClassTextPath); + UClass* AssetClass = LoadObject<UClass>(NULL, *ClassPath); + + // Check if it's a mod type + if (AssetClass) + { + for (UClass * ModClass : RegisteredModTypes) + { + if (AssetClass->IsChildOf(ModClass)) + { + success = ApplyOverridesForModClass(AssetClass) || success; + } + } + } + } + } + return success; +} + +bool UUGCRegistry::ApplyOverridesForModClass(UClass * ModClass) +{ + // Check Actors + AUGCBaseActor *UGCActor = Cast<AUGCBaseActor>(ModClass->GetDefaultObject()); + if (UGCActor != nullptr) + { + for (UClass * ClassToOverride : UGCActor->ClassesToOverride) + { + if (ClassToOverride->IsChildOf(AUGCBaseActor::StaticClass())) + { + AssignOverrideForBaseClass(ClassToOverride, UGCActor->GetClass()); + } + } + return true; + } + + // Check Pawns + AUGCBasePawn *UGCPawn = Cast<AUGCBasePawn>(ModClass->GetDefaultObject()); + if (UGCPawn != nullptr) + { + for (UClass * ClassToOverride : UGCPawn->ClassesToOverride) + { + // Character is a subclass of Pawn, so we're looking for either + if (ClassToOverride->IsChildOf(AUGCBasePawn::StaticClass()) || + ClassToOverride->IsChildOf(AUGCBaseCharacter::StaticClass())) + { + AssignOverrideForBaseClass(ClassToOverride, UGCPawn->GetClass()); + } + } + return true; + } + + // Check Characters + AUGCBaseCharacter *UGCCharacter = Cast<AUGCBaseCharacter>(ModClass->GetDefaultObject()); + if (UGCCharacter != nullptr) + { + for (UClass * ClassToOverride : UGCCharacter->ClassesToOverride) + { + // Pawn is a superclass of Character, so we're looking for either + if (ClassToOverride->IsChildOf(AUGCBasePawn::StaticClass()) || + ClassToOverride->IsChildOf(AUGCBaseCharacter::StaticClass())) + { + AssignOverrideForBaseClass(ClassToOverride, UGCCharacter->GetClass()); + } + } + return true; + } + return false; +} + +void UUGCRegistry::AssignOverrideForBaseClass(UClass * OriginClass, UClass * OverrideClass) +{ + // If already registered, Update Registration + for (FModOverridePairing &Pairing : RegisteredOverrides) + { + if (Pairing.Origin == OriginClass) + { + Pairing.Override = OverrideClass; + return; + } + } + + // If not, make new + FModOverridePairing NewPairing; + NewPairing.Origin = OriginClass; + NewPairing.Override = OverrideClass; + RegisteredOverrides.Add(NewPairing); +} + +bool UUGCRegistry::ClearOverrideForClass(UClass *OriginClass) +{ + // Find Pairing, remove From Array + for (int i = 0; i < RegisteredOverrides.Num(); i++) + { + FModOverridePairing & Pairing = RegisteredOverrides[i]; + if (Pairing.Origin == OriginClass) + { + RegisteredOverrides.RemoveAt(i, 1, true); + return true; + } + } + + // Returns False if there was no pairing to begin with + return false; +} + +UClass * UUGCRegistry::GetOverrideForClass(UClass *OriginClass) +{ + for (FModOverridePairing &Pairing : RegisteredOverrides) + { + if (Pairing.Origin == OriginClass) + { + return Pairing.Override; + } + } + return OriginClass; +}
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Public/MakeReplaceableActorComponent.h b/Plugins/SimpleUGC/Source/SimpleUGC/Public/MakeReplaceableActorComponent.h new file mode 100644 index 0000000..4cc7289 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Public/MakeReplaceableActorComponent.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "MakeReplaceableActorComponent.generated.h" + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class SIMPLEUGC_API UMakeReplaceableActorComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + UMakeReplaceableActorComponent(); + + // Only Classes of these Types can override this Actor. This is typically the type of actor you've places this component on or a safe superclass shared with an Override class. + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category ="SimpleUGC | Actor Replacement") + TSubclassOf<AActor> CompatibleReplacement; + +}; diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Public/ReplacementActorComponent.h b/Plugins/SimpleUGC/Source/SimpleUGC/Public/ReplacementActorComponent.h new file mode 100644 index 0000000..f35429c --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Public/ReplacementActorComponent.h @@ -0,0 +1,21 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "ReplacementActorComponent.generated.h" + + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class SIMPLEUGC_API UReplacementActorComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UReplacementActorComponent(); + // Add Classes you want to override here. Note: Classes added to this list MUST have a UMakeReplaceableActorComponent and This class's type be included in the component's ValidOverrideTypes + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "SimpleUGC | Actor Replacement") + TArray<TSubclassOf<AActor>> ActorClassesToReplace; + +}; diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Public/SimpleUGC.h b/Plugins/SimpleUGC/Source/SimpleUGC/Public/SimpleUGC.h new file mode 100644 index 0000000..f89e596 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Public/SimpleUGC.h @@ -0,0 +1,10 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules\ModuleManager.h" + +class FSimpleUGCModule : public IModuleInterface +{ +public: +}; diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCBaseGameInstance.h b/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCBaseGameInstance.h new file mode 100644 index 0000000..121886f --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCBaseGameInstance.h @@ -0,0 +1,25 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/GameInstance.h" +#include "UGCRegistry.h" +#include "UGCBaseGameInstance.generated.h" + +/** + * + */ +UCLASS(BlueprintType) +class SIMPLEUGC_API UUGCBaseGameInstance : public UGameInstance +{ + GENERATED_BODY() + + public: + virtual void Init() override; + + // The Registry that holds information about UGC and assigned class overrides + UPROPERTY(BlueprintReadOnly, Category = "SimpleUGC") + UUGCRegistry* UGCRegistry; + +}; diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCBlueprintLibrary.h b/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCBlueprintLibrary.h new file mode 100644 index 0000000..a0ed67c --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCBlueprintLibrary.h @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" +#include "UGCRegistry.h" +#include "UGCBlueprintLibrary.generated.h" + + +UCLASS() +class SIMPLEUGC_API UUGCBlueprintLibrary : public UBlueprintFunctionLibrary +{ + + GENERATED_BODY() +public: + + // Gets the UGC Registry found in the GameInstance + UFUNCTION(BlueprintPure, Category = "SimpleUGC", meta = (WorldContext = "WorldContextObject")) + static UUGCRegistry * GetUGCRegistry(UObject* WorldContextObject); +}; diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCRegistry.h b/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCRegistry.h new file mode 100644 index 0000000..a2dfeea --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCRegistry.h @@ -0,0 +1,98 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetRegistryModule.h" +#include "Engine.h" +#include "SimpleUGC.h" +#include "Engine/World.h" +#include "Engine/BlendableInterface.h" +#include "UGCRegistry.generated.h" + +USTRUCT(BlueprintType) +struct FUGCPackage +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, Category = "SimpleUGC") + FString PackagePath; + + UPROPERTY(BlueprintReadOnly, Category = "SimpleUGC") + FString EngineVersion; + + UPROPERTY(BlueprintReadOnly, Category = "SimpleUGC") + FString Author; + + UPROPERTY(BlueprintReadOnly, Category = "SimpleUGC") + FString Description; + + FUGCPackage() + { + PackagePath = ""; + EngineVersion = ""; + Author = ""; + Description = ""; + } +}; + +UCLASS(BlueprintType) +class SIMPLEUGC_API UUGCRegistry : public UObject +{ + GENERATED_BODY() +public: + + // This is our list of UGC packages. Populated by FindUGCPackages() + UPROPERTY(BlueprintReadOnly, Category = "SimpleUGC") + TArray<FUGCPackage> UGCPackages; + + // A pairing of Origins and Overrides. This is what the gameplay logic references when loading an effective class + UPROPERTY(BlueprintReadOnly, Category = "SimpleUGC|Actor Replacement") + TMap<TSubclassOf<AActor> /*Origin*/, TSubclassOf<AActor> /*Override*/> RegisteredOverrides; + + // This populates UGCPackages based on what is found in UGC plugin files. If you're mounting new /Plugin paks at runtime. Expose this to Blueprints. + UFUNCTION(Blueprintcallable) + bool FindUGCPackages(); + + // General DLC Asset Access. Create Similar Methods For Getting Materials, Textures, etc. + + // Returns All Classes in a UGC package. + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SimpleUGC") + bool GetAllClassesInPackage(FUGCPackage Package, TArray<UClass*> &Classes); + + // Returns All Maps in a UGC package. + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SimpleUGC") + bool GetMapsInPackage(FUGCPackage Package, TArray<FName> &Maps); + + // Actor Replacement Specific Calls + + // Returns All Actor Classes In Package that have the ability to replace a a base class. Use this when you want to register only specific class overrides from a UGC package. + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SimpleUGC|Actor Replacement") + bool GetActorClassesWithReplacementActorComponentsInPackage(FUGCPackage Package, TArray<TSubclassOf<AActor>> &ActorClasses); + + // Applies entire package of Actor Replacements. This is common for applying an entire class-based "Mod." + UFUNCTION(BlueprintCallable, Category = "SimpleUGC|Actor Replacement") + bool ApplyAllOverridesInPackage(FUGCPackage Package); + + // Applies an override for a specific Class. Find valid classes to use here by calling GetActorClassesWithReplacementActorComponentsInPackage + UFUNCTION(BlueprintCallable, Category = "SimpleUGC|Actor Replacement") + bool ApplyOverridesForActorClass(TSubclassOf<AActor> ActorClass); + + // A manual override assignment. Not reccommended for mod packages, but useful for big UGC drops (100 UGuns pack, etc) + UFUNCTION(BlueprintCallable, Category = "SimpleUGC|Actor Replacement") + void RegisterOverrideForClass(TSubclassOf<AActor> ClassToOverride, TSubclassOf<AActor> OverrideClass); + + // Used to clear the override from the registry. To clear all, loop through RegisterredOverrides, break the struct and run the Origin into this function. + UFUNCTION(BlueprintCallable, Category = "SimpleUGC|Actor Replacement") + void ClearOverrideForClass(TSubclassOf<AActor> ActorClass); + + // Used in gameplay to look up what class is actually supposed to be spawned + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SimpleUGC|Actor Replacement") + TSubclassOf<AActor> GetOverrideForActorClass(TSubclassOf<AActor> ActorClass); + + +private: + FAssetRegistryModule* CachedAssetRegistryModule; + IAssetRegistry& GetAsstRegistry(); + +}; diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCRegistry.h~RF2b28773.TMP b/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCRegistry.h~RF2b28773.TMP new file mode 100644 index 0000000..17cdab8 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/Public/UGCRegistry.h~RF2b28773.TMP @@ -0,0 +1,106 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UGCKit.h" +#include "Public/AssetRegistryModule.h" +#include "Public/ARFilter.h" +#include "UGCRegistry.generated.h" + +/** + * + */ + +USTRUCT(BlueprintType) +struct FModOverridePairing +{ + GENERATED_BODY() + UPROPERTY(BlueprintReadOnly, Category = "UGCKit|Modding") + UClass* Origin; + + UPROPERTY(BlueprintReadOnly, Category = "UGCKit|Modding") + UClass* Override; + + FModOverridePairing() + { + Origin = nullptr; + Override = nullptr; + } +}; + + +UCLASS(BlueprintType) +class UGCKIT_API UUGCRegistry : public UObject +{ + GENERATED_BODY() +public: + + // Init + UUGCRegistry() + { + RegisteredModTypes.Empty(); + RegisteredModTypes.Add(AUGCBaseActor::StaticClass()); + RegisteredModTypes.Add(AUGCBaseCharacter::StaticClass()); + RegisteredModTypes.Add(AUGCBaseGameMode::StaticClass()); + RegisteredModTypes.Add(AUGCBasePawn::StaticClass()); + RegisteredModTypes.Add(AUGCBasePlayerController::StaticClass()); + RegisteredModTypes.Add(AUGCBaseWorldSettings::StaticClass()); + + } + + // PROPERTIES + UPROPERTY(BlueprintReadOnly, Category = "UGCKit") + TArray<FName> UGCPackages; + + UPROPERTY(BlueprintReadWrite, Category = "UGCKit") + TArray<FName> PackageBlacklist; + + UPROPERTY(BlueprintReadOnly, Category = "UGCKit|Modding") + TArray <UClass*> RegisteredModTypes; + + UPROPERTY(BlueprintReadOnly, Category = "UGCKit|Modding") + TArray <UClass*> PossibleModOverrides; + + UPROPERTY(BlueprintReadOnly, Category = "UGCKit|Modding") + TArray <FModOverridePairing> RegisteredOverrides; + + // This populates UGCPackages based on what is found in mounted pak files. If you're mounting new /Plugin paks at runtime, call this again to update UGCPacakges + UFUNCTION(BlueprintCallable, Category = "UGCKit") + bool FindUGCPackages(); + + // For querying assets in a specific package. + UFUNCTION(BlueprintCallable, Category = "UGCKit") + bool GetAssetsFromPackage(FName Package, TArray<FAssetData> &Assets, bool bOnlyRegisteredModTypes); + + // Helper for Blueprints to use cached AssetData info to get an actual UClass. Mainly used in conjunction with ApplyOverrideForModClass + UFUNCTION(BlueprintCallable, Category = "UGCKit") + UClass * GetClassForAssetData(FAssetData Asset); + + // Applies entire package of mods + UFUNCTION(BlueprintCallable, Category = "UGCKit|Modding") + bool ApplyAllModsInPackage(FName Package); + + // Assigns overrides for just one mod class. + UFUNCTION(BlueprintCallable, Category = "UGCKit|Modding") + bool ApplyOverridesForModClass(UClass * ModClass); + + // A manual override assignment. Not reccommended for mod packages, but useful for bug UGC drops (100 gun pack, etc) + UFUNCTION(BlueprintCallable, Category = "UGCKit|Modding") + void AssignOverrideForBaseClass(UClass * OriginClass, UClass * OverrideClass); + + // Usually used with the above, this looks at all UGC Packages and provides a list of possible overrides (Give me all new guns) + UFUNCTION(BlueprintCallable, Category = "UGCKit|Modding") + void ShowAllPossibleOverridesForClass(UClass * ModClass, TArray<UClass*> &Overrides); + + // Used to clear the override from the registry. To clear all, loop through RegisterredOverrides, break the struct and run the Origin into this function. + UFUNCTION(BlueprintCallable, Category = "UGCKit|Modding") + bool ClearOverrideForClass(UClass *OriginClass); + + // Used in gameplay to look up what class is actually supposed to be spawned + UFUNCTION(BlueprintCallable, Category = "UGCKit|Modding") + UClass * GetOverrideForClass(UClass *OriginClass); + + + +}; diff --git a/Plugins/SimpleUGC/Source/SimpleUGC/SimpleUGC.Build.cs b/Plugins/SimpleUGC/Source/SimpleUGC/SimpleUGC.Build.cs new file mode 100644 index 0000000..60621c3 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGC/SimpleUGC.Build.cs @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class SimpleUGC : ModuleRules +{ + public SimpleUGC(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "AssetRegistry" + + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "AssetRegistry", + "Projects", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCCreator.cpp b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCCreator.cpp new file mode 100644 index 0000000..d7403a9 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCCreator.cpp @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +//#include "SimpleUGCEditorPrivatePCH.h" +#include "SimpleUGCCreator.h" + +#include "SimpleUGCPluginWizardDefinition.h" +#include "Widgets/Docking/SDockTab.h" + +// This depends on the Plugin Browser module to work correctly... +#include "IPluginBrowser.h" + + + +#define LOCTEXT_NAMESPACE "FSimpleUGCCreator" + +const FName FSimpleUGCCreator::SimpleUGCEditorPluginCreatorName("SimpleUGCPluginCreator"); + +FSimpleUGCCreator::FSimpleUGCCreator() +{ + RegisterTabSpawner(); +} + +FSimpleUGCCreator::~FSimpleUGCCreator() +{ + UnregisterTabSpawner(); +} + +void FSimpleUGCCreator::OpenNewPluginWizard(bool bSuppressErrors) const +{ + if (IPluginBrowser::IsAvailable()) + { + FGlobalTabmanager::Get()->InvokeTab(SimpleUGCEditorPluginCreatorName); + } + else if (!bSuppressErrors) + { + FMessageDialog::Open(EAppMsgType::Ok, + LOCTEXT("PluginBrowserDisabled", "Creating a game mod requires the use of the Plugin Browser, but it is currently disabled.")); + } +} + +void FSimpleUGCCreator::RegisterTabSpawner() +{ + FTabSpawnerEntry& Spawner = FGlobalTabmanager::Get()->RegisterNomadTabSpawner(SimpleUGCEditorPluginCreatorName, + FOnSpawnTab::CreateRaw(this, &FSimpleUGCCreator::HandleSpawnPluginTab)); + + // Set a default size for this tab + FVector2D DefaultSize(800.0f, 500.0f); + FTabManager::RegisterDefaultTabWindowSize(SimpleUGCEditorPluginCreatorName, DefaultSize); + + Spawner.SetDisplayName(LOCTEXT("NewUGCTabHeader", "Create New UGC Package")); + Spawner.SetMenuType(ETabSpawnerMenuType::Hidden); +} + +void FSimpleUGCCreator::UnregisterTabSpawner() +{ + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(SimpleUGCEditorPluginCreatorName); +} + +TSharedRef<SDockTab> FSimpleUGCCreator::HandleSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs) +{ + check(IPluginBrowser::IsAvailable()); + return IPluginBrowser::Get().SpawnPluginCreatorTab(SpawnTabArgs, MakeShared<FSimpleUGCPluginWizardDefinition>()); +} + +#undef LOCTEXT_NAMESPACE
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditor.cpp b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditor.cpp new file mode 100644 index 0000000..ce43dde --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditor.cpp @@ -0,0 +1,130 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SimpleUGCEditor.h" +#include "SimpleUGCEditorStyle.h" +#include "SimpleUGCEditorCommands.h" +#include "SimpleUGCCreator.h" +#include "Misc/MessageDialog.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" + +#include "LevelEditor.h" + +static const FName SimpleUGCEditorTabName("SimpleUGCEditor"); + +#define LOCTEXT_NAMESPACE "FSimpleUGCEditorModule" + +void FSimpleUGCEditorModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + + UGCCreator = MakeShared<FSimpleUGCCreator>(); + UGCPackager = MakeShared<FSimpleUGCPackager>(); + + FSimpleUGCEditorStyle::Initialize(); + FSimpleUGCEditorStyle::ReloadTextures(); + + FSimpleUGCEditorCommands::Register(); + + PluginCommands = MakeShareable(new FUICommandList); + + PluginCommands->MapAction( + FSimpleUGCEditorCommands::Get().CreateUGCAction, + FExecuteAction::CreateRaw(this, &FSimpleUGCEditorModule::CreateUGCButtonClicked), + FCanExecuteAction() + ); + + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor"); + // Add commands + { + FName MenuSection = "FileProject"; + FName ToolbarSection = "Misc"; + + // Add creator button to the menu + { + TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender()); + MenuExtender->AddMenuExtension(MenuSection, EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FSimpleUGCEditorModule::AddUGCCreatorMenuExtension)); + + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); + } + + // Add creator button to the toolbar + { + TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension(ToolbarSection, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FSimpleUGCEditorModule::AddUGCCreatorToolbarExtension)); + + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + } + + // Add packager button to the menu + { + TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender()); + MenuExtender->AddMenuExtension(MenuSection, EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FSimpleUGCEditorModule::AddUGCPackagerMenuExtension)); + + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); + } + + // Add packager button to the toolbar + { + TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension(ToolbarSection, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FSimpleUGCEditorModule::AddUGCPackagerToolbarExtension)); + + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + } + } +} + +void FSimpleUGCEditorModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + FSimpleUGCEditorStyle::Shutdown(); + + FSimpleUGCEditorCommands::Unregister(); + +} + +void FSimpleUGCEditorModule::CreateUGCButtonClicked() +{ + if (UGCCreator.IsValid()) + { + UGCCreator->OpenNewPluginWizard(); + } +} + +void FSimpleUGCEditorModule::AddUGCCreatorMenuExtension(FMenuBuilder& Builder) +{ + Builder.AddMenuEntry(FSimpleUGCEditorCommands::Get().CreateUGCAction); +} + +void FSimpleUGCEditorModule::AddUGCCreatorToolbarExtension(FToolBarBuilder& Builder) +{ + Builder.AddToolBarButton(FSimpleUGCEditorCommands::Get().CreateUGCAction); +} + +void FSimpleUGCEditorModule::AddUGCPackagerMenuExtension(FMenuBuilder& Builder) +{ + FSimpleUGCPackager* Packager = UGCPackager.Get(); + + Builder.AddSubMenu(LOCTEXT("PackageUGCMenu_Label", "Package UGC"), + LOCTEXT("PackageUGCMenu_Tooltip", "Share and distribute UGC"), + FNewMenuDelegate::CreateRaw(Packager, &FSimpleUGCPackager::GeneratePackagerMenuContent), + false, + FSlateIcon(FSimpleUGCEditorStyle::GetStyleSetName(), "SimpleUGCEditor.PackageUGCAction") + ); +} + +void FSimpleUGCEditorModule::AddUGCPackagerToolbarExtension(FToolBarBuilder& Builder) +{ + FSimpleUGCPackager* Packager = UGCPackager.Get(); + + Builder.AddComboButton(FUIAction(), + FOnGetContent::CreateSP(Packager, &FSimpleUGCPackager::GeneratePackagerComboButtonContent), + LOCTEXT("PackageUGC_Label", "Package UGC"), + LOCTEXT("PackageUGC_Tooltip", "Share and distribute UGC"), + FSlateIcon(FSimpleUGCEditorStyle::GetStyleSetName(), "SimpleUGCEditor.PackageUGCAction") + ); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FSimpleUGCEditorModule, SimpleUGCEditor) diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditorCommands.cpp b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditorCommands.cpp new file mode 100644 index 0000000..846c5a3 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditorCommands.cpp @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SimpleUGCEditorCommands.h" +#include "Interfaces/IPluginManager.h" + +#define LOCTEXT_NAMESPACE "FSimpleUGCEditorModule" + +void FSimpleUGCEditorCommands::RegisterCommands() +{ + UI_COMMAND(CreateUGCAction, "Create UGC", "Create a new UGC package in a mod plugin", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(PackageUGCAction, "Package UGC", "Share and distribute your UGC", EUserInterfaceActionType::Button, FInputGesture()); +} + +TArray<TSharedPtr<FUICommandInfo>> FSimpleUGCEditorCommands::RegisterUGCCommands(const TArray<TSharedRef<class IPlugin>>& UGCList) const +{ + TArray<TSharedPtr<FUICommandInfo>> AvailableUGCActions; + AvailableUGCActions.Reserve(UGCList.Num()); + + FSimpleUGCEditorCommands* MutableThis = const_cast<FSimpleUGCEditorCommands*>(this); + + for (int32 Index = 0; Index < UGCList.Num(); ++Index) + { + AvailableUGCActions.Add(TSharedPtr<FUICommandInfo>()); + TSharedRef<IPlugin> UGC = UGCList[Index]; + + FString CommandName = "UGCEditorUGC_" + UGC->GetName(); + + FUICommandInfo::MakeCommandInfo(MutableThis->AsShared(), + AvailableUGCActions[Index], + FName(*CommandName), + FText::FromString(UGC->GetName()), + FText::FromString(UGC->GetBaseDir()), + FSlateIcon(), + EUserInterfaceActionType::Button, + FInputGesture()); + } + + return AvailableUGCActions; +} + +void FSimpleUGCEditorCommands::UnregisterUGCCommands(TArray<TSharedPtr<FUICommandInfo>>& UICommands) const +{ + FSimpleUGCEditorCommands* MutableThis = const_cast<FSimpleUGCEditorCommands*>(this); + + for (TSharedPtr<FUICommandInfo> Command : UICommands) + { + FUICommandInfo::UnregisterCommandInfo(MutableThis->AsShared(), Command.ToSharedRef()); + } +} + +#undef LOCTEXT_NAMESPACE
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditorStyle.cpp b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditorStyle.cpp new file mode 100644 index 0000000..309261a --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCEditorStyle.cpp @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SimpleUGCEditorStyle.h" +#include "SimpleUGCEditor.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyleRegistry.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" + +TSharedPtr< FSlateStyleSet > FSimpleUGCEditorStyle::StyleInstance = NULL; + +void FSimpleUGCEditorStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FSimpleUGCEditorStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FSimpleUGCEditorStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("SimpleUGCEditorStyle")); + return StyleSetName; +} + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +const FVector2D Icon16x16(16.0f, 16.0f); +const FVector2D Icon20x20(20.0f, 20.0f); +const FVector2D Icon40x40(40.0f, 40.0f); + +TSharedRef< FSlateStyleSet > FSimpleUGCEditorStyle::Create() +{ + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("SimpleUGCEditorStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("SimpleUGC")->GetBaseDir() / TEXT("Resources")); + + Style->Set("SimpleUGCEditor.PackageUGCAction", new IMAGE_BRUSH(TEXT("PackageUGC_64x"), Icon40x40)); + Style->Set("SimpleUGCEditor.CreateUGCAction", new IMAGE_BRUSH(TEXT("CreateUGC_64x"), Icon40x40)); + + return Style; +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef OTF_FONT + +void FSimpleUGCEditorStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FSimpleUGCEditorStyle::Get() +{ + return *StyleInstance; +} diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCPackager.cpp b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCPackager.cpp new file mode 100644 index 0000000..c7bc2db --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCPackager.cpp @@ -0,0 +1,205 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SimpleUGCPackager.h" +#include "SimpleUGCEditor.h" +#include "SimpleUGCEditorCommands.h" +#include "SimpleUGCEditorStyle.h" +#include "Editor.h" +#include "Widgets/SWindow.h" +#include "Widgets/SWidget.h" +#include "Interfaces/IPluginManager.h" +#include "Developer/DesktopPlatform/Public/DesktopPlatformModule.h" +#include "Editor/UATHelper/Public/IUATHelperModule.h" +#include "Editor/MainFrame/Public/Interfaces/IMainFrameModule.h" + +#include "FileHelpers.h" +#include "Misc/PackageName.h" + +#define LOCTEXT_NAMESPACE "SimpleUGCPackager" + +FSimpleUGCPackager::FSimpleUGCPackager() +{ +} + +FSimpleUGCPackager::~FSimpleUGCPackager() +{ +} + +void FSimpleUGCPackager::OpenPluginPackager(TSharedRef<IPlugin> Plugin) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + FString DefaultDirectory = FPaths::ConvertRelativePathToFull(Plugin->GetBaseDir()); + FString OutputDirectory; + + // Prompt the user to save all dirty packages. We'll ensure that if any packages from the mod that the user wants to + // package are dirty that they will not be able to save them. + + if (!IsAllContentSaved(Plugin)) + { + FEditorFileUtils::SaveDirtyPackages( true, true, true); + } + + if (IsAllContentSaved(Plugin)) + { + void* ParentWindowWindowHandle = nullptr; + IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame")); + const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) + { + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + } + + if (DesktopPlatform->OpenDirectoryDialog(ParentWindowWindowHandle, LOCTEXT("SelectOutputFolderTitle", "Select UGC output directory:").ToString(), DefaultDirectory, OutputDirectory)) + { + PackagePlugin(Plugin, OutputDirectory); + } + } + else + { + FText PackageModError = FText::Format(LOCTEXT("PackageUGCError_UnsavedContent", "You must save all assets in {0} before you can share it."), + FText::FromString(Plugin->GetName())); + + FMessageDialog::Open(EAppMsgType::Ok, PackageModError); + } +} + +bool FSimpleUGCPackager::IsAllContentSaved(TSharedRef<IPlugin> Plugin) +{ + bool bAllContentSaved = true; + + TArray<UPackage*> UnsavedPackages; + FEditorFileUtils::GetDirtyContentPackages(UnsavedPackages); + FEditorFileUtils::GetDirtyWorldPackages(UnsavedPackages); + + if (UnsavedPackages.Num() > 0) + { + FString PluginBaseDir = Plugin->GetBaseDir(); + + for (UPackage* Package : UnsavedPackages) + { + FString PackageFilename; + if (FPackageName::TryConvertLongPackageNameToFilename(Package->GetName(), PackageFilename)) + { + if (PackageFilename.Find(PluginBaseDir) == 0) + { + bAllContentSaved = false; + break; + } + } + } + } + + return bAllContentSaved; +} + +void FSimpleUGCPackager::PackagePlugin(TSharedRef<class IPlugin> Plugin, const FString& OutputDirectory) +{ +#if PLATFORM_WINDOWS + FText PlatformName = LOCTEXT("PlatformName_Windows", "Windows"); +#elif PLATFORM_MAC + FText PlatformName = LOCTEXT("PlatformName_Mac", "Mac"); +#elif PLATFORM_LINUX + FText PlatformName = LOCTEXT("PlatformName_Linux", "Linux"); +#else + FText PlatformName = LOCTEXT("PlatformName_Desktop", "Desktop"); +#endif + + // Hard coded here for simplicity. You will probably want to read this from an ini file + FString ReleaseVersion = TEXT("UGCExampleGame_v1"); + + FString CommandLine = FString::Printf(TEXT("PackageUGC -Project=\"%s\" -PluginPath=\"%s\" -basedonreleaseversion=\"%s\" -StagingDirectory=\"%s\" -nocompile"), + *FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()), + *FPaths::ConvertRelativePathToFull(Plugin->GetDescriptorFileName()), + *ReleaseVersion, + *OutputDirectory); + + FText PackagingText = FText::Format(LOCTEXT("SimpleUGCEditor_PackagePluginTaskName", "Packaging {0}"), FText::FromString(Plugin->GetName())); + + FString FriendlyName = Plugin->GetDescriptor().FriendlyName; + IUATHelperModule::Get().CreateUatTask(CommandLine, PlatformName, PackagingText, + PackagingText, FSimpleUGCEditorStyle::Get().GetBrush(TEXT("SimpleUGCEditor.PackageUGCAction")), + [ReleaseVersion, PlatformName, FriendlyName](FString TaskResult, double TimeSec) {}); +} + +void FSimpleUGCPackager::FindAvailableGameMods(TArray<TSharedRef<IPlugin>>& OutAvailableGameMods) +{ + OutAvailableGameMods.Empty(); + + // Find available game mods from the list of discovered plugins + + for (TSharedRef<IPlugin> Plugin : IPluginManager::Get().GetDiscoveredPlugins()) + { + // All game project plugins that are marked as mods are valid + if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Project && Plugin->GetType() == EPluginType::Mod) + { + UE_LOG(LogTemp, Display, TEXT("Adding %s"), *Plugin->GetName()); + OutAvailableGameMods.AddUnique(Plugin); + } + } +} + +void FSimpleUGCPackager::GeneratePackagerMenuContent_Internal(class FMenuBuilder& MenuBuilder, const TArray<TSharedPtr<FUICommandInfo>>& Commands) +{ + for (TSharedPtr<FUICommandInfo> Command : Commands) + { + MenuBuilder.AddMenuEntry(Command, NAME_None, TAttribute<FText>(), TAttribute<FText>(), FSlateIcon(FSimpleUGCEditorStyle::GetStyleSetName(), "SimpleUGCEditor.Folder")); + } +} + +void FSimpleUGCPackager::GeneratePackagerMenuContent(class FMenuBuilder& MenuBuilder) +{ + TArray<TSharedRef<IPlugin>> AvailableGameMods; + FindAvailableGameMods(AvailableGameMods); + + TArray<TSharedPtr<FUICommandInfo>> Commands; + + GeneratePackagerMenuContent_Internal(MenuBuilder, UGCCommands); +} + +TSharedRef<SWidget> FSimpleUGCPackager::GeneratePackagerComboButtonContent() +{ + // Regenerate the game mod commands + TArray<TSharedRef<IPlugin>> AvailableGameMods; + FindAvailableGameMods(AvailableGameMods); + + GetAvailableUGCCommands(AvailableGameMods); + + // Regenerate the action list + TSharedPtr<FUICommandList> GameModActionsList = MakeShareable(new FUICommandList); + + for (int32 Index = 0; Index < UGCCommands.Num(); ++Index) + { + GameModActionsList->MapAction( + UGCCommands[Index], + FExecuteAction::CreateRaw(this, &FSimpleUGCPackager::OpenPluginPackager, AvailableGameMods[Index]), + FCanExecuteAction() + ); + } + + // Show the drop down menu + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GameModActionsList); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("PackageUGC", "Share...")); + { + GeneratePackagerMenuContent_Internal(MenuBuilder, UGCCommands); + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +void FSimpleUGCPackager::GetAvailableUGCCommands(const TArray<TSharedRef<IPlugin>>& AvailableUGC) +{ + if (UGCCommands.Num() > 0) + { + // Unregister UI Commands + FSimpleUGCEditorCommands::Get().UnregisterUGCCommands(UGCCommands); + } + UGCCommands.Empty(AvailableUGC.Num()); + + UGCCommands = FSimpleUGCEditorCommands::Get().RegisterUGCCommands(AvailableUGC); +} + +#undef LOCTEXT_NAMESPACE
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCPluginWizardDefinition.cpp b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCPluginWizardDefinition.cpp new file mode 100644 index 0000000..53abeb4 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Private/SimpleUGCPluginWizardDefinition.cpp @@ -0,0 +1,214 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SimpleUGCPluginWizardDefinition.h" +#include "ContentBrowserModule.h" +#include "EngineAnalytics.h" +#include "Interfaces/IPluginManager.h" +#include "IContentBrowserSingleton.h" +#include "Algo/Transform.h" +#include "SlateBasics.h" +#include "SourceCodeNavigation.h" + +#define LOCTEXT_NAMESPACE "SimpleUGCPluginWizard" + +FSimpleUGCPluginWizardDefinition::FSimpleUGCPluginWizardDefinition() +{ + PluginBaseDir = IPluginManager::Get().FindPlugin(TEXT("SimpleUGC"))->GetBaseDir(); + // Find the Content Only Template that ships with the plugin. + // Download the Robo Recall Mod Kit and check the Plugins/OdinEditor code for how to build and use your own UGC templates from your game content + BackingTemplate = MakeShareable(new FPluginTemplateDescription(FText(), FText(), TEXT("BaseTemplate"), true, EHostType::Runtime)); + BackingTemplatePath = PluginBaseDir / TEXT("Templates") / BackingTemplate->OnDiskPath; +} + +const TArray<TSharedRef<FPluginTemplateDescription>>& FSimpleUGCPluginWizardDefinition::GetTemplatesSource() const +{ + return TemplateDefinitions; +} + +void FSimpleUGCPluginWizardDefinition::OnTemplateSelectionChanged(TArray<TSharedRef<FPluginTemplateDescription>> InSelectedItems, ESelectInfo::Type SelectInfo) +{ + SelectedTemplates = InSelectedItems; +} + +TArray<TSharedPtr<FPluginTemplateDescription>> FSimpleUGCPluginWizardDefinition::GetSelectedTemplates() const +{ + TArray<TSharedPtr<FPluginTemplateDescription>> SelectedTemplatePtrs; + + for (TSharedRef<FPluginTemplateDescription> Ref : SelectedTemplates) + { + SelectedTemplatePtrs.Add(Ref); + } + + return SelectedTemplatePtrs; +} + +void FSimpleUGCPluginWizardDefinition::ClearTemplateSelection() +{ + SelectedTemplates.Empty(); +} + +bool FSimpleUGCPluginWizardDefinition::HasValidTemplateSelection() const +{ + // A mod should be created even if no templates are actually selected + return true; +} + +bool FSimpleUGCPluginWizardDefinition::CanContainContent() const +{ + bool bHasContent = SelectedTemplates.Num() == 0; // if no templates are selected, by default it is a content mod + + if (!bHasContent) + { + for (TSharedPtr<FPluginTemplateDescription> Template : SelectedTemplates) + { + // If at least one module can contain content, it's a content mod. Otherwise, it's a pure code mod. + if (Template->bCanContainContent) + { + bHasContent = true; + break; + } + } + } + + return bHasContent; +} + +bool FSimpleUGCPluginWizardDefinition::HasModules() const +{ + bool bHasModules = false; + + for (TSharedPtr<FPluginTemplateDescription> Template : SelectedTemplates) + { + if (FPaths::DirectoryExists(PluginBaseDir / TEXT("Templates") / Template->OnDiskPath / TEXT("Source"))) + { + bHasModules = true; + break; + } + } + + return bHasModules; +} + +bool FSimpleUGCPluginWizardDefinition::IsMod() const +{ + return true; +} + +void FSimpleUGCPluginWizardDefinition::OnShowOnStartupCheckboxChanged(ECheckBoxState CheckBoxState) +{ +} + +ECheckBoxState FSimpleUGCPluginWizardDefinition::GetShowOnStartupCheckBoxState() const +{ + return ECheckBoxState(); +} + +FText FSimpleUGCPluginWizardDefinition::GetInstructions() const +{ + return LOCTEXT("CreateNewUGCPanel", "Give your new UGC package a name and Click 'Create Mod' to make a new content only UGC package."); +} + +TSharedPtr<SWidget> FSimpleUGCPluginWizardDefinition::GetCustomHeaderWidget() +{ + if ( !CustomHeaderWidget.IsValid() ) + { + FString IconPath; + GetPluginIconPath(IconPath); + + const FName BrushName(*IconPath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if ((Size.X > 0) && (Size.Y > 0)) + { + IconBrush = MakeShareable(new FSlateDynamicImageBrush(BrushName, FVector2D(Size.X, Size.Y))); + } + + CustomHeaderWidget = SNew(SHorizontalBox) + // Header image + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f) + [ + SNew(SBox) + .WidthOverride(80.0f) + .HeightOverride(80.0f) + [ + SNew(SImage) + .Image(IconBrush.IsValid() ? IconBrush.Get() : nullptr) + ] + ]; + } + + return CustomHeaderWidget; +} + +bool FSimpleUGCPluginWizardDefinition::GetPluginIconPath(FString& OutIconPath) const +{ + // Replace this file with your own 128x128 image if desired. + OutIconPath = BackingTemplatePath / TEXT("Resources/Icon128.png"); + return false; +} + +bool FSimpleUGCPluginWizardDefinition::GetTemplateIconPath(TSharedRef<FPluginTemplateDescription> InTemplate, FString& OutIconPath) const +{ + FString TemplateName = InTemplate->Name.ToString(); + + OutIconPath = PluginBaseDir / TEXT("Resources"); + + if (TemplateToIconMap.Contains(TemplateName)) + { + OutIconPath /= TemplateToIconMap[TemplateName]; + } + else + { + // Couldn't find a suitable icon to use for this template, so use the default one instead + OutIconPath /= TEXT("Icon128.png"); + } + + return false; +} + +FString FSimpleUGCPluginWizardDefinition::GetPluginFolderPath() const +{ + return BackingTemplatePath; +} + +EHostType::Type FSimpleUGCPluginWizardDefinition::GetPluginModuleDescriptor() const +{ + return BackingTemplate->ModuleDescriptorType; +} + +ELoadingPhase::Type FSimpleUGCPluginWizardDefinition::GetPluginLoadingPhase() const +{ + return BackingTemplate->LoadingPhase; +} + +TArray<FString> FSimpleUGCPluginWizardDefinition::GetFoldersForSelection() const +{ + TArray<FString> SelectedFolders; + SelectedFolders.Add(BackingTemplatePath); // This will always be a part of the mod plugin + + for (TSharedPtr<FPluginTemplateDescription> Template : SelectedTemplates) + { + SelectedFolders.AddUnique(PluginBaseDir / TEXT("Templates") / Template->OnDiskPath); + } + + return SelectedFolders; +} + +void FSimpleUGCPluginWizardDefinition::PluginCreated(const FString& PluginName, bool bWasSuccessful) const +{ + // Override Category to UGC + if (bWasSuccessful) + { + TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(PluginName); + if (Plugin != nullptr) + { + FPluginDescriptor Desc = Plugin->GetDescriptor(); + Desc.Category = "UGC"; + FText UpdateFailureText; + Plugin->UpdateDescriptor(Desc, UpdateFailureText); + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCCreator.h b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCCreator.h new file mode 100644 index 0000000..43e41ea --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCCreator.h @@ -0,0 +1,33 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +class FSimpleUGCPluginWizardDefinition; +class SDockTab; + +class FSimpleUGCCreator : public TSharedFromThis<FSimpleUGCCreator> +{ +public: + + FSimpleUGCCreator(); + ~FSimpleUGCCreator(); + + /** + * Opens the mod creator wizard. + * @param bSuppressErrors If false, a dialog will be shown if the wizard cannot be opened for whatever reason + */ + void OpenNewPluginWizard(bool bSuppressErrors = false) const; + + /** The name to use when creating the tab for the tab spawner */ + static const FName SimpleUGCEditorPluginCreatorName; + +private: + /** Registers a nomad tab spawner that will create the mod wizard */ + void RegisterTabSpawner(); + + /** Unregisters the nomad tab spawner */ + void UnregisterTabSpawner(); + + /** Spawns the tab that hosts the mod creator wizard widget */ + TSharedRef<SDockTab> HandleSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs); +};
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditor.h b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditor.h new file mode 100644 index 0000000..574e7d9 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditor.h @@ -0,0 +1,41 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Slate.h" +#include "SimpleUGCPackager.h" +#include "Modules\ModuleManager.h" + +class FToolBarBuilder; +class FMenuBuilder; + +class FSimpleUGCEditorModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // When the Create Button is clicked + void CreateUGCButtonClicked(); + + /** Adds the plugin creator as a new toolbar button */ + void AddUGCCreatorToolbarExtension(FToolBarBuilder& Builder); + + /** Adds the plugin creator as a new menu option */ + void AddUGCCreatorMenuExtension(FMenuBuilder& Builder); + + /** Adds the plugin packager as a new toolbar button */ + void AddUGCPackagerToolbarExtension(FToolBarBuilder& Builder); + + /** Adds the plugin packager as a new menu option */ + void AddUGCPackagerMenuExtension(FMenuBuilder& Builder); + +private: + + TSharedPtr<class FSimpleUGCCreator> UGCCreator; + TSharedPtr<class FSimpleUGCPackager> UGCPackager; + TSharedPtr<class FUICommandList> PluginCommands; +};
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditorCommands.h b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditorCommands.h new file mode 100644 index 0000000..247340c --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditorCommands.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "SimpleUGCEditorStyle.h" + +class FSimpleUGCEditorCommands : public TCommands<FSimpleUGCEditorCommands> +{ +public: + + FSimpleUGCEditorCommands() + : TCommands<FSimpleUGCEditorCommands>(TEXT("SimpleUGCEditor"), NSLOCTEXT("Contexts", "SimpleUGCEditor", "SimpleUGCEditor Plugin"), NAME_None, FSimpleUGCEditorStyle::GetStyleSetName()) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + + TArray<TSharedPtr<FUICommandInfo>> RegisterUGCCommands(const TArray<TSharedRef<class IPlugin>>& UGCList) const; + void UnregisterUGCCommands(TArray<TSharedPtr<FUICommandInfo>>& UICommands) const; + +public: + TSharedPtr< FUICommandInfo > CreateUGCAction; + TSharedPtr< FUICommandInfo > PackageUGCAction; +};
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditorStyle.h b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditorStyle.h new file mode 100644 index 0000000..46f4843 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCEditorStyle.h @@ -0,0 +1,31 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +class FSimpleUGCEditorStyle +{ +public: + + static void Initialize(); + + static void Shutdown(); + + /** reloads textures used by slate renderer */ + static void ReloadTextures(); + + /** @return The Slate style set for the Shooter game */ + static const ISlateStyle& Get(); + + static FName GetStyleSetName(); + +private: + + static TSharedRef< class FSlateStyleSet > Create(); + +private: + + static TSharedPtr< class FSlateStyleSet > StyleInstance; +};
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCPackager.h b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCPackager.h new file mode 100644 index 0000000..f287732 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCPackager.h @@ -0,0 +1,49 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +struct FSimpleUGCCommand +{ + TSharedPtr<class IPlugin> PluginInfo; + TSharedPtr<class FUICommandInfo> CommandInfo; +}; + +class FSimpleUGCPackager : public TSharedFromThis<FSimpleUGCPackager> +{ +public: + FSimpleUGCPackager(); + ~FSimpleUGCPackager(); + + void OpenPluginPackager(TSharedRef<class IPlugin> Plugin); + + void PackagePlugin(TSharedRef<class IPlugin> Plugin, const FString& OutputDirectory); + + /** Generates submenu content for the plugin packager command */ + void GeneratePackagerMenuContent(class FMenuBuilder& MenuBuilder); + + /** Generates the menu content for the plugin packager toolbar button */ + TSharedRef<class SWidget> GeneratePackagerComboButtonContent(); + +private: + /** Gets all available game mod plugin packages */ + void FindAvailableGameMods(TArray<TSharedRef<class IPlugin>>& OutAvailableGameMods); + + /** Gets all available game mod plugins and registers command info for them */ + void GetAvailableUGCCommands(const TArray<TSharedRef<class IPlugin>>& AvailableUGC); + + /** Generates menu content for the supplied set of commands */ + void GeneratePackagerMenuContent_Internal(class FMenuBuilder& MenuBuilder, const TArray<TSharedPtr<FUICommandInfo>>& Commands); + + /** + * Checks if a plugin has any unsaved content + * + * @param Plugin The plugin to check for unsaved content + * @return True if all mod content has been saved, false otherwise + */ + bool IsAllContentSaved(TSharedRef<class IPlugin> Plugin); + +private: + TArray<TSharedPtr<class FUICommandInfo>> UGCCommands; +}; diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCPluginWizardDefinition.h b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCPluginWizardDefinition.h new file mode 100644 index 0000000..9543a27 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/Public/SimpleUGCPluginWizardDefinition.h @@ -0,0 +1,70 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +// Depends on code from the plugin browser to work correctly +#include "../../../../Plugins/Editor/PluginBrowser/Source/PluginBrowser/Public/IPluginWizardDefinition.h" + +class FSimpleUGCPluginWizardDefinition : public IPluginWizardDefinition +{ +public: + FSimpleUGCPluginWizardDefinition(); + + // Begin IPluginWizardDefinition interface + virtual const TArray<TSharedRef<FPluginTemplateDescription>>& GetTemplatesSource() const override; + virtual void OnTemplateSelectionChanged(TArray<TSharedRef<FPluginTemplateDescription>> InSelectedItems, ESelectInfo::Type SelectInfo) override; + virtual TArray<TSharedPtr<FPluginTemplateDescription>> GetSelectedTemplates() const override; + virtual void ClearTemplateSelection() override; + virtual bool HasValidTemplateSelection() const override; + + virtual ESelectionMode::Type GetSelectionMode() const override { return ESelectionMode::Multi; } + virtual bool AllowsEnginePlugins() const override { return false; } + virtual bool CanShowOnStartup() const override { return true; } + virtual bool CanContainContent() const override; + virtual bool HasModules() const override; + virtual bool IsMod() const override; + virtual void OnShowOnStartupCheckboxChanged(ECheckBoxState CheckBoxState) override; + virtual ECheckBoxState GetShowOnStartupCheckBoxState() const override; + virtual TSharedPtr<class SWidget> GetCustomHeaderWidget() override; + virtual FText GetInstructions() const override; + + virtual bool GetPluginIconPath(FString& OutIconPath) const override; + virtual EHostType::Type GetPluginModuleDescriptor() const override; + virtual ELoadingPhase::Type GetPluginLoadingPhase() const override; + virtual bool GetTemplateIconPath(TSharedRef<FPluginTemplateDescription> InTemplate, FString& OutIconPath) const override; + virtual FString GetPluginFolderPath() const override; + virtual TArray<FString> GetFoldersForSelection() const override; + virtual void PluginCreated(const FString& PluginName, bool bWasSuccessful) const override; + // End IPluginWizardDefinition interface + +private: + /** The available templates for the mod. They should function as mixins to the backing template */ + TArray<TSharedRef<FPluginTemplateDescription>> TemplateDefinitions; + + /** The content that will be used when creating the mod */ + TArray<TSharedRef<FPluginTemplateDescription>> SelectedTemplates; + + /** The base directory of this plugin. Used for accessing the templates used to create mods */ + FString PluginBaseDir; + + /** + * The path to the template that ultimately serves as the template that the mod will be based on. It's not intended to be + * selected directly, but rather other templates will act as mixins to define what content will exist in the plugin. + */ + FString BackingTemplatePath; + + /** The backing template definition for the mod. This should never be directly selectable */ + TSharedPtr<FPluginTemplateDescription> BackingTemplate; + + /** The base code template definition. Can be directly selectable to create an "empty" code mod, but should be included with any code mod selection */ + TSharedPtr<FPluginTemplateDescription> BaseCodeTemplate; + + /** Maps a specific template to a specific icon file */ + TMap<FString, FString> TemplateToIconMap; + + /** Brush used for drawing the custom header widget */ + TSharedPtr<struct FSlateDynamicImageBrush> IconBrush; + + /** Custom header widget */ + TSharedPtr<class SWidget> CustomHeaderWidget; +};
\ No newline at end of file diff --git a/Plugins/SimpleUGC/Source/SimpleUGCEditor/SimpleUGCEditor.Build.cs b/Plugins/SimpleUGC/Source/SimpleUGCEditor/SimpleUGCEditor.Build.cs new file mode 100644 index 0000000..b2168f5 --- /dev/null +++ b/Plugins/SimpleUGC/Source/SimpleUGCEditor/SimpleUGCEditor.Build.cs @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class SimpleUGCEditor : ModuleRules +{ + public SimpleUGCEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "PluginBrowser", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Projects", + "InputCore", + "UnrealEd", + "LevelEditor", + "CoreUObject", + "Engine", + "PluginBrowser", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Plugins/SimpleUGC/Templates/BaseTemplate/Resources/Icon128.png b/Plugins/SimpleUGC/Templates/BaseTemplate/Resources/Icon128.png Binary files differnew file mode 100644 index 0000000..85b65e7 --- /dev/null +++ b/Plugins/SimpleUGC/Templates/BaseTemplate/Resources/Icon128.png |