flockbay 0.10.15
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/README.md +56 -0
- package/bin/flockbay-mcp.mjs +56 -0
- package/bin/flockbay.mjs +78 -0
- package/dist/codex/flockbayMcpStdioBridge.cjs +383 -0
- package/dist/codex/flockbayMcpStdioBridge.d.cts +2 -0
- package/dist/codex/flockbayMcpStdioBridge.d.mts +2 -0
- package/dist/codex/flockbayMcpStdioBridge.mjs +381 -0
- package/dist/flockbayScreenshotGate-DJX3Is5d.mjs +136 -0
- package/dist/flockbayScreenshotGate-DkxU24cR.cjs +138 -0
- package/dist/index--o4BPz5o.cjs +10311 -0
- package/dist/index-CUp3juDS.mjs +10268 -0
- package/dist/index.cjs +43 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +40 -0
- package/dist/lib.cjs +33 -0
- package/dist/lib.d.cts +957 -0
- package/dist/lib.d.mts +957 -0
- package/dist/lib.mjs +23 -0
- package/dist/runCodex-D3eT-TvB.cjs +3449 -0
- package/dist/runCodex-o6PCbHQ7.mjs +3446 -0
- package/dist/runGemini-Bt0oEj_g.mjs +3183 -0
- package/dist/runGemini-CBxZp6I7.cjs +3185 -0
- package/dist/types-C-jnUdn_.cjs +4498 -0
- package/dist/types-DGd6ea2Z.mjs +4450 -0
- package/kits/kit.open_world/kit.json +59 -0
- package/package.json +130 -0
- package/scripts/claude_local_launcher.cjs +73 -0
- package/scripts/claude_remote_launcher.cjs +16 -0
- package/scripts/claude_version_utils.cjs +391 -0
- package/scripts/ripgrep_launcher.cjs +33 -0
- package/scripts/session_hook_forwarder.cjs +49 -0
- package/scripts/test-codex-abort-history.mjs +77 -0
- package/scripts/unpack-tools.cjs +222 -0
- package/tools/licenses/difftastic-LICENSE +21 -0
- package/tools/licenses/ripgrep-LICENSE +3 -0
- package/tools/unreal-mcp/UPSTREAM_VERSION.md +8 -0
- package/tools/unreal-mcp/upstream/Docs/README.md +8 -0
- package/tools/unreal-mcp/upstream/Docs/Tools/README.md +7 -0
- package/tools/unreal-mcp/upstream/Docs/Tools/actor_tools.md +184 -0
- package/tools/unreal-mcp/upstream/Docs/Tools/blueprint_tools.md +268 -0
- package/tools/unreal-mcp/upstream/Docs/Tools/editor_tools.md +104 -0
- package/tools/unreal-mcp/upstream/Docs/Tools/node_tools.md +274 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Config/FilterPlugin.ini +8 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp +1160 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp +924 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp +709 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +896 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPProjectCommands.cpp +72 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPUMGCommands.cpp +544 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp +321 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp +419 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPModule.cpp +21 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h +34 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h +27 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommonUtils.h +59 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h +40 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPProjectCommands.h +20 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPUMGCommands.h +82 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/MCPServerRunnable.h +34 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPBridge.h +64 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPModule.h +22 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs +78 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/UnrealMCP.uplugin +36 -0
- package/tools/unreal-mcp/upstream/Python/README.md +40 -0
- package/tools/unreal-mcp/upstream/Python/pyproject.toml +22 -0
- package/tools/unreal-mcp/upstream/Python/scripts/actors/test_cube.py +203 -0
- package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_blueprints_with_different_components.py +497 -0
- package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_cube_blueprint.py +194 -0
- package/tools/unreal-mcp/upstream/Python/scripts/node/test_component_reference.py +267 -0
- package/tools/unreal-mcp/upstream/Python/scripts/node/test_create_bird_blueprint_with_input_and_camera.py +618 -0
- package/tools/unreal-mcp/upstream/Python/scripts/node/test_input_mapping.py +366 -0
- package/tools/unreal-mcp/upstream/Python/scripts/node/test_physics_variables.py +390 -0
- package/tools/unreal-mcp/upstream/Python/tools/blueprint_tools.py +420 -0
- package/tools/unreal-mcp/upstream/Python/tools/editor_tools.py +369 -0
- package/tools/unreal-mcp/upstream/Python/tools/node_tools.py +430 -0
- package/tools/unreal-mcp/upstream/Python/tools/project_tools.py +64 -0
- package/tools/unreal-mcp/upstream/Python/tools/umg_tools.py +333 -0
- package/tools/unreal-mcp/upstream/Python/unreal_mcp_server.py +398 -0
- package/tools/unreal-mcp/upstream/Python/uv.lock +521 -0
|
@@ -0,0 +1,896 @@
|
|
|
1
|
+
#include "Commands/UnrealMCPEditorCommands.h"
|
|
2
|
+
#include "Commands/UnrealMCPCommonUtils.h"
|
|
3
|
+
#include "Editor.h"
|
|
4
|
+
#include "EditorViewportClient.h"
|
|
5
|
+
#include "LevelEditorViewport.h"
|
|
6
|
+
#include "IAssetViewport.h"
|
|
7
|
+
#include "ImageUtils.h"
|
|
8
|
+
#include "HighResScreenshot.h"
|
|
9
|
+
#include "Engine/GameViewportClient.h"
|
|
10
|
+
#include "Misc/FileHelper.h"
|
|
11
|
+
#include "Misc/Paths.h"
|
|
12
|
+
#include "HAL/FileManager.h"
|
|
13
|
+
#include "GameFramework/Actor.h"
|
|
14
|
+
#include "Engine/Selection.h"
|
|
15
|
+
#include "Kismet/GameplayStatics.h"
|
|
16
|
+
#include "Engine/StaticMeshActor.h"
|
|
17
|
+
#include "Engine/StaticMesh.h"
|
|
18
|
+
#include "Engine/DirectionalLight.h"
|
|
19
|
+
#include "Engine/PointLight.h"
|
|
20
|
+
#include "Engine/SpotLight.h"
|
|
21
|
+
#include "Camera/CameraActor.h"
|
|
22
|
+
#include "Components/StaticMeshComponent.h"
|
|
23
|
+
#include "EditorSubsystem.h"
|
|
24
|
+
#include "Subsystems/EditorActorSubsystem.h"
|
|
25
|
+
#include "Engine/Blueprint.h"
|
|
26
|
+
#include "Engine/BlueprintGeneratedClass.h"
|
|
27
|
+
#include "UnrealEdGlobals.h"
|
|
28
|
+
#include "Editor/UnrealEdEngine.h"
|
|
29
|
+
#include "UObject/UnrealType.h"
|
|
30
|
+
#include "Modules/ModuleManager.h"
|
|
31
|
+
#include "LevelEditor.h"
|
|
32
|
+
#include "PlayInEditorDataTypes.h"
|
|
33
|
+
#include "Settings/LevelEditorPlaySettings.h"
|
|
34
|
+
#include "Framework/Application/SlateApplication.h"
|
|
35
|
+
|
|
36
|
+
FUnrealMCPEditorCommands::FUnrealMCPEditorCommands()
|
|
37
|
+
{
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
|
|
41
|
+
{
|
|
42
|
+
// Actor manipulation commands
|
|
43
|
+
if (CommandType == TEXT("get_actors_in_level"))
|
|
44
|
+
{
|
|
45
|
+
return HandleGetActorsInLevel(Params);
|
|
46
|
+
}
|
|
47
|
+
else if (CommandType == TEXT("find_actors_by_name"))
|
|
48
|
+
{
|
|
49
|
+
return HandleFindActorsByName(Params);
|
|
50
|
+
}
|
|
51
|
+
else if (CommandType == TEXT("spawn_actor") || CommandType == TEXT("create_actor"))
|
|
52
|
+
{
|
|
53
|
+
if (CommandType == TEXT("create_actor"))
|
|
54
|
+
{
|
|
55
|
+
UE_LOG(LogTemp, Warning, TEXT("'create_actor' command is deprecated and will be removed in a future version. Please use 'spawn_actor' instead."));
|
|
56
|
+
}
|
|
57
|
+
return HandleSpawnActor(Params);
|
|
58
|
+
}
|
|
59
|
+
else if (CommandType == TEXT("delete_actor"))
|
|
60
|
+
{
|
|
61
|
+
return HandleDeleteActor(Params);
|
|
62
|
+
}
|
|
63
|
+
else if (CommandType == TEXT("set_actor_transform"))
|
|
64
|
+
{
|
|
65
|
+
return HandleSetActorTransform(Params);
|
|
66
|
+
}
|
|
67
|
+
else if (CommandType == TEXT("get_actor_properties"))
|
|
68
|
+
{
|
|
69
|
+
return HandleGetActorProperties(Params);
|
|
70
|
+
}
|
|
71
|
+
else if (CommandType == TEXT("set_actor_property"))
|
|
72
|
+
{
|
|
73
|
+
return HandleSetActorProperty(Params);
|
|
74
|
+
}
|
|
75
|
+
// Blueprint actor spawning
|
|
76
|
+
else if (CommandType == TEXT("spawn_blueprint_actor"))
|
|
77
|
+
{
|
|
78
|
+
return HandleSpawnBlueprintActor(Params);
|
|
79
|
+
}
|
|
80
|
+
// Editor viewport commands
|
|
81
|
+
else if (CommandType == TEXT("focus_viewport"))
|
|
82
|
+
{
|
|
83
|
+
return HandleFocusViewport(Params);
|
|
84
|
+
}
|
|
85
|
+
else if (CommandType == TEXT("take_screenshot"))
|
|
86
|
+
{
|
|
87
|
+
return HandleTakeScreenshot(Params);
|
|
88
|
+
}
|
|
89
|
+
else if (CommandType == TEXT("get_play_in_editor_status"))
|
|
90
|
+
{
|
|
91
|
+
return HandleGetPlayInEditorStatus(Params);
|
|
92
|
+
}
|
|
93
|
+
else if (CommandType == TEXT("play_in_editor"))
|
|
94
|
+
{
|
|
95
|
+
return HandlePlayInEditor(Params);
|
|
96
|
+
}
|
|
97
|
+
else if (CommandType == TEXT("play_in_editor_windowed"))
|
|
98
|
+
{
|
|
99
|
+
return HandlePlayInEditorWindowed(Params);
|
|
100
|
+
}
|
|
101
|
+
else if (CommandType == TEXT("stop_play_in_editor"))
|
|
102
|
+
{
|
|
103
|
+
return HandleStopPlayInEditor(Params);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown editor command: %s"), *CommandType));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static TSharedPtr<FJsonObject> CreatePlayStatusResponse()
|
|
110
|
+
{
|
|
111
|
+
if (!GEditor)
|
|
112
|
+
{
|
|
113
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Editor is not available (GEditor is null)."));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
117
|
+
ResultObj->SetBoolField(TEXT("isPlaySessionInProgress"), GEditor->IsPlaySessionInProgress());
|
|
118
|
+
ResultObj->SetBoolField(TEXT("isPlaySessionRequestQueued"), GEditor->IsPlaySessionRequestQueued());
|
|
119
|
+
ResultObj->SetBoolField(TEXT("isPlayingSessionInEditor"), GEditor->IsPlayingSessionInEditor());
|
|
120
|
+
return ResultObj;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static bool IsSafeToModifyEditorWorld(FString& OutError)
|
|
124
|
+
{
|
|
125
|
+
if (!GEditor)
|
|
126
|
+
{
|
|
127
|
+
OutError = TEXT("Editor is not available (GEditor is null).");
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (GEditor->IsPlaySessionInProgress())
|
|
132
|
+
{
|
|
133
|
+
OutError = TEXT("Play-In-Editor is running or queued. Stop PIE before modifying the editor world (use stop_play_in_editor).");
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetPlayInEditorStatus(const TSharedPtr<FJsonObject>& Params)
|
|
141
|
+
{
|
|
142
|
+
return CreatePlayStatusResponse();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandlePlayInEditor(const TSharedPtr<FJsonObject>& Params)
|
|
146
|
+
{
|
|
147
|
+
if (!GEditor)
|
|
148
|
+
{
|
|
149
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Editor is not available (GEditor is null)."));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (GEditor->IsPlaySessionInProgress())
|
|
153
|
+
{
|
|
154
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Play session already running or queued."));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Use the active editor viewport, matching how the LevelEditor subsystem triggers PIE.
|
|
158
|
+
FLevelEditorModule* LevelEditorModule = FModuleManager::LoadModulePtr<FLevelEditorModule>(TEXT("LevelEditor"));
|
|
159
|
+
if (!LevelEditorModule)
|
|
160
|
+
{
|
|
161
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("LevelEditor module not available."));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
TSharedPtr<IAssetViewport> ActiveViewport = LevelEditorModule->GetFirstActiveViewport();
|
|
165
|
+
if (!ActiveViewport.IsValid())
|
|
166
|
+
{
|
|
167
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("No active level viewport. Click the viewport and retry."));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
FRequestPlaySessionParams SessionParams;
|
|
171
|
+
SessionParams.WorldType = EPlaySessionWorldType::PlayInEditor;
|
|
172
|
+
SessionParams.DestinationSlateViewport = ActiveViewport;
|
|
173
|
+
|
|
174
|
+
GEditor->RequestPlaySession(SessionParams);
|
|
175
|
+
const bool bQueued = GEditor->IsPlaySessionRequestQueued();
|
|
176
|
+
|
|
177
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
178
|
+
ResultObj->SetBoolField(TEXT("requested"), true);
|
|
179
|
+
ResultObj->SetBoolField(TEXT("queued"), bQueued);
|
|
180
|
+
ResultObj->SetBoolField(TEXT("started"), bQueued);
|
|
181
|
+
ResultObj->SetStringField(TEXT("mode"), TEXT("pie"));
|
|
182
|
+
return ResultObj;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandlePlayInEditorWindowed(const TSharedPtr<FJsonObject>& Params)
|
|
186
|
+
{
|
|
187
|
+
if (!GEditor)
|
|
188
|
+
{
|
|
189
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Editor is not available (GEditor is null)."));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (GEditor->IsPlaySessionInProgress())
|
|
193
|
+
{
|
|
194
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Play session already running or queued."));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Match the editor's built-in "New Editor Window (PIE)" behavior by setting the play mode type and
|
|
198
|
+
// not specifying a DestinationSlateViewport.
|
|
199
|
+
ULevelEditorPlaySettings* PlaySettings = GetMutableDefault<ULevelEditorPlaySettings>();
|
|
200
|
+
if (PlaySettings)
|
|
201
|
+
{
|
|
202
|
+
PlaySettings->LastExecutedPlayModeType = EPlayModeType::PlayMode_InEditorFloating;
|
|
203
|
+
|
|
204
|
+
if (FProperty* Prop = ULevelEditorPlaySettings::StaticClass()->FindPropertyByName(
|
|
205
|
+
GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, LastExecutedPlayModeType)))
|
|
206
|
+
{
|
|
207
|
+
FPropertyChangedEvent PropChangeEvent(Prop);
|
|
208
|
+
PlaySettings->PostEditChangeProperty(PropChangeEvent);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
PlaySettings->SaveConfig();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const bool bAtPlayerStart =
|
|
215
|
+
PlaySettings && PlaySettings->LastExecutedPlayModeLocation == EPlayModeLocations::PlayLocation_DefaultPlayerStart;
|
|
216
|
+
|
|
217
|
+
FRequestPlaySessionParams SessionParams;
|
|
218
|
+
SessionParams.WorldType = EPlaySessionWorldType::PlayInEditor;
|
|
219
|
+
|
|
220
|
+
// If the user is playing from current camera location, use the active viewport camera as the start transform.
|
|
221
|
+
if (!bAtPlayerStart)
|
|
222
|
+
{
|
|
223
|
+
FLevelEditorModule* LevelEditorModule = FModuleManager::LoadModulePtr<FLevelEditorModule>(TEXT("LevelEditor"));
|
|
224
|
+
if (LevelEditorModule)
|
|
225
|
+
{
|
|
226
|
+
TSharedPtr<IAssetViewport> ActiveViewport = LevelEditorModule->GetFirstActiveViewport();
|
|
227
|
+
if (ActiveViewport.IsValid() && FSlateApplication::IsInitialized() &&
|
|
228
|
+
FSlateApplication::Get().FindWidgetWindow(ActiveViewport->AsWidget()).IsValid())
|
|
229
|
+
{
|
|
230
|
+
SessionParams.StartLocation = ActiveViewport->GetAssetViewportClient().GetViewLocation();
|
|
231
|
+
SessionParams.StartRotation = ActiveViewport->GetAssetViewportClient().GetViewRotation();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
GEditor->RequestPlaySession(SessionParams);
|
|
237
|
+
const bool bQueued = GEditor->IsPlaySessionRequestQueued();
|
|
238
|
+
|
|
239
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
240
|
+
ResultObj->SetBoolField(TEXT("requested"), true);
|
|
241
|
+
ResultObj->SetBoolField(TEXT("queued"), bQueued);
|
|
242
|
+
ResultObj->SetBoolField(TEXT("started"), bQueued);
|
|
243
|
+
ResultObj->SetStringField(TEXT("mode"), TEXT("pie_new_window"));
|
|
244
|
+
return ResultObj;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleStopPlayInEditor(const TSharedPtr<FJsonObject>& Params)
|
|
248
|
+
{
|
|
249
|
+
if (!GEditor)
|
|
250
|
+
{
|
|
251
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Editor is not available (GEditor is null)."));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (GEditor->IsPlaySessionRequestQueued() && !GEditor->IsPlayingSessionInEditor())
|
|
255
|
+
{
|
|
256
|
+
GEditor->CancelRequestPlaySession();
|
|
257
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
258
|
+
ResultObj->SetBoolField(TEXT("stopped"), true);
|
|
259
|
+
ResultObj->SetBoolField(TEXT("canceled"), true);
|
|
260
|
+
return ResultObj;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!GEditor->IsPlayingSessionInEditor())
|
|
264
|
+
{
|
|
265
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("No play session is running."));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
GEditor->RequestEndPlayMap();
|
|
269
|
+
|
|
270
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
271
|
+
ResultObj->SetBoolField(TEXT("stopped"), true);
|
|
272
|
+
return ResultObj;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetActorsInLevel(const TSharedPtr<FJsonObject>& Params)
|
|
276
|
+
{
|
|
277
|
+
TArray<AActor*> AllActors;
|
|
278
|
+
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
|
279
|
+
|
|
280
|
+
TArray<TSharedPtr<FJsonValue>> ActorArray;
|
|
281
|
+
for (AActor* Actor : AllActors)
|
|
282
|
+
{
|
|
283
|
+
if (Actor)
|
|
284
|
+
{
|
|
285
|
+
ActorArray.Add(FUnrealMCPCommonUtils::ActorToJson(Actor));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
290
|
+
ResultObj->SetArrayField(TEXT("actors"), ActorArray);
|
|
291
|
+
|
|
292
|
+
return ResultObj;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleFindActorsByName(const TSharedPtr<FJsonObject>& Params)
|
|
296
|
+
{
|
|
297
|
+
FString Pattern;
|
|
298
|
+
if (!Params->TryGetStringField(TEXT("pattern"), Pattern))
|
|
299
|
+
{
|
|
300
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'pattern' parameter"));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
TArray<AActor*> AllActors;
|
|
304
|
+
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
|
305
|
+
|
|
306
|
+
TArray<TSharedPtr<FJsonValue>> MatchingActors;
|
|
307
|
+
for (AActor* Actor : AllActors)
|
|
308
|
+
{
|
|
309
|
+
if (Actor && Actor->GetName().Contains(Pattern))
|
|
310
|
+
{
|
|
311
|
+
MatchingActors.Add(FUnrealMCPCommonUtils::ActorToJson(Actor));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
316
|
+
ResultObj->SetArrayField(TEXT("actors"), MatchingActors);
|
|
317
|
+
|
|
318
|
+
return ResultObj;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSpawnActor(const TSharedPtr<FJsonObject>& Params)
|
|
322
|
+
{
|
|
323
|
+
{
|
|
324
|
+
FString Err;
|
|
325
|
+
if (!IsSafeToModifyEditorWorld(Err))
|
|
326
|
+
{
|
|
327
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Get required parameters
|
|
332
|
+
FString ActorType;
|
|
333
|
+
if (!Params->TryGetStringField(TEXT("type"), ActorType))
|
|
334
|
+
{
|
|
335
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'type' parameter"));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Get actor name (required parameter)
|
|
339
|
+
FString ActorName;
|
|
340
|
+
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
|
341
|
+
{
|
|
342
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Get optional transform parameters
|
|
346
|
+
FVector Location(0.0f, 0.0f, 0.0f);
|
|
347
|
+
FRotator Rotation(0.0f, 0.0f, 0.0f);
|
|
348
|
+
FVector Scale(1.0f, 1.0f, 1.0f);
|
|
349
|
+
|
|
350
|
+
if (Params->HasField(TEXT("location")))
|
|
351
|
+
{
|
|
352
|
+
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
|
|
353
|
+
}
|
|
354
|
+
if (Params->HasField(TEXT("rotation")))
|
|
355
|
+
{
|
|
356
|
+
Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"));
|
|
357
|
+
}
|
|
358
|
+
if (Params->HasField(TEXT("scale")))
|
|
359
|
+
{
|
|
360
|
+
Scale = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Create the actor based on type
|
|
364
|
+
AActor* NewActor = nullptr;
|
|
365
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
366
|
+
|
|
367
|
+
if (!World)
|
|
368
|
+
{
|
|
369
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get editor world"));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Check if an actor with this name already exists
|
|
373
|
+
TArray<AActor*> AllActors;
|
|
374
|
+
UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), AllActors);
|
|
375
|
+
for (AActor* Actor : AllActors)
|
|
376
|
+
{
|
|
377
|
+
if (Actor && Actor->GetName() == ActorName)
|
|
378
|
+
{
|
|
379
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor with name '%s' already exists"), *ActorName));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
FActorSpawnParameters SpawnParams;
|
|
384
|
+
SpawnParams.Name = *ActorName;
|
|
385
|
+
|
|
386
|
+
if (ActorType == TEXT("StaticMeshActor"))
|
|
387
|
+
{
|
|
388
|
+
NewActor = World->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), Location, Rotation, SpawnParams);
|
|
389
|
+
|
|
390
|
+
// Optional: assign a StaticMesh at spawn time (preferred, since nested component property setting is limited).
|
|
391
|
+
FString StaticMeshPath;
|
|
392
|
+
if (Params->TryGetStringField(TEXT("static_mesh_path"), StaticMeshPath) ||
|
|
393
|
+
Params->TryGetStringField(TEXT("static_mesh"), StaticMeshPath))
|
|
394
|
+
{
|
|
395
|
+
StaticMeshPath = StaticMeshPath.TrimStartAndEnd();
|
|
396
|
+
if (!StaticMeshPath.IsEmpty())
|
|
397
|
+
{
|
|
398
|
+
UStaticMesh* StaticMesh = LoadObject<UStaticMesh>(nullptr, *StaticMeshPath);
|
|
399
|
+
if (!StaticMesh && !StaticMeshPath.StartsWith(TEXT("StaticMesh'")))
|
|
400
|
+
{
|
|
401
|
+
StaticMesh = LoadObject<UStaticMesh>(nullptr, *FString::Printf(TEXT("StaticMesh'%s'"), *StaticMeshPath));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!StaticMesh)
|
|
405
|
+
{
|
|
406
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(
|
|
407
|
+
FString::Printf(TEXT("Static mesh not found: %s"), *StaticMeshPath));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
AStaticMeshActor* StaticMeshActor = Cast<AStaticMeshActor>(NewActor);
|
|
411
|
+
if (!StaticMeshActor || !StaticMeshActor->GetStaticMeshComponent())
|
|
412
|
+
{
|
|
413
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Spawned StaticMeshActor has no StaticMeshComponent."));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
StaticMeshActor->GetStaticMeshComponent()->SetStaticMesh(StaticMesh);
|
|
417
|
+
StaticMeshActor->GetStaticMeshComponent()->MarkRenderStateDirty();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else if (ActorType == TEXT("PointLight"))
|
|
422
|
+
{
|
|
423
|
+
NewActor = World->SpawnActor<APointLight>(APointLight::StaticClass(), Location, Rotation, SpawnParams);
|
|
424
|
+
}
|
|
425
|
+
else if (ActorType == TEXT("SpotLight"))
|
|
426
|
+
{
|
|
427
|
+
NewActor = World->SpawnActor<ASpotLight>(ASpotLight::StaticClass(), Location, Rotation, SpawnParams);
|
|
428
|
+
}
|
|
429
|
+
else if (ActorType == TEXT("DirectionalLight"))
|
|
430
|
+
{
|
|
431
|
+
NewActor = World->SpawnActor<ADirectionalLight>(ADirectionalLight::StaticClass(), Location, Rotation, SpawnParams);
|
|
432
|
+
}
|
|
433
|
+
else if (ActorType == TEXT("CameraActor"))
|
|
434
|
+
{
|
|
435
|
+
NewActor = World->SpawnActor<ACameraActor>(ACameraActor::StaticClass(), Location, Rotation, SpawnParams);
|
|
436
|
+
}
|
|
437
|
+
else
|
|
438
|
+
{
|
|
439
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown actor type: %s"), *ActorType));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (NewActor)
|
|
443
|
+
{
|
|
444
|
+
// Set scale (since SpawnActor only takes location and rotation)
|
|
445
|
+
FTransform Transform = NewActor->GetTransform();
|
|
446
|
+
Transform.SetScale3D(Scale);
|
|
447
|
+
NewActor->SetActorTransform(Transform);
|
|
448
|
+
|
|
449
|
+
// Return the created actor's details
|
|
450
|
+
return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create actor"));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleDeleteActor(const TSharedPtr<FJsonObject>& Params)
|
|
457
|
+
{
|
|
458
|
+
{
|
|
459
|
+
FString Err;
|
|
460
|
+
if (!IsSafeToModifyEditorWorld(Err))
|
|
461
|
+
{
|
|
462
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
FString ActorName;
|
|
467
|
+
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
|
468
|
+
{
|
|
469
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
TArray<AActor*> AllActors;
|
|
473
|
+
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
|
474
|
+
|
|
475
|
+
for (AActor* Actor : AllActors)
|
|
476
|
+
{
|
|
477
|
+
if (Actor && Actor->GetName() == ActorName)
|
|
478
|
+
{
|
|
479
|
+
// Store actor info before deletion for the response
|
|
480
|
+
TSharedPtr<FJsonObject> ActorInfo = FUnrealMCPCommonUtils::ActorToJsonObject(Actor);
|
|
481
|
+
|
|
482
|
+
// Delete the actor
|
|
483
|
+
Actor->Destroy();
|
|
484
|
+
|
|
485
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
486
|
+
ResultObj->SetObjectField(TEXT("deleted_actor"), ActorInfo);
|
|
487
|
+
return ResultObj;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSetActorTransform(const TSharedPtr<FJsonObject>& Params)
|
|
495
|
+
{
|
|
496
|
+
{
|
|
497
|
+
FString Err;
|
|
498
|
+
if (!IsSafeToModifyEditorWorld(Err))
|
|
499
|
+
{
|
|
500
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Get actor name
|
|
505
|
+
FString ActorName;
|
|
506
|
+
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
|
507
|
+
{
|
|
508
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Find the actor
|
|
512
|
+
AActor* TargetActor = nullptr;
|
|
513
|
+
TArray<AActor*> AllActors;
|
|
514
|
+
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
|
515
|
+
|
|
516
|
+
for (AActor* Actor : AllActors)
|
|
517
|
+
{
|
|
518
|
+
if (Actor && Actor->GetName() == ActorName)
|
|
519
|
+
{
|
|
520
|
+
TargetActor = Actor;
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (!TargetActor)
|
|
526
|
+
{
|
|
527
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Get transform parameters
|
|
531
|
+
FTransform NewTransform = TargetActor->GetTransform();
|
|
532
|
+
|
|
533
|
+
if (Params->HasField(TEXT("location")))
|
|
534
|
+
{
|
|
535
|
+
NewTransform.SetLocation(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location")));
|
|
536
|
+
}
|
|
537
|
+
if (Params->HasField(TEXT("rotation")))
|
|
538
|
+
{
|
|
539
|
+
NewTransform.SetRotation(FQuat(FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"))));
|
|
540
|
+
}
|
|
541
|
+
if (Params->HasField(TEXT("scale")))
|
|
542
|
+
{
|
|
543
|
+
NewTransform.SetScale3D(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale")));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Set the new transform
|
|
547
|
+
TargetActor->SetActorTransform(NewTransform);
|
|
548
|
+
|
|
549
|
+
// Return updated actor info
|
|
550
|
+
return FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetActorProperties(const TSharedPtr<FJsonObject>& Params)
|
|
554
|
+
{
|
|
555
|
+
// Get actor name
|
|
556
|
+
FString ActorName;
|
|
557
|
+
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
|
558
|
+
{
|
|
559
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Find the actor
|
|
563
|
+
AActor* TargetActor = nullptr;
|
|
564
|
+
TArray<AActor*> AllActors;
|
|
565
|
+
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
|
566
|
+
|
|
567
|
+
for (AActor* Actor : AllActors)
|
|
568
|
+
{
|
|
569
|
+
if (Actor && Actor->GetName() == ActorName)
|
|
570
|
+
{
|
|
571
|
+
TargetActor = Actor;
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (!TargetActor)
|
|
577
|
+
{
|
|
578
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Always return detailed properties for this command
|
|
582
|
+
return FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSetActorProperty(const TSharedPtr<FJsonObject>& Params)
|
|
586
|
+
{
|
|
587
|
+
{
|
|
588
|
+
FString Err;
|
|
589
|
+
if (!IsSafeToModifyEditorWorld(Err))
|
|
590
|
+
{
|
|
591
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Get actor name
|
|
596
|
+
FString ActorName;
|
|
597
|
+
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
|
598
|
+
{
|
|
599
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Find the actor
|
|
603
|
+
AActor* TargetActor = nullptr;
|
|
604
|
+
TArray<AActor*> AllActors;
|
|
605
|
+
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
|
606
|
+
|
|
607
|
+
for (AActor* Actor : AllActors)
|
|
608
|
+
{
|
|
609
|
+
if (Actor && Actor->GetName() == ActorName)
|
|
610
|
+
{
|
|
611
|
+
TargetActor = Actor;
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (!TargetActor)
|
|
617
|
+
{
|
|
618
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Get property name
|
|
622
|
+
FString PropertyName;
|
|
623
|
+
if (!Params->TryGetStringField(TEXT("property_name"), PropertyName))
|
|
624
|
+
{
|
|
625
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_name' parameter"));
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Get property value
|
|
629
|
+
if (!Params->HasField(TEXT("property_value")))
|
|
630
|
+
{
|
|
631
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_value' parameter"));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
TSharedPtr<FJsonValue> PropertyValue = Params->Values.FindRef(TEXT("property_value"));
|
|
635
|
+
|
|
636
|
+
// Set the property using our utility function
|
|
637
|
+
FString ErrorMessage;
|
|
638
|
+
if (FUnrealMCPCommonUtils::SetObjectProperty(TargetActor, PropertyName, PropertyValue, ErrorMessage))
|
|
639
|
+
{
|
|
640
|
+
// Property set successfully
|
|
641
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
642
|
+
ResultObj->SetStringField(TEXT("actor"), ActorName);
|
|
643
|
+
ResultObj->SetStringField(TEXT("property"), PropertyName);
|
|
644
|
+
ResultObj->SetBoolField(TEXT("success"), true);
|
|
645
|
+
|
|
646
|
+
// Also include the full actor details
|
|
647
|
+
ResultObj->SetObjectField(TEXT("actor_details"), FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true));
|
|
648
|
+
return ResultObj;
|
|
649
|
+
}
|
|
650
|
+
else
|
|
651
|
+
{
|
|
652
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(ErrorMessage);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSpawnBlueprintActor(const TSharedPtr<FJsonObject>& Params)
|
|
657
|
+
{
|
|
658
|
+
{
|
|
659
|
+
FString Err;
|
|
660
|
+
if (!IsSafeToModifyEditorWorld(Err))
|
|
661
|
+
{
|
|
662
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Get required parameters
|
|
667
|
+
FString BlueprintName;
|
|
668
|
+
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
|
669
|
+
{
|
|
670
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
FString ActorName;
|
|
674
|
+
if (!Params->TryGetStringField(TEXT("actor_name"), ActorName))
|
|
675
|
+
{
|
|
676
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'actor_name' parameter"));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Find the blueprint
|
|
680
|
+
if (BlueprintName.IsEmpty())
|
|
681
|
+
{
|
|
682
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Blueprint name is empty"));
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
FString Root = TEXT("/Game/Blueprints/");
|
|
686
|
+
FString AssetPath = Root + BlueprintName;
|
|
687
|
+
|
|
688
|
+
if (!FPackageName::DoesPackageExist(AssetPath))
|
|
689
|
+
{
|
|
690
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint '%s' not found – it must reside under /Game/Blueprints"), *BlueprintName));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
UBlueprint* Blueprint = LoadObject<UBlueprint>(nullptr, *AssetPath);
|
|
694
|
+
if (!Blueprint)
|
|
695
|
+
{
|
|
696
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Get transform parameters
|
|
700
|
+
FVector Location(0.0f, 0.0f, 0.0f);
|
|
701
|
+
FRotator Rotation(0.0f, 0.0f, 0.0f);
|
|
702
|
+
FVector Scale(1.0f, 1.0f, 1.0f);
|
|
703
|
+
|
|
704
|
+
if (Params->HasField(TEXT("location")))
|
|
705
|
+
{
|
|
706
|
+
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
|
|
707
|
+
}
|
|
708
|
+
if (Params->HasField(TEXT("rotation")))
|
|
709
|
+
{
|
|
710
|
+
Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"));
|
|
711
|
+
}
|
|
712
|
+
if (Params->HasField(TEXT("scale")))
|
|
713
|
+
{
|
|
714
|
+
Scale = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"));
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Spawn the actor
|
|
718
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
719
|
+
if (!World)
|
|
720
|
+
{
|
|
721
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get editor world"));
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
FTransform SpawnTransform;
|
|
725
|
+
SpawnTransform.SetLocation(Location);
|
|
726
|
+
SpawnTransform.SetRotation(FQuat(Rotation));
|
|
727
|
+
SpawnTransform.SetScale3D(Scale);
|
|
728
|
+
|
|
729
|
+
FActorSpawnParameters SpawnParams;
|
|
730
|
+
SpawnParams.Name = *ActorName;
|
|
731
|
+
|
|
732
|
+
AActor* NewActor = World->SpawnActor<AActor>(Blueprint->GeneratedClass, SpawnTransform, SpawnParams);
|
|
733
|
+
if (NewActor)
|
|
734
|
+
{
|
|
735
|
+
return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to spawn blueprint actor"));
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleFocusViewport(const TSharedPtr<FJsonObject>& Params)
|
|
742
|
+
{
|
|
743
|
+
// Get target actor name if provided
|
|
744
|
+
FString TargetActorName;
|
|
745
|
+
bool HasTargetActor = Params->TryGetStringField(TEXT("target"), TargetActorName);
|
|
746
|
+
|
|
747
|
+
// Get location if provided
|
|
748
|
+
FVector Location(0.0f, 0.0f, 0.0f);
|
|
749
|
+
bool HasLocation = false;
|
|
750
|
+
if (Params->HasField(TEXT("location")))
|
|
751
|
+
{
|
|
752
|
+
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
|
|
753
|
+
HasLocation = true;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Get distance
|
|
757
|
+
float Distance = 1000.0f;
|
|
758
|
+
if (Params->HasField(TEXT("distance")))
|
|
759
|
+
{
|
|
760
|
+
Distance = Params->GetNumberField(TEXT("distance"));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Get orientation if provided
|
|
764
|
+
FRotator Orientation(0.0f, 0.0f, 0.0f);
|
|
765
|
+
bool HasOrientation = false;
|
|
766
|
+
if (Params->HasField(TEXT("orientation")))
|
|
767
|
+
{
|
|
768
|
+
Orientation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("orientation"));
|
|
769
|
+
HasOrientation = true;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Get the active viewport
|
|
773
|
+
FLevelEditorViewportClient* ViewportClient = (FLevelEditorViewportClient*)GEditor->GetActiveViewport()->GetClient();
|
|
774
|
+
if (!ViewportClient)
|
|
775
|
+
{
|
|
776
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get active viewport"));
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// If we have a target actor, focus on it
|
|
780
|
+
if (HasTargetActor)
|
|
781
|
+
{
|
|
782
|
+
// Find the actor
|
|
783
|
+
AActor* TargetActor = nullptr;
|
|
784
|
+
TArray<AActor*> AllActors;
|
|
785
|
+
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
|
786
|
+
|
|
787
|
+
for (AActor* Actor : AllActors)
|
|
788
|
+
{
|
|
789
|
+
if (Actor && Actor->GetName() == TargetActorName)
|
|
790
|
+
{
|
|
791
|
+
TargetActor = Actor;
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (!TargetActor)
|
|
797
|
+
{
|
|
798
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *TargetActorName));
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Focus on the actor
|
|
802
|
+
ViewportClient->SetViewLocation(TargetActor->GetActorLocation() - FVector(Distance, 0.0f, 0.0f));
|
|
803
|
+
}
|
|
804
|
+
// Otherwise use the provided location
|
|
805
|
+
else if (HasLocation)
|
|
806
|
+
{
|
|
807
|
+
ViewportClient->SetViewLocation(Location - FVector(Distance, 0.0f, 0.0f));
|
|
808
|
+
}
|
|
809
|
+
else
|
|
810
|
+
{
|
|
811
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Either 'target' or 'location' must be provided"));
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Set orientation if provided
|
|
815
|
+
if (HasOrientation)
|
|
816
|
+
{
|
|
817
|
+
ViewportClient->SetViewRotation(Orientation);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Force viewport to redraw
|
|
821
|
+
ViewportClient->Invalidate();
|
|
822
|
+
|
|
823
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
824
|
+
ResultObj->SetBoolField(TEXT("success"), true);
|
|
825
|
+
return ResultObj;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleTakeScreenshot(const TSharedPtr<FJsonObject>& Params)
|
|
829
|
+
{
|
|
830
|
+
// Optional file path parameter.
|
|
831
|
+
// If omitted, save to <Project>/Saved/Screenshots/Flockbay with an auto-generated filename.
|
|
832
|
+
FString FilePath;
|
|
833
|
+
const bool bHasFilePath = Params->TryGetStringField(TEXT("filepath"), FilePath);
|
|
834
|
+
|
|
835
|
+
FString FileName;
|
|
836
|
+
Params->TryGetStringField(TEXT("filename"), FileName);
|
|
837
|
+
|
|
838
|
+
const FString DefaultDir = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Screenshots"), TEXT("Flockbay"));
|
|
839
|
+
IFileManager::Get().MakeDirectory(*DefaultDir, /*Tree*/ true);
|
|
840
|
+
|
|
841
|
+
if (bHasFilePath)
|
|
842
|
+
{
|
|
843
|
+
FilePath = FilePath.TrimStartAndEnd();
|
|
844
|
+
if (FilePath.IsEmpty())
|
|
845
|
+
{
|
|
846
|
+
FilePath.Reset();
|
|
847
|
+
}
|
|
848
|
+
else if (!FPaths::IsRelative(FilePath))
|
|
849
|
+
{
|
|
850
|
+
// absolute path: use as-is
|
|
851
|
+
}
|
|
852
|
+
else
|
|
853
|
+
{
|
|
854
|
+
// relative path: resolve under our default screenshots folder
|
|
855
|
+
FilePath = FPaths::Combine(DefaultDir, FilePath);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (FilePath.IsEmpty())
|
|
860
|
+
{
|
|
861
|
+
if (FileName.IsEmpty())
|
|
862
|
+
{
|
|
863
|
+
FileName = FString::Printf(TEXT("mcp_screenshot_%s"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S")));
|
|
864
|
+
}
|
|
865
|
+
FilePath = FPaths::Combine(DefaultDir, FileName);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Ensure the file path has a proper extension
|
|
869
|
+
if (!FilePath.EndsWith(TEXT(".png")))
|
|
870
|
+
{
|
|
871
|
+
FilePath += TEXT(".png");
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Get the active viewport
|
|
875
|
+
if (GEditor && GEditor->GetActiveViewport())
|
|
876
|
+
{
|
|
877
|
+
FViewport* Viewport = GEditor->GetActiveViewport();
|
|
878
|
+
TArray<FColor> Bitmap;
|
|
879
|
+
FIntRect ViewportRect(0, 0, Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y);
|
|
880
|
+
|
|
881
|
+
if (Viewport->ReadPixels(Bitmap, FReadSurfaceDataFlags(), ViewportRect))
|
|
882
|
+
{
|
|
883
|
+
TArray<uint8> CompressedBitmap;
|
|
884
|
+
FImageUtils::CompressImageArray(Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y, Bitmap, CompressedBitmap);
|
|
885
|
+
|
|
886
|
+
if (FFileHelper::SaveArrayToFile(CompressedBitmap, *FilePath))
|
|
887
|
+
{
|
|
888
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
889
|
+
ResultObj->SetStringField(TEXT("filepath"), FilePath);
|
|
890
|
+
return ResultObj;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot"));
|
|
896
|
+
}
|