#include "ProjectEleriEditorModule.h" #include "Framework/Docking/TabManager.h" #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" #include "Styling/AppStyle.h" #include "ToolMenus.h" #include "Widgets/Docking/SDockTab.h" #include "ItemDataAssetCustomization.h" #include "ItemEditorWindow.h" #include "NpcScheduleActionCustomization.h" #include "NpcScheduleAssetCustomization.h" #include "ScheduleAssetRecheck.h" #define LOCTEXT_NAMESPACE "ProjectEleriEditorModule" IMPLEMENT_GAME_MODULE(FProjectEleriEditorModule, ProjectEleriEditor); const FName FProjectEleriEditorModule::ItemEditorTabName(TEXT("ProjectEleriItemEditor")); void FProjectEleriEditorModule::StartupModule() { // Details customization for the item data asset (property map generation widget). FPropertyEditorModule& PropEd = FModuleManager::LoadModuleChecked("PropertyEditor"); PropEd.RegisterCustomClassLayout( TEXT("ItemDataAsset"), FOnGetDetailCustomizationInstance::CreateStatic(&FItemDataAssetCustomization::MakeInstance) ); // Details customization for schedule actions, so an anchor is picked from the level instead of // being typed in as a PersistentId. Registered under the struct's script name, which is the C++ // struct name without its F prefix. PropEd.RegisterCustomPropertyTypeLayout( TEXT("ScheduledAction"), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FNpcScheduleActionCustomization::MakeInstance) ); // Details customization for the schedule asset itself, which draws the day its actions describe as // a strip under everything else the panel shows. Registered under the class's script name, which is // the C++ class name without its U prefix. PropEd.RegisterCustomClassLayout( TEXT("NpcScheduleAsset"), FOnGetDetailCustomizationInstance::CreateStatic(&FNpcScheduleAssetCustomization::MakeInstance) ); // Register the Item Editor as a docking tab. if (FGlobalTabmanager::Get()->HasTabSpawner(ItemEditorTabName)) { FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ItemEditorTabName); } FGlobalTabmanager::Get()->RegisterNomadTabSpawner( ItemEditorTabName, FOnSpawnTab::CreateLambda([](const FSpawnTabArgs& Args) { return SNew(SDockTab) .TabRole(ETabRole::NomadTab) .Label(LOCTEXT("ItemEditorTabLabel", "Item Editor")) .ToolTipText(LOCTEXT("ItemEditorTabTooltip", "Search items by name and edit their data assets")) [ SNew(SItemEditorWindow) ]; })) .SetDisplayName(LOCTEXT("ItemEditorDisplayName", "Item Editor")) .SetMenuType(ETabSpawnerMenuType::Hidden); // Add a menu entry to open the window. Menus might not be ready yet during startup. if (UToolMenus::IsToolMenuUIEnabled()) { RegisterMenus(); } else { UToolMenus::RegisterStartupCallback( FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FProjectEleriEditorModule::RegisterMenus)); } } void FProjectEleriEditorModule::ShutdownModule() { if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) { FPropertyEditorModule& PropEd = FModuleManager::GetModuleChecked("PropertyEditor"); PropEd.UnregisterCustomClassLayout(TEXT("ItemDataAsset")); PropEd.UnregisterCustomPropertyTypeLayout(TEXT("ScheduledAction")); PropEd.UnregisterCustomClassLayout(TEXT("NpcScheduleAsset")); } if (FGlobalTabmanager::Get()->HasTabSpawner(ItemEditorTabName)) { FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ItemEditorTabName); } UToolMenus::UnregisterOwner(this); } void FProjectEleriEditorModule::OpenItemEditorTab() { FGlobalTabmanager::Get()->TryInvokeTab(ItemEditorTabName); } void FProjectEleriEditorModule::RecheckScheduleAssets() { // Thin on purpose: the sweep reports what it found, in a notification and in the log, so all that // is left for the module is to give it somewhere to be reached from. Whatever it was not able to // answer for is part of that report rather than something to be decided again here. ScheduleAssetRecheck::RecheckAllScheduleAssets(); } void FProjectEleriEditorModule::RegisterMenus() { FToolMenuOwnerScoped OwnerScoped(this); if (UToolMenu* WindowMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window")) { FToolMenuSection& Section = WindowMenu->AddSection( TEXT("ItemEditor"), LOCTEXT("ItemEditorSectionLabel", "Eleri Tools"), FToolMenuInsert(NAME_None, EToolMenuInsertType::First) ); Section.AddMenuEntry( TEXT("OpenItemEditor"), LOCTEXT("ItemEditorMenuLabel", "Item Editor"), LOCTEXT("ItemEditorMenuTooltip", "Open a window to search items by name and edit their data assets."), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &FProjectEleriEditorModule::OpenItemEditorTab)) ); } // The Tools menu of the level editor, in a section of this module's own so that the entries this // module adds are gathered in one place instead of being scattered among the engine's tools. if (UToolMenu* ToolsMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools")) { FToolMenuSection& Section = ToolsMenu->AddSection( TEXT("ScheduleAssets"), LOCTEXT("ScheduleAssetsSectionLabel", "Eleri Tools"), FToolMenuInsert(NAME_None, EToolMenuInsertType::First) ); Section.AddMenuEntry( TEXT("RecheckScheduleAssets"), LOCTEXT("RecheckScheduleAssetsMenuLabel", "Recheck Schedule Assets"), LOCTEXT("RecheckScheduleAssetsTooltip", "Compare every NPC schedule asset's recorded anchor positions with the anchors standing in this level, and save the assets that have moved."), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &FProjectEleriEditorModule::RecheckScheduleAssets)) ); } // The level editor toolbar keeps a stretch of itself clear for plugins that want a button there, so // this adds to a slot that already exists rather than registering a toolbar of its own that would // then have to be found a place on screen. if (UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.User")) { FToolMenuSection& Section = ToolbarMenu->FindOrAddSection(TEXT("ScheduleAssets")); Section.AddEntry(FToolMenuEntry::InitToolBarButton( TEXT("RecheckScheduleAssets"), FUIAction(FExecuteAction::CreateRaw(this, &FProjectEleriEditorModule::RecheckScheduleAssets)), LOCTEXT("RecheckScheduleAssetsToolbarLabel", "Recheck Schedules"), LOCTEXT("RecheckScheduleAssetsTooltip", "Compare every NPC schedule asset's recorded anchor positions with the anchors standing in this level, and save the assets that have moved."), FSlateIcon(FAppStyle::GetAppStyleSetName(), TEXT("Icons.Adjust")) )); } } #undef LOCTEXT_NAMESPACE