Browse Source

Merge remote-tracking branch 'origin/master'

master
Timo Hilger 10 hours ago
parent
commit
d834d9fb25
  1. BIN
      Unreal/Content/Project/BP/BP_Project_Manager.uasset
  2. 2
      Unreal/Plugins/AvatarCore_AI/Source/AvatarCore_AI/Public/AIBaseManager.h
  3. BIN
      Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_CreateAvatar.uasset
  4. BIN
      Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_MetahumanToAvatar.uasset
  5. BIN
      Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_UpdateAvatar.uasset
  6. 31
      Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp
  7. 4
      Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp
  8. 372
      Unreal/Plugins/BSettings/Source/BSettings/Private/BSettingsSystem.cpp
  9. 269
      Unreal/Plugins/BSettings/Source/BSettings/Public/BSettingsSystem.h

BIN
Unreal/Content/Project/BP/BP_Project_Manager.uasset (Stored with Git LFS)

Binary file not shown.

2
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();
/**

BIN
Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_CreateAvatar.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_MetahumanToAvatar.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_MetaHuman/Content/BP/EditorUtility/EUS_UpdateAvatar.uasset (Stored with Git LFS)

Binary file not shown.

31
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,10 +114,11 @@ uint32 FAzureRunnable::Run()
if (ConnectionTestOnly) {
//TODO Check if Api is correct
AsyncTask(ENamedThreads::GameThread, [this] {
if (Owner != nullptr)
TWeakObjectPtr<USTTProcessorAzure> WeakOwnerLocal(Owner);
AsyncTask(ENamedThreads::GameThread, [WeakOwnerLocal] {
if (WeakOwnerLocal.IsValid())
{
Owner->OnConnectionSuccess();
WeakOwnerLocal->OnConnectionSuccess();
}
});
Stop();
@ -142,15 +144,8 @@ uint32 FAzureRunnable::Run()
}
Recognizer->StopContinuousRecognitionAsync().get();
// Use a weak pointer to Owner to avoid accessing this after destruction
TWeakObjectPtr<USTTProcessorAzure> 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<USTTProcessorAzure> 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<int16>& PCMData)

4
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;
}
if(bDebugMode && STTManager!=nullptr)
STTManager->OnSTTLog.Broadcast(TEXT("Recognition thread stopped."));
}

372
Unreal/Plugins/BSettings/Source/BSettings/Private/BSettingsSystem.cpp

@ -339,7 +339,7 @@ void UBSettingsSystem::InitializeValues(TSharedPtr<FJsonObject> 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<FString>&
}
}
// 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<FJsonObject> UBSettingsSystem::BuildFieldSchema(const FProperty* Prop)
{
TSharedPtr<FJsonObject> FieldObj = MakeShared<FJsonObject>();
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<FString> EnumValues;
CollectEnumValues(Prop, EnumValues);
TArray<TSharedPtr<FJsonValue>> JsonEnumVals;
for (const FString& Val : EnumValues)
{
JsonEnumVals.Add(MakeShared<FJsonValueString>(Val));
}
FieldObj->SetArrayField(TEXT("enum"), JsonEnumVals);
}
// Struct: recurse into sub-fields
if (TypeStr == TEXT("struct"))
{
if (const FStructProperty* StructProp = CastField<FStructProperty>(Prop))
{
TSharedPtr<FJsonObject> FieldsObj = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> 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<FArrayProperty>(Prop))
{
const FProperty* Inner = ArrProp->Inner;
const FString InnerType = GetSchemaType(Inner);
FieldObj->SetStringField(TEXT("itemsType"), InnerType);
if (InnerType == TEXT("enum"))
{
TArray<FString> EnumValues;
CollectEnumValues(Inner, EnumValues);
TArray<TSharedPtr<FJsonValue>> JsonEnumVals;
for (const FString& Val : EnumValues)
{
JsonEnumVals.Add(MakeShared<FJsonValueString>(Val));
}
FieldObj->SetArrayField(TEXT("itemsEnum"), JsonEnumVals);
}
else if (InnerType == TEXT("struct"))
{
if (const FStructProperty* InnerStructProp = CastField<FStructProperty>(Inner))
{
TSharedPtr<FJsonObject> ItemsFieldsObj = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> 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<FJsonObject> FieldObj = BuildFieldSchema(Prop);
// Build JSON object for this variable (holds its attributes)
TSharedPtr<FJsonObject> FieldObj = MakeShared<FJsonObject>();
FieldObj->SetStringField(TEXT("type"), TypeStr);
// Export default value
// Top-level only: export default value
if (TSharedPtr<FJsonValue> 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<FStructProperty>(Prop))
{
TSharedPtr<FJsonObject> FieldsObj = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> 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<FJsonObject> SubFieldObj = MakeShared<FJsonObject>();
SubFieldObj->SetStringField(TEXT("type"), SubType);
// --- Enums als direkte Struct-Subfelder (optional aber konsistent)
if (SubType == TEXT("enum"))
{
TArray<FString> EnumValues;
CollectEnumValues(SubProp, EnumValues);
TArray<TSharedPtr<FJsonValue>> JsonEnumVals;
for (const FString& V : EnumValues)
{
JsonEnumVals.Add(MakeShared<FJsonValueString>(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<FStructProperty>(SubProp))
{
TSharedPtr<FJsonObject> NestedFieldsObj = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> 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<FJsonObject> NFieldObj = MakeShared<FJsonObject>();
NFieldObj->SetStringField(TEXT("type"), NType);
// Arrays innerhalb des verschachtelten Structs (1 Ebene)
if (NType == TEXT("array"))
{
if (const FArrayProperty* DeepArr = CastField<FArrayProperty>(NProp))
{
const FProperty* DeepInner = DeepArr->Inner;
const FString DeepInnerType = GetSchemaType(DeepInner);
NFieldObj->SetStringField(TEXT("itemsType"), DeepInnerType);
if (DeepInnerType == TEXT("enum"))
{
TArray<FString> EV;
CollectEnumValues(DeepInner, EV);
TArray<TSharedPtr<FJsonValue>> JV;
for (const FString& e : EV) JV.Add(MakeShared<FJsonValueString>(e));
NFieldObj->SetArrayField(TEXT("itemsEnum"), JV);
}
else if (DeepInnerType == TEXT("struct"))
{
if (const FStructProperty* DeepInnerStruct = CastField<FStructProperty>(DeepInner))
{
TSharedPtr<FJsonObject> ItemsFieldsObj = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> 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<FJsonObject> SObj = MakeShared<FJsonObject>();
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<FArrayProperty>(SubProp))
{
const FProperty* Inner = SubArr->Inner;
const FString InnerType = GetSchemaType(Inner);
SubFieldObj->SetStringField(TEXT("itemsType"), InnerType);
if (InnerType == TEXT("enum"))
{
TArray<FString> EnumValues;
CollectEnumValues(Inner, EnumValues);
TArray<TSharedPtr<FJsonValue>> JsonEnumVals;
for (const FString& V : EnumValues)
{
JsonEnumVals.Add(MakeShared<FJsonValueString>(V));
}
SubFieldObj->SetArrayField(TEXT("itemsEnum"), JsonEnumVals);
}
else if (InnerType == TEXT("struct"))
{
if (const FStructProperty* InnerStructProp = CastField<FStructProperty>(Inner))
{
TSharedPtr<FJsonObject> ItemsFieldsObj = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> 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<FJsonObject> SObj = MakeShared<FJsonObject>();
SObj->SetStringField(TEXT("type"), SType);
// Arrays innerhalb der Item-Structs (1 Ebene)
if (SType == TEXT("array"))
{
if (const FArrayProperty* DeepArr = CastField<FArrayProperty>(SProp))
{
const FProperty* DeepInner = DeepArr->Inner;
const FString DeepInnerType = GetSchemaType(DeepInner);
SObj->SetStringField(TEXT("itemsType"), DeepInnerType);
if (DeepInnerType == TEXT("enum"))
{
TArray<FString> EV;
CollectEnumValues(DeepInner, EV);
TArray<TSharedPtr<FJsonValue>> JV;
for (const FString& e : EV) JV.Add(MakeShared<FJsonValueString>(e));
SObj->SetArrayField(TEXT("itemsEnum"), JV);
}
else if (DeepInnerType == TEXT("struct"))
{
if (const FStructProperty* DeepInnerStruct = CastField<FStructProperty>(DeepInner))
{
TSharedPtr<FJsonObject> ItemsFields2 = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> S2It(DeepInnerStruct->Struct); S2It; ++S2It)
{
const FProperty* S2Prop = *S2It;
if (!S2Prop || IsSchemaCategoryProperty(S2Prop)) continue;
TSharedPtr<FJsonObject> S2Obj = MakeShared<FJsonObject>();
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<FString> EnumValues;
CollectEnumValues(Prop, EnumValues);
TArray<TSharedPtr<FJsonValue>> JsonEnumVals;
for (const FString& Val : EnumValues)
{
JsonEnumVals.Add(MakeShared<FJsonValueString>(Val));
}
FieldObj->SetArrayField(TEXT("enum"), JsonEnumVals);
}
// Arrays: determine item type and add additional metadata
if (TypeStr == TEXT("array"))
{
if (const FArrayProperty* ArrProp = CastField<FArrayProperty>(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<FString> EnumValues;
CollectEnumValues(Inner, EnumValues);
TArray<TSharedPtr<FJsonValue>> JsonEnumVals;
for (const FString& V : EnumValues)
{
JsonEnumVals.Add(MakeShared<FJsonValueString>(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<FStructProperty>(Inner))
{
TSharedPtr<FJsonObject> ItemsFieldsObj = MakeShared<FJsonObject>();
// Same as for single struct fields, but nested under itemsFields
for (TFieldIterator<FProperty> SubIt(InnerStructProp->Struct); SubIt; ++SubIt)
{
const FProperty* SubProp = *SubIt;
const FString SubName = SubProp->GetAuthoredName();
const FString SubType = GetSchemaType(SubProp);
TSharedPtr<FJsonObject> SubFieldObj = MakeShared<FJsonObject>();
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<FJsonObject> Wrapper = MakeShared<FJsonObject>();

269
Unreal/Plugins/BSettings/Source/BSettings/Public/BSettingsSystem.h

@ -140,8 +140,8 @@ public:
}
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
FProperty* Prop = Stack.MostRecentProperty;
void* ValPtr = Stack.MostRecentPropertyAddress;
*(bool*)RESULT_PARAM = true;
if (FStructProperty* StructProperty = ExactCastField<FStructProperty>(Prop))
{
@ -170,51 +170,15 @@ public:
}
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<UUserDefinedEnum>(Enum))
{
const FString Trimmed = S.TrimStartAndEnd();
for (const TTuple<FName, FText>& 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)
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
{
FNumericProperty* Underlying = EnumProperty->GetUnderlyingProperty();
Underlying->SetIntPropertyValue(ValPtr, Raw);
EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(ValPtr, Raw);
}
}
}
@ -245,43 +209,8 @@ public:
}
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<UUserDefinedEnum>(Enum))
{
const FString Trimmed = S.TrimStartAndEnd();
for (const TTuple<FName, FText>& 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)
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()));
@ -329,7 +258,6 @@ public:
}
else if (FByteProperty* ByteProperty = ExactCastField<FByteProperty>(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");
}
@ -349,7 +277,7 @@ public:
const TArray<TSharedPtr<FJsonValue>>* Arr = nullptr;
if (Field->TryGetArray(Arr))
{
// Handle arrays of user-defined structs
// Struct-Array
if (const FStructProperty* InnerStruct = CastField<FStructProperty>(ArrayProp->Inner))
{
FScriptArrayHelper Helper(ArrayProp, ValPtr);
@ -365,20 +293,16 @@ public:
}
else if (Elem.IsValid() && Elem->Type == EJson::String)
{
// Try to parse stringified JSON object
FString Raw = Elem->AsString();
TSharedPtr<FJsonObject> ParsedObj;
{
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Raw);
TSharedPtr<FJsonObject> 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"));
@ -387,19 +311,13 @@ public:
TSharedRef<TJsonReader<>> Reader2 = TJsonReaderFactory<>::Create(Normalized);
TSharedPtr<FJsonObject> 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));
@ -407,7 +325,7 @@ public:
}
*(bool*)RESULT_PARAM = true;
}
// Handle arrays of strings (existing behavior)
// String-Array
else if (const FStrProperty* InnerStr = CastField<FStrProperty>(ArrayProp->Inner))
{
FScriptArrayHelper Helper(ArrayProp, ValPtr);
@ -427,6 +345,76 @@ public:
}
*(bool*)RESULT_PARAM = true;
}
// FEnumProperty-Array (UE5 modern enum)
else if (FEnumProperty* InnerEnum = CastField<FEnumProperty>(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<FJsonValue>& 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<FNumericProperty>(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<FJsonValue>& 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));
@ -460,8 +448,8 @@ public:
}
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
FProperty* Prop = Stack.MostRecentProperty;
void* ValPtr = Stack.MostRecentPropertyAddress;
if (Field->Type == EJson::Object)
{
if (FStructProperty* StructProperty = ExactCastField<FStructProperty>(Prop))
@ -483,32 +471,53 @@ public:
}
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<FEnumProperty>(Prop))
{
if (EnumProperty->GetEnum() == SettingsEnum)
{
SettingsJson->SetStringField(Name, SettingsEnum->GetNameStringByIndex(*(int8*)ValPtr));
const int64 Raw = EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(ValPtr);
FString OutStr;
if (const UUserDefinedEnum* UD = Cast<UUserDefinedEnum>(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));
}
}
// otherwise check if InVal is numerical
else if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Prop))
{
// check if InVal is a valid value of the respective enum
if (!NumericProperty->IsFloatingPoint() && 0 <= *(uint8*)ValPtr && *(uint8*)ValPtr < SettingsEnum->GetMaxEnumValue())
if (!NumericProperty->IsFloatingPoint())
{
SettingsJson->SetStringField(Name, SettingsEnum->GetNameStringByIndex(*(int8*)ValPtr));
const int64 Raw = NumericProperty->GetSignedIntPropertyValue(ValPtr);
if (SettingsEnum->IsValidEnumValue(Raw))
{
FString OutStr;
if (const UUserDefinedEnum* UD = Cast<UUserDefinedEnum>(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("%d is not a valid value of enum %s"), *(int32*)ValPtr, *SettingsEnum->GetAuthoredName()));
OnError.Broadcast(FString::Printf(TEXT("Type mismatch for property %s"), *Name));
}
}
else
@ -535,7 +544,6 @@ public:
}
else if (Field->Type == EJson::Number)
{
if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Prop))
{
if (NumericProperty->IsFloatingPoint())
@ -561,10 +569,9 @@ public:
}
else if (Field->Type == EJson::Array)
{
// Support TArray<FString> and TArray<Struct>
if (FArrayProperty* ArrayProp = ExactCastField<FArrayProperty>(Prop))
{
// Array of structs -> serialize each element to a JSON object
// Struct-Array
if (const FStructProperty* InnerStruct = CastField<FStructProperty>(ArrayProp->Inner))
{
FScriptArrayHelper Helper(ArrayProp, ValPtr);
@ -580,7 +587,7 @@ public:
SettingsJson->SetArrayField(Name, Out);
*(bool*)RESULT_PARAM = true;
}
// Array of strings (existing behavior)
// String-Array
else if (const FStrProperty* InnerStr = CastField<FStrProperty>(ArrayProp->Inner))
{
FScriptArrayHelper Helper(ArrayProp, ValPtr);
@ -595,6 +602,56 @@ public:
SettingsJson->SetArrayField(Name, Out);
*(bool*)RESULT_PARAM = true;
}
// FEnumProperty-Array (UE5 modern enum)
else if (FEnumProperty* InnerEnum = CastField<FEnumProperty>(ArrayProp->Inner))
{
UEnum* Enum = InnerEnum->GetEnum();
FScriptArrayHelper Helper(ArrayProp, ValPtr);
TArray<TSharedPtr<FJsonValue>> 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<UUserDefinedEnum>(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<FJsonValueString>(OutStr));
}
SettingsJson->SetArrayField(Name, Out);
*(bool*)RESULT_PARAM = true;
}
// FNumericProperty-Array mit Byte-Enum
else if (FNumericProperty* InnerNum = CastField<FNumericProperty>(ArrayProp->Inner))
{
if (InnerNum->IsEnum())
{
UEnum* Enum = InnerNum->GetIntPropertyEnum();
FScriptArrayHelper Helper(ArrayProp, ValPtr);
TArray<TSharedPtr<FJsonValue>> 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<FJsonValueString>(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));
@ -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<FString>& OutValues);
static TSharedPtr<FJsonObject> BuildFieldSchema(const FProperty* Prop);
static TSharedPtr<FJsonObject> RotatorToJson(const FRotator& R);
static bool JsonToRotator(const FJsonObject& Obj, FRotator& Out);

Loading…
Cancel
Save