ue-mcp 1.2.1 → 1.2.2
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/context-cli.js +16 -16
- package/dist/engine-observer.js +68 -68
- package/dist/flow/guard.d.ts +26 -37
- package/dist/flow/guard.js +40 -25
- package/dist/flow/guard.js.map +1 -1
- package/dist/flow/guarded-bridge.d.ts +0 -10
- package/dist/flow/guarded-bridge.js +18 -23
- package/dist/flow/guarded-bridge.js.map +1 -1
- package/dist/flow/schema.d.ts +276 -16
- package/dist/flow/task-guards.d.ts +1 -1
- package/dist/flow/task-guards.js +44 -68
- package/dist/flow/task-guards.js.map +1 -1
- package/dist/instructions.js +130 -130
- package/dist/plugin-cli.js +299 -299
- package/dist/tool-counts.json +2 -2
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.cpp +76 -76
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.h +27 -27
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +128 -128
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +63 -63
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +230 -230
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +323 -323
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +20 -20
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +537 -537
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +77 -77
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +353 -353
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +18 -18
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_InputRuntime.cpp +246 -246
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +597 -597
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +51 -51
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +878 -878
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +449 -449
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp +275 -275
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Graph.cpp +551 -551
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +531 -531
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +28 -28
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +677 -677
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +822 -822
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +22 -22
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +218 -218
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +17 -17
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.cpp +46 -46
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.h +23 -23
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/MCPExternalRegistry.cpp +83 -83
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/UE_MCP_BridgeStatusModule.cpp +37 -37
- package/dist/manifest-signature.d.ts +0 -1
- package/dist/manifest-signature.js +0 -53
- package/dist/manifest-signature.js.map +0 -1
- package/dist/pie/schema.d.ts +0 -791
- package/dist/pie/schema.js +0 -151
- package/dist/pie/schema.js.map +0 -1
- package/dist/watch-cli.js.map +0 -1
- /package/plugin/ue_mcp_bridge/{ue_mcp_bridge.uplugin → UE_MCP_Bridge.uplugin} +0 -0
|
@@ -1,878 +1,878 @@
|
|
|
1
|
-
#include "LandscapeHandlers.h"
|
|
2
|
-
#include "HandlerRegistry.h"
|
|
3
|
-
#include "HandlerUtils.h"
|
|
4
|
-
#include "Dom/JsonObject.h"
|
|
5
|
-
#include "Dom/JsonValue.h"
|
|
6
|
-
#include "Editor.h"
|
|
7
|
-
#include "Engine/World.h"
|
|
8
|
-
#include "EngineUtils.h"
|
|
9
|
-
#include "Landscape.h"
|
|
10
|
-
#include "LandscapeEditTypes.h"
|
|
11
|
-
#include "LandscapeProxy.h"
|
|
12
|
-
#include "LandscapeStreamingProxy.h"
|
|
13
|
-
#include "LandscapeInfo.h"
|
|
14
|
-
#include "LandscapeComponent.h"
|
|
15
|
-
#include "LandscapeSplineActor.h"
|
|
16
|
-
#include "LandscapeSplinesComponent.h"
|
|
17
|
-
#include "LandscapeSplineControlPoint.h"
|
|
18
|
-
#include "LandscapeSplineSegment.h"
|
|
19
|
-
#include "Kismet/KismetSystemLibrary.h"
|
|
20
|
-
#include "Misc/FileHelper.h"
|
|
21
|
-
#include "Materials/MaterialInterface.h"
|
|
22
|
-
#include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
|
|
23
|
-
#include "Components/PrimitiveComponent.h"
|
|
24
|
-
#include "LandscapeLayerInfoObject.h"
|
|
25
|
-
#include "UObject/Package.h"
|
|
26
|
-
#include "AssetRegistry/AssetRegistryModule.h"
|
|
27
|
-
#include "AssetToolsModule.h"
|
|
28
|
-
#include "IAssetTools.h"
|
|
29
|
-
|
|
30
|
-
void FLandscapeHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
31
|
-
{
|
|
32
|
-
Registry.RegisterHandler(TEXT("get_landscape_info"), &GetLandscapeInfo);
|
|
33
|
-
Registry.RegisterHandler(TEXT("list_landscape_layers"), &ListLandscapeLayers);
|
|
34
|
-
Registry.RegisterHandler(TEXT("sample_landscape"), &SampleLandscape);
|
|
35
|
-
Registry.RegisterHandler(TEXT("list_landscape_splines"), &ListLandscapeSplines);
|
|
36
|
-
Registry.RegisterHandler(TEXT("get_landscape_component"), &GetLandscapeComponent);
|
|
37
|
-
Registry.RegisterHandler(TEXT("set_landscape_material"), &SetLandscapeMaterial);
|
|
38
|
-
Registry.RegisterHandler(TEXT("add_landscape_layer_info"), &AddLandscapeLayerInfo);
|
|
39
|
-
Registry.RegisterHandler(TEXT("create_landscape"), &CreateLandscape);
|
|
40
|
-
Registry.RegisterHandler(TEXT("create_landscape_layer_info"), &CreateLandscapeLayerInfo);
|
|
41
|
-
Registry.RegisterHandler(TEXT("get_landscape_material_usage_summary"), &GetMaterialUsageSummary);
|
|
42
|
-
// #733: World Partition landscape streaming-proxy enumeration + spatial lookup.
|
|
43
|
-
Registry.RegisterHandler(TEXT("list_landscape_proxies"), &ListLandscapeProxies);
|
|
44
|
-
Registry.RegisterHandler(TEXT("find_landscape_proxy_at"), &FindLandscapeProxyAt);
|
|
45
|
-
Registry.RegisterHandlerWithTimeout(TEXT("sculpt_landscape"), &Sculpt, 120.0f);
|
|
46
|
-
Registry.RegisterHandlerWithTimeout(TEXT("paint_landscape_layer"), &PaintLayer, 120.0f);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeInfo(const TSharedPtr<FJsonObject>& Params)
|
|
50
|
-
{
|
|
51
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
52
|
-
|
|
53
|
-
// Find landscape proxies in the world
|
|
54
|
-
TArray<TSharedPtr<FJsonValue>> LandscapeArray;
|
|
55
|
-
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
56
|
-
{
|
|
57
|
-
ALandscapeProxy* Landscape = *It;
|
|
58
|
-
if (!Landscape) continue;
|
|
59
|
-
|
|
60
|
-
TSharedPtr<FJsonObject> LandscapeObj = MakeShared<FJsonObject>();
|
|
61
|
-
LandscapeObj->SetStringField(TEXT("name"), Landscape->GetName());
|
|
62
|
-
LandscapeObj->SetStringField(TEXT("class"), Landscape->GetClass()->GetName());
|
|
63
|
-
|
|
64
|
-
// Get component count
|
|
65
|
-
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
66
|
-
Landscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
67
|
-
LandscapeObj->SetNumberField(TEXT("componentCount"), LandscapeComponents.Num());
|
|
68
|
-
|
|
69
|
-
// Get bounds
|
|
70
|
-
FBox Bounds = Landscape->GetComponentsBoundingBox();
|
|
71
|
-
if (Bounds.IsValid)
|
|
72
|
-
{
|
|
73
|
-
TSharedPtr<FJsonObject> BoundsObj = MakeShared<FJsonObject>();
|
|
74
|
-
BoundsObj->SetNumberField(TEXT("minX"), Bounds.Min.X);
|
|
75
|
-
BoundsObj->SetNumberField(TEXT("minY"), Bounds.Min.Y);
|
|
76
|
-
BoundsObj->SetNumberField(TEXT("minZ"), Bounds.Min.Z);
|
|
77
|
-
BoundsObj->SetNumberField(TEXT("maxX"), Bounds.Max.X);
|
|
78
|
-
BoundsObj->SetNumberField(TEXT("maxY"), Bounds.Max.Y);
|
|
79
|
-
BoundsObj->SetNumberField(TEXT("maxZ"), Bounds.Max.Z);
|
|
80
|
-
|
|
81
|
-
FVector Size = Bounds.GetSize();
|
|
82
|
-
BoundsObj->SetNumberField(TEXT("sizeX"), Size.X);
|
|
83
|
-
BoundsObj->SetNumberField(TEXT("sizeY"), Size.Y);
|
|
84
|
-
BoundsObj->SetNumberField(TEXT("sizeZ"), Size.Z);
|
|
85
|
-
LandscapeObj->SetObjectField(TEXT("bounds"), BoundsObj);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Get location
|
|
89
|
-
FVector Location = Landscape->GetActorLocation();
|
|
90
|
-
LandscapeObj->SetNumberField(TEXT("locationX"), Location.X);
|
|
91
|
-
LandscapeObj->SetNumberField(TEXT("locationY"), Location.Y);
|
|
92
|
-
LandscapeObj->SetNumberField(TEXT("locationZ"), Location.Z);
|
|
93
|
-
|
|
94
|
-
LandscapeArray.Add(MakeShared<FJsonValueObject>(LandscapeObj));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
auto Result = MCPSuccess();
|
|
98
|
-
if (LandscapeArray.Num() == 0)
|
|
99
|
-
{
|
|
100
|
-
Result->SetStringField(TEXT("landscape"), TEXT("none"));
|
|
101
|
-
}
|
|
102
|
-
else
|
|
103
|
-
{
|
|
104
|
-
Result->SetArrayField(TEXT("landscapes"), LandscapeArray);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
Result->SetNumberField(TEXT("count"), LandscapeArray.Num());
|
|
108
|
-
return MCPResult(Result);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeLayers(const TSharedPtr<FJsonObject>& Params)
|
|
112
|
-
{
|
|
113
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
114
|
-
|
|
115
|
-
TArray<TSharedPtr<FJsonValue>> LayerArray;
|
|
116
|
-
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
117
|
-
{
|
|
118
|
-
ALandscapeProxy* Landscape = *It;
|
|
119
|
-
if (!Landscape) continue;
|
|
120
|
-
|
|
121
|
-
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
|
|
122
|
-
if (LandscapeInfo)
|
|
123
|
-
{
|
|
124
|
-
for (const FLandscapeInfoLayerSettings& LayerSettings : LandscapeInfo->Layers)
|
|
125
|
-
{
|
|
126
|
-
if (LayerSettings.LayerInfoObj)
|
|
127
|
-
{
|
|
128
|
-
TSharedPtr<FJsonObject> LayerObj = MakeShared<FJsonObject>();
|
|
129
|
-
LayerObj->SetStringField(TEXT("name"), LayerSettings.GetLayerName().ToString());
|
|
130
|
-
LayerObj->SetStringField(TEXT("landscapeName"), Landscape->GetName());
|
|
131
|
-
LayerArray.Add(MakeShared<FJsonValueObject>(LayerObj));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
auto Result = MCPSuccess();
|
|
138
|
-
Result->SetArrayField(TEXT("layers"), LayerArray);
|
|
139
|
-
Result->SetNumberField(TEXT("count"), LayerArray.Num());
|
|
140
|
-
return MCPResult(Result);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::SampleLandscape(const TSharedPtr<FJsonObject>& Params)
|
|
144
|
-
{
|
|
145
|
-
const TSharedPtr<FJsonObject>* PointObj = nullptr;
|
|
146
|
-
if (!Params->TryGetObjectField(TEXT("point"), PointObj))
|
|
147
|
-
{
|
|
148
|
-
return MCPError(TEXT("Missing 'point' parameter"));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
FVector Point;
|
|
152
|
-
Point.X = (*PointObj)->GetNumberField(TEXT("x"));
|
|
153
|
-
Point.Y = (*PointObj)->GetNumberField(TEXT("y"));
|
|
154
|
-
Point.Z = (*PointObj)->GetNumberField(TEXT("z"));
|
|
155
|
-
|
|
156
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
157
|
-
|
|
158
|
-
// Find the first landscape and sample height
|
|
159
|
-
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
160
|
-
{
|
|
161
|
-
ALandscapeProxy* Landscape = *It;
|
|
162
|
-
if (!Landscape) continue;
|
|
163
|
-
|
|
164
|
-
// Use line trace to get the landscape height at the given point
|
|
165
|
-
FVector TraceStart(Point.X, Point.Y, Point.Z + 100000.0f);
|
|
166
|
-
FVector TraceEnd(Point.X, Point.Y, Point.Z - 100000.0f);
|
|
167
|
-
|
|
168
|
-
FHitResult HitResult;
|
|
169
|
-
FCollisionQueryParams QueryParams;
|
|
170
|
-
QueryParams.bTraceComplex = true;
|
|
171
|
-
|
|
172
|
-
if (World->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_WorldStatic, QueryParams))
|
|
173
|
-
{
|
|
174
|
-
if (HitResult.GetActor() && HitResult.GetActor()->IsA(ALandscapeProxy::StaticClass()))
|
|
175
|
-
{
|
|
176
|
-
auto Result = MCPSuccess();
|
|
177
|
-
Result->SetNumberField(TEXT("height"), HitResult.Location.Z);
|
|
178
|
-
TSharedPtr<FJsonObject> HitPoint = MakeShared<FJsonObject>();
|
|
179
|
-
HitPoint->SetNumberField(TEXT("x"), HitResult.Location.X);
|
|
180
|
-
HitPoint->SetNumberField(TEXT("y"), HitResult.Location.Y);
|
|
181
|
-
HitPoint->SetNumberField(TEXT("z"), HitResult.Location.Z);
|
|
182
|
-
Result->SetObjectField(TEXT("hitLocation"), HitPoint);
|
|
183
|
-
|
|
184
|
-
TSharedPtr<FJsonObject> Normal = MakeShared<FJsonObject>();
|
|
185
|
-
Normal->SetNumberField(TEXT("x"), HitResult.Normal.X);
|
|
186
|
-
Normal->SetNumberField(TEXT("y"), HitResult.Normal.Y);
|
|
187
|
-
Normal->SetNumberField(TEXT("z"), HitResult.Normal.Z);
|
|
188
|
-
Result->SetObjectField(TEXT("normal"), Normal);
|
|
189
|
-
|
|
190
|
-
Result->SetBoolField(TEXT("hit"), true);
|
|
191
|
-
return MCPResult(Result);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
auto Result = MCPSuccess();
|
|
197
|
-
Result->SetBoolField(TEXT("hit"), false);
|
|
198
|
-
return MCPResult(Result);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeSplines(const TSharedPtr<FJsonObject>& Params)
|
|
202
|
-
{
|
|
203
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
204
|
-
|
|
205
|
-
TArray<TSharedPtr<FJsonValue>> SplineArray;
|
|
206
|
-
|
|
207
|
-
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
208
|
-
{
|
|
209
|
-
ALandscapeProxy* Landscape = *It;
|
|
210
|
-
if (!Landscape) continue;
|
|
211
|
-
|
|
212
|
-
ULandscapeSplinesComponent* SplinesComp = Landscape->GetSplinesComponent();
|
|
213
|
-
if (!SplinesComp) continue;
|
|
214
|
-
|
|
215
|
-
const TArray<TObjectPtr<ULandscapeSplineControlPoint>>& ControlPoints = SplinesComp->GetControlPoints();
|
|
216
|
-
for (const TObjectPtr<ULandscapeSplineControlPoint>& CP : ControlPoints)
|
|
217
|
-
{
|
|
218
|
-
if (!CP) continue;
|
|
219
|
-
|
|
220
|
-
TSharedPtr<FJsonObject> PointObj = MakeShared<FJsonObject>();
|
|
221
|
-
FVector Location = CP->Location;
|
|
222
|
-
PointObj->SetNumberField(TEXT("x"), Location.X);
|
|
223
|
-
PointObj->SetNumberField(TEXT("y"), Location.Y);
|
|
224
|
-
PointObj->SetNumberField(TEXT("z"), Location.Z);
|
|
225
|
-
PointObj->SetStringField(TEXT("landscapeName"), Landscape->GetName());
|
|
226
|
-
SplineArray.Add(MakeShared<FJsonValueObject>(PointObj));
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
auto Result = MCPSuccess();
|
|
231
|
-
Result->SetArrayField(TEXT("controlPoints"), SplineArray);
|
|
232
|
-
Result->SetNumberField(TEXT("count"), SplineArray.Num());
|
|
233
|
-
return MCPResult(Result);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeComponent(const TSharedPtr<FJsonObject>& Params)
|
|
237
|
-
{
|
|
238
|
-
int32 ComponentIndex = 0;
|
|
239
|
-
if (Params->HasField(TEXT("componentIndex")))
|
|
240
|
-
{
|
|
241
|
-
ComponentIndex = static_cast<int32>(Params->GetNumberField(TEXT("componentIndex")));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
245
|
-
|
|
246
|
-
// Collect all landscape components across all landscape proxies
|
|
247
|
-
TArray<ULandscapeComponent*> AllComponents;
|
|
248
|
-
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
249
|
-
{
|
|
250
|
-
ALandscapeProxy* Landscape = *It;
|
|
251
|
-
if (!Landscape) continue;
|
|
252
|
-
|
|
253
|
-
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
254
|
-
Landscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
255
|
-
AllComponents.Append(LandscapeComponents);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (ComponentIndex < 0 || ComponentIndex >= AllComponents.Num())
|
|
259
|
-
{
|
|
260
|
-
return MCPError(FString::Printf(TEXT("Component index %d out of range (0-%d)"), ComponentIndex, AllComponents.Num() - 1));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
ULandscapeComponent* Comp = AllComponents[ComponentIndex];
|
|
264
|
-
if (!Comp)
|
|
265
|
-
{
|
|
266
|
-
return MCPError(TEXT("Component is null"));
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
auto Result = MCPSuccess();
|
|
270
|
-
Result->SetNumberField(TEXT("componentIndex"), ComponentIndex);
|
|
271
|
-
Result->SetStringField(TEXT("name"), Comp->GetName());
|
|
272
|
-
|
|
273
|
-
FVector CompLocation = Comp->GetComponentLocation();
|
|
274
|
-
Result->SetNumberField(TEXT("locationX"), CompLocation.X);
|
|
275
|
-
Result->SetNumberField(TEXT("locationY"), CompLocation.Y);
|
|
276
|
-
Result->SetNumberField(TEXT("locationZ"), CompLocation.Z);
|
|
277
|
-
|
|
278
|
-
Result->SetNumberField(TEXT("sectionBaseX"), Comp->SectionBaseX);
|
|
279
|
-
Result->SetNumberField(TEXT("sectionBaseY"), Comp->SectionBaseY);
|
|
280
|
-
Result->SetNumberField(TEXT("componentSizeQuads"), Comp->ComponentSizeQuads);
|
|
281
|
-
Result->SetNumberField(TEXT("subSections"), Comp->NumSubsections);
|
|
282
|
-
|
|
283
|
-
FBox CompBounds = Comp->Bounds.GetBox();
|
|
284
|
-
if (CompBounds.IsValid)
|
|
285
|
-
{
|
|
286
|
-
TSharedPtr<FJsonObject> BoundsObj = MakeShared<FJsonObject>();
|
|
287
|
-
BoundsObj->SetNumberField(TEXT("minX"), CompBounds.Min.X);
|
|
288
|
-
BoundsObj->SetNumberField(TEXT("minY"), CompBounds.Min.Y);
|
|
289
|
-
BoundsObj->SetNumberField(TEXT("minZ"), CompBounds.Min.Z);
|
|
290
|
-
BoundsObj->SetNumberField(TEXT("maxX"), CompBounds.Max.X);
|
|
291
|
-
BoundsObj->SetNumberField(TEXT("maxY"), CompBounds.Max.Y);
|
|
292
|
-
BoundsObj->SetNumberField(TEXT("maxZ"), CompBounds.Max.Z);
|
|
293
|
-
Result->SetObjectField(TEXT("bounds"), BoundsObj);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return MCPResult(Result);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::SetLandscapeMaterial(const TSharedPtr<FJsonObject>& Params)
|
|
300
|
-
{
|
|
301
|
-
FString MaterialPath;
|
|
302
|
-
if (!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath))
|
|
303
|
-
{
|
|
304
|
-
return MCPError(TEXT("Missing 'materialPath', 'path', or 'assetPath' parameter"));
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
308
|
-
|
|
309
|
-
// Find the target landscape
|
|
310
|
-
ALandscapeProxy* TargetLandscape = nullptr;
|
|
311
|
-
FString LandscapeName = OptionalString(Params, TEXT("landscapeName"));
|
|
312
|
-
|
|
313
|
-
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
314
|
-
{
|
|
315
|
-
ALandscapeProxy* Landscape = *It;
|
|
316
|
-
if (!Landscape) continue;
|
|
317
|
-
|
|
318
|
-
if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
|
|
319
|
-
{
|
|
320
|
-
TargetLandscape = Landscape;
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (!TargetLandscape)
|
|
326
|
-
{
|
|
327
|
-
return MCPError(TEXT("No landscape found in the current level"));
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Load the material
|
|
331
|
-
UMaterialInterface* Material = LoadObject<UMaterialInterface>(nullptr, *MaterialPath);
|
|
332
|
-
if (!Material)
|
|
333
|
-
{
|
|
334
|
-
return MCPError(FString::Printf(TEXT("Material not found: %s"), *MaterialPath));
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Capture previous material for rollback and idempotency
|
|
338
|
-
UMaterialInterface* PrevMaterial = TargetLandscape->LandscapeMaterial;
|
|
339
|
-
if (PrevMaterial == Material)
|
|
340
|
-
{
|
|
341
|
-
auto Noop = MCPSuccess();
|
|
342
|
-
MCPSetExisted(Noop);
|
|
343
|
-
Noop->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
344
|
-
Noop->SetStringField(TEXT("materialPath"), MaterialPath);
|
|
345
|
-
return MCPResult(Noop);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Set the landscape material
|
|
349
|
-
TargetLandscape->LandscapeMaterial = Material;
|
|
350
|
-
|
|
351
|
-
// Update all landscape components to use the new material
|
|
352
|
-
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
353
|
-
TargetLandscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
354
|
-
for (ULandscapeComponent* Comp : LandscapeComponents)
|
|
355
|
-
{
|
|
356
|
-
if (Comp)
|
|
357
|
-
{
|
|
358
|
-
Comp->SetMaterial(0, Material);
|
|
359
|
-
Comp->MarkRenderStateDirty();
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Mark the landscape as modified
|
|
364
|
-
TargetLandscape->MarkPackageDirty();
|
|
365
|
-
|
|
366
|
-
auto Result = MCPSuccess();
|
|
367
|
-
MCPSetUpdated(Result);
|
|
368
|
-
Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
369
|
-
Result->SetStringField(TEXT("materialPath"), MaterialPath);
|
|
370
|
-
Result->SetStringField(TEXT("materialName"), Material->GetName());
|
|
371
|
-
Result->SetNumberField(TEXT("componentsUpdated"), LandscapeComponents.Num());
|
|
372
|
-
|
|
373
|
-
// Rollback: restore previous material path if any
|
|
374
|
-
if (PrevMaterial)
|
|
375
|
-
{
|
|
376
|
-
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
377
|
-
Payload->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
378
|
-
Payload->SetStringField(TEXT("materialPath"), PrevMaterial->GetPathName());
|
|
379
|
-
MCPSetRollback(Result, TEXT("set_landscape_material"), Payload);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return MCPResult(Result);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::AddLandscapeLayerInfo(const TSharedPtr<FJsonObject>& Params)
|
|
386
|
-
{
|
|
387
|
-
FString LayerName;
|
|
388
|
-
if (auto Err = RequireString(Params, TEXT("layerName"), LayerName)) return Err;
|
|
389
|
-
|
|
390
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
391
|
-
|
|
392
|
-
// Find the target landscape
|
|
393
|
-
ALandscapeProxy* TargetLandscape = nullptr;
|
|
394
|
-
FString LandscapeName = OptionalString(Params, TEXT("landscapeName"));
|
|
395
|
-
|
|
396
|
-
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
397
|
-
{
|
|
398
|
-
ALandscapeProxy* Landscape = *It;
|
|
399
|
-
if (!Landscape) continue;
|
|
400
|
-
|
|
401
|
-
if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
|
|
402
|
-
{
|
|
403
|
-
TargetLandscape = Landscape;
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (!TargetLandscape)
|
|
409
|
-
{
|
|
410
|
-
return MCPError(TEXT("No landscape found in the current level"));
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
ULandscapeInfo* LandscapeInfo = TargetLandscape->GetLandscapeInfo();
|
|
414
|
-
if (!LandscapeInfo)
|
|
415
|
-
{
|
|
416
|
-
return MCPError(TEXT("Failed to get landscape info"));
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Check if a layer with this name already exists
|
|
420
|
-
for (const FLandscapeInfoLayerSettings& ExistingLayer : LandscapeInfo->Layers)
|
|
421
|
-
{
|
|
422
|
-
if (ExistingLayer.LayerInfoObj && ExistingLayer.GetLayerName().ToString() == LayerName)
|
|
423
|
-
{
|
|
424
|
-
auto Result = MCPSuccess();
|
|
425
|
-
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
426
|
-
Result->SetStringField(TEXT("path"), ExistingLayer.LayerInfoObj->GetPathName());
|
|
427
|
-
Result->SetStringField(TEXT("note"), TEXT("Layer already exists on this landscape"));
|
|
428
|
-
return MCPResult(Result);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Create a new ULandscapeLayerInfoObject asset
|
|
433
|
-
FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Landscape/LayerInfos"));
|
|
434
|
-
|
|
435
|
-
FString AssetName = FString::Printf(TEXT("LI_%s"), *LayerName);
|
|
436
|
-
FString PackageFullPath = PackagePath / AssetName;
|
|
437
|
-
|
|
438
|
-
// Check if the asset already exists
|
|
439
|
-
ULandscapeLayerInfoObject* LayerInfoObj = LoadObject<ULandscapeLayerInfoObject>(nullptr, *(PackageFullPath + TEXT(".") + AssetName));
|
|
440
|
-
if (!LayerInfoObj)
|
|
441
|
-
{
|
|
442
|
-
UPackage* Package = CreatePackage(*PackageFullPath);
|
|
443
|
-
if (!Package)
|
|
444
|
-
{
|
|
445
|
-
return MCPError(FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
LayerInfoObj = NewObject<ULandscapeLayerInfoObject>(Package, *AssetName, RF_Public | RF_Standalone);
|
|
449
|
-
if (!LayerInfoObj)
|
|
450
|
-
{
|
|
451
|
-
return MCPError(TEXT("Failed to create LandscapeLayerInfoObject"));
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
455
|
-
LayerInfoObj->LayerName = FName(*LayerName);
|
|
456
|
-
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
457
|
-
|
|
458
|
-
// There is no weight-blend toggle to set here any more: bNoWeightBlend
|
|
459
|
-
// was removed in 5.7 and blending is controlled per-layer through
|
|
460
|
-
// landscape settings. The old 'weightBlended' param read into a unused
|
|
461
|
-
// local and the response hardcoded true, so both are gone rather than
|
|
462
|
-
// left implying a setting that is not being applied.
|
|
463
|
-
|
|
464
|
-
// Notify asset registry and save
|
|
465
|
-
FAssetRegistryModule::AssetCreated(LayerInfoObj);
|
|
466
|
-
Package->MarkPackageDirty();
|
|
467
|
-
UEditorAssetLibrary::SaveAsset(PackageFullPath, false);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Register the layer info with the landscape
|
|
471
|
-
int32 LayerIndex = LandscapeInfo->Layers.Num();
|
|
472
|
-
FLandscapeInfoLayerSettings NewLayerSettings(LayerInfoObj, TargetLandscape);
|
|
473
|
-
LandscapeInfo->Layers.Add(NewLayerSettings);
|
|
474
|
-
|
|
475
|
-
// Mark the landscape as dirty
|
|
476
|
-
TargetLandscape->MarkPackageDirty();
|
|
477
|
-
|
|
478
|
-
auto Result = MCPSuccess();
|
|
479
|
-
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
480
|
-
Result->SetStringField(TEXT("path"), LayerInfoObj->GetPathName());
|
|
481
|
-
Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
482
|
-
Result->SetNumberField(TEXT("layerIndex"), LayerIndex);
|
|
483
|
-
|
|
484
|
-
return MCPResult(Result);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// ─── #150 get_landscape_material_usage_summary ──────────────────────
|
|
488
|
-
// Compact per-proxy dump: class, label, material paths, grass / Nanite /
|
|
489
|
-
// landscape component counts. Avoids the big "get all components" blob
|
|
490
|
-
// get_actor_details produces when you only need materials + counts.
|
|
491
|
-
// #303: spawn an ALandscape with a default flat heightmap at mid-elevation
|
|
492
|
-
// (uint16 32768 = no offset). Section/quad defaults match the Editor's
|
|
493
|
-
// Landscape Mode "create new" form: 63 quads/subsection, 2 subsections/component
|
|
494
|
-
// = 127 quads/component. ComponentCount X/Y default to 8x8 producing a
|
|
495
|
-
// 1016x1016 quad landscape (~1 km at default actor scale 100,100,100).
|
|
496
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::CreateLandscape(const TSharedPtr<FJsonObject>& Params)
|
|
497
|
-
{
|
|
498
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
499
|
-
|
|
500
|
-
const int32 SubsectionSizeQuads = OptionalInt(Params, TEXT("subsectionSizeQuads"), 63);
|
|
501
|
-
const int32 NumSubsections = OptionalInt(Params, TEXT("numSubsections"), 2);
|
|
502
|
-
const int32 ComponentCountX = OptionalInt(Params, TEXT("componentCountX"), 8);
|
|
503
|
-
const int32 ComponentCountY = OptionalInt(Params, TEXT("componentCountY"), 8);
|
|
504
|
-
|
|
505
|
-
// Bounds checks: SubsectionSizeQuads must be one of the engine's supported
|
|
506
|
-
// values (7, 15, 31, 63, 127, 255), NumSubsections is 1 or 2, and the
|
|
507
|
-
// component grid has to be at least 1x1.
|
|
508
|
-
auto IsPowOf2Minus1 = [](int32 v) {
|
|
509
|
-
const int32 p = v + 1;
|
|
510
|
-
return v >= 7 && v <= 255 && (p & (p - 1)) == 0;
|
|
511
|
-
};
|
|
512
|
-
if (!IsPowOf2Minus1(SubsectionSizeQuads))
|
|
513
|
-
{
|
|
514
|
-
return MCPError(FString::Printf(
|
|
515
|
-
TEXT("subsectionSizeQuads must be one of 7, 15, 31, 63, 127, 255 (got %d)"),
|
|
516
|
-
SubsectionSizeQuads));
|
|
517
|
-
}
|
|
518
|
-
if (NumSubsections != 1 && NumSubsections != 2)
|
|
519
|
-
{
|
|
520
|
-
return MCPError(FString::Printf(TEXT("numSubsections must be 1 or 2 (got %d)"), NumSubsections));
|
|
521
|
-
}
|
|
522
|
-
if (ComponentCountX < 1 || ComponentCountY < 1)
|
|
523
|
-
{
|
|
524
|
-
return MCPError(TEXT("componentCountX and componentCountY must be >= 1"));
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const int32 ComponentSizeQuads = SubsectionSizeQuads * NumSubsections;
|
|
528
|
-
const int32 SizeX = (ComponentCountX * ComponentSizeQuads) + 1;
|
|
529
|
-
const int32 SizeY = (ComponentCountY * ComponentSizeQuads) + 1;
|
|
530
|
-
|
|
531
|
-
const int32 HeightOffset = OptionalInt(Params, TEXT("heightOffset"), 32768);
|
|
532
|
-
if (HeightOffset < 0 || HeightOffset > 65535)
|
|
533
|
-
{
|
|
534
|
-
return MCPError(TEXT("heightOffset must be in [0, 65535] (uint16 elevation)"));
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const FVector Location = OptionalVec3(Params, TEXT("location"));
|
|
538
|
-
const FVector Scale = OptionalVec3(Params, TEXT("scale"), FVector(100.0, 100.0, 100.0));
|
|
539
|
-
|
|
540
|
-
const FString Label = OptionalString(Params, TEXT("label"));
|
|
541
|
-
|
|
542
|
-
// Idempotency by label.
|
|
543
|
-
if (auto Existing = MCPCheckActorLabelExists(World, Label, TEXT("skip"), TEXT("Landscape")))
|
|
544
|
-
{
|
|
545
|
-
return Existing;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
FActorSpawnParameters SpawnParams;
|
|
549
|
-
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
550
|
-
ALandscape* Landscape = World->SpawnActor<ALandscape>(Location, FRotator::ZeroRotator, SpawnParams);
|
|
551
|
-
if (!Landscape)
|
|
552
|
-
{
|
|
553
|
-
return MCPError(TEXT("Failed to spawn ALandscape actor"));
|
|
554
|
-
}
|
|
555
|
-
Landscape->SetActorScale3D(Scale);
|
|
556
|
-
|
|
557
|
-
// Allocate a flat heightmap. Layer 0 (FGuid()) is the only edit layer for a
|
|
558
|
-
// non-edit-layer landscape, which is what gets created by the Editor's
|
|
559
|
-
// "create new landscape" defaults.
|
|
560
|
-
TArray<uint16> HeightData;
|
|
561
|
-
HeightData.SetNumUninitialized(SizeX * SizeY);
|
|
562
|
-
for (int32 i = 0; i < HeightData.Num(); ++i)
|
|
563
|
-
{
|
|
564
|
-
HeightData[i] = static_cast<uint16>(HeightOffset);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
TMap<FGuid, TArray<uint16>> ImportHeightData;
|
|
568
|
-
ImportHeightData.Add(FGuid(), MoveTemp(HeightData));
|
|
569
|
-
|
|
570
|
-
TMap<FGuid, TArray<FLandscapeImportLayerInfo>> ImportLayerInfo;
|
|
571
|
-
ImportLayerInfo.Add(FGuid(), TArray<FLandscapeImportLayerInfo>());
|
|
572
|
-
|
|
573
|
-
TArray<FLandscapeLayer> EmptyLayers;
|
|
574
|
-
Landscape->Import(
|
|
575
|
-
FGuid::NewGuid(),
|
|
576
|
-
0, 0,
|
|
577
|
-
SizeX - 1, SizeY - 1,
|
|
578
|
-
NumSubsections,
|
|
579
|
-
SubsectionSizeQuads,
|
|
580
|
-
ImportHeightData,
|
|
581
|
-
nullptr,
|
|
582
|
-
ImportLayerInfo,
|
|
583
|
-
ELandscapeImportAlphamapType::Additive,
|
|
584
|
-
#if UE_MCP_HAS_5_5_API
|
|
585
|
-
MakeArrayView(EmptyLayers)
|
|
586
|
-
#else
|
|
587
|
-
// 5.4: last arg is const TArray<FLandscapeLayer>* (TArrayView signature added in 5.5).
|
|
588
|
-
&EmptyLayers
|
|
589
|
-
#endif
|
|
590
|
-
);
|
|
591
|
-
|
|
592
|
-
if (!Label.IsEmpty())
|
|
593
|
-
{
|
|
594
|
-
Landscape->SetActorLabel(Label);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Register so subsequent get_landscape_info / sample_landscape calls find it.
|
|
598
|
-
if (ULandscapeInfo* LI = Landscape->GetLandscapeInfo())
|
|
599
|
-
{
|
|
600
|
-
LI->FixupProxiesTransform();
|
|
601
|
-
LI->RecreateCollisionComponents();
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
auto Result = MCPSuccess();
|
|
605
|
-
MCPSetCreated(Result);
|
|
606
|
-
Result->SetStringField(TEXT("actorLabel"), Landscape->GetActorLabel());
|
|
607
|
-
Result->SetStringField(TEXT("actorPath"), Landscape->GetPathName());
|
|
608
|
-
Result->SetNumberField(TEXT("componentCountX"), ComponentCountX);
|
|
609
|
-
Result->SetNumberField(TEXT("componentCountY"), ComponentCountY);
|
|
610
|
-
Result->SetNumberField(TEXT("componentSizeQuads"), ComponentSizeQuads);
|
|
611
|
-
Result->SetNumberField(TEXT("subsectionSizeQuads"), SubsectionSizeQuads);
|
|
612
|
-
Result->SetNumberField(TEXT("numSubsections"), NumSubsections);
|
|
613
|
-
Result->SetNumberField(TEXT("sizeX"), SizeX);
|
|
614
|
-
Result->SetNumberField(TEXT("sizeY"), SizeY);
|
|
615
|
-
|
|
616
|
-
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
617
|
-
Payload->SetStringField(TEXT("actorLabel"), Landscape->GetActorLabel());
|
|
618
|
-
MCPSetRollback(Result, TEXT("delete_actor"), Payload);
|
|
619
|
-
|
|
620
|
-
return MCPResult(Result);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// #251: standalone LayerInfo asset creation. Unlike add_landscape_layer_info
|
|
624
|
-
// (which requires a landscape in the world to register the layer against),
|
|
625
|
-
// this creates the ULandscapeLayerInfoObject asset in the content browser
|
|
626
|
-
// so paint workflows can pre-author layer assets before the landscape exists.
|
|
627
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::CreateLandscapeLayerInfo(const TSharedPtr<FJsonObject>& Params)
|
|
628
|
-
{
|
|
629
|
-
FString LayerName;
|
|
630
|
-
if (auto Err = RequireString(Params, TEXT("layerName"), LayerName)) return Err;
|
|
631
|
-
|
|
632
|
-
const FString Name = OptionalString(Params, TEXT("name"), FString::Printf(TEXT("LI_%s"), *LayerName));
|
|
633
|
-
const FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Landscape/LayerInfos"));
|
|
634
|
-
const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("skip"));
|
|
635
|
-
|
|
636
|
-
TSharedPtr<FJsonValue> Existing = MCPCheckAssetExists(PackagePath, Name, OnConflict, TEXT("LandscapeLayerInfoObject"));
|
|
637
|
-
if (Existing.IsValid()) return Existing;
|
|
638
|
-
|
|
639
|
-
const FString PackageFullPath = PackagePath / Name;
|
|
640
|
-
UPackage* Package = CreatePackage(*PackageFullPath);
|
|
641
|
-
if (!Package)
|
|
642
|
-
{
|
|
643
|
-
return MCPError(FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
ULandscapeLayerInfoObject* LayerInfo = NewObject<ULandscapeLayerInfoObject>(
|
|
647
|
-
Package, *Name, RF_Public | RF_Standalone);
|
|
648
|
-
if (!LayerInfo)
|
|
649
|
-
{
|
|
650
|
-
return MCPError(TEXT("Failed to create LandscapeLayerInfoObject"));
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
654
|
-
LayerInfo->LayerName = FName(*LayerName);
|
|
655
|
-
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
656
|
-
|
|
657
|
-
// physMaterial was documented here and never applied - the caller had to
|
|
658
|
-
// discover for themselves that it needed a second call. PhysicsCore is not
|
|
659
|
-
// a hard dependency of this module, so the class is reached by path and the
|
|
660
|
-
// property set through reflection rather than a link-time include.
|
|
661
|
-
const FString PhysMaterialPath = OptionalString(Params, TEXT("physMaterial"));
|
|
662
|
-
if (!PhysMaterialPath.IsEmpty())
|
|
663
|
-
{
|
|
664
|
-
UObject* PhysMat = LoadAssetByPath<UObject>(PhysMaterialPath);
|
|
665
|
-
if (!PhysMat)
|
|
666
|
-
{
|
|
667
|
-
// Both failure paths here run after the package and object exist, so
|
|
668
|
-
// bail out without leaving a half-made asset in memory.
|
|
669
|
-
LayerInfo->MarkAsGarbage();
|
|
670
|
-
return MCPError(FString::Printf(TEXT("physMaterial not found: %s"), *PhysMaterialPath));
|
|
671
|
-
}
|
|
672
|
-
FObjectProperty* Prop = CastField<FObjectProperty>(
|
|
673
|
-
ULandscapeLayerInfoObject::StaticClass()->FindPropertyByName(TEXT("PhysMaterial")));
|
|
674
|
-
if (!Prop || !Prop->PropertyClass || !PhysMat->IsA(Prop->PropertyClass))
|
|
675
|
-
{
|
|
676
|
-
LayerInfo->MarkAsGarbage();
|
|
677
|
-
return MCPError(FString::Printf(
|
|
678
|
-
TEXT("'%s' is a %s, not a PhysicalMaterial."),
|
|
679
|
-
*PhysMaterialPath, *PhysMat->GetClass()->GetName()));
|
|
680
|
-
}
|
|
681
|
-
Prop->SetObjectPropertyValue_InContainer(LayerInfo, PhysMat);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
double Hardness = 0.0;
|
|
685
|
-
if (Params->TryGetNumberField(TEXT("hardness"), Hardness))
|
|
686
|
-
{
|
|
687
|
-
// Hardness is becoming private; the setter also handles Modify() and the
|
|
688
|
-
// property-change notification the direct write skipped.
|
|
689
|
-
LayerInfo->SetHardness(static_cast<float>(Hardness), /*bInModify=*/true, EPropertyChangeType::ValueSet);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
FAssetRegistryModule::AssetCreated(LayerInfo);
|
|
693
|
-
Package->MarkPackageDirty();
|
|
694
|
-
SaveAssetPackage(LayerInfo);
|
|
695
|
-
|
|
696
|
-
auto Result = MCPSuccess();
|
|
697
|
-
MCPSetCreated(Result);
|
|
698
|
-
Result->SetStringField(TEXT("path"), LayerInfo->GetPathName());
|
|
699
|
-
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
700
|
-
Result->SetStringField(TEXT("packagePath"), PackagePath);
|
|
701
|
-
if (!PhysMaterialPath.IsEmpty()) Result->SetStringField(TEXT("physMaterial"), PhysMaterialPath);
|
|
702
|
-
MCPSetDeleteAssetRollback(Result, LayerInfo->GetPathName());
|
|
703
|
-
|
|
704
|
-
return MCPResult(Result);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::GetMaterialUsageSummary(const TSharedPtr<FJsonObject>& Params)
|
|
708
|
-
{
|
|
709
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
710
|
-
|
|
711
|
-
TArray<TSharedPtr<FJsonValue>> ProxyArray;
|
|
712
|
-
TSet<FString> UniqueMaterials;
|
|
713
|
-
int32 TotalComponents = 0, TotalGrass = 0, TotalNanite = 0;
|
|
714
|
-
|
|
715
|
-
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
716
|
-
{
|
|
717
|
-
ALandscapeProxy* Proxy = *It;
|
|
718
|
-
if (!Proxy) continue;
|
|
719
|
-
|
|
720
|
-
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
721
|
-
Obj->SetStringField(TEXT("label"), Proxy->GetActorLabel());
|
|
722
|
-
Obj->SetStringField(TEXT("name"), Proxy->GetName());
|
|
723
|
-
Obj->SetStringField(TEXT("class"), Proxy->GetClass()->GetName());
|
|
724
|
-
Obj->SetStringField(TEXT("path"), Proxy->GetPathName());
|
|
725
|
-
|
|
726
|
-
if (UMaterialInterface* Mat = Proxy->LandscapeMaterial)
|
|
727
|
-
{
|
|
728
|
-
Obj->SetStringField(TEXT("landscapeMaterial"), Mat->GetPathName());
|
|
729
|
-
UniqueMaterials.Add(Mat->GetPathName());
|
|
730
|
-
}
|
|
731
|
-
if (UMaterialInterface* HoleMat = Proxy->LandscapeHoleMaterial)
|
|
732
|
-
{
|
|
733
|
-
Obj->SetStringField(TEXT("landscapeHoleMaterial"), HoleMat->GetPathName());
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// Histogram components by class (grass / Nanite / regular landscape comps)
|
|
737
|
-
int32 LandscapeComps = 0, GrassComps = 0, NaniteComps = 0;
|
|
738
|
-
TArray<UActorComponent*> Comps;
|
|
739
|
-
Proxy->GetComponents(Comps);
|
|
740
|
-
for (UActorComponent* C : Comps)
|
|
741
|
-
{
|
|
742
|
-
if (!C) continue;
|
|
743
|
-
const FString CName = C->GetClass()->GetName();
|
|
744
|
-
if (CName == TEXT("LandscapeComponent")) LandscapeComps++;
|
|
745
|
-
else if (CName == TEXT("GrassInstancedStaticMeshComponent")) GrassComps++;
|
|
746
|
-
else if (CName == TEXT("LandscapeNaniteComponent")) NaniteComps++;
|
|
747
|
-
}
|
|
748
|
-
Obj->SetNumberField(TEXT("landscapeComponentCount"), LandscapeComps);
|
|
749
|
-
Obj->SetNumberField(TEXT("grassComponentCount"), GrassComps);
|
|
750
|
-
Obj->SetNumberField(TEXT("naniteComponentCount"), NaniteComps);
|
|
751
|
-
TotalComponents += LandscapeComps;
|
|
752
|
-
TotalGrass += GrassComps;
|
|
753
|
-
TotalNanite += NaniteComps;
|
|
754
|
-
|
|
755
|
-
const FVector Loc = Proxy->GetActorLocation();
|
|
756
|
-
const FVector Scale = Proxy->GetActorScale3D();
|
|
757
|
-
TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
|
|
758
|
-
LocObj->SetNumberField(TEXT("x"), Loc.X);
|
|
759
|
-
LocObj->SetNumberField(TEXT("y"), Loc.Y);
|
|
760
|
-
LocObj->SetNumberField(TEXT("z"), Loc.Z);
|
|
761
|
-
Obj->SetObjectField(TEXT("location"), LocObj);
|
|
762
|
-
TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
|
|
763
|
-
ScaleObj->SetNumberField(TEXT("x"), Scale.X);
|
|
764
|
-
ScaleObj->SetNumberField(TEXT("y"), Scale.Y);
|
|
765
|
-
ScaleObj->SetNumberField(TEXT("z"), Scale.Z);
|
|
766
|
-
Obj->SetObjectField(TEXT("scale"), ScaleObj);
|
|
767
|
-
|
|
768
|
-
ProxyArray.Add(MakeShared<FJsonValueObject>(Obj));
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
TArray<TSharedPtr<FJsonValue>> UniqueMatsArr;
|
|
772
|
-
for (const FString& M : UniqueMaterials) UniqueMatsArr.Add(MakeShared<FJsonValueString>(M));
|
|
773
|
-
|
|
774
|
-
auto Result = MCPSuccess();
|
|
775
|
-
Result->SetArrayField(TEXT("proxies"), ProxyArray);
|
|
776
|
-
Result->SetNumberField(TEXT("proxyCount"), ProxyArray.Num());
|
|
777
|
-
Result->SetArrayField(TEXT("uniqueLandscapeMaterials"), UniqueMatsArr);
|
|
778
|
-
Result->SetNumberField(TEXT("totalLandscapeComponents"), TotalComponents);
|
|
779
|
-
Result->SetNumberField(TEXT("totalGrassComponents"), TotalGrass);
|
|
780
|
-
Result->SetNumberField(TEXT("totalNaniteComponents"), TotalNanite);
|
|
781
|
-
return MCPResult(Result);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// #733: enumerate LandscapeStreamingProxy actors currently loaded in the world,
|
|
785
|
-
// with per-proxy world bounds and the parent Landscape count. On a World
|
|
786
|
-
// Partition map, an unloaded proxy silently reads layer weights as 0, so a
|
|
787
|
-
// measurement is only trustworthy once the covering proxy is confirmed loaded.
|
|
788
|
-
// Unloaded proxies are not spawned as actors, so the actor iterator only yields
|
|
789
|
-
// loaded ones - hence loaded is always true for enumerated entries; callers use
|
|
790
|
-
// the count + bounds to reason about coverage.
|
|
791
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeProxies(const TSharedPtr<FJsonObject>& Params)
|
|
792
|
-
{
|
|
793
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
794
|
-
|
|
795
|
-
int32 ParentLandscapes = 0;
|
|
796
|
-
TArray<TSharedPtr<FJsonValue>> Proxies;
|
|
797
|
-
for (TActorIterator<AActor> It(World); It; ++It)
|
|
798
|
-
{
|
|
799
|
-
AActor* Actor = *It;
|
|
800
|
-
if (!Actor) continue;
|
|
801
|
-
if (Actor->IsA<ALandscape>())
|
|
802
|
-
{
|
|
803
|
-
ParentLandscapes++;
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
ALandscapeStreamingProxy* Proxy = Cast<ALandscapeStreamingProxy>(Actor);
|
|
807
|
-
if (!Proxy) continue;
|
|
808
|
-
|
|
809
|
-
FVector Origin, Extent;
|
|
810
|
-
Proxy->GetActorBounds(false, Origin, Extent);
|
|
811
|
-
|
|
812
|
-
TSharedPtr<FJsonObject> ProxyObj = MakeShared<FJsonObject>();
|
|
813
|
-
ProxyObj->SetStringField(TEXT("label"), Proxy->GetActorLabel());
|
|
814
|
-
ProxyObj->SetBoolField(TEXT("loaded"), true);
|
|
815
|
-
|
|
816
|
-
TSharedPtr<FJsonObject> Bounds = MakeShared<FJsonObject>();
|
|
817
|
-
TSharedPtr<FJsonObject> OriginObj = MakeShared<FJsonObject>();
|
|
818
|
-
OriginObj->SetNumberField(TEXT("x"), Origin.X);
|
|
819
|
-
OriginObj->SetNumberField(TEXT("y"), Origin.Y);
|
|
820
|
-
OriginObj->SetNumberField(TEXT("z"), Origin.Z);
|
|
821
|
-
TSharedPtr<FJsonObject> ExtentObj = MakeShared<FJsonObject>();
|
|
822
|
-
ExtentObj->SetNumberField(TEXT("x"), Extent.X);
|
|
823
|
-
ExtentObj->SetNumberField(TEXT("y"), Extent.Y);
|
|
824
|
-
ExtentObj->SetNumberField(TEXT("z"), Extent.Z);
|
|
825
|
-
Bounds->SetObjectField(TEXT("origin"), OriginObj);
|
|
826
|
-
Bounds->SetObjectField(TEXT("extent"), ExtentObj);
|
|
827
|
-
ProxyObj->SetObjectField(TEXT("worldBounds"), Bounds);
|
|
828
|
-
|
|
829
|
-
Proxies.Add(MakeShared<FJsonValueObject>(ProxyObj));
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
auto Result = MCPSuccess();
|
|
833
|
-
Result->SetNumberField(TEXT("loadedProxies"), Proxies.Num());
|
|
834
|
-
Result->SetNumberField(TEXT("parentLandscapes"), ParentLandscapes);
|
|
835
|
-
Result->SetArrayField(TEXT("proxies"), Proxies);
|
|
836
|
-
Result->SetStringField(TEXT("note"), TEXT("World Partition unloaded proxies are not spawned as actors, so only loaded proxies are listed."));
|
|
837
|
-
return MCPResult(Result);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// #733: resolve which loaded LandscapeStreamingProxy's world bounds contain a
|
|
841
|
-
// world X/Y. Returns the covering proxy (loaded:true) or loaded:false when no
|
|
842
|
-
// loaded proxy covers the position - which usually means the covering proxy is
|
|
843
|
-
// streamed out, making any 0-weight readback there ambiguous rather than real.
|
|
844
|
-
TSharedPtr<FJsonValue> FLandscapeHandlers::FindLandscapeProxyAt(const TSharedPtr<FJsonObject>& Params)
|
|
845
|
-
{
|
|
846
|
-
REQUIRE_EDITOR_WORLD(World);
|
|
847
|
-
|
|
848
|
-
if (!Params->HasField(TEXT("worldX")) || !Params->HasField(TEXT("worldY")))
|
|
849
|
-
{
|
|
850
|
-
return MCPError(TEXT("Missing 'worldX'/'worldY' world position"));
|
|
851
|
-
}
|
|
852
|
-
const double TargetX = OptionalNumber(Params, TEXT("worldX"), 0.0);
|
|
853
|
-
const double TargetY = OptionalNumber(Params, TEXT("worldY"), 0.0);
|
|
854
|
-
|
|
855
|
-
for (TActorIterator<AActor> It(World); It; ++It)
|
|
856
|
-
{
|
|
857
|
-
ALandscapeStreamingProxy* Proxy = Cast<ALandscapeStreamingProxy>(*It);
|
|
858
|
-
if (!Proxy) continue;
|
|
859
|
-
|
|
860
|
-
FVector Origin, Extent;
|
|
861
|
-
Proxy->GetActorBounds(false, Origin, Extent);
|
|
862
|
-
if (TargetX >= Origin.X - Extent.X && TargetX <= Origin.X + Extent.X &&
|
|
863
|
-
TargetY >= Origin.Y - Extent.Y && TargetY <= Origin.Y + Extent.Y)
|
|
864
|
-
{
|
|
865
|
-
auto Result = MCPSuccess();
|
|
866
|
-
Result->SetBoolField(TEXT("found"), true);
|
|
867
|
-
Result->SetBoolField(TEXT("loaded"), true);
|
|
868
|
-
Result->SetStringField(TEXT("label"), Proxy->GetActorLabel());
|
|
869
|
-
return MCPResult(Result);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
auto Result = MCPSuccess();
|
|
874
|
-
Result->SetBoolField(TEXT("found"), false);
|
|
875
|
-
Result->SetBoolField(TEXT("loaded"), false);
|
|
876
|
-
Result->SetStringField(TEXT("note"), TEXT("No loaded proxy covers this position; the covering proxy is likely streamed out, so weight/height readbacks here are ambiguous."));
|
|
877
|
-
return MCPResult(Result);
|
|
878
|
-
}
|
|
1
|
+
#include "LandscapeHandlers.h"
|
|
2
|
+
#include "HandlerRegistry.h"
|
|
3
|
+
#include "HandlerUtils.h"
|
|
4
|
+
#include "Dom/JsonObject.h"
|
|
5
|
+
#include "Dom/JsonValue.h"
|
|
6
|
+
#include "Editor.h"
|
|
7
|
+
#include "Engine/World.h"
|
|
8
|
+
#include "EngineUtils.h"
|
|
9
|
+
#include "Landscape.h"
|
|
10
|
+
#include "LandscapeEditTypes.h"
|
|
11
|
+
#include "LandscapeProxy.h"
|
|
12
|
+
#include "LandscapeStreamingProxy.h"
|
|
13
|
+
#include "LandscapeInfo.h"
|
|
14
|
+
#include "LandscapeComponent.h"
|
|
15
|
+
#include "LandscapeSplineActor.h"
|
|
16
|
+
#include "LandscapeSplinesComponent.h"
|
|
17
|
+
#include "LandscapeSplineControlPoint.h"
|
|
18
|
+
#include "LandscapeSplineSegment.h"
|
|
19
|
+
#include "Kismet/KismetSystemLibrary.h"
|
|
20
|
+
#include "Misc/FileHelper.h"
|
|
21
|
+
#include "Materials/MaterialInterface.h"
|
|
22
|
+
#include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
|
|
23
|
+
#include "Components/PrimitiveComponent.h"
|
|
24
|
+
#include "LandscapeLayerInfoObject.h"
|
|
25
|
+
#include "UObject/Package.h"
|
|
26
|
+
#include "AssetRegistry/AssetRegistryModule.h"
|
|
27
|
+
#include "AssetToolsModule.h"
|
|
28
|
+
#include "IAssetTools.h"
|
|
29
|
+
|
|
30
|
+
void FLandscapeHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
31
|
+
{
|
|
32
|
+
Registry.RegisterHandler(TEXT("get_landscape_info"), &GetLandscapeInfo);
|
|
33
|
+
Registry.RegisterHandler(TEXT("list_landscape_layers"), &ListLandscapeLayers);
|
|
34
|
+
Registry.RegisterHandler(TEXT("sample_landscape"), &SampleLandscape);
|
|
35
|
+
Registry.RegisterHandler(TEXT("list_landscape_splines"), &ListLandscapeSplines);
|
|
36
|
+
Registry.RegisterHandler(TEXT("get_landscape_component"), &GetLandscapeComponent);
|
|
37
|
+
Registry.RegisterHandler(TEXT("set_landscape_material"), &SetLandscapeMaterial);
|
|
38
|
+
Registry.RegisterHandler(TEXT("add_landscape_layer_info"), &AddLandscapeLayerInfo);
|
|
39
|
+
Registry.RegisterHandler(TEXT("create_landscape"), &CreateLandscape);
|
|
40
|
+
Registry.RegisterHandler(TEXT("create_landscape_layer_info"), &CreateLandscapeLayerInfo);
|
|
41
|
+
Registry.RegisterHandler(TEXT("get_landscape_material_usage_summary"), &GetMaterialUsageSummary);
|
|
42
|
+
// #733: World Partition landscape streaming-proxy enumeration + spatial lookup.
|
|
43
|
+
Registry.RegisterHandler(TEXT("list_landscape_proxies"), &ListLandscapeProxies);
|
|
44
|
+
Registry.RegisterHandler(TEXT("find_landscape_proxy_at"), &FindLandscapeProxyAt);
|
|
45
|
+
Registry.RegisterHandlerWithTimeout(TEXT("sculpt_landscape"), &Sculpt, 120.0f);
|
|
46
|
+
Registry.RegisterHandlerWithTimeout(TEXT("paint_landscape_layer"), &PaintLayer, 120.0f);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeInfo(const TSharedPtr<FJsonObject>& Params)
|
|
50
|
+
{
|
|
51
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
52
|
+
|
|
53
|
+
// Find landscape proxies in the world
|
|
54
|
+
TArray<TSharedPtr<FJsonValue>> LandscapeArray;
|
|
55
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
56
|
+
{
|
|
57
|
+
ALandscapeProxy* Landscape = *It;
|
|
58
|
+
if (!Landscape) continue;
|
|
59
|
+
|
|
60
|
+
TSharedPtr<FJsonObject> LandscapeObj = MakeShared<FJsonObject>();
|
|
61
|
+
LandscapeObj->SetStringField(TEXT("name"), Landscape->GetName());
|
|
62
|
+
LandscapeObj->SetStringField(TEXT("class"), Landscape->GetClass()->GetName());
|
|
63
|
+
|
|
64
|
+
// Get component count
|
|
65
|
+
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
66
|
+
Landscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
67
|
+
LandscapeObj->SetNumberField(TEXT("componentCount"), LandscapeComponents.Num());
|
|
68
|
+
|
|
69
|
+
// Get bounds
|
|
70
|
+
FBox Bounds = Landscape->GetComponentsBoundingBox();
|
|
71
|
+
if (Bounds.IsValid)
|
|
72
|
+
{
|
|
73
|
+
TSharedPtr<FJsonObject> BoundsObj = MakeShared<FJsonObject>();
|
|
74
|
+
BoundsObj->SetNumberField(TEXT("minX"), Bounds.Min.X);
|
|
75
|
+
BoundsObj->SetNumberField(TEXT("minY"), Bounds.Min.Y);
|
|
76
|
+
BoundsObj->SetNumberField(TEXT("minZ"), Bounds.Min.Z);
|
|
77
|
+
BoundsObj->SetNumberField(TEXT("maxX"), Bounds.Max.X);
|
|
78
|
+
BoundsObj->SetNumberField(TEXT("maxY"), Bounds.Max.Y);
|
|
79
|
+
BoundsObj->SetNumberField(TEXT("maxZ"), Bounds.Max.Z);
|
|
80
|
+
|
|
81
|
+
FVector Size = Bounds.GetSize();
|
|
82
|
+
BoundsObj->SetNumberField(TEXT("sizeX"), Size.X);
|
|
83
|
+
BoundsObj->SetNumberField(TEXT("sizeY"), Size.Y);
|
|
84
|
+
BoundsObj->SetNumberField(TEXT("sizeZ"), Size.Z);
|
|
85
|
+
LandscapeObj->SetObjectField(TEXT("bounds"), BoundsObj);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Get location
|
|
89
|
+
FVector Location = Landscape->GetActorLocation();
|
|
90
|
+
LandscapeObj->SetNumberField(TEXT("locationX"), Location.X);
|
|
91
|
+
LandscapeObj->SetNumberField(TEXT("locationY"), Location.Y);
|
|
92
|
+
LandscapeObj->SetNumberField(TEXT("locationZ"), Location.Z);
|
|
93
|
+
|
|
94
|
+
LandscapeArray.Add(MakeShared<FJsonValueObject>(LandscapeObj));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
auto Result = MCPSuccess();
|
|
98
|
+
if (LandscapeArray.Num() == 0)
|
|
99
|
+
{
|
|
100
|
+
Result->SetStringField(TEXT("landscape"), TEXT("none"));
|
|
101
|
+
}
|
|
102
|
+
else
|
|
103
|
+
{
|
|
104
|
+
Result->SetArrayField(TEXT("landscapes"), LandscapeArray);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Result->SetNumberField(TEXT("count"), LandscapeArray.Num());
|
|
108
|
+
return MCPResult(Result);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeLayers(const TSharedPtr<FJsonObject>& Params)
|
|
112
|
+
{
|
|
113
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
114
|
+
|
|
115
|
+
TArray<TSharedPtr<FJsonValue>> LayerArray;
|
|
116
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
117
|
+
{
|
|
118
|
+
ALandscapeProxy* Landscape = *It;
|
|
119
|
+
if (!Landscape) continue;
|
|
120
|
+
|
|
121
|
+
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
|
|
122
|
+
if (LandscapeInfo)
|
|
123
|
+
{
|
|
124
|
+
for (const FLandscapeInfoLayerSettings& LayerSettings : LandscapeInfo->Layers)
|
|
125
|
+
{
|
|
126
|
+
if (LayerSettings.LayerInfoObj)
|
|
127
|
+
{
|
|
128
|
+
TSharedPtr<FJsonObject> LayerObj = MakeShared<FJsonObject>();
|
|
129
|
+
LayerObj->SetStringField(TEXT("name"), LayerSettings.GetLayerName().ToString());
|
|
130
|
+
LayerObj->SetStringField(TEXT("landscapeName"), Landscape->GetName());
|
|
131
|
+
LayerArray.Add(MakeShared<FJsonValueObject>(LayerObj));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
auto Result = MCPSuccess();
|
|
138
|
+
Result->SetArrayField(TEXT("layers"), LayerArray);
|
|
139
|
+
Result->SetNumberField(TEXT("count"), LayerArray.Num());
|
|
140
|
+
return MCPResult(Result);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::SampleLandscape(const TSharedPtr<FJsonObject>& Params)
|
|
144
|
+
{
|
|
145
|
+
const TSharedPtr<FJsonObject>* PointObj = nullptr;
|
|
146
|
+
if (!Params->TryGetObjectField(TEXT("point"), PointObj))
|
|
147
|
+
{
|
|
148
|
+
return MCPError(TEXT("Missing 'point' parameter"));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
FVector Point;
|
|
152
|
+
Point.X = (*PointObj)->GetNumberField(TEXT("x"));
|
|
153
|
+
Point.Y = (*PointObj)->GetNumberField(TEXT("y"));
|
|
154
|
+
Point.Z = (*PointObj)->GetNumberField(TEXT("z"));
|
|
155
|
+
|
|
156
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
157
|
+
|
|
158
|
+
// Find the first landscape and sample height
|
|
159
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
160
|
+
{
|
|
161
|
+
ALandscapeProxy* Landscape = *It;
|
|
162
|
+
if (!Landscape) continue;
|
|
163
|
+
|
|
164
|
+
// Use line trace to get the landscape height at the given point
|
|
165
|
+
FVector TraceStart(Point.X, Point.Y, Point.Z + 100000.0f);
|
|
166
|
+
FVector TraceEnd(Point.X, Point.Y, Point.Z - 100000.0f);
|
|
167
|
+
|
|
168
|
+
FHitResult HitResult;
|
|
169
|
+
FCollisionQueryParams QueryParams;
|
|
170
|
+
QueryParams.bTraceComplex = true;
|
|
171
|
+
|
|
172
|
+
if (World->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_WorldStatic, QueryParams))
|
|
173
|
+
{
|
|
174
|
+
if (HitResult.GetActor() && HitResult.GetActor()->IsA(ALandscapeProxy::StaticClass()))
|
|
175
|
+
{
|
|
176
|
+
auto Result = MCPSuccess();
|
|
177
|
+
Result->SetNumberField(TEXT("height"), HitResult.Location.Z);
|
|
178
|
+
TSharedPtr<FJsonObject> HitPoint = MakeShared<FJsonObject>();
|
|
179
|
+
HitPoint->SetNumberField(TEXT("x"), HitResult.Location.X);
|
|
180
|
+
HitPoint->SetNumberField(TEXT("y"), HitResult.Location.Y);
|
|
181
|
+
HitPoint->SetNumberField(TEXT("z"), HitResult.Location.Z);
|
|
182
|
+
Result->SetObjectField(TEXT("hitLocation"), HitPoint);
|
|
183
|
+
|
|
184
|
+
TSharedPtr<FJsonObject> Normal = MakeShared<FJsonObject>();
|
|
185
|
+
Normal->SetNumberField(TEXT("x"), HitResult.Normal.X);
|
|
186
|
+
Normal->SetNumberField(TEXT("y"), HitResult.Normal.Y);
|
|
187
|
+
Normal->SetNumberField(TEXT("z"), HitResult.Normal.Z);
|
|
188
|
+
Result->SetObjectField(TEXT("normal"), Normal);
|
|
189
|
+
|
|
190
|
+
Result->SetBoolField(TEXT("hit"), true);
|
|
191
|
+
return MCPResult(Result);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
auto Result = MCPSuccess();
|
|
197
|
+
Result->SetBoolField(TEXT("hit"), false);
|
|
198
|
+
return MCPResult(Result);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeSplines(const TSharedPtr<FJsonObject>& Params)
|
|
202
|
+
{
|
|
203
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
204
|
+
|
|
205
|
+
TArray<TSharedPtr<FJsonValue>> SplineArray;
|
|
206
|
+
|
|
207
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
208
|
+
{
|
|
209
|
+
ALandscapeProxy* Landscape = *It;
|
|
210
|
+
if (!Landscape) continue;
|
|
211
|
+
|
|
212
|
+
ULandscapeSplinesComponent* SplinesComp = Landscape->GetSplinesComponent();
|
|
213
|
+
if (!SplinesComp) continue;
|
|
214
|
+
|
|
215
|
+
const TArray<TObjectPtr<ULandscapeSplineControlPoint>>& ControlPoints = SplinesComp->GetControlPoints();
|
|
216
|
+
for (const TObjectPtr<ULandscapeSplineControlPoint>& CP : ControlPoints)
|
|
217
|
+
{
|
|
218
|
+
if (!CP) continue;
|
|
219
|
+
|
|
220
|
+
TSharedPtr<FJsonObject> PointObj = MakeShared<FJsonObject>();
|
|
221
|
+
FVector Location = CP->Location;
|
|
222
|
+
PointObj->SetNumberField(TEXT("x"), Location.X);
|
|
223
|
+
PointObj->SetNumberField(TEXT("y"), Location.Y);
|
|
224
|
+
PointObj->SetNumberField(TEXT("z"), Location.Z);
|
|
225
|
+
PointObj->SetStringField(TEXT("landscapeName"), Landscape->GetName());
|
|
226
|
+
SplineArray.Add(MakeShared<FJsonValueObject>(PointObj));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
auto Result = MCPSuccess();
|
|
231
|
+
Result->SetArrayField(TEXT("controlPoints"), SplineArray);
|
|
232
|
+
Result->SetNumberField(TEXT("count"), SplineArray.Num());
|
|
233
|
+
return MCPResult(Result);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeComponent(const TSharedPtr<FJsonObject>& Params)
|
|
237
|
+
{
|
|
238
|
+
int32 ComponentIndex = 0;
|
|
239
|
+
if (Params->HasField(TEXT("componentIndex")))
|
|
240
|
+
{
|
|
241
|
+
ComponentIndex = static_cast<int32>(Params->GetNumberField(TEXT("componentIndex")));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
245
|
+
|
|
246
|
+
// Collect all landscape components across all landscape proxies
|
|
247
|
+
TArray<ULandscapeComponent*> AllComponents;
|
|
248
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
249
|
+
{
|
|
250
|
+
ALandscapeProxy* Landscape = *It;
|
|
251
|
+
if (!Landscape) continue;
|
|
252
|
+
|
|
253
|
+
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
254
|
+
Landscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
255
|
+
AllComponents.Append(LandscapeComponents);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (ComponentIndex < 0 || ComponentIndex >= AllComponents.Num())
|
|
259
|
+
{
|
|
260
|
+
return MCPError(FString::Printf(TEXT("Component index %d out of range (0-%d)"), ComponentIndex, AllComponents.Num() - 1));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
ULandscapeComponent* Comp = AllComponents[ComponentIndex];
|
|
264
|
+
if (!Comp)
|
|
265
|
+
{
|
|
266
|
+
return MCPError(TEXT("Component is null"));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
auto Result = MCPSuccess();
|
|
270
|
+
Result->SetNumberField(TEXT("componentIndex"), ComponentIndex);
|
|
271
|
+
Result->SetStringField(TEXT("name"), Comp->GetName());
|
|
272
|
+
|
|
273
|
+
FVector CompLocation = Comp->GetComponentLocation();
|
|
274
|
+
Result->SetNumberField(TEXT("locationX"), CompLocation.X);
|
|
275
|
+
Result->SetNumberField(TEXT("locationY"), CompLocation.Y);
|
|
276
|
+
Result->SetNumberField(TEXT("locationZ"), CompLocation.Z);
|
|
277
|
+
|
|
278
|
+
Result->SetNumberField(TEXT("sectionBaseX"), Comp->SectionBaseX);
|
|
279
|
+
Result->SetNumberField(TEXT("sectionBaseY"), Comp->SectionBaseY);
|
|
280
|
+
Result->SetNumberField(TEXT("componentSizeQuads"), Comp->ComponentSizeQuads);
|
|
281
|
+
Result->SetNumberField(TEXT("subSections"), Comp->NumSubsections);
|
|
282
|
+
|
|
283
|
+
FBox CompBounds = Comp->Bounds.GetBox();
|
|
284
|
+
if (CompBounds.IsValid)
|
|
285
|
+
{
|
|
286
|
+
TSharedPtr<FJsonObject> BoundsObj = MakeShared<FJsonObject>();
|
|
287
|
+
BoundsObj->SetNumberField(TEXT("minX"), CompBounds.Min.X);
|
|
288
|
+
BoundsObj->SetNumberField(TEXT("minY"), CompBounds.Min.Y);
|
|
289
|
+
BoundsObj->SetNumberField(TEXT("minZ"), CompBounds.Min.Z);
|
|
290
|
+
BoundsObj->SetNumberField(TEXT("maxX"), CompBounds.Max.X);
|
|
291
|
+
BoundsObj->SetNumberField(TEXT("maxY"), CompBounds.Max.Y);
|
|
292
|
+
BoundsObj->SetNumberField(TEXT("maxZ"), CompBounds.Max.Z);
|
|
293
|
+
Result->SetObjectField(TEXT("bounds"), BoundsObj);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return MCPResult(Result);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::SetLandscapeMaterial(const TSharedPtr<FJsonObject>& Params)
|
|
300
|
+
{
|
|
301
|
+
FString MaterialPath;
|
|
302
|
+
if (!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath))
|
|
303
|
+
{
|
|
304
|
+
return MCPError(TEXT("Missing 'materialPath', 'path', or 'assetPath' parameter"));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
308
|
+
|
|
309
|
+
// Find the target landscape
|
|
310
|
+
ALandscapeProxy* TargetLandscape = nullptr;
|
|
311
|
+
FString LandscapeName = OptionalString(Params, TEXT("landscapeName"));
|
|
312
|
+
|
|
313
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
314
|
+
{
|
|
315
|
+
ALandscapeProxy* Landscape = *It;
|
|
316
|
+
if (!Landscape) continue;
|
|
317
|
+
|
|
318
|
+
if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
|
|
319
|
+
{
|
|
320
|
+
TargetLandscape = Landscape;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!TargetLandscape)
|
|
326
|
+
{
|
|
327
|
+
return MCPError(TEXT("No landscape found in the current level"));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Load the material
|
|
331
|
+
UMaterialInterface* Material = LoadObject<UMaterialInterface>(nullptr, *MaterialPath);
|
|
332
|
+
if (!Material)
|
|
333
|
+
{
|
|
334
|
+
return MCPError(FString::Printf(TEXT("Material not found: %s"), *MaterialPath));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Capture previous material for rollback and idempotency
|
|
338
|
+
UMaterialInterface* PrevMaterial = TargetLandscape->LandscapeMaterial;
|
|
339
|
+
if (PrevMaterial == Material)
|
|
340
|
+
{
|
|
341
|
+
auto Noop = MCPSuccess();
|
|
342
|
+
MCPSetExisted(Noop);
|
|
343
|
+
Noop->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
344
|
+
Noop->SetStringField(TEXT("materialPath"), MaterialPath);
|
|
345
|
+
return MCPResult(Noop);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Set the landscape material
|
|
349
|
+
TargetLandscape->LandscapeMaterial = Material;
|
|
350
|
+
|
|
351
|
+
// Update all landscape components to use the new material
|
|
352
|
+
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
353
|
+
TargetLandscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
354
|
+
for (ULandscapeComponent* Comp : LandscapeComponents)
|
|
355
|
+
{
|
|
356
|
+
if (Comp)
|
|
357
|
+
{
|
|
358
|
+
Comp->SetMaterial(0, Material);
|
|
359
|
+
Comp->MarkRenderStateDirty();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Mark the landscape as modified
|
|
364
|
+
TargetLandscape->MarkPackageDirty();
|
|
365
|
+
|
|
366
|
+
auto Result = MCPSuccess();
|
|
367
|
+
MCPSetUpdated(Result);
|
|
368
|
+
Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
369
|
+
Result->SetStringField(TEXT("materialPath"), MaterialPath);
|
|
370
|
+
Result->SetStringField(TEXT("materialName"), Material->GetName());
|
|
371
|
+
Result->SetNumberField(TEXT("componentsUpdated"), LandscapeComponents.Num());
|
|
372
|
+
|
|
373
|
+
// Rollback: restore previous material path if any
|
|
374
|
+
if (PrevMaterial)
|
|
375
|
+
{
|
|
376
|
+
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
377
|
+
Payload->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
378
|
+
Payload->SetStringField(TEXT("materialPath"), PrevMaterial->GetPathName());
|
|
379
|
+
MCPSetRollback(Result, TEXT("set_landscape_material"), Payload);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return MCPResult(Result);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::AddLandscapeLayerInfo(const TSharedPtr<FJsonObject>& Params)
|
|
386
|
+
{
|
|
387
|
+
FString LayerName;
|
|
388
|
+
if (auto Err = RequireString(Params, TEXT("layerName"), LayerName)) return Err;
|
|
389
|
+
|
|
390
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
391
|
+
|
|
392
|
+
// Find the target landscape
|
|
393
|
+
ALandscapeProxy* TargetLandscape = nullptr;
|
|
394
|
+
FString LandscapeName = OptionalString(Params, TEXT("landscapeName"));
|
|
395
|
+
|
|
396
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
397
|
+
{
|
|
398
|
+
ALandscapeProxy* Landscape = *It;
|
|
399
|
+
if (!Landscape) continue;
|
|
400
|
+
|
|
401
|
+
if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
|
|
402
|
+
{
|
|
403
|
+
TargetLandscape = Landscape;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!TargetLandscape)
|
|
409
|
+
{
|
|
410
|
+
return MCPError(TEXT("No landscape found in the current level"));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
ULandscapeInfo* LandscapeInfo = TargetLandscape->GetLandscapeInfo();
|
|
414
|
+
if (!LandscapeInfo)
|
|
415
|
+
{
|
|
416
|
+
return MCPError(TEXT("Failed to get landscape info"));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Check if a layer with this name already exists
|
|
420
|
+
for (const FLandscapeInfoLayerSettings& ExistingLayer : LandscapeInfo->Layers)
|
|
421
|
+
{
|
|
422
|
+
if (ExistingLayer.LayerInfoObj && ExistingLayer.GetLayerName().ToString() == LayerName)
|
|
423
|
+
{
|
|
424
|
+
auto Result = MCPSuccess();
|
|
425
|
+
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
426
|
+
Result->SetStringField(TEXT("path"), ExistingLayer.LayerInfoObj->GetPathName());
|
|
427
|
+
Result->SetStringField(TEXT("note"), TEXT("Layer already exists on this landscape"));
|
|
428
|
+
return MCPResult(Result);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Create a new ULandscapeLayerInfoObject asset
|
|
433
|
+
FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Landscape/LayerInfos"));
|
|
434
|
+
|
|
435
|
+
FString AssetName = FString::Printf(TEXT("LI_%s"), *LayerName);
|
|
436
|
+
FString PackageFullPath = PackagePath / AssetName;
|
|
437
|
+
|
|
438
|
+
// Check if the asset already exists
|
|
439
|
+
ULandscapeLayerInfoObject* LayerInfoObj = LoadObject<ULandscapeLayerInfoObject>(nullptr, *(PackageFullPath + TEXT(".") + AssetName));
|
|
440
|
+
if (!LayerInfoObj)
|
|
441
|
+
{
|
|
442
|
+
UPackage* Package = CreatePackage(*PackageFullPath);
|
|
443
|
+
if (!Package)
|
|
444
|
+
{
|
|
445
|
+
return MCPError(FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
LayerInfoObj = NewObject<ULandscapeLayerInfoObject>(Package, *AssetName, RF_Public | RF_Standalone);
|
|
449
|
+
if (!LayerInfoObj)
|
|
450
|
+
{
|
|
451
|
+
return MCPError(TEXT("Failed to create LandscapeLayerInfoObject"));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
455
|
+
LayerInfoObj->LayerName = FName(*LayerName);
|
|
456
|
+
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
457
|
+
|
|
458
|
+
// There is no weight-blend toggle to set here any more: bNoWeightBlend
|
|
459
|
+
// was removed in 5.7 and blending is controlled per-layer through
|
|
460
|
+
// landscape settings. The old 'weightBlended' param read into a unused
|
|
461
|
+
// local and the response hardcoded true, so both are gone rather than
|
|
462
|
+
// left implying a setting that is not being applied.
|
|
463
|
+
|
|
464
|
+
// Notify asset registry and save
|
|
465
|
+
FAssetRegistryModule::AssetCreated(LayerInfoObj);
|
|
466
|
+
Package->MarkPackageDirty();
|
|
467
|
+
UEditorAssetLibrary::SaveAsset(PackageFullPath, false);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Register the layer info with the landscape
|
|
471
|
+
int32 LayerIndex = LandscapeInfo->Layers.Num();
|
|
472
|
+
FLandscapeInfoLayerSettings NewLayerSettings(LayerInfoObj, TargetLandscape);
|
|
473
|
+
LandscapeInfo->Layers.Add(NewLayerSettings);
|
|
474
|
+
|
|
475
|
+
// Mark the landscape as dirty
|
|
476
|
+
TargetLandscape->MarkPackageDirty();
|
|
477
|
+
|
|
478
|
+
auto Result = MCPSuccess();
|
|
479
|
+
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
480
|
+
Result->SetStringField(TEXT("path"), LayerInfoObj->GetPathName());
|
|
481
|
+
Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
482
|
+
Result->SetNumberField(TEXT("layerIndex"), LayerIndex);
|
|
483
|
+
|
|
484
|
+
return MCPResult(Result);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ─── #150 get_landscape_material_usage_summary ──────────────────────
|
|
488
|
+
// Compact per-proxy dump: class, label, material paths, grass / Nanite /
|
|
489
|
+
// landscape component counts. Avoids the big "get all components" blob
|
|
490
|
+
// get_actor_details produces when you only need materials + counts.
|
|
491
|
+
// #303: spawn an ALandscape with a default flat heightmap at mid-elevation
|
|
492
|
+
// (uint16 32768 = no offset). Section/quad defaults match the Editor's
|
|
493
|
+
// Landscape Mode "create new" form: 63 quads/subsection, 2 subsections/component
|
|
494
|
+
// = 127 quads/component. ComponentCount X/Y default to 8x8 producing a
|
|
495
|
+
// 1016x1016 quad landscape (~1 km at default actor scale 100,100,100).
|
|
496
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::CreateLandscape(const TSharedPtr<FJsonObject>& Params)
|
|
497
|
+
{
|
|
498
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
499
|
+
|
|
500
|
+
const int32 SubsectionSizeQuads = OptionalInt(Params, TEXT("subsectionSizeQuads"), 63);
|
|
501
|
+
const int32 NumSubsections = OptionalInt(Params, TEXT("numSubsections"), 2);
|
|
502
|
+
const int32 ComponentCountX = OptionalInt(Params, TEXT("componentCountX"), 8);
|
|
503
|
+
const int32 ComponentCountY = OptionalInt(Params, TEXT("componentCountY"), 8);
|
|
504
|
+
|
|
505
|
+
// Bounds checks: SubsectionSizeQuads must be one of the engine's supported
|
|
506
|
+
// values (7, 15, 31, 63, 127, 255), NumSubsections is 1 or 2, and the
|
|
507
|
+
// component grid has to be at least 1x1.
|
|
508
|
+
auto IsPowOf2Minus1 = [](int32 v) {
|
|
509
|
+
const int32 p = v + 1;
|
|
510
|
+
return v >= 7 && v <= 255 && (p & (p - 1)) == 0;
|
|
511
|
+
};
|
|
512
|
+
if (!IsPowOf2Minus1(SubsectionSizeQuads))
|
|
513
|
+
{
|
|
514
|
+
return MCPError(FString::Printf(
|
|
515
|
+
TEXT("subsectionSizeQuads must be one of 7, 15, 31, 63, 127, 255 (got %d)"),
|
|
516
|
+
SubsectionSizeQuads));
|
|
517
|
+
}
|
|
518
|
+
if (NumSubsections != 1 && NumSubsections != 2)
|
|
519
|
+
{
|
|
520
|
+
return MCPError(FString::Printf(TEXT("numSubsections must be 1 or 2 (got %d)"), NumSubsections));
|
|
521
|
+
}
|
|
522
|
+
if (ComponentCountX < 1 || ComponentCountY < 1)
|
|
523
|
+
{
|
|
524
|
+
return MCPError(TEXT("componentCountX and componentCountY must be >= 1"));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const int32 ComponentSizeQuads = SubsectionSizeQuads * NumSubsections;
|
|
528
|
+
const int32 SizeX = (ComponentCountX * ComponentSizeQuads) + 1;
|
|
529
|
+
const int32 SizeY = (ComponentCountY * ComponentSizeQuads) + 1;
|
|
530
|
+
|
|
531
|
+
const int32 HeightOffset = OptionalInt(Params, TEXT("heightOffset"), 32768);
|
|
532
|
+
if (HeightOffset < 0 || HeightOffset > 65535)
|
|
533
|
+
{
|
|
534
|
+
return MCPError(TEXT("heightOffset must be in [0, 65535] (uint16 elevation)"));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const FVector Location = OptionalVec3(Params, TEXT("location"));
|
|
538
|
+
const FVector Scale = OptionalVec3(Params, TEXT("scale"), FVector(100.0, 100.0, 100.0));
|
|
539
|
+
|
|
540
|
+
const FString Label = OptionalString(Params, TEXT("label"));
|
|
541
|
+
|
|
542
|
+
// Idempotency by label.
|
|
543
|
+
if (auto Existing = MCPCheckActorLabelExists(World, Label, TEXT("skip"), TEXT("Landscape")))
|
|
544
|
+
{
|
|
545
|
+
return Existing;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
FActorSpawnParameters SpawnParams;
|
|
549
|
+
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
550
|
+
ALandscape* Landscape = World->SpawnActor<ALandscape>(Location, FRotator::ZeroRotator, SpawnParams);
|
|
551
|
+
if (!Landscape)
|
|
552
|
+
{
|
|
553
|
+
return MCPError(TEXT("Failed to spawn ALandscape actor"));
|
|
554
|
+
}
|
|
555
|
+
Landscape->SetActorScale3D(Scale);
|
|
556
|
+
|
|
557
|
+
// Allocate a flat heightmap. Layer 0 (FGuid()) is the only edit layer for a
|
|
558
|
+
// non-edit-layer landscape, which is what gets created by the Editor's
|
|
559
|
+
// "create new landscape" defaults.
|
|
560
|
+
TArray<uint16> HeightData;
|
|
561
|
+
HeightData.SetNumUninitialized(SizeX * SizeY);
|
|
562
|
+
for (int32 i = 0; i < HeightData.Num(); ++i)
|
|
563
|
+
{
|
|
564
|
+
HeightData[i] = static_cast<uint16>(HeightOffset);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
TMap<FGuid, TArray<uint16>> ImportHeightData;
|
|
568
|
+
ImportHeightData.Add(FGuid(), MoveTemp(HeightData));
|
|
569
|
+
|
|
570
|
+
TMap<FGuid, TArray<FLandscapeImportLayerInfo>> ImportLayerInfo;
|
|
571
|
+
ImportLayerInfo.Add(FGuid(), TArray<FLandscapeImportLayerInfo>());
|
|
572
|
+
|
|
573
|
+
TArray<FLandscapeLayer> EmptyLayers;
|
|
574
|
+
Landscape->Import(
|
|
575
|
+
FGuid::NewGuid(),
|
|
576
|
+
0, 0,
|
|
577
|
+
SizeX - 1, SizeY - 1,
|
|
578
|
+
NumSubsections,
|
|
579
|
+
SubsectionSizeQuads,
|
|
580
|
+
ImportHeightData,
|
|
581
|
+
nullptr,
|
|
582
|
+
ImportLayerInfo,
|
|
583
|
+
ELandscapeImportAlphamapType::Additive,
|
|
584
|
+
#if UE_MCP_HAS_5_5_API
|
|
585
|
+
MakeArrayView(EmptyLayers)
|
|
586
|
+
#else
|
|
587
|
+
// 5.4: last arg is const TArray<FLandscapeLayer>* (TArrayView signature added in 5.5).
|
|
588
|
+
&EmptyLayers
|
|
589
|
+
#endif
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
if (!Label.IsEmpty())
|
|
593
|
+
{
|
|
594
|
+
Landscape->SetActorLabel(Label);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Register so subsequent get_landscape_info / sample_landscape calls find it.
|
|
598
|
+
if (ULandscapeInfo* LI = Landscape->GetLandscapeInfo())
|
|
599
|
+
{
|
|
600
|
+
LI->FixupProxiesTransform();
|
|
601
|
+
LI->RecreateCollisionComponents();
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
auto Result = MCPSuccess();
|
|
605
|
+
MCPSetCreated(Result);
|
|
606
|
+
Result->SetStringField(TEXT("actorLabel"), Landscape->GetActorLabel());
|
|
607
|
+
Result->SetStringField(TEXT("actorPath"), Landscape->GetPathName());
|
|
608
|
+
Result->SetNumberField(TEXT("componentCountX"), ComponentCountX);
|
|
609
|
+
Result->SetNumberField(TEXT("componentCountY"), ComponentCountY);
|
|
610
|
+
Result->SetNumberField(TEXT("componentSizeQuads"), ComponentSizeQuads);
|
|
611
|
+
Result->SetNumberField(TEXT("subsectionSizeQuads"), SubsectionSizeQuads);
|
|
612
|
+
Result->SetNumberField(TEXT("numSubsections"), NumSubsections);
|
|
613
|
+
Result->SetNumberField(TEXT("sizeX"), SizeX);
|
|
614
|
+
Result->SetNumberField(TEXT("sizeY"), SizeY);
|
|
615
|
+
|
|
616
|
+
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
617
|
+
Payload->SetStringField(TEXT("actorLabel"), Landscape->GetActorLabel());
|
|
618
|
+
MCPSetRollback(Result, TEXT("delete_actor"), Payload);
|
|
619
|
+
|
|
620
|
+
return MCPResult(Result);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// #251: standalone LayerInfo asset creation. Unlike add_landscape_layer_info
|
|
624
|
+
// (which requires a landscape in the world to register the layer against),
|
|
625
|
+
// this creates the ULandscapeLayerInfoObject asset in the content browser
|
|
626
|
+
// so paint workflows can pre-author layer assets before the landscape exists.
|
|
627
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::CreateLandscapeLayerInfo(const TSharedPtr<FJsonObject>& Params)
|
|
628
|
+
{
|
|
629
|
+
FString LayerName;
|
|
630
|
+
if (auto Err = RequireString(Params, TEXT("layerName"), LayerName)) return Err;
|
|
631
|
+
|
|
632
|
+
const FString Name = OptionalString(Params, TEXT("name"), FString::Printf(TEXT("LI_%s"), *LayerName));
|
|
633
|
+
const FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Landscape/LayerInfos"));
|
|
634
|
+
const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("skip"));
|
|
635
|
+
|
|
636
|
+
TSharedPtr<FJsonValue> Existing = MCPCheckAssetExists(PackagePath, Name, OnConflict, TEXT("LandscapeLayerInfoObject"));
|
|
637
|
+
if (Existing.IsValid()) return Existing;
|
|
638
|
+
|
|
639
|
+
const FString PackageFullPath = PackagePath / Name;
|
|
640
|
+
UPackage* Package = CreatePackage(*PackageFullPath);
|
|
641
|
+
if (!Package)
|
|
642
|
+
{
|
|
643
|
+
return MCPError(FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
ULandscapeLayerInfoObject* LayerInfo = NewObject<ULandscapeLayerInfoObject>(
|
|
647
|
+
Package, *Name, RF_Public | RF_Standalone);
|
|
648
|
+
if (!LayerInfo)
|
|
649
|
+
{
|
|
650
|
+
return MCPError(TEXT("Failed to create LandscapeLayerInfoObject"));
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
654
|
+
LayerInfo->LayerName = FName(*LayerName);
|
|
655
|
+
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
656
|
+
|
|
657
|
+
// physMaterial was documented here and never applied - the caller had to
|
|
658
|
+
// discover for themselves that it needed a second call. PhysicsCore is not
|
|
659
|
+
// a hard dependency of this module, so the class is reached by path and the
|
|
660
|
+
// property set through reflection rather than a link-time include.
|
|
661
|
+
const FString PhysMaterialPath = OptionalString(Params, TEXT("physMaterial"));
|
|
662
|
+
if (!PhysMaterialPath.IsEmpty())
|
|
663
|
+
{
|
|
664
|
+
UObject* PhysMat = LoadAssetByPath<UObject>(PhysMaterialPath);
|
|
665
|
+
if (!PhysMat)
|
|
666
|
+
{
|
|
667
|
+
// Both failure paths here run after the package and object exist, so
|
|
668
|
+
// bail out without leaving a half-made asset in memory.
|
|
669
|
+
LayerInfo->MarkAsGarbage();
|
|
670
|
+
return MCPError(FString::Printf(TEXT("physMaterial not found: %s"), *PhysMaterialPath));
|
|
671
|
+
}
|
|
672
|
+
FObjectProperty* Prop = CastField<FObjectProperty>(
|
|
673
|
+
ULandscapeLayerInfoObject::StaticClass()->FindPropertyByName(TEXT("PhysMaterial")));
|
|
674
|
+
if (!Prop || !Prop->PropertyClass || !PhysMat->IsA(Prop->PropertyClass))
|
|
675
|
+
{
|
|
676
|
+
LayerInfo->MarkAsGarbage();
|
|
677
|
+
return MCPError(FString::Printf(
|
|
678
|
+
TEXT("'%s' is a %s, not a PhysicalMaterial."),
|
|
679
|
+
*PhysMaterialPath, *PhysMat->GetClass()->GetName()));
|
|
680
|
+
}
|
|
681
|
+
Prop->SetObjectPropertyValue_InContainer(LayerInfo, PhysMat);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
double Hardness = 0.0;
|
|
685
|
+
if (Params->TryGetNumberField(TEXT("hardness"), Hardness))
|
|
686
|
+
{
|
|
687
|
+
// Hardness is becoming private; the setter also handles Modify() and the
|
|
688
|
+
// property-change notification the direct write skipped.
|
|
689
|
+
LayerInfo->SetHardness(static_cast<float>(Hardness), /*bInModify=*/true, EPropertyChangeType::ValueSet);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
FAssetRegistryModule::AssetCreated(LayerInfo);
|
|
693
|
+
Package->MarkPackageDirty();
|
|
694
|
+
SaveAssetPackage(LayerInfo);
|
|
695
|
+
|
|
696
|
+
auto Result = MCPSuccess();
|
|
697
|
+
MCPSetCreated(Result);
|
|
698
|
+
Result->SetStringField(TEXT("path"), LayerInfo->GetPathName());
|
|
699
|
+
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
700
|
+
Result->SetStringField(TEXT("packagePath"), PackagePath);
|
|
701
|
+
if (!PhysMaterialPath.IsEmpty()) Result->SetStringField(TEXT("physMaterial"), PhysMaterialPath);
|
|
702
|
+
MCPSetDeleteAssetRollback(Result, LayerInfo->GetPathName());
|
|
703
|
+
|
|
704
|
+
return MCPResult(Result);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::GetMaterialUsageSummary(const TSharedPtr<FJsonObject>& Params)
|
|
708
|
+
{
|
|
709
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
710
|
+
|
|
711
|
+
TArray<TSharedPtr<FJsonValue>> ProxyArray;
|
|
712
|
+
TSet<FString> UniqueMaterials;
|
|
713
|
+
int32 TotalComponents = 0, TotalGrass = 0, TotalNanite = 0;
|
|
714
|
+
|
|
715
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
716
|
+
{
|
|
717
|
+
ALandscapeProxy* Proxy = *It;
|
|
718
|
+
if (!Proxy) continue;
|
|
719
|
+
|
|
720
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
721
|
+
Obj->SetStringField(TEXT("label"), Proxy->GetActorLabel());
|
|
722
|
+
Obj->SetStringField(TEXT("name"), Proxy->GetName());
|
|
723
|
+
Obj->SetStringField(TEXT("class"), Proxy->GetClass()->GetName());
|
|
724
|
+
Obj->SetStringField(TEXT("path"), Proxy->GetPathName());
|
|
725
|
+
|
|
726
|
+
if (UMaterialInterface* Mat = Proxy->LandscapeMaterial)
|
|
727
|
+
{
|
|
728
|
+
Obj->SetStringField(TEXT("landscapeMaterial"), Mat->GetPathName());
|
|
729
|
+
UniqueMaterials.Add(Mat->GetPathName());
|
|
730
|
+
}
|
|
731
|
+
if (UMaterialInterface* HoleMat = Proxy->LandscapeHoleMaterial)
|
|
732
|
+
{
|
|
733
|
+
Obj->SetStringField(TEXT("landscapeHoleMaterial"), HoleMat->GetPathName());
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Histogram components by class (grass / Nanite / regular landscape comps)
|
|
737
|
+
int32 LandscapeComps = 0, GrassComps = 0, NaniteComps = 0;
|
|
738
|
+
TArray<UActorComponent*> Comps;
|
|
739
|
+
Proxy->GetComponents(Comps);
|
|
740
|
+
for (UActorComponent* C : Comps)
|
|
741
|
+
{
|
|
742
|
+
if (!C) continue;
|
|
743
|
+
const FString CName = C->GetClass()->GetName();
|
|
744
|
+
if (CName == TEXT("LandscapeComponent")) LandscapeComps++;
|
|
745
|
+
else if (CName == TEXT("GrassInstancedStaticMeshComponent")) GrassComps++;
|
|
746
|
+
else if (CName == TEXT("LandscapeNaniteComponent")) NaniteComps++;
|
|
747
|
+
}
|
|
748
|
+
Obj->SetNumberField(TEXT("landscapeComponentCount"), LandscapeComps);
|
|
749
|
+
Obj->SetNumberField(TEXT("grassComponentCount"), GrassComps);
|
|
750
|
+
Obj->SetNumberField(TEXT("naniteComponentCount"), NaniteComps);
|
|
751
|
+
TotalComponents += LandscapeComps;
|
|
752
|
+
TotalGrass += GrassComps;
|
|
753
|
+
TotalNanite += NaniteComps;
|
|
754
|
+
|
|
755
|
+
const FVector Loc = Proxy->GetActorLocation();
|
|
756
|
+
const FVector Scale = Proxy->GetActorScale3D();
|
|
757
|
+
TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
|
|
758
|
+
LocObj->SetNumberField(TEXT("x"), Loc.X);
|
|
759
|
+
LocObj->SetNumberField(TEXT("y"), Loc.Y);
|
|
760
|
+
LocObj->SetNumberField(TEXT("z"), Loc.Z);
|
|
761
|
+
Obj->SetObjectField(TEXT("location"), LocObj);
|
|
762
|
+
TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
|
|
763
|
+
ScaleObj->SetNumberField(TEXT("x"), Scale.X);
|
|
764
|
+
ScaleObj->SetNumberField(TEXT("y"), Scale.Y);
|
|
765
|
+
ScaleObj->SetNumberField(TEXT("z"), Scale.Z);
|
|
766
|
+
Obj->SetObjectField(TEXT("scale"), ScaleObj);
|
|
767
|
+
|
|
768
|
+
ProxyArray.Add(MakeShared<FJsonValueObject>(Obj));
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
TArray<TSharedPtr<FJsonValue>> UniqueMatsArr;
|
|
772
|
+
for (const FString& M : UniqueMaterials) UniqueMatsArr.Add(MakeShared<FJsonValueString>(M));
|
|
773
|
+
|
|
774
|
+
auto Result = MCPSuccess();
|
|
775
|
+
Result->SetArrayField(TEXT("proxies"), ProxyArray);
|
|
776
|
+
Result->SetNumberField(TEXT("proxyCount"), ProxyArray.Num());
|
|
777
|
+
Result->SetArrayField(TEXT("uniqueLandscapeMaterials"), UniqueMatsArr);
|
|
778
|
+
Result->SetNumberField(TEXT("totalLandscapeComponents"), TotalComponents);
|
|
779
|
+
Result->SetNumberField(TEXT("totalGrassComponents"), TotalGrass);
|
|
780
|
+
Result->SetNumberField(TEXT("totalNaniteComponents"), TotalNanite);
|
|
781
|
+
return MCPResult(Result);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// #733: enumerate LandscapeStreamingProxy actors currently loaded in the world,
|
|
785
|
+
// with per-proxy world bounds and the parent Landscape count. On a World
|
|
786
|
+
// Partition map, an unloaded proxy silently reads layer weights as 0, so a
|
|
787
|
+
// measurement is only trustworthy once the covering proxy is confirmed loaded.
|
|
788
|
+
// Unloaded proxies are not spawned as actors, so the actor iterator only yields
|
|
789
|
+
// loaded ones - hence loaded is always true for enumerated entries; callers use
|
|
790
|
+
// the count + bounds to reason about coverage.
|
|
791
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeProxies(const TSharedPtr<FJsonObject>& Params)
|
|
792
|
+
{
|
|
793
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
794
|
+
|
|
795
|
+
int32 ParentLandscapes = 0;
|
|
796
|
+
TArray<TSharedPtr<FJsonValue>> Proxies;
|
|
797
|
+
for (TActorIterator<AActor> It(World); It; ++It)
|
|
798
|
+
{
|
|
799
|
+
AActor* Actor = *It;
|
|
800
|
+
if (!Actor) continue;
|
|
801
|
+
if (Actor->IsA<ALandscape>())
|
|
802
|
+
{
|
|
803
|
+
ParentLandscapes++;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
ALandscapeStreamingProxy* Proxy = Cast<ALandscapeStreamingProxy>(Actor);
|
|
807
|
+
if (!Proxy) continue;
|
|
808
|
+
|
|
809
|
+
FVector Origin, Extent;
|
|
810
|
+
Proxy->GetActorBounds(false, Origin, Extent);
|
|
811
|
+
|
|
812
|
+
TSharedPtr<FJsonObject> ProxyObj = MakeShared<FJsonObject>();
|
|
813
|
+
ProxyObj->SetStringField(TEXT("label"), Proxy->GetActorLabel());
|
|
814
|
+
ProxyObj->SetBoolField(TEXT("loaded"), true);
|
|
815
|
+
|
|
816
|
+
TSharedPtr<FJsonObject> Bounds = MakeShared<FJsonObject>();
|
|
817
|
+
TSharedPtr<FJsonObject> OriginObj = MakeShared<FJsonObject>();
|
|
818
|
+
OriginObj->SetNumberField(TEXT("x"), Origin.X);
|
|
819
|
+
OriginObj->SetNumberField(TEXT("y"), Origin.Y);
|
|
820
|
+
OriginObj->SetNumberField(TEXT("z"), Origin.Z);
|
|
821
|
+
TSharedPtr<FJsonObject> ExtentObj = MakeShared<FJsonObject>();
|
|
822
|
+
ExtentObj->SetNumberField(TEXT("x"), Extent.X);
|
|
823
|
+
ExtentObj->SetNumberField(TEXT("y"), Extent.Y);
|
|
824
|
+
ExtentObj->SetNumberField(TEXT("z"), Extent.Z);
|
|
825
|
+
Bounds->SetObjectField(TEXT("origin"), OriginObj);
|
|
826
|
+
Bounds->SetObjectField(TEXT("extent"), ExtentObj);
|
|
827
|
+
ProxyObj->SetObjectField(TEXT("worldBounds"), Bounds);
|
|
828
|
+
|
|
829
|
+
Proxies.Add(MakeShared<FJsonValueObject>(ProxyObj));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
auto Result = MCPSuccess();
|
|
833
|
+
Result->SetNumberField(TEXT("loadedProxies"), Proxies.Num());
|
|
834
|
+
Result->SetNumberField(TEXT("parentLandscapes"), ParentLandscapes);
|
|
835
|
+
Result->SetArrayField(TEXT("proxies"), Proxies);
|
|
836
|
+
Result->SetStringField(TEXT("note"), TEXT("World Partition unloaded proxies are not spawned as actors, so only loaded proxies are listed."));
|
|
837
|
+
return MCPResult(Result);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// #733: resolve which loaded LandscapeStreamingProxy's world bounds contain a
|
|
841
|
+
// world X/Y. Returns the covering proxy (loaded:true) or loaded:false when no
|
|
842
|
+
// loaded proxy covers the position - which usually means the covering proxy is
|
|
843
|
+
// streamed out, making any 0-weight readback there ambiguous rather than real.
|
|
844
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::FindLandscapeProxyAt(const TSharedPtr<FJsonObject>& Params)
|
|
845
|
+
{
|
|
846
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
847
|
+
|
|
848
|
+
if (!Params->HasField(TEXT("worldX")) || !Params->HasField(TEXT("worldY")))
|
|
849
|
+
{
|
|
850
|
+
return MCPError(TEXT("Missing 'worldX'/'worldY' world position"));
|
|
851
|
+
}
|
|
852
|
+
const double TargetX = OptionalNumber(Params, TEXT("worldX"), 0.0);
|
|
853
|
+
const double TargetY = OptionalNumber(Params, TEXT("worldY"), 0.0);
|
|
854
|
+
|
|
855
|
+
for (TActorIterator<AActor> It(World); It; ++It)
|
|
856
|
+
{
|
|
857
|
+
ALandscapeStreamingProxy* Proxy = Cast<ALandscapeStreamingProxy>(*It);
|
|
858
|
+
if (!Proxy) continue;
|
|
859
|
+
|
|
860
|
+
FVector Origin, Extent;
|
|
861
|
+
Proxy->GetActorBounds(false, Origin, Extent);
|
|
862
|
+
if (TargetX >= Origin.X - Extent.X && TargetX <= Origin.X + Extent.X &&
|
|
863
|
+
TargetY >= Origin.Y - Extent.Y && TargetY <= Origin.Y + Extent.Y)
|
|
864
|
+
{
|
|
865
|
+
auto Result = MCPSuccess();
|
|
866
|
+
Result->SetBoolField(TEXT("found"), true);
|
|
867
|
+
Result->SetBoolField(TEXT("loaded"), true);
|
|
868
|
+
Result->SetStringField(TEXT("label"), Proxy->GetActorLabel());
|
|
869
|
+
return MCPResult(Result);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
auto Result = MCPSuccess();
|
|
874
|
+
Result->SetBoolField(TEXT("found"), false);
|
|
875
|
+
Result->SetBoolField(TEXT("loaded"), false);
|
|
876
|
+
Result->SetStringField(TEXT("note"), TEXT("No loaded proxy covers this position; the covering proxy is likely streamed out, so weight/height readbacks here are ambiguous."));
|
|
877
|
+
return MCPResult(Result);
|
|
878
|
+
}
|