haxis/Plugins/FMODStudio/Source/FMODStudioEditor/Private/FMODStudioEditorModule.cpp

873 lines
30 KiB
C++

// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2016.
#include "FMODStudioEditorPrivatePCH.h"
#include "FMODStudioEditorModule.h"
#include "FMODStudioModule.h"
#include "FMODStudioStyle.h"
#include "FMODAudioComponent.h"
#include "FMODAssetBroker.h"
#include "FMODSettings.h"
#include "FMODUtils.h"
#include "FMODEventEditor.h"
#include "FMODAudioComponentVisualizer.h"
#include "FMODAmbientSoundDetails.h"
#include "SlateBasics.h"
#include "AssetTypeActions_FMODEvent.h"
#include "NotificationManager.h"
#include "SNotificationList.h"
#include "ISettingsModule.h"
#include "ISettingsSection.h"
#include "Editor.h"
#include "SceneViewport.h"
#include "LevelEditor.h"
#include "SocketSubsystem.h"
#include "Sockets.h"
#include "IPAddress.h"
#include "fmod_studio.hpp"
#define LOCTEXT_NAMESPACE "FMODStudio"
DEFINE_LOG_CATEGORY(LogFMOD);
class FFMODStudioLink
{
public:
FFMODStudioLink()
: SocketSubsystem(nullptr),
Socket(nullptr)
{
SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
}
~FFMODStudioLink()
{
Disconnect();
}
bool Connect()
{
if (!SocketSubsystem) return false;
Disconnect();
Socket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("FMOD Studio Connection"), false);
TSharedRef<FInternetAddr> Addr = SocketSubsystem->CreateInternetAddr();
bool Valid = false;
Addr->SetIp(TEXT("127.0.0.1"), Valid);
if (!Valid) return false;
Addr->SetPort(3663);
return Socket->Connect(*Addr);
}
void Disconnect()
{
if (SocketSubsystem && Socket)
{
SocketSubsystem->DestroySocket(Socket);
Socket = nullptr;
}
}
bool Execute(const TCHAR* Message, FString& OutMessage)
{
OutMessage = TEXT("");
if (!Socket)
{
return false;
}
UE_LOG(LogFMOD, Log, TEXT("Sent studio message: %s"), Message);
FTCHARToUTF8 MessageChars(Message);
int32 BytesSent = 0;
if (!Socket->Send((const uint8*)MessageChars.Get(), MessageChars.Length(), BytesSent))
{
return false;
}
while (1)
{
FString BackMessage;
if (!ReadMessage(BackMessage))
{
return false;
}
UE_LOG(LogFMOD, Log, TEXT("Received studio message: %s"), *BackMessage);
if (BackMessage.StartsWith(TEXT("out(): ")))
{
OutMessage = BackMessage.Mid(7).TrimTrailing();
break;
}
else
{
// Keep going
}
}
return true;
}
private:
bool ReadMessage(FString& OutMessage)
{
while (1)
{
for (int32 i=0; i<ReceivedMessage.Num(); ++i)
{
if (ReceivedMessage[i] == '\0')
{
OutMessage = FString(UTF8_TO_TCHAR(ReceivedMessage.GetData()));
ReceivedMessage.RemoveAt(0, i + 1);
return true;
}
}
int32 ExtraSpace = 64;
int32 CurrentSize = ReceivedMessage.Num();
ReceivedMessage.SetNum(CurrentSize + ExtraSpace);
int32 ActualRead = 0;
if (!Socket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(10)))
{
return false;
}
else if (!Socket->Recv((uint8*)ReceivedMessage.GetData() + CurrentSize, ExtraSpace, ActualRead))
{
return false;
}
ReceivedMessage.SetNum(CurrentSize + ActualRead);
}
}
ISocketSubsystem* SocketSubsystem;
FSocket* Socket;
TArray<char> ReceivedMessage;
};
class FFMODStudioEditorModule : public IFMODStudioEditorModule
{
public:
/** IModuleInterface implementation */
FFMODStudioEditorModule() :
bSimulating(false),
bIsInPIE(false),
bRegisteredComponentVisualizers(false)
{
}
virtual void StartupModule() override;
virtual void PostLoadCallback() override;
virtual void ShutdownModule() override;
bool HandleSettingsSaved();
/** Called after all banks were reloaded by the studio module */
void HandleBanksReloaded();
/** Show notification */
void ShowNotification(const FText& Text, SNotificationItem::ECompletionState State);
void BeginPIE(bool simulating);
void EndPIE(bool simulating);
void PausePIE(bool simulating);
void ResumePIE(bool simulating);
void ViewportDraw(UCanvas* Canvas, APlayerController*);
bool Tick( float DeltaTime );
/** Add extensions to menu */
void AddHelpMenuExtension(FMenuBuilder& MenuBuilder);
void AddFileMenuExtension(FMenuBuilder& MenuBuilder);
/** Show FMOD version */
void ShowVersion();
/** Open CHM */
void OpenCHM();
/** Open web page to online docs */
void OpenOnlineDocs();
/** Open Video tutorials page */
void OpenVideoTutorials();
/** Set Studio build path */
void ValidateFMOD();
/** Reload banks */
void ReloadBanks();
TArray<FName> RegisteredComponentClassNames;
void RegisterComponentVisualizer(FName ComponentClassName, TSharedPtr<FComponentVisualizer> Visualizer);
/** The delegate to be invoked when this profiler manager ticks. */
FTickerDelegate OnTick;
/** Handle for registered delegates. */
FDelegateHandle TickDelegateHandle;
FDelegateHandle BeginPIEDelegateHandle;
FDelegateHandle EndPIEDelegateHandle;
FDelegateHandle PausePIEDelegateHandle;
FDelegateHandle ResumePIEDelegateHandle;
FDelegateHandle HandleBanksReloadedDelegateHandle;
/** Hook for drawing viewport */
FDebugDrawDelegate ViewportDrawingDelegate;
FDelegateHandle ViewportDrawingDelegateHandle;
TSharedPtr<IComponentAssetBroker> AssetBroker;
/** The extender to pass to the level editor to extend it's window menu */
TSharedPtr<FExtender> MainMenuExtender;
/** Asset type actions for events (edit, play, stop) */
TSharedPtr<FAssetTypeActions_FMODEvent> FMODEventAssetTypeActions;
bool bSimulating;
bool bIsInPIE;
bool bRegisteredComponentVisualizers;
};
IMPLEMENT_MODULE( FFMODStudioEditorModule, FMODStudioEditor )
void FFMODStudioEditorModule::StartupModule()
{
UE_LOG(LogFMOD, Log, TEXT("FFMODStudioEditorModule startup"));
AssetBroker = MakeShareable(new FFMODAssetBroker);
FComponentAssetBrokerage::RegisterBroker(AssetBroker, UFMODAudioComponent::StaticClass(), true, true);
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
{
ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings("Project", "Plugins", "FMODStudio",
LOCTEXT("FMODStudioSettingsName", "FMOD Studio"),
LOCTEXT("FMODStudioDescription", "Configure the FMOD Studio plugin"),
GetMutableDefault<UFMODSettings>()
);
if (SettingsSection.IsValid())
{
SettingsSection->OnModified().BindRaw(this, &FFMODStudioEditorModule::HandleSettingsSaved);
}
}
// Register the details customizations
{
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.RegisterCustomClassLayout("FMODAmbientSound", FOnGetDetailCustomizationInstance::CreateStatic(&FFMODAmbientSoundDetails::MakeInstance));
PropertyModule.NotifyCustomizationModuleChanged();
}
// Need to load the editor module since it gets created after us, and we can't re-order ourselves otherwise our asset registration stops working!
// It only works if we are running the editor, not a commandlet
if (!IsRunningCommandlet())
{
MainMenuExtender = MakeShareable(new FExtender);
MainMenuExtender->AddMenuExtension("HelpBrowse", EExtensionHook::After, NULL, FMenuExtensionDelegate::CreateRaw(this, &FFMODStudioEditorModule::AddHelpMenuExtension));
MainMenuExtender->AddMenuExtension("FileLoadAndSave", EExtensionHook::After, NULL, FMenuExtensionDelegate::CreateRaw(this, &FFMODStudioEditorModule::AddFileMenuExtension));
FLevelEditorModule* LevelEditor = FModuleManager::LoadModulePtr<FLevelEditorModule>(TEXT("LevelEditor"));
if (LevelEditor)
{
LevelEditor->GetMenuExtensibilityManager()->AddExtender(MainMenuExtender);
}
}
// Register AssetTypeActions
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
FMODEventAssetTypeActions = MakeShareable(new FAssetTypeActions_FMODEvent);
AssetTools.RegisterAssetTypeActions(FMODEventAssetTypeActions.ToSharedRef());
// Register slate style overrides
FFMODStudioStyle::Initialize();
BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddRaw(this, &FFMODStudioEditorModule::BeginPIE);
EndPIEDelegateHandle = FEditorDelegates::EndPIE.AddRaw(this, &FFMODStudioEditorModule::EndPIE);
PausePIEDelegateHandle = FEditorDelegates::PausePIE.AddRaw(this, &FFMODStudioEditorModule::PausePIE);
ResumePIEDelegateHandle = FEditorDelegates::ResumePIE.AddRaw(this, &FFMODStudioEditorModule::ResumePIE);
ViewportDrawingDelegate = FDebugDrawDelegate::CreateRaw(this, &FFMODStudioEditorModule::ViewportDraw);
ViewportDrawingDelegateHandle = UDebugDrawService::Register(TEXT("Editor"), ViewportDrawingDelegate);
OnTick = FTickerDelegate::CreateRaw( this, &FFMODStudioEditorModule::Tick );
TickDelegateHandle = FTicker::GetCoreTicker().AddTicker( OnTick );
// This module is loaded after FMODStudioModule
HandleBanksReloadedDelegateHandle = IFMODStudioModule::Get().BanksReloadedEvent().AddRaw(this, &FFMODStudioEditorModule::HandleBanksReloaded);
}
void FFMODStudioEditorModule::AddHelpMenuExtension(FMenuBuilder& MenuBuilder)
{
MenuBuilder.BeginSection("FMODHelp", LOCTEXT("FMODHelpLabel", "FMOD Help"));
MenuBuilder.AddMenuEntry(
LOCTEXT("FMODVersionMenuEntryTitle", "About FMOD Studio"),
LOCTEXT("FMODVersionMenuEntryToolTip", "Shows the informationa about FMOD Studio."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::ShowVersion)));
#if PLATFORM_WINDOWS
MenuBuilder.AddMenuEntry(
LOCTEXT("FMODHelpCHMTitle", "FMOD Documentation..."),
LOCTEXT("FMODHelpCHMToolTip", "Opens the local FMOD documentation."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.BrowseAPIReference"),
FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::OpenCHM)));
#endif
MenuBuilder.AddMenuEntry(
LOCTEXT("FMODHelpOnlineTitle", "FMOD Online Documentation..."),
LOCTEXT("FMODHelpOnlineToolTip", "Go to the online FMOD documentation."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.BrowseDocumentation"),
FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::OpenOnlineDocs)));
MenuBuilder.AddMenuEntry(
LOCTEXT("FMODHelpVideosTitle", "FMOD Tutorial Videos..."),
LOCTEXT("FMODHelpVideosToolTip", "Go to the online FMOD tutorial videos."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tutorials"),
FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::OpenVideoTutorials)));
MenuBuilder.AddMenuEntry(
LOCTEXT("FMODSetStudioBuildTitle", "Validate FMOD"),
LOCTEXT("FMODSetStudioBuildToolTip", "Verifies that FMOD and FMOD Studio are working as expected."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::ValidateFMOD)));
MenuBuilder.EndSection();
}
void FFMODStudioEditorModule::AddFileMenuExtension(FMenuBuilder& MenuBuilder)
{
MenuBuilder.BeginSection("FMODFile", LOCTEXT("FMODFileLabel", "FMOD"));
MenuBuilder.AddMenuEntry(
LOCTEXT("FMODFileMenuEntryTitle", "Reload Banks"),
LOCTEXT("FMODFileMenuEntryToolTip", "Force a manual reload of all FMOD Studio banks."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateRaw(this, &FFMODStudioEditorModule::ReloadBanks)));
MenuBuilder.EndSection();
}
unsigned int GetDLLVersion()
{
// Just grab it from the audition context which is always valid
unsigned int DLLVersion = 0;
FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Auditioning);
if (StudioSystem)
{
FMOD::System* LowLevelSystem = nullptr;
if (StudioSystem->getLowLevelSystem(&LowLevelSystem) == FMOD_OK)
{
LowLevelSystem->getVersion(&DLLVersion);
}
}
return DLLVersion;
}
unsigned int StripPatch(unsigned int FullVersion)
{
return FullVersion & ~0xFF;
}
FString VersionToString(unsigned int FullVersion)
{
return FString::Printf(TEXT("%x.%02x.%02x"), (FullVersion>>16), (FullVersion>>8) & 0xFF, FullVersion & 0xFF);
}
void FFMODStudioEditorModule::ShowVersion()
{
unsigned int HeaderVersion = FMOD_VERSION;
unsigned int DLLVersion = GetDLLVersion();
FText VersionMessage = FText::Format(
LOCTEXT("FMODStudio_About", "FMOD Studio\n\nBuilt Version: {0}\nDLL Version: {1}\n\nCopyright Firelight Technologies Pty Ltd"),
FText::FromString(VersionToString(HeaderVersion)),
FText::FromString(VersionToString(DLLVersion)));
FMessageDialog::Open(EAppMsgType::Ok, VersionMessage);
}
void FFMODStudioEditorModule::OpenCHM()
{
FString APIPath = FPaths::Combine(*FPaths::EngineDir(), TEXT("Plugins/FMODStudio/Docs/FMOD UE4 Integration.chm"));
if( IFileManager::Get().FileSize( *APIPath ) != INDEX_NONE )
{
FString AbsoluteAPIPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*APIPath);
FPlatformProcess::LaunchFileInDefaultExternalApplication(*AbsoluteAPIPath);
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("Documentation", "CannotFindFMODIntegration", "Cannot open FMOD Studio Integration CHM reference; help file not found."));
}
}
void FFMODStudioEditorModule::OpenOnlineDocs()
{
FPlatformProcess::LaunchFileInDefaultExternalApplication(TEXT("http://www.fmod.org/documentation"));
}
void FFMODStudioEditorModule::OpenVideoTutorials()
{
FPlatformProcess::LaunchFileInDefaultExternalApplication(TEXT("http://www.youtube.com/user/FMODTV"));
}
void FFMODStudioEditorModule::ValidateFMOD()
{
int ProblemsFound = 0;
FFMODStudioLink StudioLink;
bool Connected = StudioLink.Connect();
if (!Connected)
{
if (EAppReturnType::No == FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("SetStudioBuildStudioNotRunning", "FMODStudio does not appear to be running. Only some validation will occur. Do you want to continue anyway?")))
{
return;
}
}
unsigned int HeaderVersion = FMOD_VERSION;
unsigned int DLLVersion = GetDLLVersion();
unsigned int StudioVersion = 0;
if (Connected)
{
FString StudioVersionString;
if (StudioLink.Execute(TEXT("studio.version"), StudioVersionString))
{
int Super = 0;
int Major = 0;
int Minor = 0;
sscanf(TCHAR_TO_UTF8(*StudioVersionString), "Version %x.%x.%x", &Super, &Major, &Minor);
StudioVersion = (Super<<16) | (Major<<8) | Minor;
}
}
if (StripPatch(HeaderVersion) != StripPatch(DLLVersion))
{
FText VersionMessage = FText::Format(
LOCTEXT("SetStudioBuildStudio_Status", "The FMOD DLL version is different to the version the integration was built against. This may cause problems running the game.\nBuilt Version: {0}\nDLL Version: {1}\n"),
FText::FromString(VersionToString(HeaderVersion)),
FText::FromString(VersionToString(DLLVersion)));
FMessageDialog::Open(EAppMsgType::Ok, VersionMessage);
ProblemsFound++;
}
if (StudioVersion > DLLVersion)
{
FText VersionMessage = FText::Format(
LOCTEXT("SetStudioBuildStudio_Version", "The Studio tool is newer than the version the integration was built against. The integration may not be able to load the banks that the tool builds.\n\nBuilt Version: {0}\nDLL Version: {1}\nStudio Version: {2}\n\nWe recommend using the Studio tool that matches the integration.\n\nDo you want to continue with the validation?"),
FText::FromString(VersionToString(HeaderVersion)),
FText::FromString(VersionToString(DLLVersion)),
FText::FromString(VersionToString(StudioVersion)));
if (EAppReturnType::No == FMessageDialog::Open(EAppMsgType::YesNo, VersionMessage))
{
return;
}
ProblemsFound++;
}
const UFMODSettings& Settings = *GetDefault<UFMODSettings>();
FString FullBankPath = Settings.BankOutputDirectory.Path;
if (FPaths::IsRelative(FullBankPath))
{
FullBankPath = FPaths::GameContentDir() / FullBankPath;
}
FString PlatformBankPath = Settings.GetFullBankPath();
FullBankPath = FPaths::ConvertRelativePathToFull(FullBankPath);
PlatformBankPath = FPaths::ConvertRelativePathToFull(PlatformBankPath);
if (Connected)
{
// File path was added in FMOD Studio 1.07.00
FString StudioProjectPath;
FString StudioProjectDir;
if (StudioVersion >= 0x00010700)
{
StudioLink.Execute(TEXT("studio.project.filePath"), StudioProjectPath);
if (StudioProjectPath.IsEmpty() || StudioProjectPath == TEXT("undefined"))
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_NewProject", "FMOD Studio has an empty project. Please go to FMOD Studio, and press Save to create your new project."));
// Just try to save anyway
FString Result;
StudioLink.Execute(TEXT("studio.project.save()"), Result);
}
StudioLink.Execute(TEXT("studio.project.filePath"), StudioProjectPath);
if (StudioProjectPath != TEXT("undefined"))
{
StudioProjectDir = FPaths::GetPath(StudioProjectPath);
}
}
FString StudioPathString;
StudioLink.Execute(TEXT("studio.project.workspace.builtBanksOutputDirectory"), StudioPathString);
if (StudioPathString == TEXT("undefined"))
{
StudioPathString = TEXT("");
}
FString CanonicalBankPath = FullBankPath;
FPaths::CollapseRelativeDirectories(CanonicalBankPath);
FPaths::NormalizeDirectoryName(CanonicalBankPath);
FPaths::RemoveDuplicateSlashes(CanonicalBankPath);
FPaths::NormalizeDirectoryName(CanonicalBankPath);
FString CanonicalStudioPath = StudioPathString;
if (FPaths::IsRelative(CanonicalStudioPath) && !StudioProjectDir.IsEmpty() && !StudioPathString.IsEmpty())
{
CanonicalStudioPath = FPaths::Combine(*StudioProjectDir, *CanonicalStudioPath);
}
FPaths::CollapseRelativeDirectories(CanonicalStudioPath);
FPaths::NormalizeDirectoryName(CanonicalStudioPath);
FPaths::RemoveDuplicateSlashes(CanonicalStudioPath);
FPaths::NormalizeDirectoryName(CanonicalStudioPath);
if (!FPaths::IsSamePath(CanonicalBankPath, CanonicalStudioPath))
{
FString BankPathToSet = FullBankPath;
// Extra logic - if we have put the studio project inside the game project, then make it relative
if (!StudioProjectDir.IsEmpty())
{
FString GameBaseDir = FPaths::ConvertRelativePathToFull(FPaths::GameDir());
FString BankPathFromGameProject = FullBankPath;
FString StudioProjectFromGameProject = StudioProjectDir;
if (FPaths::MakePathRelativeTo(BankPathFromGameProject, *GameBaseDir) && !BankPathFromGameProject.Contains(TEXT("..")) &&
FPaths::MakePathRelativeTo(StudioProjectFromGameProject, *GameBaseDir) && !StudioProjectFromGameProject.Contains(TEXT("..")))
{
FPaths::MakePathRelativeTo(BankPathToSet, *(StudioProjectDir + TEXT("/")));
}
}
ProblemsFound++;
FText AskMessage = FText::Format(
LOCTEXT("SetStudioBuildStudio_Ask", "FMOD Studio build path should be set up.\n\nCurrent Studio build path: {0}\nNew build path: {1}\n\nDo you want to fix up the project now?"),
FText::FromString(StudioPathString),
FText::FromString(BankPathToSet));
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, AskMessage))
{
FString Result;
StudioLink.Execute(*FString::Printf(TEXT("studio.project.workspace.builtBanksOutputDirectory = \"%s\";"), *BankPathToSet), Result);
StudioLink.Execute(TEXT("studio.project.workspace.builtBanksOutputDirectory"), Result);
if (Result != BankPathToSet)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_Save", "Failed to set bank directory. Please go to FMOD Studio, and set the bank path in FMOD Studio project settings."));
}
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_Save", "Please go to FMOD Studio, save your project and build banks."));
// Just try to do it again anyway
StudioLink.Execute(TEXT("studio.project.save()"), Result);
StudioLink.Execute(TEXT("studio.project.build()"), Result);
// Pretend settings have changed which will force a reload
IFMODStudioModule::Get().RefreshSettings();
}
}
}
bool AnyBankFiles = false;
if (!FPaths::DirectoryExists(FullBankPath) || !FPaths::DirectoryExists(PlatformBankPath))
{
FText DirMessage = FText::Format(
LOCTEXT("SetStudioBuildStudio_Dir", "The FMOD Content directory does not exist. Please make sure FMOD Studio is exporting banks into the correct location.\n\nBanks should be exported to: {0}\nBanks files should exist in: {1}\n"),
FText::FromString(FullBankPath),
FText::FromString(PlatformBankPath));
FMessageDialog::Open(EAppMsgType::Ok, DirMessage);
ProblemsFound++;
}
else
{
TArray<FString> BankFiles;
Settings.GetAllBankPaths(BankFiles, true);
if (BankFiles.Num() != 0)
{
AnyBankFiles = true;
}
else
{
FText EmptyBankDirMessage = FText::Format(
LOCTEXT("SetStudioBuildStudio_EmptyBankDir", "The FMOD Content directory does not have any bank files in them. Please make sure FMOD Studio is exporting banks into the correct location.\n\nBanks should be exported to: {0}\nBanks files should exist in: {1}\n"),
FText::FromString(FullBankPath),
FText::FromString(PlatformBankPath));
FMessageDialog::Open(EAppMsgType::Ok, EmptyBankDirMessage);
ProblemsFound++;
}
}
if (AnyBankFiles)
{
FMOD::Studio::System* StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Auditioning);
int BankCount = 0;
StudioSystem->getBankCount(&BankCount);
TArray<FString> FailedBanks = IFMODStudioModule::Get().GetFailedBankLoads(EFMODSystemContext::Auditioning);
if (BankCount == 0 || FailedBanks.Num() != 0)
{
FString CombinedBanks;
for (auto Bank : FailedBanks)
{
CombinedBanks += Bank;
CombinedBanks += TEXT("\n");
}
FText BankLoadMessage;
if (BankCount == 0 && FailedBanks.Num() == 0)
{
BankLoadMessage = LOCTEXT("SetStudioBuildStudio_BankLoad", "Failed to load banks\n");
}
else if (BankCount == 0)
{
BankLoadMessage = FText::Format(
LOCTEXT("SetStudioBuildStudio_BankLoad", "Failed to load banks:\n{0}\n"),
FText::FromString(CombinedBanks));
}
else
{
BankLoadMessage = FText::Format(
LOCTEXT("SetStudioBuildStudio_BankLoad", "Some banks failed to load:\n{0}\n"),
FText::FromString(CombinedBanks));
}
FMessageDialog::Open(EAppMsgType::Ok, BankLoadMessage);
ProblemsFound++;
}
else
{
int TotalEventCount = 0;
TArray<FMOD::Studio::Bank*> Banks;
Banks.SetNum(BankCount);
StudioSystem->getBankList(Banks.GetData(), BankCount, &BankCount);
for (FMOD::Studio::Bank* Bank : Banks)
{
int EventCount = 0;
Bank->getEventCount(&EventCount);
TotalEventCount += EventCount;
}
if (TotalEventCount == 0)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_NoEvents", "Banks have been loaded but they didn't have any events in them. Please make sure you have added some events to banks."));
ProblemsFound++;
}
}
}
TArray<FString> RequiredPlugins = IFMODStudioModule::Get().GetRequiredPlugins();
if (RequiredPlugins.Num() != 0 && Settings.PluginFiles.Num() == 0)
{
FString CombinedPlugins;
for (auto Name : RequiredPlugins)
{
CombinedPlugins += Name;
CombinedPlugins += TEXT("\n");
}
FText PluginMessage = FText::Format(
LOCTEXT("SetStudioBuildStudio_Plugins", "The banks require the following plugins, but no plugin filenames are listed in the settings:\n{0}\n"),
FText::FromString(CombinedPlugins));
FMessageDialog::Open(EAppMsgType::Ok, PluginMessage);
ProblemsFound++;
}
if (ProblemsFound)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_FinishedBad", "Finished validation. Problems were detected.\n"));
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetStudioBuildStudio_FinishedGood", "Finished validation. No problems detected.\n"));
}
}
void FFMODStudioEditorModule::ReloadBanks()
{
// Pretend settings have changed which will force a reload
IFMODStudioModule::Get().RefreshSettings();
}
bool FFMODStudioEditorModule::Tick( float DeltaTime )
{
if (!bRegisteredComponentVisualizers && GUnrealEd != nullptr)
{
// Register component visualizers (GUnrealED is required for this, but not initialized before this module loads, so we have to wait until GUnrealEd is available)
RegisterComponentVisualizer(UFMODAudioComponent::StaticClass()->GetFName(), MakeShareable(new FFMODAudioComponentVisualizer));
bRegisteredComponentVisualizers = true;
}
return true;
}
void FFMODStudioEditorModule::BeginPIE(bool simulating)
{
UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule BeginPIE: %d"), simulating);
bSimulating = simulating;
bIsInPIE = true;
IFMODStudioModule::Get().SetInPIE(true, simulating);
}
void FFMODStudioEditorModule::EndPIE(bool simulating)
{
UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule EndPIE: %d"), simulating);
bSimulating = false;
bIsInPIE = false;
IFMODStudioModule::Get().SetInPIE(false, simulating);
}
void FFMODStudioEditorModule::PausePIE(bool simulating)
{
UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule PausePIE%d"));
IFMODStudioModule::Get().SetSystemPaused(true);
}
void FFMODStudioEditorModule::ResumePIE(bool simulating)
{
UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule ResumePIE"));
IFMODStudioModule::Get().SetSystemPaused(false);
}
void FFMODStudioEditorModule::PostLoadCallback()
{
UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule PostLoadCallback"));
}
void FFMODStudioEditorModule::ViewportDraw(UCanvas* Canvas, APlayerController*)
{
// Only want to update based on viewport in simulate mode.
// In PIE/game, we update from the player controller instead.
if (!bSimulating)
{
return;
}
const FSceneView* View = Canvas->SceneView;
if (View->Drawer == GCurrentLevelEditingViewportClient)
{
UWorld* World = GCurrentLevelEditingViewportClient->GetWorld();
const FVector& ViewLocation = GCurrentLevelEditingViewportClient->GetViewLocation();
FMatrix CameraToWorld = View->ViewMatrices.ViewMatrix.InverseFast();
FVector ProjUp = CameraToWorld.TransformVector(FVector(0, 1000, 0));
FVector ProjRight = CameraToWorld.TransformVector(FVector(1000, 0, 0));
FTransform ListenerTransform(FRotationMatrix::MakeFromZY(ProjUp, ProjRight));
ListenerTransform.SetTranslation(ViewLocation);
ListenerTransform.NormalizeRotation();
IFMODStudioModule::Get().SetListenerPosition(0, World, ListenerTransform, 0.0f);
IFMODStudioModule::Get().FinishSetListenerPosition(1, 0.0f);
}
}
void FFMODStudioEditorModule::ShutdownModule()
{
UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioEditorModule shutdown"));
if (UObjectInitialized())
{
// Unregister tick function.
FTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle);
FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle);
FEditorDelegates::EndPIE.Remove(EndPIEDelegateHandle);
FEditorDelegates::PausePIE.Remove(PausePIEDelegateHandle);
FEditorDelegates::ResumePIE.Remove(ResumePIEDelegateHandle);
if (ViewportDrawingDelegate.IsBound())
{
UDebugDrawService::Unregister(ViewportDrawingDelegateHandle);
}
FComponentAssetBrokerage::UnregisterBroker(AssetBroker);
if (MainMenuExtender.IsValid())
{
FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr<FLevelEditorModule>( "LevelEditor" );
if (LevelEditorModule)
{
LevelEditorModule->GetMenuExtensibilityManager()->RemoveExtender(MainMenuExtender);
}
}
}
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
{
SettingsModule->UnregisterSettings("Project", "Plugins", "FMODStudio");
}
// Unregister AssetTypeActions
if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
{
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
AssetTools.UnregisterAssetTypeActions(FMODEventAssetTypeActions.ToSharedRef());
}
// Unregister component visualizers
if (GUnrealEd != nullptr)
{
// Iterate over all class names we registered for
for (FName ClassName : RegisteredComponentClassNames)
{
GUnrealEd->UnregisterComponentVisualizer(ClassName);
}
}
IFMODStudioModule::Get().BanksReloadedEvent().Remove(HandleBanksReloadedDelegateHandle);
}
bool FFMODStudioEditorModule::HandleSettingsSaved()
{
IFMODStudioModule::Get().RefreshSettings();
return true;
}
void FFMODStudioEditorModule::HandleBanksReloaded()
{
// Show a reload notification
TArray<FString> FailedBanks = IFMODStudioModule::Get().GetFailedBankLoads(EFMODSystemContext::Auditioning);
FText Message;
SNotificationItem::ECompletionState State;
if (FailedBanks.Num() == 0)
{
Message = LOCTEXT("FMODBanksReloaded", "Reloaded FMOD Banks\n");
State = SNotificationItem::CS_Success;
}
else
{
FString CombinedMessage = "Problem loading FMOD Banks:";
for (auto Entry : FailedBanks)
{
CombinedMessage += TEXT("\n");
CombinedMessage += Entry;
UE_LOG(LogFMOD, Warning, TEXT("Problem loading FMOD Bank: %s"), *Entry);
}
Message = FText::Format(
LOCTEXT("FMODBanksReloaded", "{0}"),
FText::FromString(CombinedMessage));
State = SNotificationItem::CS_Fail;
}
ShowNotification(Message, State);
}
void FFMODStudioEditorModule::ShowNotification(const FText& Text, SNotificationItem::ECompletionState State)
{
FNotificationInfo Info(Text);
Info.Image = FEditorStyle::GetBrush(TEXT("NoBrush"));
Info.FadeInDuration = 0.1f;
Info.FadeOutDuration = 0.5f;
Info.ExpireDuration = State == SNotificationItem::CS_Fail ? 6.0f : 1.5f;
Info.bUseThrobber = false;
Info.bUseSuccessFailIcons = true;
Info.bUseLargeFont = true;
Info.bFireAndForget = false;
Info.bAllowThrottleWhenFrameRateIsLow = false;
auto NotificationItem = FSlateNotificationManager::Get().AddNotification(Info);
NotificationItem->SetCompletionState(State);
NotificationItem->ExpireAndFadeout();
if (GCurrentLevelEditingViewportClient)
{
// Refresh any 3d event visualization
GCurrentLevelEditingViewportClient->bNeedsRedraw = true;
}
}
void FFMODStudioEditorModule::RegisterComponentVisualizer(FName ComponentClassName, TSharedPtr<FComponentVisualizer> Visualizer)
{
if (GUnrealEd != nullptr)
{
GUnrealEd->RegisterComponentVisualizer(ComponentClassName, Visualizer);
}
RegisteredComponentClassNames.Add(ComponentClassName);
if (Visualizer.IsValid())
{
Visualizer->OnRegister();
}
}