From b29679c01d17785434c3f81f8a1b3af249a734a0 Mon Sep 17 00:00:00 2001 From: Tillman Staffen Date: Wed, 11 Mar 2026 17:55:41 +0100 Subject: [PATCH] Matched with base, BSettings changes and fixing of STT Manger --- .../Project/BP/BP_Project_Manager.uasset | 4 +- .../AvatarCore_AI/Public/AIBaseManager.h | 2 +- .../BP/EditorUtility/EUS_CreateAvatar.uasset | 3 + .../EUS_MetahumanToAvatar.uasset | 3 - .../BP/EditorUtility/EUS_UpdateAvatar.uasset | 3 + .../Private/Processor/Azure/AzureRunnable.cpp | 33 +- .../Processor/Azure/STTProcessorAzure.cpp | 6 +- .../BSettings/Private/BSettingsSystem.cpp | 372 ++---- .../Source/BSettings/Public/BSettingsSystem.h | 1035 +++++++++-------- 9 files changed, 668 insertions(+), 793 deletions(-) create mode 100644 Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_CreateAvatar.uasset delete mode 100644 Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_MetahumanToAvatar.uasset create mode 100644 Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_UpdateAvatar.uasset diff --git a/Unreal/Content/Project/BP/BP_Project_Manager.uasset b/Unreal/Content/Project/BP/BP_Project_Manager.uasset index 03c515b..508a9d2 100644 --- a/Unreal/Content/Project/BP/BP_Project_Manager.uasset +++ b/Unreal/Content/Project/BP/BP_Project_Manager.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66158c146a00e212f299ac591db7946d1f2a197982d82b8e6a3509edf5bf286d -size 2825952 +oid sha256:3091a128cf830692654f5887a8d0498b8aeae84c12fd08c89a53ef7e7dc1c93d +size 2825669 diff --git a/Unreal/Plugins/AvatarCore_AI/Source/AvatarCore_AI/Public/AIBaseManager.h b/Unreal/Plugins/AvatarCore_AI/Source/AvatarCore_AI/Public/AIBaseManager.h index 8db63c6..3fd51ca 100644 --- a/Unreal/Plugins/AvatarCore_AI/Source/AvatarCore_AI/Public/AIBaseManager.h +++ b/Unreal/Plugins/AvatarCore_AI/Source/AvatarCore_AI/Public/AIBaseManager.h @@ -86,7 +86,7 @@ public: void OnAIReady(); // Is the AI activated and ready? - UFUNCTION(BlueprintPure, Category = "AvatarCoreAI") + UFUNCTION(BlueprintCallable, Category = "AvatarCoreAI") bool IsAIActivated(); /** diff --git a/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_CreateAvatar.uasset b/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_CreateAvatar.uasset new file mode 100644 index 0000000..1fa750e --- /dev/null +++ b/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_CreateAvatar.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54569f35b0d48be1e55240b6d1f2ecc74e4841687621af4e99ecd0bbaa49e06d +size 396988 diff --git a/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_MetahumanToAvatar.uasset b/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_MetahumanToAvatar.uasset deleted file mode 100644 index 4bbe985..0000000 --- a/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_MetahumanToAvatar.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1db8d9db14971ac70c0b250a40ca19ae9bc21f405ce84ca77c1d1eeb86ec0546 -size 399863 diff --git a/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_UpdateAvatar.uasset b/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_UpdateAvatar.uasset new file mode 100644 index 0000000..e70ad09 --- /dev/null +++ b/Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_UpdateAvatar.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aeb7364843bcb74516ed531f6ed41f45661e1a7968d442637d6f764e10ab07c4 +size 272770 diff --git a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp index 2c1f921..ea3e7a4 100644 --- a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp +++ b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp @@ -21,6 +21,7 @@ FAzureRunnable::~FAzureRunnable() if (Thread) { Owner = nullptr; + bIsRunning = false; // Unblock the Run() loop before killing the thread Thread->Kill(true); delete Thread; } @@ -113,12 +114,13 @@ uint32 FAzureRunnable::Run() if (ConnectionTestOnly) { //TODO Check if Api is correct - AsyncTask(ENamedThreads::GameThread, [this] { - if (Owner != nullptr) + TWeakObjectPtr WeakOwnerLocal(Owner); + AsyncTask(ENamedThreads::GameThread, [WeakOwnerLocal] { + if (WeakOwnerLocal.IsValid()) { - Owner->OnConnectionSuccess(); + WeakOwnerLocal->OnConnectionSuccess(); } - }); + }); Stop(); RecognitionEnd.set_value(); // Ensure promise is fulfilled return 0; @@ -142,15 +144,8 @@ uint32 FAzureRunnable::Run() } Recognizer->StopContinuousRecognitionAsync().get(); - // Use a weak pointer to Owner to avoid accessing this after destruction - TWeakObjectPtr WeakOwner(Owner); - AsyncTask(ENamedThreads::GameThread, [WeakOwner]() { - if (WeakOwner.IsValid() && WeakOwner.Get() != nullptr) - { - WeakOwner->OnRunnableEnded(); - } - }); - + // Disconnect all events BEFORE posting the game-thread task to prevent + // any in-flight SDK callbacks from firing during the async gap Recognizer->Canceled.DisconnectAll(); Recognizer->Recognized.DisconnectAll(); Recognizer->Recognizing.DisconnectAll(); @@ -159,6 +154,14 @@ uint32 FAzureRunnable::Run() Recognizer->SessionStopped.DisconnectAll(); Recognizer->SessionStarted.DisconnectAll(); + TWeakObjectPtr WeakOwner(Owner); + AsyncTask(ENamedThreads::GameThread, [WeakOwner]() { + if (WeakOwner.IsValid() && WeakOwner.Get() != nullptr) + { + WeakOwner->OnRunnableEnded(); + } + }); + RecognitionEnd.set_value(); // Ensure promise is fulfilled return 0; } @@ -170,7 +173,8 @@ void FAzureRunnable::Stop() void FAzureRunnable::StopRecognition(bool Forced) { - if (Forced) { + bIsRunning = false; // Always stop the Run() loop first + if (Forced && Recognizer) { Recognizer->Canceled.DisconnectAll(); Recognizer->Recognized.DisconnectAll(); Recognizer->Recognizing.DisconnectAll(); @@ -180,7 +184,6 @@ void FAzureRunnable::StopRecognition(bool Forced) Recognizer->SessionStarted.DisconnectAll(); Owner = nullptr; } - bIsRunning = false; } void FAzureRunnable::AddAudioChunk(const TArray& PCMData) diff --git a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp index 061e4e3..4393dbe 100644 --- a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp +++ b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp @@ -103,7 +103,7 @@ void USTTProcessorAzure::OnSpeechStateChanged(ESTTTalkingState TalkingState) StopRecognition(false); else { if (!intermediateResult.IsEmpty()) { - USTTProcessorBase::OnTranscriptionIntermediateResult(TranscriptionCounter, *intermediateResult); + USTTProcessorBase::OnTranscriptionResult(TranscriptionCounter, intermediateResult, DetectedLanguage); intermediateResult = ""; } } @@ -127,9 +127,7 @@ void USTTProcessorAzure::StopRecognition(bool Forced) if (AzureRunnable) { AzureRunnable->StopRecognition(Forced); - if (Forced) { - AzureRunnable = nullptr; - } + AzureRunnable = nullptr; if(bDebugMode && STTManager!=nullptr) STTManager->OnSTTLog.Broadcast(TEXT("Recognition thread stopped.")); } diff --git a/Unreal/Plugins/BSettings/Source/BSettings/Private/BSettingsSystem.cpp b/Unreal/Plugins/BSettings/Source/BSettings/Private/BSettingsSystem.cpp index bbd755a..12f95e9 100644 --- a/Unreal/Plugins/BSettings/Source/BSettings/Private/BSettingsSystem.cpp +++ b/Unreal/Plugins/BSettings/Source/BSettings/Private/BSettingsSystem.cpp @@ -339,7 +339,7 @@ void UBSettingsSystem::InitializeValues(TSharedPtr Destination, USt } // Helper: resolve enum value from a string using internal name, display name, and UUserDefinedEnum's DisplayNameMap -static bool ResolveEnumValueFromString(const UEnum* Enum, const FString& InString, int64& OutValue) +bool UBSettingsSystem::ResolveEnumValueFromString(const UEnum* Enum, const FString& InString, int64& OutValue) { if (!Enum) return false; @@ -1083,6 +1083,90 @@ void UBSettingsSystem::CollectEnumValues(const FProperty* Prop, TArray& } } +// Recursively builds a JSON schema description for a single property. +// Handles type, tooltip, enum values, struct sub-fields, and array items at any depth. +TSharedPtr UBSettingsSystem::BuildFieldSchema(const FProperty* Prop) +{ + TSharedPtr FieldObj = MakeShared(); + const FString TypeStr = GetSchemaType(Prop); + FieldObj->SetStringField(TEXT("type"), TypeStr); + + // Tooltip + const FString Tooltip = Prop->GetMetaData(TEXT("ToolTip")); + if (!Tooltip.IsEmpty()) + { + FieldObj->SetStringField(TEXT("tooltip"), Tooltip); + } + + // Enum: collect available values + if (TypeStr == TEXT("enum")) + { + TArray EnumValues; + CollectEnumValues(Prop, EnumValues); + TArray> JsonEnumVals; + for (const FString& Val : EnumValues) + { + JsonEnumVals.Add(MakeShared(Val)); + } + FieldObj->SetArrayField(TEXT("enum"), JsonEnumVals); + } + + // Struct: recurse into sub-fields + if (TypeStr == TEXT("struct")) + { + if (const FStructProperty* StructProp = CastField(Prop)) + { + TSharedPtr FieldsObj = MakeShared(); + for (TFieldIterator It(StructProp->Struct); It; ++It) + { + const FProperty* SubProp = *It; + if (!SubProp || IsSchemaCategoryProperty(SubProp)) continue; + FieldsObj->SetObjectField(SubProp->GetAuthoredName(), BuildFieldSchema(SubProp)); + } + FieldObj->SetObjectField(TEXT("fields"), FieldsObj); + } + } + + // Array: item type + recurse for enum/struct items + if (TypeStr == TEXT("array")) + { + if (const FArrayProperty* ArrProp = CastField(Prop)) + { + const FProperty* Inner = ArrProp->Inner; + const FString InnerType = GetSchemaType(Inner); + FieldObj->SetStringField(TEXT("itemsType"), InnerType); + + if (InnerType == TEXT("enum")) + { + TArray EnumValues; + CollectEnumValues(Inner, EnumValues); + TArray> JsonEnumVals; + for (const FString& Val : EnumValues) + { + JsonEnumVals.Add(MakeShared(Val)); + } + FieldObj->SetArrayField(TEXT("itemsEnum"), JsonEnumVals); + } + else if (InnerType == TEXT("struct")) + { + if (const FStructProperty* InnerStructProp = CastField(Inner)) + { + TSharedPtr ItemsFieldsObj = MakeShared(); + for (TFieldIterator It(InnerStructProp->Struct); It; ++It) + { + const FProperty* SubProp = *It; + if (!SubProp || IsSchemaCategoryProperty(SubProp)) continue; + ItemsFieldsObj->SetObjectField(SubProp->GetAuthoredName(), BuildFieldSchema(SubProp)); + } + FieldObj->SetObjectField(TEXT("itemsFields"), ItemsFieldsObj); + } + } + } + } + + return FieldObj; +} + // Exports a JSON schema file describing the structure of the provided UStruct. bool UBSettingsSystem::StructToJsonSchema(const UStruct* StructType, const FString& ConfigName, const FString& SavedSubFolder, const uint8* DefaultDataOverride) { @@ -1165,299 +1249,27 @@ bool UBSettingsSystem::StructToJsonSchema(const UStruct* StructType, const FStri // Use authored variable name (no generated suffixes) const FString VarName = Prop->GetAuthoredName(); - // Determine schema type for this property - const FString TypeStr = GetSchemaType(Prop); + // Build field schema recursively (type, tooltip, enum, struct fields, array items) + TSharedPtr FieldObj = BuildFieldSchema(Prop); - // Build JSON object for this variable (holds its attributes) - TSharedPtr FieldObj = MakeShared(); - - FieldObj->SetStringField(TEXT("type"), TypeStr); - - // Export default value + // Top-level only: export default value if (TSharedPtr DefVal = Defaults->TryGetField(VarName)) { FieldObj->SetField(TEXT("default"), DefVal); } - // Tooltip: only include if explicitly set via UPROPERTY(meta = (ToolTip = "...")) - const FString Tooltip = Prop->GetMetaData(TEXT("ToolTip")); - if (!Tooltip.IsEmpty()) - { - FieldObj->SetStringField(TEXT("tooltip"), Tooltip); - } - - // SaveGame: only include if explicitly set - const bool SaveGame = Prop->HasAnyPropertyFlags(CPF_SaveGame); - if (SaveGame) + // Top-level only: SaveGame flag + if (Prop->HasAnyPropertyFlags(CPF_SaveGame)) { - FieldObj->SetBoolField(TEXT("hotreload"), SaveGame); + FieldObj->SetBoolField(TEXT("hotreload"), true); } - // attach category name to each variable if a category context is active --- + // Top-level only: attach category if active if (!CurrentCategory.IsEmpty()) { FieldObj->SetStringField(TEXT("category"), CurrentCategory); } - // If this is a struct: list sub-variables and their types - if (TypeStr == TEXT("struct")) - { - if (const FStructProperty* StructProp = CastField(Prop)) - { - TSharedPtr FieldsObj = MakeShared(); - - for (TFieldIterator SubIt(StructProp->Struct); SubIt; ++SubIt) - { - const FProperty* SubProp = *SubIt; - if (!SubProp || IsSchemaCategoryProperty(SubProp)) - { - continue; // Marker/Invalides Feld überspringen - } - - const FString SubName = SubProp->GetAuthoredName(); - const FString SubType = GetSchemaType(SubProp); - - TSharedPtr SubFieldObj = MakeShared(); - SubFieldObj->SetStringField(TEXT("type"), SubType); - - // --- Enums als direkte Struct-Subfelder (optional aber konsistent) - if (SubType == TEXT("enum")) - { - TArray EnumValues; - CollectEnumValues(SubProp, EnumValues); - TArray> JsonEnumVals; - for (const FString& V : EnumValues) - { - JsonEnumVals.Add(MakeShared(V)); - } - SubFieldObj->SetArrayField(TEXT("enum"), JsonEnumVals); - } - - // --- Verschachteltes Struct als Subfeld: nur Felder (Name+Typ) auflisten - else if (SubType == TEXT("struct")) - { - if (const FStructProperty* NestedStructProp = CastField(SubProp)) - { - TSharedPtr NestedFieldsObj = MakeShared(); - - for (TFieldIterator NIt(NestedStructProp->Struct); NIt; ++NIt) - { - const FProperty* NProp = *NIt; - if (!NProp || IsSchemaCategoryProperty(NProp)) continue; - - const FString NName = NProp->GetAuthoredName(); - const FString NType = GetSchemaType(NProp); - - TSharedPtr NFieldObj = MakeShared(); - NFieldObj->SetStringField(TEXT("type"), NType); - - // Arrays innerhalb des verschachtelten Structs (1 Ebene) - if (NType == TEXT("array")) - { - if (const FArrayProperty* DeepArr = CastField(NProp)) - { - const FProperty* DeepInner = DeepArr->Inner; - const FString DeepInnerType = GetSchemaType(DeepInner); - NFieldObj->SetStringField(TEXT("itemsType"), DeepInnerType); - - if (DeepInnerType == TEXT("enum")) - { - TArray EV; - CollectEnumValues(DeepInner, EV); - TArray> JV; - for (const FString& e : EV) JV.Add(MakeShared(e)); - NFieldObj->SetArrayField(TEXT("itemsEnum"), JV); - } - else if (DeepInnerType == TEXT("struct")) - { - if (const FStructProperty* DeepInnerStruct = CastField(DeepInner)) - { - TSharedPtr ItemsFieldsObj = MakeShared(); - for (TFieldIterator SIt(DeepInnerStruct->Struct); SIt; ++SIt) - { - const FProperty* SProp = *SIt; - if (!SProp || IsSchemaCategoryProperty(SProp)) continue; - - const FString SName = SProp->GetAuthoredName(); - const FString SType = GetSchemaType(SProp); - - TSharedPtr SObj = MakeShared(); - SObj->SetStringField(TEXT("type"), SType); - ItemsFieldsObj->SetObjectField(SName, SObj); - } - NFieldObj->SetObjectField(TEXT("itemsFields"), ItemsFieldsObj); - } - } - } - } - - NestedFieldsObj->SetObjectField(NName, NFieldObj); - } - - SubFieldObj->SetObjectField(TEXT("fields"), NestedFieldsObj); - } - } - - // --- Array als Struct-Subfeld: itemsType (+ itemsEnum / itemsFields) - else if (SubType == TEXT("array")) - { - if (const FArrayProperty* SubArr = CastField(SubProp)) - { - const FProperty* Inner = SubArr->Inner; - const FString InnerType = GetSchemaType(Inner); - SubFieldObj->SetStringField(TEXT("itemsType"), InnerType); - - if (InnerType == TEXT("enum")) - { - TArray EnumValues; - CollectEnumValues(Inner, EnumValues); - TArray> JsonEnumVals; - for (const FString& V : EnumValues) - { - JsonEnumVals.Add(MakeShared(V)); - } - SubFieldObj->SetArrayField(TEXT("itemsEnum"), JsonEnumVals); - } - else if (InnerType == TEXT("struct")) - { - if (const FStructProperty* InnerStructProp = CastField(Inner)) - { - TSharedPtr ItemsFieldsObj = MakeShared(); - - for (TFieldIterator SIt(InnerStructProp->Struct); SIt; ++SIt) - { - const FProperty* SProp = *SIt; - if (!SProp || IsSchemaCategoryProperty(SProp)) continue; - - const FString SName = SProp->GetAuthoredName(); - const FString SType = GetSchemaType(SProp); - - TSharedPtr SObj = MakeShared(); - SObj->SetStringField(TEXT("type"), SType); - - // Arrays innerhalb der Item-Structs (1 Ebene) - if (SType == TEXT("array")) - { - if (const FArrayProperty* DeepArr = CastField(SProp)) - { - const FProperty* DeepInner = DeepArr->Inner; - const FString DeepInnerType = GetSchemaType(DeepInner); - SObj->SetStringField(TEXT("itemsType"), DeepInnerType); - - if (DeepInnerType == TEXT("enum")) - { - TArray EV; - CollectEnumValues(DeepInner, EV); - TArray> JV; - for (const FString& e : EV) JV.Add(MakeShared(e)); - SObj->SetArrayField(TEXT("itemsEnum"), JV); - } - else if (DeepInnerType == TEXT("struct")) - { - if (const FStructProperty* DeepInnerStruct = CastField(DeepInner)) - { - TSharedPtr ItemsFields2 = MakeShared(); - for (TFieldIterator S2It(DeepInnerStruct->Struct); S2It; ++S2It) - { - const FProperty* S2Prop = *S2It; - if (!S2Prop || IsSchemaCategoryProperty(S2Prop)) continue; - - TSharedPtr S2Obj = MakeShared(); - S2Obj->SetStringField(TEXT("type"), GetSchemaType(S2Prop)); - ItemsFields2->SetObjectField(S2Prop->GetAuthoredName(), S2Obj); - } - SObj->SetObjectField(TEXT("itemsFields"), ItemsFields2); - } - } - } - } - - ItemsFieldsObj->SetObjectField(SName, SObj); - } - - SubFieldObj->SetObjectField(TEXT("itemsFields"), ItemsFieldsObj); - } - } - } - } - - // Feld registrieren - FieldsObj->SetObjectField(SubName, SubFieldObj); - } - - // struct-Felddefinitionen anhängen - FieldObj->SetObjectField(TEXT("fields"), FieldsObj); - } - } - - // Enums: collect available values and emit as array of strings - if (TypeStr == TEXT("enum")) - { - TArray EnumValues; - CollectEnumValues(Prop, EnumValues); - - TArray> JsonEnumVals; - for (const FString& Val : EnumValues) - { - JsonEnumVals.Add(MakeShared(Val)); - } - FieldObj->SetArrayField(TEXT("enum"), JsonEnumVals); - } - - // Arrays: determine item type and add additional metadata - if (TypeStr == TEXT("array")) - { - if (const FArrayProperty* ArrProp = CastField(Prop)) - { - const FProperty* Inner = ArrProp->Inner; - const FString InnerType = GetSchemaType(Inner); - - // Always expose the item type - FieldObj->SetStringField(TEXT("itemsType"), InnerType); - - // If item is enum: export enum values for the array items - if (InnerType == TEXT("enum")) - { - TArray EnumValues; - CollectEnumValues(Inner, EnumValues); - - TArray> JsonEnumVals; - for (const FString& V : EnumValues) - { - JsonEnumVals.Add(MakeShared(V)); - } - - // Keep flat style: itemsEnum mirrors "enum" of single fields - FieldObj->SetArrayField(TEXT("itemsEnum"), JsonEnumVals); - } - - // If item is a struct: list sub-variables + their types (no tooltips) - if (InnerType == TEXT("struct")) - { - if (const FStructProperty* InnerStructProp = CastField(Inner)) - { - TSharedPtr ItemsFieldsObj = MakeShared(); - - // Same as for single struct fields, but nested under itemsFields - for (TFieldIterator SubIt(InnerStructProp->Struct); SubIt; ++SubIt) - { - const FProperty* SubProp = *SubIt; - const FString SubName = SubProp->GetAuthoredName(); - const FString SubType = GetSchemaType(SubProp); - - TSharedPtr SubFieldObj = MakeShared(); - SubFieldObj->SetStringField(TEXT("type"), SubType); - - ItemsFieldsObj->SetObjectField(SubName, SubFieldObj); - } - - // Flat style to match arrays: provide fields for items - FieldObj->SetObjectField(TEXT("itemsFields"), ItemsFieldsObj); - } - } - } - } - // push this variable object wrapped under its variable name key { TSharedPtr Wrapper = MakeShared(); diff --git a/Unreal/Plugins/BSettings/Source/BSettings/Public/BSettingsSystem.h b/Unreal/Plugins/BSettings/Source/BSettings/Public/BSettingsSystem.h index 0a8be76..4410c9b 100644 --- a/Unreal/Plugins/BSettings/Source/BSettings/Public/BSettingsSystem.h +++ b/Unreal/Plugins/BSettings/Source/BSettings/Public/BSettingsSystem.h @@ -126,494 +126,551 @@ public: DECLARE_FUNCTION(execExportStructToJsonSchema); - UFUNCTION(BlueprintCallable, Category = "BSettings", CustomThunk, meta = (CustomStructureParam = "OutVal")) - static UPARAM(DisplayName = "Ok") bool GetValueByName(FString Name, int32& OutVal); - DECLARE_FUNCTION(execGetValueByName) { - P_GET_PROPERTY(FStrProperty, Name); - Stack.StepCompiledIn(NULL); - TSharedPtr Field = SettingsJson->TryGetField(Name); - - if (!Field.IsValid()) - { - *(bool*)RESULT_PARAM = false; - OnError.Broadcast(FString::Printf(TEXT("Property '%s' not found"), *Name)); - } - else - { - FProperty* Prop = Stack.MostRecentProperty; // Gets the most recent property from the stack - void* ValPtr = Stack.MostRecentPropertyAddress; // Gets the address of the value of the most recent property - *(bool*)RESULT_PARAM = true; - if (FStructProperty* StructProperty = ExactCastField(Prop)) - { - const TSharedPtr* StructJsonObject; - if (Field->TryGetObject(StructJsonObject)) - { - GetNestedStructFromJson(StructProperty->Struct, StructJsonObject->Get(), ValPtr); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Failed to retrieve struct field '%s'"), *Name)); - } - } - else if (FEnumProperty* EnumProperty = ExactCastField(Prop)) - { - UEnum* Enum = GetEnumByPropertyName(Name); - if (Enum) - { - if (EnumProperty->GetEnum() == Enum) - { - FString S; - if (!SettingsJson->TryGetStringField(Name, S)) - { - *(bool*)RESULT_PARAM = false; - OnError.Broadcast(FString::Printf(TEXT("Failed to retrieve enum field '%s'"), *Name)); - } - else - { - int64 Raw = Enum->GetValueByNameString(S.TrimStartAndEnd()); - if (Raw == INDEX_NONE) - { - // Try display names - const int32 Count = Enum->NumEnums(); - const FString Trimmed = S.TrimStartAndEnd(); - for (int32 i = 0; i < Count; ++i) - { - const FString InternalName = Enum->GetNameStringByIndex(i); - if (InternalName.EndsWith(TEXT("_MAX"))) - { - continue; - } - const FString Display = Enum->GetDisplayNameTextByIndex(i).ToString(); - if (Trimmed.Equals(Display, ESearchCase::IgnoreCase)) - { - Raw = Enum->GetValueByIndex(i); - break; - } - } - } - if (Raw == INDEX_NONE) - { - if (const UUserDefinedEnum* UD = Cast(Enum)) - { - const FString Trimmed = S.TrimStartAndEnd(); - for (const TTuple& Pair : UD->DisplayNameMap) - { - if (Pair.Value.ToString().Equals(Trimmed, ESearchCase::IgnoreCase)) - { - Raw = Enum->GetValueByName(Pair.Key); - if (Raw != INDEX_NONE) break; - } - } - } - } - if (Raw == INDEX_NONE) - { - *(bool*)RESULT_PARAM = false; - OnError.Broadcast(FString::Printf(TEXT("%s is not a valid value of enum %s"), *S, *Enum->GetAuthoredName())); - } - else - { - FNumericProperty* Underlying = EnumProperty->GetUnderlyingProperty(); - Underlying->SetIntPropertyValue(ValPtr, Raw); - } - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Enum mismatch for property %s"), *Name)); - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("%s is not an Enum"), *Name)); - } - } - else if (FNumericProperty* NumericProperty = CastField(Prop)) - { - if (NumericProperty->IsEnum()) - { - UEnum* Enum = GetEnumByPropertyName(Name); - if (Enum) - { - if (NumericProperty->GetIntPropertyEnum() == Enum) - { - FString S; - if (!SettingsJson->TryGetStringField(Name, S)) - { - *(bool*)RESULT_PARAM = false; - OnError.Broadcast(FString::Printf(TEXT("Failed to retrieve enum field '%s'"), *Name)); - } - else - { - int64 Raw = Enum->GetValueByNameString(S.TrimStartAndEnd()); - if (Raw == INDEX_NONE) - { - // Try display names - const int32 Count = Enum->NumEnums(); - const FString Trimmed = S.TrimStartAndEnd(); - for (int32 i = 0; i < Count; ++i) - { - const FString InternalName = Enum->GetNameStringByIndex(i); - if (InternalName.EndsWith(TEXT("_MAX"))) - { - continue; - } - const FString Display = Enum->GetDisplayNameTextByIndex(i).ToString(); - if (Trimmed.Equals(Display, ESearchCase::IgnoreCase)) - { - Raw = Enum->GetValueByIndex(i); - break; - } - } - } - if (Raw == INDEX_NONE) - { - if (const UUserDefinedEnum* UD = Cast(Enum)) - { - const FString Trimmed = S.TrimStartAndEnd(); - for (const TTuple& Pair : UD->DisplayNameMap) - { - if (Pair.Value.ToString().Equals(Trimmed, ESearchCase::IgnoreCase)) - { - Raw = Enum->GetValueByName(Pair.Key); - if (Raw != INDEX_NONE) break; - } - } - } - } - if (Raw == INDEX_NONE) - { - *(bool*)RESULT_PARAM = false; - OnError.Broadcast(FString::Printf(TEXT("%s is not a valid value of enum %s"), *S, *Enum->GetAuthoredName())); - } - else - { - NumericProperty->SetIntPropertyValue(ValPtr, Raw); - } - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Enum mismatch for property %s"), *Name)); - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("%s is not an Enum"), *Name)); - } - } - else if (FIntProperty* IntProperty = ExactCastField(Prop)) - { - if (!SettingsJson->TryGetNumberField(Name, *(int32*)ValPtr)) - OnError.Broadcast("Int fail"); - } - else if (FBoolProperty* BoolProperty = ExactCastField(Prop)) - { - if (!SettingsJson->TryGetBoolField(Name, *(bool*)ValPtr)) - OnError.Broadcast("Bool fail"); - } - else if (FDoubleProperty* DoubleProperty = ExactCastField(Prop)) - { - if (!SettingsJson->TryGetNumberField(Name, *(double*)ValPtr)) - OnError.Broadcast("Float fail"); - } - else if (FFloatProperty* FloatProperty = ExactCastField(Prop)) - { - if (!SettingsJson->TryGetNumberField(Name, *(float*)ValPtr)) - OnError.Broadcast("Float fail"); - } - else if (FInt64Property* Int64Property = ExactCastField(Prop)) - { - if (!SettingsJson->TryGetNumberField(Name, *(int64*)ValPtr)) - OnError.Broadcast("Int64 fail"); - } - else if (FByteProperty* ByteProperty = ExactCastField(Prop)) - { - // Casting to int8 failed, so we cast to int32. The worst thing that can happen is an implicit cast to int8 - if (!SettingsJson->TryGetNumberField(Name, *(int32*)ValPtr)) - OnError.Broadcast("Byte fail"); - } - } - else if (FStrProperty* StrProperty = ExactCastField(Prop)) - { - if (!SettingsJson->TryGetStringField(Name, *(FString*)ValPtr)) - OnError.Broadcast("String fail"); - } - else if (FBoolProperty* BoolProperty = ExactCastField(Prop)) - { - if (!SettingsJson->TryGetBoolField(Name, *(bool*)ValPtr)) - OnError.Broadcast("Bool fail"); - } - else if (FArrayProperty* ArrayProp = ExactCastField(Prop)) - { - const TArray>* Arr = nullptr; - if (Field->TryGetArray(Arr)) - { - // Handle arrays of user-defined structs - if (const FStructProperty* InnerStruct = CastField(ArrayProp->Inner)) - { - FScriptArrayHelper Helper(ArrayProp, ValPtr); - Helper.Resize(Arr->Num()); - for (int32 i = 0; i < Arr->Num(); ++i) - { - void* ElemPtr = Helper.GetRawPtr(i); - const TSharedPtr& Elem = (*Arr)[i]; - const TSharedPtr* ObjPtr = nullptr; - if (Elem.IsValid() && Elem->TryGetObject(ObjPtr)) - { - GetNestedStructFromJson(InnerStruct->Struct, ObjPtr->Get(), ElemPtr); - } - else if (Elem.IsValid() && Elem->Type == EJson::String) - { - // Try to parse stringified JSON object - FString Raw = Elem->AsString(); - TSharedPtr ParsedObj; - { - TSharedRef> Reader = TJsonReaderFactory<>::Create(Raw); - TSharedPtr TempObj; - if (FJsonSerializer::Deserialize(Reader, TempObj) && TempObj.IsValid()) - { - ParsedObj = TempObj; - } - } - if (!ParsedObj.IsValid()) - { - // Normalize common non-JSON inputs: single quotes and Python booleans - FString Normalized = Raw.Replace(TEXT("'"), TEXT("\"")); - Normalized.ReplaceInline(TEXT(": True"), TEXT(": true")); - Normalized.ReplaceInline(TEXT(":true"), TEXT(": true")); - Normalized.ReplaceInline(TEXT(": False"), TEXT(": false")); - Normalized.ReplaceInline(TEXT(":false"), TEXT(": false")); - TSharedRef> Reader2 = TJsonReaderFactory<>::Create(Normalized); - TSharedPtr TempObj2; - if (FJsonSerializer::Deserialize(Reader2, TempObj2) && TempObj2.IsValid()) - { - ParsedObj = TempObj2; - } - } - if (ParsedObj.IsValid()) - { - GetNestedStructFromJson(InnerStruct->Struct, ParsedObj.Get(), ElemPtr); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Array '%s' element %d is a string but not valid JSON object"), *Name, i)); - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Array '%s' element %d is not an object (struct)"), *Name, i)); - } - } - *(bool*)RESULT_PARAM = true; - } - // Handle arrays of strings (existing behavior) - else if (const FStrProperty* InnerStr = CastField(ArrayProp->Inner)) - { - FScriptArrayHelper Helper(ArrayProp, ValPtr); - Helper.Resize(Arr->Num()); - for (int32 i = 0; i < Arr->Num(); ++i) - { - void* ElemPtr = Helper.GetRawPtr(i); - if ((*Arr)[i].IsValid() && (*Arr)[i]->Type == EJson::String) - { - const FString S = (*Arr)[i]->AsString(); - const_cast(InnerStr)->SetPropertyValue(ElemPtr, S); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Array '%s' element %d is not a string"), *Name, i)); - } - } - *(bool*)RESULT_PARAM = true; - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Unsupported array inner type for '%s'"), *Name)); - *(bool*)RESULT_PARAM = false; - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Failed to read array field '%s'"), *Name)); - *(bool*)RESULT_PARAM = false; - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Unsupported type on prop %s"), *Name)); - } - } - P_FINISH; - } - - UFUNCTION(BlueprintCallable, Category = "BSettings", CustomThunk, meta = (CustomStructureParam = "InVal")) - static UPARAM(DisplayName = "Ok") bool SetValueByName(FString Name, const int32& InVal); - DECLARE_FUNCTION(execSetValueByName) { - P_GET_PROPERTY(FStrProperty, Name); - Stack.StepCompiledIn(NULL); - TSharedPtr Field = SettingsJson->TryGetField(Name); - if (!Field.IsValid()) - { - *(bool*)RESULT_PARAM = false; - OnError.Broadcast(FString::Printf(TEXT("Property '%s' not found"), *Name)); - } - else - { - FProperty* Prop = Stack.MostRecentProperty; // Gets the most recent property from the stack - void* ValPtr = Stack.MostRecentPropertyAddress; // Gets the address of the value of the most recent property - if (Field->Type == EJson::Object) - { - if (FStructProperty* StructProperty = ExactCastField(Prop)) - { - const TSharedPtr* StructJsonObject; - if (Field->TryGetObject(StructJsonObject)) - { - InitializeValues(*StructJsonObject, StructProperty->Struct, (const uint8*)ValPtr); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Failed to retrieve struct field '%s'"), *Name)); - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); - } - } - else if (Field->Type == EJson::String) - { - // check if the corresponding property in the settings struct is an enum - if (UEnum* SettingsEnum = GetEnumByPropertyName(Name)) - { - // check if InVal is an enum - if (FEnumProperty* EnumProperty = ExactCastField(Prop)) - { - if (EnumProperty->GetEnum() == SettingsEnum) - { - SettingsJson->SetStringField(Name, SettingsEnum->GetNameStringByIndex(*(int8*)ValPtr)); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Enum mismatch for property %s"), *Name)); - } - } - // otherwise check if InVal is numerical - else if (FNumericProperty* NumericProperty = CastField(Prop)) - { - // check if InVal is a valid value of the respective enum - if (!NumericProperty->IsFloatingPoint() && 0 <= *(uint8*)ValPtr && *(uint8*)ValPtr < SettingsEnum->GetMaxEnumValue()) - { - SettingsJson->SetStringField(Name, SettingsEnum->GetNameStringByIndex(*(int8*)ValPtr)); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("%d is not a valid value of enum %s"), *(int32*)ValPtr, *SettingsEnum->GetAuthoredName())); - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); - } - } - else if (FStrProperty* StrProperty = CastField(Prop)) - { - SettingsJson->SetStringField(Name, StrProperty->GetPropertyValue(ValPtr)); - } - else if (FTextProperty* TextProperty = CastField(Prop)) - { - SettingsJson->SetStringField(Name, TextProperty->GetPropertyValue(ValPtr).ToString()); - } - else if (FNameProperty* NameProperty = CastField(Prop)) - { - SettingsJson->SetStringField(Name, NameProperty->GetPropertyValue(ValPtr).ToString()); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); - } - } - else if (Field->Type == EJson::Number) - { - - if (FNumericProperty* NumericProperty = CastField(Prop)) - { - if (NumericProperty->IsFloatingPoint()) - SettingsJson->SetNumberField(Name, NumericProperty->GetFloatingPointPropertyValue(ValPtr)); - else - SettingsJson->SetNumberField(Name, NumericProperty->GetSignedIntPropertyValue(ValPtr)); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); - } - } - else if (Field->Type == EJson::Boolean) - { - if (FBoolProperty* BoolProperty = CastField(Prop)) - { - SettingsJson->SetBoolField(Name, BoolProperty->GetPropertyValue(ValPtr)); - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); - } - } - else if (Field->Type == EJson::Array) - { - // Support TArray and TArray - if (FArrayProperty* ArrayProp = ExactCastField(Prop)) - { - // Array of structs -> serialize each element to a JSON object - if (const FStructProperty* InnerStruct = CastField(ArrayProp->Inner)) - { - FScriptArrayHelper Helper(ArrayProp, ValPtr); - TArray> Out; - Out.Reserve(Helper.Num()); - for (int32 i = 0; i < Helper.Num(); ++i) - { - const void* ElemPtr = Helper.GetRawPtr(i); - TSharedPtr Obj = MakeShared(); - InitializeValues(Obj, InnerStruct->Struct, (const uint8*)ElemPtr); - Out.Add(MakeShared(Obj)); - } - SettingsJson->SetArrayField(Name, Out); - *(bool*)RESULT_PARAM = true; - } - // Array of strings (existing behavior) - else if (const FStrProperty* InnerStr = CastField(ArrayProp->Inner)) - { - FScriptArrayHelper Helper(ArrayProp, ValPtr); - TArray> Out; - Out.Reserve(Helper.Num()); - for (int32 i = 0; i < Helper.Num(); ++i) - { - const void* ElemPtr = Helper.GetRawPtr(i); - const FString S = const_cast(InnerStr)->GetPropertyValue(ElemPtr); - Out.Add(MakeShared(S)); - } - SettingsJson->SetArrayField(Name, Out); - *(bool*)RESULT_PARAM = true; - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Unsupported array inner type for '%s'"), *Name)); - *(bool*)RESULT_PARAM = false; - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); - *(bool*)RESULT_PARAM = false; - } - } - else - { - OnError.Broadcast(FString::Printf(TEXT("Unsupported type for property %s"), *Name)); - } - } - P_FINISH; - } + UFUNCTION(BlueprintCallable, Category = "BSettings", CustomThunk, meta = (CustomStructureParam = "OutVal")) + static UPARAM(DisplayName = "Ok") bool GetValueByName(FString Name, int32& OutVal); + DECLARE_FUNCTION(execGetValueByName) { + P_GET_PROPERTY(FStrProperty, Name); + Stack.StepCompiledIn(NULL); + TSharedPtr Field = SettingsJson->TryGetField(Name); + + if (!Field.IsValid()) + { + *(bool*)RESULT_PARAM = false; + OnError.Broadcast(FString::Printf(TEXT("Property '%s' not found"), *Name)); + } + else + { + FProperty* Prop = Stack.MostRecentProperty; + void* ValPtr = Stack.MostRecentPropertyAddress; + *(bool*)RESULT_PARAM = true; + if (FStructProperty* StructProperty = ExactCastField(Prop)) + { + const TSharedPtr* StructJsonObject; + if (Field->TryGetObject(StructJsonObject)) + { + GetNestedStructFromJson(StructProperty->Struct, StructJsonObject->Get(), ValPtr); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Failed to retrieve struct field '%s'"), *Name)); + } + } + else if (FEnumProperty* EnumProperty = ExactCastField(Prop)) + { + UEnum* Enum = GetEnumByPropertyName(Name); + if (Enum) + { + if (EnumProperty->GetEnum() == Enum) + { + FString S; + if (!SettingsJson->TryGetStringField(Name, S)) + { + *(bool*)RESULT_PARAM = false; + OnError.Broadcast(FString::Printf(TEXT("Failed to retrieve enum field '%s'"), *Name)); + } + else + { + int64 Raw; + if (!ResolveEnumValueFromString(Enum, S, Raw)) + { + *(bool*)RESULT_PARAM = false; + OnError.Broadcast(FString::Printf(TEXT("%s is not a valid value of enum %s"), *S, *Enum->GetAuthoredName())); + } + else + { + EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(ValPtr, Raw); + } + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Enum mismatch for property %s"), *Name)); + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("%s is not an Enum"), *Name)); + } + } + else if (FNumericProperty* NumericProperty = CastField(Prop)) + { + if (NumericProperty->IsEnum()) + { + UEnum* Enum = GetEnumByPropertyName(Name); + if (Enum) + { + if (NumericProperty->GetIntPropertyEnum() == Enum) + { + FString S; + if (!SettingsJson->TryGetStringField(Name, S)) + { + *(bool*)RESULT_PARAM = false; + OnError.Broadcast(FString::Printf(TEXT("Failed to retrieve enum field '%s'"), *Name)); + } + else + { + int64 Raw; + if (!ResolveEnumValueFromString(Enum, S, Raw)) + { + *(bool*)RESULT_PARAM = false; + OnError.Broadcast(FString::Printf(TEXT("%s is not a valid value of enum %s"), *S, *Enum->GetAuthoredName())); + } + else + { + NumericProperty->SetIntPropertyValue(ValPtr, Raw); + } + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Enum mismatch for property %s"), *Name)); + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("%s is not an Enum"), *Name)); + } + } + else if (FIntProperty* IntProperty = ExactCastField(Prop)) + { + if (!SettingsJson->TryGetNumberField(Name, *(int32*)ValPtr)) + OnError.Broadcast("Int fail"); + } + else if (FBoolProperty* BoolProperty = ExactCastField(Prop)) + { + if (!SettingsJson->TryGetBoolField(Name, *(bool*)ValPtr)) + OnError.Broadcast("Bool fail"); + } + else if (FDoubleProperty* DoubleProperty = ExactCastField(Prop)) + { + if (!SettingsJson->TryGetNumberField(Name, *(double*)ValPtr)) + OnError.Broadcast("Float fail"); + } + else if (FFloatProperty* FloatProperty = ExactCastField(Prop)) + { + if (!SettingsJson->TryGetNumberField(Name, *(float*)ValPtr)) + OnError.Broadcast("Float fail"); + } + else if (FInt64Property* Int64Property = ExactCastField(Prop)) + { + if (!SettingsJson->TryGetNumberField(Name, *(int64*)ValPtr)) + OnError.Broadcast("Int64 fail"); + } + else if (FByteProperty* ByteProperty = ExactCastField(Prop)) + { + if (!SettingsJson->TryGetNumberField(Name, *(int32*)ValPtr)) + OnError.Broadcast("Byte fail"); + } + } + else if (FStrProperty* StrProperty = ExactCastField(Prop)) + { + if (!SettingsJson->TryGetStringField(Name, *(FString*)ValPtr)) + OnError.Broadcast("String fail"); + } + else if (FBoolProperty* BoolProperty = ExactCastField(Prop)) + { + if (!SettingsJson->TryGetBoolField(Name, *(bool*)ValPtr)) + OnError.Broadcast("Bool fail"); + } + else if (FArrayProperty* ArrayProp = ExactCastField(Prop)) + { + const TArray>* Arr = nullptr; + if (Field->TryGetArray(Arr)) + { + // Struct-Array + if (const FStructProperty* InnerStruct = CastField(ArrayProp->Inner)) + { + FScriptArrayHelper Helper(ArrayProp, ValPtr); + Helper.Resize(Arr->Num()); + for (int32 i = 0; i < Arr->Num(); ++i) + { + void* ElemPtr = Helper.GetRawPtr(i); + const TSharedPtr& Elem = (*Arr)[i]; + const TSharedPtr* ObjPtr = nullptr; + if (Elem.IsValid() && Elem->TryGetObject(ObjPtr)) + { + GetNestedStructFromJson(InnerStruct->Struct, ObjPtr->Get(), ElemPtr); + } + else if (Elem.IsValid() && Elem->Type == EJson::String) + { + FString Raw = Elem->AsString(); + TSharedPtr ParsedObj; + { + TSharedRef> Reader = TJsonReaderFactory<>::Create(Raw); + TSharedPtr TempObj; + if (FJsonSerializer::Deserialize(Reader, TempObj) && TempObj.IsValid()) + ParsedObj = TempObj; + } + if (!ParsedObj.IsValid()) + { + FString Normalized = Raw.Replace(TEXT("'"), TEXT("\"")); + Normalized.ReplaceInline(TEXT(": True"), TEXT(": true")); + Normalized.ReplaceInline(TEXT(":true"), TEXT(": true")); + Normalized.ReplaceInline(TEXT(": False"), TEXT(": false")); + Normalized.ReplaceInline(TEXT(":false"), TEXT(": false")); + TSharedRef> Reader2 = TJsonReaderFactory<>::Create(Normalized); + TSharedPtr TempObj2; + if (FJsonSerializer::Deserialize(Reader2, TempObj2) && TempObj2.IsValid()) + ParsedObj = TempObj2; + } + if (ParsedObj.IsValid()) + GetNestedStructFromJson(InnerStruct->Struct, ParsedObj.Get(), ElemPtr); + else + OnError.Broadcast(FString::Printf(TEXT("Array '%s' element %d is a string but not valid JSON object"), *Name, i)); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Array '%s' element %d is not an object (struct)"), *Name, i)); + } + } + *(bool*)RESULT_PARAM = true; + } + // String-Array + else if (const FStrProperty* InnerStr = CastField(ArrayProp->Inner)) + { + FScriptArrayHelper Helper(ArrayProp, ValPtr); + Helper.Resize(Arr->Num()); + for (int32 i = 0; i < Arr->Num(); ++i) + { + void* ElemPtr = Helper.GetRawPtr(i); + if ((*Arr)[i].IsValid() && (*Arr)[i]->Type == EJson::String) + { + const FString S = (*Arr)[i]->AsString(); + const_cast(InnerStr)->SetPropertyValue(ElemPtr, S); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Array '%s' element %d is not a string"), *Name, i)); + } + } + *(bool*)RESULT_PARAM = true; + } + // FEnumProperty-Array (UE5 modern enum) + else if (FEnumProperty* InnerEnum = CastField(ArrayProp->Inner)) + { + UEnum* Enum = InnerEnum->GetEnum(); + FScriptArrayHelper Helper(ArrayProp, ValPtr); + Helper.Resize(Arr->Num()); + bool bAllOk = true; + for (int32 i = 0; i < Arr->Num(); ++i) + { + void* ElemPtr = Helper.GetRawPtr(i); + const TSharedPtr& Elem = (*Arr)[i]; + if (!Elem.IsValid() || Elem->Type != EJson::String) + { + OnError.Broadcast(FString::Printf(TEXT("Array '%s' element %d is not a string (enum)"), *Name, i)); + bAllOk = false; + continue; + } + const FString S = Elem->AsString().TrimStartAndEnd(); + int64 Raw; + if (!ResolveEnumValueFromString(Enum, S, Raw)) + { + OnError.Broadcast(FString::Printf(TEXT("'%s' is not a valid value of enum %s"), *S, *Enum->GetAuthoredName())); + bAllOk = false; + } + else + { + InnerEnum->GetUnderlyingProperty()->SetIntPropertyValue(ElemPtr, Raw); + } + } + *(bool*)RESULT_PARAM = bAllOk; + } + // FNumericProperty-Array mit Byte-Enum + else if (FNumericProperty* InnerNum = CastField(ArrayProp->Inner)) + { + if (InnerNum->IsEnum()) + { + UEnum* Enum = InnerNum->GetIntPropertyEnum(); + FScriptArrayHelper Helper(ArrayProp, ValPtr); + Helper.Resize(Arr->Num()); + bool bAllOk = true; + for (int32 i = 0; i < Arr->Num(); ++i) + { + void* ElemPtr = Helper.GetRawPtr(i); + const TSharedPtr& Elem = (*Arr)[i]; + if (!Elem.IsValid() || Elem->Type != EJson::String) + { + OnError.Broadcast(FString::Printf(TEXT("Array '%s' element %d is not a string (byte-enum)"), *Name, i)); + bAllOk = false; + continue; + } + const FString S = Elem->AsString().TrimStartAndEnd(); + int64 Raw; + if (!ResolveEnumValueFromString(Enum, S, Raw)) + { + OnError.Broadcast(FString::Printf(TEXT("'%s' is not a valid value of enum %s"), *S, *Enum->GetAuthoredName())); + bAllOk = false; + } + else + { + InnerNum->SetIntPropertyValue(ElemPtr, Raw); + } + } + *(bool*)RESULT_PARAM = bAllOk; + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Unsupported array inner type for '%s'"), *Name)); + *(bool*)RESULT_PARAM = false; + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Unsupported array inner type for '%s'"), *Name)); + *(bool*)RESULT_PARAM = false; + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Failed to read array field '%s'"), *Name)); + *(bool*)RESULT_PARAM = false; + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Unsupported type on prop %s"), *Name)); + } + } + P_FINISH; + } + + UFUNCTION(BlueprintCallable, Category = "BSettings", CustomThunk, meta = (CustomStructureParam = "InVal")) + static UPARAM(DisplayName = "Ok") bool SetValueByName(FString Name, const int32& InVal); + DECLARE_FUNCTION(execSetValueByName) { + P_GET_PROPERTY(FStrProperty, Name); + Stack.StepCompiledIn(NULL); + TSharedPtr Field = SettingsJson->TryGetField(Name); + if (!Field.IsValid()) + { + *(bool*)RESULT_PARAM = false; + OnError.Broadcast(FString::Printf(TEXT("Property '%s' not found"), *Name)); + } + else + { + FProperty* Prop = Stack.MostRecentProperty; + void* ValPtr = Stack.MostRecentPropertyAddress; + if (Field->Type == EJson::Object) + { + if (FStructProperty* StructProperty = ExactCastField(Prop)) + { + const TSharedPtr* StructJsonObject; + if (Field->TryGetObject(StructJsonObject)) + { + InitializeValues(*StructJsonObject, StructProperty->Struct, (const uint8*)ValPtr); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Failed to retrieve struct field '%s'"), *Name)); + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); + } + } + else if (Field->Type == EJson::String) + { + if (UEnum* SettingsEnum = GetEnumByPropertyName(Name)) + { + if (FEnumProperty* EnumProperty = ExactCastField(Prop)) + { + if (EnumProperty->GetEnum() == SettingsEnum) + { + const int64 Raw = EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(ValPtr); + FString OutStr; + if (const UUserDefinedEnum* UD = Cast(SettingsEnum)) + { + const FName Key = SettingsEnum->GetNameByValue(Raw); + if (const FText* P = UD->DisplayNameMap.Find(Key)) OutStr = P->ToString(); + } + if (OutStr.IsEmpty()) OutStr = SettingsEnum->GetDisplayNameTextByValue(Raw).ToString(); + if (OutStr.IsEmpty()) OutStr = SettingsEnum->GetNameStringByValue(Raw); + SettingsJson->SetStringField(Name, OutStr); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Enum mismatch for property %s"), *Name)); + } + } + else if (FNumericProperty* NumericProperty = CastField(Prop)) + { + if (!NumericProperty->IsFloatingPoint()) + { + const int64 Raw = NumericProperty->GetSignedIntPropertyValue(ValPtr); + if (SettingsEnum->IsValidEnumValue(Raw)) + { + FString OutStr; + if (const UUserDefinedEnum* UD = Cast(SettingsEnum)) + { + const FName Key = SettingsEnum->GetNameByValue(Raw); + if (const FText* P = UD->DisplayNameMap.Find(Key)) OutStr = P->ToString(); + } + if (OutStr.IsEmpty()) OutStr = SettingsEnum->GetDisplayNameTextByValue(Raw).ToString(); + if (OutStr.IsEmpty()) OutStr = SettingsEnum->GetNameStringByValue(Raw); + SettingsJson->SetStringField(Name, OutStr); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("%lld is not a valid value of enum %s"), Raw, *SettingsEnum->GetAuthoredName())); + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); + } + } + else if (FStrProperty* StrProperty = CastField(Prop)) + { + SettingsJson->SetStringField(Name, StrProperty->GetPropertyValue(ValPtr)); + } + else if (FTextProperty* TextProperty = CastField(Prop)) + { + SettingsJson->SetStringField(Name, TextProperty->GetPropertyValue(ValPtr).ToString()); + } + else if (FNameProperty* NameProperty = CastField(Prop)) + { + SettingsJson->SetStringField(Name, NameProperty->GetPropertyValue(ValPtr).ToString()); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); + } + } + else if (Field->Type == EJson::Number) + { + if (FNumericProperty* NumericProperty = CastField(Prop)) + { + if (NumericProperty->IsFloatingPoint()) + SettingsJson->SetNumberField(Name, NumericProperty->GetFloatingPointPropertyValue(ValPtr)); + else + SettingsJson->SetNumberField(Name, NumericProperty->GetSignedIntPropertyValue(ValPtr)); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); + } + } + else if (Field->Type == EJson::Boolean) + { + if (FBoolProperty* BoolProperty = CastField(Prop)) + { + SettingsJson->SetBoolField(Name, BoolProperty->GetPropertyValue(ValPtr)); + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); + } + } + else if (Field->Type == EJson::Array) + { + if (FArrayProperty* ArrayProp = ExactCastField(Prop)) + { + // Struct-Array + if (const FStructProperty* InnerStruct = CastField(ArrayProp->Inner)) + { + FScriptArrayHelper Helper(ArrayProp, ValPtr); + TArray> Out; + Out.Reserve(Helper.Num()); + for (int32 i = 0; i < Helper.Num(); ++i) + { + const void* ElemPtr = Helper.GetRawPtr(i); + TSharedPtr Obj = MakeShared(); + InitializeValues(Obj, InnerStruct->Struct, (const uint8*)ElemPtr); + Out.Add(MakeShared(Obj)); + } + SettingsJson->SetArrayField(Name, Out); + *(bool*)RESULT_PARAM = true; + } + // String-Array + else if (const FStrProperty* InnerStr = CastField(ArrayProp->Inner)) + { + FScriptArrayHelper Helper(ArrayProp, ValPtr); + TArray> Out; + Out.Reserve(Helper.Num()); + for (int32 i = 0; i < Helper.Num(); ++i) + { + const void* ElemPtr = Helper.GetRawPtr(i); + const FString S = const_cast(InnerStr)->GetPropertyValue(ElemPtr); + Out.Add(MakeShared(S)); + } + SettingsJson->SetArrayField(Name, Out); + *(bool*)RESULT_PARAM = true; + } + // FEnumProperty-Array (UE5 modern enum) + else if (FEnumProperty* InnerEnum = CastField(ArrayProp->Inner)) + { + UEnum* Enum = InnerEnum->GetEnum(); + FScriptArrayHelper Helper(ArrayProp, ValPtr); + TArray> Out; + Out.Reserve(Helper.Num()); + for (int32 i = 0; i < Helper.Num(); ++i) + { + void* ElemPtr = Helper.GetRawPtr(i); + const int64 Raw = InnerEnum->GetUnderlyingProperty()->GetSignedIntPropertyValue(ElemPtr); + FString OutStr; + if (const UUserDefinedEnum* UD = Cast(Enum)) + { + const FName Key = Enum->GetNameByValue(Raw); + if (const FText* P = UD->DisplayNameMap.Find(Key)) OutStr = P->ToString(); + } + if (OutStr.IsEmpty()) OutStr = Enum->GetDisplayNameTextByValue(Raw).ToString(); + if (OutStr.IsEmpty()) OutStr = Enum->GetNameStringByValue(Raw); + Out.Add(MakeShared(OutStr)); + } + SettingsJson->SetArrayField(Name, Out); + *(bool*)RESULT_PARAM = true; + } + // FNumericProperty-Array mit Byte-Enum + else if (FNumericProperty* InnerNum = CastField(ArrayProp->Inner)) + { + if (InnerNum->IsEnum()) + { + UEnum* Enum = InnerNum->GetIntPropertyEnum(); + FScriptArrayHelper Helper(ArrayProp, ValPtr); + TArray> Out; + Out.Reserve(Helper.Num()); + for (int32 i = 0; i < Helper.Num(); ++i) + { + void* ElemPtr = Helper.GetRawPtr(i); + const int64 Raw = InnerNum->GetSignedIntPropertyValue(ElemPtr); + FString OutStr = Enum->GetDisplayNameTextByValue(Raw).ToString(); + if (OutStr.IsEmpty()) OutStr = Enum->GetNameStringByValue(Raw); + Out.Add(MakeShared(OutStr)); + } + SettingsJson->SetArrayField(Name, Out); + *(bool*)RESULT_PARAM = true; + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Unsupported array inner type for '%s'"), *Name)); + *(bool*)RESULT_PARAM = false; + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Unsupported array inner type for '%s'"), *Name)); + *(bool*)RESULT_PARAM = false; + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name)); + *(bool*)RESULT_PARAM = false; + } + } + else + { + OnError.Broadcast(FString::Printf(TEXT("Unsupported type for property %s"), *Name)); + } + } + P_FINISH; + } // GetEncryptedStringByName: Reads a JSON string, decrypts it using the provided key, // and returns the plain text. @@ -702,8 +759,10 @@ public: virtual void Deinitialize() override; private: + static bool ResolveEnumValueFromString(const UEnum* Enum, const FString& InString, int64& OutValue); static FString GetSchemaType(const FProperty* Prop); static void CollectEnumValues(const FProperty* Prop, TArray& OutValues); + static TSharedPtr BuildFieldSchema(const FProperty* Prop); static TSharedPtr RotatorToJson(const FRotator& R); static bool JsonToRotator(const FJsonObject& Obj, FRotator& Out);