ue-mcp 0.4.0-alpha

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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/dist/bridge.d.ts +25 -0
  4. package/dist/bridge.js +124 -0
  5. package/dist/bridge.js.map +1 -0
  6. package/dist/deployer.d.ts +18 -0
  7. package/dist/deployer.js +175 -0
  8. package/dist/deployer.js.map +1 -0
  9. package/dist/editor-control.d.ts +15 -0
  10. package/dist/editor-control.js +181 -0
  11. package/dist/editor-control.js.map +1 -0
  12. package/dist/github-app.d.ts +4 -0
  13. package/dist/github-app.js +94 -0
  14. package/dist/github-app.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +125 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +2 -0
  19. package/dist/init.js +290 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/instructions.d.ts +1 -0
  22. package/dist/instructions.js +150 -0
  23. package/dist/instructions.js.map +1 -0
  24. package/dist/project.d.ts +32 -0
  25. package/dist/project.js +175 -0
  26. package/dist/project.js.map +1 -0
  27. package/dist/tools/animation.d.ts +2 -0
  28. package/dist/tools/animation.js +62 -0
  29. package/dist/tools/animation.js.map +1 -0
  30. package/dist/tools/asset.d.ts +2 -0
  31. package/dist/tools/asset.js +128 -0
  32. package/dist/tools/asset.js.map +1 -0
  33. package/dist/tools/audio.d.ts +2 -0
  34. package/dist/tools/audio.js +24 -0
  35. package/dist/tools/audio.js.map +1 -0
  36. package/dist/tools/blueprint.d.ts +2 -0
  37. package/dist/tools/blueprint.js +64 -0
  38. package/dist/tools/blueprint.js.map +1 -0
  39. package/dist/tools/demo.d.ts +2 -0
  40. package/dist/tools/demo.js +10 -0
  41. package/dist/tools/demo.js.map +1 -0
  42. package/dist/tools/editor.d.ts +2 -0
  43. package/dist/tools/editor.js +133 -0
  44. package/dist/tools/editor.js.map +1 -0
  45. package/dist/tools/feedback.d.ts +2 -0
  46. package/dist/tools/feedback.js +44 -0
  47. package/dist/tools/feedback.js.map +1 -0
  48. package/dist/tools/foliage.d.ts +2 -0
  49. package/dist/tools/foliage.js +29 -0
  50. package/dist/tools/foliage.js.map +1 -0
  51. package/dist/tools/gameplay.d.ts +2 -0
  52. package/dist/tools/gameplay.js +101 -0
  53. package/dist/tools/gameplay.js.map +1 -0
  54. package/dist/tools/gas.d.ts +2 -0
  55. package/dist/tools/gas.js +43 -0
  56. package/dist/tools/gas.js.map +1 -0
  57. package/dist/tools/landscape.d.ts +2 -0
  58. package/dist/tools/landscape.js +32 -0
  59. package/dist/tools/landscape.js.map +1 -0
  60. package/dist/tools/level.d.ts +2 -0
  61. package/dist/tools/level.js +66 -0
  62. package/dist/tools/level.js.map +1 -0
  63. package/dist/tools/material.d.ts +2 -0
  64. package/dist/tools/material.js +59 -0
  65. package/dist/tools/material.js.map +1 -0
  66. package/dist/tools/networking.d.ts +2 -0
  67. package/dist/tools/networking.js +42 -0
  68. package/dist/tools/networking.js.map +1 -0
  69. package/dist/tools/niagara.d.ts +2 -0
  70. package/dist/tools/niagara.js +42 -0
  71. package/dist/tools/niagara.js.map +1 -0
  72. package/dist/tools/pcg.d.ts +2 -0
  73. package/dist/tools/pcg.js +39 -0
  74. package/dist/tools/pcg.js.map +1 -0
  75. package/dist/tools/project.d.ts +2 -0
  76. package/dist/tools/project.js +282 -0
  77. package/dist/tools/project.js.map +1 -0
  78. package/dist/tools/reflection.d.ts +2 -0
  79. package/dist/tools/reflection.js +26 -0
  80. package/dist/tools/reflection.js.map +1 -0
  81. package/dist/tools/widget.d.ts +2 -0
  82. package/dist/tools/widget.js +34 -0
  83. package/dist/tools/widget.js.map +1 -0
  84. package/dist/types.d.ts +21 -0
  85. package/dist/types.js +38 -0
  86. package/dist/types.js.map +1 -0
  87. package/package.json +76 -0
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  141. package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
@@ -0,0 +1,876 @@
1
+ #include "FoliageHandlers.h"
2
+ #include "HandlerRegistry.h"
3
+ #include "InstancedFoliageActor.h"
4
+ #include "FoliageType.h"
5
+ #include "FoliageType_InstancedStaticMesh.h"
6
+ #include "Editor.h"
7
+ #include "Engine/World.h"
8
+ #include "EngineUtils.h"
9
+ #include "Dom/JsonObject.h"
10
+ #include "Dom/JsonValue.h"
11
+ #include "Components/HierarchicalInstancedStaticMeshComponent.h"
12
+ #include "Components/InstancedStaticMeshComponent.h"
13
+ #include "AssetRegistry/AssetRegistryModule.h"
14
+ #include "UObject/Package.h"
15
+ #include "UObject/UObjectGlobals.h"
16
+ #include "Misc/PackageName.h"
17
+ #include "LandscapeGrassType.h"
18
+ #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
19
+ #include "Kismet/KismetSystemLibrary.h"
20
+
21
+ void FFoliageHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
22
+ {
23
+ Registry.RegisterHandler(TEXT("list_foliage_types"), &ListFoliageTypes);
24
+ Registry.RegisterHandler(TEXT("sample_foliage"), &SampleFoliage);
25
+ Registry.RegisterHandler(TEXT("get_foliage_settings"), &GetFoliageSettings);
26
+ Registry.RegisterHandler(TEXT("paint_foliage"), &PaintFoliage);
27
+ Registry.RegisterHandler(TEXT("erase_foliage"), &EraseFoliage);
28
+ Registry.RegisterHandler(TEXT("sample_foliage_instances"), &SampleFoliageInstances);
29
+ Registry.RegisterHandler(TEXT("create_foliage_layer"), &CreateFoliageLayer);
30
+ Registry.RegisterHandler(TEXT("get_foliage_type_settings"), &GetFoliageSettings);
31
+ Registry.RegisterHandler(TEXT("set_foliage_type_settings"), &SetFoliageTypeSettings);
32
+ Registry.RegisterHandler(TEXT("create_foliage_type"), &CreateFoliageType);
33
+ }
34
+
35
+ TSharedPtr<FJsonValue> FFoliageHandlers::ListFoliageTypes(const TSharedPtr<FJsonObject>& Params)
36
+ {
37
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
38
+
39
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
40
+ if (!World)
41
+ {
42
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
43
+ Result->SetBoolField(TEXT("success"), false);
44
+ return MakeShared<FJsonValueObject>(Result);
45
+ }
46
+
47
+ TArray<TSharedPtr<FJsonValue>> FoliageTypesArray;
48
+
49
+ for (TActorIterator<AInstancedFoliageActor> It(World); It; ++It)
50
+ {
51
+ AInstancedFoliageActor* FoliageActor = *It;
52
+ if (!FoliageActor) continue;
53
+
54
+ const auto& FoliageInfoMap = FoliageActor->GetFoliageInfos();
55
+ for (const auto& Pair : FoliageInfoMap)
56
+ {
57
+ UFoliageType* FoliageType = Pair.Key;
58
+ const FFoliageInfo& FoliageInfo = *Pair.Value;
59
+
60
+ if (!FoliageType) continue;
61
+
62
+ TSharedPtr<FJsonObject> TypeObj = MakeShared<FJsonObject>();
63
+ TypeObj->SetStringField(TEXT("name"), FoliageType->GetName());
64
+ TypeObj->SetStringField(TEXT("path"), FoliageType->GetPathName());
65
+
66
+ // UE 5.7: Instances array is private; use the HISM component for instance count
67
+ int32 InstanceCount = 0;
68
+ UHierarchicalInstancedStaticMeshComponent* HISMComp = FoliageInfo.GetComponent();
69
+ if (HISMComp)
70
+ {
71
+ InstanceCount = HISMComp->GetInstanceCount();
72
+ }
73
+ TypeObj->SetNumberField(TEXT("instanceCount"), InstanceCount);
74
+
75
+ // Get source info
76
+ TypeObj->SetStringField(TEXT("className"), FoliageType->GetClass()->GetName());
77
+
78
+ FoliageTypesArray.Add(MakeShared<FJsonValueObject>(TypeObj));
79
+ }
80
+ }
81
+
82
+ Result->SetArrayField(TEXT("foliageTypes"), FoliageTypesArray);
83
+ Result->SetNumberField(TEXT("count"), FoliageTypesArray.Num());
84
+ Result->SetBoolField(TEXT("success"), true);
85
+
86
+ return MakeShared<FJsonValueObject>(Result);
87
+ }
88
+
89
+ TSharedPtr<FJsonValue> FFoliageHandlers::SampleFoliage(const TSharedPtr<FJsonObject>& Params)
90
+ {
91
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
92
+
93
+ // Parse center point
94
+ const TSharedPtr<FJsonObject>* CenterObj = nullptr;
95
+ if (!Params->TryGetObjectField(TEXT("center"), CenterObj) || !CenterObj || !(*CenterObj).IsValid())
96
+ {
97
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'center' parameter (object with x, y, z)"));
98
+ Result->SetBoolField(TEXT("success"), false);
99
+ return MakeShared<FJsonValueObject>(Result);
100
+ }
101
+
102
+ double CenterX = 0, CenterY = 0, CenterZ = 0;
103
+ (*CenterObj)->TryGetNumberField(TEXT("x"), CenterX);
104
+ (*CenterObj)->TryGetNumberField(TEXT("y"), CenterY);
105
+ (*CenterObj)->TryGetNumberField(TEXT("z"), CenterZ);
106
+ FVector Center(CenterX, CenterY, CenterZ);
107
+
108
+ double Radius = 1000.0;
109
+ Params->TryGetNumberField(TEXT("radius"), Radius);
110
+ double RadiusSq = Radius * Radius;
111
+
112
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
113
+ if (!World)
114
+ {
115
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
116
+ Result->SetBoolField(TEXT("success"), false);
117
+ return MakeShared<FJsonValueObject>(Result);
118
+ }
119
+
120
+ TMap<FString, int32> TypeCounts;
121
+ int32 TotalCount = 0;
122
+
123
+ for (TActorIterator<AInstancedFoliageActor> It(World); It; ++It)
124
+ {
125
+ AInstancedFoliageActor* FoliageActor = *It;
126
+ if (!FoliageActor) continue;
127
+
128
+ const auto& FoliageInfoMap = FoliageActor->GetFoliageInfos();
129
+ for (const auto& Pair : FoliageInfoMap)
130
+ {
131
+ UFoliageType* FoliageType = Pair.Key;
132
+ const FFoliageInfo& FoliageInfo = *Pair.Value;
133
+
134
+ if (!FoliageType) continue;
135
+
136
+ FString TypeName = FoliageType->GetName();
137
+ int32 MatchCount = 0;
138
+
139
+ // UE 5.7: Instances array is private; use the HISM component for transforms
140
+ UHierarchicalInstancedStaticMeshComponent* HISMComp = FoliageInfo.GetComponent();
141
+ if (HISMComp)
142
+ {
143
+ int32 NumInstances = HISMComp->GetInstanceCount();
144
+ for (int32 i = 0; i < NumInstances; ++i)
145
+ {
146
+ FTransform InstanceTransform;
147
+ HISMComp->GetInstanceTransform(i, InstanceTransform, /*bWorldSpace=*/ true);
148
+ FVector InstanceLocation = InstanceTransform.GetLocation();
149
+ double DistSq = FVector::DistSquared(Center, InstanceLocation);
150
+ if (DistSq <= RadiusSq)
151
+ {
152
+ MatchCount++;
153
+ }
154
+ }
155
+ }
156
+
157
+ if (MatchCount > 0)
158
+ {
159
+ TypeCounts.FindOrAdd(TypeName) += MatchCount;
160
+ TotalCount += MatchCount;
161
+ }
162
+ }
163
+ }
164
+
165
+ TArray<TSharedPtr<FJsonValue>> TypesArray;
166
+ for (const auto& Pair : TypeCounts)
167
+ {
168
+ TSharedPtr<FJsonObject> TypeObj = MakeShared<FJsonObject>();
169
+ TypeObj->SetStringField(TEXT("type"), Pair.Key);
170
+ TypeObj->SetNumberField(TEXT("count"), Pair.Value);
171
+ TypesArray.Add(MakeShared<FJsonValueObject>(TypeObj));
172
+ }
173
+
174
+ TSharedPtr<FJsonObject> CenterResult = MakeShared<FJsonObject>();
175
+ CenterResult->SetNumberField(TEXT("x"), CenterX);
176
+ CenterResult->SetNumberField(TEXT("y"), CenterY);
177
+ CenterResult->SetNumberField(TEXT("z"), CenterZ);
178
+
179
+ Result->SetObjectField(TEXT("center"), CenterResult);
180
+ Result->SetNumberField(TEXT("radius"), Radius);
181
+ Result->SetNumberField(TEXT("totalCount"), TotalCount);
182
+ Result->SetArrayField(TEXT("types"), TypesArray);
183
+ Result->SetBoolField(TEXT("success"), true);
184
+
185
+ return MakeShared<FJsonValueObject>(Result);
186
+ }
187
+
188
+ TSharedPtr<FJsonValue> FFoliageHandlers::GetFoliageSettings(const TSharedPtr<FJsonObject>& Params)
189
+ {
190
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
191
+
192
+ FString FoliageTypePath;
193
+ if (!Params->TryGetStringField(TEXT("foliageTypePath"), FoliageTypePath))
194
+ {
195
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'foliageTypePath' parameter"));
196
+ Result->SetBoolField(TEXT("success"), false);
197
+ return MakeShared<FJsonValueObject>(Result);
198
+ }
199
+
200
+ UFoliageType* FoliageType = LoadObject<UFoliageType>(nullptr, *FoliageTypePath);
201
+ if (!FoliageType)
202
+ {
203
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Foliage type not found: %s"), *FoliageTypePath));
204
+ Result->SetBoolField(TEXT("success"), false);
205
+ return MakeShared<FJsonValueObject>(Result);
206
+ }
207
+
208
+ Result->SetStringField(TEXT("path"), FoliageTypePath);
209
+ Result->SetStringField(TEXT("name"), FoliageType->GetName());
210
+ Result->SetStringField(TEXT("className"), FoliageType->GetClass()->GetName());
211
+
212
+ // Density settings
213
+ Result->SetNumberField(TEXT("density"), FoliageType->Density);
214
+ Result->SetNumberField(TEXT("densityAdjustmentFactor"), FoliageType->DensityAdjustmentFactor);
215
+ Result->SetNumberField(TEXT("radius"), FoliageType->Radius);
216
+
217
+ // Scaling settings
218
+ TSharedPtr<FJsonObject> ScalingObj = MakeShared<FJsonObject>();
219
+ ScalingObj->SetNumberField(TEXT("scaleMinX"), FoliageType->ScaleX.Min);
220
+ ScalingObj->SetNumberField(TEXT("scaleMaxX"), FoliageType->ScaleX.Max);
221
+ ScalingObj->SetNumberField(TEXT("scaleMinY"), FoliageType->ScaleY.Min);
222
+ ScalingObj->SetNumberField(TEXT("scaleMaxY"), FoliageType->ScaleY.Max);
223
+ ScalingObj->SetNumberField(TEXT("scaleMinZ"), FoliageType->ScaleZ.Min);
224
+ ScalingObj->SetNumberField(TEXT("scaleMaxZ"), FoliageType->ScaleZ.Max);
225
+ Result->SetObjectField(TEXT("scaling"), ScalingObj);
226
+
227
+ // Placement settings
228
+ Result->SetBoolField(TEXT("alignToNormal"), FoliageType->AlignToNormal);
229
+ Result->SetNumberField(TEXT("alignMaxAngle"), FoliageType->AlignMaxAngle);
230
+ Result->SetBoolField(TEXT("randomYaw"), FoliageType->RandomYaw);
231
+ Result->SetNumberField(TEXT("randomPitchAngle"), FoliageType->RandomPitchAngle);
232
+ Result->SetNumberField(TEXT("groundSlopeAngle"), FoliageType->GroundSlopeAngle.Max);
233
+
234
+ // Height range
235
+ Result->SetNumberField(TEXT("heightMin"), FoliageType->Height.Min);
236
+ Result->SetNumberField(TEXT("heightMax"), FoliageType->Height.Max);
237
+
238
+ // Collision settings
239
+ TSharedPtr<FJsonObject> CollisionObj = MakeShared<FJsonObject>();
240
+ CollisionObj->SetBoolField(TEXT("collisionWithWorld"), FoliageType->CollisionWithWorld);
241
+ CollisionObj->SetNumberField(TEXT("collisionRadius"), FoliageType->CollisionRadius);
242
+ CollisionObj->SetNumberField(TEXT("collisionScale"), FoliageType->CollisionScale.X);
243
+ Result->SetObjectField(TEXT("collision"), CollisionObj);
244
+
245
+ // LOD settings
246
+ TSharedPtr<FJsonObject> LodObj = MakeShared<FJsonObject>();
247
+ LodObj->SetNumberField(TEXT("cullDistanceMin"), FoliageType->CullDistance.Min);
248
+ LodObj->SetNumberField(TEXT("cullDistanceMax"), FoliageType->CullDistance.Max);
249
+ Result->SetObjectField(TEXT("lod"), LodObj);
250
+
251
+ // Rendering settings
252
+ Result->SetBoolField(TEXT("castShadow"), FoliageType->CastShadow);
253
+ Result->SetBoolField(TEXT("receivesDecals"), FoliageType->bReceivesDecals);
254
+
255
+ // Mesh reference (for InstancedStaticMesh types)
256
+ UFoliageType_InstancedStaticMesh* ISMType = Cast<UFoliageType_InstancedStaticMesh>(FoliageType);
257
+ if (ISMType && ISMType->Mesh)
258
+ {
259
+ Result->SetStringField(TEXT("meshPath"), ISMType->Mesh->GetPathName());
260
+ Result->SetStringField(TEXT("meshName"), ISMType->Mesh->GetName());
261
+ }
262
+
263
+ Result->SetBoolField(TEXT("success"), true);
264
+ return MakeShared<FJsonValueObject>(Result);
265
+ }
266
+
267
+ TSharedPtr<FJsonValue> FFoliageHandlers::PaintFoliage(const TSharedPtr<FJsonObject>& Params)
268
+ {
269
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
270
+
271
+ FString FoliageTypePath;
272
+ if (!Params->TryGetStringField(TEXT("foliageType"), FoliageTypePath))
273
+ {
274
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'foliageType' parameter"));
275
+ Result->SetBoolField(TEXT("success"), false);
276
+ return MakeShared<FJsonValueObject>(Result);
277
+ }
278
+
279
+ const TSharedPtr<FJsonObject>* LocationObj = nullptr;
280
+ if (!Params->TryGetObjectField(TEXT("location"), LocationObj) || !LocationObj || !(*LocationObj).IsValid())
281
+ {
282
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'location' parameter (object with x, y, z)"));
283
+ Result->SetBoolField(TEXT("success"), false);
284
+ return MakeShared<FJsonValueObject>(Result);
285
+ }
286
+
287
+ double LocX = 0, LocY = 0, LocZ = 0;
288
+ (*LocationObj)->TryGetNumberField(TEXT("x"), LocX);
289
+ (*LocationObj)->TryGetNumberField(TEXT("y"), LocY);
290
+ (*LocationObj)->TryGetNumberField(TEXT("z"), LocZ);
291
+
292
+ double PaintRadius = 500.0;
293
+ Params->TryGetNumberField(TEXT("radius"), PaintRadius);
294
+
295
+ double PaintDensity = 100.0;
296
+ Params->TryGetNumberField(TEXT("density"), PaintDensity);
297
+
298
+ // Foliage painting is not directly exposed as a C++ editor API.
299
+ // The FoliageEdMode / FoliageEditorLibrary is internal to the foliage editor mode
300
+ // and cannot be easily called programmatically from a plugin.
301
+ // Use execute_python as a fallback to invoke the Python FoliageEditorLibrary if available.
302
+ Result->SetStringField(TEXT("foliageType"), FoliageTypePath);
303
+
304
+ TSharedPtr<FJsonObject> LocationResult = MakeShared<FJsonObject>();
305
+ LocationResult->SetNumberField(TEXT("x"), LocX);
306
+ LocationResult->SetNumberField(TEXT("y"), LocY);
307
+ LocationResult->SetNumberField(TEXT("z"), LocZ);
308
+ Result->SetObjectField(TEXT("location"), LocationResult);
309
+ Result->SetNumberField(TEXT("radius"), PaintRadius);
310
+ Result->SetNumberField(TEXT("density"), PaintDensity);
311
+
312
+ Result->SetBoolField(TEXT("success"), false);
313
+ Result->SetStringField(TEXT("note"),
314
+ TEXT("Foliage painting requires FoliageEdMode which is not accessible from C++ plugins. ")
315
+ TEXT("Use the execute_python handler with unreal.FoliageEditorLibrary.paint_foliage() if available, ")
316
+ TEXT("or manually paint in the editor foliage tool."));
317
+
318
+ return MakeShared<FJsonValueObject>(Result);
319
+ }
320
+
321
+ TSharedPtr<FJsonValue> FFoliageHandlers::EraseFoliage(const TSharedPtr<FJsonObject>& Params)
322
+ {
323
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
324
+
325
+ const TSharedPtr<FJsonObject>* LocationObj = nullptr;
326
+ if (!Params->TryGetObjectField(TEXT("location"), LocationObj) || !LocationObj || !(*LocationObj).IsValid())
327
+ {
328
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'location' parameter (object with x, y, z)"));
329
+ Result->SetBoolField(TEXT("success"), false);
330
+ return MakeShared<FJsonValueObject>(Result);
331
+ }
332
+
333
+ double LocX = 0, LocY = 0, LocZ = 0;
334
+ (*LocationObj)->TryGetNumberField(TEXT("x"), LocX);
335
+ (*LocationObj)->TryGetNumberField(TEXT("y"), LocY);
336
+ (*LocationObj)->TryGetNumberField(TEXT("z"), LocZ);
337
+
338
+ double EraseRadius = 500.0;
339
+ Params->TryGetNumberField(TEXT("radius"), EraseRadius);
340
+
341
+ FString FoliageTypeFilter;
342
+ Params->TryGetStringField(TEXT("foliageType"), FoliageTypeFilter);
343
+
344
+ // Foliage erasure, like painting, is internal to FoliageEdMode.
345
+ // Provide the same execute_python fallback note.
346
+ TSharedPtr<FJsonObject> LocationResult = MakeShared<FJsonObject>();
347
+ LocationResult->SetNumberField(TEXT("x"), LocX);
348
+ LocationResult->SetNumberField(TEXT("y"), LocY);
349
+ LocationResult->SetNumberField(TEXT("z"), LocZ);
350
+ Result->SetObjectField(TEXT("location"), LocationResult);
351
+ Result->SetNumberField(TEXT("radius"), EraseRadius);
352
+
353
+ if (!FoliageTypeFilter.IsEmpty())
354
+ {
355
+ Result->SetStringField(TEXT("foliageTypeFilter"), FoliageTypeFilter);
356
+ }
357
+
358
+ Result->SetBoolField(TEXT("success"), false);
359
+ Result->SetStringField(TEXT("note"),
360
+ TEXT("Foliage erasure requires FoliageEdMode which is not accessible from C++ plugins. ")
361
+ TEXT("Use the execute_python handler with unreal.FoliageEditorLibrary.erase_foliage() if available, ")
362
+ TEXT("or manually erase in the editor foliage tool."));
363
+
364
+ return MakeShared<FJsonValueObject>(Result);
365
+ }
366
+
367
+ TSharedPtr<FJsonValue> FFoliageHandlers::SampleFoliageInstances(const TSharedPtr<FJsonObject>& Params)
368
+ {
369
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
370
+
371
+ // Parse center location
372
+ const TSharedPtr<FJsonObject>* CenterObj = nullptr;
373
+ if (!Params->TryGetObjectField(TEXT("center"), CenterObj) || !CenterObj || !(*CenterObj).IsValid())
374
+ {
375
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'center' parameter (object with x, y, z)"));
376
+ Result->SetBoolField(TEXT("success"), false);
377
+ return MakeShared<FJsonValueObject>(Result);
378
+ }
379
+
380
+ double CenterX = 0, CenterY = 0, CenterZ = 0;
381
+ (*CenterObj)->TryGetNumberField(TEXT("x"), CenterX);
382
+ (*CenterObj)->TryGetNumberField(TEXT("y"), CenterY);
383
+ (*CenterObj)->TryGetNumberField(TEXT("z"), CenterZ);
384
+ FVector Center(CenterX, CenterY, CenterZ);
385
+
386
+ double Radius = 1000.0;
387
+ Params->TryGetNumberField(TEXT("radius"), Radius);
388
+ double RadiusSq = Radius * Radius;
389
+
390
+ int32 Limit = 100;
391
+ if (Params->HasField(TEXT("limit")))
392
+ {
393
+ Limit = static_cast<int32>(Params->GetNumberField(TEXT("limit")));
394
+ }
395
+
396
+ FString FoliageTypeFilter;
397
+ Params->TryGetStringField(TEXT("foliageType"), FoliageTypeFilter);
398
+
399
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
400
+ if (!World)
401
+ {
402
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
403
+ Result->SetBoolField(TEXT("success"), false);
404
+ return MakeShared<FJsonValueObject>(Result);
405
+ }
406
+
407
+ TArray<TSharedPtr<FJsonValue>> InstancesArray;
408
+
409
+ for (TActorIterator<AInstancedFoliageActor> It(World); It; ++It)
410
+ {
411
+ AInstancedFoliageActor* FoliageActor = *It;
412
+ if (!FoliageActor) continue;
413
+ if (InstancesArray.Num() >= Limit) break;
414
+
415
+ const auto& FoliageInfoMap = FoliageActor->GetFoliageInfos();
416
+ for (const auto& Pair : FoliageInfoMap)
417
+ {
418
+ if (InstancesArray.Num() >= Limit) break;
419
+
420
+ UFoliageType* FoliageType = Pair.Key;
421
+ const FFoliageInfo& FoliageInfo = *Pair.Value;
422
+
423
+ if (!FoliageType) continue;
424
+
425
+ FString TypeName = FoliageType->GetName();
426
+
427
+ // Apply foliage type filter if specified
428
+ if (!FoliageTypeFilter.IsEmpty() && !TypeName.Contains(FoliageTypeFilter))
429
+ {
430
+ continue;
431
+ }
432
+
433
+ // Get the instanced static mesh component for mesh info
434
+ FString MeshName = TEXT("Unknown");
435
+ UFoliageType_InstancedStaticMesh* ISMType = Cast<UFoliageType_InstancedStaticMesh>(FoliageType);
436
+ if (ISMType && ISMType->Mesh)
437
+ {
438
+ MeshName = ISMType->Mesh->GetName();
439
+ }
440
+
441
+ // UE 5.7: Instances array is private; use the HISM component for transforms
442
+ UHierarchicalInstancedStaticMeshComponent* HISMComp = FoliageInfo.GetComponent();
443
+ if (!HISMComp) continue;
444
+
445
+ int32 NumInstances = HISMComp->GetInstanceCount();
446
+ for (int32 i = 0; i < NumInstances; ++i)
447
+ {
448
+ if (InstancesArray.Num() >= Limit) break;
449
+
450
+ FTransform InstanceTransform;
451
+ HISMComp->GetInstanceTransform(i, InstanceTransform, /*bWorldSpace=*/ true);
452
+ FVector InstanceLocation = InstanceTransform.GetLocation();
453
+ double DistSq = FVector::DistSquared(Center, InstanceLocation);
454
+
455
+ if (DistSq <= RadiusSq)
456
+ {
457
+ double Distance = FMath::Sqrt(DistSq);
458
+
459
+ TSharedPtr<FJsonObject> InstanceObj = MakeShared<FJsonObject>();
460
+ InstanceObj->SetStringField(TEXT("foliageType"), TypeName);
461
+ InstanceObj->SetStringField(TEXT("mesh"), MeshName);
462
+
463
+ TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
464
+ LocObj->SetNumberField(TEXT("x"), InstanceLocation.X);
465
+ LocObj->SetNumberField(TEXT("y"), InstanceLocation.Y);
466
+ LocObj->SetNumberField(TEXT("z"), InstanceLocation.Z);
467
+ InstanceObj->SetObjectField(TEXT("location"), LocObj);
468
+
469
+ InstanceObj->SetNumberField(TEXT("distance"), FMath::RoundToFloat(Distance * 10.0f) / 10.0f);
470
+
471
+ // Extract rotation and scale from the instance transform
472
+ FRotator InstanceRotation = InstanceTransform.Rotator();
473
+ TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
474
+ RotObj->SetNumberField(TEXT("pitch"), InstanceRotation.Pitch);
475
+ RotObj->SetNumberField(TEXT("yaw"), InstanceRotation.Yaw);
476
+ RotObj->SetNumberField(TEXT("roll"), InstanceRotation.Roll);
477
+ InstanceObj->SetObjectField(TEXT("rotation"), RotObj);
478
+
479
+ FVector InstanceScale = InstanceTransform.GetScale3D();
480
+ TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
481
+ ScaleObj->SetNumberField(TEXT("x"), InstanceScale.X);
482
+ ScaleObj->SetNumberField(TEXT("y"), InstanceScale.Y);
483
+ ScaleObj->SetNumberField(TEXT("z"), InstanceScale.Z);
484
+ InstanceObj->SetObjectField(TEXT("scale"), ScaleObj);
485
+
486
+ InstancesArray.Add(MakeShared<FJsonValueObject>(InstanceObj));
487
+ }
488
+ }
489
+ }
490
+ }
491
+
492
+ TSharedPtr<FJsonObject> CenterResult = MakeShared<FJsonObject>();
493
+ CenterResult->SetNumberField(TEXT("x"), CenterX);
494
+ CenterResult->SetNumberField(TEXT("y"), CenterY);
495
+ CenterResult->SetNumberField(TEXT("z"), CenterZ);
496
+
497
+ Result->SetObjectField(TEXT("center"), CenterResult);
498
+ Result->SetNumberField(TEXT("radius"), Radius);
499
+ Result->SetNumberField(TEXT("instanceCount"), InstancesArray.Num());
500
+ Result->SetNumberField(TEXT("limit"), Limit);
501
+ Result->SetArrayField(TEXT("instances"), InstancesArray);
502
+
503
+ if (!FoliageTypeFilter.IsEmpty())
504
+ {
505
+ Result->SetStringField(TEXT("foliageTypeFilter"), FoliageTypeFilter);
506
+ }
507
+
508
+ Result->SetBoolField(TEXT("success"), true);
509
+ return MakeShared<FJsonValueObject>(Result);
510
+ }
511
+
512
+ TSharedPtr<FJsonValue> FFoliageHandlers::CreateFoliageLayer(const TSharedPtr<FJsonObject>& Params)
513
+ {
514
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
515
+
516
+ FString AssetName;
517
+ if (!Params->TryGetStringField(TEXT("name"), AssetName))
518
+ {
519
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'name' parameter"));
520
+ Result->SetBoolField(TEXT("success"), false);
521
+ return MakeShared<FJsonValueObject>(Result);
522
+ }
523
+
524
+ FString PackagePath = TEXT("/Game/Foliage");
525
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
526
+
527
+ FString MeshPath;
528
+ Params->TryGetStringField(TEXT("meshPath"), MeshPath);
529
+
530
+ FString AssetType = TEXT("FoliageType");
531
+ Params->TryGetStringField(TEXT("assetType"), AssetType);
532
+
533
+ FString FullPath = PackagePath / AssetName;
534
+
535
+ if (AssetType == TEXT("LandscapeGrassType"))
536
+ {
537
+ // Create a LandscapeGrassType asset
538
+ FString PackageFullPath = PackagePath / AssetName;
539
+ UPackage* Package = CreatePackage(*PackageFullPath);
540
+ if (!Package)
541
+ {
542
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
543
+ Result->SetBoolField(TEXT("success"), false);
544
+ return MakeShared<FJsonValueObject>(Result);
545
+ }
546
+
547
+ ULandscapeGrassType* GrassType = NewObject<ULandscapeGrassType>(Package, *AssetName, RF_Public | RF_Standalone);
548
+ if (!GrassType)
549
+ {
550
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create LandscapeGrassType object"));
551
+ Result->SetBoolField(TEXT("success"), false);
552
+ return MakeShared<FJsonValueObject>(Result);
553
+ }
554
+
555
+ // If a mesh path is provided, add it as a grass variety
556
+ if (!MeshPath.IsEmpty())
557
+ {
558
+ UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, *MeshPath);
559
+ if (Mesh)
560
+ {
561
+ FGrassVariety Variety;
562
+ Variety.GrassMesh = Mesh;
563
+ GrassType->GrassVarieties.Add(Variety);
564
+ }
565
+ }
566
+
567
+ // Notify asset registry
568
+ FAssetRegistryModule::AssetCreated(GrassType);
569
+ Package->MarkPackageDirty();
570
+
571
+ // Save the asset
572
+ UEditorAssetLibrary::SaveAsset(PackageFullPath, false);
573
+
574
+ Result->SetStringField(TEXT("path"), PackageFullPath);
575
+ Result->SetStringField(TEXT("name"), GrassType->GetName());
576
+ Result->SetStringField(TEXT("className"), GrassType->GetClass()->GetName());
577
+ Result->SetStringField(TEXT("assetType"), TEXT("LandscapeGrassType"));
578
+ if (!MeshPath.IsEmpty())
579
+ {
580
+ Result->SetStringField(TEXT("meshPath"), MeshPath);
581
+ }
582
+ Result->SetBoolField(TEXT("success"), true);
583
+ }
584
+ else
585
+ {
586
+ // Create a FoliageType_InstancedStaticMesh asset (default)
587
+ FString PackageFullPath = PackagePath / AssetName;
588
+ UPackage* Package = CreatePackage(*PackageFullPath);
589
+ if (!Package)
590
+ {
591
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
592
+ Result->SetBoolField(TEXT("success"), false);
593
+ return MakeShared<FJsonValueObject>(Result);
594
+ }
595
+
596
+ UFoliageType_InstancedStaticMesh* FoliageType = NewObject<UFoliageType_InstancedStaticMesh>(
597
+ Package, *AssetName, RF_Public | RF_Standalone);
598
+ if (!FoliageType)
599
+ {
600
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create FoliageType object"));
601
+ Result->SetBoolField(TEXT("success"), false);
602
+ return MakeShared<FJsonValueObject>(Result);
603
+ }
604
+
605
+ // Set mesh if provided
606
+ if (!MeshPath.IsEmpty())
607
+ {
608
+ UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, *MeshPath);
609
+ if (Mesh)
610
+ {
611
+ FoliageType->Mesh = Mesh;
612
+ }
613
+ }
614
+
615
+ // Notify asset registry
616
+ FAssetRegistryModule::AssetCreated(FoliageType);
617
+ Package->MarkPackageDirty();
618
+
619
+ // Save the asset
620
+ UEditorAssetLibrary::SaveAsset(PackageFullPath, false);
621
+
622
+ Result->SetStringField(TEXT("path"), PackageFullPath);
623
+ Result->SetStringField(TEXT("name"), FoliageType->GetName());
624
+ Result->SetStringField(TEXT("className"), FoliageType->GetClass()->GetName());
625
+ Result->SetStringField(TEXT("assetType"), TEXT("FoliageType"));
626
+ if (!MeshPath.IsEmpty())
627
+ {
628
+ Result->SetStringField(TEXT("meshPath"), MeshPath);
629
+ }
630
+ Result->SetBoolField(TEXT("success"), true);
631
+ }
632
+
633
+ return MakeShared<FJsonValueObject>(Result);
634
+ }
635
+
636
+ TSharedPtr<FJsonValue> FFoliageHandlers::SetFoliageTypeSettings(const TSharedPtr<FJsonObject>& Params)
637
+ {
638
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
639
+
640
+ // Accept either foliageTypePath or foliageTypeName for lookup
641
+ FString FoliageTypePath;
642
+ if (!Params->TryGetStringField(TEXT("foliageTypePath"), FoliageTypePath))
643
+ {
644
+ Params->TryGetStringField(TEXT("foliageTypeName"), FoliageTypePath);
645
+ }
646
+ if (FoliageTypePath.IsEmpty())
647
+ {
648
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'foliageTypePath' or 'foliageTypeName' parameter"));
649
+ Result->SetBoolField(TEXT("success"), false);
650
+ return MakeShared<FJsonValueObject>(Result);
651
+ }
652
+
653
+ const TSharedPtr<FJsonObject>* SettingsObj = nullptr;
654
+ if (!Params->TryGetObjectField(TEXT("settings"), SettingsObj) || !SettingsObj || !(*SettingsObj).IsValid())
655
+ {
656
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'settings' parameter (object with property name/value pairs)"));
657
+ Result->SetBoolField(TEXT("success"), false);
658
+ return MakeShared<FJsonValueObject>(Result);
659
+ }
660
+
661
+ // Try to load by path first; if not found, search by name in the world
662
+ UFoliageType* FoliageType = LoadObject<UFoliageType>(nullptr, *FoliageTypePath);
663
+
664
+ if (!FoliageType)
665
+ {
666
+ // Search by name in world foliage actors
667
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
668
+ if (World)
669
+ {
670
+ for (TActorIterator<AInstancedFoliageActor> It(World); It; ++It)
671
+ {
672
+ AInstancedFoliageActor* FoliageActor = *It;
673
+ if (!FoliageActor) continue;
674
+
675
+ const auto& FoliageInfoMap = FoliageActor->GetFoliageInfos();
676
+ for (const auto& Pair : FoliageInfoMap)
677
+ {
678
+ if (Pair.Key && (Pair.Key->GetName() == FoliageTypePath || Pair.Key->GetPathName() == FoliageTypePath))
679
+ {
680
+ FoliageType = Pair.Key;
681
+ break;
682
+ }
683
+ }
684
+ if (FoliageType) break;
685
+ }
686
+ }
687
+ }
688
+
689
+ if (!FoliageType)
690
+ {
691
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Foliage type not found: %s"), *FoliageTypePath));
692
+ Result->SetBoolField(TEXT("success"), false);
693
+ return MakeShared<FJsonValueObject>(Result);
694
+ }
695
+
696
+ // Apply settings via property reflection
697
+ TArray<FString> AppliedSettings;
698
+ TArray<FString> FailedSettings;
699
+
700
+ for (const auto& KV : (*SettingsObj)->Values)
701
+ {
702
+ FString PropertyName = KV.Key;
703
+ FString PropertyValue;
704
+
705
+ // Convert the JSON value to a string for ImportText
706
+ if (KV.Value->Type == EJson::String)
707
+ {
708
+ PropertyValue = KV.Value->AsString();
709
+ }
710
+ else if (KV.Value->Type == EJson::Number)
711
+ {
712
+ PropertyValue = FString::SanitizeFloat(KV.Value->AsNumber());
713
+ }
714
+ else if (KV.Value->Type == EJson::Boolean)
715
+ {
716
+ PropertyValue = KV.Value->AsBool() ? TEXT("True") : TEXT("False");
717
+ }
718
+ else
719
+ {
720
+ // For complex types, try serializing as string
721
+ TSharedPtr<FJsonValue> Val = KV.Value;
722
+ if (Val.IsValid())
723
+ {
724
+ PropertyValue = KV.Value->AsString();
725
+ }
726
+ }
727
+
728
+ FProperty* Property = FoliageType->GetClass()->FindPropertyByName(FName(*PropertyName));
729
+ if (!Property)
730
+ {
731
+ FailedSettings.Add(FString::Printf(TEXT("%s: property not found"), *PropertyName));
732
+ continue;
733
+ }
734
+
735
+ void* PropertyAddr = Property->ContainerPtrToValuePtr<void>(FoliageType);
736
+ const TCHAR* ImportResult = Property->ImportText_Direct(*PropertyValue, PropertyAddr, FoliageType, PPF_None);
737
+ if (ImportResult == nullptr)
738
+ {
739
+ FailedSettings.Add(FString::Printf(TEXT("%s: failed to set value '%s'"), *PropertyName, *PropertyValue));
740
+ }
741
+ else
742
+ {
743
+ AppliedSettings.Add(PropertyName);
744
+ }
745
+ }
746
+
747
+ // Mark the foliage type as dirty
748
+ FoliageType->MarkPackageDirty();
749
+
750
+ // Save the asset if it has a valid package path
751
+ FString PackagePath = FoliageType->GetPathName();
752
+ if (PackagePath.Contains(TEXT("/Game/")))
753
+ {
754
+ UEditorAssetLibrary::SaveAsset(FoliageType->GetOutermost()->GetName(), false);
755
+ }
756
+
757
+ Result->SetStringField(TEXT("foliageType"), FoliageType->GetName());
758
+ Result->SetStringField(TEXT("path"), FoliageType->GetPathName());
759
+
760
+ TArray<TSharedPtr<FJsonValue>> AppliedArray;
761
+ for (const FString& S : AppliedSettings)
762
+ {
763
+ AppliedArray.Add(MakeShared<FJsonValueString>(S));
764
+ }
765
+ Result->SetArrayField(TEXT("appliedSettings"), AppliedArray);
766
+
767
+ if (FailedSettings.Num() > 0)
768
+ {
769
+ TArray<TSharedPtr<FJsonValue>> FailedArray;
770
+ for (const FString& S : FailedSettings)
771
+ {
772
+ FailedArray.Add(MakeShared<FJsonValueString>(S));
773
+ }
774
+ Result->SetArrayField(TEXT("failedSettings"), FailedArray);
775
+ }
776
+
777
+ Result->SetBoolField(TEXT("success"), FailedSettings.Num() == 0);
778
+ return MakeShared<FJsonValueObject>(Result);
779
+ }
780
+
781
+ TSharedPtr<FJsonValue> FFoliageHandlers::CreateFoliageType(const TSharedPtr<FJsonObject>& Params)
782
+ {
783
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
784
+
785
+ FString MeshPath;
786
+ if (!Params->TryGetStringField(TEXT("meshPath"), MeshPath))
787
+ {
788
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'meshPath' parameter"));
789
+ Result->SetBoolField(TEXT("success"), false);
790
+ return MakeShared<FJsonValueObject>(Result);
791
+ }
792
+
793
+ // Load the mesh first to verify it exists
794
+ UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, *MeshPath);
795
+ if (!Mesh)
796
+ {
797
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Static mesh not found: %s"), *MeshPath));
798
+ Result->SetBoolField(TEXT("success"), false);
799
+ return MakeShared<FJsonValueObject>(Result);
800
+ }
801
+
802
+ // Determine asset name - use provided name or derive from mesh name
803
+ FString AssetName;
804
+ if (!Params->TryGetStringField(TEXT("name"), AssetName))
805
+ {
806
+ AssetName = FString::Printf(TEXT("FT_%s"), *Mesh->GetName());
807
+ }
808
+
809
+ FString PackagePath = TEXT("/Game/Foliage");
810
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
811
+
812
+ FString PackageFullPath = PackagePath / AssetName;
813
+ UPackage* Package = CreatePackage(*PackageFullPath);
814
+ if (!Package)
815
+ {
816
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
817
+ Result->SetBoolField(TEXT("success"), false);
818
+ return MakeShared<FJsonValueObject>(Result);
819
+ }
820
+
821
+ UFoliageType_InstancedStaticMesh* FoliageType = NewObject<UFoliageType_InstancedStaticMesh>(
822
+ Package, *AssetName, RF_Public | RF_Standalone);
823
+ if (!FoliageType)
824
+ {
825
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create FoliageType object"));
826
+ Result->SetBoolField(TEXT("success"), false);
827
+ return MakeShared<FJsonValueObject>(Result);
828
+ }
829
+
830
+ FoliageType->Mesh = Mesh;
831
+
832
+ // Apply optional settings if provided
833
+ const TSharedPtr<FJsonObject>* SettingsObj = nullptr;
834
+ if (Params->TryGetObjectField(TEXT("settings"), SettingsObj) && SettingsObj && (*SettingsObj).IsValid())
835
+ {
836
+ for (const auto& KV : (*SettingsObj)->Values)
837
+ {
838
+ FString PropertyName = KV.Key;
839
+ FString PropertyValue;
840
+
841
+ if (KV.Value->Type == EJson::String)
842
+ {
843
+ PropertyValue = KV.Value->AsString();
844
+ }
845
+ else if (KV.Value->Type == EJson::Number)
846
+ {
847
+ PropertyValue = FString::SanitizeFloat(KV.Value->AsNumber());
848
+ }
849
+ else if (KV.Value->Type == EJson::Boolean)
850
+ {
851
+ PropertyValue = KV.Value->AsBool() ? TEXT("True") : TEXT("False");
852
+ }
853
+
854
+ FProperty* Property = FoliageType->GetClass()->FindPropertyByName(FName(*PropertyName));
855
+ if (Property)
856
+ {
857
+ void* PropertyAddr = Property->ContainerPtrToValuePtr<void>(FoliageType);
858
+ Property->ImportText_Direct(*PropertyValue, PropertyAddr, FoliageType, PPF_None);
859
+ }
860
+ }
861
+ }
862
+
863
+ // Notify asset registry and save
864
+ FAssetRegistryModule::AssetCreated(FoliageType);
865
+ Package->MarkPackageDirty();
866
+ UEditorAssetLibrary::SaveAsset(PackageFullPath, false);
867
+
868
+ Result->SetStringField(TEXT("path"), PackageFullPath);
869
+ Result->SetStringField(TEXT("name"), FoliageType->GetName());
870
+ Result->SetStringField(TEXT("className"), FoliageType->GetClass()->GetName());
871
+ Result->SetStringField(TEXT("meshPath"), MeshPath);
872
+ Result->SetStringField(TEXT("meshName"), Mesh->GetName());
873
+ Result->SetBoolField(TEXT("success"), true);
874
+
875
+ return MakeShared<FJsonValueObject>(Result);
876
+ }