// Copyright Epic Games, Inc. All Rights Reserved. #include "SpriterDataModel.h" #include "SpriterImporterLog.h" #include "Dom/JsonObject.h" #include "Serialization/JsonSerializer.h" #include "PaperJSONHelpers.h" #define LOCTEXT_NAMESPACE "SpriterImporter" ////////////////////////////////////////////////////////////////////////// // FSpriterAuditTools #define UE_AUDIT_SPRITER_IMPORT 1 #if UE_AUDIT_SPRITER_IMPORT // This class will help to catch unknown/newly introduced properties in the future class FSpriterAuditTools { public: TSet KnownSpatialInfoKeys; TSet KnownFileKeys; TSet KnownFolderKeys; TSet KnownMapInstructionKeys; TSet KnownTagLineKeyTagKeys; TSet KnownTagLineKeyKeys; TSet KnownTagLineKeys; TSet KnownValLineKeyKeys; TSet KnownValLineKeys; TSet KnownMetaKeys; TSet KnownRefKeys; TSet KnownObjectRefKeys; TSet KnownMainlineKeyKeys; TSet KnownBasicTimelineKeyKeys; TSet KnownTimelineBoneKeyKeys; TSet KnownTimelineObjectKeyKeys; TSet KnownTimelineKeys; TSet KnownEventLineKeyKeys; TSet KnownEventLineKeys; TSet KnownAnimationKeys; TSet KnownCharacterMapKeys; TSet KnownVariableDefinitionKeys; TSet KnownObjInfoKeys; TSet KnownEntityKeys; TSet KnownSCONTagListKeys; TSet KnownSCONKeys; static FSpriterAuditTools& Get() { static FSpriterAuditTools StaticInstance; return StaticInstance; } static void AuditKeys(const TSet& TestSet, const TSharedPtr Tree, const FString& ContextString) { for (const auto& KVP : Tree->Values) { if (!TestSet.Contains(KVP.Key)) { const bool bSilent = false; SPRITER_IMPORT_WARNING(TEXT("Unexpected field '%s' in context '%s'. Parsing will continue but not all information is being imported."), *KVP.Key, *ContextString); static int32 A = 0; ++A; } } } private: FSpriterAuditTools() { KnownSpatialInfoKeys.Add(TEXT("x")); KnownSpatialInfoKeys.Add(TEXT("y")); KnownSpatialInfoKeys.Add(TEXT("angle")); KnownSpatialInfoKeys.Add(TEXT("scale_x")); KnownSpatialInfoKeys.Add(TEXT("scale_y")); KnownSpatialInfoKeys.Add(TEXT("r")); KnownSpatialInfoKeys.Add(TEXT("g")); KnownSpatialInfoKeys.Add(TEXT("b")); KnownSpatialInfoKeys.Add(TEXT("a")); KnownFileKeys.Add(TEXT("name")); KnownFileKeys.Add(TEXT("pivot_x")); KnownFileKeys.Add(TEXT("pivot_y")); KnownFileKeys.Add(TEXT("width")); KnownFileKeys.Add(TEXT("height")); KnownFileKeys.Add(TEXT("type")); KnownFileKeys.Add(TEXT("id")); // Known but being ignored KnownFolderKeys.Add(TEXT("name")); KnownFolderKeys.Add(TEXT("file")); KnownFolderKeys.Add(TEXT("id")); // Known but being ignored KnownMapInstructionKeys.Add(TEXT("file")); KnownMapInstructionKeys.Add(TEXT("folder")); KnownMapInstructionKeys.Add(TEXT("target_file")); KnownMapInstructionKeys.Add(TEXT("target_folder")); KnownTagLineKeyTagKeys.Add(TEXT("t")); KnownTagLineKeyTagKeys.Add(TEXT("id")); // Known but being ignored KnownTagLineKeyKeys.Add(TEXT("time")); KnownTagLineKeyKeys.Add(TEXT("tag")); KnownTagLineKeyKeys.Add(TEXT("id")); // Known but being ignored KnownTagLineKeys.Add(TEXT("key")); KnownValLineKeyKeys.Add(TEXT("time")); KnownValLineKeyKeys.Add(TEXT("val")); KnownValLineKeyKeys.Add(TEXT("id")); // Known but being ignored KnownValLineKeys.Add(TEXT("def")); KnownValLineKeys.Add(TEXT("key")); KnownValLineKeys.Add(TEXT("id")); // Known but being ignored KnownMetaKeys.Add(TEXT("tagline")); KnownMetaKeys.Add(TEXT("valline")); KnownRefKeys.Add(TEXT("key")); KnownRefKeys.Add(TEXT("parent")); KnownRefKeys.Add(TEXT("timeline")); KnownRefKeys.Add(TEXT("id")); // Known but being ignored KnownObjectRefKeys.Append(KnownRefKeys); KnownObjectRefKeys.Add(TEXT("z_index")); KnownMainlineKeyKeys.Add(TEXT("time")); KnownMainlineKeyKeys.Add(TEXT("bone_ref")); KnownMainlineKeyKeys.Add(TEXT("object_ref")); KnownMainlineKeyKeys.Add(TEXT("curve_type")); KnownMainlineKeyKeys.Add(TEXT("id")); // Known but being ignored KnownBasicTimelineKeyKeys.Add(TEXT("time")); KnownBasicTimelineKeyKeys.Add(TEXT("curve_type")); KnownBasicTimelineKeyKeys.Add(TEXT("c1")); KnownBasicTimelineKeyKeys.Add(TEXT("c2")); KnownBasicTimelineKeyKeys.Add(TEXT("spin")); KnownBasicTimelineKeyKeys.Add(TEXT("id")); // Known but being ignored KnownBasicTimelineKeyKeys.Add(TEXT("object")); KnownBasicTimelineKeyKeys.Add(TEXT("bone")); KnownTimelineBoneKeyKeys.Append(KnownSpatialInfoKeys); KnownTimelineObjectKeyKeys.Append(KnownSpatialInfoKeys); KnownTimelineObjectKeyKeys.Add(TEXT("file")); KnownTimelineObjectKeyKeys.Add(TEXT("folder")); KnownTimelineObjectKeyKeys.Add(TEXT("pivot_x")); KnownTimelineObjectKeyKeys.Add(TEXT("pivot_y")); KnownTimelineKeys.Add(TEXT("name")); KnownTimelineKeys.Add(TEXT("object_type")); KnownTimelineKeys.Add(TEXT("obj")); KnownTimelineKeys.Add(TEXT("key")); KnownTimelineKeys.Add(TEXT("meta")); KnownTimelineKeys.Add(TEXT("id")); // Known but being ignored KnownEventLineKeyKeys.Add(TEXT("time")); KnownEventLineKeyKeys.Add(TEXT("id")); // Known but being ignored KnownEventLineKeys.Add(TEXT("name")); KnownEventLineKeys.Add(TEXT("obj")); KnownEventLineKeys.Add(TEXT("key")); KnownEventLineKeys.Add(TEXT("id")); // Known but being ignored KnownAnimationKeys.Add(TEXT("name")); KnownAnimationKeys.Add(TEXT("length")); KnownAnimationKeys.Add(TEXT("interval")); KnownAnimationKeys.Add(TEXT("mainline")); KnownAnimationKeys.Add(TEXT("looping")); KnownAnimationKeys.Add(TEXT("timeline")); //KnownAnimationKeys.Add(TEXT("soundline")); //@TODO: Not supported yet KnownAnimationKeys.Add(TEXT("eventline")); KnownAnimationKeys.Add(TEXT("gline")); //@TODO: Not supported yet KnownAnimationKeys.Add(TEXT("meta")); KnownAnimationKeys.Add(TEXT("id")); // Known but being ignored KnownCharacterMapKeys.Add(TEXT("name")); KnownCharacterMapKeys.Add(TEXT("map")); KnownCharacterMapKeys.Add(TEXT("id")); // Known but being ignored KnownVariableDefinitionKeys.Add(TEXT("name")); KnownVariableDefinitionKeys.Add(TEXT("default")); KnownVariableDefinitionKeys.Add(TEXT("type")); KnownVariableDefinitionKeys.Add(TEXT("id")); // Known but being ignored KnownObjInfoKeys.Add(TEXT("name")); KnownObjInfoKeys.Add(TEXT("type")); KnownObjInfoKeys.Add(TEXT("w")); KnownObjInfoKeys.Add(TEXT("h")); KnownObjInfoKeys.Add(TEXT("pivot_x")); KnownObjInfoKeys.Add(TEXT("pivot_y")); KnownObjInfoKeys.Add(TEXT("id")); // Known but being ignored KnownObjInfoKeys.Add(TEXT("frames")); //@TODO: Not supported yet KnownObjInfoKeys.Add(TEXT("var_defs")); KnownEntityKeys.Add(TEXT("name")); KnownEntityKeys.Add(TEXT("obj_info")); KnownEntityKeys.Add(TEXT("animation")); KnownEntityKeys.Add(TEXT("character_map")); KnownEntityKeys.Add(TEXT("id")); // Known but being ignored KnownEntityKeys.Add(TEXT("var_defs")); KnownSCONTagListKeys.Add(TEXT("name")); KnownSCONTagListKeys.Add(TEXT("id")); // Known but being ignored KnownSCONKeys.Add(TEXT("scon_version")); KnownSCONKeys.Add(TEXT("generator")); KnownSCONKeys.Add(TEXT("generator_version")); KnownSCONKeys.Add(TEXT("entity")); KnownSCONKeys.Add(TEXT("folder")); KnownSCONKeys.Add(TEXT("tag_list")); } }; #define UE_DO_SPRITER_AUDIT(KeySet, Object, Message) FSpriterAuditTools::AuditKeys(FSpriterAuditTools::Get().KeySet, Object, Message); #else #define UE_DO_SPRITER_AUDIT(KeySet, Object, Message) #endif ////////////////////////////////////////////////////////////////////////// // FSpriterEnumHelper ESpriterObjectType FSpriterEnumHelper::StringToObjectType(const FString& InString) { if (InString == TEXT("sprite")) { return ESpriterObjectType::Sprite; } else if (InString == TEXT("bone")) { return ESpriterObjectType::Bone; } else if (InString == TEXT("box")) { return ESpriterObjectType::Box; } else if (InString == TEXT("point")) { return ESpriterObjectType::Point; } else if (InString == TEXT("sound")) { return ESpriterObjectType::Sound; } else if (InString == TEXT("entity")) { return ESpriterObjectType::Entity; } else if (InString == TEXT("variable")) { return ESpriterObjectType::Variable; } else if (InString == TEXT("event")) { return ESpriterObjectType::Event; } else { return ESpriterObjectType::INVALID; } } ESpriterCurveType FSpriterEnumHelper::StringToCurveType(const FString& InString) { if (InString == TEXT("linear")) { return ESpriterCurveType::Linear; } else if (InString == TEXT("instant")) { return ESpriterCurveType::Instant; } else if (InString == TEXT("quadratic")) { return ESpriterCurveType::Quadratic; } else if (InString == TEXT("cubic")) { return ESpriterCurveType::Cubic; } else { return ESpriterCurveType::INVALID; } } ESpriterVariableType FSpriterEnumHelper::StringToVariableType(const FString& InString) { if (InString == TEXT("float")) { return ESpriterVariableType::Float; } else if (InString == TEXT("int")) { return ESpriterVariableType::Integer; } else if (InString == TEXT("string")) { return ESpriterVariableType::String; } else { return ESpriterVariableType::INVALID; } } ESpriterFileType FSpriterEnumHelper::StringToFileType(const FString& InString) { if (InString == TEXT("sprite")) { return ESpriterFileType::Sprite; } else if (InString == TEXT("sound")) { return ESpriterFileType::Sound; } else { return ESpriterFileType::INVALID; } } ////////////////////////////////////////////////////////////////////////// // FSpriterSpatialInfo FSpriterSpatialInfo::FSpriterSpatialInfo() : X(0.0f) , Y(0.0f) , AngleInDegrees(0.0f) , ScaleX(1.0f) , ScaleY(1.0f) , Color(FLinearColor::White) { } bool FSpriterSpatialInfo::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { Tree->TryGetNumberField(TEXT("x"), /*out*/ X); Tree->TryGetNumberField(TEXT("y"), /*out*/ Y); Tree->TryGetNumberField(TEXT("angle"), /*out*/ AngleInDegrees); Tree->TryGetNumberField(TEXT("scale_x"), /*out*/ ScaleX); Tree->TryGetNumberField(TEXT("scale_y"), /*out*/ ScaleY); double DR = 1.0; double DG = 1.0; double DB = 1.0; double DA = 1.0; Tree->TryGetNumberField(TEXT("r"), /*out*/ DR); Tree->TryGetNumberField(TEXT("g"), /*out*/ DG); Tree->TryGetNumberField(TEXT("b"), /*out*/ DB); Tree->TryGetNumberField(TEXT("a"), /*out*/ DA); Color = FLinearColor(DR, DG, DB, DA); return true; } FTransform FSpriterSpatialInfo::ConvertToTransform() const { FTransform Result; Result.SetTranslation((X * PaperAxisX) + (Y * PaperAxisY)); Result.SetRotation(FRotator(AngleInDegrees, 0.0f, 0.0f).Quaternion()); Result.SetScale3D((ScaleX * PaperAxisX) + (ScaleY * PaperAxisY) + (PaperAxisZ * 1.0f)); return Result; } ////////////////////////////////////////////////////////////////////////// // FSpriterFile FSpriterFile::FSpriterFile() : PivotX(0.0f) , PivotY(1.0f) , Width(0) , Height(0) , FileType(ESpriterFileType::INVALID) { } bool FSpriterFile::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Parse the name if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the file object of '%s'."), *NameForErrors); Name = TEXT("(missing file name)"); bSuccessfullyParsed = false; } // Optionally parse the type property FString FileTypeAsString; if (Tree->TryGetStringField(TEXT("type"), /*out*/ FileTypeAsString)) { FileType = FSpriterEnumHelper::StringToFileType(FileTypeAsString); if (FileType == ESpriterFileType::INVALID) { SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'type' in file '%s' in '%s'."), *FileTypeAsString, *Name, *NameForErrors); bSuccessfullyParsed = false; } } else { // Defaults to sprite FileType = ESpriterFileType::Sprite; } Tree->TryGetNumberField(TEXT("pivot_x"), /*out*/ PivotX); Tree->TryGetNumberField(TEXT("pivot_y"), /*out*/ PivotY); Tree->TryGetNumberField(TEXT("width"), /*out*/ Width); Tree->TryGetNumberField(TEXT("height"), /*out*/ Height); UE_DO_SPRITER_AUDIT(KnownFileKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterFolder FSpriterFolder::FSpriterFolder() { } bool FSpriterFolder::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Try parsing the folder name if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the folder object of '%s'."), *NameForErrors); Name = TEXT("(missing folder name)"); bSuccessfullyParsed = false; } // Try parsing the list of files const TArray>* FileDescriptors; if (Tree->TryGetArrayField(TEXT("file"), /*out*/ FileDescriptors)) { const FString LocalNameForErrors = FString::Printf(TEXT("%s folder '%s'"), *NameForErrors, *Name); for (TSharedPtr FileDescriptor : *FileDescriptors) { FSpriterFile& File = *new (Files) FSpriterFile(); const bool bParsedFileOK = File.ParseFromJSON(FileDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedFileOK; } } else { SPRITER_IMPORT_ERROR(TEXT("JSON exported from Spriter in file '%s' has no entities."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownFolderKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterMapInstruction FSpriterMapInstruction::FSpriterMapInstruction() : Folder(0) , File(0) , TargetFolder(INDEX_NONE) , TargetFile(INDEX_NONE) { } bool FSpriterMapInstruction::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // These two are required if (!Tree->TryGetNumberField(TEXT("file"), /*out*/ File)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'file' field in the map object of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } if (!Tree->TryGetNumberField(TEXT("folder"), /*out*/ Folder)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'folder' field in the map object of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } // These two are optional Tree->TryGetNumberField(TEXT("target_file"), /*out*/ TargetFile); Tree->TryGetNumberField(TEXT("target_folder"), /*out*/ TargetFolder); UE_DO_SPRITER_AUDIT(KnownMapInstructionKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterTagLineKey FSpriterTagLineKey::FSpriterTagLineKey() : TimeInMS(0) { } bool FSpriterTagLineKey::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Read the time of the key (in milliseconds) if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS)) { // Assume 0 when missing? TimeInMS = 0; } // Parse the tag array const TArray>* TagDescriptors; if (Tree->TryGetArrayField(TEXT("tag"), /*out*/ TagDescriptors)) { for (const TSharedPtr TagDescriptorUntyped : *TagDescriptors) { const TSharedPtr TagDescriptor = TagDescriptorUntyped->AsObject(); int32 NewTagIndex = INDEX_NONE; if (TagDescriptor->TryGetNumberField(TEXT("t"), /*out*/ NewTagIndex)) { Tags.Add(NewTagIndex); } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 't' field in the objects inside the tags array of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownTagLineKeyTagKeys, TagDescriptor, NameForErrors); } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'tag' field in the tag line key '%s'."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownTagLineKeyKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterTagLine FSpriterTagLine::FSpriterTagLine() { } bool FSpriterTagLine::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Parse the key array const TArray>* KeyDescriptors; if (Tree->TryGetArrayField(TEXT("key"), /*out*/ KeyDescriptors)) { for (const TSharedPtr KeyDescriptor : *KeyDescriptors) { FSpriterTagLineKey& Key = *new (Keys) FSpriterTagLineKey(); const bool bParsedKeySuccessfully = Key.ParseFromJSON(KeyDescriptor->AsObject(), NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeySuccessfully; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in the tag line '%s'."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownTagLineKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterValLineKey FSpriterValLineKey::FSpriterValLineKey() : TimeInMS(0) , bReadAsNumber(false) , ValueAsNumber(0.0) { } bool FSpriterValLineKey::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Read the time of the key (in milliseconds) if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS)) { // Assume 0 when missing? TimeInMS = 0; } const TSharedPtr ValField = Tree->TryGetField(TEXT("val")); if (ValField.IsValid()) { if (ValField->Type == EJson::String) { bReadAsNumber = false; ValueAsString = ValField->AsString(); } else if (ValField->Type == EJson::Number) { bReadAsNumber = true; ValueAsNumber = ValField->AsNumber(); } else { SPRITER_IMPORT_ERROR(TEXT("Expected the 'val' field to be a string or number in the val line key of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'val' field in the val line key of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownValLineKeyKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterValLine FSpriterValLine::FSpriterValLine() : DefinitionIndex(INDEX_NONE) { } bool FSpriterValLine::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Read the definition index if (!Tree->TryGetNumberField(TEXT("def"), /*out*/ DefinitionIndex)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'def' field in the val line of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } // Parse the key array const TArray>* KeyDescriptors; if (Tree->TryGetArrayField(TEXT("key"), /*out*/ KeyDescriptors)) { for (const TSharedPtr KeyDescriptor : *KeyDescriptors) { FSpriterValLineKey& Key = *new (Keys) FSpriterValLineKey(); const bool bParsedKeySuccessfully = Key.ParseFromJSON(KeyDescriptor->AsObject(), NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeySuccessfully; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in the val line '%s'."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownValLineKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterMeta FSpriterMeta::FSpriterMeta() { } bool FSpriterMeta::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Parse the tagline array (optional) const TArray>* TagLineDescriptors; if (Tree->TryGetArrayField(TEXT("tagline"), /*out*/ TagLineDescriptors)) { for (const TSharedPtr TagLineDescriptor : *TagLineDescriptors) { FSpriterTagLine& TagLine = *new (TagLines) FSpriterTagLine(); const bool bParsedTagLineSuccessfully = TagLine.ParseFromJSON(TagLineDescriptor->AsObject(), NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedTagLineSuccessfully; } } // Parse the valline array (optional) const TArray>* ValLineDescriptors; if (Tree->TryGetArrayField(TEXT("valline"), /*out*/ ValLineDescriptors)) { for (const TSharedPtr ValLineDescriptor : *ValLineDescriptors) { FSpriterValLine& ValLine = *new (ValLines) FSpriterValLine(); const bool bParsedValLineProperly = ValLine.ParseFromJSON(ValLineDescriptor->AsObject(), NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedValLineProperly; } } UE_DO_SPRITER_AUDIT(KnownMetaKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterRef FSpriterRefCommon::FSpriterRefCommon() : ParentIndex(INDEX_NONE) , TimelineIndex(INDEX_NONE) , KeyIndex(INDEX_NONE) { } bool FSpriterRefCommon::ParseCommonFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; if (!Tree->TryGetNumberField(TEXT("key"), /*out*/ KeyIndex)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in the ref object of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } if (!Tree->TryGetNumberField(TEXT("parent"), /*out*/ ParentIndex)) { ParentIndex = INDEX_NONE; } if (!Tree->TryGetNumberField(TEXT("timeline"), /*out*/ TimelineIndex)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'timeline' field in the ref object of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterRef bool FSpriterRef::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { const bool bSuccessfullyParsed = ParseCommonFromJSON(Tree, NameForErrors, bSilent); UE_DO_SPRITER_AUDIT(KnownRefKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterObjectRef FSpriterObjectRef::FSpriterObjectRef() : ZIndex(0) { } bool FSpriterObjectRef::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = ParseCommonFromJSON(Tree, NameForErrors, bSilent); if (!Tree->TryGetNumberField(TEXT("z_index"), /*out*/ ZIndex)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'z_index' field in the object ref object of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownObjectRefKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterMainlineKey FSpriterMainlineKey::FSpriterMainlineKey() : TimeInMS(INDEX_NONE) , CurveType(ESpriterCurveType::INVALID) { } bool FSpriterMainlineKey::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Read the time of the key (in milliseconds) if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS)) { // Assume 0 when missing? TimeInMS = 0; } // Parse the bone_ref array const TArray>* BoneRefDescriptors; if (Tree->TryGetArrayField(TEXT("bone_ref"), /*out*/ BoneRefDescriptors)) { for (TSharedPtr BoneRefDescriptor : *BoneRefDescriptors) { FSpriterRef& BoneRef = *new (BoneRefs) FSpriterRef(); const bool bParsedBoneRefOK = BoneRef.ParseFromJSON(BoneRefDescriptor->AsObject(), NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedBoneRefOK; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'bone_ref' field in '%s'."), *NameForErrors); bSuccessfullyParsed = false; } // Optionally parse the curve_type property FString CurveTypeAsString; if (Tree->TryGetStringField(TEXT("curve_type"), /*out*/ CurveTypeAsString)) { CurveType = FSpriterEnumHelper::StringToCurveType(CurveTypeAsString); if (CurveType == ESpriterCurveType::INVALID) { SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'curve_type' in '%s'."), *CurveTypeAsString, *NameForErrors); bSuccessfullyParsed = false; } } else { // Defaults to linear CurveType = ESpriterCurveType::Linear; } // Parse the object_ref array const TArray>* ObjectRefDescriptors; if (Tree->TryGetArrayField(TEXT("object_ref"), /*out*/ ObjectRefDescriptors)) { for (TSharedPtr ObjectRefDescriptor : *ObjectRefDescriptors) { FSpriterObjectRef& ObjectRef = *new (ObjectRefs) FSpriterObjectRef(); const bool bParsedObjectRefOK = ObjectRef.ParseFromJSON(ObjectRefDescriptor->AsObject(), NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedObjectRefOK; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'object_ref' field in '%s'."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownMainlineKeyKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterTimelineKey FSpriterTimelineKey::FSpriterTimelineKey() : TimeInMS(INDEX_NONE) , CurveType(ESpriterCurveType::INVALID) , C1(0.0) , C2(0.0) , Spin(1) { } bool FSpriterTimelineKey::ParseBasicsFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Read the time of the key (in milliseconds) if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS)) { // Assume 0 when missing? TimeInMS = 0; } // Optionally parse the curve_type property FString CurveTypeAsString; if (Tree->TryGetStringField(TEXT("curve_type"), /*out*/ CurveTypeAsString)) { CurveType = FSpriterEnumHelper::StringToCurveType(CurveTypeAsString); if (CurveType == ESpriterCurveType::INVALID) { SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'curve_type' in '%s'."), *CurveTypeAsString, *NameForErrors); bSuccessfullyParsed = false; } } else { // Defaults to linear CurveType = ESpriterCurveType::Linear; } // Optionally parse c1 and c2 Tree->TryGetNumberField(TEXT("c1"), /*out*/ C1); if ((C1 < 0.0) || (C1 > 1.0)) { SPRITER_IMPORT_ERROR(TEXT("Unexpected value '%f' for 'c1' in '%s' (expected 0..1)."), C1, *NameForErrors); bSuccessfullyParsed = false; } Tree->TryGetNumberField(TEXT("c2"), /*out*/ C2); if ((C2 < 0.0) || (C2 > 1.0)) { SPRITER_IMPORT_ERROR(TEXT("Unexpected value '%f' for 'c2' in '%s' (expected 0..1)."), C2, *NameForErrors); bSuccessfullyParsed = false; } // Optionally parse the spin Tree->TryGetNumberField(TEXT("spin"), /*out*/ Spin); if ((Spin != 1) && (Spin != -1) & (Spin != 0)) { SPRITER_IMPORT_ERROR(TEXT("Unknown value '%d' for 'spin' in '%s' (expected -1, 0, or 1)."), Spin, *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownBasicTimelineKeyKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterFatTimelineKey FSpriterFatTimelineKey::FSpriterFatTimelineKey() : Folder(INDEX_NONE) , File(INDEX_NONE) , bUseDefaultPivot(true) , PivotX(0.0f) , PivotY(1.0f) { } bool FSpriterFatTimelineKey::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent, const ESpriterObjectType ObjectType) { // Parse the common stuff shared for all object types bool bSuccessfullyParsed = ParseBasicsFromJSON(Tree, NameForErrors, bSilent); if (ObjectType == ESpriterObjectType::Bone) { // Parse the bone child const TSharedPtr* BoneDescriptor; if (Tree->TryGetObjectField(TEXT("bone"), /*out*/ BoneDescriptor)) { const bool bParsedBoneOK = ParseBoneFromJSON(*BoneDescriptor, NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed & bParsedBoneOK; } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'bone' field in '%s'."), *NameForErrors); bSuccessfullyParsed = false; } } else { // Parse the object child const TSharedPtr* ObjectDescriptor; if (Tree->TryGetObjectField(TEXT("object"), /*out*/ ObjectDescriptor)) { const bool bParsedObjectOK = ParseObjectFromJSON(*ObjectDescriptor, NameForErrors, bSilent, ObjectType); bSuccessfullyParsed = bSuccessfullyParsed && bParsedObjectOK; } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'object' field in '%s'."), *NameForErrors); bSuccessfullyParsed = false; } } return bSuccessfullyParsed; } bool FSpriterFatTimelineKey::ParseBoneFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { const bool bSuccessfullyParsed = Info.ParseFromJSON(Tree, NameForErrors, bSilent); UE_DO_SPRITER_AUDIT(KnownTimelineBoneKeyKeys, Tree, NameForErrors); return bSuccessfullyParsed; } bool FSpriterFatTimelineKey::ParseObjectFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent, const ESpriterObjectType ObjectType) { const bool bSuccessfullyParsed = Info.ParseFromJSON(Tree, NameForErrors, bSilent); Tree->TryGetNumberField(TEXT("file"), /*out*/ File); Tree->TryGetNumberField(TEXT("folder"), /*out*/ Folder); const bool bHasPivotX = Tree->TryGetNumberField(TEXT("pivot_x"), /*out*/ PivotX); const bool bHasPivotY = Tree->TryGetNumberField(TEXT("pivot_y"), /*out*/ PivotY); bUseDefaultPivot = !bHasPivotX && !bHasPivotY; UE_DO_SPRITER_AUDIT(KnownTimelineObjectKeyKeys, Tree, NameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterTimeline FSpriterTimeline::FSpriterTimeline() : ObjectIndex(INDEX_NONE) , ObjectType(ESpriterObjectType::INVALID) { } bool FSpriterTimeline::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Try parsing the timeline name if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the timeline object of '%s'."), *NameForErrors); Name = TEXT("(missing timeline name)"); bSuccessfullyParsed = false; } const FString LocalNameForErrors = FString::Printf(TEXT("%s timeline '%s'"), *NameForErrors, *Name); // Optionally parse the object_type property FString ObjectTypeAsString; if (Tree->TryGetStringField(TEXT("object_type"), /*out*/ ObjectTypeAsString)) { ObjectType = FSpriterEnumHelper::StringToObjectType(ObjectTypeAsString); if (ObjectType == ESpriterObjectType::INVALID) { SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'object_type' in '%s'."), *ObjectTypeAsString, *LocalNameForErrors); bSuccessfullyParsed = false; } } else { // Defaults to sprite ObjectType = ESpriterObjectType::Sprite; } // Optionally parse the obj property if (!Tree->TryGetNumberField(TEXT("obj"), /*out*/ ObjectIndex)) { ObjectIndex = INDEX_NONE; } // Parse the key array const TArray>* TimelineKeyDescriptors; if (Tree->TryGetArrayField(TEXT("key"), /*out*/ TimelineKeyDescriptors)) { for (TSharedPtr TimelineKeyDescriptor : *TimelineKeyDescriptors) { FSpriterFatTimelineKey& Key = *new (Keys) FSpriterFatTimelineKey(); const bool bParsedKeyOK = Key.ParseFromJSON(TimelineKeyDescriptor->AsObject(), LocalNameForErrors, bSilent, ObjectType); bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeyOK; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } // Read the meta block (optional) const TSharedPtr* MetaDescriptor; if (Tree->TryGetObjectField(TEXT("meta"), /*out*/ MetaDescriptor)) { const bool bParsedMetadataOK = Metadata.ParseFromJSON(*MetaDescriptor, NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedMetadataOK; } UE_DO_SPRITER_AUDIT(KnownTimelineKeys, Tree, LocalNameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterEventLineKey FSpriterEventLineKey::FSpriterEventLineKey() : TimeInMS(0) { } bool FSpriterEventLineKey::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bParsedSuccessfully = true; // Read the time of the key (in milliseconds) if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS)) { // Assume 0 when missing? TimeInMS = 0; } UE_DO_SPRITER_AUDIT(KnownEventLineKeyKeys, Tree, NameForErrors); return bParsedSuccessfully; } ////////////////////////////////////////////////////////////////////////// // FSpriterEventLine FSpriterEventLine::FSpriterEventLine() : ObjectIndex(INDEX_NONE) { } bool FSpriterEventLine::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Try parsing the event line name if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the event line of '%s'."), *NameForErrors); Name = TEXT("(missing event line name)"); bSuccessfullyParsed = false; } const FString LocalNameForErrors = FString::Printf(TEXT("%s event line '%s'"), *NameForErrors, *Name); // Parse the object index if (!Tree->TryGetNumberField(TEXT("obj"), /*out*/ ObjectIndex)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'obj' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } // Parse the key array const TArray>* KeyDescriptors; if (Tree->TryGetArrayField(TEXT("key"), /*out*/ KeyDescriptors)) { for (const TSharedPtr KeyDescriptor : *KeyDescriptors) { FSpriterEventLineKey& Key = *new (Keys) FSpriterEventLineKey(); const bool bParsedKeySuccessfully = Key.ParseFromJSON(KeyDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeySuccessfully; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterAnimation FSpriterAnimation::FSpriterAnimation() : LengthInMS(INDEX_NONE) , IntervalInMS(INDEX_NONE) , bIsLooping(true) { } bool FSpriterAnimation::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Try parsing the animation name if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the animation object of '%s'."), *NameForErrors); Name = TEXT("(missing animation name)"); bSuccessfullyParsed = false; } const FString LocalNameForErrors = FString::Printf(TEXT("%s animation '%s'"), *NameForErrors, *Name); // Read the length of the animation (in milliseconds) if (!Tree->TryGetNumberField(TEXT("length"), /*out*/ LengthInMS)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'length' field in the animation object of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } // Read the interval of the animation (in milliseconds - I think this is probably optional (it's not mentioned in the reference)) Tree->TryGetNumberField(TEXT("interval"), /*out*/ IntervalInMS); // Read the mainline const TSharedPtr* MainlineDescriptor; if (Tree->TryGetObjectField(TEXT("mainline"), /*out*/ MainlineDescriptor)) { // Parse the keys array inside of the mainline object const TArray>* KeyDescriptors; if ((*MainlineDescriptor)->TryGetArrayField(TEXT("key"), /*out*/ KeyDescriptors)) { for (TSharedPtr KeyDescriptor : *KeyDescriptors) { FSpriterMainlineKey& Key = *new (MainlineKeys) FSpriterMainlineKey(); const bool bParsedKeyOK = Key.ParseFromJSON(KeyDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeyOK; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in the 'mainline' object in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } //@TODO: Should we do a sub-audit in the mainline object here? } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'mainline' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } // Read the looping flag if (!Tree->TryGetBoolField(TEXT("looping"), /*out*/ bIsLooping)) { // Default to looping bIsLooping = true; } // Parse the timeline array const TArray>* TimelineDescriptors; if (Tree->TryGetArrayField(TEXT("timeline"), /*out*/ TimelineDescriptors)) { for (TSharedPtr TimelineDescriptor : *TimelineDescriptors) { FSpriterTimeline& Timeline = *new (Timelines) FSpriterTimeline(); const bool bParsedTimelineSuccessfully = Timeline.ParseFromJSON(TimelineDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedTimelineSuccessfully; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'timeline' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } // Read the eventline array (optional) const TArray>* EventLineDescriptors; if (Tree->TryGetArrayField(TEXT("eventline"), /*out*/ EventLineDescriptors)) { for (const TSharedPtr EventLineDescriptor : *EventLineDescriptors) { FSpriterEventLine& EventLine = *new (EventLines) FSpriterEventLine(); const bool bParsedEventLineOK = EventLine.ParseFromJSON(EventLineDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedEventLineOK; } } // Read the meta block (optional) const TSharedPtr* MetaDescriptor; if (Tree->TryGetObjectField(TEXT("meta"), /*out*/ MetaDescriptor)) { const bool bParsedMetadataOK = Metadata.ParseFromJSON(*MetaDescriptor, NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedMetadataOK; } //@TODO: Figure out what "gline": [], is UE_DO_SPRITER_AUDIT(KnownAnimationKeys, Tree, LocalNameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterCharacterMap FSpriterCharacterMap::FSpriterCharacterMap() { } bool FSpriterCharacterMap::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Try parsing the character map name if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the character_map object of '%s'."), *NameForErrors); Name = TEXT("(missing character_map name)"); bSuccessfullyParsed = false; } const FString LocalNameForErrors = FString::Printf(TEXT("%s character map '%s'"), *NameForErrors, *Name); // Parse the map array const TArray>* MapDescriptors; if (Tree->TryGetArrayField(TEXT("map"), /*out*/ MapDescriptors)) { for (TSharedPtr MapDescriptor : *MapDescriptors) { FSpriterMapInstruction& Map = *new (Maps) FSpriterMapInstruction(); const bool bParsedMapInstructionSuccessfully = Map.ParseFromJSON(MapDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedMapInstructionSuccessfully; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'map' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownCharacterMapKeys, Tree, LocalNameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterVariableDefinition FSpriterVariableDefinition::FSpriterVariableDefinition() : VariableType(ESpriterVariableType::INVALID) , DefaultValueNumber(0.0) { } bool FSpriterVariableDefinition::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Try parsing the variable name if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the variable defintion of '%s'."), *NameForErrors); Name = TEXT("(missing variable name)"); bSuccessfullyParsed = false; } const FString LocalNameForErrors = FString::Printf(TEXT("%s variable '%s'"), *NameForErrors, *Name); // Parse the type property FString VariableTypeAsString; if (Tree->TryGetStringField(TEXT("type"), /*out*/ VariableTypeAsString)) { VariableType = FSpriterEnumHelper::StringToVariableType(VariableTypeAsString); } if (VariableType == ESpriterVariableType::INVALID) { SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'type' in '%s'."), *VariableTypeAsString, *LocalNameForErrors); bSuccessfullyParsed = false; } // Parse the default value if ((VariableType == ESpriterVariableType::Float) || (VariableType == ESpriterVariableType::Integer)) { if (!Tree->TryGetNumberField(TEXT("default"), /*out*/ DefaultValueNumber)) { SPRITER_IMPORT_ERROR(TEXT("Expected a number field named 'default' in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } } else if (VariableType == ESpriterVariableType::String) { if (!Tree->TryGetStringField(TEXT("default"), /*out*/ DefaultValueString)) { SPRITER_IMPORT_ERROR(TEXT("Expected a string field named 'default' in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } } else if (VariableType != ESpriterVariableType::INVALID) { SPRITER_IMPORT_ERROR(TEXT("No handling for 'default' in '%s' for an unknown variable type (update this when a new type is added)."), *LocalNameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownVariableDefinitionKeys, Tree, LocalNameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterObjectInfo FSpriterObjectInfo::FSpriterObjectInfo() : Width(0.0) , Height(0.0) , PivotX(0.0) , PivotY(0.0) , ObjectType(ESpriterObjectType::INVALID) { } bool FSpriterObjectInfo::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Try parsing the object name FString ObjectNameAsString; if (!Tree->TryGetStringField(TEXT("name"), /*out*/ ObjectNameAsString)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the object of '%s'."), *NameForErrors); ObjectNameAsString = TEXT("(missing object name)"); bSuccessfullyParsed = false; } ObjectName = *ObjectNameAsString; const FString LocalNameForErrors = FString::Printf(TEXT("%s object '%s'"), *NameForErrors, *ObjectNameAsString); // Parse the type property FString ObjectTypeAsString; if (Tree->TryGetStringField(TEXT("type"), /*out*/ ObjectTypeAsString)) { ObjectType = FSpriterEnumHelper::StringToObjectType(ObjectTypeAsString); } if (ObjectType == ESpriterObjectType::INVALID) { SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'type' in '%s'."), *ObjectTypeAsString, *LocalNameForErrors); bSuccessfullyParsed = false; } // Optionally parse the width and height properties Tree->TryGetNumberField(TEXT("w"), /*out*/ Width); Tree->TryGetNumberField(TEXT("h"), /*out*/ Height); // Optionally parse the pivot properties Tree->TryGetNumberField(TEXT("pivot_x"), /*out*/ PivotX); Tree->TryGetNumberField(TEXT("pivot_y"), /*out*/ PivotY); //@TODO: Parse the frames[] field of an 'event' type (once I see one that isn't empty...) // Parse the var_defs array (optional; can be missing) const TArray>* VariableDefinitionDescriptors; if (Tree->TryGetArrayField(TEXT("var_defs"), /*out*/ VariableDefinitionDescriptors)) { for (TSharedPtr VariableDefinitionDescriptor : *VariableDefinitionDescriptors) { FSpriterVariableDefinition& VariableDef = *new (VariableDefinitions)FSpriterVariableDefinition(); const bool bParsedVariableDefOK = VariableDef.ParseFromJSON(VariableDefinitionDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedVariableDefOK; } } UE_DO_SPRITER_AUDIT(KnownObjInfoKeys, Tree, LocalNameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterEntity FSpriterEntity::FSpriterEntity() { } bool FSpriterEntity::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent) { bool bSuccessfullyParsed = true; // Try parsing the entity name if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the entity object of '%s'."), *NameForErrors); Name = TEXT("(missing entity name)"); bSuccessfullyParsed = false; } const FString LocalNameForErrors = FString::Printf(TEXT("%s entity '%s'"), *NameForErrors, *Name); // Parse the obj_info array const TArray>* ObjectDescriptors; if (Tree->TryGetArrayField(TEXT("obj_info"), /*out*/ ObjectDescriptors)) { for (TSharedPtr ObjectDescriptor : *ObjectDescriptors) { FSpriterObjectInfo& ObjectInfo = *new (Objects) FSpriterObjectInfo(); const bool bParsedObjectInfoOK = ObjectInfo.ParseFromJSON(ObjectDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedObjectInfoOK; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'obj_info' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } // Parse the var_defs array (optional; can be missing) const TArray>* VariableDefinitionDescriptors; if (Tree->TryGetArrayField(TEXT("var_defs"), /*out*/ VariableDefinitionDescriptors)) { for (TSharedPtr VariableDefinitionDescriptor : *VariableDefinitionDescriptors) { FSpriterVariableDefinition& VariableDef = *new (VariableDefinitions) FSpriterVariableDefinition(); const bool bParsedVariableDefOK = VariableDef.ParseFromJSON(VariableDefinitionDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedVariableDefOK; } } // Parse the animation array const TArray>* AnimationDescriptors; if (Tree->TryGetArrayField(TEXT("animation"), /*out*/ AnimationDescriptors)) { for (TSharedPtr AnimationDescriptor : *AnimationDescriptors) { FSpriterAnimation& Animation = *new (Animations) FSpriterAnimation(); const bool bParsedAnimationOK = Animation.ParseFromJSON(AnimationDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedAnimationOK; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'animation' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } // Parse the character_map array const TArray>* CharacterMapDescriptors; if (Tree->TryGetArrayField(TEXT("character_map"), /*out*/ CharacterMapDescriptors)) { for (TSharedPtr CharacterMapDescriptor : *CharacterMapDescriptors) { FSpriterCharacterMap& CharacterMap = *new (CharacterMaps) FSpriterCharacterMap(); const bool bParsedCharacterMapSuccessfully = CharacterMap.ParseFromJSON(CharacterMapDescriptor->AsObject(), LocalNameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedCharacterMapSuccessfully; } } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'character_map' field in '%s'."), *LocalNameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownEntityKeys, Tree, LocalNameForErrors); return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// // FSpriterSCON FSpriterSCON::FSpriterSCON() : bSuccessfullyParsed(false) { } void FSpriterSCON::ParseFromJSON(TSharedPtr Tree, const FString& NameForErrors, bool bSilent, bool bPreparseOnly) { bSuccessfullyParsed = true; // Try parsing the SCON version if (!Tree->TryGetStringField(TEXT("scon_version"), /*out*/ SconVersion)) { SPRITER_IMPORT_ERROR(TEXT("Expected a 'scon_version' field in the top level object of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } // Try parsing the generator and generator version strings if (!Tree->TryGetStringField(TEXT("generator"), /*out*/ Generator)) { // No good, probably isn't the right kind of file Generator = FString(); SPRITER_IMPORT_ERROR(TEXT("Expected a 'generator' field in the top level object of '%s'."), *NameForErrors); bSuccessfullyParsed = false; } if (!Tree->TryGetStringField(TEXT("generator_version"), /*out*/ GeneratorVersion)) { GeneratorVersion = TEXT("(missing generator_version)"); } // Validate the SCON version const FString ExpectedSCONVersion(TEXT("1.0")); if (SconVersion != ExpectedSCONVersion) { // Not 100% we can handle it but we'll try SPRITER_IMPORT_WARNING(TEXT("Unknown 'scon_version' '%s' (expected '%s') SCON file '%s'. Parsing will continue but the format may not be fully supported"), *SconVersion, *ExpectedSCONVersion, *NameForErrors); } // Validate the generator const FString BrashMonkeySpriterGenerator(TEXT("BrashMonkey Spriter")); if (Generator.StartsWith(BrashMonkeySpriterGenerator)) { // Cool, we (mostly) know how to handle these sorts of files! if (!bSilent) { UE_LOG(LogSpriterImporter, Log, TEXT("Parsing Spriter character SCON v%s exported from '%s' '%s'"), *SconVersion, *Generator, *GeneratorVersion); } } else if (!Generator.IsEmpty()) { // Not 100% we can handle it but we'll try SPRITER_IMPORT_WARNING(TEXT("Unexpected 'generator' named '%s' '%s' while parsing SCON v%s file '%s'. Parsing will continue but the format may not be fully supported"), *Generator, *GeneratorVersion, *SconVersion, *NameForErrors); } // Load the rest of the data if we're doing a full parse if (!bPreparseOnly) { // Parse the entities array const TArray>* EntityDescriptors; if (Tree->TryGetArrayField(TEXT("entity"), /*out*/ EntityDescriptors)) { for (TSharedPtr EntityDescriptor : *EntityDescriptors) { FSpriterEntity& Entity = *new (Entities) FSpriterEntity(); const bool bParsedEntityOK = Entity.ParseFromJSON(EntityDescriptor->AsObject(), NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedEntityOK; } } else { SPRITER_IMPORT_ERROR(TEXT("JSON exported from Spriter in file '%s' has no entities."), *NameForErrors); bSuccessfullyParsed = false; } // Parse the folders array const TArray>* FolderDescriptors; if (Tree->TryGetArrayField(TEXT("folder"), /*out*/ FolderDescriptors)) { for (TSharedPtr FolderDescriptor : *FolderDescriptors) { FSpriterFolder& Folder = *new (Folders) FSpriterFolder(); const bool bParsedFolderOK = Folder.ParseFromJSON(FolderDescriptor->AsObject(), NameForErrors, bSilent); bSuccessfullyParsed = bSuccessfullyParsed && bParsedFolderOK; } } else { SPRITER_IMPORT_ERROR(TEXT("JSON exported from Spriter in file '%s' has no folders."), *NameForErrors); bSuccessfullyParsed = false; } // Parse the tag list array (optional) const TArray>* TagListDescriptors; if (Tree->TryGetArrayField(TEXT("tag_list"), /*out*/ TagListDescriptors)) { for (const TSharedPtr TagListDescriptorUntyped : *TagListDescriptors) { const TSharedPtr TagListDescriptor = TagListDescriptorUntyped->AsObject(); FString NewTag; if (TagListDescriptor->TryGetStringField(TEXT("name"), /*out*/ NewTag)) { Tags.Add(NewTag); } else { SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the tag object in file '%s'."), *NameForErrors); bSuccessfullyParsed = false; } UE_DO_SPRITER_AUDIT(KnownSCONTagListKeys, TagListDescriptor, NameForErrors); } } } UE_DO_SPRITER_AUDIT(KnownSCONKeys, Tree, NameForErrors); } bool FSpriterSCON::IsValid() const { return bSuccessfullyParsed; } ////////////////////////////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE