unreal-engine-mcp-server 0.5.4 → 0.5.6

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