RealtimeStyleTransferRuntime/Source/LyraEditor/LyraEditor.cpp

336 lines
10 KiB
C++

// 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<UClass*>& 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<UClass*>& Classes)
{
Classes.Empty();
for (UClass* Class : TObjectRange<UClass>())
{
if (Class->IsChildOf<AActor>() && 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<FString> 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<UAssetEditorSubsystem>()->OpenEditorForAsset(MapPath);
}
}
static bool CanShowCommonMaps()
{
return HasNoPlayWorld() && !GetDefault<ULyraDeveloperSettings>()->CommonEditorMaps.IsEmpty();
}
static TSharedRef<SWidget> GetCommonMapsDropdown()
{
FMenuBuilder MenuBuilder(true, nullptr);
for (const FSoftObjectPath& Path : GetDefault<ULyraDeveloperSettings>()->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<SWidget> 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<FText>(), 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<FAssetToolsModule>("AssetTools").Get();
AssetTools.RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_LyraContextEffectsLibrary));
}
void OnBeginPIE(bool bIsSimulating)
{
ULyraExperienceManager* ExperienceManager = GEngine->GetEngineSubsystem<ULyraExperienceManager>();
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