Initial push to repo
This commit is contained in:
65
Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditor.cpp
Normal file
65
Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditor.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#include "SUDSEditor.h"
|
||||
|
||||
#include "ISettingsModule.h"
|
||||
#include "ISettingsSection.h"
|
||||
#include "SUDSEditorSettings.h"
|
||||
#include "SUDSScriptActions.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Styling/SlateStyle.h"
|
||||
#include "Styling/SlateStyleRegistry.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FSUDSModule"
|
||||
|
||||
void FSUDSEditorModule::StartupModule()
|
||||
{
|
||||
ScriptActions = MakeShared<FSUDSScriptActions>();
|
||||
FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(ScriptActions.ToSharedRef());
|
||||
|
||||
auto SudsPlugin = IPluginManager::Get().FindPlugin(TEXT("SUDS"));
|
||||
if (SudsPlugin.IsValid())
|
||||
{
|
||||
const FString IconDir = SudsPlugin->GetBaseDir() + "/Content/Editor/Slate/Icons";
|
||||
const FVector2D Sz16 = FVector2D(16.0f, 16.0f);
|
||||
const FVector2D Sz64 = FVector2D(64.0f, 64.0f);
|
||||
|
||||
StyleSet = MakeShared<FSlateStyleSet>("SUDSStyleSet");
|
||||
StyleSet->Set(TEXT("ClassIcon.SUDSScript"), new FSlateImageBrush(IconDir / TEXT("SUDSScript_16x.png"), Sz16));
|
||||
StyleSet->Set(TEXT("ClassThumbnail.SUDSScript"), new FSlateImageBrush(IconDir / TEXT("SUDSScript_64x.png"), Sz64));
|
||||
|
||||
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
|
||||
}
|
||||
|
||||
// register settings
|
||||
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
|
||||
if (SettingsModule)
|
||||
{
|
||||
ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings("Project", "Plugins", "SUDS Editor",
|
||||
LOCTEXT("SUDSEditorSettingsName", "SUDS Editor"),
|
||||
LOCTEXT("SUDSEditorSettingsDescription", "Configure the editor parts of SUDS."),
|
||||
GetMutableDefault<USUDSEditorSettings>()
|
||||
);
|
||||
}
|
||||
|
||||
UE_LOG(LogSUDSEditor, Log, TEXT("SUDS Editor Module Started"))
|
||||
|
||||
}
|
||||
|
||||
void FSUDSEditorModule::ShutdownModule()
|
||||
{
|
||||
if (StyleSet.IsValid())
|
||||
{
|
||||
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get());
|
||||
}
|
||||
|
||||
if (!FModuleManager::Get().IsModuleLoaded("AssetTools")) return;
|
||||
FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(ScriptActions.ToSharedRef());
|
||||
|
||||
UE_LOG(LogSUDSEditor, Log, TEXT("SUDS Editor Module Shut Down"))
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FSUDSEditorModule, SUDSEditor)
|
||||
DEFINE_LOG_CATEGORY(LogSUDSEditor);
|
||||
280
Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditorScriptTools.cpp
Normal file
280
Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditorScriptTools.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#include "SUDSEditorScriptTools.h"
|
||||
|
||||
#include "SUDSMessageLogger.h"
|
||||
#include "SUDSScript.h"
|
||||
#include "SUDSScriptImporter.h"
|
||||
#include "SUDSScriptNode.h"
|
||||
#include "SUDSScriptNodeGosub.h"
|
||||
#include "SUDSScriptNodeSet.h"
|
||||
#include "SUDSScriptNodeText.h"
|
||||
#include "EditorFramework/AssetImportData.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/PackageName.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "UObject/Package.h"
|
||||
|
||||
void FSUDSEditorScriptTools::WriteBackTextIDs(USUDSScript* Script, FSUDSMessageLogger& Logger)
|
||||
{
|
||||
const auto& SrcData = Script->AssetImportData->SourceData;
|
||||
if (SrcData.SourceFiles.Num() == 1)
|
||||
{
|
||||
TArray<FString> Lines;
|
||||
FString SourceFile = SrcData.SourceFiles[0].RelativeFilename;
|
||||
auto Package = Script->GetPackage();
|
||||
if (FPaths::IsRelative(SourceFile) && Package)
|
||||
{
|
||||
FString PackagePath = FPackageName::LongPackageNameToFilename(FPackageName::GetLongPackagePath(Package->GetPathName()));
|
||||
SourceFile = FPaths::ConvertRelativePathToFull(PackagePath, SourceFile);
|
||||
}
|
||||
if (FFileHelper::LoadFileToStringArray(Lines, *SourceFile))
|
||||
{
|
||||
const int PrevErrs = Logger.NumErrors();
|
||||
const bool bHeaderChanges = WriteBackTextIDsFromNodes(Script->GetHeaderNodes(), Lines, Script->GetName(), Logger);
|
||||
const bool bBodyChanges = WriteBackTextIDsFromNodes(Script->GetNodes(), Lines, Script->GetName(), Logger);
|
||||
|
||||
if (Logger.NumErrors() > PrevErrs)
|
||||
{
|
||||
Logger.AddMessage(EMessageSeverity::Info,
|
||||
FText::FromString(FString::Printf(TEXT("Errors prevented saving updates to %s."), *Script->GetName())));
|
||||
}
|
||||
else if (bHeaderChanges || bBodyChanges)
|
||||
{
|
||||
// We need to re-hash file in memory before saving to prevent re-import
|
||||
int32 Length = 10;
|
||||
for(const FString& Line : Lines)
|
||||
{
|
||||
Length += Line.Len() + UE_ARRAY_COUNT(LINE_TERMINATOR);
|
||||
}
|
||||
FString CombinedString;
|
||||
CombinedString.Reserve(Length);
|
||||
|
||||
for(const FString& Line : Lines)
|
||||
{
|
||||
CombinedString += Line;
|
||||
CombinedString += LINE_TERMINATOR;
|
||||
}
|
||||
|
||||
// Write source file back; always encode as UTF8 not default ANSI/UTF16
|
||||
// Do this before updating asset import data so it picks up new file timestamp
|
||||
FFileHelper::SaveStringToFile(FStringView(CombinedString), *SourceFile, FFileHelper::EEncodingOptions::ForceUTF8);
|
||||
|
||||
/* BEGIN try to prevent re-import prompt
|
||||
* This unfortunately didn't work, so removed & let it reimport
|
||||
|
||||
const FMD5Hash FileHash = FSUDSScriptImporter::CalculateHash(*CombinedString, CombinedString.Len());
|
||||
Script->AssetImportData->Update(SourceFile, FileHash);
|
||||
|
||||
// Need to mark asset dirty since hash has changed
|
||||
// Try to find the outer package so we can dirty it up
|
||||
if (Script->GetOuter())
|
||||
{
|
||||
Script->GetOuter()->MarkPackageDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
Script->MarkPackageDirty();
|
||||
}
|
||||
|
||||
// Even this doesn't work:
|
||||
GUnrealEd->AutoReimportManager->IgnoreFileModification(SourceFile);
|
||||
|
||||
* END try to prevent re-import
|
||||
*/
|
||||
|
||||
Logger.AddMessage(EMessageSeverity::Info,
|
||||
FText::FromString(FString::Printf(TEXT("Successfully updated %s"), *SourceFile)));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.AddMessage(EMessageSeverity::Info,
|
||||
FText::FromString(FString::Printf(TEXT("No changes were required to %s"), *SourceFile)));
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.AddMessage(EMessageSeverity::Error,
|
||||
FText::FromString(FString::Printf(TEXT("Error opening source asset %s"), *SourceFile)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.AddMessage(EMessageSeverity::Error,
|
||||
FText::FromString(
|
||||
FString::Printf(TEXT("No source files associated with asset %s"), *Script->GetName())));
|
||||
}
|
||||
}
|
||||
|
||||
bool FSUDSEditorScriptTools::WriteBackTextIDsFromNodes(const TArray<USUDSScriptNode*> Nodes, TArray<FString>& Lines, const FString& NameForErrors, FSUDSMessageLogger& Logger)
|
||||
{
|
||||
bool bAnyChanges = false;
|
||||
// For each speaker line, set line and choice edge, use source line no to append text ID
|
||||
for (const auto& N : Nodes)
|
||||
{
|
||||
if (N->GetNodeType() == ESUDSScriptNodeType::Text)
|
||||
{
|
||||
if (const auto* TN = Cast<USUDSScriptNodeText>(N))
|
||||
{
|
||||
bAnyChanges = WriteBackTextID(TN->GetText(), TN->GetSourceLineNo(), Lines, NameForErrors, Logger) || bAnyChanges;
|
||||
}
|
||||
}
|
||||
else if (N->GetNodeType() == ESUDSScriptNodeType::SetVariable)
|
||||
{
|
||||
if (const auto* SN = Cast<USUDSScriptNodeSet>(N))
|
||||
{
|
||||
if (SN->GetExpression().IsTextLiteral())
|
||||
{
|
||||
FText Literal = SN->GetExpression().GetTextLiteralValue();
|
||||
|
||||
bAnyChanges = WriteBackTextID(Literal, SN->GetSourceLineNo(), Lines, NameForErrors, Logger) || bAnyChanges;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if (N->GetNodeType() == ESUDSScriptNodeType::Choice)
|
||||
{
|
||||
// Edges
|
||||
for (auto& Edge : N->GetEdges())
|
||||
{
|
||||
bAnyChanges = WriteBackTextID(Edge.GetText(), Edge.GetSourceLineNo(), Lines, NameForErrors, Logger) || bAnyChanges;
|
||||
}
|
||||
}
|
||||
else if (N->GetNodeType() == ESUDSScriptNodeType::Gosub)
|
||||
{
|
||||
if (const auto* GN = Cast<USUDSScriptNodeGosub>(N))
|
||||
// Need to write back Gosub IDs so that saved return stacks work
|
||||
bAnyChanges = WriteBackGosubID(GN->GetGosubID(), GN->GetSourceLineNo(), Lines, NameForErrors, Logger) || bAnyChanges;
|
||||
}
|
||||
}
|
||||
|
||||
return bAnyChanges;
|
||||
}
|
||||
|
||||
bool FSUDSEditorScriptTools::WriteBackTextID(const FText& AssetText, int LineNo, TArray<FString>& Lines, const FString& NameForErrors, FSUDSMessageLogger& Logger)
|
||||
{
|
||||
if (AssetText.IsEmpty())
|
||||
return false;
|
||||
|
||||
// Line numbers are 1-based
|
||||
const int Idx = LineNo - 1;
|
||||
|
||||
if (!Lines.IsValidIndex(Idx))
|
||||
{
|
||||
Logger.AddMessage(EMessageSeverity::Error,
|
||||
FText::FromString(FString::Printf(
|
||||
TEXT("Cannot write back TextID to '%s', source line number %d is invalid (Text: '%s')"),
|
||||
*NameForErrors,
|
||||
LineNo,
|
||||
*AssetText.ToString())));
|
||||
return false;
|
||||
}
|
||||
|
||||
const FString& SourceLine = Lines[Idx];
|
||||
const FString TextID = SUDS_GET_TEXT_KEY(AssetText);
|
||||
FString ExistingTextID;
|
||||
int ExistingNum;
|
||||
FStringView SourceLineView(SourceLine);
|
||||
const bool bFoundExisting = FSUDSScriptImporter::RetrieveTextIDFromLine(SourceLineView, ExistingTextID, ExistingNum);
|
||||
|
||||
// Existing TextID - replace if already there
|
||||
if (!bFoundExisting || ExistingTextID != TextID)
|
||||
{
|
||||
if (TextIDCheckMatch(AssetText, SourceLine))
|
||||
{
|
||||
FString Prefix(SourceLineView);
|
||||
FString UpdatedLine = FString::Printf(TEXT("%s %s"), *Prefix, *TextID);
|
||||
Lines[Idx] = UpdatedLine;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.AddMessage(EMessageSeverity::Error,
|
||||
FText::FromString(FString::Printf(TEXT(
|
||||
"Tried to set TextID on line %d of %s but source file did not contain expected text '%s'"
|
||||
),
|
||||
LineNo,
|
||||
*NameForErrors,
|
||||
*AssetText.ToString())));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Same, no need to change
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool FSUDSEditorScriptTools::TextIDCheckMatch(const FText& AssetText, const FString& SourceLine)
|
||||
{
|
||||
// Text from our asset must be present in the text in the source line
|
||||
// However, in case this is a multi-line string, take the first line only
|
||||
FString AssetStr = AssetText.ToString();
|
||||
{
|
||||
FString L, R;
|
||||
if (AssetStr.Split("\n", &L, &R))
|
||||
{
|
||||
AssetStr = L.TrimStartAndEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// Bear in mind that this line might be a text line, a choice or a set line
|
||||
// therefore we only check if the source line contains the asset text
|
||||
|
||||
return SourceLine.Contains(AssetStr);
|
||||
|
||||
}
|
||||
|
||||
bool FSUDSEditorScriptTools::WriteBackGosubID(const FString& GosubID,
|
||||
int LineNo,
|
||||
TArray<FString>& Lines,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger& Logger)
|
||||
{
|
||||
|
||||
// Line numbers are 1-based
|
||||
const int Idx = LineNo - 1;
|
||||
|
||||
if (!Lines.IsValidIndex(Idx))
|
||||
{
|
||||
Logger.AddMessage(EMessageSeverity::Error,
|
||||
FText::FromString(FString::Printf(
|
||||
TEXT("Cannot write back GosubID to '%s', source line number %d is invalid"),
|
||||
*NameForErrors,
|
||||
LineNo)));
|
||||
return false;
|
||||
}
|
||||
|
||||
const FString& SourceLine = Lines[Idx];
|
||||
FString ExistingID;
|
||||
int ExistingNum;
|
||||
FStringView SourceLineView(SourceLine);
|
||||
const bool bFoundID = FSUDSScriptImporter::RetrieveGosubIDFromLine(SourceLineView, ExistingID, ExistingNum);
|
||||
|
||||
if (!bFoundID || ExistingID != GosubID)
|
||||
{
|
||||
// Check it's a gosub line
|
||||
if (SourceLine.Contains(TEXT("gosub")) || SourceLine.Contains(TEXT("go sub")))
|
||||
{
|
||||
FString Prefix(SourceLineView);
|
||||
FString UpdatedLine = FString::Printf(TEXT("%s %s"), *Prefix, *GosubID);
|
||||
Lines[Idx] = UpdatedLine;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.AddMessage(EMessageSeverity::Error,
|
||||
FText::FromString(FString::Printf(TEXT(
|
||||
"Tried to set GosubID on line %d of %s but source file did not have a gosubu on that line"
|
||||
),
|
||||
LineNo,
|
||||
*NameForErrors)));
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
// Same, no need to change
|
||||
return false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#include "SUDSEditorSettings.h"
|
||||
#include "Misc/Paths.h"
|
||||
|
||||
bool USUDSEditorSettings::ShouldGenerateVoiceAssets(const FString& PackagePath) const
|
||||
{
|
||||
if (AlwaysAutoGenerateVoiceOverAssetsOnImport)
|
||||
return true;
|
||||
|
||||
for (auto Dir : DirectoriesToAutoGenerateVoiceOverAssetsOnImport)
|
||||
{
|
||||
if (FPaths::IsUnderDirectory(PackagePath, Dir.Path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FString USUDSEditorSettings::GetVoiceOutputDir(const FString& PackagePath, const FString& ScriptName) const
|
||||
{
|
||||
return GetOutputDir(DialogueVoiceAssetLocation, DialogueVoiceAssetSharedDir.Path, PackagePath, ScriptName);
|
||||
}
|
||||
|
||||
FString USUDSEditorSettings::GetWaveOutputDir(const FString& PackagePath, const FString& ScriptName) const
|
||||
{
|
||||
return GetOutputDir(DialogueWaveAssetLocation, DialogueWaveAssetSharedDir.Path, PackagePath, ScriptName);
|
||||
}
|
||||
|
||||
FString USUDSEditorSettings::GetOutputDir(ESUDSAssetLocation Location,
|
||||
const FString& SharedPath,
|
||||
const FString& PackagePath,
|
||||
const FString& ScriptName)
|
||||
{
|
||||
switch(Location)
|
||||
{
|
||||
default:
|
||||
case ESUDSAssetLocation::SharedDirectory:
|
||||
return SharedPath;
|
||||
case ESUDSAssetLocation::SharedDirectorySubdir:
|
||||
return FPaths::Combine(SharedPath, ScriptName);
|
||||
case ESUDSAssetLocation::ScriptDirectory:
|
||||
return PackagePath;
|
||||
case ESUDSAssetLocation::ScriptDirectorySubdir:
|
||||
return FPaths::Combine(PackagePath, ScriptName);
|
||||
}
|
||||
}
|
||||
1567
Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditorToolkit.cpp
Normal file
1567
Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditorToolkit.cpp
Normal file
File diff suppressed because it is too large
Load Diff
343
Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditorToolkit.h
Normal file
343
Plugins/SUDS/Source/SUDSEditor/Private/SUDSEditorToolkit.h
Normal file
@@ -0,0 +1,343 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SUDSValue.h"
|
||||
#include "Framework/Commands/Commands.h"
|
||||
#include "Framework/Text/BaseTextLayoutMarshaller.h"
|
||||
#include "Toolkits/AssetEditorToolkit.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Widgets/Views/STableRow.h"
|
||||
|
||||
class SMultiLineEditableTextBox;
|
||||
struct FSUDSValue;
|
||||
class USUDSDialogue;
|
||||
class USUDSScript;
|
||||
|
||||
#define LOCTEXT_NAMESPACE "SUDS"
|
||||
|
||||
class FSUDSToolbarCommands
|
||||
: public TCommands<FSUDSToolbarCommands>
|
||||
{
|
||||
public:
|
||||
|
||||
FSUDSToolbarCommands()
|
||||
: TCommands<FSUDSToolbarCommands>(
|
||||
"SUDS",
|
||||
LOCTEXT("SUDS", "Steves Unreal Dialogue System"),
|
||||
NAME_None,
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 0
|
||||
FAppStyle::GetAppStyleSetName()
|
||||
#else
|
||||
FEditorStyle::GetStyleSetName()
|
||||
#endif
|
||||
)
|
||||
{ }
|
||||
|
||||
public:
|
||||
|
||||
// TCommands interface
|
||||
|
||||
virtual void RegisterCommands() override
|
||||
{
|
||||
UI_COMMAND(StartDialogue, "Start Dialogue", "Start/restart dialogue", EUserInterfaceActionType::Button, FInputChord());
|
||||
UI_COMMAND(WriteBackTextIDs, "Write String Keys", "Write string keys back to script source to stabilise for localisation / voice asset links", EUserInterfaceActionType::Button, FInputChord());
|
||||
UI_COMMAND(GenerateVOAssets, "Generate Voice Assets", "Generate DialogueVoice and DialogueWave assets for VO", EUserInterfaceActionType::Button, FInputChord());
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
TSharedPtr<FUICommandInfo> StartDialogue;
|
||||
TSharedPtr<FUICommandInfo> WriteBackTextIDs;
|
||||
TSharedPtr<FUICommandInfo> GenerateVOAssets;
|
||||
};
|
||||
|
||||
|
||||
class FSUDSEditorOutputRow
|
||||
{
|
||||
public:
|
||||
FText Prefix;
|
||||
FText Line;
|
||||
FSlateColor PrefixColour;
|
||||
FSlateColor LineColour;
|
||||
FSlateColor BgColour;
|
||||
|
||||
FSUDSEditorOutputRow(const FText& InPrefix,
|
||||
const FText& InLine,
|
||||
const FSlateColor& InPrefixColour,
|
||||
const FSlateColor& InLineColour,
|
||||
const FSlateColor& InBgColour) :
|
||||
Prefix(InPrefix),
|
||||
Line(InLine),
|
||||
PrefixColour(InPrefixColour),
|
||||
LineColour(InLineColour),
|
||||
BgColour(InBgColour)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
class SSUDSEditorOutputItem : public SMultiColumnTableRow< TSharedPtr<FString> >
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SSUDSEditorOutputItem)
|
||||
{}
|
||||
SLATE_ARGUMENT(float, InitialWidth)
|
||||
SLATE_ARGUMENT(FText, Prefix)
|
||||
SLATE_ARGUMENT(FText, Line)
|
||||
SLATE_ARGUMENT(FSlateColor, PrefixColour)
|
||||
SLATE_ARGUMENT(FSlateColor, LineColour)
|
||||
SLATE_ARGUMENT(FSlateColor, BgColour)
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Construct this widget. Called by the SNew() Slate macro.
|
||||
*
|
||||
* @param InArgs Declaration used by the SNew() macro to construct this widget.
|
||||
* @oaram InOwnerTableView The owner table into which this row is being placed.
|
||||
*/
|
||||
void Construct( const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView );
|
||||
|
||||
/**
|
||||
* Generates the widget for the specified column.
|
||||
*
|
||||
* @param ColumnName The name of the column to generate the widget for.
|
||||
* @return The widget.
|
||||
*/
|
||||
virtual TSharedRef<SWidget> GenerateWidgetForColumn( const FName& ColumnName ) override;
|
||||
protected:
|
||||
float InitialWidth = 70;
|
||||
FText Prefix;
|
||||
FText Line;
|
||||
FSlateColor PrefixColour;
|
||||
FSlateColor LineColour;
|
||||
};
|
||||
|
||||
|
||||
class FSUDSEditorVariableRow
|
||||
{
|
||||
public:
|
||||
FName Name;
|
||||
FSUDSValue Value;
|
||||
bool bIsManualOverride;
|
||||
|
||||
FSUDSEditorVariableRow(const FName& InName, const FSUDSValue& InValue, bool bIsManual) : Name(InName),
|
||||
Value(InValue),
|
||||
bIsManualOverride(bIsManual)
|
||||
{
|
||||
}
|
||||
|
||||
friend bool operator<(const FSUDSEditorVariableRow& Lhs, const FSUDSEditorVariableRow& RHS)
|
||||
{
|
||||
return Lhs.Name.LexicalLess(RHS.Name);
|
||||
}
|
||||
|
||||
friend bool operator<(const TSharedPtr<FSUDSEditorVariableRow>& Lhs, const TSharedPtr<FSUDSEditorVariableRow>& RHS)
|
||||
{
|
||||
return *Lhs < *RHS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
class SSUDSEditorVariableItem : public SMultiColumnTableRow< TSharedPtr<FString> >
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SSUDSEditorVariableItem)
|
||||
{}
|
||||
SLATE_ARGUMENT(float, InitialWidth)
|
||||
SLATE_ARGUMENT(FName, VariableName)
|
||||
SLATE_ARGUMENT(FSUDSValue, VariableValue)
|
||||
SLATE_ARGUMENT(bool, bIsManualOverride)
|
||||
SLATE_ARGUMENT(class FSUDSEditorToolkit*, Parent)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Construct this widget. Called by the SNew() Slate macro.
|
||||
*
|
||||
* @param InArgs Declaration used by the SNew() macro to construct this widget.
|
||||
* @oaram InOwnerTableView The owner table into which this row is being placed.
|
||||
*/
|
||||
void Construct( const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView );
|
||||
|
||||
public:
|
||||
/**
|
||||
* Generates the widget for the specified column.
|
||||
*
|
||||
* @param ColumnName The name of the column to generate the widget for.
|
||||
* @return The widget.
|
||||
*/
|
||||
virtual TSharedRef<SWidget> GenerateWidgetForColumn( const FName& ColumnName ) override;
|
||||
protected:
|
||||
float InitialWidth = 70;
|
||||
FName VariableName;
|
||||
FSUDSValue VariableValue;
|
||||
bool bIsManualOverride = false;
|
||||
class FSUDSEditorToolkit* Parent = nullptr;
|
||||
|
||||
TSharedRef<class SWidget> GetGenderMenu();
|
||||
void OnGenderSelected(ETextGender TextGender);
|
||||
virtual FVector2D ComputeDesiredSize(float) const override;
|
||||
|
||||
};
|
||||
|
||||
struct FSUDSTraceLogMessage
|
||||
{
|
||||
TSharedRef<FString> Message;
|
||||
FName Category;
|
||||
FSlateColor Colour;
|
||||
|
||||
FSUDSTraceLogMessage(FName InCategory, const FString& InMessage, const FSlateColor& InColour)
|
||||
: Message(MakeShared<FString>(InMessage))
|
||||
, Category(InCategory)
|
||||
, Colour(InColour)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class FSUDSTraceLogMarshaller : public FBaseTextLayoutMarshaller
|
||||
{
|
||||
public:
|
||||
|
||||
FSUDSTraceLogMarshaller();
|
||||
|
||||
// ITextLayoutMarshaller
|
||||
virtual void SetText(const FString& SourceString, FTextLayout& TargetTextLayout) override;
|
||||
virtual void GetText(FString& TargetString, const FTextLayout& SourceTextLayout) override;
|
||||
|
||||
void AppendMessage(FName InCategory, int LineNo, const FString& Message, const FSlateColor& Colour);
|
||||
void ClearMessages();
|
||||
protected:
|
||||
TArray< TSharedPtr<FSUDSTraceLogMessage> > Messages;
|
||||
};
|
||||
|
||||
// Trace log widget, really just so we can tick and automatically scroll to the end
|
||||
class SSUDSTraceLog : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SSUDSTraceLog)
|
||||
{}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
SSUDSTraceLog() : bIsUserScrolled(false) {}
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
|
||||
|
||||
void AppendMessage(FName InCategory, int LineNo, const FString& Message, const FSlateColor& Colour);
|
||||
void ClearMessages();
|
||||
void ScrollToEnd();
|
||||
|
||||
protected:
|
||||
bool bIsUserScrolled;
|
||||
TSharedPtr<FSUDSTraceLogMarshaller> TraceLogMarshaller;
|
||||
TSharedPtr<SMultiLineEditableTextBox> TraceLogTextBox;
|
||||
|
||||
void OnUserScrolled(float X);
|
||||
|
||||
};
|
||||
|
||||
class FSUDSEditorToolkit : public FAssetEditorToolkit
|
||||
{
|
||||
public:
|
||||
void InitEditor(const TArray<UObject*>& InObjects);
|
||||
|
||||
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
|
||||
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
|
||||
|
||||
virtual FText GetBaseToolkitName() const override;
|
||||
virtual FName GetToolkitFName() const override;
|
||||
virtual FLinearColor GetWorldCentricTabColorScale() const override;
|
||||
virtual FString GetWorldCentricTabPrefix() const override;
|
||||
void UserEditVariable(const FName& Name, FSUDSValue Value);
|
||||
void DeleteVariable(const FName& Name);
|
||||
|
||||
protected:
|
||||
virtual void OnClose() override;
|
||||
|
||||
private:
|
||||
USUDSScript* Script = nullptr;
|
||||
USUDSDialogue* Dialogue = nullptr;
|
||||
float VarColumnWidth = 120;
|
||||
float PrefixColumnWidth = 100;
|
||||
FName StartLabel = NAME_None;
|
||||
bool bResetVarsOnStart = true;
|
||||
FDelegateHandle ReimportDelegateHandle;
|
||||
// FSUDSEditorDialogueRow needs to held by a TSharedPtr for SListView
|
||||
TSharedPtr<SListView<TSharedPtr<FSUDSEditorOutputRow>>> OutputListView;
|
||||
TArray<TSharedPtr<FSUDSEditorOutputRow>> OutputRows;
|
||||
TSharedPtr<SVerticalBox> ChoicesBox;
|
||||
TSharedPtr<SListView<TSharedPtr<FSUDSEditorVariableRow>>> VariablesListView;
|
||||
TArray<TSharedPtr<FSUDSEditorVariableRow>> VariableRows;
|
||||
TSharedPtr<SSUDSTraceLog> TraceLog;
|
||||
TMap<FName, FSUDSValue> ManualOverrideVariables;
|
||||
|
||||
static FName DialogueOutputTabName;
|
||||
static FName DetailsTabName;
|
||||
static FName VariablesTabName;
|
||||
static FName LogTabName;
|
||||
|
||||
|
||||
const FSlateColor SpeakerColour = FLinearColor(1.0f, 1.0f, 0.6f, 1.0f);
|
||||
const FSlateColor ChoiceColour = FLinearColor(0.4f, 1.0f, 0.4f, 1.0f);
|
||||
const FSlateColor EventColour = FLinearColor(0.2f, 0.6f, 1.0f, 1.0f);
|
||||
const FSlateColor VarSetColour = FLinearColor(0.8f, 0.6f, 0.9f, 1.0f);
|
||||
const FSlateColor VarEditColour = FLinearColor(0.6f, 0.3f, 1.0f, 1.0f);
|
||||
const FSlateColor StartColour = FLinearColor(1.0f, 0.5f, 0.0f, 1.0f);
|
||||
const FSlateColor FinishColour = FLinearColor(1.0f, 0.3f, 0.3f, 1.0f);
|
||||
const FSlateColor SelectColour = FLinearColor(1.0f, 0.0f, 0.5f, 1.0f);
|
||||
const FSlateColor RowBgColour1 = FLinearColor(0.15f, 0.15f, 0.15f, 1.0f);
|
||||
const FSlateColor RowBgColour2 = FLinearColor(0.3f, 0.3f, 0.3f, 1.0f);
|
||||
|
||||
void ExtendToolbar(FToolBarBuilder& ToolbarBuilder, TWeakPtr<SDockTab> Tab);
|
||||
TSharedRef<class SWidget> GetStartLabelMenu();
|
||||
FText GetSelectedStartLabel() const;
|
||||
void OnStartLabelSelected(FName Label);
|
||||
ECheckBoxState GetResetVarsCheckState() const;
|
||||
void OnResetVarsCheckStateChanged(ECheckBoxState NewState);
|
||||
FReply AddVariableClicked();
|
||||
void UpdateVariables();
|
||||
void EnsureTabsVisible();
|
||||
void StartDialogue();
|
||||
void DestroyDialogue();
|
||||
void UpdateOutput();
|
||||
void UpdateChoiceButtons();
|
||||
void AddOutputRow(const FText& Prefix, const FText& Line, const FSlateColor& PrefixColour, const FSlateColor& LineColour);
|
||||
void AddTraceLogRow(const FName& Category, int SourceLineNo, const FString& Message);
|
||||
|
||||
void AddDialogueStep(const FName& Category, int SourceLineNo, const FText& Description, const FText& Prefix);
|
||||
|
||||
void OnDialogueChoice(USUDSDialogue* Dialogue, int ChoiceIndex, int LineNo);
|
||||
void OnDialogueEvent(USUDSDialogue* Dialogue, FName EventName, const TArray<FSUDSValue>& Args, int LineNo);
|
||||
void OnDialogueFinished(USUDSDialogue* Dialogue);
|
||||
void OnDialogueProceeding(USUDSDialogue* Dialogue);
|
||||
void OnDialogueStarting(USUDSDialogue* Dialogue, FName LabelName);
|
||||
void OnDialogueSpeakerLine(USUDSDialogue* Dialogue, int LineNo);
|
||||
void OnDialogueSetVar(USUDSDialogue* Dialogue, FName VariableName, const FSUDSValue& ToValue, const FString& ExpressionStr, int LineNo);
|
||||
void OnDialogueUserEditedVar(USUDSDialogue* Dialogue, FName VariableName, const FSUDSValue& ToValue);
|
||||
void OnDialogueSelectEval(USUDSDialogue* Dialogue, const FString& ExpressionStr, bool bSuccess, int LineNo);
|
||||
void OnDialogueRandomEval(USUDSDialogue* Dialogue, const int RandomOutcome, int LineNo);
|
||||
|
||||
TSharedRef<ITableRow> OnGenerateRowForOutput(
|
||||
TSharedPtr<FSUDSEditorOutputRow> FsudsEditorDialogueRow,
|
||||
const TSharedRef<STableViewBase>& TableViewBase);
|
||||
TSharedRef<ITableRow> OnGenerateRowForVariable(TSharedPtr<FSUDSEditorVariableRow> Row,
|
||||
const TSharedRef<STableViewBase>& Table);
|
||||
FSlateColor GetColourForCategory(const FName& Category);
|
||||
void OnPostReimport(UObject* Object, bool bSuccess);
|
||||
void Clear();
|
||||
void WriteBackTextIDs();
|
||||
void GenerateVOAssets();
|
||||
|
||||
};
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,381 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#include "SUDSEditorVoiceOverTools.h"
|
||||
|
||||
#include "ObjectTools.h"
|
||||
#include "PackageTools.h"
|
||||
#include "SUDSEditorSettings.h"
|
||||
#include "SUDSMessageLogger.h"
|
||||
#include "SUDSScript.h"
|
||||
#include "SUDSScriptNodeText.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Internationalization/Regex.h"
|
||||
#include "Sound/DialogueVoice.h"
|
||||
#include "Sound/DialogueWave.h"
|
||||
#include "Sound/SoundWave.h"
|
||||
|
||||
|
||||
void FSUDSEditorVoiceOverTools::GenerateAssets(USUDSScript* Script, EObjectFlags Flags, FSUDSMessageLogger* Logger)
|
||||
{
|
||||
// First check for problems
|
||||
if (auto Settings = GetDefault<USUDSEditorSettings>())
|
||||
{
|
||||
bool bError = false;
|
||||
if ((Settings->DialogueVoiceAssetLocation == ESUDSAssetLocation::SharedDirectory || Settings->
|
||||
DialogueVoiceAssetLocation == ESUDSAssetLocation::SharedDirectorySubdir) &&
|
||||
(Settings->DialogueVoiceAssetSharedDir.Path.IsEmpty() ||
|
||||
!FPackageName::IsValidPath(Settings->DialogueVoiceAssetSharedDir.Path)))
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT(
|
||||
"Dialogue Voice assets are set to generate to a shared dir, but the dir '%s' is not valid."),
|
||||
*Settings->DialogueVoiceAssetSharedDir.Path);
|
||||
bError = true;
|
||||
}
|
||||
if ((Settings->DialogueWaveAssetLocation == ESUDSAssetLocation::SharedDirectory || Settings->
|
||||
DialogueWaveAssetLocation == ESUDSAssetLocation::SharedDirectorySubdir) &&
|
||||
(Settings->DialogueWaveAssetSharedDir.Path.IsEmpty() ||
|
||||
!FPackageName::IsValidPath(Settings->DialogueWaveAssetSharedDir.Path)))
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT(
|
||||
"Dialogue Wave assets are set to generate to a shared dir, but the dir '%s' is not valid."),
|
||||
*Settings->DialogueWaveAssetSharedDir.Path);
|
||||
bError = true;
|
||||
}
|
||||
if (bError) { return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error, TEXT("Unable to generate voice assets, settings not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
TMap<FString, UDialogueVoice*> UnsavedVoices;
|
||||
GenerateVoiceAssets(Script, Flags, Logger, UnsavedVoices);
|
||||
GenerateWaveAssets(Script, Flags, UnsavedVoices, Logger);
|
||||
}
|
||||
|
||||
void FSUDSEditorVoiceOverTools::GenerateVoiceAssets(USUDSScript* Script, EObjectFlags Flags, FSUDSMessageLogger* Logger, TMap<FString, UDialogueVoice*> &OutCreatedVoices)
|
||||
{
|
||||
auto Registry = &FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
|
||||
|
||||
FString Prefix;
|
||||
FString ParentDir;
|
||||
if (auto Settings = GetDefault<USUDSEditorSettings>())
|
||||
{
|
||||
Prefix = Settings->DialogueVoiceAssetPrefix;
|
||||
ParentDir = GetVoiceOutputDir(Script);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error, TEXT("Unable to generate voice assets, settings not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto Speaker : Script->GetSpeakers())
|
||||
{
|
||||
// All Dialogue Voice assets will be created in their own package
|
||||
FString PackageName;
|
||||
FString AssetName;
|
||||
if (GetSpeakerVoiceAssetNames(Script, Speaker, PackageName, AssetName))
|
||||
{
|
||||
if (FPackageName::DoesPackageExist(*PackageName))
|
||||
{
|
||||
// Package already exists, so try and import over the top of it, if it doesn't already have a source file path
|
||||
TArray<FAssetData> Assets;
|
||||
if (Registry->GetAssetsByPackageName(*PackageName, Assets))
|
||||
{
|
||||
if (Assets.Num() > 0)
|
||||
{
|
||||
if (!Assets[0].GetAsset()->IsA(UDialogueVoice::StaticClass()))
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT(
|
||||
"Asset %s already exists but is not a Dialogue Voice! Cannot replace, please move aside and re-import script."),
|
||||
*PackageName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure we still link the existing voice
|
||||
Script->SetSpeakerVoice(Speaker, Cast<UDialogueVoice>(Assets[0].GetAsset()));
|
||||
Script->MarkPackageDirty();
|
||||
}
|
||||
// Either way nothing more to do
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we got here then Dialogue Voice didn't exist (although package might have)
|
||||
// It's safe to call CreatePackage either way, it'll return the existing one if needed
|
||||
UPackage* Package = CreatePackage(*PackageName);
|
||||
if (!ensure(Package))
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT("Failed to create/retrieve package for voice asset %s"),
|
||||
*PackageName);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Logger->Logf(ELogVerbosity::Display, TEXT("Creating voice asset %s"), *PackageName);
|
||||
|
||||
UDialogueVoice* NewVoiceAsset = NewObject<UDialogueVoice>(Package, FName(AssetName), Flags);
|
||||
OutCreatedVoices.Add(Speaker, NewVoiceAsset);
|
||||
// there's nothing else to create here, voice is mostly a placeholder with the rest set up later by user
|
||||
FAssetRegistryModule::AssetCreated(NewVoiceAsset);
|
||||
|
||||
Script->SetSpeakerVoice(Speaker, NewVoiceAsset);
|
||||
Script->MarkPackageDirty();
|
||||
|
||||
Package->FullyLoad();
|
||||
NewVoiceAsset->MarkPackageDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FSUDSEditorVoiceOverTools::GetSpeakerVoicePackageName(USUDSScript* Script,
|
||||
const FString& SpeakerID,
|
||||
FString& OutPackageName)
|
||||
{
|
||||
FString NotUsed;
|
||||
return GetSpeakerVoiceAssetNames(Script, SpeakerID, OutPackageName, NotUsed);
|
||||
}
|
||||
|
||||
bool FSUDSEditorVoiceOverTools::GetSpeakerVoiceAssetNames(USUDSScript* Script,
|
||||
const FString& SpeakerID,
|
||||
FString& OutPackageName,
|
||||
FString& OutAssetName)
|
||||
{
|
||||
if (auto Settings = GetDefault<USUDSEditorSettings>())
|
||||
{
|
||||
FString Prefix = Settings->DialogueVoiceAssetPrefix;
|
||||
FString ParentDir = GetVoiceOutputDir(Script);
|
||||
|
||||
OutAssetName = FString::Printf(TEXT("%s%s"), *Prefix, *ObjectTools::SanitizeObjectName(SpeakerID));
|
||||
OutPackageName = UPackageTools::SanitizePackageName(ParentDir / OutAssetName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UDialogueVoice* FSUDSEditorVoiceOverTools::FindSpeakerVoice(USUDSScript* Script,
|
||||
const FString& SpeakerID)
|
||||
{
|
||||
FString PackageName;
|
||||
if (GetSpeakerVoicePackageName(Script, SpeakerID, PackageName))
|
||||
{
|
||||
|
||||
auto Registry = &FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
|
||||
|
||||
if (FPackageName::DoesPackageExist(*PackageName))
|
||||
{
|
||||
// Package already exists, so try and import over the top of it, if it doesn't already have a source file path
|
||||
TArray<FAssetData> Assets;
|
||||
if (Registry->GetAssetsByPackageName(*PackageName, Assets))
|
||||
{
|
||||
if (Assets.Num() > 0)
|
||||
{
|
||||
if (Assets[0].GetAsset()->IsA(UDialogueVoice::StaticClass()))
|
||||
{
|
||||
return Cast<UDialogueVoice>(Assets[0].GetAsset());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FSUDSEditorVoiceOverTools::GenerateWaveAssets(USUDSScript* Script, EObjectFlags Flags, TMap<FString, UDialogueVoice*> UnsavedVoices, FSUDSMessageLogger* Logger)
|
||||
{
|
||||
// We need to identify specific lines of dialogue to specify a wave asset to go with them, which means we
|
||||
// need to use the Text ID, just like we do for translations. This means that really, you shouldn't start to
|
||||
// assign sounds to wave assets until you've written the text IDs back to the script
|
||||
auto Registry = &FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
|
||||
|
||||
FString Prefix;
|
||||
FString ParentDir;
|
||||
if (auto Settings = GetDefault<USUDSEditorSettings>())
|
||||
{
|
||||
Prefix = FString::Printf(TEXT("%s%s_"), *Settings->DialogueWaveAssetPrefix, *GetScriptNameAsPrefix(Script));
|
||||
ParentDir = GetWaveOutputDir(Script);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error, TEXT("Unable to generate wave assets, settings not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto Node : Script->GetNodes())
|
||||
{
|
||||
if (auto Line = Cast<USUDSScriptNodeText>(Node))
|
||||
{
|
||||
// We use the TextID to identify a specific line, just like for translation
|
||||
FString TextID = Line->GetTextID();
|
||||
|
||||
// Remove the '@' characters from the text ID for creating the asset name, they just turn into excessive '_'s
|
||||
const FRegexPattern TextIDPattern(TEXT("\\@([0-9a-fA-F]+)\\@"));
|
||||
FRegexMatcher TextIDRegex(TextIDPattern, TextID);
|
||||
if (TextIDRegex.FindNext())
|
||||
{
|
||||
TextID = TextIDRegex.GetCaptureGroup(1);
|
||||
}
|
||||
|
||||
// All Dialogue Voice assets will be created in their own package
|
||||
const FString SanitizedName = FString::Printf(TEXT("%s%s"),
|
||||
*Prefix,
|
||||
*ObjectTools::SanitizeObjectName(TextID));
|
||||
const FString PackageName = UPackageTools::SanitizePackageName(ParentDir / SanitizedName);
|
||||
|
||||
UDialogueWave* WaveAsset = nullptr;
|
||||
if (FPackageName::DoesPackageExist(*PackageName))
|
||||
{
|
||||
// Package already exists, so try and import over the top of it, if it doesn't already have a source file path
|
||||
TArray<FAssetData> Assets;
|
||||
if (Registry->GetAssetsByPackageName(*PackageName, Assets))
|
||||
{
|
||||
if (Assets.Num() > 0)
|
||||
{
|
||||
if (!Assets[0].GetAsset()->IsA(UDialogueWave::StaticClass()))
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT(
|
||||
"Asset %s already exists but is not a Dialogue Wave! Cannot replace, please move aside and re-import script."),
|
||||
*PackageName);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
WaveAsset = Cast<UDialogueWave>(Assets[0].GetAsset());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UDialogueVoice* SpeakerVoice = nullptr;
|
||||
// Finding speaker assets via FAssetRegistryModule does not work on unsaved assets, so we need to check
|
||||
// the unsaved voices that have been created in the previous step first
|
||||
if (auto pSV = UnsavedVoices.Find(Line->GetSpeakerID()))
|
||||
{
|
||||
SpeakerVoice = *pSV;
|
||||
}
|
||||
if (!SpeakerVoice)
|
||||
{
|
||||
// Now try assets
|
||||
SpeakerVoice = FindSpeakerVoice(Script, Line->GetSpeakerID());
|
||||
}
|
||||
if (!SpeakerVoice)
|
||||
{
|
||||
FString SpeakerPackageName;
|
||||
GetSpeakerVoicePackageName(Script, Line->GetSpeakerID(), SpeakerPackageName);
|
||||
Logger->Logf(ELogVerbosity::Error, TEXT("Error: speaker voice asset for %s is missing, expected to be at %s"), *Line->GetSpeakerID(), *SpeakerPackageName);
|
||||
continue;
|
||||
}
|
||||
|
||||
UPackage* Package = CreatePackage(*PackageName);
|
||||
if (!ensure(Package))
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT("Failed to create/retrieve package for wave asset %s"),
|
||||
*PackageName);
|
||||
}
|
||||
else
|
||||
{
|
||||
FString NativeLangText = Line->GetText().ToString();
|
||||
USoundWave* SoundWave = nullptr;
|
||||
if (!WaveAsset)
|
||||
{
|
||||
//Logger->Logf(ELogVerbosity::Display, TEXT("Creating wave asset %s"), *PackageName);
|
||||
WaveAsset = NewObject<UDialogueWave>(Package, FName(SanitizedName), Flags);
|
||||
FAssetRegistryModule::AssetCreated(WaveAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We will always update the existing asset, but will warn if the text is changing
|
||||
if (WaveAsset->ContextMappings.Num() > 0)
|
||||
{
|
||||
auto Mapping = WaveAsset->ContextMappings[0];
|
||||
if (IsValid(Mapping.SoundWave))
|
||||
{
|
||||
SoundWave = Mapping.SoundWave;
|
||||
// This is OK if the context is what we were going to create anyway, we'll just leave it alone
|
||||
if (Mapping.Context.Speaker != SpeakerVoice ||
|
||||
WaveAsset->SpokenText != NativeLangText)
|
||||
{
|
||||
// No good, user has assigned sound to this dialogue wave, but we need to change it to
|
||||
// a different speaker / line
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT(
|
||||
"Dialogue Wave %s has an assigned Sound Wave but the speaker or text has changed. You should update the sound wave!"),
|
||||
*PackageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set spoken text - native language only. Dialogue Wave handles the localisation of this separately,
|
||||
// we can't link it to our String Table unfortunately.
|
||||
WaveAsset->SpokenText = NativeLangText;
|
||||
// Set dialogue context
|
||||
// We need a Speaker Voice, we'll leave the sound and targets blank
|
||||
WaveAsset->UpdateContext(WaveAsset->ContextMappings[0], SoundWave, SpeakerVoice, TArray<UDialogueVoice*>());
|
||||
|
||||
// Now assign the wave to the line
|
||||
Line->SetWave(WaveAsset);
|
||||
Script->MarkPackageDirty();
|
||||
|
||||
Package->FullyLoad();
|
||||
WaveAsset->MarkPackageDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FString FSUDSEditorVoiceOverTools::GetVoiceOutputDir(USUDSScript* Script)
|
||||
{
|
||||
if (auto Settings = GetDefault<USUDSEditorSettings>())
|
||||
{
|
||||
const FString PackagePath = FPackageName::GetLongPackagePath(Script->GetOuter()->GetOutermost()->GetPathName());
|
||||
const FString ScriptPrefix = GetScriptNameAsPrefix(Script);
|
||||
return Settings->GetVoiceOutputDir(PackagePath, ScriptPrefix);
|
||||
}
|
||||
return FString();
|
||||
}
|
||||
|
||||
FString FSUDSEditorVoiceOverTools::GetWaveOutputDir(USUDSScript* Script)
|
||||
{
|
||||
if (auto Settings = GetDefault<USUDSEditorSettings>())
|
||||
{
|
||||
const FString PackagePath = FPackageName::GetLongPackagePath(Script->GetOuter()->GetOutermost()->GetPathName());
|
||||
const FString ScriptPrefix = GetScriptNameAsPrefix(Script);
|
||||
return Settings->GetWaveOutputDir(PackagePath, ScriptPrefix);
|
||||
}
|
||||
return FString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
FString FSUDSEditorVoiceOverTools::GetScriptNameAsPrefix(USUDSScript* Script)
|
||||
{
|
||||
FString Name = Script->GetName();
|
||||
|
||||
if (auto Settings = GetDefault<USUDSEditorSettings>())
|
||||
{
|
||||
if (Settings->StripScriptPrefixesWhenGeneratingNames)
|
||||
{
|
||||
int32 Index = INDEX_NONE;
|
||||
if (Name.FindChar('_', Index))
|
||||
{
|
||||
if (Index < Name.Len() - 1)
|
||||
{
|
||||
Name = Name.RightChop(Index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
93
Plugins/SUDS/Source/SUDSEditor/Private/SUDSMessageLogger.cpp
Normal file
93
Plugins/SUDS/Source/SUDSEditor/Private/SUDSMessageLogger.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#include "SUDSMessageLogger.h"
|
||||
#include "IMessageLogListing.h"
|
||||
#include "MessageLogModule.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
FSUDSMessageLogger::~FSUDSMessageLogger()
|
||||
{
|
||||
if (bWriteToMessageLog)
|
||||
{
|
||||
//Always clear the old message after an import or re-import
|
||||
const TCHAR* LogTitle = TEXT("SUDS");
|
||||
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
|
||||
TSharedPtr<class IMessageLogListing> LogListing = MessageLogModule.GetLogListing(LogTitle);
|
||||
LogListing->SetLabel(FText::FromString("SUDS"));
|
||||
// Should NOT clear messages here, otherwise when FSUDSMessageLogger is used multiple times in the import process,
|
||||
// each one is clearing messages from previous stages of the import.
|
||||
//LogListing->ClearMessages();
|
||||
|
||||
if(ErrorMessages.Num() > 0)
|
||||
{
|
||||
LogListing->AddMessages(MoveTemp(ErrorMessages));
|
||||
LogListing->NotifyIfAnyMessages(NSLOCTEXT("SUDS", "ImportErrors", "There were issues with the import."), EMessageSeverity::Warning);
|
||||
MessageLogModule.OpenMessageLog(LogTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FSUDSMessageLogger::HasErrors() const
|
||||
{
|
||||
for (const TSharedRef<FTokenizedMessage>& Msg : ErrorMessages)
|
||||
{
|
||||
if (Msg->GetSeverity() == EMessageSeverity::Error)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int FSUDSMessageLogger::NumErrors() const
|
||||
{
|
||||
int Errs = 0;
|
||||
for (const TSharedRef<FTokenizedMessage>& Msg : ErrorMessages)
|
||||
{
|
||||
if (Msg->GetSeverity() == EMessageSeverity::Error)
|
||||
{
|
||||
++Errs;
|
||||
}
|
||||
}
|
||||
return Errs;
|
||||
}
|
||||
|
||||
bool FSUDSMessageLogger::HasWarnings() const
|
||||
{
|
||||
for (const TSharedRef<FTokenizedMessage>& Msg : ErrorMessages)
|
||||
{
|
||||
if (Msg->GetSeverity() == EMessageSeverity::Warning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int FSUDSMessageLogger::NumWarnings() const
|
||||
{
|
||||
int Count = 0;
|
||||
for (const TSharedRef<FTokenizedMessage>& Msg : ErrorMessages)
|
||||
{
|
||||
if (Msg->GetSeverity() == EMessageSeverity::Warning)
|
||||
{
|
||||
++Count;
|
||||
}
|
||||
}
|
||||
return Count;
|
||||
}
|
||||
|
||||
void FSUDSMessageLogger::AddMessage(EMessageSeverity::Type Severity, const FText& Text)
|
||||
{
|
||||
ErrorMessages.Add(FTokenizedMessage::Create(Severity, Text));
|
||||
}
|
||||
|
||||
void FSUDSMessageLogger::ClearMessages()
|
||||
{
|
||||
const TCHAR* LogTitle = TEXT("SUDS");
|
||||
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
|
||||
TSharedPtr<class IMessageLogListing> LogListing = MessageLogModule.GetLogListing(LogTitle);
|
||||
LogListing->SetLabel(FText::FromString("SUDS"));
|
||||
LogListing->ClearMessages();
|
||||
}
|
||||
|
||||
134
Plugins/SUDS/Source/SUDSEditor/Private/SUDSScriptActions.cpp
Normal file
134
Plugins/SUDS/Source/SUDSEditor/Private/SUDSScriptActions.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#include "SUDSScriptActions.h"
|
||||
|
||||
#include "SUDSEditorScriptTools.h"
|
||||
#include "SUDSEditorSettings.h"
|
||||
#include "SUDSEditorToolkit.h"
|
||||
#include "SUDSEditorVoiceOverTools.h"
|
||||
#include "SUDSMessageLogger.h"
|
||||
#include "SUDSScript.h"
|
||||
#include "ToolMenuSection.h"
|
||||
#include "EditorFramework/AssetImportData.h"
|
||||
#include "Misc/MessageDialog.h"
|
||||
#include "Widgets/Views/SListView.h"
|
||||
|
||||
FText FSUDSScriptActions::GetName() const
|
||||
{
|
||||
return INVTEXT("SUDS Script");
|
||||
}
|
||||
|
||||
FString FSUDSScriptActions::GetObjectDisplayName(UObject* Object) const
|
||||
{
|
||||
return Object->GetName();
|
||||
}
|
||||
|
||||
UClass* FSUDSScriptActions::GetSupportedClass() const
|
||||
{
|
||||
return USUDSScript::StaticClass();
|
||||
}
|
||||
|
||||
FColor FSUDSScriptActions::GetTypeColor() const
|
||||
{
|
||||
return FColor::Orange;
|
||||
}
|
||||
|
||||
uint32 FSUDSScriptActions::GetCategories()
|
||||
{
|
||||
return EAssetTypeCategories::Misc;
|
||||
}
|
||||
|
||||
void FSUDSScriptActions::GetResolvedSourceFilePaths(const TArray<UObject*>& TypeAssets,
|
||||
TArray<FString>& OutSourceFilePaths) const
|
||||
{
|
||||
for (auto& Asset : TypeAssets)
|
||||
{
|
||||
const auto Script = CastChecked<USUDSScript>(Asset);
|
||||
if (Script->AssetImportData)
|
||||
{
|
||||
Script->AssetImportData->ExtractFilenames(OutSourceFilePaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FSUDSScriptActions::HasActions(const TArray<UObject*>& InObjects) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FSUDSScriptActions::OpenAssetEditor(const TArray<UObject*>& InObjects,
|
||||
TSharedPtr<IToolkitHost> EditWithinLevelEditor)
|
||||
{
|
||||
MakeShared<FSUDSEditorToolkit>()->InitEditor(InObjects);
|
||||
}
|
||||
|
||||
void FSUDSScriptActions::GetActions(const TArray<UObject*>& InObjects, FToolMenuSection& Section)
|
||||
{
|
||||
const auto Scripts = GetTypedWeakObjectPtrs<USUDSScript>(InObjects);
|
||||
|
||||
Section.AddMenuEntry(
|
||||
"WriteBackTextIDs",
|
||||
NSLOCTEXT("SUDS", "WriteBackTextIDs", "Write Back String Keys"),
|
||||
NSLOCTEXT("SUDS",
|
||||
"WriteBackTextIDsTooltip",
|
||||
"Write string table keys back to source script to make them constant for localisation."),
|
||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Details"),
|
||||
FUIAction(
|
||||
FExecuteAction::CreateSP(this, &FSUDSScriptActions::WriteBackTextIDs, Scripts),
|
||||
FCanExecuteAction()
|
||||
)
|
||||
);
|
||||
Section.AddMenuEntry(
|
||||
"GenerateVOAssets",
|
||||
NSLOCTEXT("SUDS", "GenerateVOAssets", "Generate Voice Assets"),
|
||||
NSLOCTEXT("SUDS",
|
||||
"GenerateVOAssetsTooltip",
|
||||
"Generate Dialogue Voice / Dialogue Wave assets for the selected scripts"),
|
||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Toolbar.Export"),
|
||||
FUIAction(
|
||||
FExecuteAction::CreateSP(this, &FSUDSScriptActions::GenerateVOAssets, Scripts),
|
||||
FCanExecuteAction()
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
void FSUDSScriptActions::WriteBackTextIDs(TArray<TWeakObjectPtr<USUDSScript>> Scripts)
|
||||
{
|
||||
if (FMessageDialog::Open(EAppMsgType::YesNo,
|
||||
FText::FromString(
|
||||
"Are you sure you want to write string keys back to the selected scripts?"))
|
||||
== EAppReturnType::Yes)
|
||||
{
|
||||
FSUDSMessageLogger::ClearMessages();
|
||||
FSUDSMessageLogger Logger;
|
||||
for (auto Script : Scripts)
|
||||
{
|
||||
if (Script.IsValid())
|
||||
{
|
||||
FSUDSEditorScriptTools::WriteBackTextIDs(Script.Get(), Logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FSUDSScriptActions::GenerateVOAssets(TArray<TWeakObjectPtr<USUDSScript>> Scripts)
|
||||
{
|
||||
if (FMessageDialog::Open(EAppMsgType::YesNo,
|
||||
FText::FromString(
|
||||
"Are you sure you want to generate Dialogue Voice / Dialogue Wave assets for the selected scripts?"))
|
||||
== EAppReturnType::Yes)
|
||||
{
|
||||
EObjectFlags Flags = RF_Public | RF_Standalone | RF_Transactional;
|
||||
FSUDSMessageLogger::ClearMessages();
|
||||
FSUDSMessageLogger Logger;
|
||||
for (auto WeakScript : Scripts)
|
||||
{
|
||||
if (auto Script = WeakScript.Get())
|
||||
{
|
||||
FSUDSEditorVoiceOverTools::GenerateAssets(Script, Flags, &Logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
262
Plugins/SUDS/Source/SUDSEditor/Private/SUDSScriptFactory.cpp
Normal file
262
Plugins/SUDS/Source/SUDSEditor/Private/SUDSScriptFactory.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#include "SUDSScriptFactory.h"
|
||||
|
||||
#include "PackageTools.h"
|
||||
#include "SUDSEditorSettings.h"
|
||||
#include "SUDSEditorVoiceOverTools.h"
|
||||
#include "SUDSMessageLogger.h"
|
||||
#include "SUDSScript.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "EditorFramework/AssetImportData.h"
|
||||
#include "Internationalization/StringTable.h"
|
||||
#include "Editor.h"
|
||||
#include "ObjectTools.h"
|
||||
#include "Internationalization/StringTableCore.h"
|
||||
|
||||
|
||||
USUDSScriptFactory::USUDSScriptFactory()
|
||||
{
|
||||
SupportedClass = USUDSScript::StaticClass();
|
||||
bCreateNew = false;
|
||||
bEditorImport = true;
|
||||
bText = true;
|
||||
Formats.Add(TEXT("sud;SUDS Script File"));
|
||||
}
|
||||
|
||||
UObject* USUDSScriptFactory::FactoryCreateText(UClass* InClass,
|
||||
UObject* InParent,
|
||||
FName InName,
|
||||
EObjectFlags Flags,
|
||||
UObject* Context,
|
||||
const TCHAR* Type,
|
||||
const TCHAR*& Buffer,
|
||||
const TCHAR* BufferEnd,
|
||||
FFeedbackContext* Warn)
|
||||
{
|
||||
Flags |= RF_Transactional;
|
||||
FSUDSMessageLogger::ClearMessages();
|
||||
FSUDSMessageLogger Logger;
|
||||
|
||||
USUDSScript* Result = nullptr;
|
||||
|
||||
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type);
|
||||
|
||||
const FString FactoryCurrentFilename = UFactory::GetCurrentFilename();
|
||||
FString CurrentSourcePath;
|
||||
FString FilenameNoExtension;
|
||||
FString UnusedExtension;
|
||||
FPaths::Split(FactoryCurrentFilename, CurrentSourcePath, FilenameNoExtension, UnusedExtension);
|
||||
const FString LongPackagePath = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetPathName());
|
||||
|
||||
const FString NameForErrors(InName.ToString());
|
||||
|
||||
// Now parse this using utility
|
||||
if(Importer.ImportFromBuffer(Buffer, BufferEnd - Buffer, NameForErrors, &Logger, false))
|
||||
{
|
||||
|
||||
// Populate with data
|
||||
Result = NewObject<USUDSScript>(InParent, InName, Flags);
|
||||
UStringTable* StringTable = CreateStringTable(InParent, InName, Result, Flags, &Logger);
|
||||
Importer.PopulateAsset(Result, StringTable);
|
||||
|
||||
// Register source info
|
||||
const FMD5Hash Hash = FSUDSScriptImporter::CalculateHash(Buffer, BufferEnd - Buffer);
|
||||
Result->AssetImportData->Update(FactoryCurrentFilename, Hash);
|
||||
|
||||
// VO assets at import time?
|
||||
if (ShouldGenerateVoiceAssets(LongPackagePath))
|
||||
{
|
||||
FSUDSEditorVoiceOverTools::GenerateAssets(Result, Flags, &Logger);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostImport(this, Result);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool USUDSScriptFactory::ShouldGenerateVoiceAssets(const FString& PackagePath) const
|
||||
{
|
||||
if (auto Settings = GetDefault<USUDSEditorSettings>())
|
||||
{
|
||||
return Settings->ShouldGenerateVoiceAssets(PackagePath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UStringTable* USUDSScriptFactory::CreateStringTable(UObject* ScriptParent, FName InName, USUDSScript* Script, EObjectFlags Flags, FSUDSMessageLogger* Logger)
|
||||
{
|
||||
auto Settings = GetDefault<USUDSEditorSettings>();
|
||||
const bool bCreateSeparatePackage = Settings && Settings->bCreateStringTablesAsSeparatePackages;
|
||||
|
||||
const FName StringTableName = FName(InName.ToString() + "Strings");
|
||||
UStringTable* Table = nullptr;
|
||||
auto Registry = &FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
|
||||
const FString PackageDir = FPackageName::GetLongPackagePath(Script->GetOuter()->GetOutermost()->GetPathName());
|
||||
const FString PackageName = UPackageTools::SanitizePackageName(PackageDir / StringTableName.ToString());
|
||||
|
||||
if (bCreateSeparatePackage)
|
||||
{
|
||||
|
||||
// Create string table as its own package (.uasset)
|
||||
|
||||
{
|
||||
// Destroy any string table packed together with the script
|
||||
const FString ScriptPackageName = Script->GetPackage()->GetPathName();
|
||||
TArray<FAssetData> ScriptAssets;
|
||||
Registry->GetAssetsByPackageName(*ScriptPackageName, ScriptAssets);
|
||||
TArray<UObject*> StringTableAssets;
|
||||
for (auto& Asset : ScriptAssets)
|
||||
{
|
||||
if (Asset.GetClass() == UStringTable::StaticClass())
|
||||
{
|
||||
StringTableAssets.Add(Asset.GetAsset());
|
||||
}
|
||||
}
|
||||
if (StringTableAssets.Num() > 0)
|
||||
{
|
||||
ObjectTools::ForceDeleteObjects(StringTableAssets, false );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (FPackageName::DoesPackageExist(*PackageName))
|
||||
{
|
||||
// Package already exists, so try and import over the top of it, if it doesn't already have a source file path
|
||||
TArray<FAssetData> Assets;
|
||||
if (Registry->GetAssetsByPackageName(*PackageName, Assets))
|
||||
{
|
||||
if (Assets.Num() > 0)
|
||||
{
|
||||
if (Assets[0].GetAsset()->IsA(UStringTable::StaticClass()))
|
||||
{
|
||||
Table = Cast<UStringTable>(Assets[0].GetAsset());
|
||||
Table->GetMutableStringTable()->ClearSourceStrings();
|
||||
Table->MarkPackageDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT(
|
||||
"Asset %s already exists but is not a String Table! Cannot replace, please move aside and re-import script."),
|
||||
*PackageName);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Table)
|
||||
{
|
||||
// String Table didn't exist (although package might have)
|
||||
// It's safe to call CreatePackage either way, it'll return the existing one if needed
|
||||
UPackage* Package = CreatePackage(*PackageName);
|
||||
if (!ensure(Package))
|
||||
{
|
||||
Logger->Logf(ELogVerbosity::Error,
|
||||
TEXT("Failed to create/retrieve package for string table %s"),
|
||||
*PackageName);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Logger->Logf(ELogVerbosity::Display, TEXT("Creating string table %s"), *PackageName);
|
||||
|
||||
// This constructor registers the string table with FStringTableRegistry
|
||||
Table = NewObject<UStringTable>(Package, StringTableName, Flags);
|
||||
Package->FullyLoad();
|
||||
Table->MarkPackageDirty();
|
||||
FAssetRegistryModule::AssetCreated(Table);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Destroy separate strings package if it exists (could flip this option off after importing)
|
||||
if (FPackageName::DoesPackageExist(*PackageName))
|
||||
{
|
||||
TArray<FAssetData> Assets;
|
||||
if (Registry->GetAssetsByPackageName(*PackageName, Assets))
|
||||
{
|
||||
if (Assets.Num() > 0)
|
||||
{
|
||||
// Note: we need to force deletion because old string table will be referenced
|
||||
// Unfortunately there is no ObjectTools::ForceDeleteAssets, only the inner
|
||||
// ForceDeleteObjects, so we need to do it ourselves
|
||||
ForceDeleteAssets(Assets);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default route, create string table inside script package
|
||||
Table = NewObject<UStringTable>(ScriptParent, StringTableName, Flags);
|
||||
|
||||
}
|
||||
|
||||
return Table;
|
||||
|
||||
}
|
||||
|
||||
void USUDSScriptFactory::ForceDeleteAssets(const TArray<FAssetData>& AssetsToDelete)
|
||||
{
|
||||
// Copied from ObjectTools::DeleteAssets, except that we're using ForceDeleteObjects so we
|
||||
// don't have to display any confusing messages about moving the string table into the shared asset
|
||||
|
||||
TArray<TWeakObjectPtr<UPackage>> PackageFilesToDelete;
|
||||
TArray<UObject*> ObjectsToDelete;
|
||||
for ( int i = 0; i < AssetsToDelete.Num(); i++ )
|
||||
{
|
||||
const FAssetData& AssetData = AssetsToDelete[i];
|
||||
UObject *ObjectToDelete = AssetData.GetAsset({ ULevel::LoadAllExternalObjectsTag });
|
||||
// Assets can be loaded even when their underlying type/class no longer exists...
|
||||
if ( ObjectToDelete!=nullptr )
|
||||
{
|
||||
ObjectsToDelete.Add( ObjectToDelete );
|
||||
}
|
||||
else if ( AssetData.IsUAsset() )
|
||||
{
|
||||
// ... In this cases there is no underlying asset or type so remove the package itself directly after confirming it's valid to do so.
|
||||
FString PackageFilename;
|
||||
if( !FPackageName::DoesPackageExist( AssetData.PackageName.ToString(), &PackageFilename ) )
|
||||
{
|
||||
// Could not determine filename for package so we can not delete
|
||||
continue;
|
||||
}
|
||||
|
||||
UPackage* Package = FindPackage(nullptr, *AssetData.PackageName.ToString());
|
||||
if ( Package )
|
||||
{
|
||||
PackageFilesToDelete.Add(Package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 NumObjectsToDelete = ObjectsToDelete.Num();
|
||||
if ( NumObjectsToDelete > 0 )
|
||||
{
|
||||
ObjectTools::ForceDeleteObjects( ObjectsToDelete, false );
|
||||
}
|
||||
|
||||
const int32 NumPackagesToDelete = PackageFilesToDelete.Num();
|
||||
if (NumPackagesToDelete > 0)
|
||||
{
|
||||
TArray<UPackage*> PackagePointers;
|
||||
for ( const auto& PkgIt : PackageFilesToDelete )
|
||||
{
|
||||
UPackage* Package = PkgIt.Get();
|
||||
if ( Package )
|
||||
{
|
||||
PackagePointers.Add(Package);
|
||||
}
|
||||
}
|
||||
|
||||
if ( PackagePointers.Num() > 0 )
|
||||
{
|
||||
const bool bPerformReferenceCheck = true;
|
||||
ObjectTools::CleanupAfterSuccessfulDelete(PackagePointers, bPerformReferenceCheck);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2741
Plugins/SUDS/Source/SUDSEditor/Private/SUDSScriptImporter.cpp
Normal file
2741
Plugins/SUDS/Source/SUDSEditor/Private/SUDSScriptImporter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,149 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#include "SUDSScriptReimportFactory.h"
|
||||
|
||||
#include "SUDSEditor.h"
|
||||
#include "SUDSMessageLogger.h"
|
||||
#include "SUDSScript.h"
|
||||
#include "SUDSScriptNodeText.h"
|
||||
#include "EditorFramework/AssetImportData.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Sound/DialogueWave.h"
|
||||
|
||||
USUDSScriptReimportFactory::USUDSScriptReimportFactory()
|
||||
{
|
||||
SupportedClass = USUDSScript::StaticClass();
|
||||
bCreateNew = false;
|
||||
// We need to have a unique priority vs the original factory, so go after
|
||||
ImportPriority = DefaultImportPriority - 1;
|
||||
}
|
||||
|
||||
bool USUDSScriptReimportFactory::CanReimport(UObject* Obj, TArray<FString>& OutFilenames)
|
||||
{
|
||||
USUDSScript* Script = Cast<USUDSScript>(Obj);
|
||||
if (Script && Script->AssetImportData)
|
||||
{
|
||||
Script->AssetImportData->ExtractFilenames(OutFilenames);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void USUDSScriptReimportFactory::SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths)
|
||||
{
|
||||
USUDSScript* Script = Cast<USUDSScript>(Obj);
|
||||
if (Script && ensure(NewReimportPaths.Num() == 1))
|
||||
{
|
||||
Script->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
EReimportResult::Type USUDSScriptReimportFactory::Reimport(UObject* Obj)
|
||||
{
|
||||
USUDSScript* Script = Cast<USUDSScript>(Obj);
|
||||
if (!Script)
|
||||
{
|
||||
return EReimportResult::Failed;
|
||||
}
|
||||
|
||||
// Make sure file is valid and exists
|
||||
const FString Filename = Script->AssetImportData->GetFirstFilename();
|
||||
if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE)
|
||||
{
|
||||
return EReimportResult::Failed;
|
||||
}
|
||||
|
||||
// When a new script is created, it actually lives at the same address as the incoming one. UE must re-use objects
|
||||
// when you put them back at the same outer & asset name?
|
||||
// This means if we want to preserve anything from the previously imported object, such as generated VO asset links,
|
||||
// we need to copy those out now.
|
||||
TMap<FString, UDialogueVoice*> PrevSpeakerVoices = Script->GetSpeakerVoices();
|
||||
// Store the TextID -> DialogueWave, but also store the line text as well so we can detect whether it matches & warn if not
|
||||
TMap<FString, TPair<FString, UDialogueWave*> > PrevWaves;
|
||||
for (auto Node : Script->GetNodes())
|
||||
{
|
||||
if (auto TN = Cast<USUDSScriptNodeText>(Node))
|
||||
{
|
||||
if (auto W = TN->GetWave())
|
||||
{
|
||||
PrevWaves.Add(TN->GetTextID(), TPair<FString, UDialogueWave*>(TN->GetText().ToString(), W));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the import again
|
||||
EReimportResult::Type Result = EReimportResult::Failed;
|
||||
bool OutCanceled = false;
|
||||
|
||||
if (ImportObject(Script->GetClass(), Script->GetOuter(), *Script->GetName(), RF_Public | RF_Standalone, Filename, nullptr, OutCanceled) != nullptr)
|
||||
{
|
||||
UE_LOG(LogSUDSEditor, Log, TEXT("Imported successfully"));
|
||||
|
||||
FSUDSMessageLogger Logger;
|
||||
// Now, try to restore the speaker voice / line wave links from before
|
||||
for (auto SpeakerID : Script->GetSpeakers())
|
||||
{
|
||||
if (auto pVoice = PrevSpeakerVoices.Find(SpeakerID))
|
||||
{
|
||||
Script->SetSpeakerVoice(SpeakerID, *pVoice);
|
||||
}
|
||||
}
|
||||
for (auto Node : Script->GetNodes())
|
||||
{
|
||||
if (auto TN = Cast<USUDSScriptNodeText>(Node))
|
||||
{
|
||||
if (auto pWavePair = PrevWaves.Find(TN->GetTextID()))
|
||||
{
|
||||
// Set the wave link either way
|
||||
TN->SetWave(pWavePair->Value);
|
||||
|
||||
// Check that the text is the same; if it's not, then lines have potentially changed
|
||||
// we still live with the assignment we have (might be a minor edit) but user should be aware
|
||||
if (TN->GetText().ToString() != pWavePair->Key)
|
||||
{
|
||||
Logger.Logf(ELogVerbosity::Error,
|
||||
TEXT(
|
||||
"TextID %s is linked to Dialogue Wave %s, but text has changed. Check whether this line is linked to the correct wave, and consider Writing String Keys back to script before making more script changes in future."),
|
||||
*TN->GetTextID(),
|
||||
*pWavePair->Value->GetName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Script->AssetImportData->Update(Filename);
|
||||
|
||||
// Try to find the outer package so we can dirty it up
|
||||
if (Script->GetOuter())
|
||||
{
|
||||
Script->GetOuter()->MarkPackageDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
Script->MarkPackageDirty();
|
||||
}
|
||||
Result = EReimportResult::Succeeded;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OutCanceled)
|
||||
{
|
||||
UE_LOG(LogSUDSEditor, Warning, TEXT("-- import canceled"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogSUDSEditor, Warning, TEXT("-- import failed"));
|
||||
}
|
||||
|
||||
Result = EReimportResult::Failed;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
int32 USUDSScriptReimportFactory::GetPriority() const
|
||||
{
|
||||
return ImportPriority;
|
||||
}
|
||||
Reference in New Issue
Block a user