unreal-engine-mcp-server 0.5.3 → 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 (480) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/automation/bridge.d.ts +1 -0
  3. package/dist/automation/bridge.d.ts.map +1 -0
  4. package/dist/automation/bridge.js +62 -4
  5. package/dist/automation/bridge.js.map +1 -0
  6. package/dist/automation/connection-manager.d.ts.map +1 -0
  7. package/dist/automation/connection-manager.js.map +1 -0
  8. package/dist/automation/handshake.d.ts.map +1 -0
  9. package/dist/automation/handshake.js.map +1 -0
  10. package/dist/automation/index.d.ts.map +1 -0
  11. package/dist/automation/index.js.map +1 -0
  12. package/dist/automation/message-handler.d.ts.map +1 -0
  13. package/dist/automation/message-handler.js.map +1 -0
  14. package/dist/automation/request-tracker.d.ts.map +1 -0
  15. package/dist/automation/request-tracker.js.map +1 -0
  16. package/dist/automation/types.d.ts +1 -0
  17. package/dist/automation/types.d.ts.map +1 -0
  18. package/dist/automation/types.js.map +1 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +4 -3
  21. package/dist/cli.js.map +1 -0
  22. package/dist/config/class-aliases.d.ts.map +1 -0
  23. package/dist/config/class-aliases.js.map +1 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/constants.d.ts +2 -0
  27. package/dist/constants.d.ts.map +1 -0
  28. package/dist/constants.js +2 -0
  29. package/dist/constants.js.map +1 -0
  30. package/dist/graphql/loaders.d.ts.map +1 -0
  31. package/dist/graphql/loaders.js.map +1 -0
  32. package/dist/graphql/resolvers.d.ts.map +1 -0
  33. package/dist/graphql/resolvers.js +29 -29
  34. package/dist/graphql/resolvers.js.map +1 -0
  35. package/dist/graphql/schema.d.ts.map +1 -0
  36. package/dist/graphql/schema.js.map +1 -0
  37. package/dist/graphql/server.d.ts +0 -1
  38. package/dist/graphql/server.d.ts.map +1 -0
  39. package/dist/graphql/server.js +15 -16
  40. package/dist/graphql/server.js.map +1 -0
  41. package/dist/graphql/types.d.ts.map +1 -0
  42. package/dist/graphql/types.js.map +1 -0
  43. package/dist/handlers/resource-handlers.d.ts.map +1 -0
  44. package/dist/handlers/resource-handlers.js.map +1 -0
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +64 -7
  48. package/dist/index.js.map +1 -0
  49. package/dist/resources/actors.d.ts.map +1 -0
  50. package/dist/resources/actors.js.map +1 -0
  51. package/dist/resources/assets.d.ts.map +1 -0
  52. package/dist/resources/assets.js +6 -4
  53. package/dist/resources/assets.js.map +1 -0
  54. package/dist/resources/levels.d.ts.map +1 -0
  55. package/dist/resources/levels.js.map +1 -0
  56. package/dist/server/resource-registry.d.ts.map +1 -0
  57. package/dist/server/resource-registry.js.map +1 -0
  58. package/dist/server/tool-registry.d.ts.map +1 -0
  59. package/dist/server/tool-registry.js.map +1 -0
  60. package/dist/server-setup.d.ts.map +1 -0
  61. package/dist/server-setup.js.map +1 -0
  62. package/dist/services/health-monitor.d.ts.map +1 -0
  63. package/dist/services/health-monitor.js.map +1 -0
  64. package/dist/services/metrics-server.d.ts.map +1 -0
  65. package/dist/services/metrics-server.js +3 -3
  66. package/dist/services/metrics-server.js.map +1 -0
  67. package/dist/tools/actors.d.ts.map +1 -0
  68. package/dist/tools/actors.js +3 -1
  69. package/dist/tools/actors.js.map +1 -0
  70. package/dist/tools/animation.d.ts.map +1 -0
  71. package/dist/tools/animation.js +2 -2
  72. package/dist/tools/animation.js.map +1 -0
  73. package/dist/tools/assets.d.ts.map +1 -0
  74. package/dist/tools/assets.js.map +1 -0
  75. package/dist/tools/audio.d.ts.map +1 -0
  76. package/dist/tools/audio.js.map +1 -0
  77. package/dist/tools/base-tool.d.ts.map +1 -0
  78. package/dist/tools/base-tool.js.map +1 -0
  79. package/dist/tools/behavior-tree.d.ts.map +1 -0
  80. package/dist/tools/behavior-tree.js.map +1 -0
  81. package/dist/tools/blueprint.d.ts.map +1 -0
  82. package/dist/tools/blueprint.js +4 -2
  83. package/dist/tools/blueprint.js.map +1 -0
  84. package/dist/tools/consolidated-tool-definitions.d.ts.map +1 -0
  85. package/dist/tools/consolidated-tool-definitions.js.map +1 -0
  86. package/dist/tools/consolidated-tool-handlers.d.ts.map +1 -0
  87. package/dist/tools/consolidated-tool-handlers.js.map +1 -0
  88. package/dist/tools/debug.d.ts.map +1 -0
  89. package/dist/tools/debug.js +3 -1
  90. package/dist/tools/debug.js.map +1 -0
  91. package/dist/tools/dynamic-handler-registry.d.ts.map +1 -0
  92. package/dist/tools/dynamic-handler-registry.js +3 -1
  93. package/dist/tools/dynamic-handler-registry.js.map +1 -0
  94. package/dist/tools/editor.d.ts.map +1 -0
  95. package/dist/tools/editor.js +1 -1
  96. package/dist/tools/editor.js.map +1 -0
  97. package/dist/tools/engine.d.ts.map +1 -0
  98. package/dist/tools/engine.js.map +1 -0
  99. package/dist/tools/environment.d.ts.map +1 -0
  100. package/dist/tools/environment.js +2 -2
  101. package/dist/tools/environment.js.map +1 -0
  102. package/dist/tools/foliage.d.ts.map +1 -0
  103. package/dist/tools/foliage.js.map +1 -0
  104. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  105. package/dist/tools/handlers/actor-handlers.d.ts.map +1 -0
  106. package/dist/tools/handlers/actor-handlers.js +6 -5
  107. package/dist/tools/handlers/actor-handlers.js.map +1 -0
  108. package/dist/tools/handlers/animation-handlers.d.ts.map +1 -0
  109. package/dist/tools/handlers/animation-handlers.js.map +1 -0
  110. package/dist/tools/handlers/argument-helper.d.ts.map +1 -0
  111. package/dist/tools/handlers/argument-helper.js.map +1 -0
  112. package/dist/tools/handlers/asset-handlers.d.ts.map +1 -0
  113. package/dist/tools/handlers/asset-handlers.js +5 -1
  114. package/dist/tools/handlers/asset-handlers.js.map +1 -0
  115. package/dist/tools/handlers/audio-handlers.d.ts.map +1 -0
  116. package/dist/tools/handlers/audio-handlers.js.map +1 -0
  117. package/dist/tools/handlers/blueprint-handlers.d.ts.map +1 -0
  118. package/dist/tools/handlers/blueprint-handlers.js +2 -1
  119. package/dist/tools/handlers/blueprint-handlers.js.map +1 -0
  120. package/dist/tools/handlers/common-handlers.d.ts.map +1 -0
  121. package/dist/tools/handlers/common-handlers.js.map +1 -0
  122. package/dist/tools/handlers/editor-handlers.d.ts.map +1 -0
  123. package/dist/tools/handlers/editor-handlers.js +12 -2
  124. package/dist/tools/handlers/editor-handlers.js.map +1 -0
  125. package/dist/tools/handlers/effect-handlers.d.ts.map +1 -0
  126. package/dist/tools/handlers/effect-handlers.js.map +1 -0
  127. package/dist/tools/handlers/environment-handlers.d.ts.map +1 -0
  128. package/dist/tools/handlers/environment-handlers.js.map +1 -0
  129. package/dist/tools/handlers/graph-handlers.d.ts.map +1 -0
  130. package/dist/tools/handlers/graph-handlers.js +61 -1
  131. package/dist/tools/handlers/graph-handlers.js.map +1 -0
  132. package/dist/tools/handlers/input-handlers.d.ts.map +1 -0
  133. package/dist/tools/handlers/input-handlers.js.map +1 -0
  134. package/dist/tools/handlers/inspect-handlers.d.ts.map +1 -0
  135. package/dist/tools/handlers/inspect-handlers.js.map +1 -0
  136. package/dist/tools/handlers/level-handlers.d.ts.map +1 -0
  137. package/dist/tools/handlers/level-handlers.js.map +1 -0
  138. package/dist/tools/handlers/lighting-handlers.d.ts.map +1 -0
  139. package/dist/tools/handlers/lighting-handlers.js +23 -1
  140. package/dist/tools/handlers/lighting-handlers.js.map +1 -0
  141. package/dist/tools/handlers/performance-handlers.d.ts.map +1 -0
  142. package/dist/tools/handlers/performance-handlers.js +15 -2
  143. package/dist/tools/handlers/performance-handlers.js.map +1 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts.map +1 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +61 -7
  146. package/dist/tools/handlers/pipeline-handlers.js.map +1 -0
  147. package/dist/tools/handlers/sequence-handlers.d.ts.map +1 -0
  148. package/dist/tools/handlers/sequence-handlers.js.map +1 -0
  149. package/dist/tools/handlers/system-handlers.d.ts.map +1 -0
  150. package/dist/tools/handlers/system-handlers.js +16 -1
  151. package/dist/tools/handlers/system-handlers.js.map +1 -0
  152. package/dist/tools/input.d.ts.map +1 -0
  153. package/dist/tools/input.js +3 -1
  154. package/dist/tools/input.js.map +1 -0
  155. package/dist/tools/introspection.d.ts.map +1 -0
  156. package/dist/tools/introspection.js.map +1 -0
  157. package/dist/tools/landscape.d.ts.map +1 -0
  158. package/dist/tools/landscape.js +3 -1
  159. package/dist/tools/landscape.js.map +1 -0
  160. package/dist/tools/level.d.ts.map +1 -0
  161. package/dist/tools/level.js.map +1 -0
  162. package/dist/tools/lighting.d.ts.map +1 -0
  163. package/dist/tools/lighting.js +3 -1
  164. package/dist/tools/lighting.js.map +1 -0
  165. package/dist/tools/logs.d.ts.map +1 -0
  166. package/dist/tools/logs.js.map +1 -0
  167. package/dist/tools/materials.d.ts.map +1 -0
  168. package/dist/tools/materials.js +3 -1
  169. package/dist/tools/materials.js.map +1 -0
  170. package/dist/tools/niagara.d.ts.map +1 -0
  171. package/dist/tools/niagara.js +7 -5
  172. package/dist/tools/niagara.js.map +1 -0
  173. package/dist/tools/performance.d.ts.map +1 -0
  174. package/dist/tools/performance.js.map +1 -0
  175. package/dist/tools/physics.d.ts.map +1 -0
  176. package/dist/tools/physics.js +9 -7
  177. package/dist/tools/physics.js.map +1 -0
  178. package/dist/tools/property-dictionary.d.ts.map +1 -0
  179. package/dist/tools/property-dictionary.js.map +1 -0
  180. package/dist/tools/sequence.d.ts.map +1 -0
  181. package/dist/tools/sequence.js +3 -1
  182. package/dist/tools/sequence.js.map +1 -0
  183. package/dist/tools/tool-definition-utils.d.ts.map +1 -0
  184. package/dist/tools/tool-definition-utils.js.map +1 -0
  185. package/dist/tools/ui.d.ts.map +1 -0
  186. package/dist/tools/ui.js +3 -1
  187. package/dist/tools/ui.js.map +1 -0
  188. package/dist/types/automation-responses.d.ts.map +1 -0
  189. package/dist/types/automation-responses.js.map +1 -0
  190. package/dist/types/env.d.ts.map +1 -0
  191. package/dist/types/env.js.map +1 -0
  192. package/dist/types/handler-types.d.ts.map +1 -0
  193. package/dist/types/handler-types.js.map +1 -0
  194. package/dist/types/tool-interfaces.d.ts.map +1 -0
  195. package/dist/types/tool-interfaces.js.map +1 -0
  196. package/dist/types/tool-types.d.ts.map +1 -0
  197. package/dist/types/tool-types.js.map +1 -0
  198. package/dist/unreal-bridge.d.ts +1 -0
  199. package/dist/unreal-bridge.d.ts.map +1 -0
  200. package/dist/unreal-bridge.js +8 -0
  201. package/dist/unreal-bridge.js.map +1 -0
  202. package/dist/utils/command-validator.d.ts.map +1 -0
  203. package/dist/utils/command-validator.js.map +1 -0
  204. package/dist/utils/elicitation.d.ts.map +1 -0
  205. package/dist/utils/elicitation.js.map +1 -0
  206. package/dist/utils/error-handler.d.ts.map +1 -0
  207. package/dist/utils/error-handler.js.map +1 -0
  208. package/dist/utils/ini-reader.d.ts.map +1 -0
  209. package/dist/utils/ini-reader.js.map +1 -0
  210. package/dist/utils/logger.d.ts.map +1 -0
  211. package/dist/utils/logger.js.map +1 -0
  212. package/dist/utils/normalize.d.ts.map +1 -0
  213. package/dist/utils/normalize.js.map +1 -0
  214. package/dist/utils/path-security.d.ts.map +1 -0
  215. package/dist/utils/path-security.js.map +1 -0
  216. package/dist/utils/response-factory.d.ts.map +1 -0
  217. package/dist/utils/response-factory.js +3 -1
  218. package/dist/utils/response-factory.js.map +1 -0
  219. package/dist/utils/response-validator.d.ts.map +1 -0
  220. package/dist/utils/response-validator.js.map +1 -0
  221. package/dist/utils/result-helpers.d.ts.map +1 -0
  222. package/dist/utils/result-helpers.js.map +1 -0
  223. package/dist/utils/safe-json.d.ts.map +1 -0
  224. package/dist/utils/safe-json.js.map +1 -0
  225. package/dist/utils/unreal-command-queue.d.ts.map +1 -0
  226. package/dist/utils/unreal-command-queue.js.map +1 -0
  227. package/dist/utils/validation.d.ts.map +1 -0
  228. package/dist/utils/validation.js.map +1 -0
  229. package/dist/wasm/index.d.ts.map +1 -0
  230. package/dist/wasm/index.js.map +1 -0
  231. package/package.json +12 -34
  232. package/server.json +2 -2
  233. package/.dockerignore +0 -57
  234. package/.env.example +0 -26
  235. package/.env.production +0 -61
  236. package/.eslintrc.json +0 -0
  237. package/.eslintrc.override.json +0 -8
  238. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -94
  239. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  240. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -56
  241. package/.github/copilot-instructions.md +0 -478
  242. package/.github/dependabot.yml +0 -19
  243. package/.github/labeler.yml +0 -24
  244. package/.github/labels.yml +0 -70
  245. package/.github/pull_request_template.md +0 -42
  246. package/.github/release-drafter-config.yml +0 -51
  247. package/.github/workflows/auto-merge.yml +0 -38
  248. package/.github/workflows/ci.yml +0 -38
  249. package/.github/workflows/dependency-review.yml +0 -17
  250. package/.github/workflows/gemini-issue-triage.yml +0 -172
  251. package/.github/workflows/greetings.yml +0 -27
  252. package/.github/workflows/labeler.yml +0 -17
  253. package/.github/workflows/links.yml +0 -80
  254. package/.github/workflows/pr-size-labeler.yml +0 -137
  255. package/.github/workflows/publish-mcp.yml +0 -79
  256. package/.github/workflows/release-drafter.yml +0 -24
  257. package/.github/workflows/release.yml +0 -112
  258. package/.github/workflows/semantic-pull-request.yml +0 -35
  259. package/.github/workflows/smoke-test.yml +0 -36
  260. package/.github/workflows/stale.yml +0 -28
  261. package/CONTRIBUTING.md +0 -140
  262. package/Dockerfile +0 -37
  263. package/GEMINI.md +0 -115
  264. package/Public/Plugin_setup_guide.mp4 +0 -0
  265. package/Public/icon.png +0 -0
  266. package/claude_desktop_config_example.json +0 -15
  267. package/dist/types/responses.d.ts +0 -249
  268. package/dist/types/responses.js +0 -2
  269. package/docs/GraphQL-API.md +0 -888
  270. package/docs/Migration-Guide-v0.5.0.md +0 -684
  271. package/docs/Roadmap.md +0 -53
  272. package/docs/WebAssembly-Integration.md +0 -628
  273. package/docs/editor-plugin-extension.md +0 -370
  274. package/docs/handler-mapping.md +0 -249
  275. package/docs/native-automation-progress.md +0 -128
  276. package/docs/testing-guide.md +0 -423
  277. package/eslint.config.mjs +0 -68
  278. package/mcp-config-example.json +0 -14
  279. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +0 -8
  280. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +0 -64
  281. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +0 -189
  282. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +0 -22
  283. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +0 -30
  284. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +0 -1983
  285. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +0 -72
  286. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +0 -46
  287. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +0 -846
  288. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +0 -2393
  289. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +0 -300
  290. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +0 -2807
  291. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +0 -1087
  292. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +0 -488
  293. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +0 -643
  294. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +0 -31
  295. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +0 -1094
  296. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +0 -5750
  297. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +0 -152
  298. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +0 -2614
  299. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +0 -42
  300. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +0 -1237
  301. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +0 -1725
  302. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +0 -2241
  303. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +0 -954
  304. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +0 -209
  305. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +0 -41
  306. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +0 -1164
  307. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +0 -762
  308. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +0 -663
  309. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +0 -136
  310. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +0 -494
  311. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +0 -278
  312. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +0 -625
  313. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +0 -401
  314. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +0 -67
  315. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +0 -472
  316. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +0 -2634
  317. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +0 -189
  318. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +0 -917
  319. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +0 -39
  320. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +0 -2706
  321. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +0 -519
  322. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +0 -38
  323. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +0 -668
  324. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +0 -346
  325. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +0 -1330
  326. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +0 -149
  327. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -782
  328. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +0 -115
  329. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +0 -796
  330. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +0 -117
  331. package/scripts/check-unreal-connection.mjs +0 -19
  332. package/scripts/clean-tmp.js +0 -23
  333. package/scripts/patch-wasm.js +0 -26
  334. package/scripts/run-all-tests.mjs +0 -136
  335. package/scripts/smoke-test.ts +0 -94
  336. package/scripts/sync-mcp-plugin.js +0 -143
  337. package/scripts/test-no-plugin-alternates.mjs +0 -113
  338. package/scripts/validate-server.js +0 -46
  339. package/scripts/verify-automation-bridge.js +0 -200
  340. package/src/automation/bridge.ts +0 -560
  341. package/src/automation/connection-manager.ts +0 -148
  342. package/src/automation/handshake.ts +0 -99
  343. package/src/automation/index.ts +0 -2
  344. package/src/automation/message-handler.ts +0 -192
  345. package/src/automation/request-tracker.ts +0 -155
  346. package/src/automation/types.ts +0 -107
  347. package/src/cli.ts +0 -34
  348. package/src/config/class-aliases.ts +0 -65
  349. package/src/config.ts +0 -73
  350. package/src/constants.ts +0 -24
  351. package/src/graphql/loaders.ts +0 -244
  352. package/src/graphql/resolvers.ts +0 -1008
  353. package/src/graphql/schema.ts +0 -452
  354. package/src/graphql/server.ts +0 -156
  355. package/src/graphql/types.ts +0 -10
  356. package/src/handlers/resource-handlers.ts +0 -186
  357. package/src/index.ts +0 -243
  358. package/src/resources/actors.ts +0 -127
  359. package/src/resources/assets.ts +0 -286
  360. package/src/resources/levels.ts +0 -68
  361. package/src/server/resource-registry.ts +0 -47
  362. package/src/server/tool-registry.ts +0 -354
  363. package/src/server-setup.ts +0 -114
  364. package/src/services/health-monitor.ts +0 -132
  365. package/src/services/metrics-server.ts +0 -176
  366. package/src/tools/actors.ts +0 -564
  367. package/src/tools/animation.ts +0 -941
  368. package/src/tools/assets.ts +0 -394
  369. package/src/tools/audio.ts +0 -499
  370. package/src/tools/base-tool.ts +0 -52
  371. package/src/tools/behavior-tree.ts +0 -45
  372. package/src/tools/blueprint.ts +0 -940
  373. package/src/tools/consolidated-tool-definitions.ts +0 -1256
  374. package/src/tools/consolidated-tool-handlers.ts +0 -302
  375. package/src/tools/debug.ts +0 -622
  376. package/src/tools/dynamic-handler-registry.ts +0 -33
  377. package/src/tools/editor.ts +0 -435
  378. package/src/tools/engine.ts +0 -43
  379. package/src/tools/environment.ts +0 -281
  380. package/src/tools/foliage.ts +0 -596
  381. package/src/tools/handlers/actor-handlers.ts +0 -244
  382. package/src/tools/handlers/animation-handlers.ts +0 -237
  383. package/src/tools/handlers/argument-helper.ts +0 -142
  384. package/src/tools/handlers/asset-handlers.ts +0 -550
  385. package/src/tools/handlers/audio-handlers.ts +0 -194
  386. package/src/tools/handlers/blueprint-handlers.ts +0 -380
  387. package/src/tools/handlers/common-handlers.ts +0 -108
  388. package/src/tools/handlers/editor-handlers.ts +0 -124
  389. package/src/tools/handlers/effect-handlers.ts +0 -224
  390. package/src/tools/handlers/environment-handlers.ts +0 -183
  391. package/src/tools/handlers/graph-handlers.ts +0 -117
  392. package/src/tools/handlers/input-handlers.ts +0 -28
  393. package/src/tools/handlers/inspect-handlers.ts +0 -450
  394. package/src/tools/handlers/level-handlers.ts +0 -253
  395. package/src/tools/handlers/lighting-handlers.ts +0 -151
  396. package/src/tools/handlers/performance-handlers.ts +0 -132
  397. package/src/tools/handlers/pipeline-handlers.ts +0 -128
  398. package/src/tools/handlers/sequence-handlers.ts +0 -438
  399. package/src/tools/handlers/system-handlers.ts +0 -564
  400. package/src/tools/input.ts +0 -160
  401. package/src/tools/introspection.ts +0 -689
  402. package/src/tools/landscape.ts +0 -649
  403. package/src/tools/level.ts +0 -989
  404. package/src/tools/lighting.ts +0 -1052
  405. package/src/tools/logs.ts +0 -219
  406. package/src/tools/materials.ts +0 -295
  407. package/src/tools/niagara.ts +0 -485
  408. package/src/tools/performance.ts +0 -661
  409. package/src/tools/physics.ts +0 -679
  410. package/src/tools/property-dictionary.ts +0 -98
  411. package/src/tools/sequence.ts +0 -385
  412. package/src/tools/tool-definition-utils.ts +0 -35
  413. package/src/tools/ui.ts +0 -452
  414. package/src/types/automation-responses.ts +0 -119
  415. package/src/types/env.ts +0 -17
  416. package/src/types/handler-types.ts +0 -442
  417. package/src/types/responses.ts +0 -355
  418. package/src/types/tool-interfaces.ts +0 -250
  419. package/src/types/tool-types.ts +0 -575
  420. package/src/unreal-bridge.ts +0 -693
  421. package/src/utils/command-validator.ts +0 -139
  422. package/src/utils/elicitation.ts +0 -132
  423. package/src/utils/error-handler.ts +0 -287
  424. package/src/utils/ini-reader.ts +0 -86
  425. package/src/utils/logger.ts +0 -35
  426. package/src/utils/normalize.test.ts +0 -162
  427. package/src/utils/normalize.ts +0 -146
  428. package/src/utils/path-security.ts +0 -43
  429. package/src/utils/response-factory.ts +0 -44
  430. package/src/utils/response-validator.ts +0 -395
  431. package/src/utils/result-helpers.ts +0 -195
  432. package/src/utils/safe-json.test.ts +0 -90
  433. package/src/utils/safe-json.ts +0 -70
  434. package/src/utils/unreal-command-queue.ts +0 -166
  435. package/src/utils/validation.test.ts +0 -184
  436. package/src/utils/validation.ts +0 -312
  437. package/src/wasm/index.ts +0 -838
  438. package/test-server.mjs +0 -100
  439. package/tests/run-unreal-tool-tests.mjs +0 -948
  440. package/tests/test-animation.mjs +0 -369
  441. package/tests/test-asset-advanced.mjs +0 -82
  442. package/tests/test-asset-errors.mjs +0 -35
  443. package/tests/test-asset-graph.mjs +0 -311
  444. package/tests/test-audio.mjs +0 -417
  445. package/tests/test-automation-timeouts.mjs +0 -98
  446. package/tests/test-behavior-tree.mjs +0 -444
  447. package/tests/test-blueprint-graph.mjs +0 -410
  448. package/tests/test-blueprint.mjs +0 -577
  449. package/tests/test-client-mode.mjs +0 -86
  450. package/tests/test-console-command.mjs +0 -56
  451. package/tests/test-control-actor.mjs +0 -425
  452. package/tests/test-control-editor.mjs +0 -112
  453. package/tests/test-graphql.mjs +0 -372
  454. package/tests/test-input.mjs +0 -349
  455. package/tests/test-inspect.mjs +0 -302
  456. package/tests/test-landscape.mjs +0 -316
  457. package/tests/test-lighting.mjs +0 -428
  458. package/tests/test-manage-asset.mjs +0 -438
  459. package/tests/test-manage-level.mjs +0 -89
  460. package/tests/test-materials.mjs +0 -356
  461. package/tests/test-niagara.mjs +0 -185
  462. package/tests/test-no-inline-python.mjs +0 -122
  463. package/tests/test-performance.mjs +0 -539
  464. package/tests/test-plugin-handshake.mjs +0 -82
  465. package/tests/test-runner.mjs +0 -933
  466. package/tests/test-sequence.mjs +0 -104
  467. package/tests/test-system.mjs +0 -96
  468. package/tests/test-wasm.mjs +0 -283
  469. package/tests/test-world-partition.mjs +0 -215
  470. package/tsconfig.json +0 -56
  471. package/vitest.config.ts +0 -35
  472. package/wasm/Cargo.lock +0 -363
  473. package/wasm/Cargo.toml +0 -42
  474. package/wasm/LICENSE +0 -21
  475. package/wasm/README.md +0 -253
  476. package/wasm/src/dependency_resolver.rs +0 -377
  477. package/wasm/src/lib.rs +0 -153
  478. package/wasm/src/property_parser.rs +0 -271
  479. package/wasm/src/transform_math.rs +0 -396
  480. package/wasm/tests/integration.rs +0 -109
@@ -1,2241 +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
- // Execute the command
1028
- try {
1029
- UWorld *TargetWorld = nullptr;
1030
- #if WITH_EDITOR
1031
- if (GEditor) {
1032
- // Prefer PIE world if active, otherwise Editor world
1033
- TargetWorld = GEditor->PlayWorld;
1034
- if (!TargetWorld) {
1035
- TargetWorld = GEditor->GetEditorWorldContext().World();
1036
- }
1037
- }
1038
- #endif
1039
-
1040
- // Fallback to GWorld if no editor/PIE world found (e.g. game mode)
1041
- if (!TargetWorld && GEngine) {
1042
- // Note: In some contexts GWorld is a macro for a proxy, but here we need
1043
- // a raw pointer. We'll rely on Exec handling nullptr if we really can't
1044
- // find one, but explicitly passing the editor world fixes many "command
1045
- // not handled" or crash issues.
1046
- }
1047
-
1048
- GEngine->Exec(TargetWorld, *Command);
1049
-
1050
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1051
- Result->SetStringField(TEXT("command"), Command);
1052
- Result->SetBoolField(TEXT("executed"), true);
1053
-
1054
- SendAutomationResponse(
1055
- RequestingSocket, RequestId, true,
1056
- FString::Printf(TEXT("Executed console command: %s"), *Command), Result,
1057
- FString());
1058
- return true;
1059
- } catch (...) {
1060
- SendAutomationResponse(
1061
- RequestingSocket, RequestId, false,
1062
- FString::Printf(TEXT("Failed to execute command: %s"), *Command),
1063
- nullptr, TEXT("EXECUTION_FAILED"));
1064
- return true;
1065
- }
1066
- }
1067
-
1068
- bool UMcpAutomationBridgeSubsystem::HandleInspectAction(
1069
- const FString &RequestId, const FString &Action,
1070
- const TSharedPtr<FJsonObject> &Payload,
1071
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
1072
- if (!Action.Equals(TEXT("inspect"), ESearchCase::IgnoreCase)) {
1073
- return false;
1074
- }
1075
-
1076
- if (!Payload.IsValid()) {
1077
- SendAutomationResponse(RequestingSocket, RequestId, false,
1078
- TEXT("Inspect action requires valid payload"),
1079
- nullptr, TEXT("INVALID_PAYLOAD"));
1080
- return true;
1081
- }
1082
-
1083
- FString SubAction;
1084
- if (!Payload->TryGetStringField(TEXT("action"), SubAction)) {
1085
- SendAutomationResponse(RequestingSocket, RequestId, false,
1086
- TEXT("Inspect action requires action parameter"),
1087
- nullptr, TEXT("INVALID_ARGUMENT"));
1088
- return true;
1089
- }
1090
-
1091
- FString LowerSub = SubAction.ToLower();
1092
- TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1093
-
1094
- // Inspect object
1095
- if (LowerSub == TEXT("inspect_object")) {
1096
- FString ObjectPath;
1097
- if (!Payload->TryGetStringField(TEXT("objectPath"), ObjectPath)) {
1098
- SendAutomationResponse(
1099
- RequestingSocket, RequestId, false,
1100
- TEXT("inspect_object requires objectPath parameter"), nullptr,
1101
- TEXT("INVALID_ARGUMENT"));
1102
- return true;
1103
- }
1104
-
1105
- UObject *TargetObject = FindObject<UObject>(nullptr, *ObjectPath);
1106
-
1107
- // Compatibility: allow passing actor label/name/path as objectPath.
1108
- // Many callers use simple names like "MyActor".
1109
- if (!TargetObject) {
1110
- if (AActor *FoundActor = FindActorByName(ObjectPath)) {
1111
- TargetObject = FoundActor;
1112
- ObjectPath = FoundActor->GetPathName();
1113
- }
1114
- }
1115
- if (!TargetObject) {
1116
- SendAutomationResponse(
1117
- RequestingSocket, RequestId, false,
1118
- FString::Printf(TEXT("Object not found: %s"), *ObjectPath), nullptr,
1119
- TEXT("OBJECT_NOT_FOUND"));
1120
- return true;
1121
- }
1122
-
1123
- Result->SetStringField(TEXT("objectPath"), ObjectPath);
1124
- Result->SetStringField(TEXT("objectName"), TargetObject->GetName());
1125
- Result->SetStringField(TEXT("objectClass"),
1126
- TargetObject->GetClass()->GetName());
1127
- Result->SetStringField(TEXT("objectType"),
1128
- TargetObject->GetClass()->GetFName().ToString());
1129
-
1130
- SendAutomationResponse(
1131
- RequestingSocket, RequestId, true,
1132
- FString::Printf(TEXT("Inspected object: %s"), *ObjectPath), Result,
1133
- FString());
1134
- return true;
1135
- }
1136
-
1137
- // Get property
1138
- if (LowerSub == TEXT("get_property")) {
1139
- FString ObjectPath;
1140
- FString PropertyName;
1141
-
1142
- if (!Payload->TryGetStringField(TEXT("objectPath"), ObjectPath) ||
1143
- !Payload->TryGetStringField(TEXT("propertyName"), PropertyName)) {
1144
- SendAutomationResponse(
1145
- RequestingSocket, RequestId, false,
1146
- TEXT("get_property requires objectPath and propertyName parameters"),
1147
- nullptr, TEXT("INVALID_ARGUMENT"));
1148
- return true;
1149
- }
1150
-
1151
- UObject *TargetObject = FindObject<UObject>(nullptr, *ObjectPath);
1152
-
1153
- // Compatibility: allow passing actor label/name/path as objectPath.
1154
- if (!TargetObject) {
1155
- if (AActor *FoundActor = FindActorByName(ObjectPath)) {
1156
- TargetObject = FoundActor;
1157
- ObjectPath = FoundActor->GetPathName();
1158
- }
1159
- }
1160
- if (!TargetObject) {
1161
- SendAutomationResponse(
1162
- RequestingSocket, RequestId, false,
1163
- FString::Printf(TEXT("Object not found: %s"), *ObjectPath), nullptr,
1164
- TEXT("OBJECT_NOT_FOUND"));
1165
- return true;
1166
- }
1167
-
1168
- UClass *ObjectClass = TargetObject->GetClass();
1169
- FProperty *Property = ObjectClass->FindPropertyByName(*PropertyName);
1170
-
1171
- if (!Property) {
1172
- SendAutomationResponse(
1173
- RequestingSocket, RequestId, false,
1174
- FString::Printf(TEXT("Property not found: %s"), *PropertyName),
1175
- nullptr, TEXT("PROPERTY_NOT_FOUND"));
1176
- return true;
1177
- }
1178
-
1179
- Result->SetStringField(TEXT("objectPath"), ObjectPath);
1180
- Result->SetStringField(TEXT("propertyName"), PropertyName);
1181
- Result->SetStringField(TEXT("propertyType"),
1182
- Property->GetClass()->GetName());
1183
-
1184
- // Return value as string for broad compatibility.
1185
- FString ValueText;
1186
- const void *ValuePtr = Property->ContainerPtrToValuePtr<void>(TargetObject);
1187
- Property->ExportTextItem_Direct(ValueText, ValuePtr, nullptr, TargetObject,
1188
- PPF_None);
1189
- Result->SetStringField(TEXT("value"), ValueText);
1190
-
1191
- SendAutomationResponse(RequestingSocket, RequestId, true,
1192
- FString::Printf(TEXT("Retrieved property: %s.%s"),
1193
- *ObjectPath, *PropertyName),
1194
- Result, FString());
1195
- return true;
1196
- }
1197
-
1198
- // Set property (simplified implementation)
1199
- if (LowerSub == TEXT("set_property")) {
1200
- FString ObjectPath;
1201
- FString PropertyName;
1202
-
1203
- if (!Payload->TryGetStringField(TEXT("objectPath"), ObjectPath) ||
1204
- !Payload->TryGetStringField(TEXT("propertyName"), PropertyName)) {
1205
- SendAutomationResponse(
1206
- RequestingSocket, RequestId, false,
1207
- TEXT("set_property requires objectPath and propertyName parameters"),
1208
- nullptr, TEXT("INVALID_ARGUMENT"));
1209
- return true;
1210
- }
1211
-
1212
- // Critical Property Protection
1213
- TArray<FString> ProtectedProperties = {TEXT("Class"), TEXT("Outer"),
1214
- TEXT("Archetype"), TEXT("Linker"),
1215
- TEXT("LinkerIndex")};
1216
- if (ProtectedProperties.Contains(PropertyName)) {
1217
- SendAutomationResponse(
1218
- RequestingSocket, RequestId, false,
1219
- FString::Printf(
1220
- TEXT("Modification of critical property '%s' is blocked"),
1221
- *PropertyName),
1222
- nullptr, TEXT("PROPERTY_BLOCKED"));
1223
- return true;
1224
- }
1225
-
1226
- UObject *TargetObject = FindObject<UObject>(nullptr, *ObjectPath);
1227
-
1228
- // Compatibility: allow passing actor label/name/path as objectPath.
1229
- if (!TargetObject) {
1230
- if (AActor *FoundActor = FindActorByName(ObjectPath)) {
1231
- TargetObject = FoundActor;
1232
- ObjectPath = FoundActor->GetPathName();
1233
- }
1234
- }
1235
- if (!TargetObject) {
1236
- SendAutomationResponse(
1237
- RequestingSocket, RequestId, false,
1238
- FString::Printf(TEXT("Object not found: %s"), *ObjectPath), nullptr,
1239
- TEXT("OBJECT_NOT_FOUND"));
1240
- return true;
1241
- }
1242
-
1243
- // Get the property value from payload
1244
- FString PropertyValue;
1245
- if (!Payload->TryGetStringField(TEXT("value"), PropertyValue)) {
1246
- SendAutomationResponse(RequestingSocket, RequestId, false,
1247
- TEXT("set_property requires 'value' field"),
1248
- nullptr, TEXT("INVALID_ARGUMENT"));
1249
- return true;
1250
- }
1251
-
1252
- // Find the property using Unreal's reflection system
1253
- FProperty *FoundProperty =
1254
- TargetObject->GetClass()->FindPropertyByName(FName(*PropertyName));
1255
- if (!FoundProperty) {
1256
- SendAutomationResponse(
1257
- RequestingSocket, RequestId, false,
1258
- FString::Printf(TEXT("Property '%s' not found on object '%s'"),
1259
- *PropertyName, *ObjectPath),
1260
- nullptr, TEXT("PROPERTY_NOT_FOUND"));
1261
- return true;
1262
- }
1263
-
1264
- // Set the property value based on type
1265
- bool bSuccess = false;
1266
- FString ErrorMessage;
1267
-
1268
- if (FStrProperty *StrProp = CastField<FStrProperty>(FoundProperty)) {
1269
- void *PropAddr = StrProp->ContainerPtrToValuePtr<void>(TargetObject);
1270
- StrProp->SetPropertyValue(PropAddr, PropertyValue);
1271
- bSuccess = true;
1272
- } else if (FFloatProperty *FloatProp =
1273
- CastField<FFloatProperty>(FoundProperty)) {
1274
- void *PropAddr = FloatProp->ContainerPtrToValuePtr<void>(TargetObject);
1275
- float Value = FCString::Atof(*PropertyValue);
1276
- FloatProp->SetPropertyValue(PropAddr, Value);
1277
- bSuccess = true;
1278
- } else if (FDoubleProperty *DoubleProp =
1279
- CastField<FDoubleProperty>(FoundProperty)) {
1280
- void *PropAddr = DoubleProp->ContainerPtrToValuePtr<void>(TargetObject);
1281
- double Value = FCString::Atod(*PropertyValue);
1282
- DoubleProp->SetPropertyValue(PropAddr, Value);
1283
- bSuccess = true;
1284
- } else if (FIntProperty *IntProp = CastField<FIntProperty>(FoundProperty)) {
1285
- void *PropAddr = IntProp->ContainerPtrToValuePtr<void>(TargetObject);
1286
- int32 Value = FCString::Atoi(*PropertyValue);
1287
- IntProp->SetPropertyValue(PropAddr, Value);
1288
- bSuccess = true;
1289
- } else if (FInt64Property *Int64Prop =
1290
- CastField<FInt64Property>(FoundProperty)) {
1291
- void *PropAddr = Int64Prop->ContainerPtrToValuePtr<void>(TargetObject);
1292
- int64 Value = FCString::Atoi64(*PropertyValue);
1293
- Int64Prop->SetPropertyValue(PropAddr, Value);
1294
- bSuccess = true;
1295
- } else if (FBoolProperty *BoolProp =
1296
- CastField<FBoolProperty>(FoundProperty)) {
1297
- void *PropAddr = BoolProp->ContainerPtrToValuePtr<void>(TargetObject);
1298
- bool Value = PropertyValue.ToBool();
1299
- BoolProp->SetPropertyValue(PropAddr, Value);
1300
- bSuccess = true;
1301
- } else if (FObjectProperty *ObjProp =
1302
- CastField<FObjectProperty>(FoundProperty)) {
1303
- // Try to find the object by path
1304
- UObject *ObjValue = FindObject<UObject>(nullptr, *PropertyValue);
1305
- if (ObjValue || PropertyValue.IsEmpty()) {
1306
- void *PropAddr = ObjProp->ContainerPtrToValuePtr<void>(TargetObject);
1307
- ObjProp->SetPropertyValue(PropAddr, ObjValue);
1308
- bSuccess = true;
1309
- } else {
1310
- ErrorMessage = FString::Printf(
1311
- TEXT("Object property requires valid object path, got: %s"),
1312
- *PropertyValue);
1313
- }
1314
- } else if (FStructProperty *StructProp =
1315
- CastField<FStructProperty>(FoundProperty)) {
1316
- // Handle struct properties (FVector, FVector2D, FLinearColor, etc.)
1317
- void *PropAddr = StructProp->ContainerPtrToValuePtr<void>(TargetObject);
1318
- FString StructName =
1319
- StructProp->Struct ? StructProp->Struct->GetName() : FString();
1320
-
1321
- // Try to parse JSON object value from payload
1322
- const TSharedPtr<FJsonObject> *JsonObjValue = nullptr;
1323
- if (Payload->TryGetObjectField(TEXT("value"), JsonObjValue) &&
1324
- JsonObjValue->IsValid()) {
1325
- // Handle FVector explicitly
1326
- if (StructName.Equals(TEXT("Vector"), ESearchCase::IgnoreCase)) {
1327
- FVector *Vec = static_cast<FVector *>(PropAddr);
1328
- double X = 0, Y = 0, Z = 0;
1329
- (*JsonObjValue)->TryGetNumberField(TEXT("X"), X);
1330
- (*JsonObjValue)->TryGetNumberField(TEXT("Y"), Y);
1331
- (*JsonObjValue)->TryGetNumberField(TEXT("Z"), Z);
1332
- if (X == 0 && Y == 0 && Z == 0) {
1333
- (*JsonObjValue)->TryGetNumberField(TEXT("x"), X);
1334
- (*JsonObjValue)->TryGetNumberField(TEXT("y"), Y);
1335
- (*JsonObjValue)->TryGetNumberField(TEXT("z"), Z);
1336
- }
1337
- *Vec = FVector(X, Y, Z);
1338
- bSuccess = true;
1339
- }
1340
- // Handle FVector2D
1341
- else if (StructName.Equals(TEXT("Vector2D"), ESearchCase::IgnoreCase)) {
1342
- FVector2D *Vec = static_cast<FVector2D *>(PropAddr);
1343
- double X = 0, Y = 0;
1344
- (*JsonObjValue)->TryGetNumberField(TEXT("X"), X);
1345
- (*JsonObjValue)->TryGetNumberField(TEXT("Y"), Y);
1346
- if (X == 0 && Y == 0) {
1347
- (*JsonObjValue)->TryGetNumberField(TEXT("x"), X);
1348
- (*JsonObjValue)->TryGetNumberField(TEXT("y"), Y);
1349
- }
1350
- *Vec = FVector2D(X, Y);
1351
- bSuccess = true;
1352
- }
1353
- // Handle FLinearColor
1354
- else if (StructName.Equals(TEXT("LinearColor"),
1355
- ESearchCase::IgnoreCase)) {
1356
- FLinearColor *Color = static_cast<FLinearColor *>(PropAddr);
1357
- double R = 0, G = 0, B = 0, A = 1;
1358
- (*JsonObjValue)->TryGetNumberField(TEXT("R"), R);
1359
- (*JsonObjValue)->TryGetNumberField(TEXT("G"), G);
1360
- (*JsonObjValue)->TryGetNumberField(TEXT("B"), B);
1361
- (*JsonObjValue)->TryGetNumberField(TEXT("A"), A);
1362
- if (R == 0 && G == 0 && B == 0) {
1363
- (*JsonObjValue)->TryGetNumberField(TEXT("r"), R);
1364
- (*JsonObjValue)->TryGetNumberField(TEXT("g"), G);
1365
- (*JsonObjValue)->TryGetNumberField(TEXT("b"), B);
1366
- (*JsonObjValue)->TryGetNumberField(TEXT("a"), A);
1367
- }
1368
- *Color = FLinearColor(R, G, B, A);
1369
- bSuccess = true;
1370
- }
1371
- // Handle FRotator
1372
- else if (StructName.Equals(TEXT("Rotator"), ESearchCase::IgnoreCase)) {
1373
- FRotator *Rot = static_cast<FRotator *>(PropAddr);
1374
- double Pitch = 0, Yaw = 0, Roll = 0;
1375
- (*JsonObjValue)->TryGetNumberField(TEXT("Pitch"), Pitch);
1376
- (*JsonObjValue)->TryGetNumberField(TEXT("Yaw"), Yaw);
1377
- (*JsonObjValue)->TryGetNumberField(TEXT("Roll"), Roll);
1378
- if (Pitch == 0 && Yaw == 0 && Roll == 0) {
1379
- (*JsonObjValue)->TryGetNumberField(TEXT("pitch"), Pitch);
1380
- (*JsonObjValue)->TryGetNumberField(TEXT("yaw"), Yaw);
1381
- (*JsonObjValue)->TryGetNumberField(TEXT("roll"), Roll);
1382
- }
1383
- *Rot = FRotator(Pitch, Yaw, Roll);
1384
- bSuccess = true;
1385
- }
1386
- }
1387
-
1388
- // Fallback: try ImportText for string representation
1389
- if (!bSuccess && !PropertyValue.IsEmpty() && StructProp->Struct) {
1390
- const TCHAR *Buffer = *PropertyValue;
1391
- // Use UScriptStruct::ImportText (not FStructProperty)
1392
- const TCHAR *ImportResult = StructProp->Struct->ImportText(
1393
- Buffer, PropAddr, nullptr, PPF_None, GWarn, StructName);
1394
- bSuccess = (ImportResult != nullptr);
1395
- if (!bSuccess) {
1396
- ErrorMessage = FString::Printf(
1397
- TEXT("Failed to parse struct value '%s' for property '%s' of "
1398
- "type '%s'. For FVector use {\"X\":val,\"Y\":val,\"Z\":val} "
1399
- "or string \"(X=val,Y=val,Z=val)\""),
1400
- *PropertyValue, *PropertyName, *StructName);
1401
- }
1402
- }
1403
-
1404
- if (!bSuccess && ErrorMessage.IsEmpty()) {
1405
- ErrorMessage = FString::Printf(
1406
- TEXT("Struct property '%s' of type '%s' requires JSON object "
1407
- "value like {\"X\":val,\"Y\":val,\"Z\":val}"),
1408
- *PropertyName, *StructName);
1409
- }
1410
- } else {
1411
- ErrorMessage =
1412
- FString::Printf(TEXT("Property type '%s' not supported for setting"),
1413
- *FoundProperty->GetClass()->GetName());
1414
- }
1415
-
1416
- if (bSuccess) {
1417
- Result->SetStringField(TEXT("objectPath"), ObjectPath);
1418
- Result->SetStringField(TEXT("propertyName"), PropertyName);
1419
- Result->SetStringField(TEXT("value"), PropertyValue);
1420
- SendAutomationResponse(RequestingSocket, RequestId, true,
1421
- TEXT("Property set successfully"), Result,
1422
- FString());
1423
- } else {
1424
- Result->SetStringField(TEXT("objectPath"), ObjectPath);
1425
- Result->SetStringField(TEXT("propertyName"), PropertyName);
1426
- Result->SetStringField(TEXT("error"), ErrorMessage);
1427
- SendAutomationResponse(RequestingSocket, RequestId, false,
1428
- TEXT("Failed to set property"), Result,
1429
- TEXT("PROPERTY_SET_FAILED"));
1430
- }
1431
- return true;
1432
- }
1433
-
1434
- // Get bounding box (get_bounding_box)
1435
- if (LowerSub == TEXT("get_bounding_box")) {
1436
- FString ActorName;
1437
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1438
- FString ObjectPath;
1439
- Payload->TryGetStringField(TEXT("objectPath"), ObjectPath);
1440
-
1441
- if (ActorName.IsEmpty() && ObjectPath.IsEmpty()) {
1442
- SendAutomationResponse(
1443
- RequestingSocket, RequestId, false,
1444
- TEXT("get_bounding_box requires actorName or objectPath"), nullptr,
1445
- TEXT("INVALID_ARGUMENT"));
1446
- return true;
1447
- }
1448
-
1449
- AActor *TargetActor = nullptr;
1450
- UPrimitiveComponent *PrimComp = nullptr;
1451
-
1452
- #if WITH_EDITOR
1453
- if (GEditor && !ActorName.IsEmpty()) {
1454
- UEditorActorSubsystem *ActorSS =
1455
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
1456
- if (ActorSS) {
1457
- TArray<AActor *> Actors = ActorSS->GetAllLevelActors();
1458
- for (AActor *A : Actors) {
1459
- if (A &&
1460
- (A->GetActorLabel() == ActorName || A->GetName() == ActorName)) {
1461
- TargetActor = A;
1462
- break;
1463
- }
1464
- }
1465
- }
1466
- }
1467
- #endif
1468
-
1469
- if (!TargetActor && !ObjectPath.IsEmpty()) {
1470
- UObject *Obj = FindObject<UObject>(nullptr, *ObjectPath);
1471
- if (Obj) {
1472
- if (AActor *A = Cast<AActor>(Obj)) {
1473
- TargetActor = A;
1474
- } else if (UPrimitiveComponent *PC = Cast<UPrimitiveComponent>(Obj)) {
1475
- PrimComp = PC;
1476
- }
1477
- }
1478
- }
1479
-
1480
- FBox Box(ForceInit);
1481
- bool bFound = false;
1482
-
1483
- if (TargetActor) {
1484
- Box = TargetActor->GetComponentsBoundingBox(true);
1485
- bFound = true;
1486
- } else if (PrimComp) {
1487
- Box = PrimComp->Bounds.GetBox();
1488
- bFound = true;
1489
- }
1490
-
1491
- if (bFound) {
1492
- FVector Origin = Box.GetCenter();
1493
- FVector Extent = Box.GetExtent();
1494
- TSharedPtr<FJsonObject> BoxObj = MakeShared<FJsonObject>();
1495
-
1496
- TSharedPtr<FJsonObject> OrgObj = MakeShared<FJsonObject>();
1497
- OrgObj->SetNumberField(TEXT("x"), Origin.X);
1498
- OrgObj->SetNumberField(TEXT("y"), Origin.Y);
1499
- OrgObj->SetNumberField(TEXT("z"), Origin.Z);
1500
- BoxObj->SetObjectField(TEXT("origin"), OrgObj);
1501
-
1502
- TSharedPtr<FJsonObject> ExtObj = MakeShared<FJsonObject>();
1503
- ExtObj->SetNumberField(TEXT("x"), Extent.X);
1504
- ExtObj->SetNumberField(TEXT("y"), Extent.Y);
1505
- ExtObj->SetNumberField(TEXT("z"), Extent.Z);
1506
- BoxObj->SetObjectField(TEXT("extent"), ExtObj);
1507
-
1508
- SendAutomationResponse(RequestingSocket, RequestId, true,
1509
- TEXT("Bounding box retrieved"), BoxObj, FString());
1510
- } else {
1511
- SendAutomationResponse(RequestingSocket, RequestId, false,
1512
- TEXT("Object not found or has no bounds"), nullptr,
1513
- TEXT("OBJECT_NOT_FOUND"));
1514
- }
1515
- return true;
1516
- }
1517
-
1518
- // Get components (get_components)
1519
- if (LowerSub == TEXT("get_components")) {
1520
- FString ObjectPath;
1521
- if (!Payload->TryGetStringField(TEXT("objectPath"), ObjectPath)) {
1522
- Payload->TryGetStringField(TEXT("actorName"), ObjectPath);
1523
- }
1524
-
1525
- if (ObjectPath.IsEmpty()) {
1526
- SendAutomationResponse(
1527
- RequestingSocket, RequestId, false,
1528
- TEXT("get_components requires objectPath or actorName"), nullptr,
1529
- TEXT("INVALID_ARGUMENT"));
1530
- return true;
1531
- }
1532
-
1533
- AActor *FoundActor = FindActorByName(ObjectPath);
1534
- if (!FoundActor) {
1535
- if (UObject *Asset = UEditorAssetLibrary::LoadAsset(ObjectPath)) {
1536
- if (UBlueprint *BP = Cast<UBlueprint>(Asset)) {
1537
- if (BP->GeneratedClass) {
1538
- FoundActor = Cast<AActor>(BP->GeneratedClass->GetDefaultObject());
1539
- }
1540
- }
1541
- }
1542
- }
1543
-
1544
- if (!FoundActor) {
1545
- SendAutomationResponse(
1546
- RequestingSocket, RequestId, false,
1547
- FString::Printf(TEXT("Actor or Blueprint not found: %s"),
1548
- *ObjectPath),
1549
- nullptr, TEXT("OBJECT_NOT_FOUND"));
1550
- return true;
1551
- }
1552
-
1553
- TSharedPtr<FJsonObject> ComponentsObj = MakeShared<FJsonObject>();
1554
- TArray<TSharedPtr<FJsonValue>> ComponentList;
1555
-
1556
- for (UActorComponent *Comp : FoundActor->GetComponents()) {
1557
- if (!Comp)
1558
- continue;
1559
- TSharedPtr<FJsonObject> CompData = MakeShared<FJsonObject>();
1560
- CompData->SetStringField(TEXT("name"), Comp->GetName());
1561
- CompData->SetStringField(TEXT("class"), Comp->GetClass()->GetName());
1562
- CompData->SetStringField(TEXT("path"), Comp->GetPathName());
1563
-
1564
- if (USceneComponent *SceneComp = Cast<USceneComponent>(Comp)) {
1565
- CompData->SetBoolField(TEXT("isSceneComponent"), true);
1566
- TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
1567
- FVector Loc = SceneComp->GetRelativeLocation();
1568
- LocObj->SetNumberField("x", Loc.X);
1569
- LocObj->SetNumberField("y", Loc.Y);
1570
- LocObj->SetNumberField("z", Loc.Z);
1571
- CompData->SetObjectField("relativeLocation", LocObj);
1572
- }
1573
-
1574
- ComponentList.Add(MakeShared<FJsonValueObject>(CompData));
1575
- }
1576
-
1577
- TSharedPtr<FJsonObject> ComponentsResult = MakeShared<FJsonObject>();
1578
- ComponentsResult->SetArrayField(TEXT("components"), ComponentList);
1579
- ComponentsResult->SetNumberField(TEXT("count"), ComponentList.Num());
1580
-
1581
- SendAutomationResponse(RequestingSocket, RequestId, true,
1582
- TEXT("Actor components retrieved"), ComponentsResult,
1583
- FString());
1584
- return true;
1585
- }
1586
-
1587
- // Find by class (find_by_class)
1588
- if (LowerSub == TEXT("find_by_class")) {
1589
- #if WITH_EDITOR
1590
- FString ClassName;
1591
- Payload->TryGetStringField(TEXT("className"), ClassName);
1592
- // Also accept classPath as alias
1593
- if (ClassName.IsEmpty()) {
1594
- Payload->TryGetStringField(TEXT("classPath"), ClassName);
1595
- }
1596
-
1597
- if (GEditor) {
1598
- UEditorActorSubsystem *ActorSS =
1599
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
1600
- if (ActorSS) {
1601
- TArray<AActor *> Actors = ActorSS->GetAllLevelActors();
1602
- TArray<TSharedPtr<FJsonValue>> Matches;
1603
-
1604
- // Normalize class name for matching
1605
- FString SearchName = ClassName;
1606
- FString SearchNameLower = ClassName.ToLower();
1607
-
1608
- // Common prefix variants to try
1609
- TArray<FString> SearchVariants;
1610
- SearchVariants.Add(SearchName);
1611
- if (!SearchName.StartsWith(TEXT("A")) &&
1612
- !SearchName.Contains(TEXT("/"))) {
1613
- SearchVariants.Add(TEXT("A") + SearchName); // AActor pattern
1614
- }
1615
- if (!SearchName.StartsWith(TEXT("U")) &&
1616
- !SearchName.Contains(TEXT("/"))) {
1617
- SearchVariants.Add(TEXT("U") + SearchName); // UObject pattern
1618
- }
1619
-
1620
- for (AActor *Actor : Actors) {
1621
- if (!Actor)
1622
- continue;
1623
- FString ActorClassName = Actor->GetClass()->GetName();
1624
- FString ActorClassPath = Actor->GetClass()->GetPathName();
1625
- FString ActorClassLower = ActorClassName.ToLower();
1626
-
1627
- bool bMatches = false;
1628
- if (ClassName.IsEmpty()) {
1629
- bMatches = true; // Return all actors if no filter
1630
- } else {
1631
- // Check all variants
1632
- for (const FString &Variant : SearchVariants) {
1633
- if (ActorClassName.Equals(Variant, ESearchCase::IgnoreCase) ||
1634
- ActorClassName.Contains(Variant, ESearchCase::IgnoreCase) ||
1635
- ActorClassPath.Contains(Variant, ESearchCase::IgnoreCase)) {
1636
- bMatches = true;
1637
- break;
1638
- }
1639
- }
1640
- }
1641
-
1642
- if (bMatches) {
1643
- TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1644
- Entry->SetStringField(TEXT("name"), Actor->GetActorLabel());
1645
- Entry->SetStringField(TEXT("path"), Actor->GetPathName());
1646
- Entry->SetStringField(TEXT("class"), ActorClassPath);
1647
- Entry->SetStringField(TEXT("classShort"), ActorClassName);
1648
- Matches.Add(MakeShared<FJsonValueObject>(Entry));
1649
- }
1650
- }
1651
-
1652
- Result->SetBoolField(TEXT("success"), true);
1653
- Result->SetArrayField(TEXT("actors"), Matches);
1654
- Result->SetNumberField(TEXT("count"), Matches.Num());
1655
- Result->SetStringField(TEXT("searchedFor"), ClassName);
1656
- SendAutomationResponse(RequestingSocket, RequestId, true,
1657
- TEXT("Found actors by class"), Result,
1658
- FString());
1659
- return true;
1660
- }
1661
- }
1662
- SendAutomationResponse(RequestingSocket, RequestId, false,
1663
- TEXT("Editor not available"), nullptr,
1664
- TEXT("EDITOR_NOT_AVAILABLE"));
1665
- return true;
1666
- #else
1667
- SendAutomationResponse(RequestingSocket, RequestId, false,
1668
- TEXT("find_by_class requires editor build"), nullptr,
1669
- TEXT("NOT_IMPLEMENTED"));
1670
- return true;
1671
- #endif
1672
- }
1673
-
1674
- // Inspect class (inspect_class)
1675
- if (LowerSub == TEXT("inspect_class")) {
1676
- FString ClassPath;
1677
- if (!Payload->TryGetStringField(TEXT("classPath"), ClassPath)) {
1678
- SendAutomationResponse(RequestingSocket, RequestId, false,
1679
- TEXT("classPath required"), nullptr,
1680
- TEXT("INVALID_ARGUMENT"));
1681
- return true;
1682
- }
1683
-
1684
- UClass *ResolvedClass = ResolveClassByName(ClassPath);
1685
- if (!ResolvedClass) {
1686
- // Try loading as asset
1687
- if (UObject *Found =
1688
- StaticLoadObject(UObject::StaticClass(), nullptr, *ClassPath)) {
1689
- if (UBlueprint *BP = Cast<UBlueprint>(Found))
1690
- ResolvedClass = BP->GeneratedClass;
1691
- else if (UClass *C = Cast<UClass>(Found))
1692
- ResolvedClass = C;
1693
- }
1694
- }
1695
-
1696
- if (ResolvedClass) {
1697
- Result->SetStringField(TEXT("className"), ResolvedClass->GetName());
1698
- Result->SetStringField(TEXT("classPath"), ResolvedClass->GetPathName());
1699
- if (ResolvedClass->GetSuperClass())
1700
- Result->SetStringField(TEXT("parentClass"),
1701
- ResolvedClass->GetSuperClass()->GetName());
1702
-
1703
- // List properties
1704
- TArray<TSharedPtr<FJsonValue>> Props;
1705
- for (TFieldIterator<FProperty> PropIt(ResolvedClass); PropIt; ++PropIt) {
1706
- FProperty *Prop = *PropIt;
1707
- TSharedPtr<FJsonObject> P = MakeShared<FJsonObject>();
1708
- P->SetStringField(TEXT("name"), Prop->GetName());
1709
- P->SetStringField(TEXT("type"), Prop->GetClass()->GetName());
1710
- Props.Add(MakeShared<FJsonValueObject>(P));
1711
- }
1712
- Result->SetArrayField(TEXT("properties"), Props);
1713
-
1714
- SendAutomationResponse(RequestingSocket, RequestId, true,
1715
- TEXT("Class inspected"), Result, FString());
1716
- return true;
1717
- }
1718
-
1719
- SendAutomationResponse(RequestingSocket, RequestId, false,
1720
- TEXT("Class not found"), nullptr,
1721
- TEXT("CLASS_NOT_FOUND"));
1722
- return true;
1723
- }
1724
-
1725
- // Get components (get_components) - enumerate all components on an actor
1726
- if (LowerSub == TEXT("get_components")) {
1727
- #if WITH_EDITOR
1728
- FString ActorName;
1729
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1730
- FString ObjectPath;
1731
- Payload->TryGetStringField(TEXT("objectPath"), ObjectPath);
1732
-
1733
- if (ActorName.IsEmpty() && ObjectPath.IsEmpty()) {
1734
- SendAutomationResponse(
1735
- RequestingSocket, RequestId, false,
1736
- TEXT("get_components requires actorName or objectPath"), nullptr,
1737
- TEXT("INVALID_ARGUMENT"));
1738
- return true;
1739
- }
1740
-
1741
- AActor *TargetActor = nullptr;
1742
- if (!ActorName.IsEmpty()) {
1743
- TargetActor = FindActorByName(ActorName);
1744
- }
1745
- if (!TargetActor && !ObjectPath.IsEmpty()) {
1746
- TargetActor = FindActorByName(ObjectPath);
1747
- }
1748
-
1749
- if (!TargetActor) {
1750
- SendAutomationResponse(
1751
- RequestingSocket, RequestId, false,
1752
- FString::Printf(TEXT("Failed to get components for actor %s"),
1753
- ActorName.IsEmpty() ? *ObjectPath : *ActorName),
1754
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1755
- return true;
1756
- }
1757
-
1758
- TArray<TSharedPtr<FJsonValue>> ComponentsArray;
1759
- for (UActorComponent *Comp : TargetActor->GetComponents()) {
1760
- if (!Comp)
1761
- continue;
1762
- TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1763
- Entry->SetStringField(TEXT("name"), Comp->GetName());
1764
- Entry->SetStringField(TEXT("readableName"), Comp->GetReadableName());
1765
- Entry->SetStringField(TEXT("class"), Comp->GetClass()
1766
- ? Comp->GetClass()->GetPathName()
1767
- : TEXT(""));
1768
- Entry->SetStringField(TEXT("path"), Comp->GetPathName());
1769
- if (USceneComponent *SceneComp = Cast<USceneComponent>(Comp)) {
1770
- FVector Loc = SceneComp->GetRelativeLocation();
1771
- FRotator Rot = SceneComp->GetRelativeRotation();
1772
- FVector Scale = SceneComp->GetRelativeScale3D();
1773
-
1774
- TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
1775
- LocObj->SetNumberField(TEXT("x"), Loc.X);
1776
- LocObj->SetNumberField(TEXT("y"), Loc.Y);
1777
- LocObj->SetNumberField(TEXT("z"), Loc.Z);
1778
- Entry->SetObjectField(TEXT("relativeLocation"), LocObj);
1779
-
1780
- TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
1781
- RotObj->SetNumberField(TEXT("pitch"), Rot.Pitch);
1782
- RotObj->SetNumberField(TEXT("yaw"), Rot.Yaw);
1783
- RotObj->SetNumberField(TEXT("roll"), Rot.Roll);
1784
- Entry->SetObjectField(TEXT("relativeRotation"), RotObj);
1785
-
1786
- TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
1787
- ScaleObj->SetNumberField(TEXT("x"), Scale.X);
1788
- ScaleObj->SetNumberField(TEXT("y"), Scale.Y);
1789
- ScaleObj->SetNumberField(TEXT("z"), Scale.Z);
1790
- Entry->SetObjectField(TEXT("relativeScale"), ScaleObj);
1791
- }
1792
- ComponentsArray.Add(MakeShared<FJsonValueObject>(Entry));
1793
- }
1794
-
1795
- Result->SetArrayField(TEXT("components"), ComponentsArray);
1796
- Result->SetNumberField(TEXT("count"), ComponentsArray.Num());
1797
- Result->SetStringField(TEXT("actorName"), TargetActor->GetActorLabel());
1798
- SendAutomationResponse(RequestingSocket, RequestId, true,
1799
- TEXT("Actor components retrieved"), Result,
1800
- FString());
1801
- return true;
1802
- #else
1803
- SendAutomationResponse(RequestingSocket, RequestId, false,
1804
- TEXT("get_components requires editor build"),
1805
- nullptr, TEXT("NOT_IMPLEMENTED"));
1806
- return true;
1807
- #endif
1808
- }
1809
-
1810
- // Get component property (get_component_property)
1811
- if (LowerSub == TEXT("get_component_property")) {
1812
- #if WITH_EDITOR
1813
- FString ActorName;
1814
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1815
- FString ObjectPath;
1816
- Payload->TryGetStringField(TEXT("objectPath"), ObjectPath);
1817
- FString ComponentName;
1818
- Payload->TryGetStringField(TEXT("componentName"), ComponentName);
1819
- FString PropertyName;
1820
- Payload->TryGetStringField(TEXT("propertyName"), PropertyName);
1821
-
1822
- if ((ActorName.IsEmpty() && ObjectPath.IsEmpty()) ||
1823
- ComponentName.IsEmpty() || PropertyName.IsEmpty()) {
1824
- SendAutomationResponse(RequestingSocket, RequestId, false,
1825
- TEXT("get_component_property requires "
1826
- "actorName/objectPath, componentName, and "
1827
- "propertyName"),
1828
- nullptr, TEXT("INVALID_ARGUMENT"));
1829
- return true;
1830
- }
1831
-
1832
- AActor *TargetActor = nullptr;
1833
- if (!ActorName.IsEmpty()) {
1834
- TargetActor = FindActorByName(ActorName);
1835
- }
1836
- if (!TargetActor && !ObjectPath.IsEmpty()) {
1837
- TargetActor = FindActorByName(ObjectPath);
1838
- }
1839
-
1840
- if (!TargetActor) {
1841
- SendAutomationResponse(RequestingSocket, RequestId, false,
1842
- TEXT("Actor not found"), nullptr,
1843
- TEXT("ACTOR_NOT_FOUND"));
1844
- return true;
1845
- }
1846
-
1847
- // Find component by name (fuzzy matching)
1848
- UActorComponent *TargetComponent = nullptr;
1849
- for (UActorComponent *Comp : TargetActor->GetComponents()) {
1850
- if (!Comp)
1851
- continue;
1852
- if (Comp->GetName().Equals(ComponentName, ESearchCase::IgnoreCase) ||
1853
- Comp->GetReadableName().Equals(ComponentName,
1854
- ESearchCase::IgnoreCase) ||
1855
- Comp->GetName().Contains(ComponentName, ESearchCase::IgnoreCase)) {
1856
- TargetComponent = Comp;
1857
- break;
1858
- }
1859
- }
1860
-
1861
- if (!TargetComponent) {
1862
- SendAutomationResponse(
1863
- RequestingSocket, RequestId, false,
1864
- FString::Printf(TEXT("Component not found on actor '%s': %s"),
1865
- *TargetActor->GetActorLabel(), *ComponentName),
1866
- nullptr, TEXT("COMPONENT_NOT_FOUND"));
1867
- return true;
1868
- }
1869
-
1870
- FProperty *Property =
1871
- TargetComponent->GetClass()->FindPropertyByName(*PropertyName);
1872
- if (!Property) {
1873
- SendAutomationResponse(
1874
- RequestingSocket, RequestId, false,
1875
- FString::Printf(TEXT("Property not found: %s"), *PropertyName),
1876
- nullptr, TEXT("PROPERTY_NOT_FOUND"));
1877
- return true;
1878
- }
1879
-
1880
- FString ValueText;
1881
- const void *ValuePtr =
1882
- Property->ContainerPtrToValuePtr<void>(TargetComponent);
1883
- Property->ExportTextItem_Direct(ValueText, ValuePtr, nullptr,
1884
- TargetComponent, PPF_None);
1885
-
1886
- Result->SetStringField(TEXT("componentName"), TargetComponent->GetName());
1887
- Result->SetStringField(TEXT("propertyName"), PropertyName);
1888
- Result->SetStringField(TEXT("value"), ValueText);
1889
- Result->SetStringField(TEXT("propertyType"),
1890
- Property->GetClass()->GetName());
1891
- SendAutomationResponse(RequestingSocket, RequestId, true,
1892
- TEXT("Component property retrieved"), Result,
1893
- FString());
1894
- return true;
1895
- #else
1896
- SendAutomationResponse(RequestingSocket, RequestId, false,
1897
- TEXT("get_component_property requires editor build"),
1898
- nullptr, TEXT("NOT_IMPLEMENTED"));
1899
- return true;
1900
- #endif
1901
- }
1902
-
1903
- // Set component property (set_component_property)
1904
- if (LowerSub == TEXT("set_component_property")) {
1905
- #if WITH_EDITOR
1906
- FString ActorName;
1907
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1908
- FString ObjectPath;
1909
- Payload->TryGetStringField(TEXT("objectPath"), ObjectPath);
1910
- FString ComponentName;
1911
- Payload->TryGetStringField(TEXT("componentName"), ComponentName);
1912
- FString PropertyName;
1913
- Payload->TryGetStringField(TEXT("propertyName"), PropertyName);
1914
-
1915
- if ((ActorName.IsEmpty() && ObjectPath.IsEmpty()) ||
1916
- ComponentName.IsEmpty() || PropertyName.IsEmpty()) {
1917
- SendAutomationResponse(RequestingSocket, RequestId, false,
1918
- TEXT("set_component_property requires "
1919
- "actorName/objectPath, componentName, and "
1920
- "propertyName"),
1921
- nullptr, TEXT("INVALID_ARGUMENT"));
1922
- return true;
1923
- }
1924
-
1925
- AActor *TargetActor = nullptr;
1926
- if (!ActorName.IsEmpty()) {
1927
- TargetActor = FindActorByName(ActorName);
1928
- }
1929
- if (!TargetActor && !ObjectPath.IsEmpty()) {
1930
- TargetActor = FindActorByName(ObjectPath);
1931
- }
1932
-
1933
- if (!TargetActor) {
1934
- SendAutomationResponse(RequestingSocket, RequestId, false,
1935
- TEXT("Actor not found"), nullptr,
1936
- TEXT("ACTOR_NOT_FOUND"));
1937
- return true;
1938
- }
1939
-
1940
- // Find component by name (fuzzy matching)
1941
- UActorComponent *TargetComponent = nullptr;
1942
- for (UActorComponent *Comp : TargetActor->GetComponents()) {
1943
- if (!Comp)
1944
- continue;
1945
- if (Comp->GetName().Equals(ComponentName, ESearchCase::IgnoreCase) ||
1946
- Comp->GetReadableName().Equals(ComponentName,
1947
- ESearchCase::IgnoreCase) ||
1948
- Comp->GetName().Contains(ComponentName, ESearchCase::IgnoreCase)) {
1949
- TargetComponent = Comp;
1950
- break;
1951
- }
1952
- }
1953
-
1954
- if (!TargetComponent) {
1955
- SendAutomationResponse(
1956
- RequestingSocket, RequestId, false,
1957
- FString::Printf(TEXT("Component not found on actor '%s': %s"),
1958
- *TargetActor->GetActorLabel(), *ComponentName),
1959
- nullptr, TEXT("COMPONENT_NOT_FOUND"));
1960
- return true;
1961
- }
1962
-
1963
- FString PropertyValue;
1964
- if (!Payload->TryGetStringField(TEXT("value"), PropertyValue)) {
1965
- SendAutomationResponse(RequestingSocket, RequestId, false,
1966
- TEXT("set_component_property requires 'value'"),
1967
- nullptr, TEXT("INVALID_ARGUMENT"));
1968
- return true;
1969
- }
1970
-
1971
- FProperty *FoundProperty =
1972
- TargetComponent->GetClass()->FindPropertyByName(FName(*PropertyName));
1973
- if (!FoundProperty) {
1974
- SendAutomationResponse(
1975
- RequestingSocket, RequestId, false,
1976
- FString::Printf(TEXT("Property '%s' not found on component"),
1977
- *PropertyName),
1978
- nullptr, TEXT("PROPERTY_NOT_FOUND"));
1979
- return true;
1980
- }
1981
-
1982
- bool bSuccess = false;
1983
- FString ErrorMessage;
1984
-
1985
- if (FStrProperty *StrProp = CastField<FStrProperty>(FoundProperty)) {
1986
- void *PropAddr = StrProp->ContainerPtrToValuePtr<void>(TargetComponent);
1987
- StrProp->SetPropertyValue(PropAddr, PropertyValue);
1988
- bSuccess = true;
1989
- } else if (FFloatProperty *FloatProp =
1990
- CastField<FFloatProperty>(FoundProperty)) {
1991
- void *PropAddr = FloatProp->ContainerPtrToValuePtr<void>(TargetComponent);
1992
- float Value = FCString::Atof(*PropertyValue);
1993
- FloatProp->SetPropertyValue(PropAddr, Value);
1994
- bSuccess = true;
1995
- } else if (FDoubleProperty *DoubleProp =
1996
- CastField<FDoubleProperty>(FoundProperty)) {
1997
- void *PropAddr =
1998
- DoubleProp->ContainerPtrToValuePtr<void>(TargetComponent);
1999
- double Value = FCString::Atod(*PropertyValue);
2000
- DoubleProp->SetPropertyValue(PropAddr, Value);
2001
- bSuccess = true;
2002
- } else if (FIntProperty *IntProp = CastField<FIntProperty>(FoundProperty)) {
2003
- void *PropAddr = IntProp->ContainerPtrToValuePtr<void>(TargetComponent);
2004
- int32 Value = FCString::Atoi(*PropertyValue);
2005
- IntProp->SetPropertyValue(PropAddr, Value);
2006
- bSuccess = true;
2007
- } else if (FBoolProperty *BoolProp =
2008
- CastField<FBoolProperty>(FoundProperty)) {
2009
- void *PropAddr = BoolProp->ContainerPtrToValuePtr<void>(TargetComponent);
2010
- bool Value = PropertyValue.ToBool();
2011
- BoolProp->SetPropertyValue(PropAddr, Value);
2012
- bSuccess = true;
2013
- } else {
2014
- ErrorMessage =
2015
- FString::Printf(TEXT("Property type '%s' not supported for setting"),
2016
- *FoundProperty->GetClass()->GetName());
2017
- }
2018
-
2019
- if (bSuccess) {
2020
- if (USceneComponent *SceneComponent =
2021
- Cast<USceneComponent>(TargetComponent)) {
2022
- SceneComponent->MarkRenderStateDirty();
2023
- SceneComponent->UpdateComponentToWorld();
2024
- }
2025
- TargetComponent->MarkPackageDirty();
2026
-
2027
- Result->SetStringField(TEXT("componentName"), TargetComponent->GetName());
2028
- Result->SetStringField(TEXT("propertyName"), PropertyName);
2029
- Result->SetStringField(TEXT("value"), PropertyValue);
2030
- SendAutomationResponse(RequestingSocket, RequestId, true,
2031
- TEXT("Component property set"), Result, FString());
2032
- } else {
2033
- Result->SetStringField(TEXT("error"), ErrorMessage);
2034
- SendAutomationResponse(RequestingSocket, RequestId, false,
2035
- TEXT("Failed to set component property"), Result,
2036
- TEXT("PROPERTY_SET_FAILED"));
2037
- }
2038
- return true;
2039
- #else
2040
- SendAutomationResponse(RequestingSocket, RequestId, false,
2041
- TEXT("set_component_property requires editor build"),
2042
- nullptr, TEXT("NOT_IMPLEMENTED"));
2043
- return true;
2044
- #endif
2045
- }
2046
-
2047
- return true;
2048
- }
2049
-
2050
- bool UMcpAutomationBridgeSubsystem::HandleCreateProceduralTerrain(
2051
- const FString &RequestId, const FString &Action,
2052
- const TSharedPtr<FJsonObject> &Payload,
2053
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2054
- const FString Lower = Action.ToLower();
2055
- if (!Lower.Equals(TEXT("create_procedural_terrain"),
2056
- ESearchCase::IgnoreCase)) {
2057
- return false;
2058
- }
2059
-
2060
- #if WITH_EDITOR
2061
- if (!Payload.IsValid()) {
2062
- SendAutomationError(RequestingSocket, RequestId,
2063
- TEXT("create_procedural_terrain payload missing"),
2064
- TEXT("INVALID_PAYLOAD"));
2065
- return true;
2066
- }
2067
-
2068
- FString Name;
2069
- if (!Payload->TryGetStringField(TEXT("name"), Name) || Name.IsEmpty()) {
2070
- SendAutomationError(RequestingSocket, RequestId, TEXT("name required"),
2071
- TEXT("INVALID_ARGUMENT"));
2072
- return true;
2073
- }
2074
-
2075
- FVector Location(0, 0, 0);
2076
- const TArray<TSharedPtr<FJsonValue>> *LocArr = nullptr;
2077
- if (Payload->TryGetArrayField(TEXT("location"), LocArr) && LocArr &&
2078
- LocArr->Num() >= 3) {
2079
- Location.X = (*LocArr)[0]->AsNumber();
2080
- Location.Y = (*LocArr)[1]->AsNumber();
2081
- Location.Z = (*LocArr)[2]->AsNumber();
2082
- }
2083
-
2084
- double SizeX = 2000.0;
2085
- double SizeY = 2000.0;
2086
- Payload->TryGetNumberField(TEXT("sizeX"), SizeX);
2087
- Payload->TryGetNumberField(TEXT("sizeY"), SizeY);
2088
-
2089
- int32 Subdivisions = 50;
2090
- Payload->TryGetNumberField(TEXT("subdivisions"), Subdivisions);
2091
- Subdivisions = FMath::Clamp(Subdivisions, 2, 255);
2092
-
2093
- FString MaterialPath;
2094
- Payload->TryGetStringField(TEXT("material"), MaterialPath);
2095
-
2096
- if (!GEditor) {
2097
- SendAutomationError(RequestingSocket, RequestId,
2098
- TEXT("Editor not available"),
2099
- TEXT("EDITOR_NOT_AVAILABLE"));
2100
- return true;
2101
- }
2102
-
2103
- UEditorActorSubsystem *ActorSS =
2104
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
2105
- if (!ActorSS) {
2106
- SendAutomationError(RequestingSocket, RequestId,
2107
- TEXT("EditorActorSubsystem not available"),
2108
- TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
2109
- return true;
2110
- }
2111
-
2112
- AActor *NewActor = SpawnActorInActiveWorld<AActor>(
2113
- AActor::StaticClass(), Location, FRotator::ZeroRotator, Name);
2114
- if (!NewActor) {
2115
- SendAutomationError(RequestingSocket, RequestId,
2116
- TEXT("Failed to spawn actor"), TEXT("SPAWN_FAILED"));
2117
- return true;
2118
- }
2119
-
2120
- UProceduralMeshComponent *ProcMesh = NewObject<UProceduralMeshComponent>(
2121
- NewActor, FName(TEXT("ProceduralTerrain")));
2122
- if (!ProcMesh) {
2123
- ActorSS->DestroyActor(NewActor);
2124
- SendAutomationError(RequestingSocket, RequestId,
2125
- TEXT("Failed to create ProceduralMeshComponent"),
2126
- TEXT("COMPONENT_CREATION_FAILED"));
2127
- return true;
2128
- }
2129
-
2130
- ProcMesh->RegisterComponent();
2131
- NewActor->SetRootComponent(ProcMesh);
2132
- NewActor->AddInstanceComponent(ProcMesh);
2133
-
2134
- // Generate grid
2135
- TArray<FVector> Vertices;
2136
- TArray<int32> Triangles;
2137
- TArray<FVector> Normals;
2138
- TArray<FVector2D> UV0;
2139
- TArray<FColor> VertexColors;
2140
- TArray<FProcMeshTangent> Tangents;
2141
-
2142
- const float StepX = SizeX / Subdivisions;
2143
- const float StepY = SizeY / Subdivisions;
2144
- const float UVStep = 1.0f / Subdivisions;
2145
-
2146
- for (int32 Y = 0; Y <= Subdivisions; Y++) {
2147
- for (int32 X = 0; X <= Subdivisions; X++) {
2148
- float Z = 0.0f;
2149
- // Simple sine wave terrain as default since we can't easily parse the
2150
- // math string
2151
- Z = FMath::Sin(X * 0.1f) * 50.0f + FMath::Cos(Y * 0.1f) * 30.0f;
2152
-
2153
- Vertices.Add(FVector(X * StepX - SizeX / 2, Y * StepY - SizeY / 2, Z));
2154
- Normals.Add(FVector(0, 0, 1)); // Simplified normal
2155
- UV0.Add(FVector2D(X * UVStep, Y * UVStep));
2156
- VertexColors.Add(FColor::White);
2157
- Tangents.Add(FProcMeshTangent(1, 0, 0));
2158
- }
2159
- }
2160
-
2161
- for (int32 Y = 0; Y < Subdivisions; Y++) {
2162
- for (int32 X = 0; X < Subdivisions; X++) {
2163
- int32 TopLeft = Y * (Subdivisions + 1) + X;
2164
- int32 TopRight = TopLeft + 1;
2165
- int32 BottomLeft = (Y + 1) * (Subdivisions + 1) + X;
2166
- int32 BottomRight = BottomLeft + 1;
2167
-
2168
- Triangles.Add(TopLeft);
2169
- Triangles.Add(BottomLeft);
2170
- Triangles.Add(TopRight);
2171
-
2172
- Triangles.Add(TopRight);
2173
- Triangles.Add(BottomLeft);
2174
- Triangles.Add(BottomRight);
2175
- }
2176
- }
2177
-
2178
- ProcMesh->CreateMeshSection(0, Vertices, Triangles, Normals, UV0,
2179
- VertexColors, Tangents, true);
2180
-
2181
- if (!MaterialPath.IsEmpty()) {
2182
- UMaterialInterface *Mat =
2183
- LoadObject<UMaterialInterface>(nullptr, *MaterialPath);
2184
- if (Mat) {
2185
- ProcMesh->SetMaterial(0, Mat);
2186
- }
2187
- }
2188
-
2189
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2190
- Resp->SetBoolField(TEXT("success"), true);
2191
- Resp->SetStringField(TEXT("actor_name"), NewActor->GetActorLabel());
2192
- Resp->SetNumberField(TEXT("vertices"), Vertices.Num());
2193
- Resp->SetNumberField(TEXT("triangles"), Triangles.Num() / 3);
2194
-
2195
- TSharedPtr<FJsonObject> SizeObj = MakeShared<FJsonObject>();
2196
- SizeObj->SetNumberField(TEXT("x"), SizeX);
2197
- SizeObj->SetNumberField(TEXT("y"), SizeY);
2198
- Resp->SetObjectField(TEXT("size"), SizeObj);
2199
- Resp->SetNumberField(TEXT("subdivisions"), Subdivisions);
2200
-
2201
- SendAutomationResponse(RequestingSocket, RequestId, true,
2202
- TEXT("Procedural terrain created"), Resp, FString());
2203
- return true;
2204
- #else
2205
- SendAutomationResponse(
2206
- RequestingSocket, RequestId, false,
2207
- TEXT("create_procedural_terrain requires editor build."), nullptr,
2208
- TEXT("NOT_IMPLEMENTED"));
2209
- return true;
2210
- #endif
2211
- }
2212
-
2213
- bool UMcpAutomationBridgeSubsystem::HandleBakeLightmap(
2214
- const FString &RequestId, const FString &Action,
2215
- const TSharedPtr<FJsonObject> &Payload,
2216
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2217
- const FString Lower = Action.ToLower();
2218
- if (!Lower.Equals(TEXT("bake_lightmap"), ESearchCase::IgnoreCase)) {
2219
- return false;
2220
- }
2221
-
2222
- #if WITH_EDITOR
2223
- FString QualityStr = TEXT("Preview");
2224
- if (Payload.IsValid())
2225
- Payload->TryGetStringField(TEXT("quality"), QualityStr);
2226
-
2227
- // Reuse HandleExecuteEditorFunction logic
2228
- TSharedPtr<FJsonObject> P = MakeShared<FJsonObject>();
2229
- P->SetStringField(TEXT("functionName"), TEXT("BUILD_LIGHTING"));
2230
- P->SetStringField(TEXT("quality"), QualityStr);
2231
-
2232
- return HandleExecuteEditorFunction(RequestId, TEXT("execute_editor_function"),
2233
- P, RequestingSocket);
2234
-
2235
- #else
2236
- SendAutomationResponse(RequestingSocket, RequestId, false,
2237
- TEXT("Requires editor"), nullptr,
2238
- TEXT("NOT_IMPLEMENTED"));
2239
- return true;
2240
- #endif
2241
- }