unreal-engine-mcp-server 0.5.4 → 0.5.5

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 (468) hide show
  1. package/dist/automation/bridge.d.ts.map +1 -0
  2. package/dist/automation/bridge.js.map +1 -0
  3. package/dist/automation/connection-manager.d.ts.map +1 -0
  4. package/dist/automation/connection-manager.js.map +1 -0
  5. package/dist/automation/handshake.d.ts.map +1 -0
  6. package/dist/automation/handshake.js.map +1 -0
  7. package/dist/automation/index.d.ts.map +1 -0
  8. package/dist/automation/index.js.map +1 -0
  9. package/dist/automation/message-handler.d.ts.map +1 -0
  10. package/dist/automation/message-handler.js.map +1 -0
  11. package/dist/automation/request-tracker.d.ts.map +1 -0
  12. package/dist/automation/request-tracker.js.map +1 -0
  13. package/dist/automation/types.d.ts.map +1 -0
  14. package/dist/automation/types.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +4 -3
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config/class-aliases.d.ts.map +1 -0
  19. package/dist/config/class-aliases.js.map +1 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js.map +1 -0
  24. package/dist/graphql/loaders.d.ts.map +1 -0
  25. package/dist/graphql/loaders.js.map +1 -0
  26. package/dist/graphql/resolvers.d.ts.map +1 -0
  27. package/dist/graphql/resolvers.js +29 -29
  28. package/dist/graphql/resolvers.js.map +1 -0
  29. package/dist/graphql/schema.d.ts.map +1 -0
  30. package/dist/graphql/schema.js.map +1 -0
  31. package/dist/graphql/server.d.ts.map +1 -0
  32. package/dist/graphql/server.js.map +1 -0
  33. package/dist/graphql/types.d.ts.map +1 -0
  34. package/dist/graphql/types.js.map +1 -0
  35. package/dist/handlers/resource-handlers.d.ts.map +1 -0
  36. package/dist/handlers/resource-handlers.js.map +1 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +64 -7
  40. package/dist/index.js.map +1 -0
  41. package/dist/resources/actors.d.ts.map +1 -0
  42. package/dist/resources/actors.js.map +1 -0
  43. package/dist/resources/assets.d.ts.map +1 -0
  44. package/dist/resources/assets.js +6 -4
  45. package/dist/resources/assets.js.map +1 -0
  46. package/dist/resources/levels.d.ts.map +1 -0
  47. package/dist/resources/levels.js.map +1 -0
  48. package/dist/server/resource-registry.d.ts.map +1 -0
  49. package/dist/server/resource-registry.js.map +1 -0
  50. package/dist/server/tool-registry.d.ts.map +1 -0
  51. package/dist/server/tool-registry.js.map +1 -0
  52. package/dist/server-setup.d.ts.map +1 -0
  53. package/dist/server-setup.js.map +1 -0
  54. package/dist/services/health-monitor.d.ts.map +1 -0
  55. package/dist/services/health-monitor.js.map +1 -0
  56. package/dist/services/metrics-server.d.ts.map +1 -0
  57. package/dist/services/metrics-server.js.map +1 -0
  58. package/dist/tools/actors.d.ts.map +1 -0
  59. package/dist/tools/actors.js +3 -1
  60. package/dist/tools/actors.js.map +1 -0
  61. package/dist/tools/animation.d.ts.map +1 -0
  62. package/dist/tools/animation.js +2 -2
  63. package/dist/tools/animation.js.map +1 -0
  64. package/dist/tools/assets.d.ts.map +1 -0
  65. package/dist/tools/assets.js.map +1 -0
  66. package/dist/tools/audio.d.ts.map +1 -0
  67. package/dist/tools/audio.js.map +1 -0
  68. package/dist/tools/base-tool.d.ts.map +1 -0
  69. package/dist/tools/base-tool.js.map +1 -0
  70. package/dist/tools/behavior-tree.d.ts.map +1 -0
  71. package/dist/tools/behavior-tree.js.map +1 -0
  72. package/dist/tools/blueprint.d.ts.map +1 -0
  73. package/dist/tools/blueprint.js +4 -2
  74. package/dist/tools/blueprint.js.map +1 -0
  75. package/dist/tools/consolidated-tool-definitions.d.ts.map +1 -0
  76. package/dist/tools/consolidated-tool-definitions.js.map +1 -0
  77. package/dist/tools/consolidated-tool-handlers.d.ts.map +1 -0
  78. package/dist/tools/consolidated-tool-handlers.js.map +1 -0
  79. package/dist/tools/debug.d.ts.map +1 -0
  80. package/dist/tools/debug.js +3 -1
  81. package/dist/tools/debug.js.map +1 -0
  82. package/dist/tools/dynamic-handler-registry.d.ts.map +1 -0
  83. package/dist/tools/dynamic-handler-registry.js +3 -1
  84. package/dist/tools/dynamic-handler-registry.js.map +1 -0
  85. package/dist/tools/editor.d.ts.map +1 -0
  86. package/dist/tools/editor.js +1 -1
  87. package/dist/tools/editor.js.map +1 -0
  88. package/dist/tools/engine.d.ts.map +1 -0
  89. package/dist/tools/engine.js.map +1 -0
  90. package/dist/tools/environment.d.ts.map +1 -0
  91. package/dist/tools/environment.js +2 -2
  92. package/dist/tools/environment.js.map +1 -0
  93. package/dist/tools/foliage.d.ts.map +1 -0
  94. package/dist/tools/foliage.js.map +1 -0
  95. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  96. package/dist/tools/handlers/actor-handlers.d.ts.map +1 -0
  97. package/dist/tools/handlers/actor-handlers.js +6 -5
  98. package/dist/tools/handlers/actor-handlers.js.map +1 -0
  99. package/dist/tools/handlers/animation-handlers.d.ts.map +1 -0
  100. package/dist/tools/handlers/animation-handlers.js.map +1 -0
  101. package/dist/tools/handlers/argument-helper.d.ts.map +1 -0
  102. package/dist/tools/handlers/argument-helper.js.map +1 -0
  103. package/dist/tools/handlers/asset-handlers.d.ts.map +1 -0
  104. package/dist/tools/handlers/asset-handlers.js +5 -1
  105. package/dist/tools/handlers/asset-handlers.js.map +1 -0
  106. package/dist/tools/handlers/audio-handlers.d.ts.map +1 -0
  107. package/dist/tools/handlers/audio-handlers.js.map +1 -0
  108. package/dist/tools/handlers/blueprint-handlers.d.ts.map +1 -0
  109. package/dist/tools/handlers/blueprint-handlers.js +2 -1
  110. package/dist/tools/handlers/blueprint-handlers.js.map +1 -0
  111. package/dist/tools/handlers/common-handlers.d.ts.map +1 -0
  112. package/dist/tools/handlers/common-handlers.js.map +1 -0
  113. package/dist/tools/handlers/editor-handlers.d.ts.map +1 -0
  114. package/dist/tools/handlers/editor-handlers.js +12 -2
  115. package/dist/tools/handlers/editor-handlers.js.map +1 -0
  116. package/dist/tools/handlers/effect-handlers.d.ts.map +1 -0
  117. package/dist/tools/handlers/effect-handlers.js.map +1 -0
  118. package/dist/tools/handlers/environment-handlers.d.ts.map +1 -0
  119. package/dist/tools/handlers/environment-handlers.js.map +1 -0
  120. package/dist/tools/handlers/graph-handlers.d.ts.map +1 -0
  121. package/dist/tools/handlers/graph-handlers.js +61 -1
  122. package/dist/tools/handlers/graph-handlers.js.map +1 -0
  123. package/dist/tools/handlers/input-handlers.d.ts.map +1 -0
  124. package/dist/tools/handlers/input-handlers.js.map +1 -0
  125. package/dist/tools/handlers/inspect-handlers.d.ts.map +1 -0
  126. package/dist/tools/handlers/inspect-handlers.js.map +1 -0
  127. package/dist/tools/handlers/level-handlers.d.ts.map +1 -0
  128. package/dist/tools/handlers/level-handlers.js.map +1 -0
  129. package/dist/tools/handlers/lighting-handlers.d.ts.map +1 -0
  130. package/dist/tools/handlers/lighting-handlers.js +23 -1
  131. package/dist/tools/handlers/lighting-handlers.js.map +1 -0
  132. package/dist/tools/handlers/performance-handlers.d.ts.map +1 -0
  133. package/dist/tools/handlers/performance-handlers.js +15 -2
  134. package/dist/tools/handlers/performance-handlers.js.map +1 -0
  135. package/dist/tools/handlers/pipeline-handlers.d.ts.map +1 -0
  136. package/dist/tools/handlers/pipeline-handlers.js.map +1 -0
  137. package/dist/tools/handlers/sequence-handlers.d.ts.map +1 -0
  138. package/dist/tools/handlers/sequence-handlers.js.map +1 -0
  139. package/dist/tools/handlers/system-handlers.d.ts.map +1 -0
  140. package/dist/tools/handlers/system-handlers.js +16 -1
  141. package/dist/tools/handlers/system-handlers.js.map +1 -0
  142. package/dist/tools/input.d.ts.map +1 -0
  143. package/dist/tools/input.js +3 -1
  144. package/dist/tools/input.js.map +1 -0
  145. package/dist/tools/introspection.d.ts.map +1 -0
  146. package/dist/tools/introspection.js.map +1 -0
  147. package/dist/tools/landscape.d.ts.map +1 -0
  148. package/dist/tools/landscape.js +3 -1
  149. package/dist/tools/landscape.js.map +1 -0
  150. package/dist/tools/level.d.ts.map +1 -0
  151. package/dist/tools/level.js.map +1 -0
  152. package/dist/tools/lighting.d.ts.map +1 -0
  153. package/dist/tools/lighting.js +3 -1
  154. package/dist/tools/lighting.js.map +1 -0
  155. package/dist/tools/logs.d.ts.map +1 -0
  156. package/dist/tools/logs.js.map +1 -0
  157. package/dist/tools/materials.d.ts.map +1 -0
  158. package/dist/tools/materials.js +3 -1
  159. package/dist/tools/materials.js.map +1 -0
  160. package/dist/tools/niagara.d.ts.map +1 -0
  161. package/dist/tools/niagara.js +7 -5
  162. package/dist/tools/niagara.js.map +1 -0
  163. package/dist/tools/performance.d.ts.map +1 -0
  164. package/dist/tools/performance.js.map +1 -0
  165. package/dist/tools/physics.d.ts.map +1 -0
  166. package/dist/tools/physics.js +9 -7
  167. package/dist/tools/physics.js.map +1 -0
  168. package/dist/tools/property-dictionary.d.ts.map +1 -0
  169. package/dist/tools/property-dictionary.js.map +1 -0
  170. package/dist/tools/sequence.d.ts.map +1 -0
  171. package/dist/tools/sequence.js +3 -1
  172. package/dist/tools/sequence.js.map +1 -0
  173. package/dist/tools/tool-definition-utils.d.ts.map +1 -0
  174. package/dist/tools/tool-definition-utils.js.map +1 -0
  175. package/dist/tools/ui.d.ts.map +1 -0
  176. package/dist/tools/ui.js +3 -1
  177. package/dist/tools/ui.js.map +1 -0
  178. package/dist/types/automation-responses.d.ts.map +1 -0
  179. package/dist/types/automation-responses.js.map +1 -0
  180. package/dist/types/env.d.ts.map +1 -0
  181. package/dist/types/env.js.map +1 -0
  182. package/dist/types/handler-types.d.ts.map +1 -0
  183. package/dist/types/handler-types.js.map +1 -0
  184. package/dist/types/tool-interfaces.d.ts.map +1 -0
  185. package/dist/types/tool-interfaces.js.map +1 -0
  186. package/dist/types/tool-types.d.ts.map +1 -0
  187. package/dist/types/tool-types.js.map +1 -0
  188. package/dist/unreal-bridge.d.ts +1 -0
  189. package/dist/unreal-bridge.d.ts.map +1 -0
  190. package/dist/unreal-bridge.js +8 -0
  191. package/dist/unreal-bridge.js.map +1 -0
  192. package/dist/utils/command-validator.d.ts.map +1 -0
  193. package/dist/utils/command-validator.js.map +1 -0
  194. package/dist/utils/elicitation.d.ts.map +1 -0
  195. package/dist/utils/elicitation.js.map +1 -0
  196. package/dist/utils/error-handler.d.ts.map +1 -0
  197. package/dist/utils/error-handler.js.map +1 -0
  198. package/dist/utils/ini-reader.d.ts.map +1 -0
  199. package/dist/utils/ini-reader.js.map +1 -0
  200. package/dist/utils/logger.d.ts.map +1 -0
  201. package/dist/utils/logger.js.map +1 -0
  202. package/dist/utils/normalize.d.ts.map +1 -0
  203. package/dist/utils/normalize.js.map +1 -0
  204. package/dist/utils/path-security.d.ts.map +1 -0
  205. package/dist/utils/path-security.js.map +1 -0
  206. package/dist/utils/response-factory.d.ts.map +1 -0
  207. package/dist/utils/response-factory.js +3 -1
  208. package/dist/utils/response-factory.js.map +1 -0
  209. package/dist/utils/response-validator.d.ts.map +1 -0
  210. package/dist/utils/response-validator.js.map +1 -0
  211. package/dist/utils/result-helpers.d.ts.map +1 -0
  212. package/dist/utils/result-helpers.js.map +1 -0
  213. package/dist/utils/safe-json.d.ts.map +1 -0
  214. package/dist/utils/safe-json.js.map +1 -0
  215. package/dist/utils/unreal-command-queue.d.ts.map +1 -0
  216. package/dist/utils/unreal-command-queue.js.map +1 -0
  217. package/dist/utils/validation.d.ts.map +1 -0
  218. package/dist/utils/validation.js.map +1 -0
  219. package/dist/wasm/index.d.ts.map +1 -0
  220. package/dist/wasm/index.js.map +1 -0
  221. package/package.json +12 -34
  222. package/server.json +2 -2
  223. package/.dockerignore +0 -57
  224. package/.env.example +0 -26
  225. package/.env.production +0 -61
  226. package/.eslintrc.json +0 -0
  227. package/.eslintrc.override.json +0 -8
  228. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -94
  229. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  230. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -56
  231. package/.github/copilot-instructions.md +0 -478
  232. package/.github/dependabot.yml +0 -19
  233. package/.github/labeler.yml +0 -24
  234. package/.github/labels.yml +0 -70
  235. package/.github/pull_request_template.md +0 -42
  236. package/.github/release-drafter-config.yml +0 -51
  237. package/.github/workflows/auto-merge.yml +0 -38
  238. package/.github/workflows/ci.yml +0 -38
  239. package/.github/workflows/dependency-review.yml +0 -17
  240. package/.github/workflows/gemini-issue-triage.yml +0 -172
  241. package/.github/workflows/greetings.yml +0 -27
  242. package/.github/workflows/labeler.yml +0 -17
  243. package/.github/workflows/links.yml +0 -80
  244. package/.github/workflows/pr-size-labeler.yml +0 -137
  245. package/.github/workflows/publish-mcp.yml +0 -79
  246. package/.github/workflows/release-drafter.yml +0 -24
  247. package/.github/workflows/release.yml +0 -112
  248. package/.github/workflows/semantic-pull-request.yml +0 -35
  249. package/.github/workflows/smoke-test.yml +0 -36
  250. package/.github/workflows/stale.yml +0 -28
  251. package/CONTRIBUTING.md +0 -140
  252. package/Dockerfile +0 -37
  253. package/GEMINI.md +0 -115
  254. package/Public/Plugin_setup_guide.mp4 +0 -0
  255. package/Public/icon.png +0 -0
  256. package/claude_desktop_config_example.json +0 -15
  257. package/dist/types/responses.d.ts +0 -249
  258. package/dist/types/responses.js +0 -2
  259. package/docs/GraphQL-API.md +0 -888
  260. package/docs/Migration-Guide-v0.5.0.md +0 -684
  261. package/docs/Roadmap.md +0 -53
  262. package/docs/WebAssembly-Integration.md +0 -628
  263. package/docs/editor-plugin-extension.md +0 -370
  264. package/docs/handler-mapping.md +0 -249
  265. package/docs/native-automation-progress.md +0 -128
  266. package/docs/testing-guide.md +0 -423
  267. package/eslint.config.mjs +0 -68
  268. package/mcp-config-example.json +0 -14
  269. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +0 -8
  270. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +0 -64
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +0 -189
  272. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +0 -22
  273. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +0 -30
  274. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +0 -1983
  275. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +0 -72
  276. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +0 -46
  277. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +0 -846
  278. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +0 -2393
  279. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +0 -300
  280. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +0 -2807
  281. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +0 -1087
  282. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +0 -488
  283. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +0 -643
  284. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +0 -31
  285. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +0 -1094
  286. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +0 -5750
  287. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +0 -152
  288. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +0 -2614
  289. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +0 -42
  290. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +0 -1237
  291. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +0 -1725
  292. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +0 -2265
  293. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +0 -954
  294. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +0 -209
  295. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +0 -41
  296. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +0 -1164
  297. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +0 -762
  298. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +0 -663
  299. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +0 -136
  300. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +0 -494
  301. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +0 -278
  302. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +0 -625
  303. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +0 -401
  304. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +0 -67
  305. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +0 -472
  306. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +0 -2634
  307. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +0 -189
  308. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +0 -917
  309. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +0 -39
  310. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +0 -2706
  311. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +0 -519
  312. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +0 -38
  313. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +0 -668
  314. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +0 -346
  315. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +0 -1345
  316. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +0 -149
  317. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -782
  318. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +0 -115
  319. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +0 -796
  320. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +0 -117
  321. package/scripts/check-unreal-connection.mjs +0 -19
  322. package/scripts/clean-tmp.js +0 -23
  323. package/scripts/patch-wasm.js +0 -26
  324. package/scripts/run-all-tests.mjs +0 -136
  325. package/scripts/smoke-test.ts +0 -94
  326. package/scripts/sync-mcp-plugin.js +0 -143
  327. package/scripts/test-no-plugin-alternates.mjs +0 -113
  328. package/scripts/validate-server.js +0 -46
  329. package/scripts/verify-automation-bridge.js +0 -200
  330. package/src/automation/bridge.ts +0 -630
  331. package/src/automation/connection-manager.ts +0 -148
  332. package/src/automation/handshake.ts +0 -99
  333. package/src/automation/index.ts +0 -2
  334. package/src/automation/message-handler.ts +0 -192
  335. package/src/automation/request-tracker.ts +0 -155
  336. package/src/automation/types.ts +0 -108
  337. package/src/cli.ts +0 -34
  338. package/src/config/class-aliases.ts +0 -65
  339. package/src/config.ts +0 -73
  340. package/src/constants.ts +0 -29
  341. package/src/graphql/loaders.ts +0 -244
  342. package/src/graphql/resolvers.ts +0 -1008
  343. package/src/graphql/schema.ts +0 -452
  344. package/src/graphql/server.ts +0 -156
  345. package/src/graphql/types.ts +0 -10
  346. package/src/handlers/resource-handlers.ts +0 -186
  347. package/src/index.ts +0 -243
  348. package/src/resources/actors.ts +0 -127
  349. package/src/resources/assets.ts +0 -286
  350. package/src/resources/levels.ts +0 -68
  351. package/src/server/resource-registry.ts +0 -47
  352. package/src/server/tool-registry.ts +0 -354
  353. package/src/server-setup.ts +0 -114
  354. package/src/services/health-monitor.ts +0 -132
  355. package/src/services/metrics-server.ts +0 -176
  356. package/src/tools/actors.ts +0 -564
  357. package/src/tools/animation.ts +0 -941
  358. package/src/tools/assets.ts +0 -394
  359. package/src/tools/audio.ts +0 -499
  360. package/src/tools/base-tool.ts +0 -52
  361. package/src/tools/behavior-tree.ts +0 -45
  362. package/src/tools/blueprint.ts +0 -940
  363. package/src/tools/consolidated-tool-definitions.ts +0 -1256
  364. package/src/tools/consolidated-tool-handlers.ts +0 -302
  365. package/src/tools/debug.ts +0 -622
  366. package/src/tools/dynamic-handler-registry.ts +0 -33
  367. package/src/tools/editor.ts +0 -435
  368. package/src/tools/engine.ts +0 -43
  369. package/src/tools/environment.ts +0 -281
  370. package/src/tools/foliage.ts +0 -596
  371. package/src/tools/handlers/actor-handlers.ts +0 -244
  372. package/src/tools/handlers/animation-handlers.ts +0 -237
  373. package/src/tools/handlers/argument-helper.ts +0 -142
  374. package/src/tools/handlers/asset-handlers.ts +0 -550
  375. package/src/tools/handlers/audio-handlers.ts +0 -194
  376. package/src/tools/handlers/blueprint-handlers.ts +0 -380
  377. package/src/tools/handlers/common-handlers.ts +0 -108
  378. package/src/tools/handlers/editor-handlers.ts +0 -124
  379. package/src/tools/handlers/effect-handlers.ts +0 -224
  380. package/src/tools/handlers/environment-handlers.ts +0 -183
  381. package/src/tools/handlers/graph-handlers.ts +0 -117
  382. package/src/tools/handlers/input-handlers.ts +0 -28
  383. package/src/tools/handlers/inspect-handlers.ts +0 -450
  384. package/src/tools/handlers/level-handlers.ts +0 -253
  385. package/src/tools/handlers/lighting-handlers.ts +0 -151
  386. package/src/tools/handlers/performance-handlers.ts +0 -132
  387. package/src/tools/handlers/pipeline-handlers.ts +0 -194
  388. package/src/tools/handlers/sequence-handlers.ts +0 -438
  389. package/src/tools/handlers/system-handlers.ts +0 -564
  390. package/src/tools/input.ts +0 -160
  391. package/src/tools/introspection.ts +0 -689
  392. package/src/tools/landscape.ts +0 -649
  393. package/src/tools/level.ts +0 -989
  394. package/src/tools/lighting.ts +0 -1052
  395. package/src/tools/logs.ts +0 -219
  396. package/src/tools/materials.ts +0 -295
  397. package/src/tools/niagara.ts +0 -485
  398. package/src/tools/performance.ts +0 -661
  399. package/src/tools/physics.ts +0 -679
  400. package/src/tools/property-dictionary.ts +0 -98
  401. package/src/tools/sequence.ts +0 -385
  402. package/src/tools/tool-definition-utils.ts +0 -35
  403. package/src/tools/ui.ts +0 -452
  404. package/src/types/automation-responses.ts +0 -119
  405. package/src/types/env.ts +0 -17
  406. package/src/types/handler-types.ts +0 -442
  407. package/src/types/responses.ts +0 -355
  408. package/src/types/tool-interfaces.ts +0 -250
  409. package/src/types/tool-types.ts +0 -575
  410. package/src/unreal-bridge.ts +0 -693
  411. package/src/utils/command-validator.ts +0 -139
  412. package/src/utils/elicitation.ts +0 -132
  413. package/src/utils/error-handler.ts +0 -287
  414. package/src/utils/ini-reader.ts +0 -86
  415. package/src/utils/logger.ts +0 -35
  416. package/src/utils/normalize.test.ts +0 -162
  417. package/src/utils/normalize.ts +0 -146
  418. package/src/utils/path-security.ts +0 -43
  419. package/src/utils/response-factory.ts +0 -44
  420. package/src/utils/response-validator.ts +0 -395
  421. package/src/utils/result-helpers.ts +0 -195
  422. package/src/utils/safe-json.test.ts +0 -90
  423. package/src/utils/safe-json.ts +0 -70
  424. package/src/utils/unreal-command-queue.ts +0 -166
  425. package/src/utils/validation.test.ts +0 -184
  426. package/src/utils/validation.ts +0 -312
  427. package/src/wasm/index.ts +0 -838
  428. package/test-server.mjs +0 -100
  429. package/tests/test-animation.mjs +0 -369
  430. package/tests/test-asset-advanced.mjs +0 -82
  431. package/tests/test-asset-graph.mjs +0 -311
  432. package/tests/test-audio.mjs +0 -417
  433. package/tests/test-automation-timeouts.mjs +0 -98
  434. package/tests/test-behavior-tree.mjs +0 -444
  435. package/tests/test-blueprint-graph.mjs +0 -410
  436. package/tests/test-blueprint.mjs +0 -577
  437. package/tests/test-client-mode.mjs +0 -86
  438. package/tests/test-console-command.mjs +0 -56
  439. package/tests/test-control-actor.mjs +0 -425
  440. package/tests/test-control-editor.mjs +0 -112
  441. package/tests/test-graphql.mjs +0 -372
  442. package/tests/test-input.mjs +0 -349
  443. package/tests/test-inspect.mjs +0 -302
  444. package/tests/test-landscape.mjs +0 -316
  445. package/tests/test-lighting.mjs +0 -428
  446. package/tests/test-manage-asset.mjs +0 -438
  447. package/tests/test-manage-level.mjs +0 -89
  448. package/tests/test-materials.mjs +0 -356
  449. package/tests/test-niagara.mjs +0 -185
  450. package/tests/test-no-inline-python.mjs +0 -122
  451. package/tests/test-performance.mjs +0 -539
  452. package/tests/test-plugin-handshake.mjs +0 -82
  453. package/tests/test-runner.mjs +0 -993
  454. package/tests/test-sequence.mjs +0 -104
  455. package/tests/test-system.mjs +0 -96
  456. package/tests/test-wasm.mjs +0 -283
  457. package/tests/test-world-partition.mjs +0 -215
  458. package/tsconfig.json +0 -56
  459. package/vitest.config.ts +0 -35
  460. package/wasm/Cargo.lock +0 -363
  461. package/wasm/Cargo.toml +0 -42
  462. package/wasm/LICENSE +0 -21
  463. package/wasm/README.md +0 -253
  464. package/wasm/src/dependency_resolver.rs +0 -377
  465. package/wasm/src/lib.rs +0 -153
  466. package/wasm/src/property_parser.rs +0 -271
  467. package/wasm/src/transform_math.rs +0 -396
  468. package/wasm/tests/integration.rs +0 -109
@@ -1,2265 +0,0 @@
1
- #include "McpAutomationBridgeGlobals.h"
2
- #include "McpAutomationBridgeHelpers.h"
3
- #include "McpAutomationBridgeSubsystem.h"
4
-
5
- #if WITH_EDITOR
6
- #include "Editor.h"
7
- #include "EditorAssetLibrary.h"
8
-
9
- #if __has_include("Subsystems/EditorActorSubsystem.h")
10
- #include "Subsystems/EditorActorSubsystem.h"
11
- #elif __has_include("EditorActorSubsystem.h")
12
- #include "EditorActorSubsystem.h"
13
- #endif
14
- #if __has_include("Subsystems/UnrealEditorSubsystem.h")
15
- #include "Subsystems/UnrealEditorSubsystem.h"
16
- #elif __has_include("UnrealEditorSubsystem.h")
17
- #include "UnrealEditorSubsystem.h"
18
- #endif
19
- #if __has_include("Subsystems/LevelEditorSubsystem.h")
20
- #include "Subsystems/LevelEditorSubsystem.h"
21
- #elif __has_include("LevelEditorSubsystem.h")
22
- #include "LevelEditorSubsystem.h"
23
- #endif
24
- #include "Components/DirectionalLightComponent.h"
25
- #include "Components/SkyLightComponent.h"
26
- #include "Developer/AssetTools/Public/AssetToolsModule.h"
27
- #include "EditorValidatorSubsystem.h"
28
- #include "Engine/Blueprint.h"
29
- #include "Engine/DirectionalLight.h"
30
- #include "Engine/SkyLight.h"
31
- #include "EngineUtils.h"
32
- #include "FileHelpers.h"
33
- #include "GeneralProjectSettings.h"
34
- #include "KismetProceduralMeshLibrary.h"
35
- #include "Misc/FileHelper.h"
36
- #include "NiagaraComponent.h"
37
- #include "NiagaraSystem.h"
38
- #include "ProceduralMeshComponent.h"
39
-
40
- #endif
41
-
42
- bool UMcpAutomationBridgeSubsystem::HandleBuildEnvironmentAction(
43
- const FString &RequestId, const FString &Action,
44
- const TSharedPtr<FJsonObject> &Payload,
45
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
46
- const FString Lower = Action.ToLower();
47
- if (!Lower.Equals(TEXT("build_environment"), ESearchCase::IgnoreCase) &&
48
- !Lower.StartsWith(TEXT("build_environment")))
49
- return false;
50
-
51
- if (!Payload.IsValid()) {
52
- SendAutomationError(RequestingSocket, RequestId,
53
- TEXT("build_environment payload missing."),
54
- TEXT("INVALID_PAYLOAD"));
55
- return true;
56
- }
57
-
58
- FString SubAction;
59
- Payload->TryGetStringField(TEXT("action"), SubAction);
60
- const FString LowerSub = SubAction.ToLower();
61
-
62
- // Fast-path foliage sub-actions to dedicated native handlers to avoid double
63
- // responses
64
- if (LowerSub == TEXT("add_foliage_instances")) {
65
- // Transform from build_environment schema to foliage handler schema
66
- FString FoliageTypePath;
67
- Payload->TryGetStringField(TEXT("foliageType"), FoliageTypePath);
68
- const TArray<TSharedPtr<FJsonValue>> *Transforms = nullptr;
69
- Payload->TryGetArrayField(TEXT("transforms"), Transforms);
70
- TSharedPtr<FJsonObject> FoliagePayload = MakeShared<FJsonObject>();
71
- if (!FoliageTypePath.IsEmpty()) {
72
- FoliagePayload->SetStringField(TEXT("foliageTypePath"), FoliageTypePath);
73
- }
74
- TArray<TSharedPtr<FJsonValue>> Locations;
75
- if (Transforms) {
76
- for (const TSharedPtr<FJsonValue> &V : *Transforms) {
77
- if (!V.IsValid() || V->Type != EJson::Object)
78
- continue;
79
- const TSharedPtr<FJsonObject> *TObj = nullptr;
80
- if (!V->TryGetObject(TObj) || !TObj)
81
- continue;
82
- const TSharedPtr<FJsonObject> *LocObj = nullptr;
83
- if (!(*TObj)->TryGetObjectField(TEXT("location"), LocObj) || !LocObj)
84
- continue;
85
- double X = 0, Y = 0, Z = 0;
86
- (*LocObj)->TryGetNumberField(TEXT("x"), X);
87
- (*LocObj)->TryGetNumberField(TEXT("y"), Y);
88
- (*LocObj)->TryGetNumberField(TEXT("z"), Z);
89
- TSharedPtr<FJsonObject> L = MakeShared<FJsonObject>();
90
- L->SetNumberField(TEXT("x"), X);
91
- L->SetNumberField(TEXT("y"), Y);
92
- L->SetNumberField(TEXT("z"), Z);
93
- Locations.Add(MakeShared<FJsonValueObject>(L));
94
- }
95
- }
96
- FoliagePayload->SetArrayField(TEXT("locations"), Locations);
97
- return HandlePaintFoliage(RequestId, TEXT("paint_foliage"), FoliagePayload,
98
- RequestingSocket);
99
- } else if (LowerSub == TEXT("get_foliage_instances")) {
100
- FString FoliageTypePath;
101
- Payload->TryGetStringField(TEXT("foliageType"), FoliageTypePath);
102
- TSharedPtr<FJsonObject> FoliagePayload = MakeShared<FJsonObject>();
103
- if (!FoliageTypePath.IsEmpty()) {
104
- FoliagePayload->SetStringField(TEXT("foliageTypePath"), FoliageTypePath);
105
- }
106
- return HandleGetFoliageInstances(RequestId, TEXT("get_foliage_instances"),
107
- FoliagePayload, RequestingSocket);
108
- } else if (LowerSub == TEXT("remove_foliage")) {
109
- FString FoliageTypePath;
110
- Payload->TryGetStringField(TEXT("foliageType"), FoliageTypePath);
111
- bool bRemoveAll = false;
112
- Payload->TryGetBoolField(TEXT("removeAll"), bRemoveAll);
113
- TSharedPtr<FJsonObject> FoliagePayload = MakeShared<FJsonObject>();
114
- if (!FoliageTypePath.IsEmpty()) {
115
- FoliagePayload->SetStringField(TEXT("foliageTypePath"), FoliageTypePath);
116
- }
117
- FoliagePayload->SetBoolField(TEXT("removeAll"), bRemoveAll);
118
- return HandleRemoveFoliage(RequestId, TEXT("remove_foliage"),
119
- FoliagePayload, RequestingSocket);
120
- }
121
- // Dispatch landscape operations
122
- else if (LowerSub == TEXT("paint_landscape") ||
123
- LowerSub == TEXT("paint_landscape_layer")) {
124
- return HandlePaintLandscapeLayer(RequestId, TEXT("paint_landscape_layer"),
125
- Payload, RequestingSocket);
126
- } else if (LowerSub == TEXT("sculpt_landscape")) {
127
- return HandleSculptLandscape(RequestId, TEXT("sculpt_landscape"), Payload,
128
- RequestingSocket);
129
- } else if (LowerSub == TEXT("modify_heightmap")) {
130
- return HandleModifyHeightmap(RequestId, TEXT("modify_heightmap"), Payload,
131
- RequestingSocket);
132
- } else if (LowerSub == TEXT("set_landscape_material")) {
133
- return HandleSetLandscapeMaterial(RequestId, TEXT("set_landscape_material"),
134
- Payload, RequestingSocket);
135
- } else if (LowerSub == TEXT("create_landscape_grass_type")) {
136
- return HandleCreateLandscapeGrassType(RequestId,
137
- TEXT("create_landscape_grass_type"),
138
- Payload, RequestingSocket);
139
- } else if (LowerSub == TEXT("generate_lods")) {
140
- return HandleGenerateLODs(RequestId, TEXT("generate_lods"), Payload,
141
- RequestingSocket);
142
- } else if (LowerSub == TEXT("bake_lightmap")) {
143
- return HandleBakeLightmap(RequestId, TEXT("bake_lightmap"), Payload,
144
- RequestingSocket);
145
- }
146
-
147
- #if WITH_EDITOR
148
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
149
- Resp->SetStringField(TEXT("action"), LowerSub);
150
- bool bSuccess = true;
151
- FString Message =
152
- FString::Printf(TEXT("Environment action '%s' completed"), *LowerSub);
153
- FString ErrorCode;
154
-
155
- if (LowerSub == TEXT("export_snapshot")) {
156
- FString Path;
157
- Payload->TryGetStringField(TEXT("path"), Path);
158
- if (Path.IsEmpty()) {
159
- bSuccess = false;
160
- Message = TEXT("path required for export_snapshot");
161
- ErrorCode = TEXT("INVALID_ARGUMENT");
162
- Resp->SetStringField(TEXT("error"), Message);
163
- } else {
164
- TSharedPtr<FJsonObject> Snapshot = MakeShared<FJsonObject>();
165
- Snapshot->SetStringField(TEXT("timestamp"),
166
- FDateTime::UtcNow().ToString());
167
- Snapshot->SetStringField(TEXT("type"), TEXT("environment_snapshot"));
168
-
169
- FString JsonString;
170
- TSharedRef<TJsonWriter<>> Writer =
171
- TJsonWriterFactory<>::Create(&JsonString);
172
- if (FJsonSerializer::Serialize(Snapshot.ToSharedRef(), Writer)) {
173
- if (FFileHelper::SaveStringToFile(JsonString, *Path)) {
174
- Resp->SetStringField(TEXT("exportPath"), Path);
175
- Resp->SetStringField(TEXT("message"), TEXT("Snapshot exported"));
176
- } else {
177
- bSuccess = false;
178
- Message = TEXT("Failed to write snapshot file");
179
- ErrorCode = TEXT("WRITE_FAILED");
180
- Resp->SetStringField(TEXT("error"), Message);
181
- }
182
- } else {
183
- bSuccess = false;
184
- Message = TEXT("Failed to serialize snapshot");
185
- ErrorCode = TEXT("SERIALIZE_FAILED");
186
- Resp->SetStringField(TEXT("error"), Message);
187
- }
188
- }
189
- } else if (LowerSub == TEXT("import_snapshot")) {
190
- FString Path;
191
- Payload->TryGetStringField(TEXT("path"), Path);
192
- if (Path.IsEmpty()) {
193
- bSuccess = false;
194
- Message = TEXT("path required for import_snapshot");
195
- ErrorCode = TEXT("INVALID_ARGUMENT");
196
- Resp->SetStringField(TEXT("error"), Message);
197
- } else {
198
- FString JsonString;
199
- if (!FFileHelper::LoadFileToString(JsonString, *Path)) {
200
- bSuccess = false;
201
- Message = TEXT("Failed to read snapshot file");
202
- ErrorCode = TEXT("LOAD_FAILED");
203
- Resp->SetStringField(TEXT("error"), Message);
204
- } else {
205
- TSharedPtr<FJsonObject> SnapshotObj;
206
- TSharedRef<TJsonReader<>> Reader =
207
- TJsonReaderFactory<>::Create(JsonString);
208
- if (!FJsonSerializer::Deserialize(Reader, SnapshotObj) ||
209
- !SnapshotObj.IsValid()) {
210
- bSuccess = false;
211
- Message = TEXT("Failed to parse snapshot");
212
- ErrorCode = TEXT("PARSE_FAILED");
213
- Resp->SetStringField(TEXT("error"), Message);
214
- } else {
215
- Resp->SetObjectField(TEXT("snapshot"), SnapshotObj.ToSharedRef());
216
- Resp->SetStringField(TEXT("message"), TEXT("Snapshot imported"));
217
- }
218
- }
219
- }
220
- } else if (LowerSub == TEXT("delete")) {
221
- const TArray<TSharedPtr<FJsonValue>> *NamesArray = nullptr;
222
- if (!Payload->TryGetArrayField(TEXT("names"), NamesArray) || !NamesArray) {
223
- bSuccess = false;
224
- Message = TEXT("names array required for delete");
225
- ErrorCode = TEXT("INVALID_ARGUMENT");
226
- Resp->SetStringField(TEXT("error"), Message);
227
- } else if (!GEditor) {
228
- bSuccess = false;
229
- Message = TEXT("Editor not available");
230
- ErrorCode = TEXT("EDITOR_NOT_AVAILABLE");
231
- Resp->SetStringField(TEXT("error"), Message);
232
- } else {
233
- UEditorActorSubsystem *ActorSS =
234
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
235
- if (!ActorSS) {
236
- bSuccess = false;
237
- Message = TEXT("EditorActorSubsystem not available");
238
- ErrorCode = TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING");
239
- Resp->SetStringField(TEXT("error"), Message);
240
- } else {
241
- TArray<FString> Deleted;
242
- TArray<FString> Missing;
243
- for (const TSharedPtr<FJsonValue> &Val : *NamesArray) {
244
- if (Val.IsValid() && Val->Type == EJson::String) {
245
- FString Name = Val->AsString();
246
- TArray<AActor *> AllActors = ActorSS->GetAllLevelActors();
247
- bool bRemoved = false;
248
- for (AActor *A : AllActors) {
249
- if (A &&
250
- A->GetActorLabel().Equals(Name, ESearchCase::IgnoreCase)) {
251
- if (ActorSS->DestroyActor(A)) {
252
- Deleted.Add(Name);
253
- bRemoved = true;
254
- }
255
- break;
256
- }
257
- }
258
- if (!bRemoved) {
259
- Missing.Add(Name);
260
- }
261
- }
262
- }
263
-
264
- TArray<TSharedPtr<FJsonValue>> DeletedArray;
265
- for (const FString &Name : Deleted) {
266
- DeletedArray.Add(MakeShared<FJsonValueString>(Name));
267
- }
268
- Resp->SetArrayField(TEXT("deleted"), DeletedArray);
269
- Resp->SetNumberField(TEXT("deletedCount"), Deleted.Num());
270
-
271
- if (Missing.Num() > 0) {
272
- TArray<TSharedPtr<FJsonValue>> MissingArray;
273
- for (const FString &Name : Missing) {
274
- MissingArray.Add(MakeShared<FJsonValueString>(Name));
275
- }
276
- Resp->SetArrayField(TEXT("missing"), MissingArray);
277
- bSuccess = false;
278
- Message = TEXT("Some environment actors could not be removed");
279
- ErrorCode = TEXT("DELETE_PARTIAL");
280
- Resp->SetStringField(TEXT("error"), Message);
281
- } else {
282
- Message = TEXT("Environment actors deleted");
283
- }
284
- }
285
- }
286
- } else if (LowerSub == TEXT("create_sky_sphere")) {
287
- if (GEditor) {
288
- UClass *SkySphereClass = LoadClass<AActor>(
289
- nullptr, TEXT("/Script/Engine.Blueprint'/Engine/Maps/Templates/"
290
- "SkySphere.SkySphere_C'"));
291
- if (SkySphereClass) {
292
- AActor *SkySphere = SpawnActorInActiveWorld<AActor>(
293
- SkySphereClass, FVector::ZeroVector, FRotator::ZeroRotator,
294
- TEXT("SkySphere"));
295
- if (SkySphere) {
296
- bSuccess = true;
297
- Message = TEXT("Sky sphere created");
298
- Resp->SetStringField(TEXT("actorName"), SkySphere->GetActorLabel());
299
- }
300
- }
301
- }
302
- if (!bSuccess) {
303
- bSuccess = false;
304
- Message = TEXT("Failed to create sky sphere");
305
- ErrorCode = TEXT("CREATION_FAILED");
306
- }
307
- } else if (LowerSub == TEXT("set_time_of_day")) {
308
- float TimeOfDay = 12.0f;
309
- Payload->TryGetNumberField(TEXT("time"), TimeOfDay);
310
-
311
- if (GEditor) {
312
- UEditorActorSubsystem *ActorSS =
313
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
314
- if (ActorSS) {
315
- for (AActor *Actor : ActorSS->GetAllLevelActors()) {
316
- if (Actor->GetClass()->GetName().Contains(TEXT("SkySphere"))) {
317
- UFunction *SetTimeFunction =
318
- Actor->FindFunction(TEXT("SetTimeOfDay"));
319
- if (SetTimeFunction) {
320
- float TimeParam = TimeOfDay;
321
- Actor->ProcessEvent(SetTimeFunction, &TimeParam);
322
- bSuccess = true;
323
- Message =
324
- FString::Printf(TEXT("Time of day set to %.2f"), TimeOfDay);
325
- break;
326
- }
327
- }
328
- }
329
- }
330
- }
331
- if (!bSuccess) {
332
- bSuccess = false;
333
- Message = TEXT("Sky sphere not found or time function not available");
334
- ErrorCode = TEXT("SET_TIME_FAILED");
335
- }
336
- } else if (LowerSub == TEXT("create_fog_volume")) {
337
- FVector Location(0, 0, 0);
338
- Payload->TryGetNumberField(TEXT("x"), Location.X);
339
- Payload->TryGetNumberField(TEXT("y"), Location.Y);
340
- Payload->TryGetNumberField(TEXT("z"), Location.Z);
341
-
342
- if (GEditor) {
343
- UClass *FogClass = LoadClass<AActor>(
344
- nullptr, TEXT("/Script/Engine.ExponentialHeightFog"));
345
- if (FogClass) {
346
- AActor *FogVolume = SpawnActorInActiveWorld<AActor>(
347
- FogClass, Location, FRotator::ZeroRotator, TEXT("FogVolume"));
348
- if (FogVolume) {
349
- bSuccess = true;
350
- Message = TEXT("Fog volume created");
351
- Resp->SetStringField(TEXT("actorName"), FogVolume->GetActorLabel());
352
- }
353
- }
354
- }
355
- if (!bSuccess) {
356
- bSuccess = false;
357
- Message = TEXT("Failed to create fog volume");
358
- ErrorCode = TEXT("CREATION_FAILED");
359
- }
360
- } else {
361
- bSuccess = false;
362
- Message = FString::Printf(TEXT("Environment action '%s' not implemented"),
363
- *LowerSub);
364
- ErrorCode = TEXT("NOT_IMPLEMENTED");
365
- Resp->SetStringField(TEXT("error"), Message);
366
- }
367
-
368
- Resp->SetBoolField(TEXT("success"), bSuccess);
369
- SendAutomationResponse(RequestingSocket, RequestId, bSuccess, Message, Resp,
370
- ErrorCode);
371
- return true;
372
- #else
373
- SendAutomationResponse(
374
- RequestingSocket, RequestId, false,
375
- TEXT("Environment building actions require editor build."), nullptr,
376
- TEXT("NOT_IMPLEMENTED"));
377
- return true;
378
- #endif
379
- }
380
-
381
- bool UMcpAutomationBridgeSubsystem::HandleControlEnvironmentAction(
382
- const FString &RequestId, const FString &Action,
383
- const TSharedPtr<FJsonObject> &Payload,
384
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
385
- const FString Lower = Action.ToLower();
386
- if (!Lower.Equals(TEXT("control_environment"), ESearchCase::IgnoreCase) &&
387
- !Lower.StartsWith(TEXT("control_environment"))) {
388
- return false;
389
- }
390
-
391
- if (!Payload.IsValid()) {
392
- SendAutomationError(RequestingSocket, RequestId,
393
- TEXT("control_environment payload missing."),
394
- TEXT("INVALID_PAYLOAD"));
395
- return true;
396
- }
397
-
398
- FString SubAction;
399
- Payload->TryGetStringField(TEXT("action"), SubAction);
400
- const FString LowerSub = SubAction.ToLower();
401
-
402
- #if WITH_EDITOR
403
- auto SendResult = [&](bool bSuccess, const TCHAR *Message,
404
- const FString &ErrorCode,
405
- const TSharedPtr<FJsonObject> &Result) {
406
- if (bSuccess) {
407
- SendAutomationResponse(RequestingSocket, RequestId, true,
408
- Message ? Message
409
- : TEXT("Environment control succeeded."),
410
- Result, FString());
411
- } else {
412
- SendAutomationResponse(RequestingSocket, RequestId, false,
413
- Message ? Message
414
- : TEXT("Environment control failed."),
415
- Result, ErrorCode);
416
- }
417
- };
418
-
419
- UWorld *World = nullptr;
420
- if (GEditor) {
421
- World = GEditor->GetEditorWorldContext().World();
422
- }
423
-
424
- if (!World) {
425
- SendResult(false, TEXT("Editor world is unavailable"),
426
- TEXT("WORLD_NOT_AVAILABLE"), nullptr);
427
- return true;
428
- }
429
-
430
- auto FindFirstDirectionalLight = [&]() -> ADirectionalLight * {
431
- for (TActorIterator<ADirectionalLight> It(World); It; ++It) {
432
- if (ADirectionalLight *Light = *It) {
433
- if (IsValid(Light)) {
434
- return Light;
435
- }
436
- }
437
- }
438
- return nullptr;
439
- };
440
-
441
- auto FindFirstSkyLight = [&]() -> ASkyLight * {
442
- for (TActorIterator<ASkyLight> It(World); It; ++It) {
443
- if (ASkyLight *Sky = *It) {
444
- if (IsValid(Sky)) {
445
- return Sky;
446
- }
447
- }
448
- }
449
- return nullptr;
450
- };
451
-
452
- if (LowerSub == TEXT("set_time_of_day")) {
453
- double Hour = 0.0;
454
- const bool bHasHour = Payload->TryGetNumberField(TEXT("hour"), Hour);
455
- if (!bHasHour) {
456
- SendResult(false, TEXT("Missing hour parameter"),
457
- TEXT("INVALID_ARGUMENT"), nullptr);
458
- return true;
459
- }
460
-
461
- ADirectionalLight *SunLight = FindFirstDirectionalLight();
462
- if (!SunLight) {
463
- SendResult(false, TEXT("No directional light found"),
464
- TEXT("SUN_NOT_FOUND"), nullptr);
465
- return true;
466
- }
467
-
468
- const float ClampedHour =
469
- FMath::Clamp(static_cast<float>(Hour), 0.0f, 24.0f);
470
- const float SolarPitch = (ClampedHour / 24.0f) * 360.0f - 90.0f;
471
-
472
- SunLight->Modify();
473
- FRotator NewRotation = SunLight->GetActorRotation();
474
- NewRotation.Pitch = SolarPitch;
475
- SunLight->SetActorRotation(NewRotation);
476
-
477
- if (UDirectionalLightComponent *LightComp =
478
- Cast<UDirectionalLightComponent>(SunLight->GetLightComponent())) {
479
- LightComp->MarkRenderStateDirty();
480
- }
481
-
482
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
483
- Result->SetNumberField(TEXT("hour"), ClampedHour);
484
- Result->SetNumberField(TEXT("pitch"), SolarPitch);
485
- Result->SetStringField(TEXT("actor"), SunLight->GetPathName());
486
- SendResult(true, TEXT("Time of day updated"), FString(), Result);
487
- return true;
488
- }
489
-
490
- if (LowerSub == TEXT("set_sun_intensity")) {
491
- double Intensity = 0.0;
492
- if (!Payload->TryGetNumberField(TEXT("intensity"), Intensity)) {
493
- SendResult(false, TEXT("Missing intensity parameter"),
494
- TEXT("INVALID_ARGUMENT"), nullptr);
495
- return true;
496
- }
497
-
498
- ADirectionalLight *SunLight = FindFirstDirectionalLight();
499
- if (!SunLight) {
500
- SendResult(false, TEXT("No directional light found"),
501
- TEXT("SUN_NOT_FOUND"), nullptr);
502
- return true;
503
- }
504
-
505
- if (UDirectionalLightComponent *LightComp =
506
- Cast<UDirectionalLightComponent>(SunLight->GetLightComponent())) {
507
- LightComp->SetIntensity(static_cast<float>(Intensity));
508
- LightComp->MarkRenderStateDirty();
509
- }
510
-
511
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
512
- Result->SetNumberField(TEXT("intensity"), Intensity);
513
- Result->SetStringField(TEXT("actor"), SunLight->GetPathName());
514
- SendResult(true, TEXT("Sun intensity updated"), FString(), Result);
515
- return true;
516
- }
517
-
518
- if (LowerSub == TEXT("set_skylight_intensity")) {
519
- double Intensity = 0.0;
520
- if (!Payload->TryGetNumberField(TEXT("intensity"), Intensity)) {
521
- SendResult(false, TEXT("Missing intensity parameter"),
522
- TEXT("INVALID_ARGUMENT"), nullptr);
523
- return true;
524
- }
525
-
526
- ASkyLight *SkyActor = FindFirstSkyLight();
527
- if (!SkyActor) {
528
- SendResult(false, TEXT("No skylight found"), TEXT("SKYLIGHT_NOT_FOUND"),
529
- nullptr);
530
- return true;
531
- }
532
-
533
- if (USkyLightComponent *SkyComp = SkyActor->GetLightComponent()) {
534
- SkyComp->SetIntensity(static_cast<float>(Intensity));
535
- SkyComp->MarkRenderStateDirty();
536
- SkyActor->MarkComponentsRenderStateDirty();
537
- }
538
-
539
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
540
- Result->SetNumberField(TEXT("intensity"), Intensity);
541
- Result->SetStringField(TEXT("actor"), SkyActor->GetPathName());
542
- SendResult(true, TEXT("Skylight intensity updated"), FString(), Result);
543
- return true;
544
- }
545
-
546
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
547
- Result->SetStringField(TEXT("action"), LowerSub);
548
- SendResult(false, TEXT("Unsupported environment control action"),
549
- TEXT("UNSUPPORTED_ACTION"), Result);
550
- return true;
551
- #else
552
- SendAutomationResponse(RequestingSocket, RequestId, false,
553
- TEXT("Environment control requires editor build"),
554
- nullptr, TEXT("NOT_IMPLEMENTED"));
555
- return true;
556
- #endif
557
- }
558
-
559
- bool UMcpAutomationBridgeSubsystem::HandleSystemControlAction(
560
- const FString &RequestId, const FString &Action,
561
- const TSharedPtr<FJsonObject> &Payload,
562
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
563
- const FString Lower = Action.ToLower();
564
- if (!Lower.Equals(TEXT("system_control"), ESearchCase::IgnoreCase) &&
565
- !Lower.StartsWith(TEXT("system_control")))
566
- return false;
567
-
568
- if (!Payload.IsValid()) {
569
- SendAutomationResponse(RequestingSocket, RequestId, false,
570
- TEXT("System control requires valid payload"),
571
- nullptr, TEXT("INVALID_PAYLOAD"));
572
- return true;
573
- }
574
-
575
- FString SubAction;
576
- if (!Payload->TryGetStringField(TEXT("action"), SubAction)) {
577
- SendAutomationResponse(RequestingSocket, RequestId, false,
578
- TEXT("System control requires action parameter"),
579
- nullptr, TEXT("INVALID_ARGUMENT"));
580
- return true;
581
- }
582
-
583
- FString LowerSub = SubAction.ToLower();
584
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
585
-
586
- // Profile commands
587
- if (LowerSub == TEXT("profile")) {
588
- FString ProfileType;
589
- bool bEnabled = true;
590
- Payload->TryGetStringField(TEXT("profileType"), ProfileType);
591
- Payload->TryGetBoolField(TEXT("enabled"), bEnabled);
592
-
593
- FString Command;
594
- if (ProfileType.ToLower() == TEXT("cpu")) {
595
- Command = bEnabled ? TEXT("stat cpu") : TEXT("stat cpu");
596
- } else if (ProfileType.ToLower() == TEXT("gpu")) {
597
- Command = bEnabled ? TEXT("stat gpu") : TEXT("stat gpu");
598
- } else if (ProfileType.ToLower() == TEXT("memory")) {
599
- Command = bEnabled ? TEXT("stat memory") : TEXT("stat memory");
600
- } else if (ProfileType.ToLower() == TEXT("fps")) {
601
- Command = bEnabled ? TEXT("stat fps") : TEXT("stat fps");
602
- }
603
-
604
- if (!Command.IsEmpty()) {
605
- GEngine->Exec(nullptr, *Command);
606
- Result->SetStringField(TEXT("command"), Command);
607
- Result->SetBoolField(TEXT("enabled"), bEnabled);
608
- SendAutomationResponse(
609
- RequestingSocket, RequestId, true,
610
- FString::Printf(TEXT("Executed profile command: %s"), *Command),
611
- Result, FString());
612
- return true;
613
- }
614
- }
615
-
616
- // Show FPS
617
- if (LowerSub == TEXT("show_fps")) {
618
- bool bEnabled = true;
619
- Payload->TryGetBoolField(TEXT("enabled"), bEnabled);
620
-
621
- FString Command = bEnabled ? TEXT("stat fps") : TEXT("stat fps");
622
- GEngine->Exec(nullptr, *Command);
623
- Result->SetStringField(TEXT("command"), Command);
624
- Result->SetBoolField(TEXT("enabled"), bEnabled);
625
- SendAutomationResponse(
626
- RequestingSocket, RequestId, true,
627
- FString::Printf(TEXT("FPS display %s"),
628
- bEnabled ? TEXT("enabled") : TEXT("disabled")),
629
- Result, FString());
630
- return true;
631
- }
632
-
633
- // Set quality
634
- if (LowerSub == TEXT("set_quality")) {
635
- FString Category;
636
- int32 Level = 1;
637
- Payload->TryGetStringField(TEXT("category"), Category);
638
- Payload->TryGetNumberField(TEXT("level"), Level);
639
-
640
- if (!Category.IsEmpty()) {
641
- FString Command = FString::Printf(TEXT("sg.%s %d"), *Category, Level);
642
- GEngine->Exec(nullptr, *Command);
643
- Result->SetStringField(TEXT("command"), Command);
644
- Result->SetStringField(TEXT("category"), Category);
645
- Result->SetNumberField(TEXT("level"), Level);
646
- SendAutomationResponse(
647
- RequestingSocket, RequestId, true,
648
- FString::Printf(TEXT("Set quality %s to %d"), *Category, Level),
649
- Result, FString());
650
- return true;
651
- }
652
- }
653
-
654
- // Screenshot
655
- if (LowerSub == TEXT("screenshot")) {
656
- FString Filename = TEXT("screenshot");
657
- Payload->TryGetStringField(TEXT("filename"), Filename);
658
-
659
- FString Command = FString::Printf(TEXT("screenshot %s"), *Filename);
660
- GEngine->Exec(nullptr, *Command);
661
- Result->SetStringField(TEXT("command"), Command);
662
- Result->SetStringField(TEXT("filename"), Filename);
663
- SendAutomationResponse(
664
- RequestingSocket, RequestId, true,
665
- FString::Printf(TEXT("Screenshot captured: %s"), *Filename), Result,
666
- FString());
667
- return true;
668
- }
669
-
670
- if (LowerSub == TEXT("get_project_settings")) {
671
- #if WITH_EDITOR
672
- FString Category;
673
- Payload->TryGetStringField(TEXT("category"), Category);
674
- const FString LowerCategory = Category.ToLower();
675
-
676
- const UGeneralProjectSettings *ProjectSettings =
677
- GetDefault<UGeneralProjectSettings>();
678
- TSharedPtr<FJsonObject> SettingsObj = MakeShared<FJsonObject>();
679
- if (ProjectSettings) {
680
- SettingsObj->SetStringField(TEXT("projectName"),
681
- ProjectSettings->ProjectName);
682
- SettingsObj->SetStringField(TEXT("companyName"),
683
- ProjectSettings->CompanyName);
684
- SettingsObj->SetStringField(TEXT("projectVersion"),
685
- ProjectSettings->ProjectVersion);
686
- SettingsObj->SetStringField(TEXT("description"),
687
- ProjectSettings->Description);
688
- }
689
-
690
- TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
691
- Out->SetStringField(TEXT("category"),
692
- Category.IsEmpty() ? TEXT("Project") : Category);
693
- Out->SetObjectField(TEXT("settings"), SettingsObj);
694
-
695
- SendAutomationResponse(RequestingSocket, RequestId, true,
696
- TEXT("Project settings retrieved"), Out, FString());
697
- return true;
698
- #else
699
- SendAutomationResponse(RequestingSocket, RequestId, false,
700
- TEXT("get_project_settings requires editor build"),
701
- nullptr, TEXT("NOT_IMPLEMENTED"));
702
- return true;
703
- #endif
704
- }
705
-
706
- if (LowerSub == TEXT("get_engine_version")) {
707
- #if WITH_EDITOR
708
- const FEngineVersion &EngineVer = FEngineVersion::Current();
709
- TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
710
- Out->SetStringField(TEXT("version"), EngineVer.ToString());
711
- Out->SetNumberField(TEXT("major"), EngineVer.GetMajor());
712
- Out->SetNumberField(TEXT("minor"), EngineVer.GetMinor());
713
- Out->SetNumberField(TEXT("patch"), EngineVer.GetPatch());
714
- const bool bIs56OrAbove =
715
- (EngineVer.GetMajor() > 5) ||
716
- (EngineVer.GetMajor() == 5 && EngineVer.GetMinor() >= 6);
717
- Out->SetBoolField(TEXT("isUE56OrAbove"), bIs56OrAbove);
718
- SendAutomationResponse(RequestingSocket, RequestId, true,
719
- TEXT("Engine version retrieved"), Out, FString());
720
- return true;
721
- #else
722
- SendAutomationResponse(RequestingSocket, RequestId, false,
723
- TEXT("get_engine_version requires editor build"),
724
- nullptr, TEXT("NOT_IMPLEMENTED"));
725
- return true;
726
- #endif
727
- }
728
-
729
- if (LowerSub == TEXT("get_feature_flags")) {
730
- #if WITH_EDITOR
731
- bool bUnrealEditor = false;
732
- bool bLevelEditor = false;
733
- bool bEditorActor = false;
734
-
735
- if (GEditor) {
736
- if (UUnrealEditorSubsystem *UnrealEditorSS =
737
- GEditor->GetEditorSubsystem<UUnrealEditorSubsystem>()) {
738
- bUnrealEditor = true;
739
- }
740
- if (ULevelEditorSubsystem *LevelEditorSS =
741
- GEditor->GetEditorSubsystem<ULevelEditorSubsystem>()) {
742
- bLevelEditor = true;
743
- }
744
- if (UEditorActorSubsystem *ActorSS =
745
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>()) {
746
- bEditorActor = true;
747
- }
748
- }
749
-
750
- TSharedPtr<FJsonObject> SubsystemsObj = MakeShared<FJsonObject>();
751
- SubsystemsObj->SetBoolField(TEXT("unrealEditor"), bUnrealEditor);
752
- SubsystemsObj->SetBoolField(TEXT("levelEditor"), bLevelEditor);
753
- SubsystemsObj->SetBoolField(TEXT("editorActor"), bEditorActor);
754
-
755
- TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
756
- Out->SetObjectField(TEXT("subsystems"), SubsystemsObj);
757
-
758
- SendAutomationResponse(RequestingSocket, RequestId, true,
759
- TEXT("Feature flags retrieved"), Out, FString());
760
- return true;
761
- #else
762
- SendAutomationResponse(RequestingSocket, RequestId, false,
763
- TEXT("get_feature_flags requires editor build"),
764
- nullptr, TEXT("NOT_IMPLEMENTED"));
765
- return true;
766
- #endif
767
- }
768
-
769
- if (LowerSub == TEXT("set_project_setting")) {
770
- #if WITH_EDITOR
771
- FString Section;
772
- FString Key;
773
- FString Value;
774
- FString ConfigName;
775
-
776
- if (!Payload->TryGetStringField(TEXT("section"), Section) ||
777
- !Payload->TryGetStringField(TEXT("key"), Key) ||
778
- !Payload->TryGetStringField(TEXT("value"), Value)) {
779
- SendAutomationResponse(RequestingSocket, RequestId, false,
780
- TEXT("Missing section, key, or value"), nullptr,
781
- TEXT("INVALID_ARGUMENT"));
782
- return true;
783
- }
784
-
785
- // Default to GGameIni (DefaultGame.ini) but allow overrides
786
- if (!Payload->TryGetStringField(TEXT("configName"), ConfigName) ||
787
- ConfigName.IsEmpty()) {
788
- ConfigName = GGameIni;
789
- } else if (ConfigName == TEXT("Engine")) {
790
- ConfigName = GEngineIni;
791
- } else if (ConfigName == TEXT("Input")) {
792
- ConfigName = GInputIni;
793
- } else if (ConfigName == TEXT("Game")) {
794
- ConfigName = GGameIni;
795
- }
796
-
797
- if (!GConfig) {
798
- SendAutomationResponse(RequestingSocket, RequestId, false,
799
- TEXT("GConfig not available"), nullptr,
800
- TEXT("ENGINE_ERROR"));
801
- return true;
802
- }
803
-
804
- GConfig->SetString(*Section, *Key, *Value, ConfigName);
805
- GConfig->Flush(false, ConfigName);
806
-
807
- SendAutomationResponse(
808
- RequestingSocket, RequestId, true,
809
- FString::Printf(TEXT("Project setting set: [%s] %s = %s"), *Section,
810
- *Key, *Value),
811
- nullptr);
812
- return true;
813
- #else
814
- SendAutomationResponse(RequestingSocket, RequestId, false,
815
- TEXT("set_project_setting requires editor build"),
816
- nullptr, TEXT("NOT_IMPLEMENTED"));
817
- return true;
818
- #endif
819
- }
820
-
821
- if (LowerSub == TEXT("validate_assets")) {
822
- #if WITH_EDITOR
823
- const TArray<TSharedPtr<FJsonValue>> *PathsPtr = nullptr;
824
- if (!Payload->TryGetArrayField(TEXT("paths"), PathsPtr) || !PathsPtr) {
825
- SendAutomationResponse(RequestingSocket, RequestId, false,
826
- TEXT("paths array required"), nullptr,
827
- TEXT("INVALID_ARGUMENT"));
828
- return true;
829
- }
830
-
831
- TArray<FString> AssetPaths;
832
- for (const auto &Val : *PathsPtr) {
833
- if (Val.IsValid() && Val->Type == EJson::String) {
834
- AssetPaths.Add(Val->AsString());
835
- }
836
- }
837
-
838
- if (AssetPaths.Num() == 0) {
839
- SendAutomationResponse(RequestingSocket, RequestId, false,
840
- TEXT("No paths provided"), nullptr,
841
- TEXT("INVALID_ARGUMENT"));
842
- return true;
843
- }
844
-
845
- if (GEditor) {
846
- if (UEditorValidatorSubsystem *Validator =
847
- GEditor->GetEditorSubsystem<UEditorValidatorSubsystem>()) {
848
- FValidateAssetsSettings Settings;
849
- Settings.bSkipExcludedDirectories = true;
850
- Settings.bShowIfNoFailures = false;
851
- Settings.ValidationUsecase = EDataValidationUsecase::Script;
852
-
853
- TArray<FAssetData> AssetsToValidate;
854
- for (const FString &Path : AssetPaths) {
855
- // Simple logic: if it's a folder, list assets; if it's a file, try to
856
- // find it. We assume anything without a dot is a folder, effectively.
857
- // But UEditorAssetLibrary::ListAssets works recursively on module
858
- // paths.
859
- if (UEditorAssetLibrary::DoesDirectoryExist(Path)) {
860
- TArray<FString> FoundAssets =
861
- UEditorAssetLibrary::ListAssets(Path, true);
862
- for (const FString &AssetPath : FoundAssets) {
863
- FAssetData AssetData =
864
- UEditorAssetLibrary::FindAssetData(AssetPath);
865
- if (AssetData.IsValid()) {
866
- AssetsToValidate.Add(AssetData);
867
- }
868
- }
869
- } else {
870
- FAssetData SpecificAsset = UEditorAssetLibrary::FindAssetData(Path);
871
- if (SpecificAsset.IsValid()) {
872
- AssetsToValidate.AddUnique(SpecificAsset);
873
- }
874
- }
875
- }
876
-
877
- if (AssetsToValidate.Num() == 0) {
878
- Result->SetBoolField(TEXT("success"), true);
879
- Result->SetStringField(TEXT("message"),
880
- TEXT("No assets found to validate"));
881
- SendAutomationResponse(RequestingSocket, RequestId, true,
882
- TEXT("Validation skipped (no assets)"), Result,
883
- FString());
884
- return true;
885
- }
886
-
887
- FValidateAssetsResults ValidationResults;
888
- int32 NumChecked = Validator->ValidateAssetsWithSettings(
889
- AssetsToValidate, Settings, ValidationResults);
890
-
891
- Result->SetNumberField(TEXT("checkedCount"), NumChecked);
892
- Result->SetNumberField(TEXT("failedCount"),
893
- ValidationResults.NumInvalid);
894
- Result->SetNumberField(TEXT("warningCount"),
895
- ValidationResults.NumWarnings);
896
- Result->SetNumberField(TEXT("skippedCount"),
897
- ValidationResults.NumSkipped);
898
-
899
- bool bOverallSuccess = (ValidationResults.NumInvalid == 0);
900
- Result->SetStringField(
901
- TEXT("result"), bOverallSuccess ? TEXT("Valid") : TEXT("Invalid"));
902
-
903
- SendAutomationResponse(RequestingSocket, RequestId, true,
904
- bOverallSuccess ? TEXT("Validation Passed")
905
- : TEXT("Validation Failed"),
906
- Result, FString());
907
- return true;
908
- } else {
909
- SendAutomationResponse(RequestingSocket, RequestId, false,
910
- TEXT("EditorValidatorSubsystem not available"),
911
- nullptr, TEXT("SUBSYSTEM_MISSING"));
912
- return true;
913
- }
914
- }
915
- return true;
916
- #else
917
- SendAutomationResponse(RequestingSocket, RequestId, false,
918
- TEXT("validate_assets requires editor build"),
919
- nullptr, TEXT("NOT_IMPLEMENTED"));
920
- return true;
921
- #endif
922
- }
923
-
924
- // Engine quit (disabled for safety)
925
- if (LowerSub == TEXT("engine_quit")) {
926
- SendAutomationResponse(RequestingSocket, RequestId, false,
927
- TEXT("Engine quit command is disabled for safety"),
928
- nullptr, TEXT("NOT_ALLOWED"));
929
- return true;
930
- }
931
-
932
- // Unknown sub-action: return false to allow other handlers (e.g.
933
- // HandleUiAction) to attempt handling it.
934
- // NOTE: Simple return false is not enough if the dispatcher doesn't fallback.
935
- // We explicitly try the UI handler here as system_control and ui actions
936
- // overlap.
937
- return HandleUiAction(RequestId, Action, Payload, RequestingSocket);
938
- }
939
-
940
- bool UMcpAutomationBridgeSubsystem::HandleConsoleCommandAction(
941
- const FString &RequestId, const FString &Action,
942
- const TSharedPtr<FJsonObject> &Payload,
943
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
944
- if (!Action.Equals(TEXT("console_command"), ESearchCase::IgnoreCase)) {
945
- return false;
946
- }
947
-
948
- if (!Payload.IsValid()) {
949
- SendAutomationResponse(RequestingSocket, RequestId, false,
950
- TEXT("Console command requires valid payload"),
951
- nullptr, TEXT("INVALID_PAYLOAD"));
952
- return true;
953
- }
954
-
955
- FString Command;
956
- if (!Payload->TryGetStringField(TEXT("command"), Command)) {
957
- SendAutomationResponse(RequestingSocket, RequestId, false,
958
- TEXT("Console command requires command parameter"),
959
- nullptr, TEXT("INVALID_ARGUMENT"));
960
- return true;
961
- }
962
-
963
- // Block dangerous commands (Defense-in-Depth)
964
- FString LowerCommand = Command.ToLower();
965
-
966
- // 1. Explicit command blocking
967
- TArray<FString> ExplicitBlockedCommands = {
968
- TEXT("quit"), TEXT("exit"), TEXT("crash"), TEXT("shutdown"),
969
- TEXT("restart"), TEXT("reboot"), TEXT("debug exec")};
970
-
971
- for (const FString &Blocked : ExplicitBlockedCommands) {
972
- if (LowerCommand.Equals(Blocked) ||
973
- LowerCommand.StartsWith(Blocked + TEXT(" "))) {
974
- SendAutomationResponse(
975
- RequestingSocket, RequestId, false,
976
- FString::Printf(TEXT("Command '%s' is explicitly blocked for safety"),
977
- *Command),
978
- nullptr, TEXT("COMMAND_BLOCKED"));
979
- return true;
980
- }
981
- }
982
-
983
- // 2. Token-based blocking (preventing system commands, file manipulation, and
984
- // python hacks)
985
- TArray<FString> ForbiddenTokens = {TEXT("rm "),
986
- TEXT("rm-"),
987
- TEXT("del "),
988
- TEXT("format "),
989
- TEXT("rmdir"),
990
- TEXT("mklink"),
991
- TEXT("copy "),
992
- TEXT("move "),
993
- TEXT("start \""),
994
- TEXT("system("),
995
- TEXT("import os"),
996
- TEXT("import subprocess"),
997
- TEXT("subprocess."),
998
- TEXT("os.system"),
999
- TEXT("exec("),
1000
- TEXT("eval("),
1001
- TEXT("__import__"),
1002
- TEXT("import sys"),
1003
- TEXT("import importlib"),
1004
- TEXT("with open"),
1005
- TEXT("open(")};
1006
-
1007
- for (const FString &Token : ForbiddenTokens) {
1008
- if (LowerCommand.Contains(Token)) {
1009
- SendAutomationResponse(
1010
- RequestingSocket, RequestId, false,
1011
- FString::Printf(
1012
- TEXT("Command '%s' contains forbidden token '%s' and is blocked"),
1013
- *Command, *Token),
1014
- nullptr, TEXT("COMMAND_BLOCKED"));
1015
- return true;
1016
- }
1017
- }
1018
-
1019
- // 3. Block Chaining
1020
- if (LowerCommand.Contains(TEXT("&&")) || LowerCommand.Contains(TEXT("||"))) {
1021
- SendAutomationResponse(RequestingSocket, RequestId, false,
1022
- TEXT("Command chaining is blocked for safety"),
1023
- nullptr, TEXT("COMMAND_BLOCKED"));
1024
- return true;
1025
- }
1026
-
1027
- // 4. Block line breaks
1028
- if (LowerCommand.Contains(TEXT("\n")) || LowerCommand.Contains(TEXT("\r"))) {
1029
- SendAutomationResponse(RequestingSocket, RequestId, false,
1030
- TEXT("Multi-line commands are blocked for safety"),
1031
- nullptr, TEXT("COMMAND_BLOCKED"));
1032
- return true;
1033
- }
1034
-
1035
- // 5. Block semicolon and pipe
1036
- if (LowerCommand.Contains(TEXT(";")) || LowerCommand.Contains(TEXT("|"))) {
1037
- SendAutomationResponse(RequestingSocket, RequestId, false,
1038
- TEXT("Command chaining with semicolon or pipe is blocked for safety"),
1039
- nullptr, TEXT("COMMAND_BLOCKED"));
1040
- return true;
1041
- }
1042
-
1043
- // 6. Block backticks
1044
- if (LowerCommand.Contains(TEXT("`"))) {
1045
- SendAutomationResponse(RequestingSocket, RequestId, false,
1046
- TEXT("Commands containing backticks are blocked for safety"),
1047
- nullptr, TEXT("COMMAND_BLOCKED"));
1048
- return true;
1049
- }
1050
-
1051
- // Execute the command
1052
- try {
1053
- UWorld *TargetWorld = nullptr;
1054
- #if WITH_EDITOR
1055
- if (GEditor) {
1056
- // Prefer PIE world if active, otherwise Editor world
1057
- TargetWorld = GEditor->PlayWorld;
1058
- if (!TargetWorld) {
1059
- TargetWorld = GEditor->GetEditorWorldContext().World();
1060
- }
1061
- }
1062
- #endif
1063
-
1064
- // Fallback to GWorld if no editor/PIE world found (e.g. game mode)
1065
- if (!TargetWorld && GEngine) {
1066
- // Note: In some contexts GWorld is a macro for a proxy, but here we need
1067
- // a raw pointer. We'll rely on Exec handling nullptr if we really can't
1068
- // find one, but explicitly passing the editor world fixes many "command
1069
- // not handled" or crash issues.
1070
- }
1071
-
1072
- GEngine->Exec(TargetWorld, *Command);
1073
-
1074
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1075
- Result->SetStringField(TEXT("command"), Command);
1076
- Result->SetBoolField(TEXT("executed"), true);
1077
-
1078
- SendAutomationResponse(
1079
- RequestingSocket, RequestId, true,
1080
- FString::Printf(TEXT("Executed console command: %s"), *Command), Result,
1081
- FString());
1082
- return true;
1083
- } catch (...) {
1084
- SendAutomationResponse(
1085
- RequestingSocket, RequestId, false,
1086
- FString::Printf(TEXT("Failed to execute command: %s"), *Command),
1087
- nullptr, TEXT("EXECUTION_FAILED"));
1088
- return true;
1089
- }
1090
- }
1091
-
1092
- bool UMcpAutomationBridgeSubsystem::HandleInspectAction(
1093
- const FString &RequestId, const FString &Action,
1094
- const TSharedPtr<FJsonObject> &Payload,
1095
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
1096
- if (!Action.Equals(TEXT("inspect"), ESearchCase::IgnoreCase)) {
1097
- return false;
1098
- }
1099
-
1100
- if (!Payload.IsValid()) {
1101
- SendAutomationResponse(RequestingSocket, RequestId, false,
1102
- TEXT("Inspect action requires valid payload"),
1103
- nullptr, TEXT("INVALID_PAYLOAD"));
1104
- return true;
1105
- }
1106
-
1107
- FString SubAction;
1108
- if (!Payload->TryGetStringField(TEXT("action"), SubAction)) {
1109
- SendAutomationResponse(RequestingSocket, RequestId, false,
1110
- TEXT("Inspect action requires action parameter"),
1111
- nullptr, TEXT("INVALID_ARGUMENT"));
1112
- return true;
1113
- }
1114
-
1115
- FString LowerSub = SubAction.ToLower();
1116
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1117
-
1118
- // Inspect object
1119
- if (LowerSub == TEXT("inspect_object")) {
1120
- FString ObjectPath;
1121
- if (!Payload->TryGetStringField(TEXT("objectPath"), ObjectPath)) {
1122
- SendAutomationResponse(
1123
- RequestingSocket, RequestId, false,
1124
- TEXT("inspect_object requires objectPath parameter"), nullptr,
1125
- TEXT("INVALID_ARGUMENT"));
1126
- return true;
1127
- }
1128
-
1129
- UObject *TargetObject = FindObject<UObject>(nullptr, *ObjectPath);
1130
-
1131
- // Compatibility: allow passing actor label/name/path as objectPath.
1132
- // Many callers use simple names like "MyActor".
1133
- if (!TargetObject) {
1134
- if (AActor *FoundActor = FindActorByName(ObjectPath)) {
1135
- TargetObject = FoundActor;
1136
- ObjectPath = FoundActor->GetPathName();
1137
- }
1138
- }
1139
- if (!TargetObject) {
1140
- SendAutomationResponse(
1141
- RequestingSocket, RequestId, false,
1142
- FString::Printf(TEXT("Object not found: %s"), *ObjectPath), nullptr,
1143
- TEXT("OBJECT_NOT_FOUND"));
1144
- return true;
1145
- }
1146
-
1147
- Result->SetStringField(TEXT("objectPath"), ObjectPath);
1148
- Result->SetStringField(TEXT("objectName"), TargetObject->GetName());
1149
- Result->SetStringField(TEXT("objectClass"),
1150
- TargetObject->GetClass()->GetName());
1151
- Result->SetStringField(TEXT("objectType"),
1152
- TargetObject->GetClass()->GetFName().ToString());
1153
-
1154
- SendAutomationResponse(
1155
- RequestingSocket, RequestId, true,
1156
- FString::Printf(TEXT("Inspected object: %s"), *ObjectPath), Result,
1157
- FString());
1158
- return true;
1159
- }
1160
-
1161
- // Get property
1162
- if (LowerSub == TEXT("get_property")) {
1163
- FString ObjectPath;
1164
- FString PropertyName;
1165
-
1166
- if (!Payload->TryGetStringField(TEXT("objectPath"), ObjectPath) ||
1167
- !Payload->TryGetStringField(TEXT("propertyName"), PropertyName)) {
1168
- SendAutomationResponse(
1169
- RequestingSocket, RequestId, false,
1170
- TEXT("get_property requires objectPath and propertyName parameters"),
1171
- nullptr, TEXT("INVALID_ARGUMENT"));
1172
- return true;
1173
- }
1174
-
1175
- UObject *TargetObject = FindObject<UObject>(nullptr, *ObjectPath);
1176
-
1177
- // Compatibility: allow passing actor label/name/path as objectPath.
1178
- if (!TargetObject) {
1179
- if (AActor *FoundActor = FindActorByName(ObjectPath)) {
1180
- TargetObject = FoundActor;
1181
- ObjectPath = FoundActor->GetPathName();
1182
- }
1183
- }
1184
- if (!TargetObject) {
1185
- SendAutomationResponse(
1186
- RequestingSocket, RequestId, false,
1187
- FString::Printf(TEXT("Object not found: %s"), *ObjectPath), nullptr,
1188
- TEXT("OBJECT_NOT_FOUND"));
1189
- return true;
1190
- }
1191
-
1192
- UClass *ObjectClass = TargetObject->GetClass();
1193
- FProperty *Property = ObjectClass->FindPropertyByName(*PropertyName);
1194
-
1195
- if (!Property) {
1196
- SendAutomationResponse(
1197
- RequestingSocket, RequestId, false,
1198
- FString::Printf(TEXT("Property not found: %s"), *PropertyName),
1199
- nullptr, TEXT("PROPERTY_NOT_FOUND"));
1200
- return true;
1201
- }
1202
-
1203
- Result->SetStringField(TEXT("objectPath"), ObjectPath);
1204
- Result->SetStringField(TEXT("propertyName"), PropertyName);
1205
- Result->SetStringField(TEXT("propertyType"),
1206
- Property->GetClass()->GetName());
1207
-
1208
- // Return value as string for broad compatibility.
1209
- FString ValueText;
1210
- const void *ValuePtr = Property->ContainerPtrToValuePtr<void>(TargetObject);
1211
- Property->ExportTextItem_Direct(ValueText, ValuePtr, nullptr, TargetObject,
1212
- PPF_None);
1213
- Result->SetStringField(TEXT("value"), ValueText);
1214
-
1215
- SendAutomationResponse(RequestingSocket, RequestId, true,
1216
- FString::Printf(TEXT("Retrieved property: %s.%s"),
1217
- *ObjectPath, *PropertyName),
1218
- Result, FString());
1219
- return true;
1220
- }
1221
-
1222
- // Set property (simplified implementation)
1223
- if (LowerSub == TEXT("set_property")) {
1224
- FString ObjectPath;
1225
- FString PropertyName;
1226
-
1227
- if (!Payload->TryGetStringField(TEXT("objectPath"), ObjectPath) ||
1228
- !Payload->TryGetStringField(TEXT("propertyName"), PropertyName)) {
1229
- SendAutomationResponse(
1230
- RequestingSocket, RequestId, false,
1231
- TEXT("set_property requires objectPath and propertyName parameters"),
1232
- nullptr, TEXT("INVALID_ARGUMENT"));
1233
- return true;
1234
- }
1235
-
1236
- // Critical Property Protection
1237
- TArray<FString> ProtectedProperties = {TEXT("Class"), TEXT("Outer"),
1238
- TEXT("Archetype"), TEXT("Linker"),
1239
- TEXT("LinkerIndex")};
1240
- if (ProtectedProperties.Contains(PropertyName)) {
1241
- SendAutomationResponse(
1242
- RequestingSocket, RequestId, false,
1243
- FString::Printf(
1244
- TEXT("Modification of critical property '%s' is blocked"),
1245
- *PropertyName),
1246
- nullptr, TEXT("PROPERTY_BLOCKED"));
1247
- return true;
1248
- }
1249
-
1250
- UObject *TargetObject = FindObject<UObject>(nullptr, *ObjectPath);
1251
-
1252
- // Compatibility: allow passing actor label/name/path as objectPath.
1253
- if (!TargetObject) {
1254
- if (AActor *FoundActor = FindActorByName(ObjectPath)) {
1255
- TargetObject = FoundActor;
1256
- ObjectPath = FoundActor->GetPathName();
1257
- }
1258
- }
1259
- if (!TargetObject) {
1260
- SendAutomationResponse(
1261
- RequestingSocket, RequestId, false,
1262
- FString::Printf(TEXT("Object not found: %s"), *ObjectPath), nullptr,
1263
- TEXT("OBJECT_NOT_FOUND"));
1264
- return true;
1265
- }
1266
-
1267
- // Get the property value from payload
1268
- FString PropertyValue;
1269
- if (!Payload->TryGetStringField(TEXT("value"), PropertyValue)) {
1270
- SendAutomationResponse(RequestingSocket, RequestId, false,
1271
- TEXT("set_property requires 'value' field"),
1272
- nullptr, TEXT("INVALID_ARGUMENT"));
1273
- return true;
1274
- }
1275
-
1276
- // Find the property using Unreal's reflection system
1277
- FProperty *FoundProperty =
1278
- TargetObject->GetClass()->FindPropertyByName(FName(*PropertyName));
1279
- if (!FoundProperty) {
1280
- SendAutomationResponse(
1281
- RequestingSocket, RequestId, false,
1282
- FString::Printf(TEXT("Property '%s' not found on object '%s'"),
1283
- *PropertyName, *ObjectPath),
1284
- nullptr, TEXT("PROPERTY_NOT_FOUND"));
1285
- return true;
1286
- }
1287
-
1288
- // Set the property value based on type
1289
- bool bSuccess = false;
1290
- FString ErrorMessage;
1291
-
1292
- if (FStrProperty *StrProp = CastField<FStrProperty>(FoundProperty)) {
1293
- void *PropAddr = StrProp->ContainerPtrToValuePtr<void>(TargetObject);
1294
- StrProp->SetPropertyValue(PropAddr, PropertyValue);
1295
- bSuccess = true;
1296
- } else if (FFloatProperty *FloatProp =
1297
- CastField<FFloatProperty>(FoundProperty)) {
1298
- void *PropAddr = FloatProp->ContainerPtrToValuePtr<void>(TargetObject);
1299
- float Value = FCString::Atof(*PropertyValue);
1300
- FloatProp->SetPropertyValue(PropAddr, Value);
1301
- bSuccess = true;
1302
- } else if (FDoubleProperty *DoubleProp =
1303
- CastField<FDoubleProperty>(FoundProperty)) {
1304
- void *PropAddr = DoubleProp->ContainerPtrToValuePtr<void>(TargetObject);
1305
- double Value = FCString::Atod(*PropertyValue);
1306
- DoubleProp->SetPropertyValue(PropAddr, Value);
1307
- bSuccess = true;
1308
- } else if (FIntProperty *IntProp = CastField<FIntProperty>(FoundProperty)) {
1309
- void *PropAddr = IntProp->ContainerPtrToValuePtr<void>(TargetObject);
1310
- int32 Value = FCString::Atoi(*PropertyValue);
1311
- IntProp->SetPropertyValue(PropAddr, Value);
1312
- bSuccess = true;
1313
- } else if (FInt64Property *Int64Prop =
1314
- CastField<FInt64Property>(FoundProperty)) {
1315
- void *PropAddr = Int64Prop->ContainerPtrToValuePtr<void>(TargetObject);
1316
- int64 Value = FCString::Atoi64(*PropertyValue);
1317
- Int64Prop->SetPropertyValue(PropAddr, Value);
1318
- bSuccess = true;
1319
- } else if (FBoolProperty *BoolProp =
1320
- CastField<FBoolProperty>(FoundProperty)) {
1321
- void *PropAddr = BoolProp->ContainerPtrToValuePtr<void>(TargetObject);
1322
- bool Value = PropertyValue.ToBool();
1323
- BoolProp->SetPropertyValue(PropAddr, Value);
1324
- bSuccess = true;
1325
- } else if (FObjectProperty *ObjProp =
1326
- CastField<FObjectProperty>(FoundProperty)) {
1327
- // Try to find the object by path
1328
- UObject *ObjValue = FindObject<UObject>(nullptr, *PropertyValue);
1329
- if (ObjValue || PropertyValue.IsEmpty()) {
1330
- void *PropAddr = ObjProp->ContainerPtrToValuePtr<void>(TargetObject);
1331
- ObjProp->SetPropertyValue(PropAddr, ObjValue);
1332
- bSuccess = true;
1333
- } else {
1334
- ErrorMessage = FString::Printf(
1335
- TEXT("Object property requires valid object path, got: %s"),
1336
- *PropertyValue);
1337
- }
1338
- } else if (FStructProperty *StructProp =
1339
- CastField<FStructProperty>(FoundProperty)) {
1340
- // Handle struct properties (FVector, FVector2D, FLinearColor, etc.)
1341
- void *PropAddr = StructProp->ContainerPtrToValuePtr<void>(TargetObject);
1342
- FString StructName =
1343
- StructProp->Struct ? StructProp->Struct->GetName() : FString();
1344
-
1345
- // Try to parse JSON object value from payload
1346
- const TSharedPtr<FJsonObject> *JsonObjValue = nullptr;
1347
- if (Payload->TryGetObjectField(TEXT("value"), JsonObjValue) &&
1348
- JsonObjValue->IsValid()) {
1349
- // Handle FVector explicitly
1350
- if (StructName.Equals(TEXT("Vector"), ESearchCase::IgnoreCase)) {
1351
- FVector *Vec = static_cast<FVector *>(PropAddr);
1352
- double X = 0, Y = 0, Z = 0;
1353
- (*JsonObjValue)->TryGetNumberField(TEXT("X"), X);
1354
- (*JsonObjValue)->TryGetNumberField(TEXT("Y"), Y);
1355
- (*JsonObjValue)->TryGetNumberField(TEXT("Z"), Z);
1356
- if (X == 0 && Y == 0 && Z == 0) {
1357
- (*JsonObjValue)->TryGetNumberField(TEXT("x"), X);
1358
- (*JsonObjValue)->TryGetNumberField(TEXT("y"), Y);
1359
- (*JsonObjValue)->TryGetNumberField(TEXT("z"), Z);
1360
- }
1361
- *Vec = FVector(X, Y, Z);
1362
- bSuccess = true;
1363
- }
1364
- // Handle FVector2D
1365
- else if (StructName.Equals(TEXT("Vector2D"), ESearchCase::IgnoreCase)) {
1366
- FVector2D *Vec = static_cast<FVector2D *>(PropAddr);
1367
- double X = 0, Y = 0;
1368
- (*JsonObjValue)->TryGetNumberField(TEXT("X"), X);
1369
- (*JsonObjValue)->TryGetNumberField(TEXT("Y"), Y);
1370
- if (X == 0 && Y == 0) {
1371
- (*JsonObjValue)->TryGetNumberField(TEXT("x"), X);
1372
- (*JsonObjValue)->TryGetNumberField(TEXT("y"), Y);
1373
- }
1374
- *Vec = FVector2D(X, Y);
1375
- bSuccess = true;
1376
- }
1377
- // Handle FLinearColor
1378
- else if (StructName.Equals(TEXT("LinearColor"),
1379
- ESearchCase::IgnoreCase)) {
1380
- FLinearColor *Color = static_cast<FLinearColor *>(PropAddr);
1381
- double R = 0, G = 0, B = 0, A = 1;
1382
- (*JsonObjValue)->TryGetNumberField(TEXT("R"), R);
1383
- (*JsonObjValue)->TryGetNumberField(TEXT("G"), G);
1384
- (*JsonObjValue)->TryGetNumberField(TEXT("B"), B);
1385
- (*JsonObjValue)->TryGetNumberField(TEXT("A"), A);
1386
- if (R == 0 && G == 0 && B == 0) {
1387
- (*JsonObjValue)->TryGetNumberField(TEXT("r"), R);
1388
- (*JsonObjValue)->TryGetNumberField(TEXT("g"), G);
1389
- (*JsonObjValue)->TryGetNumberField(TEXT("b"), B);
1390
- (*JsonObjValue)->TryGetNumberField(TEXT("a"), A);
1391
- }
1392
- *Color = FLinearColor(R, G, B, A);
1393
- bSuccess = true;
1394
- }
1395
- // Handle FRotator
1396
- else if (StructName.Equals(TEXT("Rotator"), ESearchCase::IgnoreCase)) {
1397
- FRotator *Rot = static_cast<FRotator *>(PropAddr);
1398
- double Pitch = 0, Yaw = 0, Roll = 0;
1399
- (*JsonObjValue)->TryGetNumberField(TEXT("Pitch"), Pitch);
1400
- (*JsonObjValue)->TryGetNumberField(TEXT("Yaw"), Yaw);
1401
- (*JsonObjValue)->TryGetNumberField(TEXT("Roll"), Roll);
1402
- if (Pitch == 0 && Yaw == 0 && Roll == 0) {
1403
- (*JsonObjValue)->TryGetNumberField(TEXT("pitch"), Pitch);
1404
- (*JsonObjValue)->TryGetNumberField(TEXT("yaw"), Yaw);
1405
- (*JsonObjValue)->TryGetNumberField(TEXT("roll"), Roll);
1406
- }
1407
- *Rot = FRotator(Pitch, Yaw, Roll);
1408
- bSuccess = true;
1409
- }
1410
- }
1411
-
1412
- // Fallback: try ImportText for string representation
1413
- if (!bSuccess && !PropertyValue.IsEmpty() && StructProp->Struct) {
1414
- const TCHAR *Buffer = *PropertyValue;
1415
- // Use UScriptStruct::ImportText (not FStructProperty)
1416
- const TCHAR *ImportResult = StructProp->Struct->ImportText(
1417
- Buffer, PropAddr, nullptr, PPF_None, GWarn, StructName);
1418
- bSuccess = (ImportResult != nullptr);
1419
- if (!bSuccess) {
1420
- ErrorMessage = FString::Printf(
1421
- TEXT("Failed to parse struct value '%s' for property '%s' of "
1422
- "type '%s'. For FVector use {\"X\":val,\"Y\":val,\"Z\":val} "
1423
- "or string \"(X=val,Y=val,Z=val)\""),
1424
- *PropertyValue, *PropertyName, *StructName);
1425
- }
1426
- }
1427
-
1428
- if (!bSuccess && ErrorMessage.IsEmpty()) {
1429
- ErrorMessage = FString::Printf(
1430
- TEXT("Struct property '%s' of type '%s' requires JSON object "
1431
- "value like {\"X\":val,\"Y\":val,\"Z\":val}"),
1432
- *PropertyName, *StructName);
1433
- }
1434
- } else {
1435
- ErrorMessage =
1436
- FString::Printf(TEXT("Property type '%s' not supported for setting"),
1437
- *FoundProperty->GetClass()->GetName());
1438
- }
1439
-
1440
- if (bSuccess) {
1441
- Result->SetStringField(TEXT("objectPath"), ObjectPath);
1442
- Result->SetStringField(TEXT("propertyName"), PropertyName);
1443
- Result->SetStringField(TEXT("value"), PropertyValue);
1444
- SendAutomationResponse(RequestingSocket, RequestId, true,
1445
- TEXT("Property set successfully"), Result,
1446
- FString());
1447
- } else {
1448
- Result->SetStringField(TEXT("objectPath"), ObjectPath);
1449
- Result->SetStringField(TEXT("propertyName"), PropertyName);
1450
- Result->SetStringField(TEXT("error"), ErrorMessage);
1451
- SendAutomationResponse(RequestingSocket, RequestId, false,
1452
- TEXT("Failed to set property"), Result,
1453
- TEXT("PROPERTY_SET_FAILED"));
1454
- }
1455
- return true;
1456
- }
1457
-
1458
- // Get bounding box (get_bounding_box)
1459
- if (LowerSub == TEXT("get_bounding_box")) {
1460
- FString ActorName;
1461
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1462
- FString ObjectPath;
1463
- Payload->TryGetStringField(TEXT("objectPath"), ObjectPath);
1464
-
1465
- if (ActorName.IsEmpty() && ObjectPath.IsEmpty()) {
1466
- SendAutomationResponse(
1467
- RequestingSocket, RequestId, false,
1468
- TEXT("get_bounding_box requires actorName or objectPath"), nullptr,
1469
- TEXT("INVALID_ARGUMENT"));
1470
- return true;
1471
- }
1472
-
1473
- AActor *TargetActor = nullptr;
1474
- UPrimitiveComponent *PrimComp = nullptr;
1475
-
1476
- #if WITH_EDITOR
1477
- if (GEditor && !ActorName.IsEmpty()) {
1478
- UEditorActorSubsystem *ActorSS =
1479
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
1480
- if (ActorSS) {
1481
- TArray<AActor *> Actors = ActorSS->GetAllLevelActors();
1482
- for (AActor *A : Actors) {
1483
- if (A &&
1484
- (A->GetActorLabel() == ActorName || A->GetName() == ActorName)) {
1485
- TargetActor = A;
1486
- break;
1487
- }
1488
- }
1489
- }
1490
- }
1491
- #endif
1492
-
1493
- if (!TargetActor && !ObjectPath.IsEmpty()) {
1494
- UObject *Obj = FindObject<UObject>(nullptr, *ObjectPath);
1495
- if (Obj) {
1496
- if (AActor *A = Cast<AActor>(Obj)) {
1497
- TargetActor = A;
1498
- } else if (UPrimitiveComponent *PC = Cast<UPrimitiveComponent>(Obj)) {
1499
- PrimComp = PC;
1500
- }
1501
- }
1502
- }
1503
-
1504
- FBox Box(ForceInit);
1505
- bool bFound = false;
1506
-
1507
- if (TargetActor) {
1508
- Box = TargetActor->GetComponentsBoundingBox(true);
1509
- bFound = true;
1510
- } else if (PrimComp) {
1511
- Box = PrimComp->Bounds.GetBox();
1512
- bFound = true;
1513
- }
1514
-
1515
- if (bFound) {
1516
- FVector Origin = Box.GetCenter();
1517
- FVector Extent = Box.GetExtent();
1518
- TSharedPtr<FJsonObject> BoxObj = MakeShared<FJsonObject>();
1519
-
1520
- TSharedPtr<FJsonObject> OrgObj = MakeShared<FJsonObject>();
1521
- OrgObj->SetNumberField(TEXT("x"), Origin.X);
1522
- OrgObj->SetNumberField(TEXT("y"), Origin.Y);
1523
- OrgObj->SetNumberField(TEXT("z"), Origin.Z);
1524
- BoxObj->SetObjectField(TEXT("origin"), OrgObj);
1525
-
1526
- TSharedPtr<FJsonObject> ExtObj = MakeShared<FJsonObject>();
1527
- ExtObj->SetNumberField(TEXT("x"), Extent.X);
1528
- ExtObj->SetNumberField(TEXT("y"), Extent.Y);
1529
- ExtObj->SetNumberField(TEXT("z"), Extent.Z);
1530
- BoxObj->SetObjectField(TEXT("extent"), ExtObj);
1531
-
1532
- SendAutomationResponse(RequestingSocket, RequestId, true,
1533
- TEXT("Bounding box retrieved"), BoxObj, FString());
1534
- } else {
1535
- SendAutomationResponse(RequestingSocket, RequestId, false,
1536
- TEXT("Object not found or has no bounds"), nullptr,
1537
- TEXT("OBJECT_NOT_FOUND"));
1538
- }
1539
- return true;
1540
- }
1541
-
1542
- // Get components (get_components)
1543
- if (LowerSub == TEXT("get_components")) {
1544
- FString ObjectPath;
1545
- if (!Payload->TryGetStringField(TEXT("objectPath"), ObjectPath)) {
1546
- Payload->TryGetStringField(TEXT("actorName"), ObjectPath);
1547
- }
1548
-
1549
- if (ObjectPath.IsEmpty()) {
1550
- SendAutomationResponse(
1551
- RequestingSocket, RequestId, false,
1552
- TEXT("get_components requires objectPath or actorName"), nullptr,
1553
- TEXT("INVALID_ARGUMENT"));
1554
- return true;
1555
- }
1556
-
1557
- AActor *FoundActor = FindActorByName(ObjectPath);
1558
- if (!FoundActor) {
1559
- if (UObject *Asset = UEditorAssetLibrary::LoadAsset(ObjectPath)) {
1560
- if (UBlueprint *BP = Cast<UBlueprint>(Asset)) {
1561
- if (BP->GeneratedClass) {
1562
- FoundActor = Cast<AActor>(BP->GeneratedClass->GetDefaultObject());
1563
- }
1564
- }
1565
- }
1566
- }
1567
-
1568
- if (!FoundActor) {
1569
- SendAutomationResponse(
1570
- RequestingSocket, RequestId, false,
1571
- FString::Printf(TEXT("Actor or Blueprint not found: %s"),
1572
- *ObjectPath),
1573
- nullptr, TEXT("OBJECT_NOT_FOUND"));
1574
- return true;
1575
- }
1576
-
1577
- TSharedPtr<FJsonObject> ComponentsObj = MakeShared<FJsonObject>();
1578
- TArray<TSharedPtr<FJsonValue>> ComponentList;
1579
-
1580
- for (UActorComponent *Comp : FoundActor->GetComponents()) {
1581
- if (!Comp)
1582
- continue;
1583
- TSharedPtr<FJsonObject> CompData = MakeShared<FJsonObject>();
1584
- CompData->SetStringField(TEXT("name"), Comp->GetName());
1585
- CompData->SetStringField(TEXT("class"), Comp->GetClass()->GetName());
1586
- CompData->SetStringField(TEXT("path"), Comp->GetPathName());
1587
-
1588
- if (USceneComponent *SceneComp = Cast<USceneComponent>(Comp)) {
1589
- CompData->SetBoolField(TEXT("isSceneComponent"), true);
1590
- TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
1591
- FVector Loc = SceneComp->GetRelativeLocation();
1592
- LocObj->SetNumberField("x", Loc.X);
1593
- LocObj->SetNumberField("y", Loc.Y);
1594
- LocObj->SetNumberField("z", Loc.Z);
1595
- CompData->SetObjectField("relativeLocation", LocObj);
1596
- }
1597
-
1598
- ComponentList.Add(MakeShared<FJsonValueObject>(CompData));
1599
- }
1600
-
1601
- TSharedPtr<FJsonObject> ComponentsResult = MakeShared<FJsonObject>();
1602
- ComponentsResult->SetArrayField(TEXT("components"), ComponentList);
1603
- ComponentsResult->SetNumberField(TEXT("count"), ComponentList.Num());
1604
-
1605
- SendAutomationResponse(RequestingSocket, RequestId, true,
1606
- TEXT("Actor components retrieved"), ComponentsResult,
1607
- FString());
1608
- return true;
1609
- }
1610
-
1611
- // Find by class (find_by_class)
1612
- if (LowerSub == TEXT("find_by_class")) {
1613
- #if WITH_EDITOR
1614
- FString ClassName;
1615
- Payload->TryGetStringField(TEXT("className"), ClassName);
1616
- // Also accept classPath as alias
1617
- if (ClassName.IsEmpty()) {
1618
- Payload->TryGetStringField(TEXT("classPath"), ClassName);
1619
- }
1620
-
1621
- if (GEditor) {
1622
- UEditorActorSubsystem *ActorSS =
1623
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
1624
- if (ActorSS) {
1625
- TArray<AActor *> Actors = ActorSS->GetAllLevelActors();
1626
- TArray<TSharedPtr<FJsonValue>> Matches;
1627
-
1628
- // Normalize class name for matching
1629
- FString SearchName = ClassName;
1630
- FString SearchNameLower = ClassName.ToLower();
1631
-
1632
- // Common prefix variants to try
1633
- TArray<FString> SearchVariants;
1634
- SearchVariants.Add(SearchName);
1635
- if (!SearchName.StartsWith(TEXT("A")) &&
1636
- !SearchName.Contains(TEXT("/"))) {
1637
- SearchVariants.Add(TEXT("A") + SearchName); // AActor pattern
1638
- }
1639
- if (!SearchName.StartsWith(TEXT("U")) &&
1640
- !SearchName.Contains(TEXT("/"))) {
1641
- SearchVariants.Add(TEXT("U") + SearchName); // UObject pattern
1642
- }
1643
-
1644
- for (AActor *Actor : Actors) {
1645
- if (!Actor)
1646
- continue;
1647
- FString ActorClassName = Actor->GetClass()->GetName();
1648
- FString ActorClassPath = Actor->GetClass()->GetPathName();
1649
- FString ActorClassLower = ActorClassName.ToLower();
1650
-
1651
- bool bMatches = false;
1652
- if (ClassName.IsEmpty()) {
1653
- bMatches = true; // Return all actors if no filter
1654
- } else {
1655
- // Check all variants
1656
- for (const FString &Variant : SearchVariants) {
1657
- if (ActorClassName.Equals(Variant, ESearchCase::IgnoreCase) ||
1658
- ActorClassName.Contains(Variant, ESearchCase::IgnoreCase) ||
1659
- ActorClassPath.Contains(Variant, ESearchCase::IgnoreCase)) {
1660
- bMatches = true;
1661
- break;
1662
- }
1663
- }
1664
- }
1665
-
1666
- if (bMatches) {
1667
- TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1668
- Entry->SetStringField(TEXT("name"), Actor->GetActorLabel());
1669
- Entry->SetStringField(TEXT("path"), Actor->GetPathName());
1670
- Entry->SetStringField(TEXT("class"), ActorClassPath);
1671
- Entry->SetStringField(TEXT("classShort"), ActorClassName);
1672
- Matches.Add(MakeShared<FJsonValueObject>(Entry));
1673
- }
1674
- }
1675
-
1676
- Result->SetBoolField(TEXT("success"), true);
1677
- Result->SetArrayField(TEXT("actors"), Matches);
1678
- Result->SetNumberField(TEXT("count"), Matches.Num());
1679
- Result->SetStringField(TEXT("searchedFor"), ClassName);
1680
- SendAutomationResponse(RequestingSocket, RequestId, true,
1681
- TEXT("Found actors by class"), Result,
1682
- FString());
1683
- return true;
1684
- }
1685
- }
1686
- SendAutomationResponse(RequestingSocket, RequestId, false,
1687
- TEXT("Editor not available"), nullptr,
1688
- TEXT("EDITOR_NOT_AVAILABLE"));
1689
- return true;
1690
- #else
1691
- SendAutomationResponse(RequestingSocket, RequestId, false,
1692
- TEXT("find_by_class requires editor build"), nullptr,
1693
- TEXT("NOT_IMPLEMENTED"));
1694
- return true;
1695
- #endif
1696
- }
1697
-
1698
- // Inspect class (inspect_class)
1699
- if (LowerSub == TEXT("inspect_class")) {
1700
- FString ClassPath;
1701
- if (!Payload->TryGetStringField(TEXT("classPath"), ClassPath)) {
1702
- SendAutomationResponse(RequestingSocket, RequestId, false,
1703
- TEXT("classPath required"), nullptr,
1704
- TEXT("INVALID_ARGUMENT"));
1705
- return true;
1706
- }
1707
-
1708
- UClass *ResolvedClass = ResolveClassByName(ClassPath);
1709
- if (!ResolvedClass) {
1710
- // Try loading as asset
1711
- if (UObject *Found =
1712
- StaticLoadObject(UObject::StaticClass(), nullptr, *ClassPath)) {
1713
- if (UBlueprint *BP = Cast<UBlueprint>(Found))
1714
- ResolvedClass = BP->GeneratedClass;
1715
- else if (UClass *C = Cast<UClass>(Found))
1716
- ResolvedClass = C;
1717
- }
1718
- }
1719
-
1720
- if (ResolvedClass) {
1721
- Result->SetStringField(TEXT("className"), ResolvedClass->GetName());
1722
- Result->SetStringField(TEXT("classPath"), ResolvedClass->GetPathName());
1723
- if (ResolvedClass->GetSuperClass())
1724
- Result->SetStringField(TEXT("parentClass"),
1725
- ResolvedClass->GetSuperClass()->GetName());
1726
-
1727
- // List properties
1728
- TArray<TSharedPtr<FJsonValue>> Props;
1729
- for (TFieldIterator<FProperty> PropIt(ResolvedClass); PropIt; ++PropIt) {
1730
- FProperty *Prop = *PropIt;
1731
- TSharedPtr<FJsonObject> P = MakeShared<FJsonObject>();
1732
- P->SetStringField(TEXT("name"), Prop->GetName());
1733
- P->SetStringField(TEXT("type"), Prop->GetClass()->GetName());
1734
- Props.Add(MakeShared<FJsonValueObject>(P));
1735
- }
1736
- Result->SetArrayField(TEXT("properties"), Props);
1737
-
1738
- SendAutomationResponse(RequestingSocket, RequestId, true,
1739
- TEXT("Class inspected"), Result, FString());
1740
- return true;
1741
- }
1742
-
1743
- SendAutomationResponse(RequestingSocket, RequestId, false,
1744
- TEXT("Class not found"), nullptr,
1745
- TEXT("CLASS_NOT_FOUND"));
1746
- return true;
1747
- }
1748
-
1749
- // Get components (get_components) - enumerate all components on an actor
1750
- if (LowerSub == TEXT("get_components")) {
1751
- #if WITH_EDITOR
1752
- FString ActorName;
1753
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1754
- FString ObjectPath;
1755
- Payload->TryGetStringField(TEXT("objectPath"), ObjectPath);
1756
-
1757
- if (ActorName.IsEmpty() && ObjectPath.IsEmpty()) {
1758
- SendAutomationResponse(
1759
- RequestingSocket, RequestId, false,
1760
- TEXT("get_components requires actorName or objectPath"), nullptr,
1761
- TEXT("INVALID_ARGUMENT"));
1762
- return true;
1763
- }
1764
-
1765
- AActor *TargetActor = nullptr;
1766
- if (!ActorName.IsEmpty()) {
1767
- TargetActor = FindActorByName(ActorName);
1768
- }
1769
- if (!TargetActor && !ObjectPath.IsEmpty()) {
1770
- TargetActor = FindActorByName(ObjectPath);
1771
- }
1772
-
1773
- if (!TargetActor) {
1774
- SendAutomationResponse(
1775
- RequestingSocket, RequestId, false,
1776
- FString::Printf(TEXT("Failed to get components for actor %s"),
1777
- ActorName.IsEmpty() ? *ObjectPath : *ActorName),
1778
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1779
- return true;
1780
- }
1781
-
1782
- TArray<TSharedPtr<FJsonValue>> ComponentsArray;
1783
- for (UActorComponent *Comp : TargetActor->GetComponents()) {
1784
- if (!Comp)
1785
- continue;
1786
- TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1787
- Entry->SetStringField(TEXT("name"), Comp->GetName());
1788
- Entry->SetStringField(TEXT("readableName"), Comp->GetReadableName());
1789
- Entry->SetStringField(TEXT("class"), Comp->GetClass()
1790
- ? Comp->GetClass()->GetPathName()
1791
- : TEXT(""));
1792
- Entry->SetStringField(TEXT("path"), Comp->GetPathName());
1793
- if (USceneComponent *SceneComp = Cast<USceneComponent>(Comp)) {
1794
- FVector Loc = SceneComp->GetRelativeLocation();
1795
- FRotator Rot = SceneComp->GetRelativeRotation();
1796
- FVector Scale = SceneComp->GetRelativeScale3D();
1797
-
1798
- TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
1799
- LocObj->SetNumberField(TEXT("x"), Loc.X);
1800
- LocObj->SetNumberField(TEXT("y"), Loc.Y);
1801
- LocObj->SetNumberField(TEXT("z"), Loc.Z);
1802
- Entry->SetObjectField(TEXT("relativeLocation"), LocObj);
1803
-
1804
- TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
1805
- RotObj->SetNumberField(TEXT("pitch"), Rot.Pitch);
1806
- RotObj->SetNumberField(TEXT("yaw"), Rot.Yaw);
1807
- RotObj->SetNumberField(TEXT("roll"), Rot.Roll);
1808
- Entry->SetObjectField(TEXT("relativeRotation"), RotObj);
1809
-
1810
- TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
1811
- ScaleObj->SetNumberField(TEXT("x"), Scale.X);
1812
- ScaleObj->SetNumberField(TEXT("y"), Scale.Y);
1813
- ScaleObj->SetNumberField(TEXT("z"), Scale.Z);
1814
- Entry->SetObjectField(TEXT("relativeScale"), ScaleObj);
1815
- }
1816
- ComponentsArray.Add(MakeShared<FJsonValueObject>(Entry));
1817
- }
1818
-
1819
- Result->SetArrayField(TEXT("components"), ComponentsArray);
1820
- Result->SetNumberField(TEXT("count"), ComponentsArray.Num());
1821
- Result->SetStringField(TEXT("actorName"), TargetActor->GetActorLabel());
1822
- SendAutomationResponse(RequestingSocket, RequestId, true,
1823
- TEXT("Actor components retrieved"), Result,
1824
- FString());
1825
- return true;
1826
- #else
1827
- SendAutomationResponse(RequestingSocket, RequestId, false,
1828
- TEXT("get_components requires editor build"),
1829
- nullptr, TEXT("NOT_IMPLEMENTED"));
1830
- return true;
1831
- #endif
1832
- }
1833
-
1834
- // Get component property (get_component_property)
1835
- if (LowerSub == TEXT("get_component_property")) {
1836
- #if WITH_EDITOR
1837
- FString ActorName;
1838
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1839
- FString ObjectPath;
1840
- Payload->TryGetStringField(TEXT("objectPath"), ObjectPath);
1841
- FString ComponentName;
1842
- Payload->TryGetStringField(TEXT("componentName"), ComponentName);
1843
- FString PropertyName;
1844
- Payload->TryGetStringField(TEXT("propertyName"), PropertyName);
1845
-
1846
- if ((ActorName.IsEmpty() && ObjectPath.IsEmpty()) ||
1847
- ComponentName.IsEmpty() || PropertyName.IsEmpty()) {
1848
- SendAutomationResponse(RequestingSocket, RequestId, false,
1849
- TEXT("get_component_property requires "
1850
- "actorName/objectPath, componentName, and "
1851
- "propertyName"),
1852
- nullptr, TEXT("INVALID_ARGUMENT"));
1853
- return true;
1854
- }
1855
-
1856
- AActor *TargetActor = nullptr;
1857
- if (!ActorName.IsEmpty()) {
1858
- TargetActor = FindActorByName(ActorName);
1859
- }
1860
- if (!TargetActor && !ObjectPath.IsEmpty()) {
1861
- TargetActor = FindActorByName(ObjectPath);
1862
- }
1863
-
1864
- if (!TargetActor) {
1865
- SendAutomationResponse(RequestingSocket, RequestId, false,
1866
- TEXT("Actor not found"), nullptr,
1867
- TEXT("ACTOR_NOT_FOUND"));
1868
- return true;
1869
- }
1870
-
1871
- // Find component by name (fuzzy matching)
1872
- UActorComponent *TargetComponent = nullptr;
1873
- for (UActorComponent *Comp : TargetActor->GetComponents()) {
1874
- if (!Comp)
1875
- continue;
1876
- if (Comp->GetName().Equals(ComponentName, ESearchCase::IgnoreCase) ||
1877
- Comp->GetReadableName().Equals(ComponentName,
1878
- ESearchCase::IgnoreCase) ||
1879
- Comp->GetName().Contains(ComponentName, ESearchCase::IgnoreCase)) {
1880
- TargetComponent = Comp;
1881
- break;
1882
- }
1883
- }
1884
-
1885
- if (!TargetComponent) {
1886
- SendAutomationResponse(
1887
- RequestingSocket, RequestId, false,
1888
- FString::Printf(TEXT("Component not found on actor '%s': %s"),
1889
- *TargetActor->GetActorLabel(), *ComponentName),
1890
- nullptr, TEXT("COMPONENT_NOT_FOUND"));
1891
- return true;
1892
- }
1893
-
1894
- FProperty *Property =
1895
- TargetComponent->GetClass()->FindPropertyByName(*PropertyName);
1896
- if (!Property) {
1897
- SendAutomationResponse(
1898
- RequestingSocket, RequestId, false,
1899
- FString::Printf(TEXT("Property not found: %s"), *PropertyName),
1900
- nullptr, TEXT("PROPERTY_NOT_FOUND"));
1901
- return true;
1902
- }
1903
-
1904
- FString ValueText;
1905
- const void *ValuePtr =
1906
- Property->ContainerPtrToValuePtr<void>(TargetComponent);
1907
- Property->ExportTextItem_Direct(ValueText, ValuePtr, nullptr,
1908
- TargetComponent, PPF_None);
1909
-
1910
- Result->SetStringField(TEXT("componentName"), TargetComponent->GetName());
1911
- Result->SetStringField(TEXT("propertyName"), PropertyName);
1912
- Result->SetStringField(TEXT("value"), ValueText);
1913
- Result->SetStringField(TEXT("propertyType"),
1914
- Property->GetClass()->GetName());
1915
- SendAutomationResponse(RequestingSocket, RequestId, true,
1916
- TEXT("Component property retrieved"), Result,
1917
- FString());
1918
- return true;
1919
- #else
1920
- SendAutomationResponse(RequestingSocket, RequestId, false,
1921
- TEXT("get_component_property requires editor build"),
1922
- nullptr, TEXT("NOT_IMPLEMENTED"));
1923
- return true;
1924
- #endif
1925
- }
1926
-
1927
- // Set component property (set_component_property)
1928
- if (LowerSub == TEXT("set_component_property")) {
1929
- #if WITH_EDITOR
1930
- FString ActorName;
1931
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1932
- FString ObjectPath;
1933
- Payload->TryGetStringField(TEXT("objectPath"), ObjectPath);
1934
- FString ComponentName;
1935
- Payload->TryGetStringField(TEXT("componentName"), ComponentName);
1936
- FString PropertyName;
1937
- Payload->TryGetStringField(TEXT("propertyName"), PropertyName);
1938
-
1939
- if ((ActorName.IsEmpty() && ObjectPath.IsEmpty()) ||
1940
- ComponentName.IsEmpty() || PropertyName.IsEmpty()) {
1941
- SendAutomationResponse(RequestingSocket, RequestId, false,
1942
- TEXT("set_component_property requires "
1943
- "actorName/objectPath, componentName, and "
1944
- "propertyName"),
1945
- nullptr, TEXT("INVALID_ARGUMENT"));
1946
- return true;
1947
- }
1948
-
1949
- AActor *TargetActor = nullptr;
1950
- if (!ActorName.IsEmpty()) {
1951
- TargetActor = FindActorByName(ActorName);
1952
- }
1953
- if (!TargetActor && !ObjectPath.IsEmpty()) {
1954
- TargetActor = FindActorByName(ObjectPath);
1955
- }
1956
-
1957
- if (!TargetActor) {
1958
- SendAutomationResponse(RequestingSocket, RequestId, false,
1959
- TEXT("Actor not found"), nullptr,
1960
- TEXT("ACTOR_NOT_FOUND"));
1961
- return true;
1962
- }
1963
-
1964
- // Find component by name (fuzzy matching)
1965
- UActorComponent *TargetComponent = nullptr;
1966
- for (UActorComponent *Comp : TargetActor->GetComponents()) {
1967
- if (!Comp)
1968
- continue;
1969
- if (Comp->GetName().Equals(ComponentName, ESearchCase::IgnoreCase) ||
1970
- Comp->GetReadableName().Equals(ComponentName,
1971
- ESearchCase::IgnoreCase) ||
1972
- Comp->GetName().Contains(ComponentName, ESearchCase::IgnoreCase)) {
1973
- TargetComponent = Comp;
1974
- break;
1975
- }
1976
- }
1977
-
1978
- if (!TargetComponent) {
1979
- SendAutomationResponse(
1980
- RequestingSocket, RequestId, false,
1981
- FString::Printf(TEXT("Component not found on actor '%s': %s"),
1982
- *TargetActor->GetActorLabel(), *ComponentName),
1983
- nullptr, TEXT("COMPONENT_NOT_FOUND"));
1984
- return true;
1985
- }
1986
-
1987
- FString PropertyValue;
1988
- if (!Payload->TryGetStringField(TEXT("value"), PropertyValue)) {
1989
- SendAutomationResponse(RequestingSocket, RequestId, false,
1990
- TEXT("set_component_property requires 'value'"),
1991
- nullptr, TEXT("INVALID_ARGUMENT"));
1992
- return true;
1993
- }
1994
-
1995
- FProperty *FoundProperty =
1996
- TargetComponent->GetClass()->FindPropertyByName(FName(*PropertyName));
1997
- if (!FoundProperty) {
1998
- SendAutomationResponse(
1999
- RequestingSocket, RequestId, false,
2000
- FString::Printf(TEXT("Property '%s' not found on component"),
2001
- *PropertyName),
2002
- nullptr, TEXT("PROPERTY_NOT_FOUND"));
2003
- return true;
2004
- }
2005
-
2006
- bool bSuccess = false;
2007
- FString ErrorMessage;
2008
-
2009
- if (FStrProperty *StrProp = CastField<FStrProperty>(FoundProperty)) {
2010
- void *PropAddr = StrProp->ContainerPtrToValuePtr<void>(TargetComponent);
2011
- StrProp->SetPropertyValue(PropAddr, PropertyValue);
2012
- bSuccess = true;
2013
- } else if (FFloatProperty *FloatProp =
2014
- CastField<FFloatProperty>(FoundProperty)) {
2015
- void *PropAddr = FloatProp->ContainerPtrToValuePtr<void>(TargetComponent);
2016
- float Value = FCString::Atof(*PropertyValue);
2017
- FloatProp->SetPropertyValue(PropAddr, Value);
2018
- bSuccess = true;
2019
- } else if (FDoubleProperty *DoubleProp =
2020
- CastField<FDoubleProperty>(FoundProperty)) {
2021
- void *PropAddr =
2022
- DoubleProp->ContainerPtrToValuePtr<void>(TargetComponent);
2023
- double Value = FCString::Atod(*PropertyValue);
2024
- DoubleProp->SetPropertyValue(PropAddr, Value);
2025
- bSuccess = true;
2026
- } else if (FIntProperty *IntProp = CastField<FIntProperty>(FoundProperty)) {
2027
- void *PropAddr = IntProp->ContainerPtrToValuePtr<void>(TargetComponent);
2028
- int32 Value = FCString::Atoi(*PropertyValue);
2029
- IntProp->SetPropertyValue(PropAddr, Value);
2030
- bSuccess = true;
2031
- } else if (FBoolProperty *BoolProp =
2032
- CastField<FBoolProperty>(FoundProperty)) {
2033
- void *PropAddr = BoolProp->ContainerPtrToValuePtr<void>(TargetComponent);
2034
- bool Value = PropertyValue.ToBool();
2035
- BoolProp->SetPropertyValue(PropAddr, Value);
2036
- bSuccess = true;
2037
- } else {
2038
- ErrorMessage =
2039
- FString::Printf(TEXT("Property type '%s' not supported for setting"),
2040
- *FoundProperty->GetClass()->GetName());
2041
- }
2042
-
2043
- if (bSuccess) {
2044
- if (USceneComponent *SceneComponent =
2045
- Cast<USceneComponent>(TargetComponent)) {
2046
- SceneComponent->MarkRenderStateDirty();
2047
- SceneComponent->UpdateComponentToWorld();
2048
- }
2049
- TargetComponent->MarkPackageDirty();
2050
-
2051
- Result->SetStringField(TEXT("componentName"), TargetComponent->GetName());
2052
- Result->SetStringField(TEXT("propertyName"), PropertyName);
2053
- Result->SetStringField(TEXT("value"), PropertyValue);
2054
- SendAutomationResponse(RequestingSocket, RequestId, true,
2055
- TEXT("Component property set"), Result, FString());
2056
- } else {
2057
- Result->SetStringField(TEXT("error"), ErrorMessage);
2058
- SendAutomationResponse(RequestingSocket, RequestId, false,
2059
- TEXT("Failed to set component property"), Result,
2060
- TEXT("PROPERTY_SET_FAILED"));
2061
- }
2062
- return true;
2063
- #else
2064
- SendAutomationResponse(RequestingSocket, RequestId, false,
2065
- TEXT("set_component_property requires editor build"),
2066
- nullptr, TEXT("NOT_IMPLEMENTED"));
2067
- return true;
2068
- #endif
2069
- }
2070
-
2071
- return true;
2072
- }
2073
-
2074
- bool UMcpAutomationBridgeSubsystem::HandleCreateProceduralTerrain(
2075
- const FString &RequestId, const FString &Action,
2076
- const TSharedPtr<FJsonObject> &Payload,
2077
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2078
- const FString Lower = Action.ToLower();
2079
- if (!Lower.Equals(TEXT("create_procedural_terrain"),
2080
- ESearchCase::IgnoreCase)) {
2081
- return false;
2082
- }
2083
-
2084
- #if WITH_EDITOR
2085
- if (!Payload.IsValid()) {
2086
- SendAutomationError(RequestingSocket, RequestId,
2087
- TEXT("create_procedural_terrain payload missing"),
2088
- TEXT("INVALID_PAYLOAD"));
2089
- return true;
2090
- }
2091
-
2092
- FString Name;
2093
- if (!Payload->TryGetStringField(TEXT("name"), Name) || Name.IsEmpty()) {
2094
- SendAutomationError(RequestingSocket, RequestId, TEXT("name required"),
2095
- TEXT("INVALID_ARGUMENT"));
2096
- return true;
2097
- }
2098
-
2099
- FVector Location(0, 0, 0);
2100
- const TArray<TSharedPtr<FJsonValue>> *LocArr = nullptr;
2101
- if (Payload->TryGetArrayField(TEXT("location"), LocArr) && LocArr &&
2102
- LocArr->Num() >= 3) {
2103
- Location.X = (*LocArr)[0]->AsNumber();
2104
- Location.Y = (*LocArr)[1]->AsNumber();
2105
- Location.Z = (*LocArr)[2]->AsNumber();
2106
- }
2107
-
2108
- double SizeX = 2000.0;
2109
- double SizeY = 2000.0;
2110
- Payload->TryGetNumberField(TEXT("sizeX"), SizeX);
2111
- Payload->TryGetNumberField(TEXT("sizeY"), SizeY);
2112
-
2113
- int32 Subdivisions = 50;
2114
- Payload->TryGetNumberField(TEXT("subdivisions"), Subdivisions);
2115
- Subdivisions = FMath::Clamp(Subdivisions, 2, 255);
2116
-
2117
- FString MaterialPath;
2118
- Payload->TryGetStringField(TEXT("material"), MaterialPath);
2119
-
2120
- if (!GEditor) {
2121
- SendAutomationError(RequestingSocket, RequestId,
2122
- TEXT("Editor not available"),
2123
- TEXT("EDITOR_NOT_AVAILABLE"));
2124
- return true;
2125
- }
2126
-
2127
- UEditorActorSubsystem *ActorSS =
2128
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
2129
- if (!ActorSS) {
2130
- SendAutomationError(RequestingSocket, RequestId,
2131
- TEXT("EditorActorSubsystem not available"),
2132
- TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
2133
- return true;
2134
- }
2135
-
2136
- AActor *NewActor = SpawnActorInActiveWorld<AActor>(
2137
- AActor::StaticClass(), Location, FRotator::ZeroRotator, Name);
2138
- if (!NewActor) {
2139
- SendAutomationError(RequestingSocket, RequestId,
2140
- TEXT("Failed to spawn actor"), TEXT("SPAWN_FAILED"));
2141
- return true;
2142
- }
2143
-
2144
- UProceduralMeshComponent *ProcMesh = NewObject<UProceduralMeshComponent>(
2145
- NewActor, FName(TEXT("ProceduralTerrain")));
2146
- if (!ProcMesh) {
2147
- ActorSS->DestroyActor(NewActor);
2148
- SendAutomationError(RequestingSocket, RequestId,
2149
- TEXT("Failed to create ProceduralMeshComponent"),
2150
- TEXT("COMPONENT_CREATION_FAILED"));
2151
- return true;
2152
- }
2153
-
2154
- ProcMesh->RegisterComponent();
2155
- NewActor->SetRootComponent(ProcMesh);
2156
- NewActor->AddInstanceComponent(ProcMesh);
2157
-
2158
- // Generate grid
2159
- TArray<FVector> Vertices;
2160
- TArray<int32> Triangles;
2161
- TArray<FVector> Normals;
2162
- TArray<FVector2D> UV0;
2163
- TArray<FColor> VertexColors;
2164
- TArray<FProcMeshTangent> Tangents;
2165
-
2166
- const float StepX = SizeX / Subdivisions;
2167
- const float StepY = SizeY / Subdivisions;
2168
- const float UVStep = 1.0f / Subdivisions;
2169
-
2170
- for (int32 Y = 0; Y <= Subdivisions; Y++) {
2171
- for (int32 X = 0; X <= Subdivisions; X++) {
2172
- float Z = 0.0f;
2173
- // Simple sine wave terrain as default since we can't easily parse the
2174
- // math string
2175
- Z = FMath::Sin(X * 0.1f) * 50.0f + FMath::Cos(Y * 0.1f) * 30.0f;
2176
-
2177
- Vertices.Add(FVector(X * StepX - SizeX / 2, Y * StepY - SizeY / 2, Z));
2178
- Normals.Add(FVector(0, 0, 1)); // Simplified normal
2179
- UV0.Add(FVector2D(X * UVStep, Y * UVStep));
2180
- VertexColors.Add(FColor::White);
2181
- Tangents.Add(FProcMeshTangent(1, 0, 0));
2182
- }
2183
- }
2184
-
2185
- for (int32 Y = 0; Y < Subdivisions; Y++) {
2186
- for (int32 X = 0; X < Subdivisions; X++) {
2187
- int32 TopLeft = Y * (Subdivisions + 1) + X;
2188
- int32 TopRight = TopLeft + 1;
2189
- int32 BottomLeft = (Y + 1) * (Subdivisions + 1) + X;
2190
- int32 BottomRight = BottomLeft + 1;
2191
-
2192
- Triangles.Add(TopLeft);
2193
- Triangles.Add(BottomLeft);
2194
- Triangles.Add(TopRight);
2195
-
2196
- Triangles.Add(TopRight);
2197
- Triangles.Add(BottomLeft);
2198
- Triangles.Add(BottomRight);
2199
- }
2200
- }
2201
-
2202
- ProcMesh->CreateMeshSection(0, Vertices, Triangles, Normals, UV0,
2203
- VertexColors, Tangents, true);
2204
-
2205
- if (!MaterialPath.IsEmpty()) {
2206
- UMaterialInterface *Mat =
2207
- LoadObject<UMaterialInterface>(nullptr, *MaterialPath);
2208
- if (Mat) {
2209
- ProcMesh->SetMaterial(0, Mat);
2210
- }
2211
- }
2212
-
2213
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2214
- Resp->SetBoolField(TEXT("success"), true);
2215
- Resp->SetStringField(TEXT("actor_name"), NewActor->GetActorLabel());
2216
- Resp->SetNumberField(TEXT("vertices"), Vertices.Num());
2217
- Resp->SetNumberField(TEXT("triangles"), Triangles.Num() / 3);
2218
-
2219
- TSharedPtr<FJsonObject> SizeObj = MakeShared<FJsonObject>();
2220
- SizeObj->SetNumberField(TEXT("x"), SizeX);
2221
- SizeObj->SetNumberField(TEXT("y"), SizeY);
2222
- Resp->SetObjectField(TEXT("size"), SizeObj);
2223
- Resp->SetNumberField(TEXT("subdivisions"), Subdivisions);
2224
-
2225
- SendAutomationResponse(RequestingSocket, RequestId, true,
2226
- TEXT("Procedural terrain created"), Resp, FString());
2227
- return true;
2228
- #else
2229
- SendAutomationResponse(
2230
- RequestingSocket, RequestId, false,
2231
- TEXT("create_procedural_terrain requires editor build."), nullptr,
2232
- TEXT("NOT_IMPLEMENTED"));
2233
- return true;
2234
- #endif
2235
- }
2236
-
2237
- bool UMcpAutomationBridgeSubsystem::HandleBakeLightmap(
2238
- const FString &RequestId, const FString &Action,
2239
- const TSharedPtr<FJsonObject> &Payload,
2240
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2241
- const FString Lower = Action.ToLower();
2242
- if (!Lower.Equals(TEXT("bake_lightmap"), ESearchCase::IgnoreCase)) {
2243
- return false;
2244
- }
2245
-
2246
- #if WITH_EDITOR
2247
- FString QualityStr = TEXT("Preview");
2248
- if (Payload.IsValid())
2249
- Payload->TryGetStringField(TEXT("quality"), QualityStr);
2250
-
2251
- // Reuse HandleExecuteEditorFunction logic
2252
- TSharedPtr<FJsonObject> P = MakeShared<FJsonObject>();
2253
- P->SetStringField(TEXT("functionName"), TEXT("BUILD_LIGHTING"));
2254
- P->SetStringField(TEXT("quality"), QualityStr);
2255
-
2256
- return HandleExecuteEditorFunction(RequestId, TEXT("execute_editor_function"),
2257
- P, RequestingSocket);
2258
-
2259
- #else
2260
- SendAutomationResponse(RequestingSocket, RequestId, false,
2261
- TEXT("Requires editor"), nullptr,
2262
- TEXT("NOT_IMPLEMENTED"));
2263
- return true;
2264
- #endif
2265
- }