commit 21a58e4b26def92655f9eb4c4da63603509596da Author: _Redstone_c_ <2824517378@qq.com> Date: Tue Dec 1 15:59:57 2020 +0800 实现模组新建/打包 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93e6106 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +Binaries +DerivedDataCache +Intermediate +Saved +Build +.vscode +.vs +*.VC.db +*.opensdf +*.opendb +*.sdf +*.sln +*.suo +*.xcodeproj +*.xcworkspace diff --git a/ModSupport.uplugin b/ModSupport.uplugin new file mode 100644 index 0000000..a822df8 --- /dev/null +++ b/ModSupport.uplugin @@ -0,0 +1,34 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Mod Support", + "Description": "", + "Category": "Other", + "CreatedBy": "_Redstone_c_", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "ModSupport", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "ModSupportEditor", + "Type": "Editor", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "PluginBrowser", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e10c93 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## UE4 Plugin: ModSupport +[ModSupport](http://git.myredstone.top/summary/UE4-Plugins!ModSupport.git) 是一个辅助制作 UE4 游戏模块化模组的插件,借鉴 [UGCExample](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934) 的思路管理和组织模组,配合 [HotPatcher](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934) 进行打包来实现单 Pak 包模组,可以在发布版的 UE4 上运行而无需源码。 + +目前支持的 UE4 版本:4.25.4 +前置插件:[HotPatcher](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934) diff --git a/Resources/ButtonIcon_40x.png b/Resources/ButtonIcon_40x.png new file mode 100644 index 0000000..2e8bbfd Binary files /dev/null and b/Resources/ButtonIcon_40x.png differ diff --git a/Resources/CreateMod_128x.png b/Resources/CreateMod_128x.png new file mode 100644 index 0000000..5ffc3c6 Binary files /dev/null and b/Resources/CreateMod_128x.png differ diff --git a/Resources/CreateMod_16x.png b/Resources/CreateMod_16x.png new file mode 100644 index 0000000..f61daf0 Binary files /dev/null and b/Resources/CreateMod_16x.png differ diff --git a/Resources/CreateMod_48x.png b/Resources/CreateMod_48x.png new file mode 100644 index 0000000..a958056 Binary files /dev/null and b/Resources/CreateMod_48x.png differ diff --git a/Resources/CreateMod_64x.png b/Resources/CreateMod_64x.png new file mode 100644 index 0000000..c4278cd Binary files /dev/null and b/Resources/CreateMod_64x.png differ diff --git a/Resources/Icon128.png b/Resources/Icon128.png new file mode 100644 index 0000000..1231d4a Binary files /dev/null and b/Resources/Icon128.png differ diff --git a/Resources/ModPackageCofnig.json b/Resources/ModPackageCofnig.json new file mode 100644 index 0000000..c7cf9dc --- /dev/null +++ b/Resources/ModPackageCofnig.json @@ -0,0 +1,61 @@ +{ + "bByBaseVersion": false, + "baseVersion": + { + "filePath": "" + }, + "versionId": "%%%PluginName%%%", + "assetIncludeFilters": [ + { + "path": "%%%PluginContentDir%%%" + } + ], + "assetIgnoreFilters": [], + "bForceSkipContent": true, + "forceSkipContentRules": [ + { + "path": "/Engine/Editor" + }, + { + "path": "/Engine/VREditor" + } + ], + "forceSkipAssets": [], + "bIncludeHasRefAssetsOnly": false, + "bAnalysisFilterDependencies": false, + "bRecursiveWidgetTree": false, + "assetRegistryDependencyTypes": [], + "includeSpecifyAssets": [], + "bIncludeAssetRegistry": false, + "bIncludeGlobalShaderCache": false, + "bIncludeShaderBytecode": false, + "bIncludeEngineIni": false, + "bIncludePluginIni": false, + "bIncludeProjectIni": false, + "bEnableExternFilesDiff": false, + "ignoreDeletionModulesAsset": [], + "addExternAssetsToPlatform": [], + "bEnableChunk": false, + "chunkInfos": [], + "bCookPatchAssets": false, + "pakCommandOptions": [], + "replacePakCommandTexts": [], + "unrealPakOptions": [ + "-compress", + "-compressionformats=Zlib" + ], + "pakTargetPlatforms": [ + "%%%TargetPlatform%%%" + ], + "bCustomPakNameRegular": true, + "pakNameRegular": "{VERSION}", + "bSaveDeletedAssetsToNewReleaseJson": false, + "bSavePakList": false, + "bSaveDiffAnalysis": false, + "bSaveAssetRelatedInfo": false, + "bSavePatchConfig": false, + "savePath": + { + "path": "%%%OutputDirectory%%%" + } +} \ No newline at end of file diff --git a/Resources/PackageMod_128x.png b/Resources/PackageMod_128x.png new file mode 100644 index 0000000..4221113 Binary files /dev/null and b/Resources/PackageMod_128x.png differ diff --git a/Resources/PackageMod_16x.png b/Resources/PackageMod_16x.png new file mode 100644 index 0000000..cbe6e06 Binary files /dev/null and b/Resources/PackageMod_16x.png differ diff --git a/Resources/PackageMod_48x.png b/Resources/PackageMod_48x.png new file mode 100644 index 0000000..8ca1ebd Binary files /dev/null and b/Resources/PackageMod_48x.png differ diff --git a/Resources/PackageMod_64x.png b/Resources/PackageMod_64x.png new file mode 100644 index 0000000..0657a05 Binary files /dev/null and b/Resources/PackageMod_64x.png differ diff --git a/Source/ModSupport/ModSupport.Build.cs b/Source/ModSupport/ModSupport.Build.cs new file mode 100644 index 0000000..4a99027 --- /dev/null +++ b/Source/ModSupport/ModSupport.Build.cs @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class ModSupport : ModuleRules +{ + public ModSupport(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", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "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/Source/ModSupport/Private/ModInfo.cpp b/Source/ModSupport/Private/ModInfo.cpp new file mode 100644 index 0000000..6927568 --- /dev/null +++ b/Source/ModSupport/Private/ModInfo.cpp @@ -0,0 +1,3 @@ +#include "ModInfo.h" + +#include "Interfaces/IPluginManager.h" diff --git a/Source/ModSupport/Private/ModSupport.cpp b/Source/ModSupport/Private/ModSupport.cpp new file mode 100644 index 0000000..876e7e5 --- /dev/null +++ b/Source/ModSupport/Private/ModSupport.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModSupport.h" + +#define LOCTEXT_NAMESPACE "FModSupportModule" + +void FModSupportModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FModSupportModule::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. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FModSupportModule, ModSupport) \ No newline at end of file diff --git a/Source/ModSupport/Private/ModSupportLog.cpp b/Source/ModSupport/Private/ModSupportLog.cpp new file mode 100644 index 0000000..8b307b4 --- /dev/null +++ b/Source/ModSupport/Private/ModSupportLog.cpp @@ -0,0 +1,3 @@ +#include "ModSupportLog.h" + +DEFINE_LOG_CATEGORY(LogModSupport); diff --git a/Source/ModSupport/Public/ModInfo.h b/Source/ModSupport/Public/ModInfo.h new file mode 100644 index 0000000..30b6e46 --- /dev/null +++ b/Source/ModSupport/Public/ModInfo.h @@ -0,0 +1,67 @@ +#pragma once + +#include "CoreMinimal.h" +#include "ModInfo.generated.h" + +USTRUCT(BlueprintType, Category = "ModSupport|ModInfo") +struct MODSUPPORT_API FModInfo +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString Name; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString ContentDir; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString VirtualMountPoint; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + int32 Version; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString VersionName; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString FriendlyName; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString Description; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString Category; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString CreatedBy; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString CreatedByURL; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString DocsURL; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString MarketplaceURL; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString SupportURL; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString EngineVersion; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString ParentPluginName; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + bool bIsBetaVersion; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + bool bIsExperimentalVersion; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + bool bIsHidden; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + TArray PluginsRequire; +}; diff --git a/Source/ModSupport/Public/ModSupport.h b/Source/ModSupport/Public/ModSupport.h new file mode 100644 index 0000000..3fb56cd --- /dev/null +++ b/Source/ModSupport/Public/ModSupport.h @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FModSupportModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Source/ModSupport/Public/ModSupportLog.h b/Source/ModSupport/Public/ModSupportLog.h new file mode 100644 index 0000000..2f5a232 --- /dev/null +++ b/Source/ModSupport/Public/ModSupportLog.h @@ -0,0 +1,6 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +MODSUPPORT_API DECLARE_LOG_CATEGORY_EXTERN(LogModSupport, Log, All); diff --git a/Source/ModSupportEditor/ModSupportEditor.Build.cs b/Source/ModSupportEditor/ModSupportEditor.Build.cs new file mode 100644 index 0000000..e8ca401 --- /dev/null +++ b/Source/ModSupportEditor/ModSupportEditor.Build.cs @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class ModSupportEditor : ModuleRules +{ + public ModSupportEditor(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", + "ModSupport", + "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/Source/ModSupportEditor/Private/ModCreator.cpp b/Source/ModSupportEditor/Private/ModCreator.cpp new file mode 100644 index 0000000..bb6927d --- /dev/null +++ b/Source/ModSupportEditor/Private/ModCreator.cpp @@ -0,0 +1,64 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModCreator.h" + +#include "ModPluginWizardDefinition.h" +#include "Widgets/Docking/SDockTab.h" + +// This depends on the Plugin Browser module to work correctly... +#include "IPluginBrowser.h" + + + +#define LOCTEXT_NAMESPACE "FModCreator" + +const FName FModCreator::ModSupportEditorPluginCreatorName("ModCreator"); + +FModCreator::FModCreator() +{ + RegisterTabSpawner(); +} + +FModCreator::~FModCreator() +{ + UnregisterTabSpawner(); +} + +void FModCreator::OpenNewPluginWizard(bool bSuppressErrors) const +{ + if (IPluginBrowser::IsAvailable()) + { + FGlobalTabmanager::Get()->InvokeTab(ModSupportEditorPluginCreatorName); + } + 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 FModCreator::RegisterTabSpawner() +{ + FTabSpawnerEntry& Spawner = FGlobalTabmanager::Get()->RegisterNomadTabSpawner(ModSupportEditorPluginCreatorName, + FOnSpawnTab::CreateRaw(this, &FModCreator::HandleSpawnPluginTab)); + + // Set a default size for this tab + FVector2D DefaultSize(800.0f, 500.0f); + FTabManager::RegisterDefaultTabWindowSize(ModSupportEditorPluginCreatorName, DefaultSize); + + Spawner.SetDisplayName(LOCTEXT("NewModTabHeader", "Create New Mod Package")); + Spawner.SetMenuType(ETabSpawnerMenuType::Hidden); +} + +void FModCreator::UnregisterTabSpawner() +{ + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ModSupportEditorPluginCreatorName); +} + +TSharedRef FModCreator::HandleSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs) +{ + check(IPluginBrowser::IsAvailable()); + return IPluginBrowser::Get().SpawnPluginCreatorTab(SpawnTabArgs, MakeShared()); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ModSupportEditor/Private/ModPackager.cpp b/Source/ModSupportEditor/Private/ModPackager.cpp new file mode 100644 index 0000000..5af5867 --- /dev/null +++ b/Source/ModSupportEditor/Private/ModPackager.cpp @@ -0,0 +1,221 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModPackager.h" +#include "ModSupportEditor.h" +#include "ModSupportEditorCommands.h" +#include "ModSupportEditorStyle.h" +#include "ModSupportEditorLog.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/FileHelper.h" +#include "Misc/PackageName.h" + +#define LOCTEXT_NAMESPACE "ModPackager" + +FModPackager::FModPackager() +{ +} + +FModPackager::~FModPackager() +{ +} + +void FModPackager::OpenPluginPackager(TSharedRef 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(TEXT("MainFrame")); + const TSharedPtr& MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) + { + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + } + + if (DesktopPlatform->OpenDirectoryDialog(ParentWindowWindowHandle, LOCTEXT("SelectOutputFolderTitle", "Select Mod output directory:").ToString(), DefaultDirectory, OutputDirectory)) + { + PackagePlugin(Plugin, OutputDirectory); + } + } + else + { + FText PackageModError = FText::Format(LOCTEXT("PackageModError_UnsavedContent", "You must save all assets in {0} before you can share it."), + FText::FromString(Plugin->GetName())); + + FMessageDialog::Open(EAppMsgType::Ok, PackageModError); + } +} + +bool FModPackager::IsAllContentSaved(TSharedRef Plugin) +{ + bool bAllContentSaved = true; + + TArray 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 FModPackager::PackagePlugin(TSharedRef Plugin, const FString& OutputDirectory) +{ + FString PackageCofnig; + FString PackageCofnigTemplate; + PackageCofnigTemplate = IPluginManager::Get().FindPlugin(TEXT("ModSupport"))->GetBaseDir() / TEXT("Resources") / TEXT("ModPackageCofnig.json"); + + if (!FFileHelper::LoadFileToString(PackageCofnig, *PackageCofnigTemplate)) + { + UE_LOG(LogModSupportEditor, Error, TEXT("Failed to load configuration template")); + return; + } + + PackageCofnig = PackageCofnig.Replace(TEXT("%%%PluginName%%%"), *Plugin->GetName()); + PackageCofnig = PackageCofnig.Replace(TEXT("%%%PluginContentDir%%%"), *Plugin->GetMountedAssetPath()); + +#if PLATFORM_WINDOWS + PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("WindowsNoEditor")); +#elif PLATFORM_MAC + PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("MacNoEditor")); +#elif PLATFORM_LINUX + PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("LinuxNoEditor")); +#else + PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("AllDesktop")); +#endif + + PackageCofnig = PackageCofnig.Replace(TEXT("%%%OutputDirectory%%%"), *OutputDirectory); + + FString PackageCofnigSavePath; + PackageCofnigSavePath = FPaths::ProjectSavedDir() / TEXT("ModInfo") / TEXT("ModPackageCofnig.json"); + + if (!FFileHelper::SaveStringToFile(PackageCofnig, *PackageCofnigSavePath)) + { + UE_LOG(LogModSupportEditor, Error, TEXT("Failed to save configuration")); + return; + } + +#if PLATFORM_WINDOWS + PackageCofnigSavePath = PackageCofnigSavePath.Replace(TEXT("/"), TEXT("\\")); +#endif + + FText OptTitle = LOCTEXT("PackageCofnigDialog", "Saved packaging configuration file"); + FText Message = FText::FromString(PackageCofnigSavePath); + FMessageDialog::Open(EAppMsgType::Ok, Message, &OptTitle); + UE_LOG(LogModSupportEditor, Display, TEXT("Saved packaging configuration file to %s"), *PackageCofnigSavePath); +} + +void FModPackager::FindAvailableGameMods(TArray>& OutAvailableGameMods) +{ + OutAvailableGameMods.Empty(); + + for (TSharedRef Plugin : IPluginManager::Get().GetDiscoveredPlugins()) + { + if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Project && Plugin->GetType() == EPluginType::Mod) + { + UE_LOG(LogModSupportEditor, Display, TEXT("Adding %s"), *Plugin->GetName()); + OutAvailableGameMods.AddUnique(Plugin); + } + } +} + +void FModPackager::GeneratePackagerMenuContent_Internal(class FMenuBuilder& MenuBuilder, const TArray>& Commands) +{ + for (TSharedPtr Command : Commands) + { + MenuBuilder.AddMenuEntry(Command, NAME_None, TAttribute(), TAttribute(), FSlateIcon(FModSupportEditorStyle::GetStyleSetName(), "ModSupportEditor.Folder")); + } +} + +void FModPackager::GeneratePackagerMenuContent(class FMenuBuilder& MenuBuilder) +{ + TArray> AvailableGameMods; + FindAvailableGameMods(AvailableGameMods); + + TArray> Commands; + + GeneratePackagerMenuContent_Internal(MenuBuilder, ModCommands); +} + +TSharedRef FModPackager::GeneratePackagerComboButtonContent() +{ + // Regenerate the game mod commands + TArray> AvailableGameMods; + FindAvailableGameMods(AvailableGameMods); + + GetAvailableModCommands(AvailableGameMods); + + // Regenerate the action list + TSharedPtr GameModActionsList = MakeShareable(new FUICommandList); + + for (int32 Index = 0; Index < ModCommands.Num(); ++Index) + { + GameModActionsList->MapAction( + ModCommands[Index], + FExecuteAction::CreateRaw(this, &FModPackager::OpenPluginPackager, AvailableGameMods[Index]), + FCanExecuteAction() + ); + } + + // Show the drop down menu + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GameModActionsList); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("PackageMod", "Share...")); + { + GeneratePackagerMenuContent_Internal(MenuBuilder, ModCommands); + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +void FModPackager::GetAvailableModCommands(const TArray>& AvailableMod) +{ + if (ModCommands.Num() > 0) + { + // Unregister UI Commands + FModSupportEditorCommands::Get().UnregisterModCommands(ModCommands); + } + ModCommands.Empty(AvailableMod.Num()); + + ModCommands = FModSupportEditorCommands::Get().RegisterModCommands(AvailableMod); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ModSupportEditor/Private/ModPluginWizardDefinition.cpp b/Source/ModSupportEditor/Private/ModPluginWizardDefinition.cpp new file mode 100644 index 0000000..ee0592e --- /dev/null +++ b/Source/ModSupportEditor/Private/ModPluginWizardDefinition.cpp @@ -0,0 +1,201 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModPluginWizardDefinition.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 "SimpleModPluginWizard" + +FModPluginWizardDefinition::FModPluginWizardDefinition() +{ + PluginBaseDir = IPluginManager::Get().FindPlugin(TEXT("ModSupport"))->GetBaseDir(); + BackingTemplate = MakeShareable(new FPluginTemplateDescription(FText(), FText(), TEXT("BaseTemplate"), true, EHostType::Runtime)); + BackingTemplatePath = PluginBaseDir / TEXT("Templates") / BackingTemplate->OnDiskPath; +} + +const TArray>& FModPluginWizardDefinition::GetTemplatesSource() const +{ + return TemplateDefinitions; +} + +void FModPluginWizardDefinition::OnTemplateSelectionChanged(TArray> InSelectedItems, ESelectInfo::Type SelectInfo) +{ + SelectedTemplates = InSelectedItems; +} + +TArray> FModPluginWizardDefinition::GetSelectedTemplates() const +{ + TArray> SelectedTemplatePtrs; + + for (TSharedRef Ref : SelectedTemplates) + { + SelectedTemplatePtrs.Add(Ref); + } + + return SelectedTemplatePtrs; +} + +void FModPluginWizardDefinition::ClearTemplateSelection() +{ + SelectedTemplates.Empty(); +} + +bool FModPluginWizardDefinition::HasValidTemplateSelection() const +{ + // A mod should be created even if no templates are actually selected + return true; +} + +bool FModPluginWizardDefinition::CanContainContent() const +{ + bool bHasContent = SelectedTemplates.Num() == 0; // if no templates are selected, by default it is a content mod + + if (!bHasContent) + { + for (TSharedPtr 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 FModPluginWizardDefinition::HasModules() const +{ + return false; +} + +bool FModPluginWizardDefinition::IsMod() const +{ + return true; +} + +void FModPluginWizardDefinition::OnShowOnStartupCheckboxChanged(ECheckBoxState CheckBoxState) +{ +} + +ECheckBoxState FModPluginWizardDefinition::GetShowOnStartupCheckBoxState() const +{ + return ECheckBoxState(); +} + +FText FModPluginWizardDefinition::GetInstructions() const +{ + return LOCTEXT("CreateNewModPanel", "Give your new Mod package a name and Click 'Create Mod' to make a new content only Mod package."); +} + +TSharedPtr FModPluginWizardDefinition::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 FModPluginWizardDefinition::GetPluginIconPath(FString& OutIconPath) const +{ + // Replace this file with your own 128x128 image if desired. + OutIconPath = BackingTemplatePath / TEXT("Resources/Icon128.png"); + return false; +} + +bool FModPluginWizardDefinition::GetTemplateIconPath(TSharedRef 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 FModPluginWizardDefinition::GetPluginFolderPath() const +{ + return BackingTemplatePath; +} + +EHostType::Type FModPluginWizardDefinition::GetPluginModuleDescriptor() const +{ + return BackingTemplate->ModuleDescriptorType; +} + +ELoadingPhase::Type FModPluginWizardDefinition::GetPluginLoadingPhase() const +{ + return BackingTemplate->LoadingPhase; +} + +TArray FModPluginWizardDefinition::GetFoldersForSelection() const +{ + TArray SelectedFolders; + SelectedFolders.Add(BackingTemplatePath); // This will always be a part of the mod plugin + + for (TSharedPtr Template : SelectedTemplates) + { + SelectedFolders.AddUnique(PluginBaseDir / TEXT("Templates") / Template->OnDiskPath); + } + + return SelectedFolders; +} + +void FModPluginWizardDefinition::PluginCreated(const FString& PluginName, bool bWasSuccessful) const +{ + // Override Category to Mod + if (bWasSuccessful) + { + TSharedPtr Plugin = IPluginManager::Get().FindPlugin(PluginName); + if (Plugin != nullptr) + { + FPluginDescriptor Desc = Plugin->GetDescriptor(); + Desc.Category = "Mod"; + FText UpdateFailureText; + Plugin->UpdateDescriptor(Desc, UpdateFailureText); + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ModSupportEditor/Private/ModSupportEditor.cpp b/Source/ModSupportEditor/Private/ModSupportEditor.cpp new file mode 100644 index 0000000..16031c7 --- /dev/null +++ b/Source/ModSupportEditor/Private/ModSupportEditor.cpp @@ -0,0 +1,96 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModSupportEditor.h" + +#include "ModCreator.h" +#include "ModPackager.h" +#include "Misc/MessageDialog.h" +#include "ModSupportEditorStyle.h" +#include "ModSupportEditorCommands.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" + +#include "LevelEditor.h" + +#define LOCTEXT_NAMESPACE "FModSupportEditorModule" + +void FModSupportEditorModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + + ModCreator = MakeShared(); + ModPackager = MakeShared(); + + FModSupportEditorStyle::Initialize(); + FModSupportEditorStyle::ReloadTextures(); + + FModSupportEditorCommands::Register(); + + PluginCommands = MakeShareable(new FUICommandList); + + PluginCommands->MapAction( + FModSupportEditorCommands::Get().CreateModAction, + FExecuteAction::CreateRaw(this, &FModSupportEditorModule::CreateModButtonClicked), + FCanExecuteAction() + ); + + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + // Add commands + { + FName MenuSection = "FileProject"; + FName ToolbarSection = "Misc"; + + // Add creator button to the toolbar + { + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension(ToolbarSection, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FModSupportEditorModule::AddModCreatorToolbarExtension)); + + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + } + + // Add packager button to the toolbar + { + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension(ToolbarSection, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FModSupportEditorModule::AddModPackagerToolbarExtension)); + + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + } + } +} + +void FModSupportEditorModule::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. + + FModSupportEditorStyle::Shutdown(); + FModSupportEditorCommands::Unregister(); +} + +void FModSupportEditorModule::CreateModButtonClicked() +{ + if (ModCreator.IsValid()) + { + ModCreator->OpenNewPluginWizard(); + } +} + +void FModSupportEditorModule::AddModCreatorToolbarExtension(FToolBarBuilder& Builder) +{ + Builder.AddToolBarButton(FModSupportEditorCommands::Get().CreateModAction); +} + +void FModSupportEditorModule::AddModPackagerToolbarExtension(FToolBarBuilder& Builder) +{ + FModPackager* Packager = ModPackager.Get(); + + Builder.AddComboButton(FUIAction(), + FOnGetContent::CreateSP(Packager, &FModPackager::GeneratePackagerComboButtonContent), + LOCTEXT("PackageMod_Label", "Package Mod"), + LOCTEXT("PackageMod_Tooltip", "Share and distribute Mod"), + FSlateIcon(FModSupportEditorStyle::GetStyleSetName(), "ModSupportEditor.PackageModAction") + ); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FModSupportEditorModule, ModSupportEditor) \ No newline at end of file diff --git a/Source/ModSupportEditor/Private/ModSupportEditorCommands.cpp b/Source/ModSupportEditor/Private/ModSupportEditorCommands.cpp new file mode 100644 index 0000000..291b3cc --- /dev/null +++ b/Source/ModSupportEditor/Private/ModSupportEditorCommands.cpp @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModSupportEditorCommands.h" +#include "Interfaces/IPluginManager.h" + +#define LOCTEXT_NAMESPACE "FModSupportEditorModule" + +void FModSupportEditorCommands::RegisterCommands() +{ + UI_COMMAND(CreateModAction, "Create Mod", "Create a new Mod package in a mod plugin", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(PackageModAction, "Package Mod", "Share and distribute your Mod", EUserInterfaceActionType::Button, FInputGesture()); +} + +TArray> FModSupportEditorCommands::RegisterModCommands(const TArray>& ModList) const +{ + TArray> AvailableModActions; + AvailableModActions.Reserve(ModList.Num()); + + FModSupportEditorCommands* MutableThis = const_cast(this); + + for (int32 Index = 0; Index < ModList.Num(); ++Index) + { + AvailableModActions.Add(TSharedPtr()); + TSharedRef Mod = ModList[Index]; + + FString CommandName = "ModEditor_" + Mod->GetName(); + + FUICommandInfo::MakeCommandInfo(MutableThis->AsShared(), + AvailableModActions[Index], + FName(*CommandName), + FText::FromString(Mod->GetName()), + FText::FromString(Mod->GetBaseDir()), + FSlateIcon(), + EUserInterfaceActionType::Button, + FInputGesture()); + } + + return AvailableModActions; +} + +void FModSupportEditorCommands::UnregisterModCommands(TArray>& UICommands) const +{ + FModSupportEditorCommands* MutableThis = const_cast(this); + + for (TSharedPtr Command : UICommands) + { + FUICommandInfo::UnregisterCommandInfo(MutableThis->AsShared(), Command.ToSharedRef()); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ModSupportEditor/Private/ModSupportEditorLog.cpp b/Source/ModSupportEditor/Private/ModSupportEditorLog.cpp new file mode 100644 index 0000000..db8cb15 --- /dev/null +++ b/Source/ModSupportEditor/Private/ModSupportEditorLog.cpp @@ -0,0 +1,4 @@ +#include "ModSupportEditorLog.h" + + +DEFINE_LOG_CATEGORY(LogModSupportEditor); diff --git a/Source/ModSupportEditor/Private/ModSupportEditorStyle.cpp b/Source/ModSupportEditor/Private/ModSupportEditorStyle.cpp new file mode 100644 index 0000000..fe0de69 --- /dev/null +++ b/Source/ModSupportEditor/Private/ModSupportEditorStyle.cpp @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModSupportEditorStyle.h" +#include "ModSupportEditor.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyleRegistry.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" + +TSharedPtr< FSlateStyleSet > FModSupportEditorStyle::StyleInstance = NULL; + +void FModSupportEditorStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FModSupportEditorStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FModSupportEditorStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("ModSupportEditorStyle")); + 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 > FModSupportEditorStyle::Create() +{ + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("ModSupportEditorStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("ModSupport")->GetBaseDir() / TEXT("Resources")); + + Style->Set("ModSupportEditor.PackageModAction", new IMAGE_BRUSH(TEXT("PackageMod_64x"), Icon40x40)); + Style->Set("ModSupportEditor.CreateModAction", new IMAGE_BRUSH(TEXT("CreateMod_64x"), Icon40x40)); + + return Style; +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef OTF_FONT + +void FModSupportEditorStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FModSupportEditorStyle::Get() +{ + return *StyleInstance; +} diff --git a/Source/ModSupportEditor/Public/ModCreator.h b/Source/ModSupportEditor/Public/ModCreator.h new file mode 100644 index 0000000..b361675 --- /dev/null +++ b/Source/ModSupportEditor/Public/ModCreator.h @@ -0,0 +1,33 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +class FModSupportPluginWizardDefinition; +class SDockTab; + +class FModCreator : public TSharedFromThis +{ +public: + + FModCreator(); + ~FModCreator(); + + /** + * 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 ModSupportEditorPluginCreatorName; + +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 HandleSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs); +}; \ No newline at end of file diff --git a/Source/ModSupportEditor/Public/ModPackager.h b/Source/ModSupportEditor/Public/ModPackager.h new file mode 100644 index 0000000..073b2ef --- /dev/null +++ b/Source/ModSupportEditor/Public/ModPackager.h @@ -0,0 +1,49 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +struct FModSupportCommand +{ + TSharedPtr PluginInfo; + TSharedPtr CommandInfo; +}; + +class FModPackager : public TSharedFromThis +{ +public: + FModPackager(); + ~FModPackager(); + + void OpenPluginPackager(TSharedRef Plugin); + + void PackagePlugin(TSharedRef 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 GeneratePackagerComboButtonContent(); + +private: + /** Gets all available game mod plugin packages */ + void FindAvailableGameMods(TArray>& OutAvailableGameMods); + + /** Gets all available game mod plugins and registers command info for them */ + void GetAvailableModCommands(const TArray>& AvailableMod); + + /** Generates menu content for the supplied set of commands */ + void GeneratePackagerMenuContent_Internal(class FMenuBuilder& MenuBuilder, const TArray>& 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 Plugin); + +private: + TArray> ModCommands; +}; diff --git a/Source/ModSupportEditor/Public/ModPluginWizardDefinition.h b/Source/ModSupportEditor/Public/ModPluginWizardDefinition.h new file mode 100644 index 0000000..dd14bca --- /dev/null +++ b/Source/ModSupportEditor/Public/ModPluginWizardDefinition.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 "IPluginWizardDefinition.h" + +class FModPluginWizardDefinition : public IPluginWizardDefinition +{ +public: + FModPluginWizardDefinition(); + + // Begin IPluginWizardDefinition interface + virtual const TArray>& GetTemplatesSource() const override; + virtual void OnTemplateSelectionChanged(TArray> InSelectedItems, ESelectInfo::Type SelectInfo) override; + virtual TArray> 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 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 InTemplate, FString& OutIconPath) const override; + virtual FString GetPluginFolderPath() const override; + virtual TArray 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> TemplateDefinitions; + + /** The content that will be used when creating the mod */ + TArray> 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 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 BaseCodeTemplate; + + /** Maps a specific template to a specific icon file */ + TMap TemplateToIconMap; + + /** Brush used for drawing the custom header widget */ + TSharedPtr IconBrush; + + /** Custom header widget */ + TSharedPtr CustomHeaderWidget; +}; \ No newline at end of file diff --git a/Source/ModSupportEditor/Public/ModSupportEditor.h b/Source/ModSupportEditor/Public/ModSupportEditor.h new file mode 100644 index 0000000..d49370b --- /dev/null +++ b/Source/ModSupportEditor/Public/ModSupportEditor.h @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FModSupportEditorModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // When the Create Button is clicked + void CreateModButtonClicked(); + + /** Adds the plugin creator as a new toolbar button */ + void AddModCreatorToolbarExtension(FToolBarBuilder& Builder); + + /** Adds the plugin packager as a new toolbar button */ + void AddModPackagerToolbarExtension(FToolBarBuilder& Builder); + +private: + + TSharedPtr ModCreator; + TSharedPtr ModPackager; + TSharedPtr PluginCommands; +}; diff --git a/Source/ModSupportEditor/Public/ModSupportEditorCommands.h b/Source/ModSupportEditor/Public/ModSupportEditorCommands.h new file mode 100644 index 0000000..247bf51 --- /dev/null +++ b/Source/ModSupportEditor/Public/ModSupportEditorCommands.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "ModSupportEditorStyle.h" + +class FModSupportEditorCommands : public TCommands +{ +public: + + FModSupportEditorCommands() + : TCommands(TEXT("ModSupportEditor"), NSLOCTEXT("Contexts", "ModSupportEditor", "ModSupportEditor Plugin"), NAME_None, FModSupportEditorStyle::GetStyleSetName()) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + + TArray> RegisterModCommands(const TArray>& ModList) const; + void UnregisterModCommands(TArray>& UICommands) const; + +public: + TSharedPtr< FUICommandInfo > CreateModAction; + TSharedPtr< FUICommandInfo > PackageModAction; +}; \ No newline at end of file diff --git a/Source/ModSupportEditor/Public/ModSupportEditorLog.h b/Source/ModSupportEditor/Public/ModSupportEditorLog.h new file mode 100644 index 0000000..2e68e5a --- /dev/null +++ b/Source/ModSupportEditor/Public/ModSupportEditorLog.h @@ -0,0 +1,6 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +MODSUPPORTEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogModSupportEditor, Log, All); diff --git a/Source/ModSupportEditor/Public/ModSupportEditorStyle.h b/Source/ModSupportEditor/Public/ModSupportEditorStyle.h new file mode 100644 index 0000000..e0fd50d --- /dev/null +++ b/Source/ModSupportEditor/Public/ModSupportEditorStyle.h @@ -0,0 +1,31 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +class FModSupportEditorStyle +{ +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/Templates/BaseTemplate/Resources/Icon128.png b/Templates/BaseTemplate/Resources/Icon128.png new file mode 100644 index 0000000..85b65e7 Binary files /dev/null and b/Templates/BaseTemplate/Resources/Icon128.png differ