Initial push to repo
This commit is contained in:
24
Plugins/SUDS/Source/SUDSEditor/Public/SUDSEditor.h
Normal file
24
Plugins/SUDS/Source/SUDSEditor/Public/SUDSEditor.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
|
||||
class FSUDSScriptActions;
|
||||
class FSlateStyleSet;
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogSUDSEditor, Verbose, All);
|
||||
|
||||
class FSUDSEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
protected:
|
||||
TSharedPtr<FSUDSScriptActions> ScriptActions;
|
||||
TSharedPtr<FSlateStyleSet> StyleSet;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class USUDSScript;
|
||||
class USUDSScriptNode;
|
||||
struct FSUDSMessageLogger;
|
||||
|
||||
class FSUDSEditorScriptTools
|
||||
{
|
||||
public:
|
||||
|
||||
static void WriteBackTextIDs(USUDSScript* Script, FSUDSMessageLogger& Logger);
|
||||
static bool WriteBackTextIDsFromNodes(const TArray<USUDSScriptNode*> Nodes, TArray<FString>& Lines, const FString& NameForErrors, FSUDSMessageLogger& Logger);
|
||||
static bool WriteBackTextID(const FText& AssetText, int LineNo, TArray<FString>& Lines, const FString& NameForErrors, FSUDSMessageLogger& Logger);
|
||||
static bool WriteBackGosubID(const FString& GosubID, int LineNo, TArray<FString>& Lines, const FString& NameForErrors, FSUDSMessageLogger& Logger);
|
||||
static bool TextIDCheckMatch(const FText& AssetText, const FString& SourceLine);
|
||||
|
||||
|
||||
};
|
||||
73
Plugins/SUDS/Source/SUDSEditor/Public/SUDSEditorSettings.h
Normal file
73
Plugins/SUDS/Source/SUDSEditor/Public/SUDSEditorSettings.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "SUDSEditorSettings.generated.h"
|
||||
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class ESUDSAssetLocation : uint8
|
||||
{
|
||||
/// Use a single flat shared directory
|
||||
SharedDirectory,
|
||||
/// Use a shared base directory, but create subfolders based on the script asset name
|
||||
SharedDirectorySubdir,
|
||||
/// Place asset alongside the script that originated it
|
||||
ScriptDirectory,
|
||||
/// Place asset in a subfolder of the folder containing the script that generated it, named the same as the script
|
||||
ScriptDirectorySubdir
|
||||
};
|
||||
|
||||
class USUDSScript;
|
||||
/**
|
||||
* Settings for editor-specific aspects of SUDS (no effect at runtime)
|
||||
*/
|
||||
UCLASS(config = Editor, defaultconfig, meta=(DisplayName="SUDS Editor"))
|
||||
class SUDSEDITOR_API USUDSEditorSettings : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", meta = (Tooltip = "Where to place Dialogue Voice assets for speakers in scripts when generated", RelativeToGameContentDir, LongPackageName))
|
||||
ESUDSAssetLocation DialogueVoiceAssetLocation = ESUDSAssetLocation::SharedDirectory;
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", meta = (Tooltip = "Shared directory for Dialogue Voice assets, if using a shared directory", RelativeToGameContentDir, LongPackageName))
|
||||
FDirectoryPath DialogueVoiceAssetSharedDir;
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", meta = (Tooltip = "Where to place Dialogue Wave assets for speaker lines in scripts", RelativeToGameContentDir, LongPackageName))
|
||||
ESUDSAssetLocation DialogueWaveAssetLocation = ESUDSAssetLocation::ScriptDirectorySubdir;
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", meta = (Tooltip = "Shared directory for Dialogue Wave assets, if using a shared directory", RelativeToGameContentDir, LongPackageName))
|
||||
FDirectoryPath DialogueWaveAssetSharedDir;
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", meta = (Tooltip = "Prefix to give Dialogue Voice assets in front of their SpeakerID", RelativeToGameContentDir, LongPackageName))
|
||||
FString DialogueVoiceAssetPrefix = "DV_";
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", meta = (Tooltip = "Prefix to give Dialogue Wave assets in front of their SpeakerID", RelativeToGameContentDir, LongPackageName))
|
||||
FString DialogueWaveAssetPrefix = "DW_";
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", meta = (Tooltip = "When generating subdirectories and wave asset names from script names, whether to strip characters before the first '_' to avoid including script prefix", RelativeToGameContentDir, LongPackageName))
|
||||
bool StripScriptPrefixesWhenGeneratingNames = true;
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", AdvancedDisplay, meta = (Tooltip = "Whether to auto-generate Dialogue Voice/Wave assets for ALL dialogue scripts on import. Note: you can always generate VO assets manually."))
|
||||
bool AlwaysAutoGenerateVoiceOverAssetsOnImport = false;
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Voice", AdvancedDisplay, meta = (Tooltip = "Auto-generate Dialogue Voice/Wave assets for scripts in these directories (and subdirectories) on import. Note: you can always generate VO assets manually.", RelativeToGameContentDir, LongPackageName))
|
||||
TArray<FDirectoryPath> DirectoriesToAutoGenerateVoiceOverAssetsOnImport;
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Choices", meta = (Tooltip = "Whether to generate a spoken line for choices (default false)."))
|
||||
bool AlwaysGenerateSpeakerLinesFromChoices = false;
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Choices", meta = (Tooltip = "The SpeakerID to use for speaker lines generated from choices"))
|
||||
FString SpeakerIdForGeneratedLinesFromChoices = "Player";
|
||||
|
||||
UPROPERTY(config, EditAnywhere, Category = "Assets", AdvancedDisplay, meta = (Tooltip = "Whether to create string tables as a separate package (.uasset) from the SUDS Script, which will cause them to appear separately in the Content Browser (requires script re-import)"))
|
||||
bool bCreateStringTablesAsSeparatePackages = true;
|
||||
|
||||
USUDSEditorSettings() {}
|
||||
|
||||
bool ShouldGenerateVoiceAssets(const FString& PackagePath) const;
|
||||
FString GetVoiceOutputDir(const FString& PackagePath, const FString& ScriptName) const;
|
||||
FString GetWaveOutputDir(const FString& PackagePath, const FString& ScriptName) const;
|
||||
static FString GetOutputDir(ESUDSAssetLocation Location, const FString& SharedPath, const FString& PackagePath, const FString& ScriptName);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SUDSScriptNodeText.h"
|
||||
#include "Sound/DialogueVoice.h"
|
||||
|
||||
|
||||
struct FSUDSMessageLogger;
|
||||
class USUDSScript;
|
||||
|
||||
class SUDSEDITOR_API FSUDSEditorVoiceOverTools
|
||||
{
|
||||
public:
|
||||
static void GenerateAssets(USUDSScript* Script, EObjectFlags Flags, FSUDSMessageLogger* Logger);
|
||||
protected:
|
||||
static void GenerateVoiceAssets(USUDSScript* Script,
|
||||
EObjectFlags Flags,
|
||||
FSUDSMessageLogger *Logger,
|
||||
TMap<FString, UDialogueVoice*> &OutCreatedVoices);
|
||||
static void GenerateWaveAssets(USUDSScript* Script, EObjectFlags Flags, TMap<FString, UDialogueVoice*>, FSUDSMessageLogger* Logger);
|
||||
static bool GetSpeakerVoicePackageName(USUDSScript* Script, const FString& SpeakerID, FString& OutPackageName);
|
||||
static bool GetSpeakerVoiceAssetNames(USUDSScript* Script,
|
||||
const FString& SpeakerID,
|
||||
FString& OutPackageName,
|
||||
FString& OutAssetName);
|
||||
static UDialogueVoice* FindSpeakerVoice(USUDSScript* Script, const FString& SpeakerID);
|
||||
static FString GetScriptNameAsPrefix(USUDSScript* Script);
|
||||
|
||||
static FString GetVoiceOutputDir(USUDSScript* Script);
|
||||
static FString GetWaveOutputDir(USUDSScript* Script);
|
||||
|
||||
};
|
||||
60
Plugins/SUDS/Source/SUDSEditor/Public/SUDSMessageLogger.h
Normal file
60
Plugins/SUDS/Source/SUDSEditor/Public/SUDSMessageLogger.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Logging/TokenizedMessage.h"
|
||||
|
||||
class FTokenizedMessage;
|
||||
|
||||
struct SUDSEDITOR_API FSUDSMessageLogger
|
||||
{
|
||||
protected:
|
||||
TArray<TSharedRef<FTokenizedMessage>> ErrorMessages;
|
||||
|
||||
bool bWriteToMessageLog = true;
|
||||
public:
|
||||
FSUDSMessageLogger() {}
|
||||
FSUDSMessageLogger(bool bWriteToMsg) : bWriteToMessageLog(bWriteToMsg) {}
|
||||
~FSUDSMessageLogger();
|
||||
|
||||
void SetWriteToMessageLog(bool bWrite) { bWriteToMessageLog = bWrite; }
|
||||
bool HasErrors() const;
|
||||
int NumErrors() const;
|
||||
bool HasWarnings() const;
|
||||
int NumWarnings() const;
|
||||
|
||||
bool GetWriteToMessageLog() const { return bWriteToMessageLog; }
|
||||
void AddMessage(EMessageSeverity::Type Severity, const FText& Text);
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6
|
||||
template <typename... Types>
|
||||
FORCEINLINE void Logf(ELogVerbosity::Type Verbosity, UE::Core::TCheckedFormatString<FString::FmtCharType, Types...> Fmt, Types... Args)
|
||||
#else
|
||||
template <typename FmtType, typename... Types>
|
||||
FORCEINLINE void Logf(ELogVerbosity::Type Verbosity, const FmtType& Fmt, Types... Args)
|
||||
#endif
|
||||
{
|
||||
EMessageSeverity::Type Sev = EMessageSeverity::Info;
|
||||
switch(Verbosity)
|
||||
{
|
||||
case ELogVerbosity::Fatal:
|
||||
case ELogVerbosity::Error:
|
||||
Sev = EMessageSeverity::Error;
|
||||
break;
|
||||
case ELogVerbosity::Warning:
|
||||
Sev = EMessageSeverity::Warning;
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
AddMessage(Sev, FText::FromString(FString::Printf(Fmt, Args...)));
|
||||
}
|
||||
|
||||
const TArray<TSharedRef<FTokenizedMessage>>& GetErrorMessages() const { return ErrorMessages; }
|
||||
|
||||
/// Clear messages in preparation for an import
|
||||
static void ClearMessages();
|
||||
|
||||
|
||||
|
||||
};
|
||||
37
Plugins/SUDS/Source/SUDSEditor/Public/SUDSScriptActions.h
Normal file
37
Plugins/SUDS/Source/SUDSEditor/Public/SUDSScriptActions.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
#include "SUDSScriptNodeGosub.h"
|
||||
#include "SUDSScriptNodeText.h"
|
||||
|
||||
class USUDSScriptNode;
|
||||
class USUDSScript;
|
||||
struct FSUDSMessageLogger;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class FSUDSScriptActions : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FString GetObjectDisplayName(UObject* Object) const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
virtual void GetResolvedSourceFilePaths(const TArray<UObject*>& TypeAssets,
|
||||
TArray<FString>& OutSourceFilePaths) const override;
|
||||
virtual bool IsImportedAsset() const override { return true; }
|
||||
virtual void GetActions(const TArray<UObject*>& InObjects, FToolMenuSection& Section) override;
|
||||
virtual bool HasActions(const TArray<UObject*>& InObjects) const override;
|
||||
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects,
|
||||
TSharedPtr<IToolkitHost> EditWithinLevelEditor) override;
|
||||
|
||||
protected:
|
||||
void WriteBackTextIDs(TArray<TWeakObjectPtr<USUDSScript>> Scripts);
|
||||
|
||||
void GenerateVOAssets(TArray<TWeakObjectPtr<USUDSScript>> Scripts);
|
||||
|
||||
};
|
||||
37
Plugins/SUDS/Source/SUDSEditor/Public/SUDSScriptFactory.h
Normal file
37
Plugins/SUDS/Source/SUDSEditor/Public/SUDSScriptFactory.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SUDSScriptImporter.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "SUDSScriptFactory.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS()
|
||||
class SUDSEDITOR_API USUDSScriptFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
USUDSScriptFactory();
|
||||
protected:
|
||||
virtual UObject* FactoryCreateText(UClass* InClass,
|
||||
UObject* InParent,
|
||||
FName InName,
|
||||
EObjectFlags Flags,
|
||||
UObject* Context,
|
||||
const TCHAR* Type,
|
||||
const TCHAR*& Buffer,
|
||||
const TCHAR* BufferEnd,
|
||||
FFeedbackContext* Warn) override;
|
||||
|
||||
bool ShouldGenerateVoiceAssets(const FString& PackagePath) const;
|
||||
FSUDSScriptImporter Importer;
|
||||
|
||||
void ForceDeleteAssets(const TArray<FAssetData>& Assets);
|
||||
UStringTable* CreateStringTable(UObject* ScriptParent, FName InName, USUDSScript* Script, EObjectFlags Flags, FSUDSMessageLogger* Logger);
|
||||
};
|
||||
520
Plugins/SUDS/Source/SUDSEditor/Public/SUDSScriptImporter.h
Normal file
520
Plugins/SUDS/Source/SUDSEditor/Public/SUDSScriptImporter.h
Normal file
@@ -0,0 +1,520 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SUDSExpression.h"
|
||||
|
||||
struct FSUDSMessageLogger;
|
||||
class USUDSScript;
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogSUDSImporter, Verbose, All);
|
||||
|
||||
struct SUDSEDITOR_API FSUDSParsedEdge
|
||||
{
|
||||
public:
|
||||
/// Text associated with this edge (if a player choice option)
|
||||
FString Text;
|
||||
/// Identifier of the text, for the string table
|
||||
FString TextID;
|
||||
/// Metadata associated with text, for translator comments
|
||||
TMap<FName, FString> TextMetadata;
|
||||
/// The line this edge was created on
|
||||
int SourceLineNo;
|
||||
/// Condition expression that applies to this edge (for select nodes)
|
||||
FSUDSExpression ConditionExpression;
|
||||
/// Custom metadata for a node which the user can set. Can be used to annotate choice lines
|
||||
/// Could be used to disable a choice if you don't have the stats to take it, or anything else
|
||||
/// where you need a bit of extra information about a line that doesn't suit setting a dialogue variable for.
|
||||
TMap<FName, FSUDSExpression> UserMetadata;
|
||||
|
||||
int SourceNodeIdx = -1;
|
||||
int TargetNodeIdx = -1;
|
||||
|
||||
FSUDSParsedEdge(int LineNo) : SourceLineNo(LineNo){}
|
||||
|
||||
FSUDSParsedEdge(int FromNodeIdx, int ToNodeIdx, int LineNo, const FString& InText, const FString& InTextID, const TMap<FName, FString>& Metadata, const TMap<FName, FSUDSExpression>& UserMeta)
|
||||
: Text(InText),
|
||||
TextID(InTextID),
|
||||
TextMetadata(Metadata),
|
||||
SourceLineNo(LineNo),
|
||||
UserMetadata(UserMeta),
|
||||
SourceNodeIdx(FromNodeIdx),
|
||||
TargetNodeIdx(ToNodeIdx)
|
||||
{
|
||||
}
|
||||
|
||||
FSUDSParsedEdge(int FromNodeIdx, int ToNodeIdx, int LineNo)
|
||||
: SourceLineNo(LineNo),
|
||||
SourceNodeIdx(FromNodeIdx),
|
||||
TargetNodeIdx(ToNodeIdx)
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
*this = FSUDSParsedEdge(-1);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
enum class ESUDSParsedNodeType : uint8
|
||||
{
|
||||
/// Text node, displaying a line of dialogue
|
||||
Text,
|
||||
/// Choice node, displaying a series of user choices which navigate to other nodes
|
||||
Choice,
|
||||
/// Select node, automatically selecting one which navigates to another node based on state (also Random)
|
||||
Select,
|
||||
/// Goto node, redirects execution somewhere else
|
||||
/// Gotos are only nodes in the parsing structure, because they need to be discoverable as a fallthrough destination
|
||||
/// When converting to runtime use they just become edges
|
||||
Goto,
|
||||
/// Gosub node, acts like goto except stores return location
|
||||
Gosub,
|
||||
/// Return node, returns from a gosub
|
||||
Return,
|
||||
/// Set variable node
|
||||
SetVariable,
|
||||
/// Event node
|
||||
Event
|
||||
};
|
||||
|
||||
/// Intermediate parsed node from script text
|
||||
/// This will be converted into a final asset later
|
||||
struct SUDSEDITOR_API FSUDSParsedNode
|
||||
{
|
||||
public:
|
||||
ESUDSParsedNodeType NodeType;
|
||||
int OriginalIndent;
|
||||
/// Identifier is speaker ID, goto label, variable name etc
|
||||
FString Identifier;
|
||||
/// Text in native language
|
||||
FString Text;
|
||||
/// Identifier of the text, for the string table
|
||||
FString TextID;
|
||||
/// Metadata associated with text, for translator comments
|
||||
TMap<FName, FString> TextMetadata;
|
||||
/// Expression, for nodes that use it (e.g. set)
|
||||
FSUDSExpression Expression;
|
||||
/// Event arguments, for event nodes
|
||||
TArray<FSUDSExpression> EventArgs;
|
||||
/// Labels which lead to this node
|
||||
TArray<FString> Labels;
|
||||
/// Edges leading to other nodes
|
||||
TArray<FSUDSParsedEdge> Edges;
|
||||
/// The line this node was created on
|
||||
int SourceLineNo;
|
||||
/// Whether this is a valid fall-through target
|
||||
bool AllowFallthrough = true;
|
||||
|
||||
/// Custom metadata for a node which the user can set. Can be used to annotate speaker lines
|
||||
TMap<FName, FSUDSExpression> UserMetadata;
|
||||
|
||||
// Path hierarchy of choice nodes leading to this node, of the form "/C002/C006" etc, not including this node index
|
||||
// This helps us identify valid fallthroughs
|
||||
FString ChoicePath;
|
||||
// Path hierarchy of select nodes leading to this node, of the form "/S002/S006" etc, not including this node index
|
||||
// This helps us identify valid fallthroughs
|
||||
FString ConditionalPath;
|
||||
|
||||
/// Although multiple edges can lead here, this index is for the auto-connected parent (may be nothing)
|
||||
int ParentNodeIdx = -1;
|
||||
|
||||
FSUDSParsedNode(ESUDSParsedNodeType InNodeType, int Indent, int LineNo) : NodeType(InNodeType),
|
||||
OriginalIndent(Indent),
|
||||
SourceLineNo(LineNo)
|
||||
{
|
||||
}
|
||||
|
||||
FSUDSParsedNode(const FString& Label,
|
||||
const FString& GosubID,
|
||||
int Indent,
|
||||
int LineNo) : NodeType(ESUDSParsedNodeType::Gosub),
|
||||
OriginalIndent(Indent),
|
||||
Identifier(Label),
|
||||
TextID(GosubID),
|
||||
SourceLineNo(LineNo)
|
||||
{
|
||||
}
|
||||
|
||||
FSUDSParsedNode(const FString& InSpeaker, const FString& InText, const FString& InTextID, const TMap<FName, FString>& Metadata, const TMap<FName, FSUDSExpression>& UserMeta, int Indent, int LineNo)
|
||||
: NodeType(ESUDSParsedNodeType::Text),
|
||||
OriginalIndent(Indent),
|
||||
Identifier(InSpeaker),
|
||||
Text(InText),
|
||||
TextID(InTextID),
|
||||
TextMetadata(Metadata),
|
||||
SourceLineNo(LineNo),
|
||||
UserMetadata(UserMeta)
|
||||
{
|
||||
}
|
||||
|
||||
FSUDSParsedNode(const FString& GotoLabel, int Indent, int LineNo)
|
||||
: NodeType(ESUDSParsedNodeType::Goto), OriginalIndent(Indent), Identifier(GotoLabel), SourceLineNo(LineNo)
|
||||
{
|
||||
}
|
||||
|
||||
FSUDSParsedNode(const FString& VariableName, const FSUDSExpression& InExpr, int Indent, int LineNo)
|
||||
: NodeType(ESUDSParsedNodeType::SetVariable),
|
||||
OriginalIndent(Indent),
|
||||
Identifier(VariableName),
|
||||
Expression(InExpr),
|
||||
SourceLineNo(LineNo)
|
||||
{
|
||||
}
|
||||
|
||||
FSUDSParsedNode(const FString& VariableName,
|
||||
const FSUDSExpression& InExpr,
|
||||
const FString& InTextID,
|
||||
int Indent,
|
||||
int LineNo)
|
||||
: NodeType(ESUDSParsedNodeType::SetVariable),
|
||||
OriginalIndent(Indent),
|
||||
Identifier(VariableName),
|
||||
TextID(InTextID),
|
||||
Expression(InExpr),
|
||||
SourceLineNo(LineNo)
|
||||
{
|
||||
}
|
||||
};
|
||||
class SUDSEDITOR_API FSUDSScriptImporter
|
||||
{
|
||||
public:
|
||||
bool ImportFromBuffer(const TCHAR* Buffer, int32 Len, const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
void PopulateAsset(USUDSScript* Asset, UStringTable* StringTable);
|
||||
static FMD5Hash CalculateHash(const TCHAR* Buffer, int32 Len);
|
||||
static const FString EndGotoLabel;
|
||||
protected:
|
||||
static const FString TreePathSeparator;
|
||||
|
||||
enum class EConditionalStage : uint8
|
||||
{
|
||||
IfStage,
|
||||
ElseIfStage,
|
||||
ElseStage,
|
||||
RandomStage,
|
||||
RandomOptionStage
|
||||
};
|
||||
enum class EConditionalParent : uint8
|
||||
{
|
||||
Select,
|
||||
ElseIfStage,
|
||||
ElseStage
|
||||
};
|
||||
|
||||
// Struct for tracking if/elseif blocks
|
||||
struct ConditionalContext
|
||||
{
|
||||
/// Index of parent select node where else will be added
|
||||
int SelectNodeIdx = -1;
|
||||
/// Previous block index (parent for nesting)
|
||||
int PreviousBlockIdx = -1;
|
||||
/// Track whether we're in if/elseif/else
|
||||
EConditionalStage Stage;
|
||||
/// String identifying the current condition; for elseif or else contains original "if" context
|
||||
FString ConditionPathElement;
|
||||
|
||||
ConditionalContext(int InSelectNodeIdx, int InPrevBlockIdx, EConditionalStage InStage, const FString& InCondStr) :
|
||||
SelectNodeIdx(InSelectNodeIdx),
|
||||
PreviousBlockIdx(InPrevBlockIdx),
|
||||
Stage(InStage),
|
||||
ConditionPathElement(InCondStr)
|
||||
{
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/// Struct for tracking indents
|
||||
struct IndentContext
|
||||
{
|
||||
public:
|
||||
// The index of the Node which is the parent of this context
|
||||
// This potentially changes every time a sequential text node is encountered in the same context, so it's
|
||||
// always pointing to the last node encountered at this level, for connection
|
||||
int LastNodeIdx = -1;
|
||||
|
||||
/// The outermost indent level where this context lives
|
||||
/// You can indent things that don't create a new context, e.g.
|
||||
/// 1. Indent a text line under another text line: this is the same as no indent, just a continuation
|
||||
/// 2. Indent choices or conditions under a text line
|
||||
/// This is just good for readability, but does not create a new context, it's just a linear sequence
|
||||
/// Therefore the ThresholdIndent tracks the outermost indent relating to the current linear sequence, to know
|
||||
/// when you do in fact need to pop the current context off the stack.
|
||||
int ThresholdIndent = 0;
|
||||
|
||||
int LastTextNodeIdx = -1;
|
||||
|
||||
/// The path entry for this indent, to be combined with all previous levels to provide full path context
|
||||
FString PathEntry;
|
||||
|
||||
IndentContext(int NodeIdx, int Indent, const FString& Path) : LastNodeIdx(NodeIdx), ThresholdIndent(Indent), LastTextNodeIdx(-1), PathEntry(Path) {}
|
||||
|
||||
};
|
||||
|
||||
/// A tree of nodes. Contained to separate header nodes from body nodes
|
||||
struct ParsedTree
|
||||
{
|
||||
public:
|
||||
/// The indent context stack representing where we are in the indentation tree while parsing
|
||||
/// There must always be 1 level (root)
|
||||
TArray<IndentContext> IndentLevelStack;
|
||||
/// When encountering conditions and choice lines, we are building up details for an edge to another node, but
|
||||
/// we currently don't know the target node. We keep these pending details here
|
||||
int EdgeInProgressNodeIdx = -1;
|
||||
int EdgeInProgressEdgeIdx = -1;
|
||||
/// List of all nodes, appended to as parsing progresses
|
||||
/// Ordering is important, these nodes must be in the order encountered in the file
|
||||
TArray<FSUDSParsedNode> Nodes;
|
||||
/// Record of goto labels to node index, built up during parsing (forward refs are OK so not complete until end of parsing)
|
||||
TMap<FString, int> GotoLabelList;
|
||||
/// Goto labels which have been encountered but we haven't found a destination yet
|
||||
TArray<FString> PendingGotoLabels;
|
||||
/// Goto labels that lead directly to another goto and thus are just aliases
|
||||
TMap<FString, FString> AliasedGotoLabels;
|
||||
|
||||
/// Conditional blocks
|
||||
/// "if" creates a new context, uses current as parent
|
||||
/// "elseif" and "else" also create new contexts, but copies parent from current (sibling)
|
||||
/// "endif" ends the context
|
||||
/// You cannot have conditionals that were started in an indent context ending outside it
|
||||
TArray<ConditionalContext> ConditionalBlocks;
|
||||
/// Index of the current conditional block, if any
|
||||
int CurrentConditionalBlockIdx = -1;
|
||||
|
||||
void Reset()
|
||||
{
|
||||
IndentLevelStack.Reset();
|
||||
EdgeInProgressNodeIdx = EdgeInProgressEdgeIdx -1;
|
||||
Nodes.Reset();
|
||||
GotoLabelList.Reset();
|
||||
PendingGotoLabels.Reset();
|
||||
AliasedGotoLabels.Reset();
|
||||
ConditionalBlocks.Reset();
|
||||
CurrentConditionalBlockIdx = -1;
|
||||
}
|
||||
};
|
||||
|
||||
ParsedTree HeaderTree;
|
||||
ParsedTree BodyTree;
|
||||
|
||||
struct ParsedMetadata
|
||||
{
|
||||
public:
|
||||
FName Key;
|
||||
FString Value;
|
||||
int IndentLevel;
|
||||
|
||||
ParsedMetadata(const FName& InKey, const FString& InValue, int InIndentLevel)
|
||||
: Key(InKey),
|
||||
Value(InValue),
|
||||
IndentLevel(InIndentLevel)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// Text Metadata applied to speaker lines / choices until reset
|
||||
/// For each key there's a stack of metadata, with most indented at the top
|
||||
TMap<FName, TArray<ParsedMetadata>> PersistentMetadata;
|
||||
/// Text Metadata applied just to the next speaker line or choice
|
||||
TMap<FName, ParsedMetadata> TransientMetadata;
|
||||
/// User metadata for the next speaker line or choice
|
||||
TMap<FName, FSUDSExpression> UserMetadata;
|
||||
|
||||
/// List of speakers, detected during parsing of lines of text
|
||||
TArray<FString> ReferencedSpeakers;
|
||||
|
||||
const int TabIndentValue = 4;
|
||||
bool bHeaderDone = false;
|
||||
bool bTooLateForHeader = false;
|
||||
bool bHeaderInProgress = false;
|
||||
TOptional<bool> bOverrideGenerateSpeakerLineForChoice;
|
||||
TOptional<FString> OverrideChoiceSpeakerID;
|
||||
bool bTextInProgress = false;
|
||||
int ChoiceUniqueId = 0;
|
||||
/// For generating text IDs
|
||||
int TextIDHighestNumber = 0;
|
||||
/// For generating gosub IDs
|
||||
int GosubIDHighestNumber = 0;
|
||||
/// Parse a single line
|
||||
bool ParseLine(const FStringView& Line, int LineNo, const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
bool ParseHeaderLine(const FStringView& Line, int IndentLevel, int LineNo, const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
bool ParseBodyLine(const FStringView& Line, int IndentLevel, int LineNo, const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
bool ParseCommentMetadataLine(const FStringView& Line, int IndentLevel, int LineNo, const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
bool IsLastNodeOfType(const ParsedTree& Tree, ESUDSParsedNodeType Type);
|
||||
bool ParseChoiceLine(const FStringView& Line, ParsedTree& Tree, int IndentLevel, int LineNo, const FString& NameForErrors, FSUDSMessageLogger*
|
||||
Logger,
|
||||
bool bSilent);
|
||||
FSUDSParsedEdge* GetEdgeInProgress(ParsedTree& Tree);
|
||||
void EnsureChoiceNodeExistsAboveSelect(ParsedTree& Tree, int IndentLevel, int LineNo);
|
||||
static bool IsConditionalLine(const FStringView& Line);
|
||||
bool ParseConditionalLine(const FStringView& Line, ParsedTree& Tree, int IndentLevel, int LineNo, const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
bool ParseIfLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
const FString& ConditionStr,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseElseIfLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
const FString& ConditionStr,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
|
||||
bool ParseElseLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseEndIfLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger*
|
||||
Logger,
|
||||
bool bSilent);
|
||||
static bool IsRandomLine(const FStringView& Line);
|
||||
bool ParseRandomLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseBeginRandomLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseRandomOptionLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseEndRandomLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseGotoLabelLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseGotoLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseGosubLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseReturnLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseSetLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseEventLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseTextLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool ParseImportSettingLine(const FStringView& Line,
|
||||
ParsedTree& Tree,
|
||||
int IndentLevel,
|
||||
int LineNo,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
TMap<FName, FString> GetTextMetadataForNextEntry(int CurrentLineIndent);
|
||||
TMap<FName, FSUDSExpression> ConsumeUserMetadata();
|
||||
bool IsCommentLine(const FStringView& TrimmedLine);
|
||||
FStringView TrimLine(const FStringView& Line, int& OutIndentLevel) const;
|
||||
int FindChoiceAfterTextNode(const FSUDSScriptImporter::ParsedTree& Tree, int TextNodeIdx, const FString& ConditionalPath);
|
||||
int RecurseChoiceNodeFindDeepest(const ParsedTree& Tree, int FromChoiceIdx, const FString& ConditionalPath);
|
||||
int RecurseSelectNodeFindDeepestChoice(const ParsedTree& Tree, int FromSelectNode, const FString& ConditionalPath);
|
||||
int FindEdge(const FSUDSScriptImporter::ParsedTree& Tree, int ParentNodeIdx, int TargetNodeIndex);
|
||||
FString MakeIfConditionPathElement(int SelectNodeIdx, const FString& ConditionStr);
|
||||
FString MakeElseIfConditionPathElement(int SelectNodeIdx, const FString& ConditionStr);
|
||||
FString MakeElseConditionPathElement(int SelectNodeIdx);
|
||||
int FindLastChoiceNode(const ParsedTree& Tree, int IndentLevel);
|
||||
int FindLastChoiceNode(const ParsedTree& Tree, int IndentLevel, int FromIndex, const FString& ConditionPath);
|
||||
void PopIndent(ParsedTree& Tree);
|
||||
void PushIndent(ParsedTree& Tree, int NodeIdx, int Indent, const FString& Path);
|
||||
FString GetCurrentTreePath(const FSUDSScriptImporter::ParsedTree& Tree);
|
||||
FString GetCurrentTreeConditionalPath(const FSUDSScriptImporter::ParsedTree& Tree);
|
||||
FString GetTreeConditionalPath(const ParsedTree& Tree, int NodeIndex);
|
||||
void SetFallthroughForNewNode(FSUDSScriptImporter::ParsedTree& Tree, FSUDSParsedNode& NewNode);
|
||||
int AppendNode(ParsedTree& Tree, const FSUDSParsedNode& InNode);
|
||||
bool SelectNodeIsMissingElsePath(const FSUDSScriptImporter::ParsedTree& Tree, const FSUDSParsedNode& Node);
|
||||
bool PostImportSanityCheck(const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
bool ChoiceNodeCheckPaths(const FSUDSParsedNode& ChoiceNode,
|
||||
const FString& NameForErrors,
|
||||
FSUDSMessageLogger* Logger,
|
||||
bool bSilent);
|
||||
bool RecurseChoiceNodeCheckPaths(const FSUDSParsedNode& OrigChoiceNode, const FSUDSParsedNode& CurrChoiceNode, const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
bool RecurseChoiceNodeCheckPaths(const FSUDSParsedNode& ChoiceNode, const FSUDSParsedEdge& Edge, const FString& NameForErrors, FSUDSMessageLogger*
|
||||
Logger,
|
||||
bool bSilent);
|
||||
void ConnectRemainingNodes(ParsedTree& Tree, const FString& NameForErrors, FSUDSMessageLogger* Logger, bool bSilent);
|
||||
void GenerateTextIDs(ParsedTree& BodyTree);
|
||||
int FindFallthroughNodeIndex(ParsedTree& Tree, int StartNodeIndex, const FString& FromChoicePath, const FString& FromConditionalPath);
|
||||
bool RetrieveAndRemoveTextID(FStringView& InOutLine, FString& OutTextID);
|
||||
bool RetrieveAndRemoveGosubID(FStringView& InOutLine, FString& OutTextID);
|
||||
FString GenerateTextID();
|
||||
const FSUDSParsedNode* GetNode(const ParsedTree& Tree, int Index = 0);
|
||||
int GetGotoTargetNodeIndex(const ParsedTree& Tree, const FString& InLabel);
|
||||
void PopulateAssetFromTree(USUDSScript* Asset,
|
||||
const ParsedTree& Tree,
|
||||
TArray<TObjectPtr<class USUDSScriptNode>>* pOutNodes,
|
||||
TMap<FName, int>* pOutLabels,
|
||||
UStringTable* StringTable);
|
||||
|
||||
public:
|
||||
const FSUDSParsedNode* GetNode(int Index = 0);
|
||||
const FSUDSParsedNode* GetHeaderNode(int Index = 0);
|
||||
/// Resolve a goto label to a target index (after import), or -1 if not resolvable
|
||||
int GetGotoTargetNodeIndex(const FString& Label);
|
||||
static bool RetrieveTextIDFromLine(FStringView& InOutLine, FString& OutTextID, int& OutNumber);
|
||||
static bool RetrieveGosubIDFromLine(FStringView& InOutLine, FString& OutID, int& OutNumber);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright Steve Streeting 2022
|
||||
// Released under the MIT license https://opensource.org/license/MIT/
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "EditorReimportHandler.h"
|
||||
#include "SUDSScriptFactory.h"
|
||||
|
||||
#include "SUDSScriptReimportFactory.generated.h"
|
||||
|
||||
// Reimports a USUDSScriptFactory asset
|
||||
// Necessary to get the import pop-up
|
||||
UCLASS()
|
||||
class USUDSScriptReimportFactory : public USUDSScriptFactory, public FReimportHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
USUDSScriptReimportFactory();
|
||||
|
||||
//~ Begin FReimportHandler Interface
|
||||
virtual bool CanReimport(UObject* Obj, TArray<FString>& OutFilenames) override;
|
||||
virtual void SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths) override;
|
||||
virtual EReimportResult::Type Reimport(UObject* Obj) override;
|
||||
virtual int32 GetPriority() const override;
|
||||
//~ End FReimportHandler Interface
|
||||
};
|
||||
Reference in New Issue
Block a user