You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This change consists of multiple changes: Core: - Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject) - Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter - Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses - Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names - Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed. - Added static UClass::TryConvertShortNameToPathName utility function - Added static UClass::TryFixShortClassNameExportPath utility function - Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass') - All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath - Added a new startup test that checks for short type names in UClass/FProperty MetaData values AssetRegistry: - Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath - Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names - This also applies to a few other modules' APIs to match AssetRegistry changes Everything else: - Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input) - Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName() - Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names #jira UE-99463 #rb many.people [FYI] Marcus.Wassmer #preflight 629248ec2256738f75de9b32 #codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786 #ROBOMERGE-OWNER: robert.manuszewski #ROBOMERGE-AUTHOR: robert.manuszewski #ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246) [CL 20448496 by robert manuszewski in ue5-main branch]
481 lines
18 KiB
C++
481 lines
18 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Commandlets/ExportDialogueScriptCommandlet.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "Sound/SoundWave.h"
|
|
#include "Sound/DialogueWave.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/App.h"
|
|
#include "UObject/PropertyPortFlags.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "Sound/DialogueVoice.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogExportDialogueScriptCommandlet, Log, All);
|
|
|
|
namespace
|
|
{
|
|
struct FCollapsedDialogueContextKey
|
|
{
|
|
FCollapsedDialogueContextKey(const UDialogueWave* InDialogueWave, const FDialogueContextMapping* InContext, FString InLocalizedSpokenText)
|
|
: DialogueWave(InDialogueWave)
|
|
, Context(InContext)
|
|
, LocalizedSpokenText(MoveTemp(InLocalizedSpokenText))
|
|
{
|
|
}
|
|
|
|
bool operator==(const FCollapsedDialogueContextKey& InOther) const
|
|
{
|
|
// We only care about the text that is spoken, and the voice that is speaking it
|
|
return LocalizedSpokenText.Equals(InOther.LocalizedSpokenText, ESearchCase::CaseSensitive)
|
|
&& Context->Context.Speaker == InOther.Context->Context.Speaker;
|
|
}
|
|
|
|
bool operator!=(const FCollapsedDialogueContextKey& InOther) const
|
|
{
|
|
return !(*this == InOther);
|
|
}
|
|
|
|
friend inline uint32 GetTypeHash(const FCollapsedDialogueContextKey& InKey)
|
|
{
|
|
// We only care about the text that is spoken, and the voice that is speaking it
|
|
uint32 KeyHash = 0;
|
|
KeyHash = HashCombine(KeyHash, FCrc::StrCrc32(*InKey.LocalizedSpokenText)); // Need case-sensitive hash
|
|
KeyHash = HashCombine(KeyHash, GetTypeHash(InKey.Context->Context.Speaker));
|
|
return KeyHash;
|
|
}
|
|
|
|
const UDialogueWave* DialogueWave;
|
|
const FDialogueContextMapping* Context;
|
|
FString LocalizedSpokenText;
|
|
};
|
|
}
|
|
|
|
UExportDialogueScriptCommandlet::UExportDialogueScriptCommandlet(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
int32 UExportDialogueScriptCommandlet::Main(const FString& Params)
|
|
{
|
|
// Parse command line
|
|
TArray<FString> Tokens;
|
|
TArray<FString> Switches;
|
|
TMap<FString, FString> ParamVals;
|
|
UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals);
|
|
|
|
// Set config path
|
|
FString ConfigPath;
|
|
{
|
|
const FString* ConfigPathParamVal = ParamVals.Find(FString(TEXT("Config")));
|
|
if (!ConfigPathParamVal)
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No config specified."));
|
|
return -1;
|
|
}
|
|
ConfigPath = *ConfigPathParamVal;
|
|
}
|
|
|
|
// Set config section
|
|
FString SectionName;
|
|
{
|
|
const FString* SectionNameParamVal = ParamVals.Find(FString(TEXT("Section")));
|
|
if (!SectionNameParamVal)
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No config section specified."));
|
|
return -1;
|
|
}
|
|
SectionName = *SectionNameParamVal;
|
|
}
|
|
|
|
// Source path to the root folder that manifest/archive files live in
|
|
FString SourcePath;
|
|
if (!GetPathFromConfig(*SectionName, TEXT("SourcePath"), SourcePath, ConfigPath))
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No source path specified."));
|
|
return -1;
|
|
}
|
|
|
|
// Destination path to the root folder that dialogue script CSV files live in
|
|
FString DestinationPath;
|
|
if (!GetPathFromConfig(*SectionName, TEXT("DestinationPath"), DestinationPath, ConfigPath))
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No destination path specified."));
|
|
return -1;
|
|
}
|
|
|
|
// Get culture directory setting, default to true if not specified (used to allow picking of export directory with windows file dialog from Translation Editor)
|
|
bool bUseCultureDirectory = true;
|
|
if (!GetBoolFromConfig(*SectionName, TEXT("bUseCultureDirectory"), bUseCultureDirectory, ConfigPath))
|
|
{
|
|
bUseCultureDirectory = true;
|
|
}
|
|
|
|
// Get the native culture
|
|
FString NativeCulture;
|
|
if (!GetStringFromConfig(*SectionName, TEXT("NativeCulture"), NativeCulture, ConfigPath))
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No native culture specified."));
|
|
return -1;
|
|
}
|
|
|
|
// Get cultures to generate
|
|
TArray<FString> CulturesToGenerate;
|
|
if (GetStringArrayFromConfig(*SectionName, TEXT("CulturesToGenerate"), CulturesToGenerate, ConfigPath) == 0)
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No cultures specified for import."));
|
|
return -1;
|
|
}
|
|
|
|
// Get the manifest name
|
|
FString ManifestName;
|
|
if (!GetStringFromConfig(*SectionName, TEXT("ManifestName"), ManifestName, ConfigPath))
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No manifest name specified."));
|
|
return -1;
|
|
}
|
|
|
|
// Get the archive name
|
|
FString ArchiveName;
|
|
if (!GetStringFromConfig(*SectionName, TEXT("ArchiveName"), ArchiveName, ConfigPath))
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No archive name specified."));
|
|
return -1;
|
|
}
|
|
|
|
// Get the dialogue script name
|
|
FString DialogueScriptName;
|
|
if (!GetStringFromConfig(*SectionName, TEXT("DialogueScriptName"), DialogueScriptName, ConfigPath))
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("No dialogue script name specified."));
|
|
return -1;
|
|
}
|
|
|
|
// We may only have a single culture if using this setting
|
|
if (!bUseCultureDirectory && CulturesToGenerate.Num() > 1)
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("bUseCultureDirectory may only be used with a single culture."));
|
|
return false;
|
|
}
|
|
|
|
// Load the manifest and all archives
|
|
FLocTextHelper LocTextHelper(SourcePath, ManifestName, ArchiveName, NativeCulture, CulturesToGenerate, MakeShareable(new FLocFileSCCNotifies(SourceControlInfo)));
|
|
{
|
|
FText LoadError;
|
|
if (!LocTextHelper.LoadAll(ELocTextHelperLoadFlags::LoadOrCreate, &LoadError))
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("%s"), *LoadError.ToString());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const FString RootAssetPath = FApp::HasProjectName() ? TEXT("/Game") : TEXT("/Engine");
|
|
|
|
// Prepare the asset registry
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
|
|
AssetRegistry.SearchAllAssets(true);
|
|
|
|
// We want all the non-localized project specific dialogue waves
|
|
TArray<FAssetData> AssetDataArrayForDialogueWaves;
|
|
if (!FLocalizedAssetUtil::GetAssetsByPathAndClass(AssetRegistry, *RootAssetPath, UDialogueWave::StaticClass()->GetClassPathName(), /*bIncludeLocalizedAssets*/false, AssetDataArrayForDialogueWaves))
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("Unable to get dialogue wave asset data from asset registry."));
|
|
return -1;
|
|
}
|
|
|
|
for (const FString& CultureName : CulturesToGenerate)
|
|
{
|
|
const bool bIsNativeCulture = CultureName == NativeCulture;
|
|
const FString CultureSourcePath = SourcePath / CultureName;
|
|
const FString CultureDestinationPath = DestinationPath / (bUseCultureDirectory ? CultureName : TEXT(""));
|
|
|
|
TArray<TSharedPtr<FDialogueScriptEntry>> ExportedDialogueLines;
|
|
for (const FAssetData& AssetData : AssetDataArrayForDialogueWaves)
|
|
{
|
|
// Verify that the found asset is a dialogue wave
|
|
if (AssetData.GetClass() != UDialogueWave::StaticClass())
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("Asset registry found asset '%s', but the asset with this name is not actually a dialogue wave."), *AssetData.AssetName.ToString());
|
|
continue;
|
|
}
|
|
|
|
// Get the dialogue wave
|
|
UDialogueWave* const DialogueWave = Cast<UDialogueWave>(AssetData.GetAsset());
|
|
|
|
// Verify that the dialogue wave was loaded
|
|
if (!DialogueWave)
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("Asset registry found asset '%s', but the dialogue wave could not be accessed."), *AssetData.AssetName.ToString());
|
|
continue;
|
|
}
|
|
|
|
// This maps collapsed context keys to additional contexts that were collapsed into the primary context (the context within the key) - all contexts belong to the dialogue wave in the key
|
|
// If multiple contexts have the same speaking voice and use the same dialogue (because it translates to the same text), then only one of those contexts needs to be exported
|
|
// The resultant audio file will create a shared asset automatically when the dialogue is imported
|
|
TMap<FCollapsedDialogueContextKey, TArray<const FDialogueContextMapping*>> CollapsedDialogueContexts;
|
|
|
|
// Iterate over each context to build the list of unique entries
|
|
for (FDialogueContextMapping& ContextMapping : DialogueWave->ContextMappings)
|
|
{
|
|
const FString ContextLocalizationKey = DialogueWave->GetContextLocalizationKey(ContextMapping);
|
|
|
|
// Check that this entry exists in the manifest file, as we want to skip over dialogue that we aren't gathering
|
|
TSharedPtr<FManifestEntry> ContextManifestEntry = LocTextHelper.FindSourceText(FDialogueConstants::DialogueNamespace, ContextLocalizationKey, &DialogueWave->SpokenText);
|
|
if (!ContextManifestEntry.IsValid())
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Log, TEXT("No internationalization manifest entry was found for context '%s' in culture '%s'. This context will be skipped."), *ContextLocalizationKey, *CultureName);
|
|
continue;
|
|
}
|
|
|
|
// Find the correct entry for our context
|
|
const FManifestContext* ContextManifestEntryContext = ContextManifestEntry->FindContextByKey(ContextLocalizationKey);
|
|
check(ContextManifestEntryContext); // This should never fail as we pass in the key to FindSourceText
|
|
|
|
// Get the localized text to export
|
|
FLocItem ExportedSource;
|
|
FLocItem ExportedTranslation;
|
|
LocTextHelper.GetExportText(CultureName, FDialogueConstants::DialogueNamespace, ContextManifestEntryContext->Key, ContextManifestEntryContext->KeyMetadataObj, ELocTextExportSourceMethod::NativeText, ContextManifestEntry->Source, ExportedSource, ExportedTranslation);
|
|
|
|
if (ExportedTranslation.Text.IsEmpty())
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Log, TEXT("Empty translation found for context '%s' in culture '%s'. This context will be skipped."), *ContextLocalizationKey, *CultureName);
|
|
continue;
|
|
}
|
|
|
|
const auto CollapsedDialogueContextKey = FCollapsedDialogueContextKey(DialogueWave, &ContextMapping, ExportedTranslation.Text);
|
|
TArray<const FDialogueContextMapping*>* MergedContextsPtr = CollapsedDialogueContexts.Find(CollapsedDialogueContextKey);
|
|
if (MergedContextsPtr)
|
|
{
|
|
MergedContextsPtr->Add(&ContextMapping);
|
|
}
|
|
else
|
|
{
|
|
CollapsedDialogueContexts.Add(CollapsedDialogueContextKey);
|
|
}
|
|
}
|
|
|
|
// Get the localized voice actor direction
|
|
FLocItem ExportedVoiceActorDirectionSource;
|
|
FLocItem ExportedVoiceActorDirectionTranslation;
|
|
LocTextHelper.GetExportText(CultureName, FDialogueConstants::DialogueNamespace, DialogueWave->LocalizationGUID.ToString() + FDialogueConstants::ActingDirectionKeySuffix, nullptr, ELocTextExportSourceMethod::NativeText, FLocItem(DialogueWave->VoiceActorDirection), ExportedVoiceActorDirectionSource, ExportedVoiceActorDirectionTranslation);
|
|
|
|
// Get the localized version of the dialogue wave for the current culture
|
|
UDialogueWave* LocalizedDialogueWave = nullptr;
|
|
{
|
|
const FString LocalizedDialogueWavePackagePath = FPackageName::GetLocalizedPackagePath(AssetData.PackageName.ToString(), CultureName);
|
|
const FAssetData LocalizedDialogueWaveAssetData = AssetRegistry.GetAssetByObjectPath(*FString::Printf(TEXT("%s.%s"), *LocalizedDialogueWavePackagePath, *AssetData.AssetName.ToString()));
|
|
LocalizedDialogueWave = Cast<UDialogueWave>(LocalizedDialogueWaveAssetData.GetAsset());
|
|
if (LocalizedDialogueWave == DialogueWave)
|
|
{
|
|
LocalizedDialogueWave = nullptr;
|
|
}
|
|
}
|
|
|
|
// Iterate over the unique contexts and generate exported data for them
|
|
for (const auto& CollapsedDialogueContextPair : CollapsedDialogueContexts)
|
|
{
|
|
TSharedRef<FDialogueScriptEntry> ExportedDialogueLine = MakeShareable(new FDialogueScriptEntry());
|
|
PopulateDialogueScriptEntry(DialogueWave, LocalizedDialogueWave, *CollapsedDialogueContextPair.Key.Context, CollapsedDialogueContextPair.Value, CollapsedDialogueContextPair.Key.LocalizedSpokenText, ExportedVoiceActorDirectionTranslation.Text, *ExportedDialogueLine);
|
|
ExportedDialogueLines.Add(ExportedDialogueLine);
|
|
}
|
|
}
|
|
|
|
// Sort the exported lines to maintain a consistent order between exports
|
|
// Sort order is speaking voice name, then localized dialogue
|
|
ExportedDialogueLines.Sort([](const TSharedPtr<FDialogueScriptEntry>& InFirstEntry, const TSharedPtr<FDialogueScriptEntry>& InSecondEntry) -> bool
|
|
{
|
|
const int32 SpeakingVoiceResult = InFirstEntry->SpeakingVoice.Compare(InSecondEntry->SpeakingVoice, ESearchCase::CaseSensitive);
|
|
if (SpeakingVoiceResult < 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (SpeakingVoiceResult == 0 && InFirstEntry->SpokenDialogue.Compare(InSecondEntry->SpokenDialogue, ESearchCase::CaseSensitive) < 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
{
|
|
FString CSVFileData;
|
|
CSVFileData += GenerateCSVHeader() + TEXT("\n");
|
|
for (const auto& DialogueScriptEntry : ExportedDialogueLines)
|
|
{
|
|
CSVFileData += GenerateCSVRow(*DialogueScriptEntry) + TEXT("\n");
|
|
}
|
|
|
|
const FString CSVFileName = CultureDestinationPath / DialogueScriptName;
|
|
const bool bCSVFileSaved = FLocalizedAssetSCCUtil::SaveFileWithSCC(SourceControlInfo, CSVFileName, [&](const FString& InSaveFileName) -> bool
|
|
{
|
|
return FFileHelper::SaveStringToFile(CSVFileData, *InSaveFileName, FFileHelper::EEncodingOptions::ForceUTF8);
|
|
});
|
|
|
|
if (!bCSVFileSaved)
|
|
{
|
|
UE_LOG(LogExportDialogueScriptCommandlet, Error, TEXT("Failed to write CSV file for culture '%s' to '%s'."), *CultureName, *CSVFileName);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
FString UExportDialogueScriptCommandlet::GenerateCSVHeader()
|
|
{
|
|
FString CSVHeader;
|
|
|
|
for (TFieldIterator<const FProperty> PropertyIt(FDialogueScriptEntry::StaticStruct(), EFieldIteratorFlags::IncludeSuper, EFieldIteratorFlags::ExcludeDeprecated, EFieldIteratorFlags::IncludeInterfaces); PropertyIt; ++PropertyIt)
|
|
{
|
|
if (!CSVHeader.IsEmpty())
|
|
{
|
|
CSVHeader += TEXT(",");
|
|
}
|
|
|
|
const FString PropertyName = PropertyIt->GetName();
|
|
|
|
CSVHeader += TEXT("\"");
|
|
CSVHeader += PropertyName.Replace(TEXT("\""), TEXT("\"\""));
|
|
CSVHeader += TEXT("\"");
|
|
}
|
|
|
|
return CSVHeader;
|
|
}
|
|
|
|
FString UExportDialogueScriptCommandlet::GenerateCSVRow(const FDialogueScriptEntry& InDialogueScriptEntry)
|
|
{
|
|
FString CSVRow;
|
|
|
|
for (TFieldIterator<const FProperty> PropertyIt(FDialogueScriptEntry::StaticStruct(), EFieldIteratorFlags::IncludeSuper, EFieldIteratorFlags::ExcludeDeprecated, EFieldIteratorFlags::IncludeInterfaces); PropertyIt; ++PropertyIt)
|
|
{
|
|
if (!CSVRow.IsEmpty())
|
|
{
|
|
CSVRow += TEXT(",");
|
|
}
|
|
|
|
FString PropertyValue;
|
|
PropertyIt->ExportTextItem_InContainer(PropertyValue, &InDialogueScriptEntry, nullptr, nullptr, PPF_None);
|
|
|
|
CSVRow += TEXT("\"");
|
|
CSVRow += PropertyValue.Replace(TEXT("\""), TEXT("\"\""));
|
|
CSVRow += TEXT("\"");
|
|
}
|
|
|
|
return CSVRow;
|
|
}
|
|
|
|
void UExportDialogueScriptCommandlet::PopulateDialogueScriptEntry(const UDialogueWave* InDialogueWave, const UDialogueWave* InLocalizedDialogueWave, const FDialogueContextMapping& InPrimaryContext, const TArray<const FDialogueContextMapping*>& InAdditionalContexts, const FString& InLocalizedDialogue, const FString& InLocalizedVoiceActorDirection, FDialogueScriptEntry& OutDialogueScriptEntry)
|
|
{
|
|
auto AppendTargetVoices = [&](const FDialogueContext& InContext)
|
|
{
|
|
if (InContext.Targets.Num() > 0)
|
|
{
|
|
FString TargetVoicesText;
|
|
|
|
const bool bIsArray = InContext.Targets.Num() > 1;
|
|
if (bIsArray)
|
|
{
|
|
TargetVoicesText += TEXT("[");
|
|
}
|
|
|
|
bool bIsFirst = true;
|
|
for (const UDialogueVoice* TargetVoice : InContext.Targets)
|
|
{
|
|
if (!bIsFirst)
|
|
{
|
|
TargetVoicesText += TEXT(",");
|
|
}
|
|
bIsFirst = false;
|
|
|
|
TargetVoicesText += TargetVoice->GetName();
|
|
}
|
|
|
|
if (bIsArray)
|
|
{
|
|
TargetVoicesText += TEXT("]");
|
|
}
|
|
|
|
OutDialogueScriptEntry.TargetVoices.Add(MoveTemp(TargetVoicesText));
|
|
}
|
|
};
|
|
|
|
auto AppendTargetVoiceGUIDs = [&](const FDialogueContext& InContext)
|
|
{
|
|
if (InContext.Targets.Num() > 0)
|
|
{
|
|
FString TargetVoiceGUIDsText;
|
|
|
|
const bool bIsArray = InContext.Targets.Num() > 1;
|
|
if (bIsArray)
|
|
{
|
|
TargetVoiceGUIDsText += TEXT("[");
|
|
}
|
|
|
|
bool bIsFirst = true;
|
|
for (const UDialogueVoice* TargetVoice : InContext.Targets)
|
|
{
|
|
if (!bIsFirst)
|
|
{
|
|
TargetVoiceGUIDsText += TEXT(",");
|
|
}
|
|
bIsFirst = false;
|
|
|
|
TargetVoiceGUIDsText += TargetVoice->LocalizationGUID.ToString();
|
|
}
|
|
|
|
if (bIsArray)
|
|
{
|
|
TargetVoiceGUIDsText += TEXT("]");
|
|
}
|
|
|
|
OutDialogueScriptEntry.TargetVoiceGUIDs.Add(MoveTemp(TargetVoiceGUIDsText));
|
|
}
|
|
};
|
|
|
|
auto HasLocalizedSoundWave = [&](const FDialogueContext& InContext) -> bool
|
|
{
|
|
if (InLocalizedDialogueWave)
|
|
{
|
|
for (const FDialogueContextMapping& LocalizedContextMapping : InLocalizedDialogueWave->ContextMappings)
|
|
{
|
|
if (LocalizedContextMapping.Context == InContext)
|
|
{
|
|
return LocalizedContextMapping.SoundWave && LocalizedContextMapping.SoundWave->IsLocalizedResource();
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
OutDialogueScriptEntry.SpokenDialogue = InLocalizedDialogue;
|
|
OutDialogueScriptEntry.VoiceActorDirection = InLocalizedVoiceActorDirection;
|
|
OutDialogueScriptEntry.AudioFileName = InDialogueWave->GetContextRecordedAudioFilename(InPrimaryContext);
|
|
OutDialogueScriptEntry.DialogueAsset = InDialogueWave->GetPathName();
|
|
OutDialogueScriptEntry.IsRecorded = HasLocalizedSoundWave(InPrimaryContext.Context);
|
|
|
|
OutDialogueScriptEntry.SpeakingVoice = InPrimaryContext.Context.Speaker->GetName();
|
|
OutDialogueScriptEntry.SpeakingVoiceGUID = InPrimaryContext.Context.Speaker->LocalizationGUID.ToString();
|
|
OutDialogueScriptEntry.DialogueAssetGUID = InDialogueWave->LocalizationGUID.ToString();
|
|
|
|
OutDialogueScriptEntry.LocalizationKeys.Add(InDialogueWave->GetContextLocalizationKey(InPrimaryContext));
|
|
AppendTargetVoices(InPrimaryContext.Context);
|
|
AppendTargetVoiceGUIDs(InPrimaryContext.Context);
|
|
|
|
for (const FDialogueContextMapping* AdditionalContext : InAdditionalContexts)
|
|
{
|
|
if (!OutDialogueScriptEntry.IsRecorded)
|
|
{
|
|
OutDialogueScriptEntry.IsRecorded = HasLocalizedSoundWave(AdditionalContext->Context);
|
|
}
|
|
|
|
OutDialogueScriptEntry.LocalizationKeys.Add(InDialogueWave->GetContextLocalizationKey(*AdditionalContext));
|
|
AppendTargetVoices(AdditionalContext->Context);
|
|
AppendTargetVoiceGUIDs(AdditionalContext->Context);
|
|
}
|
|
}
|