// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraEditor.h" #include "AbilitySystemGlobals.h" #include "AssetToolsModule.h" #include "Containers/Array.h" #include "Containers/UnrealString.h" #include "DataValidationModule.h" #include "Delegates/Delegate.h" #include "Development/LyraDeveloperSettings.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "Editor/UnrealEdEngine.h" #include "Engine/Engine.h" #include "Engine/GameInstance.h" #include "Engine/World.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/SlateDelegates.h" #include "GameEditorStyle.h" #include "GameFramework/Actor.h" #include "GameModes/LyraExperienceManager.h" #include "GameplayAbilitiesEditorModule.h" #include "GameplayAbilitiesModule.h" #include "GameplayCueInterface.h" #include "GameplayCueNotify_Burst.h" #include "GameplayCueNotify_BurstLatent.h" #include "GameplayCueNotify_Looping.h" #include "HAL/Platform.h" #include "IAssetTools.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "Misc/CoreMisc.h" #include "Modules/ModuleManager.h" #include "Private/AssetTypeActions_LyraContextEffectsLibrary.h" #include "Subsystems/AssetEditorSubsystem.h" #include "Styling/AppStyle.h" #include "Templates/SharedPointer.h" #include "Textures/SlateIcon.h" #include "ToolMenu.h" #include "ToolMenuEntry.h" #include "ToolMenuMisc.h" #include "ToolMenuSection.h" #include "ToolMenus.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/ObjectPtr.h" #include "UObject/UObjectIterator.h" #include "UnrealEdGlobals.h" #include "Validation/EditorValidator.h" class SWidget; #define LOCTEXT_NAMESPACE "LyraEditor" DEFINE_LOG_CATEGORY(LogLyraEditor); // This function tells the GameplayCue editor what classes to expose when creating new notifies. static void GetGameplayCueDefaultClasses(TArray& Classes) { Classes.Empty(); Classes.Add(UGameplayCueNotify_Burst::StaticClass()); Classes.Add(AGameplayCueNotify_BurstLatent::StaticClass()); Classes.Add(AGameplayCueNotify_Looping::StaticClass()); } // This function tells the GameplayCue editor what classes to search for GameplayCue events. static void GetGameplayCueInterfaceClasses(TArray& Classes) { Classes.Empty(); for (UClass* Class : TObjectRange()) { if (Class->IsChildOf() && Class->ImplementsInterface(UGameplayCueInterface::StaticClass())) { Classes.Add(Class); } } } // This function tells the GameplayCue editor where to create the GameplayCue notifies based on their tag. static FString GetGameplayCuePath(FString GameplayCueTag) { FString Path = FString(TEXT("/Game")); //@TODO: Try plugins (e.g., GameplayCue.ShooterGame.Foo should be in ShooterCore or something) // Default path to the first entry in the UAbilitySystemGlobals::GameplayCueNotifyPaths. if (IGameplayAbilitiesModule::IsAvailable()) { IGameplayAbilitiesModule& GameplayAbilitiesModule = IGameplayAbilitiesModule::Get(); if (GameplayAbilitiesModule.IsAbilitySystemGlobalsAvailable()) { UAbilitySystemGlobals* AbilitySystemGlobals = GameplayAbilitiesModule.GetAbilitySystemGlobals(); check(AbilitySystemGlobals); TArray GetGameplayCueNotifyPaths = AbilitySystemGlobals->GetGameplayCueNotifyPaths(); if (GetGameplayCueNotifyPaths.Num() > 0) { Path = GetGameplayCueNotifyPaths[0]; } } } GameplayCueTag.RemoveFromStart(TEXT("GameplayCue.")); FString NewDefaultPathName = FString::Printf(TEXT("%s/GCN_%s"), *Path, *GameplayCueTag); return NewDefaultPathName; } static bool HasPlayWorld() { return GEditor->PlayWorld != nullptr; } static bool HasNoPlayWorld() { return !HasPlayWorld(); } static bool HasPlayWorldAndRunning() { return HasPlayWorld() && !GUnrealEd->PlayWorld->bDebugPauseExecution; } static void OpenCommonMap_Clicked(const FString MapPath) { if (ensure(MapPath.Len())) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(MapPath); } } static bool CanShowCommonMaps() { return HasNoPlayWorld() && !GetDefault()->CommonEditorMaps.IsEmpty(); } static TSharedRef GetCommonMapsDropdown() { FMenuBuilder MenuBuilder(true, nullptr); for (const FSoftObjectPath& Path : GetDefault()->CommonEditorMaps) { if (!Path.IsValid()) { continue; } const FText DisplayName = FText::FromString(Path.GetAssetName()); MenuBuilder.AddMenuEntry( DisplayName, LOCTEXT("CommonPathDescription", "Opens this map in the editor"), FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&OpenCommonMap_Clicked, Path.ToString()), FCanExecuteAction::CreateStatic(&HasNoPlayWorld), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&HasNoPlayWorld) ) ); } return MenuBuilder.MakeWidget(); } static void AddPlayer_Clicked() { if (ensure(GEditor->PlayWorld)) { if (UGameInstance* GameInstance = GEditor->PlayWorld->GetGameInstance()) { if (GameInstance->GetNumLocalPlayers() == 1) { GameInstance->DebugCreatePlayer(1); } else { GameInstance->DebugRemovePlayer(1); } } } } static TSharedRef AddLocalPlayer() { FMenuBuilder MenuBuilder(true, nullptr); MenuBuilder.AddMenuEntry( LOCTEXT("SplitscreenButton", "Splitscreen"), LOCTEXT("SplitscreenDescription", "Adds/Removes a Splitscreen Player to the current PIE session"), FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&AddPlayer_Clicked), FCanExecuteAction::CreateStatic(&HasPlayWorld), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&HasPlayWorld) ) ); return MenuBuilder.MakeWidget(); } static void CheckGameContent_Clicked() { UEditorValidator::ValidateCheckedOutContent(/*bInteractive=*/true, EDataValidationUsecase::Manual); } static void RegisterGameEditorMenus() { UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar"); FToolMenuSection& Section = Menu->AddSection("PlayGameExtensions", TAttribute(), FToolMenuInsert("Play", EToolMenuInsertType::After)); FToolMenuEntry BlueprintEntry = FToolMenuEntry::InitComboButton( "OpenGameMenu", FUIAction( FExecuteAction(), FCanExecuteAction::CreateStatic(&HasPlayWorld), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&HasPlayWorld)), FOnGetContent::CreateStatic(&AddLocalPlayer), LOCTEXT("GameOptions_Label", "Game Options"), LOCTEXT("GameOptions_ToolTip", "Game Options"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.OpenLevelBlueprint") ); BlueprintEntry.StyleNameOverride = "CalloutToolbar"; Section.AddEntry(BlueprintEntry); FToolMenuEntry CheckContentEntry = FToolMenuEntry::InitToolBarButton( "CheckContent", FUIAction( FExecuteAction::CreateStatic(&CheckGameContent_Clicked), FCanExecuteAction::CreateStatic(&HasNoPlayWorld), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&HasNoPlayWorld)), LOCTEXT( "CheckContentButton", "Check Content" ), LOCTEXT( "CheckContentDescription", "Runs the Content Validation job on all checked out assets to look for warnings and errors" ), FSlateIcon(FGameEditorStyle::GetStyleSetName(), "GameEditor.CheckContent") ); CheckContentEntry.StyleNameOverride = "CalloutToolbar"; Section.AddEntry(CheckContentEntry); FToolMenuEntry CommonMapEntry = FToolMenuEntry::InitComboButton( "CommonMapOptions", FUIAction( FExecuteAction(), FCanExecuteAction::CreateStatic(&HasNoPlayWorld), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&CanShowCommonMaps)), FOnGetContent::CreateStatic(&GetCommonMapsDropdown), LOCTEXT("CommonMaps_Label", "Common Maps"), LOCTEXT("CommonMaps_ToolTip", "Some commonly desired maps while using the editor"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Level") ); CommonMapEntry.StyleNameOverride = "CalloutToolbar"; Section.AddEntry(CommonMapEntry); } /** * FLyraEditorModule */ class FLyraEditorModule : public FDefaultGameModuleImpl { typedef FLyraEditorModule ThisClass; virtual void StartupModule() override { FGameEditorStyle::Initialize(); if (!IsRunningGame()) { FModuleManager::Get().OnModulesChanged().AddRaw(this, &FLyraEditorModule::ModulesChangedCallback); BindGameplayAbilitiesEditorDelegates(); if (FSlateApplication::IsInitialized()) { UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateStatic(&RegisterGameEditorMenus)); } FEditorDelegates::BeginPIE.AddRaw(this, &ThisClass::OnBeginPIE); FEditorDelegates::EndPIE.AddRaw(this, &ThisClass::OnEndPIE); } // Register the Context Effects Library asset type actions. IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); AssetTools.RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_LyraContextEffectsLibrary)); } void OnBeginPIE(bool bIsSimulating) { ULyraExperienceManager* ExperienceManager = GEngine->GetEngineSubsystem(); check(ExperienceManager); ExperienceManager->OnPlayInEditorBegun(); } void OnEndPIE(bool bIsSimulating) { } virtual void ShutdownModule() override { FModuleManager::Get().OnModulesChanged().RemoveAll(this); } protected: static void BindGameplayAbilitiesEditorDelegates() { IGameplayAbilitiesEditorModule& GameplayAbilitiesEditorModule = IGameplayAbilitiesEditorModule::Get(); GameplayAbilitiesEditorModule.GetGameplayCueNotifyClassesDelegate().BindStatic(&GetGameplayCueDefaultClasses); GameplayAbilitiesEditorModule.GetGameplayCueInterfaceClassesDelegate().BindStatic(&GetGameplayCueInterfaceClasses); GameplayAbilitiesEditorModule.GetGameplayCueNotifyPathDelegate().BindStatic(&GetGameplayCuePath); } void ModulesChangedCallback(FName ModuleThatChanged, EModuleChangeReason ReasonForChange) { if ((ReasonForChange == EModuleChangeReason::ModuleLoaded) && (ModuleThatChanged.ToString() == TEXT("GameplayAbilitiesEditor"))) { BindGameplayAbilitiesEditorDelegates(); } } }; IMPLEMENT_MODULE(FLyraEditorModule, LyraEditor); #undef LOCTEXT_NAMESPACE