flockbay 0.10.15 → 0.10.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/codex/flockbayMcpStdioBridge.cjs +339 -0
  2. package/dist/codex/flockbayMcpStdioBridge.mjs +339 -0
  3. package/dist/{index--o4BPz5o.cjs → index-Cau-_Qvn.cjs} +2683 -609
  4. package/dist/{index-CUp3juDS.mjs → index-DtmFQzXY.mjs} +2684 -611
  5. package/dist/index.cjs +3 -5
  6. package/dist/index.mjs +3 -5
  7. package/dist/lib.cjs +7 -9
  8. package/dist/lib.d.cts +219 -531
  9. package/dist/lib.d.mts +219 -531
  10. package/dist/lib.mjs +7 -9
  11. package/dist/{runCodex-o6PCbHQ7.mjs → runCodex-Di9eHddq.mjs} +263 -42
  12. package/dist/{runCodex-D3eT-TvB.cjs → runCodex-DzP3VUa-.cjs} +264 -43
  13. package/dist/{runGemini-Bt0oEj_g.mjs → runGemini-BS6sBU_V.mjs} +63 -28
  14. package/dist/{runGemini-CBxZp6I7.cjs → runGemini-CpmehDQ2.cjs} +64 -29
  15. package/dist/{types-DGd6ea2Z.mjs → types-CwzNqYEx.mjs} +465 -1142
  16. package/dist/{types-C-jnUdn_.cjs → types-SUAKq-K0.cjs} +466 -1146
  17. package/package.json +1 -1
  18. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp +195 -6
  19. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp +376 -5
  20. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommandSchema.cpp +731 -0
  21. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp +476 -8
  22. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +1518 -94
  23. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp +7 -4
  24. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp +150 -112
  25. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h +2 -1
  26. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h +4 -1
  27. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommandSchema.h +42 -0
  28. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h +21 -0
  29. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs +4 -1
  30. package/dist/flockbayScreenshotGate-DJX3Is5d.mjs +0 -136
  31. package/dist/flockbayScreenshotGate-DkxU24cR.cjs +0 -138
@@ -16,6 +16,7 @@
16
16
  #include "Components/LightComponent.h"
17
17
  #include "Components/PrimitiveComponent.h"
18
18
  #include "Components/SceneComponent.h"
19
+ #include "UObject/UnrealType.h"
19
20
  #include "UObject/UObjectIterator.h"
20
21
  #include "Engine/Selection.h"
21
22
  #include "EditorAssetLibrary.h"
@@ -95,6 +96,19 @@ FVector2D FUnrealMCPCommonUtils::GetVector2DFromJson(const TSharedPtr<FJsonObjec
95
96
  return Result;
96
97
  }
97
98
 
99
+ // Accept either [x,y] or {x,y}.
100
+ const TSharedPtr<FJsonObject>* ObjValue = nullptr;
101
+ if (JsonObject->TryGetObjectField(FieldName, ObjValue) && ObjValue && ObjValue->IsValid())
102
+ {
103
+ double X = 0.0;
104
+ double Y = 0.0;
105
+ (*ObjValue)->TryGetNumberField(TEXT("x"), X) || (*ObjValue)->TryGetNumberField(TEXT("X"), X);
106
+ (*ObjValue)->TryGetNumberField(TEXT("y"), Y) || (*ObjValue)->TryGetNumberField(TEXT("Y"), Y);
107
+ Result.X = (float)X;
108
+ Result.Y = (float)Y;
109
+ return Result;
110
+ }
111
+
98
112
  const TArray<TSharedPtr<FJsonValue>>* JsonArray;
99
113
  if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 2)
100
114
  {
@@ -114,6 +128,22 @@ FVector FUnrealMCPCommonUtils::GetVectorFromJson(const TSharedPtr<FJsonObject>&
114
128
  return Result;
115
129
  }
116
130
 
131
+ // Accept either [x,y,z] or {x,y,z}.
132
+ const TSharedPtr<FJsonObject>* ObjValue = nullptr;
133
+ if (JsonObject->TryGetObjectField(FieldName, ObjValue) && ObjValue && ObjValue->IsValid())
134
+ {
135
+ double X = 0.0;
136
+ double Y = 0.0;
137
+ double Z = 0.0;
138
+ ((*ObjValue)->TryGetNumberField(TEXT("x"), X) || (*ObjValue)->TryGetNumberField(TEXT("X"), X));
139
+ ((*ObjValue)->TryGetNumberField(TEXT("y"), Y) || (*ObjValue)->TryGetNumberField(TEXT("Y"), Y));
140
+ ((*ObjValue)->TryGetNumberField(TEXT("z"), Z) || (*ObjValue)->TryGetNumberField(TEXT("Z"), Z));
141
+ Result.X = (float)X;
142
+ Result.Y = (float)Y;
143
+ Result.Z = (float)Z;
144
+ return Result;
145
+ }
146
+
117
147
  const TArray<TSharedPtr<FJsonValue>>* JsonArray;
118
148
  if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 3)
119
149
  {
@@ -134,6 +164,22 @@ FRotator FUnrealMCPCommonUtils::GetRotatorFromJson(const TSharedPtr<FJsonObject>
134
164
  return Result;
135
165
  }
136
166
 
167
+ // Accept either [pitch,yaw,roll] or {pitch,yaw,roll}.
168
+ const TSharedPtr<FJsonObject>* ObjValue = nullptr;
169
+ if (JsonObject->TryGetObjectField(FieldName, ObjValue) && ObjValue && ObjValue->IsValid())
170
+ {
171
+ double Pitch = 0.0;
172
+ double Yaw = 0.0;
173
+ double Roll = 0.0;
174
+ ((*ObjValue)->TryGetNumberField(TEXT("pitch"), Pitch) || (*ObjValue)->TryGetNumberField(TEXT("Pitch"), Pitch));
175
+ ((*ObjValue)->TryGetNumberField(TEXT("yaw"), Yaw) || (*ObjValue)->TryGetNumberField(TEXT("Yaw"), Yaw));
176
+ ((*ObjValue)->TryGetNumberField(TEXT("roll"), Roll) || (*ObjValue)->TryGetNumberField(TEXT("Roll"), Roll));
177
+ Result.Pitch = (float)Pitch;
178
+ Result.Yaw = (float)Yaw;
179
+ Result.Roll = (float)Roll;
180
+ return Result;
181
+ }
182
+
137
183
  const TArray<TSharedPtr<FJsonValue>>* JsonArray;
138
184
  if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 3)
139
185
  {
@@ -153,8 +199,34 @@ UBlueprint* FUnrealMCPCommonUtils::FindBlueprint(const FString& BlueprintName)
153
199
 
154
200
  UBlueprint* FUnrealMCPCommonUtils::FindBlueprintByName(const FString& BlueprintName)
155
201
  {
156
- FString AssetPath = TEXT("/Game/Blueprints/") + BlueprintName;
157
- return LoadObject<UBlueprint>(nullptr, *AssetPath);
202
+ FString Trimmed = BlueprintName.TrimStartAndEnd();
203
+ if (Trimmed.IsEmpty())
204
+ {
205
+ return nullptr;
206
+ }
207
+
208
+ // Allow callers to pass either a simple asset name (BP_MyAsset),
209
+ // or a full object path (/Game/Path/BP_MyAsset.BP_MyAsset),
210
+ // or a Blueprint'...' soft path.
211
+ if (Trimmed.StartsWith(TEXT("/")) || Trimmed.StartsWith(TEXT("Blueprint'")))
212
+ {
213
+ UObject* Obj = LoadObject<UObject>(nullptr, *Trimmed);
214
+ if (!Obj && Trimmed.StartsWith(TEXT("/")) && !Trimmed.Contains(TEXT("'")))
215
+ {
216
+ Obj = LoadObject<UObject>(nullptr, *FString::Printf(TEXT("Blueprint'%s'"), *Trimmed));
217
+ }
218
+ if (UBlueprint* BP = Cast<UBlueprint>(Obj)) return BP;
219
+ if (UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(Obj))
220
+ {
221
+ return Cast<UBlueprint>(BPGC->ClassGeneratedBy);
222
+ }
223
+ return nullptr;
224
+ }
225
+
226
+ FString AssetPath = TEXT("/Game/Blueprints/") + Trimmed;
227
+ if (UBlueprint* BP = LoadObject<UBlueprint>(nullptr, *AssetPath)) return BP;
228
+ if (UBlueprint* BP = LoadObject<UBlueprint>(nullptr, *FString::Printf(TEXT("Blueprint'%s'"), *AssetPath))) return BP;
229
+ return nullptr;
158
230
  }
159
231
 
160
232
  UEdGraph* FUnrealMCPCommonUtils::FindOrCreateEventGraph(UBlueprint* Blueprint)
@@ -463,6 +535,10 @@ TSharedPtr<FJsonObject> FUnrealMCPCommonUtils::ActorToJsonObject(AActor* Actor,
463
535
  TSharedPtr<FJsonObject> ActorObject = MakeShared<FJsonObject>();
464
536
  ActorObject->SetStringField(TEXT("name"), Actor->GetName());
465
537
  ActorObject->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
538
+
539
+ #if WITH_EDITOR
540
+ ActorObject->SetStringField(TEXT("label"), Actor->GetActorLabel());
541
+ #endif
466
542
 
467
543
  FVector Location = Actor->GetActorLocation();
468
544
  TArray<TSharedPtr<FJsonValue>> LocationArray;
@@ -484,7 +560,100 @@ TSharedPtr<FJsonObject> FUnrealMCPCommonUtils::ActorToJsonObject(AActor* Actor,
484
560
  ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Y));
485
561
  ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Z));
486
562
  ActorObject->SetArrayField(TEXT("scale"), ScaleArray);
487
-
563
+
564
+ if (!bDetailed)
565
+ {
566
+ return ActorObject;
567
+ }
568
+
569
+ const auto isEditableProperty = [](const FProperty* Prop) -> bool {
570
+ if (!Prop) return false;
571
+ if (!Prop->HasAnyPropertyFlags(CPF_Edit)) return false;
572
+ if (Prop->HasAnyPropertyFlags(CPF_Transient | CPF_Deprecated | CPF_DisableEditOnInstance)) return false;
573
+ return true;
574
+ };
575
+
576
+ const auto addPropertyDescriptors = [&](UObject* Obj, int32 Limit, TArray<TSharedPtr<FJsonValue>>& OutProps) {
577
+ if (!Obj) return;
578
+ int32 Count = 0;
579
+ for (TFieldIterator<FProperty> It(Obj->GetClass()); It; ++It)
580
+ {
581
+ FProperty* Prop = *It;
582
+ if (!isEditableProperty(Prop)) continue;
583
+ if (Limit > 0 && Count >= Limit) break;
584
+
585
+ TSharedPtr<FJsonObject> P = MakeShared<FJsonObject>();
586
+ P->SetStringField(TEXT("name"), Prop->GetName());
587
+ P->SetStringField(TEXT("type"), Prop->GetCPPType());
588
+ P->SetBoolField(TEXT("isArray"), Prop->IsA<FArrayProperty>());
589
+ OutProps.Add(MakeShared<FJsonValueObject>(P));
590
+ Count++;
591
+ }
592
+ };
593
+
594
+ // Actor editable properties
595
+ {
596
+ TArray<TSharedPtr<FJsonValue>> Props;
597
+ addPropertyDescriptors(Actor, 250, Props);
598
+ ActorObject->SetArrayField(TEXT("properties"), Props);
599
+ }
600
+
601
+ // Actor component pointer fields (useful for nested property paths like PointLightComponent.LightColor)
602
+ {
603
+ TArray<TSharedPtr<FJsonValue>> Fields;
604
+ int32 Count = 0;
605
+ for (TFieldIterator<FProperty> It(Actor->GetClass()); It; ++It)
606
+ {
607
+ FProperty* Prop = *It;
608
+ FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(Prop);
609
+ if (!ObjProp) continue;
610
+ if (!ObjProp->PropertyClass || !ObjProp->PropertyClass->IsChildOf(UActorComponent::StaticClass())) continue;
611
+ if (Count >= 50) break;
612
+
613
+ UObject* Ref = ObjProp->GetObjectPropertyValue_InContainer(Actor);
614
+ UActorComponent* Comp = Cast<UActorComponent>(Ref);
615
+
616
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
617
+ Obj->SetStringField(TEXT("property"), Prop->GetName());
618
+ Obj->SetStringField(TEXT("class"), ObjProp->PropertyClass->GetName());
619
+ if (Comp)
620
+ {
621
+ Obj->SetStringField(TEXT("componentName"), Comp->GetName());
622
+ }
623
+ Fields.Add(MakeShared<FJsonValueObject>(Obj));
624
+ Count++;
625
+ }
626
+ ActorObject->SetArrayField(TEXT("componentFields"), Fields);
627
+ }
628
+
629
+ // Components + their editable properties (best-effort, bounded)
630
+ {
631
+ TArray<TSharedPtr<FJsonValue>> Components;
632
+ TArray<UActorComponent*> Comps;
633
+ Actor->GetComponents(Comps);
634
+ Comps.Sort([](const UActorComponent& A, const UActorComponent& B) {
635
+ return A.GetFName().LexicalLess(B.GetFName());
636
+ });
637
+
638
+ int32 Added = 0;
639
+ for (UActorComponent* C : Comps)
640
+ {
641
+ if (!C) continue;
642
+ if (Added >= 50) break;
643
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
644
+ Obj->SetStringField(TEXT("name"), C->GetName());
645
+ Obj->SetStringField(TEXT("class"), C->GetClass()->GetName());
646
+
647
+ TArray<TSharedPtr<FJsonValue>> Props;
648
+ addPropertyDescriptors(C, 200, Props);
649
+ Obj->SetArrayField(TEXT("properties"), Props);
650
+
651
+ Components.Add(MakeShared<FJsonValueObject>(Obj));
652
+ Added++;
653
+ }
654
+ ActorObject->SetArrayField(TEXT("components"), Components);
655
+ }
656
+
488
657
  return ActorObject;
489
658
  }
490
659
 
@@ -518,14 +687,88 @@ bool FUnrealMCPCommonUtils::SetObjectProperty(UObject* Object, const FString& Pr
518
687
  return false;
519
688
  }
520
689
 
521
- FProperty* Property = Object->GetClass()->FindPropertyByName(*PropertyName);
522
- if (!Property)
690
+ const auto splitPath = [](const FString& Raw) -> TArray<FString> {
691
+ FString P = Raw;
692
+ P.ReplaceInline(TEXT("/"), TEXT("."), ESearchCase::IgnoreCase);
693
+ P = P.TrimStartAndEnd();
694
+ TArray<FString> Segs;
695
+ P.ParseIntoArray(Segs, TEXT("."), true);
696
+ Segs.RemoveAll([](const FString& S) { return S.TrimStartAndEnd().IsEmpty(); });
697
+ for (FString& S : Segs) S = S.TrimStartAndEnd();
698
+ return Segs;
699
+ };
700
+
701
+ const auto resolvePropertyPath = [&](UObject* Root, const FString& RawPath, UObject*& OutContainerObj, FProperty*& OutProp, void*& OutPropAddr) -> bool {
702
+ OutContainerObj = Root;
703
+ OutProp = nullptr;
704
+ OutPropAddr = nullptr;
705
+
706
+ const TArray<FString> Segs = splitPath(RawPath);
707
+ if (Segs.Num() == 0)
708
+ {
709
+ OutErrorMessage = TEXT("Property path is empty");
710
+ return false;
711
+ }
712
+
713
+ UObject* Current = Root;
714
+ for (int32 i = 0; i < Segs.Num(); i++)
715
+ {
716
+ const FString& Seg = Segs[i];
717
+ const bool bIsLeaf = (i == Segs.Num() - 1);
718
+
719
+ FProperty* Prop = Current->GetClass()->FindPropertyByName(*Seg);
720
+ if (!Prop)
721
+ {
722
+ OutErrorMessage = FString::Printf(TEXT("Property not found: %s"), *Seg);
723
+ return false;
724
+ }
725
+
726
+ if (bIsLeaf)
727
+ {
728
+ OutContainerObj = Current;
729
+ OutProp = Prop;
730
+ OutPropAddr = Prop->ContainerPtrToValuePtr<void>(Current);
731
+ return true;
732
+ }
733
+
734
+ FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(Prop);
735
+ if (!ObjProp)
736
+ {
737
+ OutErrorMessage = FString::Printf(TEXT("Unsupported property path segment (not an object): %s"), *Seg);
738
+ return false;
739
+ }
740
+
741
+ UObject* Next = ObjProp->GetObjectPropertyValue_InContainer(Current);
742
+ if (!Next)
743
+ {
744
+ OutErrorMessage = FString::Printf(TEXT("Null object at path segment: %s"), *Seg);
745
+ return false;
746
+ }
747
+ Current = Next;
748
+ }
749
+
750
+ OutErrorMessage = TEXT("Failed to resolve property path");
751
+ return false;
752
+ };
753
+
754
+ UObject* ContainerObj = nullptr;
755
+ FProperty* Property = nullptr;
756
+ void* PropertyAddr = nullptr;
757
+ if (!resolvePropertyPath(Object, PropertyName, ContainerObj, Property, PropertyAddr))
758
+ {
759
+ return false;
760
+ }
761
+
762
+ if (!Property || !ContainerObj || !PropertyAddr)
523
763
  {
524
- OutErrorMessage = FString::Printf(TEXT("Property not found: %s"), *PropertyName);
764
+ OutErrorMessage = TEXT("Property resolution failed");
525
765
  return false;
526
766
  }
527
767
 
528
- void* PropertyAddr = Property->ContainerPtrToValuePtr<void>(Object);
768
+ // Mark dirty for undo/redo and details panel refresh (editor only).
769
+ #if WITH_EDITOR
770
+ ContainerObj->Modify();
771
+ #endif
529
772
 
530
773
  // Handle different property types
531
774
  if (Property->IsA<FBoolProperty>())
@@ -553,6 +796,231 @@ bool FUnrealMCPCommonUtils::SetObjectProperty(UObject* Object, const FString& Pr
553
796
  ((FStrProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsString());
554
797
  return true;
555
798
  }
799
+ else if (Property->IsA<FNameProperty>())
800
+ {
801
+ const FString S = Value->Type == EJson::String ? Value->AsString() : FString::Printf(TEXT("%g"), Value->AsNumber());
802
+ CastFieldChecked<FNameProperty>(Property)->SetPropertyValue(PropertyAddr, FName(*S));
803
+ return true;
804
+ }
805
+ else if (Property->IsA<FTextProperty>())
806
+ {
807
+ const FString S = Value->Type == EJson::String ? Value->AsString() : FString::Printf(TEXT("%g"), Value->AsNumber());
808
+ CastFieldChecked<FTextProperty>(Property)->SetPropertyValue(PropertyAddr, FText::FromString(S));
809
+ return true;
810
+ }
811
+ else if (Property->IsA<FStructProperty>())
812
+ {
813
+ FStructProperty* StructProp = CastField<FStructProperty>(Property);
814
+ if (!StructProp || !StructProp->Struct)
815
+ {
816
+ OutErrorMessage = FString::Printf(TEXT("Invalid struct property: %s"), *PropertyName);
817
+ return false;
818
+ }
819
+
820
+ const UScriptStruct* S = StructProp->Struct;
821
+ if (S == TBaseStructure<FVector>::Get())
822
+ {
823
+ if (Value->Type != EJson::Array || Value->AsArray().Num() != 3)
824
+ {
825
+ OutErrorMessage = TEXT("FVector requires an array of 3 numbers");
826
+ return false;
827
+ }
828
+ const auto& Arr = Value->AsArray();
829
+ const FVector V((float)Arr[0]->AsNumber(), (float)Arr[1]->AsNumber(), (float)Arr[2]->AsNumber());
830
+ StructProp->CopySingleValue(PropertyAddr, &V);
831
+ return true;
832
+ }
833
+ if (S == TBaseStructure<FVector2D>::Get())
834
+ {
835
+ if (Value->Type != EJson::Array || Value->AsArray().Num() != 2)
836
+ {
837
+ OutErrorMessage = TEXT("FVector2D requires an array of 2 numbers");
838
+ return false;
839
+ }
840
+ const auto& Arr = Value->AsArray();
841
+ const FVector2D V((float)Arr[0]->AsNumber(), (float)Arr[1]->AsNumber());
842
+ StructProp->CopySingleValue(PropertyAddr, &V);
843
+ return true;
844
+ }
845
+ if (S == TBaseStructure<FRotator>::Get())
846
+ {
847
+ if (Value->Type != EJson::Array || Value->AsArray().Num() != 3)
848
+ {
849
+ OutErrorMessage = TEXT("FRotator requires an array of 3 numbers [Pitch,Yaw,Roll]");
850
+ return false;
851
+ }
852
+ const auto& Arr = Value->AsArray();
853
+ const FRotator R((float)Arr[0]->AsNumber(), (float)Arr[1]->AsNumber(), (float)Arr[2]->AsNumber());
854
+ StructProp->CopySingleValue(PropertyAddr, &R);
855
+ return true;
856
+ }
857
+ if (S == TBaseStructure<FLinearColor>::Get())
858
+ {
859
+ if (Value->Type != EJson::Array || (Value->AsArray().Num() != 3 && Value->AsArray().Num() != 4))
860
+ {
861
+ OutErrorMessage = TEXT("FLinearColor requires an array of 3 or 4 numbers [R,G,B,(A)]");
862
+ return false;
863
+ }
864
+ const auto& Arr = Value->AsArray();
865
+ const float A = Arr.Num() >= 4 ? (float)Arr[3]->AsNumber() : 1.0f;
866
+ const FLinearColor C((float)Arr[0]->AsNumber(), (float)Arr[1]->AsNumber(), (float)Arr[2]->AsNumber(), A);
867
+ StructProp->CopySingleValue(PropertyAddr, &C);
868
+ return true;
869
+ }
870
+ if (S == TBaseStructure<FColor>::Get())
871
+ {
872
+ if (Value->Type != EJson::Array || (Value->AsArray().Num() != 3 && Value->AsArray().Num() != 4))
873
+ {
874
+ OutErrorMessage = TEXT("FColor requires an array of 3 or 4 numbers [R,G,B,(A)]");
875
+ return false;
876
+ }
877
+ const auto& Arr = Value->AsArray();
878
+ const double r = Arr[0]->AsNumber();
879
+ const double g = Arr[1]->AsNumber();
880
+ const double b = Arr[2]->AsNumber();
881
+ const double a = Arr.Num() >= 4 ? Arr[3]->AsNumber() : 255.0;
882
+ const bool bLooksNormalized = r <= 1.0 && g <= 1.0 && b <= 1.0 && a <= 1.0;
883
+ const auto toByte = [&](double v) -> uint8 {
884
+ const double x = bLooksNormalized ? v * 255.0 : v;
885
+ return (uint8)FMath::Clamp((int32)FMath::RoundHalfToZero(x), 0, 255);
886
+ };
887
+ const FColor C(toByte(r), toByte(g), toByte(b), toByte(a));
888
+ StructProp->CopySingleValue(PropertyAddr, &C);
889
+ return true;
890
+ }
891
+
892
+ OutErrorMessage = FString::Printf(TEXT("Unsupported struct type: %s"), *S->GetName());
893
+ return false;
894
+ }
895
+ else if (Property->IsA<FObjectPropertyBase>())
896
+ {
897
+ FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(Property);
898
+ if (!ObjProp)
899
+ {
900
+ OutErrorMessage = FString::Printf(TEXT("Invalid ObjectProperty: %s"), *PropertyName);
901
+ return false;
902
+ }
903
+
904
+ if (Value->Type == EJson::Null)
905
+ {
906
+ ObjProp->SetObjectPropertyValue(PropertyAddr, nullptr);
907
+ return true;
908
+ }
909
+
910
+ if (Value->Type != EJson::String)
911
+ {
912
+ OutErrorMessage = FString::Printf(TEXT("ObjectProperty requires a string object path (or null): %s"), *PropertyName);
913
+ return false;
914
+ }
915
+
916
+ FString Path = Value->AsString().TrimStartAndEnd();
917
+ if (Path.IsEmpty())
918
+ {
919
+ ObjProp->SetObjectPropertyValue(PropertyAddr, nullptr);
920
+ return true;
921
+ }
922
+
923
+ UObject* Loaded = StaticLoadObject(ObjProp->PropertyClass, nullptr, *Path);
924
+ if (!Loaded && !Path.Contains(TEXT("'")))
925
+ {
926
+ Loaded = StaticLoadObject(
927
+ ObjProp->PropertyClass,
928
+ nullptr,
929
+ *FString::Printf(TEXT("%s'%s'"), *ObjProp->PropertyClass->GetName(), *Path));
930
+ }
931
+
932
+ if (!Loaded)
933
+ {
934
+ OutErrorMessage = FString::Printf(TEXT("Failed to load object for %s: %s"), *PropertyName, *Path);
935
+ return false;
936
+ }
937
+
938
+ ObjProp->SetObjectPropertyValue(PropertyAddr, Loaded);
939
+ return true;
940
+ }
941
+ else if (Property->IsA<FArrayProperty>())
942
+ {
943
+ if (Value->Type != EJson::Array)
944
+ {
945
+ OutErrorMessage = FString::Printf(TEXT("ArrayProperty requires an array value: %s"), *PropertyName);
946
+ return false;
947
+ }
948
+
949
+ FArrayProperty* ArrProp = CastField<FArrayProperty>(Property);
950
+ if (!ArrProp || !ArrProp->Inner)
951
+ {
952
+ OutErrorMessage = FString::Printf(TEXT("Invalid ArrayProperty: %s"), *PropertyName);
953
+ return false;
954
+ }
955
+
956
+ const TArray<TSharedPtr<FJsonValue>>& Arr = Value->AsArray();
957
+ FScriptArrayHelper Helper(ArrProp, PropertyAddr);
958
+ Helper.Resize(Arr.Num());
959
+
960
+ for (int32 i = 0; i < Arr.Num(); i++)
961
+ {
962
+ void* ElemPtr = Helper.GetRawPtr(i);
963
+ const TSharedPtr<FJsonValue>& Elem = Arr[i];
964
+
965
+ if (FObjectPropertyBase* InnerObj = CastField<FObjectPropertyBase>(ArrProp->Inner))
966
+ {
967
+ if (!Elem.IsValid() || Elem->Type == EJson::Null)
968
+ {
969
+ InnerObj->SetObjectPropertyValue(ElemPtr, nullptr);
970
+ continue;
971
+ }
972
+ if (Elem->Type != EJson::String)
973
+ {
974
+ OutErrorMessage = FString::Printf(TEXT("Array element %d must be a string object path (or null) for %s"), i, *PropertyName);
975
+ return false;
976
+ }
977
+ FString Path = Elem->AsString().TrimStartAndEnd();
978
+ UObject* Loaded = Path.IsEmpty() ? nullptr : StaticLoadObject(InnerObj->PropertyClass, nullptr, *Path);
979
+ if (!Loaded && !Path.IsEmpty() && !Path.Contains(TEXT("'")))
980
+ {
981
+ Loaded = StaticLoadObject(
982
+ InnerObj->PropertyClass,
983
+ nullptr,
984
+ *FString::Printf(TEXT("%s'%s'"), *InnerObj->PropertyClass->GetName(), *Path));
985
+ }
986
+ if (!Path.IsEmpty() && !Loaded)
987
+ {
988
+ OutErrorMessage = FString::Printf(TEXT("Failed to load object for %s[%d]: %s"), *PropertyName, i, *Path);
989
+ return false;
990
+ }
991
+ InnerObj->SetObjectPropertyValue(ElemPtr, Loaded);
992
+ continue;
993
+ }
994
+
995
+ if (FStrProperty* InnerStr = CastField<FStrProperty>(ArrProp->Inner))
996
+ {
997
+ if (!Elem.IsValid() || Elem->Type == EJson::Null)
998
+ {
999
+ InnerStr->SetPropertyValue(ElemPtr, TEXT(""));
1000
+ continue;
1001
+ }
1002
+ InnerStr->SetPropertyValue(ElemPtr, Elem->Type == EJson::String ? Elem->AsString() : FString::Printf(TEXT("%g"), Elem->AsNumber()));
1003
+ continue;
1004
+ }
1005
+
1006
+ if (FNumericProperty* InnerNum = CastField<FNumericProperty>(ArrProp->Inner))
1007
+ {
1008
+ if (!Elem.IsValid() || Elem->Type != EJson::Number)
1009
+ {
1010
+ OutErrorMessage = FString::Printf(TEXT("Array element %d must be a number for %s"), i, *PropertyName);
1011
+ return false;
1012
+ }
1013
+ if (InnerNum->IsInteger()) InnerNum->SetIntPropertyValue(ElemPtr, (int64)Elem->AsNumber());
1014
+ else InnerNum->SetFloatingPointPropertyValue(ElemPtr, Elem->AsNumber());
1015
+ continue;
1016
+ }
1017
+
1018
+ OutErrorMessage = FString::Printf(TEXT("Unsupported ArrayProperty element type: %s for %s"), *ArrProp->Inner->GetClass()->GetName(), *PropertyName);
1019
+ return false;
1020
+ }
1021
+
1022
+ return true;
1023
+ }
556
1024
  else if (Property->IsA<FByteProperty>())
557
1025
  {
558
1026
  FByteProperty* ByteProp = CastField<FByteProperty>(Property);
@@ -706,4 +1174,4 @@ bool FUnrealMCPCommonUtils::SetObjectProperty(UObject* Object, const FString& Pr
706
1174
  OutErrorMessage = FString::Printf(TEXT("Unsupported property type: %s for property %s"),
707
1175
  *Property->GetClass()->GetName(), *PropertyName);
708
1176
  return false;
709
- }
1177
+ }