ue-mcp 0.4.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/dist/bridge.d.ts +25 -0
  4. package/dist/bridge.js +124 -0
  5. package/dist/bridge.js.map +1 -0
  6. package/dist/deployer.d.ts +18 -0
  7. package/dist/deployer.js +175 -0
  8. package/dist/deployer.js.map +1 -0
  9. package/dist/editor-control.d.ts +15 -0
  10. package/dist/editor-control.js +181 -0
  11. package/dist/editor-control.js.map +1 -0
  12. package/dist/github-app.d.ts +4 -0
  13. package/dist/github-app.js +94 -0
  14. package/dist/github-app.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +125 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +2 -0
  19. package/dist/init.js +290 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/instructions.d.ts +1 -0
  22. package/dist/instructions.js +150 -0
  23. package/dist/instructions.js.map +1 -0
  24. package/dist/project.d.ts +32 -0
  25. package/dist/project.js +175 -0
  26. package/dist/project.js.map +1 -0
  27. package/dist/tools/animation.d.ts +2 -0
  28. package/dist/tools/animation.js +62 -0
  29. package/dist/tools/animation.js.map +1 -0
  30. package/dist/tools/asset.d.ts +2 -0
  31. package/dist/tools/asset.js +128 -0
  32. package/dist/tools/asset.js.map +1 -0
  33. package/dist/tools/audio.d.ts +2 -0
  34. package/dist/tools/audio.js +24 -0
  35. package/dist/tools/audio.js.map +1 -0
  36. package/dist/tools/blueprint.d.ts +2 -0
  37. package/dist/tools/blueprint.js +64 -0
  38. package/dist/tools/blueprint.js.map +1 -0
  39. package/dist/tools/demo.d.ts +2 -0
  40. package/dist/tools/demo.js +10 -0
  41. package/dist/tools/demo.js.map +1 -0
  42. package/dist/tools/editor.d.ts +2 -0
  43. package/dist/tools/editor.js +133 -0
  44. package/dist/tools/editor.js.map +1 -0
  45. package/dist/tools/feedback.d.ts +2 -0
  46. package/dist/tools/feedback.js +44 -0
  47. package/dist/tools/feedback.js.map +1 -0
  48. package/dist/tools/foliage.d.ts +2 -0
  49. package/dist/tools/foliage.js +29 -0
  50. package/dist/tools/foliage.js.map +1 -0
  51. package/dist/tools/gameplay.d.ts +2 -0
  52. package/dist/tools/gameplay.js +101 -0
  53. package/dist/tools/gameplay.js.map +1 -0
  54. package/dist/tools/gas.d.ts +2 -0
  55. package/dist/tools/gas.js +43 -0
  56. package/dist/tools/gas.js.map +1 -0
  57. package/dist/tools/landscape.d.ts +2 -0
  58. package/dist/tools/landscape.js +32 -0
  59. package/dist/tools/landscape.js.map +1 -0
  60. package/dist/tools/level.d.ts +2 -0
  61. package/dist/tools/level.js +66 -0
  62. package/dist/tools/level.js.map +1 -0
  63. package/dist/tools/material.d.ts +2 -0
  64. package/dist/tools/material.js +59 -0
  65. package/dist/tools/material.js.map +1 -0
  66. package/dist/tools/networking.d.ts +2 -0
  67. package/dist/tools/networking.js +42 -0
  68. package/dist/tools/networking.js.map +1 -0
  69. package/dist/tools/niagara.d.ts +2 -0
  70. package/dist/tools/niagara.js +42 -0
  71. package/dist/tools/niagara.js.map +1 -0
  72. package/dist/tools/pcg.d.ts +2 -0
  73. package/dist/tools/pcg.js +39 -0
  74. package/dist/tools/pcg.js.map +1 -0
  75. package/dist/tools/project.d.ts +2 -0
  76. package/dist/tools/project.js +282 -0
  77. package/dist/tools/project.js.map +1 -0
  78. package/dist/tools/reflection.d.ts +2 -0
  79. package/dist/tools/reflection.js +26 -0
  80. package/dist/tools/reflection.js.map +1 -0
  81. package/dist/tools/widget.d.ts +2 -0
  82. package/dist/tools/widget.js +34 -0
  83. package/dist/tools/widget.js.map +1 -0
  84. package/dist/types.d.ts +21 -0
  85. package/dist/types.js +38 -0
  86. package/dist/types.js.map +1 -0
  87. package/package.json +76 -0
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  141. package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
@@ -0,0 +1,1676 @@
1
+ #include "EditorHandlers.h"
2
+ #include "HandlerRegistry.h"
3
+ #include "Engine/World.h"
4
+ #include "Engine/Engine.h"
5
+ #include "UObject/UObjectGlobals.h"
6
+ #include "UObject/UnrealType.h"
7
+ #include "Editor/EditorEngine.h"
8
+ #include "Editor.h"
9
+ #include "Kismet/KismetSystemLibrary.h"
10
+ #include "Kismet/GameplayStatics.h"
11
+ #include "AssetRegistry/AssetRegistryModule.h"
12
+ #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
13
+ #include "Dom/JsonObject.h"
14
+ #include "Dom/JsonValue.h"
15
+ #include "IPythonScriptPlugin.h"
16
+ #include "Misc/ConfigCacheIni.h"
17
+ #include "Misc/ConfigContext.h"
18
+ #include "Misc/Paths.h"
19
+ #include "HAL/PlatformFileManager.h"
20
+ #include "Misc/FileHelper.h"
21
+ #include "LevelEditorViewport.h"
22
+ #include "UnrealClient.h"
23
+ #include "Slate/SceneViewport.h"
24
+ #include "HAL/PlatformMemory.h"
25
+ #include "Misc/App.h"
26
+ #include "Logging/MessageLog.h"
27
+ #include "HighResScreenshot.h"
28
+ #include "Misc/OutputDeviceRedirector.h"
29
+ #include "FileHelpers.h"
30
+ #include "Misc/DateTime.h"
31
+ #include "HAL/FileManager.h"
32
+ #include "EngineUtils.h"
33
+ #include "GameFramework/Actor.h"
34
+ #include "EditorValidatorSubsystem.h"
35
+ #include "GenericPlatform/GenericPlatformCrashContext.h"
36
+ #include "ILiveCodingModule.h"
37
+ #include "LevelEditorSubsystem.h"
38
+ #include "Subsystems/AssetEditorSubsystem.h"
39
+
40
+ void FEditorHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
41
+ {
42
+ Registry.RegisterHandler(TEXT("execute_command"), &ExecuteCommand);
43
+ Registry.RegisterHandler(TEXT("execute_python"), &ExecutePython);
44
+ Registry.RegisterHandler(TEXT("set_property"), &SetProperty);
45
+ Registry.RegisterHandler(TEXT("set_config"), &SetConfig);
46
+ Registry.RegisterHandler(TEXT("read_config"), &ReadConfig);
47
+ Registry.RegisterHandler(TEXT("get_viewport_info"), &GetViewportInfo);
48
+ Registry.RegisterHandler(TEXT("get_editor_performance_stats"), &GetEditorPerformanceStats);
49
+ Registry.RegisterHandler(TEXT("get_output_log"), &GetOutputLog);
50
+ Registry.RegisterHandler(TEXT("search_log"), &SearchLog);
51
+ Registry.RegisterHandler(TEXT("get_message_log"), &GetMessageLog);
52
+ Registry.RegisterHandler(TEXT("get_build_status"), &GetBuildStatus);
53
+ Registry.RegisterHandler(TEXT("pie_control"), &PieControl);
54
+ Registry.RegisterHandler(TEXT("capture_screenshot"), &CaptureScreenshot);
55
+ Registry.RegisterHandler(TEXT("set_viewport_camera"), &SetViewportCamera);
56
+ Registry.RegisterHandler(TEXT("undo"), &Undo);
57
+ Registry.RegisterHandler(TEXT("redo"), &Redo);
58
+ Registry.RegisterHandler(TEXT("reload_handlers"), &ReloadHandlers);
59
+ Registry.RegisterHandler(TEXT("save_asset"), &SaveAsset);
60
+ Registry.RegisterHandler(TEXT("save_all"), &SaveAll);
61
+ Registry.RegisterHandler(TEXT("get_crash_reports"), &GetCrashReports);
62
+ Registry.RegisterHandler(TEXT("read_editor_log"), &ReadEditorLog);
63
+ Registry.RegisterHandler(TEXT("pie_get_runtime_value"), &PieGetRuntimeValue);
64
+ Registry.RegisterHandler(TEXT("build_lighting"), &BuildLighting);
65
+ Registry.RegisterHandler(TEXT("build_all"), &BuildAll);
66
+ Registry.RegisterHandler(TEXT("validate_assets"), &ValidateAssets);
67
+ Registry.RegisterHandler(TEXT("cook_content"), &CookContent);
68
+ Registry.RegisterHandler(TEXT("focus_viewport_on_actor"), &FocusViewportOnActor);
69
+ Registry.RegisterHandler(TEXT("hot_reload"), &HotReload);
70
+ Registry.RegisterHandler(TEXT("create_new_level"), &CreateNewLevel);
71
+ Registry.RegisterHandler(TEXT("save_current_level"), &SaveCurrentLevel);
72
+ Registry.RegisterHandler(TEXT("open_asset"), &OpenAsset);
73
+ // Aliases for TS tool compatibility
74
+ Registry.RegisterHandler(TEXT("get_runtime_value"), &PieGetRuntimeValue);
75
+ // New handlers
76
+ Registry.RegisterHandler(TEXT("run_stat_command"), &RunStatCommand);
77
+ Registry.RegisterHandler(TEXT("set_scalability"), &SetScalability);
78
+ Registry.RegisterHandler(TEXT("build_geometry"), &BuildGeometry);
79
+ Registry.RegisterHandler(TEXT("build_hlod"), &BuildHlod);
80
+ Registry.RegisterHandler(TEXT("list_crashes"), &ListCrashes);
81
+ Registry.RegisterHandler(TEXT("get_crash_info"), &GetCrashInfo);
82
+ Registry.RegisterHandler(TEXT("check_for_crashes"), &CheckForCrashes);
83
+ }
84
+
85
+ TSharedPtr<FJsonValue> FEditorHandlers::ExecuteCommand(const TSharedPtr<FJsonObject>& Params)
86
+ {
87
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
88
+
89
+ FString Command;
90
+ if (!Params->TryGetStringField(TEXT("command"), Command))
91
+ {
92
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'command' parameter"));
93
+ Result->SetBoolField(TEXT("success"), false);
94
+ return MakeShared<FJsonValueObject>(Result);
95
+ }
96
+
97
+ if (GEditor && GEditor->GetEditorWorldContext().World())
98
+ {
99
+ UKismetSystemLibrary::ExecuteConsoleCommand(
100
+ GEditor->GetEditorWorldContext().World(),
101
+ Command,
102
+ nullptr
103
+ );
104
+ Result->SetStringField(TEXT("command"), Command);
105
+ Result->SetBoolField(TEXT("success"), true);
106
+ }
107
+ else
108
+ {
109
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
110
+ Result->SetBoolField(TEXT("success"), false);
111
+ }
112
+
113
+ return MakeShared<FJsonValueObject>(Result);
114
+ }
115
+
116
+ TSharedPtr<FJsonValue> FEditorHandlers::ExecutePython(const TSharedPtr<FJsonObject>& Params)
117
+ {
118
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
119
+
120
+ FString Code;
121
+ if (!Params->TryGetStringField(TEXT("code"), Code))
122
+ {
123
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'code' parameter"));
124
+ Result->SetBoolField(TEXT("success"), false);
125
+ return MakeShared<FJsonValueObject>(Result);
126
+ }
127
+
128
+ IPythonScriptPlugin* PythonPlugin = IPythonScriptPlugin::Get();
129
+ if (!PythonPlugin || !PythonPlugin->IsPythonAvailable())
130
+ {
131
+ Result->SetStringField(TEXT("error"), TEXT("Python scripting is not available"));
132
+ Result->SetBoolField(TEXT("success"), false);
133
+ return MakeShared<FJsonValueObject>(Result);
134
+ }
135
+
136
+ FPythonCommandEx PythonCommand;
137
+ PythonCommand.Command = Code;
138
+ PythonCommand.ExecutionMode = EPythonCommandExecutionMode::ExecuteFile;
139
+ PythonCommand.FileExecutionScope = EPythonFileExecutionScope::Public;
140
+
141
+ bool bSuccess = PythonPlugin->ExecPythonCommandEx(PythonCommand);
142
+
143
+ Result->SetBoolField(TEXT("success"), bSuccess);
144
+ Result->SetStringField(TEXT("result"), PythonCommand.CommandResult);
145
+
146
+ TArray<TSharedPtr<FJsonValue>> LogArray;
147
+ for (const FPythonLogOutputEntry& Entry : PythonCommand.LogOutput)
148
+ {
149
+ TSharedPtr<FJsonObject> LogEntry = MakeShared<FJsonObject>();
150
+ LogEntry->SetStringField(TEXT("type"), LexToString(Entry.Type));
151
+ LogEntry->SetStringField(TEXT("output"), Entry.Output);
152
+ LogArray.Add(MakeShared<FJsonValueObject>(LogEntry));
153
+ }
154
+ Result->SetArrayField(TEXT("log_output"), LogArray);
155
+
156
+ FString CombinedOutput;
157
+ for (const FPythonLogOutputEntry& Entry : PythonCommand.LogOutput)
158
+ {
159
+ if (!CombinedOutput.IsEmpty()) CombinedOutput += TEXT("\n");
160
+ CombinedOutput += Entry.Output;
161
+ }
162
+ Result->SetStringField(TEXT("output"), CombinedOutput);
163
+
164
+ return MakeShared<FJsonValueObject>(Result);
165
+ }
166
+
167
+ TSharedPtr<FJsonValue> FEditorHandlers::SetProperty(const TSharedPtr<FJsonObject>& Params)
168
+ {
169
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
170
+
171
+ FString AssetPath;
172
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath) && !Params->TryGetStringField(TEXT("assetPath"), AssetPath))
173
+ {
174
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
175
+ Result->SetBoolField(TEXT("success"), false);
176
+ return MakeShared<FJsonValueObject>(Result);
177
+ }
178
+
179
+ FString PropertyName;
180
+ if (!Params->TryGetStringField(TEXT("propertyName"), PropertyName))
181
+ {
182
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'propertyName' parameter"));
183
+ Result->SetBoolField(TEXT("success"), false);
184
+ return MakeShared<FJsonValueObject>(Result);
185
+ }
186
+
187
+ // Load asset
188
+ UObject* Asset = LoadObject<UObject>(nullptr, *AssetPath);
189
+ if (!Asset)
190
+ {
191
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
192
+ Result->SetBoolField(TEXT("success"), false);
193
+ return MakeShared<FJsonValueObject>(Result);
194
+ }
195
+
196
+ // Get property
197
+ FProperty* Property = Asset->GetClass()->FindPropertyByName(*PropertyName);
198
+ if (!Property)
199
+ {
200
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Property not found: %s"), *PropertyName));
201
+ Result->SetBoolField(TEXT("success"), false);
202
+ return MakeShared<FJsonValueObject>(Result);
203
+ }
204
+
205
+ // Get value from params
206
+ TSharedPtr<FJsonValue> ValueJsonRef = Params->TryGetField(TEXT("value"));
207
+ if (!ValueJsonRef.IsValid())
208
+ {
209
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'value' parameter"));
210
+ Result->SetBoolField(TEXT("success"), false);
211
+ return MakeShared<FJsonValueObject>(Result);
212
+ }
213
+
214
+ // Set property value based on type
215
+ // TODO: Implement proper type conversion from JSON to property value
216
+ // For now, basic implementation
217
+ void* PropertyValue = Property->ContainerPtrToValuePtr<void>(Asset);
218
+ if (FStrProperty* StrProp = CastField<FStrProperty>(Property))
219
+ {
220
+ if (ValueJsonRef->Type == EJson::String)
221
+ {
222
+ StrProp->SetPropertyValue(PropertyValue, ValueJsonRef->AsString());
223
+ }
224
+ }
225
+ else if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Property))
226
+ {
227
+ if (ValueJsonRef->Type == EJson::Boolean)
228
+ {
229
+ BoolProp->SetPropertyValue(PropertyValue, ValueJsonRef->AsBool());
230
+ }
231
+ }
232
+ else if (FIntProperty* IntProp = CastField<FIntProperty>(Property))
233
+ {
234
+ if (ValueJsonRef->Type == EJson::Number)
235
+ {
236
+ IntProp->SetPropertyValue(PropertyValue, (int32)ValueJsonRef->AsNumber());
237
+ }
238
+ }
239
+ else if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Property))
240
+ {
241
+ if (ValueJsonRef->Type == EJson::Number)
242
+ {
243
+ FloatProp->SetPropertyValue(PropertyValue, (float)ValueJsonRef->AsNumber());
244
+ }
245
+ }
246
+
247
+ Result->SetStringField(TEXT("path"), AssetPath);
248
+ Result->SetStringField(TEXT("propertyName"), PropertyName);
249
+ Result->SetBoolField(TEXT("success"), true);
250
+
251
+ return MakeShared<FJsonValueObject>(Result);
252
+ }
253
+
254
+ TSharedPtr<FJsonValue> FEditorHandlers::SetConfig(const TSharedPtr<FJsonObject>& Params)
255
+ {
256
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
257
+
258
+ FString ConfigName;
259
+ if (!Params->TryGetStringField(TEXT("configName"), ConfigName))
260
+ {
261
+ Params->TryGetStringField(TEXT("configFile"), ConfigName);
262
+ }
263
+ FString Section;
264
+ if (!Params->TryGetStringField(TEXT("section"), Section))
265
+ {
266
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'section' parameter"));
267
+ Result->SetBoolField(TEXT("success"), false);
268
+ return MakeShared<FJsonValueObject>(Result);
269
+ }
270
+ FString Key;
271
+ if (!Params->TryGetStringField(TEXT("key"), Key))
272
+ {
273
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'key' parameter"));
274
+ Result->SetBoolField(TEXT("success"), false);
275
+ return MakeShared<FJsonValueObject>(Result);
276
+ }
277
+ FString Value;
278
+ Params->TryGetStringField(TEXT("value"), Value);
279
+
280
+ if (ConfigName.IsEmpty())
281
+ {
282
+ ConfigName = TEXT("DefaultEngine.ini");
283
+ }
284
+ else if (!ConfigName.EndsWith(TEXT(".ini")))
285
+ {
286
+ ConfigName = FString::Printf(TEXT("Default%s.ini"), *ConfigName);
287
+ }
288
+
289
+ FString ConfigDir = FPaths::ProjectConfigDir();
290
+ FString IniPath = FPaths::Combine(ConfigDir, ConfigName);
291
+
292
+ GConfig->SetString(*Section, *Key, *Value, IniPath);
293
+ GConfig->Flush(false, IniPath);
294
+
295
+ Result->SetStringField(TEXT("configFile"), ConfigName);
296
+ Result->SetStringField(TEXT("section"), Section);
297
+ Result->SetStringField(TEXT("key"), Key);
298
+ Result->SetStringField(TEXT("value"), Value);
299
+ Result->SetBoolField(TEXT("success"), true);
300
+
301
+ return MakeShared<FJsonValueObject>(Result);
302
+ }
303
+
304
+ TSharedPtr<FJsonValue> FEditorHandlers::ReadConfig(const TSharedPtr<FJsonObject>& Params)
305
+ {
306
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
307
+
308
+ FString ConfigName;
309
+ if (!Params->TryGetStringField(TEXT("configFile"), ConfigName))
310
+ {
311
+ Params->TryGetStringField(TEXT("configName"), ConfigName);
312
+ }
313
+ FString Section;
314
+ if (!Params->TryGetStringField(TEXT("section"), Section))
315
+ {
316
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'section' parameter"));
317
+ Result->SetBoolField(TEXT("success"), false);
318
+ return MakeShared<FJsonValueObject>(Result);
319
+ }
320
+ FString Key;
321
+ if (!Params->TryGetStringField(TEXT("key"), Key))
322
+ {
323
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'key' parameter"));
324
+ Result->SetBoolField(TEXT("success"), false);
325
+ return MakeShared<FJsonValueObject>(Result);
326
+ }
327
+
328
+ if (ConfigName.IsEmpty())
329
+ {
330
+ ConfigName = TEXT("DefaultEngine.ini");
331
+ }
332
+ else if (!ConfigName.EndsWith(TEXT(".ini")))
333
+ {
334
+ ConfigName = FString::Printf(TEXT("Default%s.ini"), *ConfigName);
335
+ }
336
+
337
+ FString ConfigDir = FPaths::ProjectConfigDir();
338
+ FString IniPath = FPaths::Combine(ConfigDir, ConfigName);
339
+
340
+ FString Value;
341
+ bool bFound = GConfig->GetString(*Section, *Key, Value, IniPath);
342
+
343
+ Result->SetStringField(TEXT("configFile"), ConfigName);
344
+ Result->SetStringField(TEXT("section"), Section);
345
+ Result->SetStringField(TEXT("key"), Key);
346
+ Result->SetBoolField(TEXT("found"), bFound);
347
+ if (bFound)
348
+ {
349
+ Result->SetStringField(TEXT("value"), Value);
350
+ }
351
+ Result->SetBoolField(TEXT("success"), true);
352
+
353
+ return MakeShared<FJsonValueObject>(Result);
354
+ }
355
+
356
+ TSharedPtr<FJsonValue> FEditorHandlers::GetViewportInfo(const TSharedPtr<FJsonObject>& Params)
357
+ {
358
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
359
+
360
+ if (!GEditor)
361
+ {
362
+ Result->SetStringField(TEXT("error"), TEXT("Editor not available"));
363
+ Result->SetBoolField(TEXT("success"), false);
364
+ return MakeShared<FJsonValueObject>(Result);
365
+ }
366
+
367
+ FLevelEditorViewportClient* ViewportClient = GCurrentLevelEditingViewportClient;
368
+ if (!ViewportClient)
369
+ {
370
+ // Try to get from level viewport clients list
371
+ const TArray<FLevelEditorViewportClient*>& ViewportClients = GEditor->GetLevelViewportClients();
372
+ if (ViewportClients.Num() > 0)
373
+ {
374
+ ViewportClient = ViewportClients[0];
375
+ }
376
+ }
377
+
378
+ if (!ViewportClient)
379
+ {
380
+ Result->SetStringField(TEXT("error"), TEXT("No viewport client available"));
381
+ Result->SetBoolField(TEXT("success"), false);
382
+ return MakeShared<FJsonValueObject>(Result);
383
+ }
384
+
385
+ FVector Location = ViewportClient->GetViewLocation();
386
+ FRotator Rotation = ViewportClient->GetViewRotation();
387
+ float FOV = ViewportClient->ViewFOV;
388
+
389
+ TSharedPtr<FJsonObject> LocationObj = MakeShared<FJsonObject>();
390
+ LocationObj->SetNumberField(TEXT("x"), Location.X);
391
+ LocationObj->SetNumberField(TEXT("y"), Location.Y);
392
+ LocationObj->SetNumberField(TEXT("z"), Location.Z);
393
+ Result->SetObjectField(TEXT("location"), LocationObj);
394
+
395
+ TSharedPtr<FJsonObject> RotationObj = MakeShared<FJsonObject>();
396
+ RotationObj->SetNumberField(TEXT("pitch"), Rotation.Pitch);
397
+ RotationObj->SetNumberField(TEXT("yaw"), Rotation.Yaw);
398
+ RotationObj->SetNumberField(TEXT("roll"), Rotation.Roll);
399
+ Result->SetObjectField(TEXT("rotation"), RotationObj);
400
+
401
+ Result->SetNumberField(TEXT("fov"), FOV);
402
+ Result->SetBoolField(TEXT("success"), true);
403
+
404
+ return MakeShared<FJsonValueObject>(Result);
405
+ }
406
+
407
+ TSharedPtr<FJsonValue> FEditorHandlers::GetEditorPerformanceStats(const TSharedPtr<FJsonObject>& Params)
408
+ {
409
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
410
+
411
+ // FPS from delta time
412
+ double DeltaTime = FApp::GetDeltaTime();
413
+ double FPS = (DeltaTime > 0.0) ? (1.0 / DeltaTime) : 0.0;
414
+ Result->SetNumberField(TEXT("fps"), FPS);
415
+ Result->SetNumberField(TEXT("deltaTime"), DeltaTime);
416
+
417
+ // Memory stats
418
+ FPlatformMemoryStats MemStats = FPlatformMemory::GetStats();
419
+ TSharedPtr<FJsonObject> MemoryObj = MakeShared<FJsonObject>();
420
+ MemoryObj->SetNumberField(TEXT("usedPhysical"), static_cast<double>(MemStats.UsedPhysical));
421
+ MemoryObj->SetNumberField(TEXT("availablePhysical"), static_cast<double>(MemStats.AvailablePhysical));
422
+ MemoryObj->SetNumberField(TEXT("usedVirtual"), static_cast<double>(MemStats.UsedVirtual));
423
+ MemoryObj->SetNumberField(TEXT("availableVirtual"), static_cast<double>(MemStats.AvailableVirtual));
424
+ Result->SetObjectField(TEXT("memory"), MemoryObj);
425
+
426
+ Result->SetBoolField(TEXT("success"), true);
427
+
428
+ return MakeShared<FJsonValueObject>(Result);
429
+ }
430
+
431
+ TSharedPtr<FJsonValue> FEditorHandlers::GetOutputLog(const TSharedPtr<FJsonObject>& Params)
432
+ {
433
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
434
+
435
+ // maxLines parameter with default of 100
436
+ int32 MaxLines = 100;
437
+ if (Params->HasField(TEXT("maxLines")))
438
+ {
439
+ MaxLines = static_cast<int32>(Params->GetNumberField(TEXT("maxLines")));
440
+ }
441
+
442
+ // Output log capture is not trivially available in C++ without a custom output device.
443
+ // Return success with an empty lines array as a baseline implementation.
444
+ TArray<TSharedPtr<FJsonValue>> LinesArray;
445
+ Result->SetArrayField(TEXT("lines"), LinesArray);
446
+ Result->SetNumberField(TEXT("maxLines"), MaxLines);
447
+ Result->SetBoolField(TEXT("success"), true);
448
+
449
+ return MakeShared<FJsonValueObject>(Result);
450
+ }
451
+
452
+ TSharedPtr<FJsonValue> FEditorHandlers::SearchLog(const TSharedPtr<FJsonObject>& Params)
453
+ {
454
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
455
+
456
+ FString Query;
457
+ if (!Params->TryGetStringField(TEXT("query"), Query))
458
+ {
459
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'query' parameter"));
460
+ Result->SetBoolField(TEXT("success"), false);
461
+ return MakeShared<FJsonValueObject>(Result);
462
+ }
463
+
464
+ // Similar to GetOutputLog - return success with empty matches as baseline
465
+ TArray<TSharedPtr<FJsonValue>> MatchesArray;
466
+ Result->SetArrayField(TEXT("matches"), MatchesArray);
467
+ Result->SetStringField(TEXT("query"), Query);
468
+ Result->SetBoolField(TEXT("success"), true);
469
+
470
+ return MakeShared<FJsonValueObject>(Result);
471
+ }
472
+
473
+ TSharedPtr<FJsonValue> FEditorHandlers::GetMessageLog(const TSharedPtr<FJsonObject>& Params)
474
+ {
475
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
476
+
477
+ // FMessageLog does not expose a simple API to read back entries in C++.
478
+ // Return success with an empty messages array as a baseline implementation.
479
+ TArray<TSharedPtr<FJsonValue>> MessagesArray;
480
+ Result->SetArrayField(TEXT("messages"), MessagesArray);
481
+ Result->SetBoolField(TEXT("success"), true);
482
+
483
+ return MakeShared<FJsonValueObject>(Result);
484
+ }
485
+
486
+ TSharedPtr<FJsonValue> FEditorHandlers::GetBuildStatus(const TSharedPtr<FJsonObject>& Params)
487
+ {
488
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
489
+
490
+ // Basic build status - report as idle since we cannot easily query
491
+ // the live compilation state from within the editor module.
492
+ Result->SetStringField(TEXT("status"), TEXT("idle"));
493
+ Result->SetBoolField(TEXT("success"), true);
494
+
495
+ return MakeShared<FJsonValueObject>(Result);
496
+ }
497
+
498
+ TSharedPtr<FJsonValue> FEditorHandlers::PieControl(const TSharedPtr<FJsonObject>& Params)
499
+ {
500
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
501
+
502
+ FString Action;
503
+ if (!Params->TryGetStringField(TEXT("action"), Action))
504
+ {
505
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'action' parameter"));
506
+ Result->SetBoolField(TEXT("success"), false);
507
+ return MakeShared<FJsonValueObject>(Result);
508
+ }
509
+
510
+ if (!GEditor)
511
+ {
512
+ Result->SetStringField(TEXT("error"), TEXT("Editor not available"));
513
+ Result->SetBoolField(TEXT("success"), false);
514
+ return MakeShared<FJsonValueObject>(Result);
515
+ }
516
+
517
+ if (Action == TEXT("status"))
518
+ {
519
+ bool bIsPlaying = (GEditor->PlayWorld != nullptr);
520
+ Result->SetBoolField(TEXT("isPlaying"), bIsPlaying);
521
+ Result->SetStringField(TEXT("action"), Action);
522
+ Result->SetBoolField(TEXT("success"), true);
523
+ }
524
+ else if (Action == TEXT("start"))
525
+ {
526
+ if (GEditor->PlayWorld != nullptr)
527
+ {
528
+ Result->SetStringField(TEXT("error"), TEXT("PIE session already active"));
529
+ Result->SetBoolField(TEXT("success"), false);
530
+ }
531
+ else
532
+ {
533
+ FRequestPlaySessionParams SessionParams;
534
+ GEditor->RequestPlaySession(SessionParams);
535
+ Result->SetStringField(TEXT("action"), Action);
536
+ Result->SetBoolField(TEXT("success"), true);
537
+ }
538
+ }
539
+ else if (Action == TEXT("stop"))
540
+ {
541
+ if (GEditor->PlayWorld == nullptr)
542
+ {
543
+ Result->SetStringField(TEXT("error"), TEXT("No PIE session active"));
544
+ Result->SetBoolField(TEXT("success"), false);
545
+ }
546
+ else
547
+ {
548
+ GEditor->RequestEndPlayMap();
549
+ Result->SetStringField(TEXT("action"), Action);
550
+ Result->SetBoolField(TEXT("success"), true);
551
+ }
552
+ }
553
+ else
554
+ {
555
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown action: %s. Expected 'status', 'start', or 'stop'"), *Action));
556
+ Result->SetBoolField(TEXT("success"), false);
557
+ }
558
+
559
+ return MakeShared<FJsonValueObject>(Result);
560
+ }
561
+
562
+ TSharedPtr<FJsonValue> FEditorHandlers::CaptureScreenshot(const TSharedPtr<FJsonObject>& Params)
563
+ {
564
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
565
+
566
+ FString Filename;
567
+ if (!Params->TryGetStringField(TEXT("filename"), Filename))
568
+ {
569
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'filename' parameter"));
570
+ Result->SetBoolField(TEXT("success"), false);
571
+ return MakeShared<FJsonValueObject>(Result);
572
+ }
573
+
574
+ // Ensure the filename has a proper extension
575
+ if (!Filename.EndsWith(TEXT(".png")) && !Filename.EndsWith(TEXT(".jpg")) && !Filename.EndsWith(TEXT(".bmp")))
576
+ {
577
+ Filename += TEXT(".png");
578
+ }
579
+
580
+ FScreenshotRequest::RequestScreenshot(*Filename, false, false);
581
+
582
+ Result->SetStringField(TEXT("filename"), Filename);
583
+ Result->SetBoolField(TEXT("success"), true);
584
+
585
+ return MakeShared<FJsonValueObject>(Result);
586
+ }
587
+
588
+ TSharedPtr<FJsonValue> FEditorHandlers::SetViewportCamera(const TSharedPtr<FJsonObject>& Params)
589
+ {
590
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
591
+
592
+ if (!GEditor)
593
+ {
594
+ Result->SetStringField(TEXT("error"), TEXT("Editor not available"));
595
+ Result->SetBoolField(TEXT("success"), false);
596
+ return MakeShared<FJsonValueObject>(Result);
597
+ }
598
+
599
+ FLevelEditorViewportClient* ViewportClient = GCurrentLevelEditingViewportClient;
600
+ if (!ViewportClient)
601
+ {
602
+ const TArray<FLevelEditorViewportClient*>& ViewportClients = GEditor->GetLevelViewportClients();
603
+ if (ViewportClients.Num() > 0)
604
+ {
605
+ ViewportClient = ViewportClients[0];
606
+ }
607
+ }
608
+
609
+ if (!ViewportClient)
610
+ {
611
+ Result->SetStringField(TEXT("error"), TEXT("No viewport client available"));
612
+ Result->SetBoolField(TEXT("success"), false);
613
+ return MakeShared<FJsonValueObject>(Result);
614
+ }
615
+
616
+ // Set location if provided
617
+ const TSharedPtr<FJsonObject>* LocationObj = nullptr;
618
+ if (Params->TryGetObjectField(TEXT("location"), LocationObj) && LocationObj)
619
+ {
620
+ FVector Location;
621
+ Location.X = (*LocationObj)->GetNumberField(TEXT("x"));
622
+ Location.Y = (*LocationObj)->GetNumberField(TEXT("y"));
623
+ Location.Z = (*LocationObj)->GetNumberField(TEXT("z"));
624
+ ViewportClient->SetViewLocation(Location);
625
+ }
626
+
627
+ // Set rotation if provided
628
+ const TSharedPtr<FJsonObject>* RotationObj = nullptr;
629
+ if (Params->TryGetObjectField(TEXT("rotation"), RotationObj) && RotationObj)
630
+ {
631
+ FRotator Rotation;
632
+ Rotation.Pitch = (*RotationObj)->GetNumberField(TEXT("pitch"));
633
+ Rotation.Yaw = (*RotationObj)->GetNumberField(TEXT("yaw"));
634
+ Rotation.Roll = (*RotationObj)->GetNumberField(TEXT("roll"));
635
+ ViewportClient->SetViewRotation(Rotation);
636
+ }
637
+
638
+ Result->SetBoolField(TEXT("success"), true);
639
+
640
+ return MakeShared<FJsonValueObject>(Result);
641
+ }
642
+
643
+ TSharedPtr<FJsonValue> FEditorHandlers::Undo(const TSharedPtr<FJsonObject>& Params)
644
+ {
645
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
646
+
647
+ if (!GEditor)
648
+ {
649
+ Result->SetStringField(TEXT("error"), TEXT("Editor not available"));
650
+ Result->SetBoolField(TEXT("success"), false);
651
+ return MakeShared<FJsonValueObject>(Result);
652
+ }
653
+
654
+ bool bSuccess = GEditor->UndoTransaction();
655
+ Result->SetBoolField(TEXT("success"), bSuccess);
656
+
657
+ return MakeShared<FJsonValueObject>(Result);
658
+ }
659
+
660
+ TSharedPtr<FJsonValue> FEditorHandlers::Redo(const TSharedPtr<FJsonObject>& Params)
661
+ {
662
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
663
+
664
+ if (!GEditor)
665
+ {
666
+ Result->SetStringField(TEXT("error"), TEXT("Editor not available"));
667
+ Result->SetBoolField(TEXT("success"), false);
668
+ return MakeShared<FJsonValueObject>(Result);
669
+ }
670
+
671
+ bool bSuccess = GEditor->RedoTransaction();
672
+ Result->SetBoolField(TEXT("success"), bSuccess);
673
+
674
+ return MakeShared<FJsonValueObject>(Result);
675
+ }
676
+
677
+ TSharedPtr<FJsonValue> FEditorHandlers::ReloadHandlers(const TSharedPtr<FJsonObject>& Params)
678
+ {
679
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
680
+
681
+ // No-op in C++ bridge - this was used in the Python bridge to reload Python handler modules.
682
+ Result->SetBoolField(TEXT("success"), true);
683
+
684
+ return MakeShared<FJsonValueObject>(Result);
685
+ }
686
+
687
+ TSharedPtr<FJsonValue> FEditorHandlers::SaveAsset(const TSharedPtr<FJsonObject>& Params)
688
+ {
689
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
690
+
691
+ FString AssetPath;
692
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath) && !Params->TryGetStringField(TEXT("assetPath"), AssetPath))
693
+ {
694
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
695
+ Result->SetBoolField(TEXT("success"), false);
696
+ return MakeShared<FJsonValueObject>(Result);
697
+ }
698
+
699
+ bool bSuccess = UEditorAssetLibrary::SaveAsset(AssetPath);
700
+
701
+ Result->SetStringField(TEXT("path"), AssetPath);
702
+ Result->SetBoolField(TEXT("success"), bSuccess);
703
+ if (!bSuccess)
704
+ {
705
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to save asset: %s"), *AssetPath));
706
+ }
707
+
708
+ return MakeShared<FJsonValueObject>(Result);
709
+ }
710
+
711
+ TSharedPtr<FJsonValue> FEditorHandlers::SaveAll(const TSharedPtr<FJsonObject>& Params)
712
+ {
713
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
714
+
715
+ // Save all dirty packages using FEditorFileUtils
716
+ bool bPromptUserToSave = false;
717
+ bool bSaveMapPackages = true;
718
+ bool bSaveContentPackages = true;
719
+ bool bSuccess = FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages);
720
+
721
+ Result->SetBoolField(TEXT("success"), bSuccess);
722
+ Result->SetStringField(TEXT("message"), bSuccess ? TEXT("All dirty packages saved") : TEXT("Some packages may have failed to save"));
723
+
724
+ return MakeShared<FJsonValueObject>(Result);
725
+ }
726
+
727
+ TSharedPtr<FJsonValue> FEditorHandlers::GetCrashReports(const TSharedPtr<FJsonObject>& Params)
728
+ {
729
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
730
+
731
+ FString CrashesDir = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Crashes"));
732
+ IFileManager& FileManager = IFileManager::Get();
733
+
734
+ TArray<TSharedPtr<FJsonValue>> CrashesArray;
735
+
736
+ if (FileManager.DirectoryExists(*CrashesDir))
737
+ {
738
+ // Find all subdirectories in Crashes folder
739
+ TArray<FString> CrashFolders;
740
+ FileManager.FindFiles(CrashFolders, *FPaths::Combine(CrashesDir, TEXT("*")), false, true);
741
+
742
+ for (const FString& FolderName : CrashFolders)
743
+ {
744
+ FString FolderPath = FPaths::Combine(CrashesDir, FolderName);
745
+
746
+ TSharedPtr<FJsonObject> CrashObj = MakeShared<FJsonObject>();
747
+ CrashObj->SetStringField(TEXT("folder"), FolderName);
748
+ CrashObj->SetStringField(TEXT("path"), FolderPath);
749
+
750
+ // Get folder timestamp
751
+ FDateTime TimeStamp = FileManager.GetTimeStamp(*FolderPath);
752
+ if (TimeStamp != FDateTime::MinValue())
753
+ {
754
+ CrashObj->SetStringField(TEXT("timestamp"), TimeStamp.ToString());
755
+ }
756
+
757
+ // List files inside the crash folder
758
+ TArray<FString> CrashFiles;
759
+ FileManager.FindFiles(CrashFiles, *FPaths::Combine(FolderPath, TEXT("*")), true, false);
760
+
761
+ TArray<TSharedPtr<FJsonValue>> FilesArray;
762
+ for (const FString& FileName : CrashFiles)
763
+ {
764
+ FilesArray.Add(MakeShared<FJsonValueString>(FileName));
765
+ }
766
+ CrashObj->SetArrayField(TEXT("files"), FilesArray);
767
+
768
+ CrashesArray.Add(MakeShared<FJsonValueObject>(CrashObj));
769
+ }
770
+ }
771
+
772
+ Result->SetStringField(TEXT("crashesDir"), CrashesDir);
773
+ Result->SetNumberField(TEXT("crashCount"), CrashesArray.Num());
774
+ Result->SetArrayField(TEXT("crashes"), CrashesArray);
775
+ Result->SetBoolField(TEXT("success"), true);
776
+
777
+ return MakeShared<FJsonValueObject>(Result);
778
+ }
779
+
780
+ TSharedPtr<FJsonValue> FEditorHandlers::ReadEditorLog(const TSharedPtr<FJsonObject>& Params)
781
+ {
782
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
783
+
784
+ // Parameters
785
+ int32 LastN = 100;
786
+ if (Params->HasField(TEXT("lastN")))
787
+ {
788
+ LastN = static_cast<int32>(Params->GetNumberField(TEXT("lastN")));
789
+ }
790
+
791
+ FString Filter;
792
+ Params->TryGetStringField(TEXT("filter"), Filter);
793
+
794
+ // Locate the editor log file
795
+ FString LogDir = FPaths::ProjectLogDir();
796
+ FString LogFilePath = FPaths::Combine(LogDir, TEXT("Editor.log"));
797
+
798
+ // If Editor.log doesn't exist, try the current log file
799
+ if (!FPaths::FileExists(LogFilePath))
800
+ {
801
+ LogFilePath = FPaths::Combine(LogDir, FString(FApp::GetProjectName()) + TEXT(".log"));
802
+ }
803
+
804
+ if (!FPaths::FileExists(LogFilePath))
805
+ {
806
+ Result->SetStringField(TEXT("error"), TEXT("Editor log file not found"));
807
+ Result->SetStringField(TEXT("logDir"), LogDir);
808
+ Result->SetBoolField(TEXT("success"), false);
809
+ return MakeShared<FJsonValueObject>(Result);
810
+ }
811
+
812
+ // Read the log file into lines
813
+ TArray<FString> AllLines;
814
+ if (!FFileHelper::LoadFileToStringArray(AllLines, *LogFilePath))
815
+ {
816
+ Result->SetStringField(TEXT("error"), TEXT("Failed to read editor log file"));
817
+ Result->SetBoolField(TEXT("success"), false);
818
+ return MakeShared<FJsonValueObject>(Result);
819
+ }
820
+
821
+ // Apply filter and take last N lines
822
+ TArray<FString> ResultLines;
823
+ if (Filter.IsEmpty())
824
+ {
825
+ // No filter - take the last N lines directly
826
+ int32 StartIndex = FMath::Max(0, AllLines.Num() - LastN);
827
+ for (int32 i = StartIndex; i < AllLines.Num(); ++i)
828
+ {
829
+ ResultLines.Add(AllLines[i]);
830
+ }
831
+ }
832
+ else
833
+ {
834
+ // Filter lines (case-insensitive) then take last N
835
+ FString FilterLower = Filter.ToLower();
836
+ TArray<FString> FilteredLines;
837
+ for (const FString& Line : AllLines)
838
+ {
839
+ if (Line.ToLower().Contains(FilterLower))
840
+ {
841
+ FilteredLines.Add(Line);
842
+ }
843
+ }
844
+ int32 StartIndex = FMath::Max(0, FilteredLines.Num() - LastN);
845
+ for (int32 i = StartIndex; i < FilteredLines.Num(); ++i)
846
+ {
847
+ ResultLines.Add(FilteredLines[i]);
848
+ }
849
+ }
850
+
851
+ // Convert to JSON array
852
+ TArray<TSharedPtr<FJsonValue>> LinesArray;
853
+ for (const FString& Line : ResultLines)
854
+ {
855
+ LinesArray.Add(MakeShared<FJsonValueString>(Line));
856
+ }
857
+
858
+ Result->SetStringField(TEXT("logFile"), LogFilePath);
859
+ Result->SetNumberField(TEXT("lineCount"), ResultLines.Num());
860
+ Result->SetNumberField(TEXT("totalLines"), AllLines.Num());
861
+ Result->SetArrayField(TEXT("lines"), LinesArray);
862
+ Result->SetBoolField(TEXT("success"), true);
863
+
864
+ return MakeShared<FJsonValueObject>(Result);
865
+ }
866
+
867
+ TSharedPtr<FJsonValue> FEditorHandlers::PieGetRuntimeValue(const TSharedPtr<FJsonObject>& Params)
868
+ {
869
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
870
+
871
+ if (!GEditor)
872
+ {
873
+ Result->SetStringField(TEXT("error"), TEXT("Editor not available"));
874
+ Result->SetBoolField(TEXT("success"), false);
875
+ return MakeShared<FJsonValueObject>(Result);
876
+ }
877
+
878
+ // Check if PIE is active
879
+ if (GEditor->PlayWorld == nullptr)
880
+ {
881
+ Result->SetStringField(TEXT("error"), TEXT("PIE is not active. Start a PIE session first."));
882
+ Result->SetBoolField(TEXT("success"), false);
883
+ return MakeShared<FJsonValueObject>(Result);
884
+ }
885
+
886
+ FString ActorPath;
887
+ if (!Params->TryGetStringField(TEXT("actorPath"), ActorPath))
888
+ {
889
+ // Also accept actorLabel as a fallback
890
+ if (!Params->TryGetStringField(TEXT("actorLabel"), ActorPath))
891
+ {
892
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'actorPath' parameter"));
893
+ Result->SetBoolField(TEXT("success"), false);
894
+ return MakeShared<FJsonValueObject>(Result);
895
+ }
896
+ }
897
+
898
+ FString PropertyName;
899
+ if (!Params->TryGetStringField(TEXT("propertyName"), PropertyName))
900
+ {
901
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'propertyName' parameter"));
902
+ Result->SetBoolField(TEXT("success"), false);
903
+ return MakeShared<FJsonValueObject>(Result);
904
+ }
905
+
906
+ // Search for the actor in the PIE world
907
+ UWorld* PIEWorld = GEditor->PlayWorld;
908
+ AActor* TargetActor = nullptr;
909
+
910
+ for (TActorIterator<AActor> It(PIEWorld); It; ++It)
911
+ {
912
+ AActor* Actor = *It;
913
+ if (Actor->GetName() == ActorPath ||
914
+ Actor->GetActorLabel() == ActorPath ||
915
+ Actor->GetPathName() == ActorPath)
916
+ {
917
+ TargetActor = Actor;
918
+ break;
919
+ }
920
+ }
921
+
922
+ if (!TargetActor)
923
+ {
924
+ // Collect available actor names for the error message
925
+ TArray<TSharedPtr<FJsonValue>> AvailableActors;
926
+ int32 Count = 0;
927
+ for (TActorIterator<AActor> It(PIEWorld); It && Count < 20; ++It, ++Count)
928
+ {
929
+ AvailableActors.Add(MakeShared<FJsonValueString>((*It)->GetActorLabel()));
930
+ }
931
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Actor '%s' not found in PIE world"), *ActorPath));
932
+ Result->SetArrayField(TEXT("availableActors"), AvailableActors);
933
+ Result->SetBoolField(TEXT("success"), false);
934
+ return MakeShared<FJsonValueObject>(Result);
935
+ }
936
+
937
+ // Find the property via reflection
938
+ FProperty* Property = TargetActor->GetClass()->FindPropertyByName(*PropertyName);
939
+ if (!Property)
940
+ {
941
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Property '%s' not found on actor '%s'"), *PropertyName, *ActorPath));
942
+ Result->SetBoolField(TEXT("success"), false);
943
+ return MakeShared<FJsonValueObject>(Result);
944
+ }
945
+
946
+ // Read property value and serialize based on type
947
+ const void* PropertyValue = Property->ContainerPtrToValuePtr<void>(TargetActor);
948
+
949
+ if (FStrProperty* StrProp = CastField<FStrProperty>(Property))
950
+ {
951
+ FString Value = StrProp->GetPropertyValue(PropertyValue);
952
+ Result->SetStringField(TEXT("value"), Value);
953
+ Result->SetStringField(TEXT("type"), TEXT("String"));
954
+ }
955
+ else if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Property))
956
+ {
957
+ bool Value = BoolProp->GetPropertyValue(PropertyValue);
958
+ Result->SetBoolField(TEXT("value"), Value);
959
+ Result->SetStringField(TEXT("type"), TEXT("Bool"));
960
+ }
961
+ else if (FIntProperty* IntProp = CastField<FIntProperty>(Property))
962
+ {
963
+ int32 Value = IntProp->GetPropertyValue(PropertyValue);
964
+ Result->SetNumberField(TEXT("value"), Value);
965
+ Result->SetStringField(TEXT("type"), TEXT("Int"));
966
+ }
967
+ else if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Property))
968
+ {
969
+ float Value = FloatProp->GetPropertyValue(PropertyValue);
970
+ Result->SetNumberField(TEXT("value"), Value);
971
+ Result->SetStringField(TEXT("type"), TEXT("Float"));
972
+ }
973
+ else if (FDoubleProperty* DoubleProp = CastField<FDoubleProperty>(Property))
974
+ {
975
+ double Value = DoubleProp->GetPropertyValue(PropertyValue);
976
+ Result->SetNumberField(TEXT("value"), Value);
977
+ Result->SetStringField(TEXT("type"), TEXT("Double"));
978
+ }
979
+ else if (FNameProperty* NameProp = CastField<FNameProperty>(Property))
980
+ {
981
+ FName Value = NameProp->GetPropertyValue(PropertyValue);
982
+ Result->SetStringField(TEXT("value"), Value.ToString());
983
+ Result->SetStringField(TEXT("type"), TEXT("Name"));
984
+ }
985
+ else if (FTextProperty* TextProp = CastField<FTextProperty>(Property))
986
+ {
987
+ FText Value = TextProp->GetPropertyValue(PropertyValue);
988
+ Result->SetStringField(TEXT("value"), Value.ToString());
989
+ Result->SetStringField(TEXT("type"), TEXT("Text"));
990
+ }
991
+ else if (FStructProperty* StructProp = CastField<FStructProperty>(Property))
992
+ {
993
+ // Handle common struct types: FVector, FRotator, FLinearColor
994
+ if (StructProp->Struct == TBaseStructure<FVector>::Get())
995
+ {
996
+ const FVector* Vec = reinterpret_cast<const FVector*>(PropertyValue);
997
+ TSharedPtr<FJsonObject> VecObj = MakeShared<FJsonObject>();
998
+ VecObj->SetNumberField(TEXT("x"), Vec->X);
999
+ VecObj->SetNumberField(TEXT("y"), Vec->Y);
1000
+ VecObj->SetNumberField(TEXT("z"), Vec->Z);
1001
+ Result->SetObjectField(TEXT("value"), VecObj);
1002
+ Result->SetStringField(TEXT("type"), TEXT("Vector"));
1003
+ }
1004
+ else if (StructProp->Struct == TBaseStructure<FRotator>::Get())
1005
+ {
1006
+ const FRotator* Rot = reinterpret_cast<const FRotator*>(PropertyValue);
1007
+ TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
1008
+ RotObj->SetNumberField(TEXT("pitch"), Rot->Pitch);
1009
+ RotObj->SetNumberField(TEXT("yaw"), Rot->Yaw);
1010
+ RotObj->SetNumberField(TEXT("roll"), Rot->Roll);
1011
+ Result->SetObjectField(TEXT("value"), RotObj);
1012
+ Result->SetStringField(TEXT("type"), TEXT("Rotator"));
1013
+ }
1014
+ else if (StructProp->Struct == TBaseStructure<FLinearColor>::Get())
1015
+ {
1016
+ const FLinearColor* Color = reinterpret_cast<const FLinearColor*>(PropertyValue);
1017
+ TSharedPtr<FJsonObject> ColorObj = MakeShared<FJsonObject>();
1018
+ ColorObj->SetNumberField(TEXT("r"), Color->R);
1019
+ ColorObj->SetNumberField(TEXT("g"), Color->G);
1020
+ ColorObj->SetNumberField(TEXT("b"), Color->B);
1021
+ ColorObj->SetNumberField(TEXT("a"), Color->A);
1022
+ Result->SetObjectField(TEXT("value"), ColorObj);
1023
+ Result->SetStringField(TEXT("type"), TEXT("LinearColor"));
1024
+ }
1025
+ else
1026
+ {
1027
+ // Generic struct: export to string
1028
+ FString ExportedValue;
1029
+ Property->ExportTextItem_Direct(ExportedValue, PropertyValue, nullptr, nullptr, PPF_None);
1030
+ Result->SetStringField(TEXT("value"), ExportedValue);
1031
+ Result->SetStringField(TEXT("type"), StructProp->Struct->GetName());
1032
+ }
1033
+ }
1034
+ else
1035
+ {
1036
+ // Fallback: export property value as string
1037
+ FString ExportedValue;
1038
+ Property->ExportTextItem_Direct(ExportedValue, PropertyValue, nullptr, nullptr, PPF_None);
1039
+ Result->SetStringField(TEXT("value"), ExportedValue);
1040
+ Result->SetStringField(TEXT("type"), Property->GetCPPType());
1041
+ }
1042
+
1043
+ Result->SetStringField(TEXT("actorPath"), ActorPath);
1044
+ Result->SetStringField(TEXT("propertyName"), PropertyName);
1045
+ Result->SetBoolField(TEXT("success"), true);
1046
+
1047
+ return MakeShared<FJsonValueObject>(Result);
1048
+ }
1049
+
1050
+ TSharedPtr<FJsonValue> FEditorHandlers::BuildLighting(const TSharedPtr<FJsonObject>& Params)
1051
+ {
1052
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1053
+
1054
+ if (!GEditor || !GEditor->GetEditorWorldContext().World())
1055
+ {
1056
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
1057
+ Result->SetBoolField(TEXT("success"), false);
1058
+ return MakeShared<FJsonValueObject>(Result);
1059
+ }
1060
+
1061
+ FString Quality;
1062
+ if (!Params->TryGetStringField(TEXT("quality"), Quality))
1063
+ {
1064
+ Quality = TEXT("Preview");
1065
+ }
1066
+
1067
+ // Map quality string to console command
1068
+ FString Command;
1069
+ if (Quality == TEXT("Preview"))
1070
+ {
1071
+ Command = TEXT("BUILD LIGHTING QUALITY=Preview");
1072
+ }
1073
+ else if (Quality == TEXT("Medium"))
1074
+ {
1075
+ Command = TEXT("BUILD LIGHTING QUALITY=Medium");
1076
+ }
1077
+ else if (Quality == TEXT("High"))
1078
+ {
1079
+ Command = TEXT("BUILD LIGHTING QUALITY=High");
1080
+ }
1081
+ else if (Quality == TEXT("Production"))
1082
+ {
1083
+ Command = TEXT("BUILD LIGHTING QUALITY=Production");
1084
+ }
1085
+ else
1086
+ {
1087
+ Command = TEXT("BUILD LIGHTING QUALITY=Preview");
1088
+ }
1089
+
1090
+ UKismetSystemLibrary::ExecuteConsoleCommand(
1091
+ GEditor->GetEditorWorldContext().World(),
1092
+ Command,
1093
+ nullptr
1094
+ );
1095
+
1096
+ Result->SetStringField(TEXT("quality"), Quality);
1097
+ Result->SetStringField(TEXT("command"), Command);
1098
+ Result->SetStringField(TEXT("message"), FString::Printf(TEXT("Lighting build triggered (%s)"), *Quality));
1099
+ Result->SetBoolField(TEXT("success"), true);
1100
+
1101
+ return MakeShared<FJsonValueObject>(Result);
1102
+ }
1103
+
1104
+ TSharedPtr<FJsonValue> FEditorHandlers::BuildAll(const TSharedPtr<FJsonObject>& Params)
1105
+ {
1106
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1107
+
1108
+ if (!GEditor || !GEditor->GetEditorWorldContext().World())
1109
+ {
1110
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
1111
+ Result->SetBoolField(TEXT("success"), false);
1112
+ return MakeShared<FJsonValueObject>(Result);
1113
+ }
1114
+
1115
+ UWorld* World = GEditor->GetEditorWorldContext().World();
1116
+
1117
+ // Execute full build: geometry, lighting, and paths
1118
+ UKismetSystemLibrary::ExecuteConsoleCommand(World, TEXT("MAP REBUILD"), nullptr);
1119
+ UKismetSystemLibrary::ExecuteConsoleCommand(World, TEXT("BUILD LIGHTING"), nullptr);
1120
+ UKismetSystemLibrary::ExecuteConsoleCommand(World, TEXT("RebuildNavigation"), nullptr);
1121
+
1122
+ Result->SetStringField(TEXT("message"), TEXT("Build All triggered (geometry + lighting + navigation)"));
1123
+ Result->SetBoolField(TEXT("success"), true);
1124
+
1125
+ return MakeShared<FJsonValueObject>(Result);
1126
+ }
1127
+
1128
+ TSharedPtr<FJsonValue> FEditorHandlers::ValidateAssets(const TSharedPtr<FJsonObject>& Params)
1129
+ {
1130
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1131
+
1132
+ FString Directory;
1133
+ if (!Params->TryGetStringField(TEXT("directory"), Directory))
1134
+ {
1135
+ Directory = TEXT("/Game/");
1136
+ }
1137
+
1138
+ // Try to use the EditorValidatorSubsystem if available
1139
+ UEditorValidatorSubsystem* ValidatorSubsystem = GEditor ? GEditor->GetEditorSubsystem<UEditorValidatorSubsystem>() : nullptr;
1140
+
1141
+ if (ValidatorSubsystem)
1142
+ {
1143
+ // Use the DataValidation console command for broad validation
1144
+ if (GEditor && GEditor->GetEditorWorldContext().World())
1145
+ {
1146
+ FString Command = FString::Printf(TEXT("DataValidation.ValidateAssets %s"), *Directory);
1147
+ UKismetSystemLibrary::ExecuteConsoleCommand(
1148
+ GEditor->GetEditorWorldContext().World(),
1149
+ Command,
1150
+ nullptr
1151
+ );
1152
+ }
1153
+
1154
+ Result->SetStringField(TEXT("directory"), Directory);
1155
+ Result->SetStringField(TEXT("message"), TEXT("Asset validation triggered via EditorValidatorSubsystem"));
1156
+ Result->SetBoolField(TEXT("success"), true);
1157
+ }
1158
+ else
1159
+ {
1160
+ // Fallback: trigger via console command
1161
+ if (GEditor && GEditor->GetEditorWorldContext().World())
1162
+ {
1163
+ UKismetSystemLibrary::ExecuteConsoleCommand(
1164
+ GEditor->GetEditorWorldContext().World(),
1165
+ TEXT("DataValidation.ValidateAssets"),
1166
+ nullptr
1167
+ );
1168
+ }
1169
+
1170
+ Result->SetStringField(TEXT("directory"), Directory);
1171
+ Result->SetStringField(TEXT("message"), TEXT("Asset validation triggered via console command"));
1172
+ Result->SetBoolField(TEXT("success"), true);
1173
+ }
1174
+
1175
+ return MakeShared<FJsonValueObject>(Result);
1176
+ }
1177
+
1178
+ TSharedPtr<FJsonValue> FEditorHandlers::CookContent(const TSharedPtr<FJsonObject>& Params)
1179
+ {
1180
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1181
+
1182
+ if (!GEditor || !GEditor->GetEditorWorldContext().World())
1183
+ {
1184
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
1185
+ Result->SetBoolField(TEXT("success"), false);
1186
+ return MakeShared<FJsonValueObject>(Result);
1187
+ }
1188
+
1189
+ FString Platform;
1190
+ if (!Params->TryGetStringField(TEXT("platform"), Platform))
1191
+ {
1192
+ Platform = TEXT("Windows");
1193
+ }
1194
+
1195
+ FString Command = FString::Printf(TEXT("CookOnTheFly -TargetPlatform=%s"), *Platform);
1196
+ UKismetSystemLibrary::ExecuteConsoleCommand(
1197
+ GEditor->GetEditorWorldContext().World(),
1198
+ Command,
1199
+ nullptr
1200
+ );
1201
+
1202
+ Result->SetStringField(TEXT("platform"), Platform);
1203
+ Result->SetStringField(TEXT("command"), Command);
1204
+ Result->SetStringField(TEXT("message"), FString::Printf(TEXT("Cook triggered for %s"), *Platform));
1205
+ Result->SetBoolField(TEXT("success"), true);
1206
+
1207
+ return MakeShared<FJsonValueObject>(Result);
1208
+ }
1209
+
1210
+ TSharedPtr<FJsonValue> FEditorHandlers::FocusViewportOnActor(const TSharedPtr<FJsonObject>& Params)
1211
+ {
1212
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1213
+
1214
+ FString ActorLabel;
1215
+ if (!Params->TryGetStringField(TEXT("actorLabel"), ActorLabel))
1216
+ {
1217
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'actorLabel' parameter"));
1218
+ Result->SetBoolField(TEXT("success"), false);
1219
+ return MakeShared<FJsonValueObject>(Result);
1220
+ }
1221
+
1222
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1223
+ if (!World)
1224
+ {
1225
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
1226
+ Result->SetBoolField(TEXT("success"), false);
1227
+ return MakeShared<FJsonValueObject>(Result);
1228
+ }
1229
+
1230
+ // Find the actor by label
1231
+ AActor* TargetActor = nullptr;
1232
+ for (TActorIterator<AActor> It(World); It; ++It)
1233
+ {
1234
+ if ((*It)->GetActorLabel() == ActorLabel)
1235
+ {
1236
+ TargetActor = *It;
1237
+ break;
1238
+ }
1239
+ }
1240
+
1241
+ if (!TargetActor)
1242
+ {
1243
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Actor '%s' not found"), *ActorLabel));
1244
+ Result->SetBoolField(TEXT("success"), false);
1245
+ return MakeShared<FJsonValueObject>(Result);
1246
+ }
1247
+
1248
+ // Get the viewport client
1249
+ FLevelEditorViewportClient* ViewportClient = GCurrentLevelEditingViewportClient;
1250
+ if (!ViewportClient)
1251
+ {
1252
+ const TArray<FLevelEditorViewportClient*>& ViewportClients = GEditor->GetLevelViewportClients();
1253
+ if (ViewportClients.Num() > 0)
1254
+ {
1255
+ ViewportClient = ViewportClients[0];
1256
+ }
1257
+ }
1258
+
1259
+ if (!ViewportClient)
1260
+ {
1261
+ Result->SetStringField(TEXT("error"), TEXT("No viewport client available"));
1262
+ Result->SetBoolField(TEXT("success"), false);
1263
+ return MakeShared<FJsonValueObject>(Result);
1264
+ }
1265
+
1266
+ // Focus on the actor's bounding box
1267
+ FBox ActorBounds = TargetActor->GetComponentsBoundingBox(true);
1268
+ if (ActorBounds.IsValid)
1269
+ {
1270
+ ViewportClient->FocusViewportOnBox(ActorBounds);
1271
+ }
1272
+ else
1273
+ {
1274
+ // Fallback: just move the camera to the actor's location
1275
+ FVector ActorLocation = TargetActor->GetActorLocation();
1276
+ FVector CameraOffset(0.0, -500.0, 200.0);
1277
+ ViewportClient->SetViewLocation(ActorLocation + CameraOffset);
1278
+ FRotator LookAt = (ActorLocation - (ActorLocation + CameraOffset)).Rotation();
1279
+ ViewportClient->SetViewRotation(LookAt);
1280
+ }
1281
+
1282
+ FVector FinalLocation = ViewportClient->GetViewLocation();
1283
+ TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
1284
+ LocObj->SetNumberField(TEXT("x"), FinalLocation.X);
1285
+ LocObj->SetNumberField(TEXT("y"), FinalLocation.Y);
1286
+ LocObj->SetNumberField(TEXT("z"), FinalLocation.Z);
1287
+ Result->SetObjectField(TEXT("viewLocation"), LocObj);
1288
+ Result->SetStringField(TEXT("actorLabel"), ActorLabel);
1289
+ Result->SetBoolField(TEXT("success"), true);
1290
+
1291
+ return MakeShared<FJsonValueObject>(Result);
1292
+ }
1293
+
1294
+ TSharedPtr<FJsonValue> FEditorHandlers::HotReload(const TSharedPtr<FJsonObject>& Params)
1295
+ {
1296
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1297
+
1298
+ ILiveCodingModule* LiveCoding = FModuleManager::GetModulePtr<ILiveCodingModule>(LIVE_CODING_MODULE_NAME);
1299
+ if (LiveCoding && LiveCoding->IsEnabledForSession())
1300
+ {
1301
+ if (LiveCoding->IsCompiling())
1302
+ {
1303
+ Result->SetStringField(TEXT("message"), TEXT("Live Coding compile already in progress"));
1304
+ Result->SetBoolField(TEXT("success"), true);
1305
+ return MakeShared<FJsonValueObject>(Result);
1306
+ }
1307
+
1308
+ LiveCoding->EnableByDefault(true);
1309
+ LiveCoding->Compile();
1310
+ Result->SetStringField(TEXT("message"), TEXT("Live Coding compile triggered"));
1311
+ Result->SetBoolField(TEXT("success"), true);
1312
+ }
1313
+ else
1314
+ {
1315
+ // Live Coding not available - fall back to console command
1316
+ if (GEditor && GEditor->GetEditorWorldContext().World())
1317
+ {
1318
+ UKismetSystemLibrary::ExecuteConsoleCommand(
1319
+ GEditor->GetEditorWorldContext().World(),
1320
+ TEXT("LiveCoding.Compile"),
1321
+ nullptr
1322
+ );
1323
+ Result->SetStringField(TEXT("message"), TEXT("Hot reload triggered via console command (Live Coding module not active in session)"));
1324
+ Result->SetBoolField(TEXT("success"), true);
1325
+ }
1326
+ else
1327
+ {
1328
+ Result->SetStringField(TEXT("error"), TEXT("Neither Live Coding module nor editor world available for hot reload"));
1329
+ Result->SetBoolField(TEXT("success"), false);
1330
+ }
1331
+ }
1332
+
1333
+ return MakeShared<FJsonValueObject>(Result);
1334
+ }
1335
+
1336
+ TSharedPtr<FJsonValue> FEditorHandlers::CreateNewLevel(const TSharedPtr<FJsonObject>& Params)
1337
+ {
1338
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1339
+
1340
+ FString LevelPath;
1341
+ if (!Params->TryGetStringField(TEXT("levelPath"), LevelPath))
1342
+ {
1343
+ LevelPath = TEXT("");
1344
+ }
1345
+
1346
+ FString TemplateLevel;
1347
+ Params->TryGetStringField(TEXT("templateLevel"), TemplateLevel);
1348
+
1349
+ ULevelEditorSubsystem* LevelEditorSubsystem = GEditor ? GEditor->GetEditorSubsystem<ULevelEditorSubsystem>() : nullptr;
1350
+ if (!LevelEditorSubsystem)
1351
+ {
1352
+ Result->SetStringField(TEXT("error"), TEXT("LevelEditorSubsystem not available"));
1353
+ Result->SetBoolField(TEXT("success"), false);
1354
+ return MakeShared<FJsonValueObject>(Result);
1355
+ }
1356
+
1357
+ bool bSuccess = false;
1358
+ if (TemplateLevel.IsEmpty())
1359
+ {
1360
+ bSuccess = LevelEditorSubsystem->NewLevel(LevelPath);
1361
+ }
1362
+ else
1363
+ {
1364
+ bSuccess = LevelEditorSubsystem->NewLevelFromTemplate(LevelPath, TemplateLevel);
1365
+ }
1366
+
1367
+ if (!bSuccess)
1368
+ {
1369
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to create new level at: %s"), *LevelPath));
1370
+ Result->SetBoolField(TEXT("success"), false);
1371
+ return MakeShared<FJsonValueObject>(Result);
1372
+ }
1373
+
1374
+ // Get info about the new world
1375
+ UWorld* World = GEditor->GetEditorWorldContext().World();
1376
+ if (World)
1377
+ {
1378
+ Result->SetStringField(TEXT("worldName"), World->GetName());
1379
+ Result->SetStringField(TEXT("worldPath"), World->GetPathName());
1380
+ }
1381
+
1382
+ Result->SetStringField(TEXT("levelPath"), LevelPath);
1383
+ Result->SetStringField(TEXT("message"), TEXT("New level created"));
1384
+ Result->SetBoolField(TEXT("success"), true);
1385
+
1386
+ return MakeShared<FJsonValueObject>(Result);
1387
+ }
1388
+
1389
+ TSharedPtr<FJsonValue> FEditorHandlers::SaveCurrentLevel(const TSharedPtr<FJsonObject>& Params)
1390
+ {
1391
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1392
+
1393
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1394
+ if (!World)
1395
+ {
1396
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
1397
+ Result->SetBoolField(TEXT("success"), false);
1398
+ return MakeShared<FJsonValueObject>(Result);
1399
+ }
1400
+
1401
+ ULevelEditorSubsystem* LevelEditorSubsystem = GEditor->GetEditorSubsystem<ULevelEditorSubsystem>();
1402
+ if (!LevelEditorSubsystem)
1403
+ {
1404
+ Result->SetStringField(TEXT("error"), TEXT("LevelEditorSubsystem not available"));
1405
+ Result->SetBoolField(TEXT("success"), false);
1406
+ return MakeShared<FJsonValueObject>(Result);
1407
+ }
1408
+
1409
+ bool bSuccess = LevelEditorSubsystem->SaveCurrentLevel();
1410
+
1411
+ Result->SetStringField(TEXT("levelName"), World->GetName());
1412
+ Result->SetStringField(TEXT("levelPath"), World->GetPathName());
1413
+ Result->SetBoolField(TEXT("success"), bSuccess);
1414
+
1415
+ if (!bSuccess)
1416
+ {
1417
+ Result->SetStringField(TEXT("error"), TEXT("Failed to save current level"));
1418
+ }
1419
+ else
1420
+ {
1421
+ Result->SetStringField(TEXT("message"), TEXT("Current level saved"));
1422
+ }
1423
+
1424
+ return MakeShared<FJsonValueObject>(Result);
1425
+ }
1426
+
1427
+ TSharedPtr<FJsonValue> FEditorHandlers::OpenAsset(const TSharedPtr<FJsonObject>& Params)
1428
+ {
1429
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1430
+
1431
+ FString AssetPath;
1432
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
1433
+ {
1434
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'assetPath' parameter"));
1435
+ Result->SetBoolField(TEXT("success"), false);
1436
+ return MakeShared<FJsonValueObject>(Result);
1437
+ }
1438
+
1439
+ UObject* Asset = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetPath);
1440
+ if (!Asset)
1441
+ {
1442
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load asset at '%s'"), *AssetPath));
1443
+ Result->SetBoolField(TEXT("success"), false);
1444
+ return MakeShared<FJsonValueObject>(Result);
1445
+ }
1446
+
1447
+ GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Asset);
1448
+
1449
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
1450
+ Result->SetStringField(TEXT("assetClass"), Asset->GetClass()->GetName());
1451
+ Result->SetBoolField(TEXT("success"), true);
1452
+
1453
+ return MakeShared<FJsonValueObject>(Result);
1454
+ }
1455
+
1456
+ TSharedPtr<FJsonValue> FEditorHandlers::RunStatCommand(const TSharedPtr<FJsonObject>& Params)
1457
+ {
1458
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1459
+ FString Command = TEXT("stat fps");
1460
+ Params->TryGetStringField(TEXT("command"), Command);
1461
+
1462
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1463
+ if (!World)
1464
+ {
1465
+ Result->SetStringField(TEXT("error"), TEXT("No editor world"));
1466
+ return MakeShared<FJsonValueObject>(Result);
1467
+ }
1468
+
1469
+ GEditor->Exec(World, *Command);
1470
+ Result->SetStringField(TEXT("command"), Command);
1471
+ Result->SetBoolField(TEXT("success"), true);
1472
+ return MakeShared<FJsonValueObject>(Result);
1473
+ }
1474
+
1475
+ TSharedPtr<FJsonValue> FEditorHandlers::SetScalability(const TSharedPtr<FJsonObject>& Params)
1476
+ {
1477
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1478
+ FString Level = TEXT("Epic");
1479
+ Params->TryGetStringField(TEXT("level"), Level);
1480
+
1481
+ int32 Idx = 3; // Default to Epic
1482
+ if (Level == TEXT("Low")) Idx = 0;
1483
+ else if (Level == TEXT("Medium")) Idx = 1;
1484
+ else if (Level == TEXT("High")) Idx = 2;
1485
+ else if (Level == TEXT("Epic")) Idx = 3;
1486
+ else if (Level == TEXT("Cinematic")) Idx = 4;
1487
+
1488
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1489
+ if (!World)
1490
+ {
1491
+ Result->SetStringField(TEXT("error"), TEXT("No editor world"));
1492
+ return MakeShared<FJsonValueObject>(Result);
1493
+ }
1494
+
1495
+ TArray<FString> Commands = {
1496
+ FString::Printf(TEXT("sg.ViewDistanceQuality %d"), Idx),
1497
+ FString::Printf(TEXT("sg.AntiAliasingQuality %d"), Idx),
1498
+ FString::Printf(TEXT("sg.ShadowQuality %d"), Idx),
1499
+ FString::Printf(TEXT("sg.GlobalIlluminationQuality %d"), Idx),
1500
+ FString::Printf(TEXT("sg.ReflectionQuality %d"), Idx),
1501
+ FString::Printf(TEXT("sg.PostProcessQuality %d"), Idx),
1502
+ FString::Printf(TEXT("sg.TextureQuality %d"), Idx),
1503
+ FString::Printf(TEXT("sg.EffectsQuality %d"), Idx),
1504
+ FString::Printf(TEXT("sg.FoliageQuality %d"), Idx),
1505
+ FString::Printf(TEXT("sg.ShadingQuality %d"), Idx),
1506
+ };
1507
+
1508
+ for (const FString& Cmd : Commands)
1509
+ {
1510
+ GEditor->Exec(World, *Cmd);
1511
+ }
1512
+
1513
+ Result->SetStringField(TEXT("level"), Level);
1514
+ Result->SetBoolField(TEXT("success"), true);
1515
+ return MakeShared<FJsonValueObject>(Result);
1516
+ }
1517
+
1518
+ TSharedPtr<FJsonValue> FEditorHandlers::BuildGeometry(const TSharedPtr<FJsonObject>& Params)
1519
+ {
1520
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1521
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1522
+ if (!World)
1523
+ {
1524
+ Result->SetStringField(TEXT("error"), TEXT("No editor world"));
1525
+ return MakeShared<FJsonValueObject>(Result);
1526
+ }
1527
+ GEditor->Exec(World, TEXT("MAP REBUILD"));
1528
+ Result->SetBoolField(TEXT("success"), true);
1529
+ Result->SetStringField(TEXT("message"), TEXT("Geometry rebuild triggered"));
1530
+ return MakeShared<FJsonValueObject>(Result);
1531
+ }
1532
+
1533
+ TSharedPtr<FJsonValue> FEditorHandlers::BuildHlod(const TSharedPtr<FJsonObject>& Params)
1534
+ {
1535
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1536
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1537
+ if (!World)
1538
+ {
1539
+ Result->SetStringField(TEXT("error"), TEXT("No editor world"));
1540
+ return MakeShared<FJsonValueObject>(Result);
1541
+ }
1542
+ GEditor->Exec(World, TEXT("BuildHLOD"));
1543
+ Result->SetBoolField(TEXT("success"), true);
1544
+ Result->SetStringField(TEXT("message"), TEXT("HLOD build triggered"));
1545
+ return MakeShared<FJsonValueObject>(Result);
1546
+ }
1547
+
1548
+ TSharedPtr<FJsonValue> FEditorHandlers::ListCrashes(const TSharedPtr<FJsonObject>& Params)
1549
+ {
1550
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1551
+ FString CrashesDir = FPaths::ProjectSavedDir() / TEXT("Crashes");
1552
+ Result->SetStringField(TEXT("crashesDir"), CrashesDir);
1553
+
1554
+ TArray<TSharedPtr<FJsonValue>> CrashArray;
1555
+ IFileManager& FileManager = IFileManager::Get();
1556
+
1557
+ TArray<FString> CrashFolders;
1558
+ FileManager.FindFiles(CrashFolders, *(CrashesDir / TEXT("*")), false, true);
1559
+
1560
+ for (const FString& Folder : CrashFolders)
1561
+ {
1562
+ TSharedPtr<FJsonObject> CrashObj = MakeShared<FJsonObject>();
1563
+ FString FullPath = CrashesDir / Folder;
1564
+ CrashObj->SetStringField(TEXT("folder"), Folder);
1565
+ CrashObj->SetStringField(TEXT("path"), FullPath);
1566
+
1567
+ FFileStatData StatData = FileManager.GetStatData(*FullPath);
1568
+ if (StatData.bIsValid)
1569
+ {
1570
+ CrashObj->SetNumberField(TEXT("modified"), StatData.ModificationTime.ToUnixTimestamp());
1571
+ }
1572
+
1573
+ TArray<FString> Files;
1574
+ FileManager.FindFiles(Files, *(FullPath / TEXT("*")), true, false);
1575
+ TArray<TSharedPtr<FJsonValue>> FileArray;
1576
+ for (const FString& File : Files)
1577
+ {
1578
+ FileArray.Add(MakeShared<FJsonValueString>(File));
1579
+ }
1580
+ CrashObj->SetArrayField(TEXT("files"), FileArray);
1581
+ CrashArray.Add(MakeShared<FJsonValueObject>(CrashObj));
1582
+ }
1583
+
1584
+ Result->SetNumberField(TEXT("crashCount"), CrashArray.Num());
1585
+ Result->SetArrayField(TEXT("crashes"), CrashArray);
1586
+ return MakeShared<FJsonValueObject>(Result);
1587
+ }
1588
+
1589
+ TSharedPtr<FJsonValue> FEditorHandlers::GetCrashInfo(const TSharedPtr<FJsonObject>& Params)
1590
+ {
1591
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1592
+ FString CrashFolder;
1593
+ if (!Params->TryGetStringField(TEXT("crashFolder"), CrashFolder))
1594
+ {
1595
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'crashFolder' parameter"));
1596
+ return MakeShared<FJsonValueObject>(Result);
1597
+ }
1598
+
1599
+ FString CrashPath = FPaths::ProjectSavedDir() / TEXT("Crashes") / CrashFolder;
1600
+ if (!IFileManager::Get().DirectoryExists(*CrashPath))
1601
+ {
1602
+ Result->SetBoolField(TEXT("available"), false);
1603
+ Result->SetStringField(TEXT("note"), FString::Printf(TEXT("Crash folder not found: %s"), *CrashFolder));
1604
+ return MakeShared<FJsonValueObject>(Result);
1605
+ }
1606
+
1607
+ Result->SetStringField(TEXT("folder"), CrashFolder);
1608
+ Result->SetStringField(TEXT("path"), CrashPath);
1609
+
1610
+ TSharedPtr<FJsonObject> FilesObj = MakeShared<FJsonObject>();
1611
+ TArray<FString> Files;
1612
+ IFileManager::Get().FindFiles(Files, *(CrashPath / TEXT("*")), true, false);
1613
+
1614
+ for (const FString& File : Files)
1615
+ {
1616
+ TSharedPtr<FJsonObject> FileInfo = MakeShared<FJsonObject>();
1617
+ FString FilePath = CrashPath / File;
1618
+ FFileStatData StatData = IFileManager::Get().GetStatData(*FilePath);
1619
+ if (StatData.bIsValid)
1620
+ {
1621
+ FileInfo->SetNumberField(TEXT("size"), StatData.FileSize);
1622
+ FileInfo->SetNumberField(TEXT("modified"), StatData.ModificationTime.ToUnixTimestamp());
1623
+ }
1624
+
1625
+ // Read text files
1626
+ if (File.EndsWith(TEXT(".log")) || File.EndsWith(TEXT(".txt")) || File.EndsWith(TEXT(".xml")) || File.EndsWith(TEXT(".json")))
1627
+ {
1628
+ FString Content;
1629
+ if (FFileHelper::LoadFileToString(Content, *FilePath))
1630
+ {
1631
+ // Limit content to 50KB
1632
+ if (Content.Len() > 50000)
1633
+ {
1634
+ Content = Content.Left(50000) + TEXT("\n... [truncated]");
1635
+ }
1636
+ FileInfo->SetStringField(TEXT("content"), Content);
1637
+ }
1638
+ }
1639
+ FilesObj->SetObjectField(File, FileInfo);
1640
+ }
1641
+
1642
+ Result->SetObjectField(TEXT("files"), FilesObj);
1643
+ return MakeShared<FJsonValueObject>(Result);
1644
+ }
1645
+
1646
+ TSharedPtr<FJsonValue> FEditorHandlers::CheckForCrashes(const TSharedPtr<FJsonObject>& Params)
1647
+ {
1648
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1649
+ FString CrashesDir = FPaths::ProjectSavedDir() / TEXT("Crashes");
1650
+
1651
+ TArray<TSharedPtr<FJsonValue>> RecentCrashes;
1652
+ IFileManager& FileManager = IFileManager::Get();
1653
+ FDateTime Now = FDateTime::UtcNow();
1654
+ FDateTime Threshold = Now - FTimespan::FromHours(24);
1655
+
1656
+ TArray<FString> CrashFolders;
1657
+ FileManager.FindFiles(CrashFolders, *(CrashesDir / TEXT("*")), false, true);
1658
+
1659
+ for (const FString& Folder : CrashFolders)
1660
+ {
1661
+ FString FullPath = CrashesDir / Folder;
1662
+ FFileStatData StatData = FileManager.GetStatData(*FullPath);
1663
+ if (StatData.bIsValid && StatData.ModificationTime > Threshold)
1664
+ {
1665
+ TSharedPtr<FJsonObject> CrashObj = MakeShared<FJsonObject>();
1666
+ CrashObj->SetStringField(TEXT("folder"), Folder);
1667
+ CrashObj->SetStringField(TEXT("path"), FullPath);
1668
+ CrashObj->SetNumberField(TEXT("timestamp"), StatData.ModificationTime.ToUnixTimestamp());
1669
+ RecentCrashes.Add(MakeShared<FJsonValueObject>(CrashObj));
1670
+ }
1671
+ }
1672
+
1673
+ Result->SetNumberField(TEXT("recentCrashCount"), RecentCrashes.Num());
1674
+ Result->SetArrayField(TEXT("recentCrashes"), RecentCrashes);
1675
+ return MakeShared<FJsonValueObject>(Result);
1676
+ }