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,898 @@
1
+ #include "LandscapeHandlers.h"
2
+ #include "HandlerRegistry.h"
3
+ #include "Dom/JsonObject.h"
4
+ #include "Dom/JsonValue.h"
5
+ #include "Editor.h"
6
+ #include "Engine/World.h"
7
+ #include "EngineUtils.h"
8
+ #include "Landscape.h"
9
+ #include "LandscapeProxy.h"
10
+ #include "LandscapeInfo.h"
11
+ #include "LandscapeComponent.h"
12
+ #include "LandscapeSplineActor.h"
13
+ #include "LandscapeSplinesComponent.h"
14
+ #include "LandscapeSplineControlPoint.h"
15
+ #include "LandscapeSplineSegment.h"
16
+ #include "Kismet/KismetSystemLibrary.h"
17
+ #include "Misc/FileHelper.h"
18
+ #include "Materials/MaterialInterface.h"
19
+ #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
20
+ #include "Components/PrimitiveComponent.h"
21
+ #include "LandscapeLayerInfoObject.h"
22
+ #include "UObject/Package.h"
23
+ #include "AssetRegistry/AssetRegistryModule.h"
24
+ #include "AssetToolsModule.h"
25
+ #include "IAssetTools.h"
26
+
27
+ void FLandscapeHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
28
+ {
29
+ Registry.RegisterHandler(TEXT("get_landscape_info"), &GetLandscapeInfo);
30
+ Registry.RegisterHandler(TEXT("list_landscape_layers"), &ListLandscapeLayers);
31
+ Registry.RegisterHandler(TEXT("sample_landscape"), &SampleLandscape);
32
+ Registry.RegisterHandler(TEXT("list_landscape_splines"), &ListLandscapeSplines);
33
+ Registry.RegisterHandler(TEXT("get_landscape_component"), &GetLandscapeComponent);
34
+ Registry.RegisterHandler(TEXT("sculpt_landscape"), &SculptLandscape);
35
+ Registry.RegisterHandler(TEXT("paint_landscape_layer"), &PaintLandscapeLayer);
36
+ Registry.RegisterHandler(TEXT("import_heightmap"), &ImportHeightmap);
37
+ Registry.RegisterHandler(TEXT("set_landscape_material"), &SetLandscapeMaterial);
38
+ Registry.RegisterHandler(TEXT("get_landscape_bounds"), &GetLandscapeBounds);
39
+ Registry.RegisterHandler(TEXT("add_landscape_layer_info"), &AddLandscapeLayerInfo);
40
+ Registry.RegisterHandler(TEXT("import_landscape_heightmap"), &ImportHeightmap);
41
+ }
42
+
43
+ TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeInfo(const TSharedPtr<FJsonObject>& Params)
44
+ {
45
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
46
+
47
+ UWorld* World = GEditor->GetEditorWorldContext().World();
48
+ if (!World)
49
+ {
50
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
51
+ Result->SetBoolField(TEXT("success"), false);
52
+ return MakeShared<FJsonValueObject>(Result);
53
+ }
54
+
55
+ // Find landscape proxies in the world
56
+ TArray<TSharedPtr<FJsonValue>> LandscapeArray;
57
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
58
+ {
59
+ ALandscapeProxy* Landscape = *It;
60
+ if (!Landscape) continue;
61
+
62
+ TSharedPtr<FJsonObject> LandscapeObj = MakeShared<FJsonObject>();
63
+ LandscapeObj->SetStringField(TEXT("name"), Landscape->GetName());
64
+ LandscapeObj->SetStringField(TEXT("class"), Landscape->GetClass()->GetName());
65
+
66
+ // Get component count
67
+ TArray<ULandscapeComponent*> LandscapeComponents;
68
+ Landscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
69
+ LandscapeObj->SetNumberField(TEXT("componentCount"), LandscapeComponents.Num());
70
+
71
+ // Get bounds
72
+ FBox Bounds = Landscape->GetComponentsBoundingBox();
73
+ if (Bounds.IsValid)
74
+ {
75
+ TSharedPtr<FJsonObject> BoundsObj = MakeShared<FJsonObject>();
76
+ BoundsObj->SetNumberField(TEXT("minX"), Bounds.Min.X);
77
+ BoundsObj->SetNumberField(TEXT("minY"), Bounds.Min.Y);
78
+ BoundsObj->SetNumberField(TEXT("minZ"), Bounds.Min.Z);
79
+ BoundsObj->SetNumberField(TEXT("maxX"), Bounds.Max.X);
80
+ BoundsObj->SetNumberField(TEXT("maxY"), Bounds.Max.Y);
81
+ BoundsObj->SetNumberField(TEXT("maxZ"), Bounds.Max.Z);
82
+
83
+ FVector Size = Bounds.GetSize();
84
+ BoundsObj->SetNumberField(TEXT("sizeX"), Size.X);
85
+ BoundsObj->SetNumberField(TEXT("sizeY"), Size.Y);
86
+ BoundsObj->SetNumberField(TEXT("sizeZ"), Size.Z);
87
+ LandscapeObj->SetObjectField(TEXT("bounds"), BoundsObj);
88
+ }
89
+
90
+ // Get location
91
+ FVector Location = Landscape->GetActorLocation();
92
+ LandscapeObj->SetNumberField(TEXT("locationX"), Location.X);
93
+ LandscapeObj->SetNumberField(TEXT("locationY"), Location.Y);
94
+ LandscapeObj->SetNumberField(TEXT("locationZ"), Location.Z);
95
+
96
+ LandscapeArray.Add(MakeShared<FJsonValueObject>(LandscapeObj));
97
+ }
98
+
99
+ if (LandscapeArray.Num() == 0)
100
+ {
101
+ Result->SetStringField(TEXT("landscape"), TEXT("none"));
102
+ }
103
+ else
104
+ {
105
+ Result->SetArrayField(TEXT("landscapes"), LandscapeArray);
106
+ }
107
+
108
+ Result->SetNumberField(TEXT("count"), LandscapeArray.Num());
109
+ Result->SetBoolField(TEXT("success"), true);
110
+ return MakeShared<FJsonValueObject>(Result);
111
+ }
112
+
113
+ TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeLayers(const TSharedPtr<FJsonObject>& Params)
114
+ {
115
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
116
+
117
+ UWorld* World = GEditor->GetEditorWorldContext().World();
118
+ if (!World)
119
+ {
120
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
121
+ Result->SetBoolField(TEXT("success"), false);
122
+ return MakeShared<FJsonValueObject>(Result);
123
+ }
124
+
125
+ TArray<TSharedPtr<FJsonValue>> LayerArray;
126
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
127
+ {
128
+ ALandscapeProxy* Landscape = *It;
129
+ if (!Landscape) continue;
130
+
131
+ ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
132
+ if (LandscapeInfo)
133
+ {
134
+ for (const FLandscapeInfoLayerSettings& LayerSettings : LandscapeInfo->Layers)
135
+ {
136
+ if (LayerSettings.LayerInfoObj)
137
+ {
138
+ TSharedPtr<FJsonObject> LayerObj = MakeShared<FJsonObject>();
139
+ LayerObj->SetStringField(TEXT("name"), LayerSettings.GetLayerName().ToString());
140
+ LayerObj->SetStringField(TEXT("landscapeName"), Landscape->GetName());
141
+ LayerArray.Add(MakeShared<FJsonValueObject>(LayerObj));
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ Result->SetArrayField(TEXT("layers"), LayerArray);
148
+ Result->SetNumberField(TEXT("count"), LayerArray.Num());
149
+ Result->SetBoolField(TEXT("success"), true);
150
+ return MakeShared<FJsonValueObject>(Result);
151
+ }
152
+
153
+ TSharedPtr<FJsonValue> FLandscapeHandlers::SampleLandscape(const TSharedPtr<FJsonObject>& Params)
154
+ {
155
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
156
+
157
+ const TSharedPtr<FJsonObject>* PointObj = nullptr;
158
+ if (!Params->TryGetObjectField(TEXT("point"), PointObj))
159
+ {
160
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'point' parameter"));
161
+ Result->SetBoolField(TEXT("success"), false);
162
+ return MakeShared<FJsonValueObject>(Result);
163
+ }
164
+
165
+ FVector Point;
166
+ Point.X = (*PointObj)->GetNumberField(TEXT("x"));
167
+ Point.Y = (*PointObj)->GetNumberField(TEXT("y"));
168
+ Point.Z = (*PointObj)->GetNumberField(TEXT("z"));
169
+
170
+ UWorld* World = GEditor->GetEditorWorldContext().World();
171
+ if (!World)
172
+ {
173
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
174
+ Result->SetBoolField(TEXT("success"), false);
175
+ return MakeShared<FJsonValueObject>(Result);
176
+ }
177
+
178
+ // Find the first landscape and sample height
179
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
180
+ {
181
+ ALandscapeProxy* Landscape = *It;
182
+ if (!Landscape) continue;
183
+
184
+ // Use line trace to get the landscape height at the given point
185
+ FVector TraceStart(Point.X, Point.Y, Point.Z + 100000.0f);
186
+ FVector TraceEnd(Point.X, Point.Y, Point.Z - 100000.0f);
187
+
188
+ FHitResult HitResult;
189
+ FCollisionQueryParams QueryParams;
190
+ QueryParams.bTraceComplex = true;
191
+
192
+ if (World->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_WorldStatic, QueryParams))
193
+ {
194
+ if (HitResult.GetActor() && HitResult.GetActor()->IsA(ALandscapeProxy::StaticClass()))
195
+ {
196
+ Result->SetNumberField(TEXT("height"), HitResult.Location.Z);
197
+ TSharedPtr<FJsonObject> HitPoint = MakeShared<FJsonObject>();
198
+ HitPoint->SetNumberField(TEXT("x"), HitResult.Location.X);
199
+ HitPoint->SetNumberField(TEXT("y"), HitResult.Location.Y);
200
+ HitPoint->SetNumberField(TEXT("z"), HitResult.Location.Z);
201
+ Result->SetObjectField(TEXT("hitLocation"), HitPoint);
202
+
203
+ TSharedPtr<FJsonObject> Normal = MakeShared<FJsonObject>();
204
+ Normal->SetNumberField(TEXT("x"), HitResult.Normal.X);
205
+ Normal->SetNumberField(TEXT("y"), HitResult.Normal.Y);
206
+ Normal->SetNumberField(TEXT("z"), HitResult.Normal.Z);
207
+ Result->SetObjectField(TEXT("normal"), Normal);
208
+
209
+ Result->SetBoolField(TEXT("hit"), true);
210
+ Result->SetBoolField(TEXT("success"), true);
211
+ return MakeShared<FJsonValueObject>(Result);
212
+ }
213
+ }
214
+ }
215
+
216
+ Result->SetBoolField(TEXT("hit"), false);
217
+ Result->SetBoolField(TEXT("success"), true);
218
+ return MakeShared<FJsonValueObject>(Result);
219
+ }
220
+
221
+ TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeSplines(const TSharedPtr<FJsonObject>& Params)
222
+ {
223
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
224
+
225
+ UWorld* World = GEditor->GetEditorWorldContext().World();
226
+ if (!World)
227
+ {
228
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
229
+ Result->SetBoolField(TEXT("success"), false);
230
+ return MakeShared<FJsonValueObject>(Result);
231
+ }
232
+
233
+ TArray<TSharedPtr<FJsonValue>> SplineArray;
234
+
235
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
236
+ {
237
+ ALandscapeProxy* Landscape = *It;
238
+ if (!Landscape) continue;
239
+
240
+ ULandscapeSplinesComponent* SplinesComp = Landscape->GetSplinesComponent();
241
+ if (!SplinesComp) continue;
242
+
243
+ const TArray<TObjectPtr<ULandscapeSplineControlPoint>>& ControlPoints = SplinesComp->GetControlPoints();
244
+ for (const TObjectPtr<ULandscapeSplineControlPoint>& CP : ControlPoints)
245
+ {
246
+ if (!CP) continue;
247
+
248
+ TSharedPtr<FJsonObject> PointObj = MakeShared<FJsonObject>();
249
+ FVector Location = CP->Location;
250
+ PointObj->SetNumberField(TEXT("x"), Location.X);
251
+ PointObj->SetNumberField(TEXT("y"), Location.Y);
252
+ PointObj->SetNumberField(TEXT("z"), Location.Z);
253
+ PointObj->SetStringField(TEXT("landscapeName"), Landscape->GetName());
254
+ SplineArray.Add(MakeShared<FJsonValueObject>(PointObj));
255
+ }
256
+ }
257
+
258
+ Result->SetArrayField(TEXT("controlPoints"), SplineArray);
259
+ Result->SetNumberField(TEXT("count"), SplineArray.Num());
260
+ Result->SetBoolField(TEXT("success"), true);
261
+ return MakeShared<FJsonValueObject>(Result);
262
+ }
263
+
264
+ TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeComponent(const TSharedPtr<FJsonObject>& Params)
265
+ {
266
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
267
+
268
+ int32 ComponentIndex = 0;
269
+ if (Params->HasField(TEXT("componentIndex")))
270
+ {
271
+ ComponentIndex = static_cast<int32>(Params->GetNumberField(TEXT("componentIndex")));
272
+ }
273
+
274
+ UWorld* World = GEditor->GetEditorWorldContext().World();
275
+ if (!World)
276
+ {
277
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
278
+ Result->SetBoolField(TEXT("success"), false);
279
+ return MakeShared<FJsonValueObject>(Result);
280
+ }
281
+
282
+ // Collect all landscape components across all landscape proxies
283
+ TArray<ULandscapeComponent*> AllComponents;
284
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
285
+ {
286
+ ALandscapeProxy* Landscape = *It;
287
+ if (!Landscape) continue;
288
+
289
+ TArray<ULandscapeComponent*> LandscapeComponents;
290
+ Landscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
291
+ AllComponents.Append(LandscapeComponents);
292
+ }
293
+
294
+ if (ComponentIndex < 0 || ComponentIndex >= AllComponents.Num())
295
+ {
296
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Component index %d out of range (0-%d)"), ComponentIndex, AllComponents.Num() - 1));
297
+ Result->SetBoolField(TEXT("success"), false);
298
+ return MakeShared<FJsonValueObject>(Result);
299
+ }
300
+
301
+ ULandscapeComponent* Comp = AllComponents[ComponentIndex];
302
+ if (!Comp)
303
+ {
304
+ Result->SetStringField(TEXT("error"), TEXT("Component is null"));
305
+ Result->SetBoolField(TEXT("success"), false);
306
+ return MakeShared<FJsonValueObject>(Result);
307
+ }
308
+
309
+ Result->SetNumberField(TEXT("componentIndex"), ComponentIndex);
310
+ Result->SetStringField(TEXT("name"), Comp->GetName());
311
+
312
+ FVector CompLocation = Comp->GetComponentLocation();
313
+ Result->SetNumberField(TEXT("locationX"), CompLocation.X);
314
+ Result->SetNumberField(TEXT("locationY"), CompLocation.Y);
315
+ Result->SetNumberField(TEXT("locationZ"), CompLocation.Z);
316
+
317
+ Result->SetNumberField(TEXT("sectionBaseX"), Comp->SectionBaseX);
318
+ Result->SetNumberField(TEXT("sectionBaseY"), Comp->SectionBaseY);
319
+ Result->SetNumberField(TEXT("componentSizeQuads"), Comp->ComponentSizeQuads);
320
+ Result->SetNumberField(TEXT("subSections"), Comp->NumSubsections);
321
+
322
+ FBox CompBounds = Comp->Bounds.GetBox();
323
+ if (CompBounds.IsValid)
324
+ {
325
+ TSharedPtr<FJsonObject> BoundsObj = MakeShared<FJsonObject>();
326
+ BoundsObj->SetNumberField(TEXT("minX"), CompBounds.Min.X);
327
+ BoundsObj->SetNumberField(TEXT("minY"), CompBounds.Min.Y);
328
+ BoundsObj->SetNumberField(TEXT("minZ"), CompBounds.Min.Z);
329
+ BoundsObj->SetNumberField(TEXT("maxX"), CompBounds.Max.X);
330
+ BoundsObj->SetNumberField(TEXT("maxY"), CompBounds.Max.Y);
331
+ BoundsObj->SetNumberField(TEXT("maxZ"), CompBounds.Max.Z);
332
+ Result->SetObjectField(TEXT("bounds"), BoundsObj);
333
+ }
334
+
335
+ Result->SetBoolField(TEXT("success"), true);
336
+ return MakeShared<FJsonValueObject>(Result);
337
+ }
338
+
339
+ TSharedPtr<FJsonValue> FLandscapeHandlers::SculptLandscape(const TSharedPtr<FJsonObject>& Params)
340
+ {
341
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
342
+
343
+ const TSharedPtr<FJsonObject>* LocationObj = nullptr;
344
+ if (!Params->TryGetObjectField(TEXT("location"), LocationObj) || !LocationObj || !(*LocationObj).IsValid())
345
+ {
346
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'location' parameter (object with x, y)"));
347
+ Result->SetBoolField(TEXT("success"), false);
348
+ return MakeShared<FJsonValueObject>(Result);
349
+ }
350
+
351
+ double LocX = 0, LocY = 0;
352
+ (*LocationObj)->TryGetNumberField(TEXT("x"), LocX);
353
+ (*LocationObj)->TryGetNumberField(TEXT("y"), LocY);
354
+
355
+ double SculptRadius = 500.0;
356
+ Params->TryGetNumberField(TEXT("radius"), SculptRadius);
357
+
358
+ double Strength = 0.5;
359
+ Params->TryGetNumberField(TEXT("strength"), Strength);
360
+
361
+ FString Operation = TEXT("raise");
362
+ Params->TryGetStringField(TEXT("operation"), Operation);
363
+
364
+ double Falloff = 0.5;
365
+ Params->TryGetNumberField(TEXT("falloff"), Falloff);
366
+
367
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
368
+ if (!World)
369
+ {
370
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
371
+ Result->SetBoolField(TEXT("success"), false);
372
+ return MakeShared<FJsonValueObject>(Result);
373
+ }
374
+
375
+ // Verify a landscape exists by line tracing at the target location
376
+ bool bFoundLandscape = false;
377
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
378
+ {
379
+ if (*It)
380
+ {
381
+ bFoundLandscape = true;
382
+ break;
383
+ }
384
+ }
385
+
386
+ if (!bFoundLandscape)
387
+ {
388
+ Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
389
+ Result->SetBoolField(TEXT("success"), false);
390
+ return MakeShared<FJsonValueObject>(Result);
391
+ }
392
+
393
+ // Landscape sculpting is not directly exposed as a simple C++ API.
394
+ // The LandscapeEdMode (editor mode) handles sculpting internally.
395
+ // Fall back to console command approach.
396
+ FString Command = FString::Printf(
397
+ TEXT("Landscape.Sculpt X=%.1f Y=%.1f Radius=%.1f Strength=%.2f Op=%s"),
398
+ LocX, LocY, SculptRadius, Strength, *Operation);
399
+
400
+ UKismetSystemLibrary::ExecuteConsoleCommand(World, Command, nullptr);
401
+
402
+ TSharedPtr<FJsonObject> LocationResult = MakeShared<FJsonObject>();
403
+ LocationResult->SetNumberField(TEXT("x"), LocX);
404
+ LocationResult->SetNumberField(TEXT("y"), LocY);
405
+ Result->SetObjectField(TEXT("location"), LocationResult);
406
+ Result->SetNumberField(TEXT("radius"), SculptRadius);
407
+ Result->SetNumberField(TEXT("strength"), Strength);
408
+ Result->SetStringField(TEXT("operation"), Operation);
409
+ Result->SetNumberField(TEXT("falloff"), Falloff);
410
+ Result->SetStringField(TEXT("note"), TEXT("Executed via console command. Verify visually. If the console command is not supported, use execute_python with unreal.LandscapeEditorLibrary.sculpt() instead."));
411
+ Result->SetBoolField(TEXT("success"), true);
412
+
413
+ return MakeShared<FJsonValueObject>(Result);
414
+ }
415
+
416
+ TSharedPtr<FJsonValue> FLandscapeHandlers::PaintLandscapeLayer(const TSharedPtr<FJsonObject>& Params)
417
+ {
418
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
419
+
420
+ FString LayerName;
421
+ if (!Params->TryGetStringField(TEXT("layerName"), LayerName))
422
+ {
423
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'layerName' parameter"));
424
+ Result->SetBoolField(TEXT("success"), false);
425
+ return MakeShared<FJsonValueObject>(Result);
426
+ }
427
+
428
+ const TSharedPtr<FJsonObject>* LocationObj = nullptr;
429
+ if (!Params->TryGetObjectField(TEXT("location"), LocationObj) || !LocationObj || !(*LocationObj).IsValid())
430
+ {
431
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'location' parameter (object with x, y)"));
432
+ Result->SetBoolField(TEXT("success"), false);
433
+ return MakeShared<FJsonValueObject>(Result);
434
+ }
435
+
436
+ double LocX = 0, LocY = 0;
437
+ (*LocationObj)->TryGetNumberField(TEXT("x"), LocX);
438
+ (*LocationObj)->TryGetNumberField(TEXT("y"), LocY);
439
+
440
+ double PaintRadius = 500.0;
441
+ Params->TryGetNumberField(TEXT("radius"), PaintRadius);
442
+
443
+ double Strength = 1.0;
444
+ Params->TryGetNumberField(TEXT("strength"), Strength);
445
+
446
+ double Falloff = 0.5;
447
+ Params->TryGetNumberField(TEXT("falloff"), Falloff);
448
+
449
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
450
+ if (!World)
451
+ {
452
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
453
+ Result->SetBoolField(TEXT("success"), false);
454
+ return MakeShared<FJsonValueObject>(Result);
455
+ }
456
+
457
+ // Verify a landscape exists
458
+ bool bFoundLandscape = false;
459
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
460
+ {
461
+ if (*It)
462
+ {
463
+ bFoundLandscape = true;
464
+ break;
465
+ }
466
+ }
467
+
468
+ if (!bFoundLandscape)
469
+ {
470
+ Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
471
+ Result->SetBoolField(TEXT("success"), false);
472
+ return MakeShared<FJsonValueObject>(Result);
473
+ }
474
+
475
+ // Landscape layer painting is internal to LandscapeEdMode.
476
+ // The C++ API for painting layers requires the landscape editor mode to be active
477
+ // and is not trivially accessible from plugins.
478
+ // Provide the fallback note for using execute_python.
479
+ TSharedPtr<FJsonObject> LocationResult = MakeShared<FJsonObject>();
480
+ LocationResult->SetNumberField(TEXT("x"), LocX);
481
+ LocationResult->SetNumberField(TEXT("y"), LocY);
482
+ Result->SetObjectField(TEXT("location"), LocationResult);
483
+ Result->SetStringField(TEXT("layerName"), LayerName);
484
+ Result->SetNumberField(TEXT("radius"), PaintRadius);
485
+ Result->SetNumberField(TEXT("strength"), Strength);
486
+ Result->SetNumberField(TEXT("falloff"), Falloff);
487
+
488
+ Result->SetBoolField(TEXT("success"), false);
489
+ Result->SetStringField(TEXT("note"),
490
+ TEXT("Landscape layer painting requires LandscapeEdMode which is not accessible from C++ plugins. ")
491
+ TEXT("Use the execute_python handler with unreal.LandscapeEditorLibrary.paint_layer() if available, ")
492
+ TEXT("or manually paint in the editor landscape tool."));
493
+
494
+ return MakeShared<FJsonValueObject>(Result);
495
+ }
496
+
497
+ TSharedPtr<FJsonValue> FLandscapeHandlers::ImportHeightmap(const TSharedPtr<FJsonObject>& Params)
498
+ {
499
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
500
+
501
+ FString FilePath;
502
+ if (!Params->TryGetStringField(TEXT("filePath"), FilePath))
503
+ {
504
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'filePath' parameter"));
505
+ Result->SetBoolField(TEXT("success"), false);
506
+ return MakeShared<FJsonValueObject>(Result);
507
+ }
508
+
509
+ // Verify the file exists
510
+ if (!FPaths::FileExists(FilePath))
511
+ {
512
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("File not found: %s"), *FilePath));
513
+ Result->SetBoolField(TEXT("success"), false);
514
+ return MakeShared<FJsonValueObject>(Result);
515
+ }
516
+
517
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
518
+ if (!World)
519
+ {
520
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
521
+ Result->SetBoolField(TEXT("success"), false);
522
+ return MakeShared<FJsonValueObject>(Result);
523
+ }
524
+
525
+ // Find the landscape
526
+ ALandscapeProxy* TargetLandscape = nullptr;
527
+ FString LandscapeName;
528
+ Params->TryGetStringField(TEXT("landscapeName"), LandscapeName);
529
+
530
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
531
+ {
532
+ ALandscapeProxy* Landscape = *It;
533
+ if (!Landscape) continue;
534
+
535
+ if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
536
+ {
537
+ TargetLandscape = Landscape;
538
+ break;
539
+ }
540
+ }
541
+
542
+ if (!TargetLandscape)
543
+ {
544
+ Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
545
+ Result->SetBoolField(TEXT("success"), false);
546
+ return MakeShared<FJsonValueObject>(Result);
547
+ }
548
+
549
+ // Read the heightmap file
550
+ TArray<uint8> FileData;
551
+ if (!FFileHelper::LoadFileToArray(FileData, *FilePath))
552
+ {
553
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to read file: %s"), *FilePath));
554
+ Result->SetBoolField(TEXT("success"), false);
555
+ return MakeShared<FJsonValueObject>(Result);
556
+ }
557
+
558
+ // Heightmap import requires the landscape editor subsystem which is internal to LandscapeEdMode.
559
+ // The raw heightmap data has been loaded successfully.
560
+ // Provide information about the file and a note about the import path.
561
+ Result->SetStringField(TEXT("filePath"), FilePath);
562
+ Result->SetNumberField(TEXT("fileSizeBytes"), FileData.Num());
563
+ Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
564
+
565
+ // Determine if this looks like a 16-bit raw heightmap based on file size
566
+ int64 FileSize = FileData.Num();
567
+ bool bLooksLikeRaw16 = false;
568
+ int32 PossibleResolution = 0;
569
+ for (int32 Res = 127; Res <= 8161; Res += 2)
570
+ {
571
+ if (FileSize == (int64)Res * Res * 2)
572
+ {
573
+ bLooksLikeRaw16 = true;
574
+ PossibleResolution = Res;
575
+ break;
576
+ }
577
+ }
578
+
579
+ if (bLooksLikeRaw16)
580
+ {
581
+ Result->SetNumberField(TEXT("possibleResolution"), PossibleResolution);
582
+ Result->SetStringField(TEXT("format"), TEXT("RAW16"));
583
+ }
584
+
585
+ Result->SetBoolField(TEXT("success"), false);
586
+ Result->SetStringField(TEXT("note"),
587
+ TEXT("Heightmap file loaded and validated. Direct heightmap import requires LandscapeEditorUtils ")
588
+ TEXT("which is internal to the landscape editor module. Use the execute_python handler with ")
589
+ TEXT("unreal.LandscapeEditorLibrary.import_heightmap() if available, or import through the ")
590
+ TEXT("Landscape editor mode Import tool."));
591
+
592
+ return MakeShared<FJsonValueObject>(Result);
593
+ }
594
+
595
+ TSharedPtr<FJsonValue> FLandscapeHandlers::SetLandscapeMaterial(const TSharedPtr<FJsonObject>& Params)
596
+ {
597
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
598
+
599
+ FString MaterialPath;
600
+ if (!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath))
601
+ {
602
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'materialPath', 'path', or 'assetPath' parameter"));
603
+ Result->SetBoolField(TEXT("success"), false);
604
+ return MakeShared<FJsonValueObject>(Result);
605
+ }
606
+
607
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
608
+ if (!World)
609
+ {
610
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
611
+ Result->SetBoolField(TEXT("success"), false);
612
+ return MakeShared<FJsonValueObject>(Result);
613
+ }
614
+
615
+ // Find the target landscape
616
+ ALandscapeProxy* TargetLandscape = nullptr;
617
+ FString LandscapeName;
618
+ Params->TryGetStringField(TEXT("landscapeName"), LandscapeName);
619
+
620
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
621
+ {
622
+ ALandscapeProxy* Landscape = *It;
623
+ if (!Landscape) continue;
624
+
625
+ if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
626
+ {
627
+ TargetLandscape = Landscape;
628
+ break;
629
+ }
630
+ }
631
+
632
+ if (!TargetLandscape)
633
+ {
634
+ Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
635
+ Result->SetBoolField(TEXT("success"), false);
636
+ return MakeShared<FJsonValueObject>(Result);
637
+ }
638
+
639
+ // Load the material
640
+ UMaterialInterface* Material = LoadObject<UMaterialInterface>(nullptr, *MaterialPath);
641
+ if (!Material)
642
+ {
643
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Material not found: %s"), *MaterialPath));
644
+ Result->SetBoolField(TEXT("success"), false);
645
+ return MakeShared<FJsonValueObject>(Result);
646
+ }
647
+
648
+ // Set the landscape material
649
+ TargetLandscape->LandscapeMaterial = Material;
650
+
651
+ // Update all landscape components to use the new material
652
+ TArray<ULandscapeComponent*> LandscapeComponents;
653
+ TargetLandscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
654
+ for (ULandscapeComponent* Comp : LandscapeComponents)
655
+ {
656
+ if (Comp)
657
+ {
658
+ Comp->SetMaterial(0, Material);
659
+ Comp->MarkRenderStateDirty();
660
+ }
661
+ }
662
+
663
+ // Mark the landscape as modified
664
+ TargetLandscape->MarkPackageDirty();
665
+
666
+ Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
667
+ Result->SetStringField(TEXT("materialPath"), MaterialPath);
668
+ Result->SetStringField(TEXT("materialName"), Material->GetName());
669
+ Result->SetNumberField(TEXT("componentsUpdated"), LandscapeComponents.Num());
670
+ Result->SetBoolField(TEXT("success"), true);
671
+
672
+ return MakeShared<FJsonValueObject>(Result);
673
+ }
674
+
675
+ TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeBounds(const TSharedPtr<FJsonObject>& Params)
676
+ {
677
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
678
+
679
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
680
+ if (!World)
681
+ {
682
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
683
+ Result->SetBoolField(TEXT("success"), false);
684
+ return MakeShared<FJsonValueObject>(Result);
685
+ }
686
+
687
+ FString LandscapeName;
688
+ Params->TryGetStringField(TEXT("landscapeName"), LandscapeName);
689
+
690
+ TArray<TSharedPtr<FJsonValue>> LandscapeBoundsArray;
691
+
692
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
693
+ {
694
+ ALandscapeProxy* Landscape = *It;
695
+ if (!Landscape) continue;
696
+
697
+ // Filter by name if specified
698
+ if (!LandscapeName.IsEmpty() && Landscape->GetName() != LandscapeName)
699
+ {
700
+ continue;
701
+ }
702
+
703
+ TSharedPtr<FJsonObject> LandscapeObj = MakeShared<FJsonObject>();
704
+ LandscapeObj->SetStringField(TEXT("name"), Landscape->GetName());
705
+
706
+ // Get actor bounds using GetActorBounds
707
+ FVector Origin;
708
+ FVector BoxExtent;
709
+ Landscape->GetActorBounds(false, Origin, BoxExtent);
710
+
711
+ TSharedPtr<FJsonObject> OriginObj = MakeShared<FJsonObject>();
712
+ OriginObj->SetNumberField(TEXT("x"), Origin.X);
713
+ OriginObj->SetNumberField(TEXT("y"), Origin.Y);
714
+ OriginObj->SetNumberField(TEXT("z"), Origin.Z);
715
+ LandscapeObj->SetObjectField(TEXT("origin"), OriginObj);
716
+
717
+ TSharedPtr<FJsonObject> ExtentObj = MakeShared<FJsonObject>();
718
+ ExtentObj->SetNumberField(TEXT("x"), BoxExtent.X);
719
+ ExtentObj->SetNumberField(TEXT("y"), BoxExtent.Y);
720
+ ExtentObj->SetNumberField(TEXT("z"), BoxExtent.Z);
721
+ LandscapeObj->SetObjectField(TEXT("boxExtent"), ExtentObj);
722
+
723
+ // Also provide min/max corners for convenience
724
+ FVector BoundsMin = Origin - BoxExtent;
725
+ FVector BoundsMax = Origin + BoxExtent;
726
+
727
+ TSharedPtr<FJsonObject> MinObj = MakeShared<FJsonObject>();
728
+ MinObj->SetNumberField(TEXT("x"), BoundsMin.X);
729
+ MinObj->SetNumberField(TEXT("y"), BoundsMin.Y);
730
+ MinObj->SetNumberField(TEXT("z"), BoundsMin.Z);
731
+ LandscapeObj->SetObjectField(TEXT("min"), MinObj);
732
+
733
+ TSharedPtr<FJsonObject> MaxObj = MakeShared<FJsonObject>();
734
+ MaxObj->SetNumberField(TEXT("x"), BoundsMax.X);
735
+ MaxObj->SetNumberField(TEXT("y"), BoundsMax.Y);
736
+ MaxObj->SetNumberField(TEXT("z"), BoundsMax.Z);
737
+ LandscapeObj->SetObjectField(TEXT("max"), MaxObj);
738
+
739
+ // Size
740
+ FVector Size = BoxExtent * 2.0;
741
+ TSharedPtr<FJsonObject> SizeObj = MakeShared<FJsonObject>();
742
+ SizeObj->SetNumberField(TEXT("x"), Size.X);
743
+ SizeObj->SetNumberField(TEXT("y"), Size.Y);
744
+ SizeObj->SetNumberField(TEXT("z"), Size.Z);
745
+ LandscapeObj->SetObjectField(TEXT("size"), SizeObj);
746
+
747
+ // Location
748
+ FVector Location = Landscape->GetActorLocation();
749
+ TSharedPtr<FJsonObject> LocationResultObj = MakeShared<FJsonObject>();
750
+ LocationResultObj->SetNumberField(TEXT("x"), Location.X);
751
+ LocationResultObj->SetNumberField(TEXT("y"), Location.Y);
752
+ LocationResultObj->SetNumberField(TEXT("z"), Location.Z);
753
+ LandscapeObj->SetObjectField(TEXT("location"), LocationResultObj);
754
+
755
+ LandscapeBoundsArray.Add(MakeShared<FJsonValueObject>(LandscapeObj));
756
+ }
757
+
758
+ if (LandscapeBoundsArray.Num() == 0)
759
+ {
760
+ Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
761
+ Result->SetBoolField(TEXT("success"), false);
762
+ return MakeShared<FJsonValueObject>(Result);
763
+ }
764
+
765
+ Result->SetArrayField(TEXT("landscapes"), LandscapeBoundsArray);
766
+ Result->SetNumberField(TEXT("count"), LandscapeBoundsArray.Num());
767
+ Result->SetBoolField(TEXT("success"), true);
768
+
769
+ return MakeShared<FJsonValueObject>(Result);
770
+ }
771
+
772
+ TSharedPtr<FJsonValue> FLandscapeHandlers::AddLandscapeLayerInfo(const TSharedPtr<FJsonObject>& Params)
773
+ {
774
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
775
+
776
+ FString LayerName;
777
+ if (!Params->TryGetStringField(TEXT("layerName"), LayerName))
778
+ {
779
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'layerName' parameter"));
780
+ Result->SetBoolField(TEXT("success"), false);
781
+ return MakeShared<FJsonValueObject>(Result);
782
+ }
783
+
784
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
785
+ if (!World)
786
+ {
787
+ Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
788
+ Result->SetBoolField(TEXT("success"), false);
789
+ return MakeShared<FJsonValueObject>(Result);
790
+ }
791
+
792
+ // Find the target landscape
793
+ ALandscapeProxy* TargetLandscape = nullptr;
794
+ FString LandscapeName;
795
+ Params->TryGetStringField(TEXT("landscapeName"), LandscapeName);
796
+
797
+ for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
798
+ {
799
+ ALandscapeProxy* Landscape = *It;
800
+ if (!Landscape) continue;
801
+
802
+ if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
803
+ {
804
+ TargetLandscape = Landscape;
805
+ break;
806
+ }
807
+ }
808
+
809
+ if (!TargetLandscape)
810
+ {
811
+ Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
812
+ Result->SetBoolField(TEXT("success"), false);
813
+ return MakeShared<FJsonValueObject>(Result);
814
+ }
815
+
816
+ ULandscapeInfo* LandscapeInfo = TargetLandscape->GetLandscapeInfo();
817
+ if (!LandscapeInfo)
818
+ {
819
+ Result->SetStringField(TEXT("error"), TEXT("Failed to get landscape info"));
820
+ Result->SetBoolField(TEXT("success"), false);
821
+ return MakeShared<FJsonValueObject>(Result);
822
+ }
823
+
824
+ // Check if a layer with this name already exists
825
+ for (const FLandscapeInfoLayerSettings& ExistingLayer : LandscapeInfo->Layers)
826
+ {
827
+ if (ExistingLayer.LayerInfoObj && ExistingLayer.GetLayerName().ToString() == LayerName)
828
+ {
829
+ Result->SetStringField(TEXT("layerName"), LayerName);
830
+ Result->SetStringField(TEXT("path"), ExistingLayer.LayerInfoObj->GetPathName());
831
+ Result->SetStringField(TEXT("note"), TEXT("Layer already exists on this landscape"));
832
+ Result->SetBoolField(TEXT("success"), true);
833
+ return MakeShared<FJsonValueObject>(Result);
834
+ }
835
+ }
836
+
837
+ // Create a new ULandscapeLayerInfoObject asset
838
+ FString PackagePath = TEXT("/Game/Landscape/LayerInfos");
839
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
840
+
841
+ FString AssetName = FString::Printf(TEXT("LI_%s"), *LayerName);
842
+ FString PackageFullPath = PackagePath / AssetName;
843
+
844
+ // Check if the asset already exists
845
+ ULandscapeLayerInfoObject* LayerInfoObj = LoadObject<ULandscapeLayerInfoObject>(nullptr, *(PackageFullPath + TEXT(".") + AssetName));
846
+ if (!LayerInfoObj)
847
+ {
848
+ UPackage* Package = CreatePackage(*PackageFullPath);
849
+ if (!Package)
850
+ {
851
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
852
+ Result->SetBoolField(TEXT("success"), false);
853
+ return MakeShared<FJsonValueObject>(Result);
854
+ }
855
+
856
+ LayerInfoObj = NewObject<ULandscapeLayerInfoObject>(Package, *AssetName, RF_Public | RF_Standalone);
857
+ if (!LayerInfoObj)
858
+ {
859
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create LandscapeLayerInfoObject"));
860
+ Result->SetBoolField(TEXT("success"), false);
861
+ return MakeShared<FJsonValueObject>(Result);
862
+ }
863
+
864
+ PRAGMA_DISABLE_DEPRECATION_WARNINGS
865
+ LayerInfoObj->LayerName = FName(*LayerName);
866
+ PRAGMA_ENABLE_DEPRECATION_WARNINGS
867
+
868
+ // Set optional properties
869
+ bool bIsWeightBlended = true;
870
+ if (Params->HasField(TEXT("weightBlended")))
871
+ {
872
+ bIsWeightBlended = Params->GetBoolField(TEXT("weightBlended"));
873
+ }
874
+ // bNoWeightBlend removed in UE 5.7 — weight blending is now controlled per-layer via landscape settings
875
+
876
+ // Notify asset registry and save
877
+ FAssetRegistryModule::AssetCreated(LayerInfoObj);
878
+ Package->MarkPackageDirty();
879
+ UEditorAssetLibrary::SaveAsset(PackageFullPath, false);
880
+ }
881
+
882
+ // Register the layer info with the landscape
883
+ int32 LayerIndex = LandscapeInfo->Layers.Num();
884
+ FLandscapeInfoLayerSettings NewLayerSettings(LayerInfoObj, TargetLandscape);
885
+ LandscapeInfo->Layers.Add(NewLayerSettings);
886
+
887
+ // Mark the landscape as dirty
888
+ TargetLandscape->MarkPackageDirty();
889
+
890
+ Result->SetStringField(TEXT("layerName"), LayerName);
891
+ Result->SetStringField(TEXT("path"), LayerInfoObj->GetPathName());
892
+ Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
893
+ Result->SetNumberField(TEXT("layerIndex"), LayerIndex);
894
+ Result->SetBoolField(TEXT("weightBlended"), true);
895
+ Result->SetBoolField(TEXT("success"), true);
896
+
897
+ return MakeShared<FJsonValueObject>(Result);
898
+ }