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.
- package/dist/codex/flockbayMcpStdioBridge.cjs +339 -0
- package/dist/codex/flockbayMcpStdioBridge.mjs +339 -0
- package/dist/{index--o4BPz5o.cjs → index-Cau-_Qvn.cjs} +2683 -609
- package/dist/{index-CUp3juDS.mjs → index-DtmFQzXY.mjs} +2684 -611
- package/dist/index.cjs +3 -5
- package/dist/index.mjs +3 -5
- package/dist/lib.cjs +7 -9
- package/dist/lib.d.cts +219 -531
- package/dist/lib.d.mts +219 -531
- package/dist/lib.mjs +7 -9
- package/dist/{runCodex-o6PCbHQ7.mjs → runCodex-Di9eHddq.mjs} +263 -42
- package/dist/{runCodex-D3eT-TvB.cjs → runCodex-DzP3VUa-.cjs} +264 -43
- package/dist/{runGemini-Bt0oEj_g.mjs → runGemini-BS6sBU_V.mjs} +63 -28
- package/dist/{runGemini-CBxZp6I7.cjs → runGemini-CpmehDQ2.cjs} +64 -29
- package/dist/{types-DGd6ea2Z.mjs → types-CwzNqYEx.mjs} +465 -1142
- package/dist/{types-C-jnUdn_.cjs → types-SUAKq-K0.cjs} +466 -1146
- package/package.json +1 -1
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp +195 -6
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp +376 -5
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommandSchema.cpp +731 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp +476 -8
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +1518 -94
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp +7 -4
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp +150 -112
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h +2 -1
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h +4 -1
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommandSchema.h +42 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h +21 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs +4 -1
- package/dist/flockbayScreenshotGate-DJX3Is5d.mjs +0 -136
- 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
|
|
157
|
-
|
|
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
|
-
|
|
522
|
-
|
|
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 =
|
|
764
|
+
OutErrorMessage = TEXT("Property resolution failed");
|
|
525
765
|
return false;
|
|
526
766
|
}
|
|
527
767
|
|
|
528
|
-
|
|
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
|
+
}
|