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.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/bridge.d.ts +25 -0
- package/dist/bridge.js +124 -0
- package/dist/bridge.js.map +1 -0
- package/dist/deployer.d.ts +18 -0
- package/dist/deployer.js +175 -0
- package/dist/deployer.js.map +1 -0
- package/dist/editor-control.d.ts +15 -0
- package/dist/editor-control.js +181 -0
- package/dist/editor-control.js.map +1 -0
- package/dist/github-app.d.ts +4 -0
- package/dist/github-app.js +94 -0
- package/dist/github-app.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +290 -0
- package/dist/init.js.map +1 -0
- package/dist/instructions.d.ts +1 -0
- package/dist/instructions.js +150 -0
- package/dist/instructions.js.map +1 -0
- package/dist/project.d.ts +32 -0
- package/dist/project.js +175 -0
- package/dist/project.js.map +1 -0
- package/dist/tools/animation.d.ts +2 -0
- package/dist/tools/animation.js +62 -0
- package/dist/tools/animation.js.map +1 -0
- package/dist/tools/asset.d.ts +2 -0
- package/dist/tools/asset.js +128 -0
- package/dist/tools/asset.js.map +1 -0
- package/dist/tools/audio.d.ts +2 -0
- package/dist/tools/audio.js +24 -0
- package/dist/tools/audio.js.map +1 -0
- package/dist/tools/blueprint.d.ts +2 -0
- package/dist/tools/blueprint.js +64 -0
- package/dist/tools/blueprint.js.map +1 -0
- package/dist/tools/demo.d.ts +2 -0
- package/dist/tools/demo.js +10 -0
- package/dist/tools/demo.js.map +1 -0
- package/dist/tools/editor.d.ts +2 -0
- package/dist/tools/editor.js +133 -0
- package/dist/tools/editor.js.map +1 -0
- package/dist/tools/feedback.d.ts +2 -0
- package/dist/tools/feedback.js +44 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/foliage.d.ts +2 -0
- package/dist/tools/foliage.js +29 -0
- package/dist/tools/foliage.js.map +1 -0
- package/dist/tools/gameplay.d.ts +2 -0
- package/dist/tools/gameplay.js +101 -0
- package/dist/tools/gameplay.js.map +1 -0
- package/dist/tools/gas.d.ts +2 -0
- package/dist/tools/gas.js +43 -0
- package/dist/tools/gas.js.map +1 -0
- package/dist/tools/landscape.d.ts +2 -0
- package/dist/tools/landscape.js +32 -0
- package/dist/tools/landscape.js.map +1 -0
- package/dist/tools/level.d.ts +2 -0
- package/dist/tools/level.js +66 -0
- package/dist/tools/level.js.map +1 -0
- package/dist/tools/material.d.ts +2 -0
- package/dist/tools/material.js +59 -0
- package/dist/tools/material.js.map +1 -0
- package/dist/tools/networking.d.ts +2 -0
- package/dist/tools/networking.js +42 -0
- package/dist/tools/networking.js.map +1 -0
- package/dist/tools/niagara.d.ts +2 -0
- package/dist/tools/niagara.js +42 -0
- package/dist/tools/niagara.js.map +1 -0
- package/dist/tools/pcg.d.ts +2 -0
- package/dist/tools/pcg.js +39 -0
- package/dist/tools/pcg.js.map +1 -0
- package/dist/tools/project.d.ts +2 -0
- package/dist/tools/project.js +282 -0
- package/dist/tools/project.js.map +1 -0
- package/dist/tools/reflection.d.ts +2 -0
- package/dist/tools/reflection.js +26 -0
- package/dist/tools/reflection.js.map +1 -0
- package/dist/tools/widget.d.ts +2 -0
- package/dist/tools/widget.js +34 -0
- package/dist/tools/widget.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +38 -0
- package/dist/types.js.map +1 -0
- package/package.json +76 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
- 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
|
+
}
|