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,2393 +0,0 @@
1
- #include "McpAutomationBridgeGlobals.h"
2
- #include "McpAutomationBridgeHelpers.h"
3
- #include "McpAutomationBridgeSubsystem.h"
4
-
5
- #if WITH_EDITOR
6
- #include "Animation/AnimBlueprint.h"
7
- #include "Animation/AnimBlueprintGeneratedClass.h"
8
- #include "Animation/AnimMontage.h"
9
- #include "Animation/AnimSequence.h"
10
- #include "Animation/AnimationAsset.h"
11
- #include "Animation/Skeleton.h"
12
- #include "Engine/SkeletalMesh.h"
13
-
14
- #if __has_include("Animation/AnimationBlueprintLibrary.h")
15
- #include "Animation/AnimationBlueprintLibrary.h"
16
- #elif __has_include("AnimationBlueprintLibrary.h")
17
- #include "AnimationBlueprintLibrary.h"
18
- #endif
19
- #if __has_include("Animation/AnimBlueprintLibrary.h")
20
- #include "Animation/AnimBlueprintLibrary.h"
21
- #endif
22
- #include "Animation/BlendSpace.h"
23
- #include "Animation/BlendSpace1D.h"
24
- #include "Editor.h"
25
- #include "Editor/EditorEngine.h"
26
- #include "EngineUtils.h"
27
- #include "RenderingThread.h"
28
-
29
- #if __has_include("Animation/BlendSpaceBase.h")
30
- #include "Animation/BlendSpaceBase.h"
31
- #define MCP_HAS_BLENDSPACE_BASE 1
32
- #elif __has_include("BlendSpaceBase.h")
33
- #include "BlendSpaceBase.h"
34
- #define MCP_HAS_BLENDSPACE_BASE 1
35
- #else
36
- #include "Animation/AnimTypes.h"
37
- #define MCP_HAS_BLENDSPACE_BASE 0
38
- #endif
39
- #if __has_include("Factories/BlendSpaceFactoryNew.h") && \
40
- __has_include("Factories/BlendSpaceFactory1D.h")
41
- #include "Factories/BlendSpaceFactory1D.h"
42
- #include "Factories/BlendSpaceFactoryNew.h"
43
-
44
- #define MCP_HAS_BLENDSPACE_FACTORY 1
45
- #else
46
- #define MCP_HAS_BLENDSPACE_FACTORY 0
47
- #endif
48
- #include "ControlRig.h"
49
- // ControlRig headers removed for dynamic loading compatibility
50
- // #include "ControlRigBlueprint.h" etc.
51
- #include "AssetRegistry/AssetRegistryModule.h"
52
- #include "AssetToolsModule.h"
53
- #include "EditorAssetLibrary.h"
54
- #include "Factories/AnimBlueprintFactory.h"
55
- #include "Factories/AnimMontageFactory.h"
56
- #include "Factories/AnimSequenceFactory.h"
57
- #include "Factories/PhysicsAssetFactory.h"
58
- #include "Kismet2/BlueprintEditorUtils.h"
59
- #include "Misc/PackageName.h"
60
- #include "Misc/Paths.h"
61
- #include "Modules/ModuleManager.h"
62
- #include "PhysicsEngine/PhysicsAsset.h"
63
-
64
- #if __has_include("Subsystems/EditorActorSubsystem.h")
65
- #include "Subsystems/EditorActorSubsystem.h"
66
- #elif __has_include("EditorActorSubsystem.h")
67
- #include "EditorActorSubsystem.h"
68
- #endif
69
- #if __has_include("Subsystems/AssetEditorSubsystem.h")
70
- #include "Subsystems/AssetEditorSubsystem.h"
71
- #define MCP_HAS_ASSET_EDITOR_SUBSYSTEM 1
72
- #elif __has_include("AssetEditorSubsystem.h")
73
- #include "AssetEditorSubsystem.h"
74
- #define MCP_HAS_ASSET_EDITOR_SUBSYSTEM 1
75
- #else
76
- #define MCP_HAS_ASSET_EDITOR_SUBSYSTEM 0
77
- #endif
78
- #include "UObject/Script.h"
79
- #include "UObject/UnrealType.h"
80
-
81
- namespace {
82
- #if MCP_HAS_BLENDSPACE_FACTORY
83
- /**
84
- * @brief Creates a new 1D or 2D Blend Space asset bound to a target skeleton.
85
- *
86
- * Creates and returns a newly created UBlendSpace (2D) or UBlendSpace1D (1D)
87
- * asset using the appropriate factory and places it at the given package path.
88
- *
89
- * @param AssetName Name to assign to the new asset.
90
- * @param PackagePath Package path where the asset will be created (e.g.
91
- * "/Game/Animations").
92
- * @param TargetSkeleton Skeleton to bind the created Blend Space to.
93
- * @param bTwoDimensional If true, creates a 2D UBlendSpace; if false, creates a
94
- * 1D UBlendSpace1D.
95
- * @param OutError Receives a human-readable error message on failure.
96
- * @return UObject* Pointer to the created blend space asset on success, or
97
- * `nullptr` on failure.
98
- */
99
- static UObject *CreateBlendSpaceAsset(const FString &AssetName,
100
- const FString &PackagePath,
101
- USkeleton *TargetSkeleton,
102
- bool bTwoDimensional, FString &OutError) {
103
- OutError.Reset();
104
-
105
- UFactory *Factory = nullptr;
106
- UClass *DesiredClass = nullptr;
107
-
108
- if (bTwoDimensional) {
109
- UBlendSpaceFactoryNew *Factory2D = NewObject<UBlendSpaceFactoryNew>();
110
- if (!Factory2D) {
111
- OutError = TEXT("Failed to allocate BlendSpace factory");
112
- return nullptr;
113
- }
114
- Factory2D->TargetSkeleton = TargetSkeleton;
115
- Factory = Factory2D;
116
- DesiredClass = UBlendSpace::StaticClass();
117
- } else {
118
- UBlendSpaceFactory1D *Factory1D = NewObject<UBlendSpaceFactory1D>();
119
- if (!Factory1D) {
120
- OutError = TEXT("Failed to allocate BlendSpace1D factory");
121
- return nullptr;
122
- }
123
- Factory1D->TargetSkeleton = TargetSkeleton;
124
- Factory = Factory1D;
125
- DesiredClass = UBlendSpace1D::StaticClass();
126
- }
127
-
128
- if (!Factory || !DesiredClass) {
129
- OutError = TEXT("BlendSpace factory unavailable");
130
- return nullptr;
131
- }
132
-
133
- FAssetToolsModule &AssetToolsModule =
134
- FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
135
- return AssetToolsModule.Get().CreateAsset(AssetName, PackagePath,
136
- DesiredClass, Factory);
137
- }
138
-
139
- /**
140
- * @brief Applies axis range and grid configuration to a blend space asset.
141
- *
142
- * Reads numeric fields from the provided JSON payload and updates the blend
143
- * space's first axis (minX, maxX, gridX) and, if bTwoDimensional is true,
144
- * the second axis (minY, maxY, gridY). Marks the asset package dirty when
145
- * modifications are applied.
146
- *
147
- * @param BlendSpaceAsset Blend space or blend space base object to configure.
148
- * If null, the function is a no-op.
149
- * @param Payload JSON object containing axis configuration fields:
150
- * - "minX", "maxX", "gridX" for axis 0 (required defaults:
151
- * 0,1,3)
152
- * - "minY", "maxY", "gridY" for axis 1 when bTwoDimensional is
153
- * true
154
- * @param bTwoDimensional If true, the second axis is also configured.
155
- *
156
- * Notes:
157
- * - If the engine headers/types required to modify blend parameters are
158
- * unavailable, the function logs and skips axis configuration.
159
- * - Grid values are clamped to a minimum of 1.
160
- */
161
- static void ApplyBlendSpaceConfiguration(UObject *BlendSpaceAsset,
162
- const TSharedPtr<FJsonObject> &Payload,
163
- bool bTwoDimensional) {
164
- if (!BlendSpaceAsset || !Payload.IsValid()) {
165
- return;
166
- }
167
-
168
- double MinX = 0.0, MaxX = 1.0, GridX = 3.0;
169
- Payload->TryGetNumberField(TEXT("minX"), MinX);
170
- Payload->TryGetNumberField(TEXT("maxX"), MaxX);
171
- Payload->TryGetNumberField(TEXT("gridX"), GridX);
172
-
173
- #if MCP_HAS_BLENDSPACE_BASE
174
- if (UBlendSpaceBase *BlendBase = Cast<UBlendSpaceBase>(BlendSpaceAsset)) {
175
- BlendBase->Modify();
176
-
177
- FBlendParameter &Axis0 =
178
- const_cast<FBlendParameter &>(BlendBase->GetBlendParameter(0));
179
- Axis0.Min = static_cast<float>(MinX);
180
- Axis0.Max = static_cast<float>(MaxX);
181
- Axis0.GridNum = FMath::Max(1, static_cast<int32>(GridX));
182
-
183
- if (bTwoDimensional) {
184
- double MinY = 0.0, MaxY = 1.0, GridY = 3.0;
185
- Payload->TryGetNumberField(TEXT("minY"), MinY);
186
- Payload->TryGetNumberField(TEXT("maxY"), MaxY);
187
- Payload->TryGetNumberField(TEXT("gridY"), GridY);
188
-
189
- FBlendParameter &Axis1 =
190
- const_cast<FBlendParameter &>(BlendBase->GetBlendParameter(1));
191
- Axis1.Min = static_cast<float>(MinY);
192
- Axis1.Max = static_cast<float>(MaxY);
193
- Axis1.GridNum = FMath::Max(1, static_cast<int32>(GridY));
194
- }
195
-
196
- BlendBase->MarkPackageDirty();
197
- }
198
- #else
199
- UE_LOG(LogMcpAutomationBridgeSubsystem, Verbose,
200
- TEXT("ApplyBlendSpaceConfiguration: BlendSpaceBase headers "
201
- "unavailable; skipping axis configuration."));
202
- if (bTwoDimensional) {
203
- UE_LOG(LogMcpAutomationBridgeSubsystem, Verbose,
204
- TEXT("Requested 2D blend space but BlendSpaceBase headers are "
205
- "missing; axis configuration skipped."));
206
- }
207
- if (!BlendSpaceAsset->IsA<UBlendSpace>() &&
208
- !BlendSpaceAsset->IsA<UBlendSpace1D>()) {
209
- UE_LOG(
210
- LogMcpAutomationBridgeSubsystem, Warning,
211
- TEXT("ApplyBlendSpaceConfiguration: Asset %s is not a BlendSpace type"),
212
- *BlendSpaceAsset->GetName());
213
- }
214
- #endif
215
- }
216
- #endif /** \
217
- * @brief Executes a list of editor console commands against the \
218
- * current editor world. \
219
- * \
220
- * Skips empty or whitespace-only commands. If any command fails or the \
221
- * editor/world is unavailable, an explanatory message is written to \
222
- * OutErrorMessage. \
223
- * \
224
- * @param Commands Array of editor command strings to execute. \
225
- * @param OutErrorMessage Populated with an error description when \
226
- * execution fails. \
227
- * @return true if all non-empty commands executed successfully, false \
228
- * otherwise. \
229
- */
230
-
231
- static bool ExecuteEditorCommandsInternal(const TArray<FString> &Commands,
232
- FString &OutErrorMessage) {
233
- OutErrorMessage.Reset();
234
-
235
- if (!GEditor) {
236
- OutErrorMessage = TEXT("Editor instance unavailable");
237
- return false;
238
- }
239
-
240
- UWorld *EditorWorld = nullptr;
241
- FWorldContext &EditorContext = GEditor->GetEditorWorldContext(false);
242
- EditorWorld = EditorContext.World();
243
-
244
- for (const FString &Command : Commands) {
245
- const FString Trimmed = Command.TrimStartAndEnd();
246
- if (Trimmed.IsEmpty()) {
247
- continue;
248
- }
249
-
250
- if (!GEditor->Exec(EditorWorld, *Trimmed)) {
251
- OutErrorMessage = FString::Printf(
252
- TEXT("Failed to execute editor command: %s"), *Trimmed);
253
- return false;
254
- }
255
- }
256
-
257
- return true;
258
- }
259
- } // namespace
260
- #else
261
- #define MCP_HAS_BLENDSPACE_FACTORY 0
262
- #endif // WITH_EDITOR
263
-
264
- /**
265
- * @brief Process an "animation_physics" automation request and send a
266
- * structured response.
267
- *
268
- * Handles sub-actions encoded in the JSON payload (for example: cleanup,
269
- * create_animation_bp, create_blend_space, create_state_machine, setup_ik,
270
- * configure_vehicle, setup_physics_simulation, create_animation_asset,
271
- * setup_retargeting, play_anim_montage, add_notify, etc.). In editor builds
272
- * this may create/modify assets, execute editor commands, or perform
273
- * actor/component operations; in non-editor builds it will return a
274
- * not-implemented response.
275
- *
276
- * @param RequestId Unique identifier for the incoming request; included in the
277
- * response.
278
- * @param Action Top-level action string (expected to be "animation_physics" or
279
- * start with it).
280
- * @param Payload JSON object containing the sub-action and parameters required
281
- * to perform it.
282
- * @param RequestingSocket Optional websocket that will receive the automation
283
- * response/error.
284
- * @return true if the request was handled (a response was sent, even on error);
285
- * false if the action did not match "animation_physics" and the handler did not
286
- * process it.
287
- */
288
- bool UMcpAutomationBridgeSubsystem::HandleAnimationPhysicsAction(
289
- const FString &RequestId, const FString &Action,
290
- const TSharedPtr<FJsonObject> &Payload,
291
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
292
- UE_LOG(LogMcpAutomationBridgeSubsystem, Verbose,
293
- TEXT(">>> HandleAnimationPhysicsAction ENTRY: RequestId=%s "
294
- "RawAction='%s'"),
295
- *RequestId, *Action);
296
- const FString Lower = Action.ToLower();
297
- if (!Lower.Equals(TEXT("animation_physics"), ESearchCase::IgnoreCase) &&
298
- !Lower.StartsWith(TEXT("animation_physics")))
299
- return false;
300
-
301
- if (!Payload.IsValid()) {
302
- SendAutomationError(RequestingSocket, RequestId,
303
- TEXT("animation_physics payload missing."),
304
- TEXT("INVALID_PAYLOAD"));
305
- return true;
306
- }
307
-
308
- FString SubAction;
309
- Payload->TryGetStringField(TEXT("action"), SubAction);
310
- const FString LowerSub = SubAction.ToLower();
311
- UE_LOG(LogMcpAutomationBridgeSubsystem, Verbose,
312
- TEXT("HandleAnimationPhysicsAction: subaction='%s'"), *LowerSub);
313
-
314
- #if WITH_EDITOR
315
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
316
- Resp->SetStringField(TEXT("action"), LowerSub);
317
- bool bSuccess = false;
318
- FString Message;
319
- FString ErrorCode;
320
-
321
- if (LowerSub == TEXT("cleanup")) {
322
- const TArray<TSharedPtr<FJsonValue>> *ArtifactsArray = nullptr;
323
- if (!Payload->TryGetArrayField(TEXT("artifacts"), ArtifactsArray) ||
324
- !ArtifactsArray) {
325
- Message = TEXT("artifacts array required for cleanup");
326
- ErrorCode = TEXT("INVALID_ARGUMENT");
327
- } else {
328
- TArray<FString> Cleaned;
329
- TArray<FString> Missing;
330
- TArray<FString> Failed;
331
-
332
- for (const TSharedPtr<FJsonValue> &Val : *ArtifactsArray) {
333
- if (!Val.IsValid() || Val->Type != EJson::String) {
334
- continue;
335
- }
336
-
337
- const FString ArtifactPath = Val->AsString().TrimStartAndEnd();
338
- if (ArtifactPath.IsEmpty()) {
339
- continue;
340
- }
341
-
342
- if (UEditorAssetLibrary::DoesAssetExist(ArtifactPath)) {
343
- // Close editors to ensure asset can be deleted
344
- #if MCP_HAS_ASSET_EDITOR_SUBSYSTEM
345
- if (GEditor) {
346
- UObject *Asset = LoadObject<UObject>(nullptr, *ArtifactPath);
347
- if (Asset) {
348
- if (UAssetEditorSubsystem *AssetEditorSubsystem =
349
- GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()) {
350
- AssetEditorSubsystem->CloseAllEditorsForAsset(Asset);
351
- }
352
- }
353
- }
354
- #endif
355
-
356
- // Flush before deleting to release references
357
- if (GEditor) {
358
- FlushRenderingCommands();
359
- GEditor->ForceGarbageCollection(true);
360
- FlushRenderingCommands();
361
- }
362
-
363
- if (UEditorAssetLibrary::DeleteAsset(ArtifactPath)) {
364
- Cleaned.Add(ArtifactPath);
365
- } else {
366
- Failed.Add(ArtifactPath);
367
- }
368
- } else {
369
- Missing.Add(ArtifactPath);
370
- }
371
- }
372
-
373
- TArray<TSharedPtr<FJsonValue>> CleanedArray;
374
- for (const FString &Path : Cleaned) {
375
- CleanedArray.Add(MakeShared<FJsonValueString>(Path));
376
- }
377
- if (CleanedArray.Num() > 0) {
378
- Resp->SetArrayField(TEXT("cleaned"), CleanedArray);
379
- }
380
- Resp->SetNumberField(TEXT("cleanedCount"), Cleaned.Num());
381
-
382
- if (Missing.Num() > 0) {
383
- TArray<TSharedPtr<FJsonValue>> MissingArray;
384
- for (const FString &Path : Missing) {
385
- MissingArray.Add(MakeShared<FJsonValueString>(Path));
386
- }
387
- Resp->SetArrayField(TEXT("missing"), MissingArray);
388
- }
389
-
390
- if (Failed.Num() > 0) {
391
- TArray<TSharedPtr<FJsonValue>> FailedArray;
392
- for (const FString &Path : Failed) {
393
- FailedArray.Add(MakeShared<FJsonValueString>(Path));
394
- }
395
- Resp->SetArrayField(TEXT("failed"), FailedArray);
396
- }
397
-
398
- if (Cleaned.Num() > 0 && Failed.Num() == 0) {
399
- bSuccess = true;
400
- Message = TEXT("Animation artifacts removed");
401
- } else {
402
- bSuccess = false;
403
- Message = Failed.Num() > 0
404
- ? TEXT("Some animation artifacts could not be removed")
405
- : TEXT("No animation artifacts were removed");
406
- ErrorCode =
407
- Failed.Num() > 0 ? TEXT("CLEANUP_PARTIAL") : TEXT("CLEANUP_NO_OP");
408
- Resp->SetStringField(TEXT("error"), Message);
409
- }
410
- }
411
- } else if (LowerSub == TEXT("create_animation_bp")) {
412
- FString Name;
413
- if (!Payload->TryGetStringField(TEXT("name"), Name) || Name.IsEmpty()) {
414
- Message = TEXT("name field required for animation blueprint creation");
415
- ErrorCode = TEXT("INVALID_ARGUMENT");
416
- Resp->SetStringField(TEXT("error"), Message);
417
- } else {
418
- FString SavePath;
419
- Payload->TryGetStringField(TEXT("savePath"), SavePath);
420
- if (SavePath.IsEmpty()) {
421
- SavePath = TEXT("/Game/Animations");
422
- }
423
-
424
- FString SkeletonPath;
425
- Payload->TryGetStringField(TEXT("skeletonPath"), SkeletonPath);
426
-
427
- USkeleton *TargetSkeleton = nullptr;
428
- if (!SkeletonPath.IsEmpty()) {
429
- TargetSkeleton = LoadObject<USkeleton>(nullptr, *SkeletonPath);
430
- }
431
-
432
- // Fallback: try meshPath if skeleton missing
433
- if (!TargetSkeleton) {
434
- FString MeshPath;
435
- if (Payload->TryGetStringField(TEXT("meshPath"), MeshPath) &&
436
- !MeshPath.IsEmpty()) {
437
- USkeletalMesh *Mesh = LoadObject<USkeletalMesh>(nullptr, *MeshPath);
438
- if (Mesh) {
439
- TargetSkeleton = Mesh->GetSkeleton();
440
- }
441
- }
442
- }
443
-
444
- if (!TargetSkeleton) {
445
- Message =
446
- TEXT("Valid skeletonPath or meshPath required to find skeleton");
447
- ErrorCode = TEXT("INVALID_ARGUMENT");
448
- Resp->SetStringField(TEXT("error"), Message);
449
- } else {
450
- UAnimBlueprintFactory *Factory = NewObject<UAnimBlueprintFactory>();
451
- Factory->TargetSkeleton = TargetSkeleton;
452
-
453
- // Allow parent class override
454
- FString ParentClassPath;
455
- if (Payload->TryGetStringField(TEXT("parentClass"), ParentClassPath) &&
456
- !ParentClassPath.IsEmpty()) {
457
- UClass *ParentClass = LoadClass<UObject>(nullptr, *ParentClassPath);
458
- if (ParentClass) {
459
- Factory->ParentClass = ParentClass;
460
- }
461
- }
462
-
463
- FAssetToolsModule &AssetToolsModule =
464
- FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
465
- UObject *NewAsset = AssetToolsModule.Get().CreateAsset(
466
- Name, SavePath, UAnimBlueprint::StaticClass(), Factory);
467
-
468
- if (NewAsset) {
469
- bSuccess = true;
470
- Message = TEXT("Animation Blueprint created");
471
- Resp->SetStringField(TEXT("blueprintPath"), NewAsset->GetPathName());
472
- Resp->SetStringField(TEXT("skeletonPath"),
473
- TargetSkeleton->GetPathName());
474
- UEditorAssetLibrary::SaveAsset(NewAsset->GetPathName());
475
- } else {
476
- Message = TEXT("Failed to create Animation Blueprint asset");
477
- ErrorCode = TEXT("ASSET_CREATION_FAILED");
478
- Resp->SetStringField(TEXT("error"), Message);
479
- }
480
- }
481
- }
482
- } else if (LowerSub == TEXT("create_blend_space") ||
483
- LowerSub == TEXT("create_blend_tree") ||
484
- LowerSub == TEXT("create_procedural_anim")) {
485
- FString Name;
486
- if (!Payload->TryGetStringField(TEXT("name"), Name) || Name.IsEmpty()) {
487
- Message = TEXT("name field required for blend space creation");
488
- ErrorCode = TEXT("INVALID_ARGUMENT");
489
- Resp->SetStringField(TEXT("error"), Message);
490
- } else {
491
- FString SavePath;
492
- Payload->TryGetStringField(TEXT("savePath"), SavePath);
493
- if (SavePath.IsEmpty()) {
494
- SavePath = TEXT("/Game/Animations");
495
- }
496
-
497
- FString SkeletonPath;
498
- if (!Payload->TryGetStringField(TEXT("skeletonPath"), SkeletonPath) ||
499
- SkeletonPath.IsEmpty()) {
500
- Message =
501
- TEXT("skeletonPath is required to bind blend space to a skeleton");
502
- ErrorCode = TEXT("INVALID_ARGUMENT");
503
- Resp->SetStringField(TEXT("error"), Message);
504
- } else {
505
- USkeleton *TargetSkeleton =
506
- LoadObject<USkeleton>(nullptr, *SkeletonPath);
507
- if (!TargetSkeleton) {
508
- Message = TEXT("Failed to load skeleton for blend space");
509
- ErrorCode = TEXT("LOAD_FAILED");
510
- Resp->SetStringField(TEXT("error"), Message);
511
- } else {
512
- int32 Dimensions = 1;
513
- double DimensionsNumber = 1.0;
514
- if (Payload->TryGetNumberField(TEXT("dimensions"),
515
- DimensionsNumber)) {
516
- Dimensions = static_cast<int32>(DimensionsNumber);
517
- }
518
- const bool bTwoDimensional =
519
- LowerSub != TEXT("create_blend_space") ? true : (Dimensions >= 2);
520
-
521
- // Validation for Issue #10
522
- double MinX = 0.0, MaxX = 1.0, GridX = 3.0;
523
- Payload->TryGetNumberField(TEXT("minX"), MinX);
524
- Payload->TryGetNumberField(TEXT("maxX"), MaxX);
525
- Payload->TryGetNumberField(TEXT("gridX"), GridX);
526
-
527
- if (MinX >= MaxX) {
528
- Message = TEXT("minX must be less than maxX");
529
- ErrorCode = TEXT("INVALID_ARGUMENT");
530
- Resp->SetStringField(TEXT("error"), Message);
531
- } else if (GridX <= 0) {
532
- Message = TEXT("gridX must be greater than 0");
533
- ErrorCode = TEXT("INVALID_ARGUMENT");
534
- Resp->SetStringField(TEXT("error"), Message);
535
- } else {
536
- if (bTwoDimensional) {
537
- double MinY = 0.0, MaxY = 1.0, GridY = 3.0;
538
- Payload->TryGetNumberField(TEXT("minY"), MinY);
539
- Payload->TryGetNumberField(TEXT("maxY"), MaxY);
540
- Payload->TryGetNumberField(TEXT("gridY"), GridY);
541
-
542
- if (MinY >= MaxY) {
543
- Message = TEXT("minY must be less than maxY");
544
- ErrorCode = TEXT("INVALID_ARGUMENT");
545
- Resp->SetStringField(TEXT("error"), Message);
546
- goto ValidationFailed;
547
- }
548
- if (GridY <= 0) {
549
- Message = TEXT("gridY must be greater than 0");
550
- ErrorCode = TEXT("INVALID_ARGUMENT");
551
- Resp->SetStringField(TEXT("error"), Message);
552
- goto ValidationFailed;
553
- }
554
- }
555
-
556
- FString FactoryError;
557
- #if MCP_HAS_BLENDSPACE_FACTORY
558
- UObject *CreatedBlendAsset = CreateBlendSpaceAsset(
559
- Name, SavePath, TargetSkeleton, bTwoDimensional, FactoryError);
560
- if (CreatedBlendAsset) {
561
- ApplyBlendSpaceConfiguration(CreatedBlendAsset, Payload,
562
- bTwoDimensional);
563
- #if MCP_HAS_BLENDSPACE_BASE
564
- if (UBlendSpaceBase *BlendSpace =
565
- Cast<UBlendSpaceBase>(CreatedBlendAsset)) {
566
- UEditorAssetLibrary::SaveAsset(BlendSpace->GetPathName());
567
-
568
- bSuccess = true;
569
- Message = TEXT("Blend space created successfully");
570
- Resp->SetStringField(TEXT("blendSpacePath"),
571
- BlendSpace->GetPathName());
572
- Resp->SetStringField(TEXT("skeletonPath"), SkeletonPath);
573
- Resp->SetBoolField(TEXT("twoDimensional"), bTwoDimensional);
574
- } else {
575
- Message =
576
- TEXT("Created asset is not a BlendSpaceBase instance");
577
- ErrorCode = TEXT("TYPE_MISMATCH");
578
- Resp->SetStringField(TEXT("error"), Message);
579
- }
580
- #else
581
- UEditorAssetLibrary::SaveAsset(CreatedBlendAsset->GetPathName());
582
-
583
- bSuccess = true;
584
- Message = TEXT("Blend space created (limited configuration)");
585
- Resp->SetStringField(TEXT("blendSpacePath"),
586
- CreatedBlendAsset->GetPathName());
587
- Resp->SetStringField(TEXT("skeletonPath"), SkeletonPath);
588
- Resp->SetBoolField(TEXT("twoDimensional"), bTwoDimensional);
589
- Resp->SetStringField(TEXT("warning"),
590
- TEXT("BlendSpaceBase headers unavailable; "
591
- "axis configuration skipped."));
592
- #endif // MCP_HAS_BLENDSPACE_BASE
593
- } else {
594
- Message = FactoryError.IsEmpty()
595
- ? TEXT("Failed to create blend space asset")
596
- : FactoryError;
597
- ErrorCode = TEXT("ASSET_CREATION_FAILED");
598
- Resp->SetStringField(TEXT("error"), Message);
599
- }
600
- #else
601
- Message = TEXT(
602
- "Blend space creation requires editor blend space factories");
603
- ErrorCode = TEXT("NOT_AVAILABLE");
604
- Resp->SetStringField(TEXT("error"), Message);
605
- #endif
606
- } // End valid params
607
-
608
- ValidationFailed:;
609
- }
610
- }
611
- }
612
- } else if (LowerSub == TEXT("create_state_machine")) {
613
- FString BlueprintPath;
614
- Payload->TryGetStringField(TEXT("blueprintPath"), BlueprintPath);
615
- if (BlueprintPath.IsEmpty()) {
616
- Payload->TryGetStringField(TEXT("name"), BlueprintPath);
617
- }
618
-
619
- if (BlueprintPath.IsEmpty()) {
620
- Message = TEXT("blueprintPath is required for create_state_machine");
621
- ErrorCode = TEXT("INVALID_ARGUMENT");
622
- Resp->SetStringField(TEXT("error"), Message);
623
- } else {
624
- FString MachineName;
625
- Payload->TryGetStringField(TEXT("machineName"), MachineName);
626
- if (MachineName.IsEmpty()) {
627
- MachineName = TEXT("StateMachine");
628
- }
629
-
630
- TArray<FString> Commands;
631
- Commands.Add(FString::Printf(TEXT("AddAnimStateMachine %s %s"),
632
- *BlueprintPath, *MachineName));
633
-
634
- const TArray<TSharedPtr<FJsonValue>> *StatesArray = nullptr;
635
- if (Payload->TryGetArrayField(TEXT("states"), StatesArray) &&
636
- StatesArray) {
637
- for (const TSharedPtr<FJsonValue> &StateValue : *StatesArray) {
638
- if (!StateValue.IsValid() || StateValue->Type != EJson::Object) {
639
- continue;
640
- }
641
-
642
- const TSharedPtr<FJsonObject> StateObj = StateValue->AsObject();
643
- FString StateName;
644
- StateObj->TryGetStringField(TEXT("name"), StateName);
645
- if (StateName.IsEmpty()) {
646
- continue;
647
- }
648
-
649
- FString AnimationName;
650
- StateObj->TryGetStringField(TEXT("animation"), AnimationName);
651
- Commands.Add(FString::Printf(TEXT("AddAnimState %s %s %s %s"),
652
- *BlueprintPath, *MachineName, *StateName,
653
- *AnimationName));
654
-
655
- bool bIsEntry = false;
656
- bool bIsExit = false;
657
- StateObj->TryGetBoolField(TEXT("isEntry"), bIsEntry);
658
- StateObj->TryGetBoolField(TEXT("isExit"), bIsExit);
659
- if (bIsEntry) {
660
- Commands.Add(FString::Printf(TEXT("SetAnimStateEntry %s %s %s"),
661
- *BlueprintPath, *MachineName,
662
- *StateName));
663
- }
664
- if (bIsExit) {
665
- Commands.Add(FString::Printf(TEXT("SetAnimStateExit %s %s %s"),
666
- *BlueprintPath, *MachineName,
667
- *StateName));
668
- }
669
- }
670
- }
671
-
672
- const TArray<TSharedPtr<FJsonValue>> *TransitionsArray = nullptr;
673
- if (Payload->TryGetArrayField(TEXT("transitions"), TransitionsArray) &&
674
- TransitionsArray) {
675
- for (const TSharedPtr<FJsonValue> &TransitionValue :
676
- *TransitionsArray) {
677
- if (!TransitionValue.IsValid() ||
678
- TransitionValue->Type != EJson::Object) {
679
- continue;
680
- }
681
-
682
- const TSharedPtr<FJsonObject> TransitionObj =
683
- TransitionValue->AsObject();
684
- FString SourceState;
685
- FString TargetState;
686
- TransitionObj->TryGetStringField(TEXT("sourceState"), SourceState);
687
- TransitionObj->TryGetStringField(TEXT("targetState"), TargetState);
688
- if (SourceState.IsEmpty() || TargetState.IsEmpty()) {
689
- continue;
690
- }
691
- Commands.Add(FString::Printf(TEXT("AddAnimTransition %s %s %s %s"),
692
- *BlueprintPath, *MachineName,
693
- *SourceState, *TargetState));
694
-
695
- FString Condition;
696
- if (TransitionObj->TryGetStringField(TEXT("condition"), Condition) &&
697
- !Condition.IsEmpty()) {
698
- Commands.Add(FString::Printf(
699
- TEXT("SetAnimTransitionRule %s %s %s %s %s"), *BlueprintPath,
700
- *MachineName, *SourceState, *TargetState, *Condition));
701
- }
702
- }
703
- }
704
-
705
- FString CommandError;
706
- if (!ExecuteEditorCommands(Commands, CommandError)) {
707
- Message = CommandError.IsEmpty()
708
- ? TEXT("Failed to create animation state machine")
709
- : CommandError;
710
- ErrorCode = TEXT("COMMAND_FAILED");
711
- Resp->SetStringField(TEXT("error"), Message);
712
- } else {
713
- bSuccess = true;
714
- Message = FString::Printf(TEXT("State machine '%s' added to %s"),
715
- *MachineName, *BlueprintPath);
716
- Resp->SetStringField(TEXT("blueprintPath"), BlueprintPath);
717
- Resp->SetStringField(TEXT("machineName"), MachineName);
718
- }
719
- }
720
- } else if (LowerSub == TEXT("setup_ik")) {
721
- FString IKName;
722
- if (!Payload->TryGetStringField(TEXT("name"), IKName) || IKName.IsEmpty()) {
723
- Message = TEXT("name field required for IK setup");
724
- ErrorCode = TEXT("INVALID_ARGUMENT");
725
- Resp->SetStringField(TEXT("error"), Message);
726
- } else {
727
- FString SavePath;
728
- Payload->TryGetStringField(TEXT("savePath"), SavePath);
729
- if (SavePath.IsEmpty()) {
730
- SavePath = TEXT("/Game/Animations");
731
- }
732
-
733
- FString SkeletonPath;
734
- if (!Payload->TryGetStringField(TEXT("skeletonPath"), SkeletonPath) ||
735
- SkeletonPath.IsEmpty()) {
736
- Message = TEXT("skeletonPath is required to bind IK to a skeleton");
737
- ErrorCode = TEXT("INVALID_ARGUMENT");
738
- Resp->SetStringField(TEXT("error"), Message);
739
- } else {
740
- USkeleton *TargetSkeleton =
741
- LoadObject<USkeleton>(nullptr, *SkeletonPath);
742
- if (!TargetSkeleton) {
743
- Message = TEXT("Failed to load skeleton for IK");
744
- ErrorCode = TEXT("LOAD_FAILED");
745
- Resp->SetStringField(TEXT("error"), Message);
746
- } else {
747
- FString FactoryError;
748
- UBlueprint *ControlRigBlueprint = nullptr;
749
- #if MCP_HAS_CONTROLRIG_FACTORY
750
- ControlRigBlueprint = CreateControlRigBlueprint(
751
- IKName, SavePath, TargetSkeleton, FactoryError);
752
- #else
753
- FactoryError =
754
- TEXT("Control Rig factory not available in this editor build");
755
- #endif
756
- if (!ControlRigBlueprint) {
757
- Message = FactoryError.IsEmpty() ? TEXT("Failed to create IK asset")
758
- : FactoryError;
759
- ErrorCode = TEXT("ASSET_CREATION_FAILED");
760
- Resp->SetStringField(TEXT("error"), Message);
761
- } else {
762
- bSuccess = true;
763
- Message = TEXT("IK setup created successfully");
764
- const FString ControlRigPath = ControlRigBlueprint->GetPathName();
765
- Resp->SetStringField(TEXT("ikPath"), ControlRigPath);
766
- Resp->SetStringField(TEXT("controlRigPath"), ControlRigPath);
767
- Resp->SetStringField(TEXT("skeletonPath"), SkeletonPath);
768
- }
769
- }
770
- }
771
- }
772
- } else if (LowerSub == TEXT("configure_vehicle")) {
773
- FString VehicleName;
774
- if (!Payload->TryGetStringField(TEXT("vehicleName"), VehicleName) ||
775
- VehicleName.IsEmpty()) {
776
- Message = TEXT("vehicleName is required");
777
- ErrorCode = TEXT("INVALID_ARGUMENT");
778
- Resp->SetStringField(TEXT("error"), Message);
779
- } else {
780
- FString VehicleTypeRaw;
781
- Payload->TryGetStringField(TEXT("vehicleType"), VehicleTypeRaw);
782
- if (VehicleTypeRaw.IsEmpty()) {
783
- Message = TEXT("vehicleType is required");
784
- ErrorCode = TEXT("INVALID_ARGUMENT");
785
- Resp->SetStringField(TEXT("error"), Message);
786
- } else {
787
- const FString NormalizedType = VehicleTypeRaw.ToLower();
788
- const TMap<FString, FString> VehicleTypeMap = {
789
- {TEXT("car"), TEXT("Car")},
790
- {TEXT("bike"), TEXT("Bike")},
791
- {TEXT("motorcycle"), TEXT("Bike")},
792
- {TEXT("motorbike"), TEXT("Bike")},
793
- {TEXT("tank"), TEXT("Tank")},
794
- {TEXT("aircraft"), TEXT("Aircraft")},
795
- {TEXT("plane"), TEXT("Aircraft")}};
796
-
797
- const FString *VehicleTypePtr = VehicleTypeMap.Find(NormalizedType);
798
- // Use mapped value or passthrough raw value for unknown types
799
- FString FinalVehicleType =
800
- VehicleTypePtr ? *VehicleTypePtr : VehicleTypeRaw;
801
-
802
- {
803
- TArray<FString> Commands;
804
- Commands.Add(FString::Printf(TEXT("CreateVehicle %s %s"),
805
- *VehicleName, *FinalVehicleType));
806
-
807
- const TArray<TSharedPtr<FJsonValue>> *WheelsArray = nullptr;
808
- if (Payload->TryGetArrayField(TEXT("wheels"), WheelsArray) &&
809
- WheelsArray) {
810
- for (int32 Index = 0; Index < WheelsArray->Num(); ++Index) {
811
- const TSharedPtr<FJsonValue> &WheelValue = (*WheelsArray)[Index];
812
- if (!WheelValue.IsValid() || WheelValue->Type != EJson::Object) {
813
- continue;
814
- }
815
-
816
- const TSharedPtr<FJsonObject> WheelObj = WheelValue->AsObject();
817
- FString WheelName;
818
- WheelObj->TryGetStringField(TEXT("name"), WheelName);
819
- if (WheelName.IsEmpty()) {
820
- WheelName = FString::Printf(TEXT("Wheel_%d"), Index);
821
- }
822
-
823
- double Radius = 0.0, Width = 0.0, Mass = 0.0;
824
- WheelObj->TryGetNumberField(TEXT("radius"), Radius);
825
- WheelObj->TryGetNumberField(TEXT("width"), Width);
826
- WheelObj->TryGetNumberField(TEXT("mass"), Mass);
827
-
828
- Commands.Add(FString::Printf(
829
- TEXT("AddVehicleWheel %s %s %.4f %.4f %.4f"), *VehicleName,
830
- *WheelName, Radius, Width, Mass));
831
-
832
- bool bSteering = false;
833
- if (WheelObj->TryGetBoolField(TEXT("isSteering"), bSteering) &&
834
- bSteering) {
835
- Commands.Add(
836
- FString::Printf(TEXT("SetWheelSteering %s %s true"),
837
- *VehicleName, *WheelName));
838
- }
839
-
840
- bool bDriving = false;
841
- if (WheelObj->TryGetBoolField(TEXT("isDriving"), bDriving) &&
842
- bDriving) {
843
- Commands.Add(FString::Printf(TEXT("SetWheelDriving %s %s true"),
844
- *VehicleName, *WheelName));
845
- }
846
- }
847
- }
848
-
849
- const TSharedPtr<FJsonObject> *EngineObj = nullptr;
850
- if (Payload->TryGetObjectField(TEXT("engine"), EngineObj) &&
851
- EngineObj && (*EngineObj).IsValid()) {
852
- double MaxRPM = 0.0;
853
- (*EngineObj)->TryGetNumberField(TEXT("maxRPM"), MaxRPM);
854
- if (MaxRPM > 0.0) {
855
- Commands.Add(FString::Printf(TEXT("SetEngineMaxRPM %s %.4f"),
856
- *VehicleName, MaxRPM));
857
- }
858
-
859
- const TArray<TSharedPtr<FJsonValue>> *TorqueCurve = nullptr;
860
- if ((*EngineObj)
861
- ->TryGetArrayField(TEXT("torqueCurve"), TorqueCurve) &&
862
- TorqueCurve) {
863
- for (const TSharedPtr<FJsonValue> &TorqueValue : *TorqueCurve) {
864
- if (!TorqueValue.IsValid()) {
865
- continue;
866
- }
867
-
868
- double RPM = 0.0;
869
- double Torque = 0.0;
870
-
871
- if (TorqueValue->Type == EJson::Array) {
872
- const TArray<TSharedPtr<FJsonValue>> TorquePair =
873
- TorqueValue->AsArray();
874
- if (TorquePair.Num() >= 2) {
875
- RPM = TorquePair[0]->AsNumber();
876
- Torque = TorquePair[1]->AsNumber();
877
- }
878
- } else if (TorqueValue->Type == EJson::Object) {
879
- const TSharedPtr<FJsonObject> TorqueObj =
880
- TorqueValue->AsObject();
881
- TorqueObj->TryGetNumberField(TEXT("rpm"), RPM);
882
- TorqueObj->TryGetNumberField(TEXT("torque"), Torque);
883
- }
884
-
885
- Commands.Add(
886
- FString::Printf(TEXT("AddTorqueCurvePoint %s %.4f %.4f"),
887
- *VehicleName, RPM, Torque));
888
- }
889
- }
890
- }
891
-
892
- const TSharedPtr<FJsonObject> *TransmissionObj = nullptr;
893
- if (Payload->TryGetObjectField(TEXT("transmission"),
894
- TransmissionObj) &&
895
- TransmissionObj && (*TransmissionObj).IsValid()) {
896
- const TArray<TSharedPtr<FJsonValue>> *GearsArray = nullptr;
897
- if ((*TransmissionObj)
898
- ->TryGetArrayField(TEXT("gears"), GearsArray) &&
899
- GearsArray) {
900
- for (int32 GearIndex = 0; GearIndex < GearsArray->Num();
901
- ++GearIndex) {
902
- const double GearRatio = (*GearsArray)[GearIndex]->AsNumber();
903
- Commands.Add(FString::Printf(TEXT("SetGearRatio %s %d %.4f"),
904
- *VehicleName, GearIndex,
905
- GearRatio));
906
- }
907
- }
908
-
909
- double FinalDrive = 0.0;
910
- if ((*TransmissionObj)
911
- ->TryGetNumberField(TEXT("finalDriveRatio"), FinalDrive)) {
912
- Commands.Add(FString::Printf(TEXT("SetFinalDriveRatio %s %.4f"),
913
- *VehicleName, FinalDrive));
914
- }
915
- }
916
-
917
- FString CommandError;
918
- if (!ExecuteEditorCommands(Commands, CommandError)) {
919
- Message = CommandError.IsEmpty()
920
- ? TEXT("Failed to configure vehicle")
921
- : CommandError;
922
- ErrorCode = TEXT("COMMAND_FAILED");
923
- Resp->SetStringField(TEXT("error"), Message);
924
- } else {
925
- bSuccess = true;
926
- Message =
927
- FString::Printf(TEXT("Vehicle %s configured"), *VehicleName);
928
- Resp->SetStringField(TEXT("vehicleName"), VehicleName);
929
- Resp->SetStringField(TEXT("vehicleType"), *VehicleTypePtr);
930
-
931
- const TArray<TSharedPtr<FJsonValue>> *PluginDeps = nullptr;
932
- if (Payload->TryGetArrayField(TEXT("pluginDependencies"),
933
- PluginDeps) &&
934
- PluginDeps) {
935
- TArray<TSharedPtr<FJsonValue>> PluginArray;
936
- for (const TSharedPtr<FJsonValue> &DepValue : *PluginDeps) {
937
- if (DepValue.IsValid() && DepValue->Type == EJson::String) {
938
- PluginArray.Add(
939
- MakeShared<FJsonValueString>(DepValue->AsString()));
940
- }
941
- }
942
- if (PluginArray.Num() > 0) {
943
- Resp->SetArrayField(TEXT("pluginDependencies"), PluginArray);
944
- }
945
- }
946
- }
947
- }
948
- }
949
- }
950
- } else if (LowerSub == TEXT("setup_physics_simulation")) {
951
- FString MeshPath;
952
- Payload->TryGetStringField(TEXT("meshPath"), MeshPath);
953
-
954
- FString SkeletonPath;
955
- Payload->TryGetStringField(TEXT("skeletonPath"), SkeletonPath);
956
-
957
- // Support actorName parameter to find skeletal mesh from a spawned actor
958
- FString ActorName;
959
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
960
-
961
- const bool bMeshProvided = !MeshPath.IsEmpty();
962
- const bool bSkeletonProvided = !SkeletonPath.IsEmpty();
963
- const bool bActorProvided = !ActorName.IsEmpty();
964
-
965
- bool bMeshLoadFailed = false;
966
- bool bSkeletonLoadFailed = false;
967
- bool bSkeletonMissingPreview = false;
968
-
969
- USkeletalMesh *TargetMesh = nullptr;
970
- bool bMeshTypeMismatch = false;
971
- FString FoundClassName;
972
-
973
- // If actorName provided, try to find the actor and get its skeletal mesh
974
- if (!bMeshProvided && !bSkeletonProvided && bActorProvided) {
975
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
976
- TEXT("Attempting to find actor by name: '%s'"), *ActorName);
977
- AActor *FoundActor = FindActorByName(ActorName);
978
- if (FoundActor) {
979
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
980
- TEXT("Found actor: '%s' (Label: '%s')"), *FoundActor->GetName(),
981
- *FoundActor->GetActorLabel());
982
- // Try to get skeletal mesh component
983
- if (USkeletalMeshComponent *SkelComp =
984
- FoundActor->FindComponentByClass<USkeletalMeshComponent>()) {
985
- TargetMesh = SkelComp->GetSkeletalMeshAsset();
986
- if (TargetMesh) {
987
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
988
- TEXT("Found skeletal mesh asset: '%s'"),
989
- *TargetMesh->GetName());
990
- } else {
991
- Message =
992
- FString::Printf(TEXT("Actor '%s' has a SkeletalMeshComponent "
993
- "but no SkeletalMesh asset assigned."),
994
- *FoundActor->GetName());
995
- ErrorCode = TEXT("ACTOR_SKELETAL_MESH_ASSET_NULL");
996
- UE_LOG(LogMcpAutomationBridgeSubsystem, Error, TEXT("%s"),
997
- *Message);
998
- }
999
- } else {
1000
- Message = FString::Printf(
1001
- TEXT("Actor '%s' does not have a SkeletalMeshComponent."),
1002
- *FoundActor->GetName());
1003
- ErrorCode = TEXT("ACTOR_NO_SKELETAL_MESH_COMPONENT");
1004
- UE_LOG(LogMcpAutomationBridgeSubsystem, Error, TEXT("%s"), *Message);
1005
- }
1006
- } else {
1007
- Message = FString::Printf(TEXT("Actor '%s' not found."), *ActorName);
1008
- ErrorCode = TEXT("ACTOR_NOT_FOUND");
1009
- UE_LOG(LogMcpAutomationBridgeSubsystem, Error, TEXT("%s"), *Message);
1010
- }
1011
-
1012
- if (!TargetMesh) {
1013
- Resp->SetStringField(TEXT("actorName"), ActorName);
1014
- bSuccess = false;
1015
- SendAutomationResponse(RequestingSocket, RequestId, bSuccess, Message,
1016
- Resp, ErrorCode);
1017
- return true;
1018
- }
1019
- }
1020
-
1021
- if (bMeshProvided) {
1022
- if (UEditorAssetLibrary::DoesAssetExist(MeshPath)) {
1023
- UObject *Asset = UEditorAssetLibrary::LoadAsset(MeshPath);
1024
- TargetMesh = Cast<USkeletalMesh>(Asset);
1025
- if (!TargetMesh && Asset) {
1026
- bMeshTypeMismatch = true;
1027
- FoundClassName = Asset->GetClass()->GetName();
1028
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
1029
- TEXT("setup_physics_simulation: Asset %s is not a "
1030
- "SkeletalMesh (Class: %s)"),
1031
- *MeshPath, *FoundClassName);
1032
- } else if (!Asset) {
1033
- bMeshLoadFailed = true;
1034
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
1035
- TEXT("setup_physics_simulation: failed to load mesh asset %s"),
1036
- *MeshPath);
1037
- }
1038
- } else {
1039
- bMeshLoadFailed = true;
1040
- }
1041
- }
1042
-
1043
- USkeleton *TargetSkeleton = nullptr;
1044
- if (!TargetMesh && bSkeletonProvided) {
1045
- if (UEditorAssetLibrary::DoesAssetExist(SkeletonPath)) {
1046
- TargetSkeleton = LoadObject<USkeleton>(nullptr, *SkeletonPath);
1047
- if (TargetSkeleton) {
1048
- TargetMesh = TargetSkeleton->GetPreviewMesh();
1049
- if (!TargetMesh) {
1050
- bSkeletonMissingPreview = true;
1051
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
1052
- TEXT("setup_physics_simulation: skeleton %s has no preview "
1053
- "mesh"),
1054
- *SkeletonPath);
1055
- }
1056
- } else {
1057
- bSkeletonLoadFailed = true;
1058
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
1059
- TEXT("setup_physics_simulation: failed to load skeleton %s"),
1060
- *SkeletonPath);
1061
- }
1062
- } else {
1063
- bSkeletonLoadFailed = true;
1064
- }
1065
- }
1066
-
1067
- if (!TargetSkeleton && TargetMesh) {
1068
- TargetSkeleton = TargetMesh->GetSkeleton();
1069
- }
1070
-
1071
- if (!TargetMesh) {
1072
- if (bMeshTypeMismatch) {
1073
- Message = FString::Printf(
1074
- TEXT("asset found but is not a SkeletalMesh: %s (is %s)"),
1075
- *MeshPath, *FoundClassName);
1076
- ErrorCode = TEXT("TYPE_MISMATCH");
1077
- Resp->SetStringField(TEXT("meshPath"), MeshPath);
1078
- Resp->SetStringField(TEXT("actualClass"), FoundClassName);
1079
- } else if (bMeshLoadFailed) {
1080
- Message = FString::Printf(TEXT("asset not found: skeletal mesh %s"),
1081
- *MeshPath);
1082
- ErrorCode = TEXT("ASSET_NOT_FOUND");
1083
- Resp->SetStringField(TEXT("meshPath"), MeshPath);
1084
- } else if (bSkeletonLoadFailed) {
1085
- Message = FString::Printf(TEXT("asset not found: skeleton %s"),
1086
- *SkeletonPath);
1087
- ErrorCode = TEXT("ASSET_NOT_FOUND");
1088
- Resp->SetStringField(TEXT("skeletonPath"), SkeletonPath);
1089
- } else if (bSkeletonMissingPreview) {
1090
- Message = FString::Printf(TEXT("asset not found: skeleton %s (no "
1091
- "preview mesh for physics simulation)"),
1092
- *SkeletonPath);
1093
- ErrorCode = TEXT("ASSET_NOT_FOUND");
1094
- Resp->SetStringField(TEXT("skeletonPath"), SkeletonPath);
1095
- } else {
1096
- Message = TEXT("asset not found: no valid skeletal mesh provided for "
1097
- "physics simulation setup");
1098
- ErrorCode = TEXT("ASSET_NOT_FOUND");
1099
- }
1100
-
1101
- Resp->SetStringField(TEXT("error"), Message);
1102
- } else {
1103
- if (!TargetSkeleton && !SkeletonPath.IsEmpty()) {
1104
- TargetSkeleton = LoadObject<USkeleton>(nullptr, *SkeletonPath);
1105
- }
1106
-
1107
- FString PhysicsAssetName;
1108
- Payload->TryGetStringField(TEXT("physicsAssetName"), PhysicsAssetName);
1109
- if (PhysicsAssetName.IsEmpty()) {
1110
- PhysicsAssetName = TargetMesh->GetName() + TEXT("_Physics");
1111
- }
1112
-
1113
- FString SavePath;
1114
- Payload->TryGetStringField(TEXT("savePath"), SavePath);
1115
- if (SavePath.IsEmpty()) {
1116
- SavePath = TEXT("/Game/Physics");
1117
- }
1118
- SavePath = SavePath.TrimStartAndEnd();
1119
-
1120
- if (!FPackageName::IsValidLongPackageName(SavePath)) {
1121
- FString NormalizedPath;
1122
- if (!FPackageName::TryConvertFilenameToLongPackageName(
1123
- SavePath, NormalizedPath)) {
1124
- Message = TEXT("Invalid savePath for physics asset");
1125
- ErrorCode = TEXT("INVALID_ARGUMENT");
1126
- Resp->SetStringField(TEXT("error"), Message);
1127
- SavePath.Reset();
1128
- } else {
1129
- SavePath = NormalizedPath;
1130
- }
1131
- }
1132
-
1133
- if (!SavePath.IsEmpty()) {
1134
- if (!UEditorAssetLibrary::DoesDirectoryExist(SavePath)) {
1135
- UEditorAssetLibrary::MakeDirectory(SavePath);
1136
- }
1137
-
1138
- const FString PhysicsAssetObjectPath =
1139
- FString::Printf(TEXT("%s/%s"), *SavePath, *PhysicsAssetName);
1140
-
1141
- if (UEditorAssetLibrary::DoesAssetExist(PhysicsAssetObjectPath)) {
1142
- bSuccess = true;
1143
- Message = TEXT(
1144
- "Physics simulation already configured - existing asset reused");
1145
- Resp->SetStringField(TEXT("physicsAssetPath"),
1146
- PhysicsAssetObjectPath);
1147
- Resp->SetBoolField(TEXT("existingAsset"), true);
1148
- Resp->SetStringField(TEXT("savePath"), SavePath);
1149
- Resp->SetStringField(TEXT("meshPath"), TargetMesh->GetPathName());
1150
- if (TargetSkeleton) {
1151
- Resp->SetStringField(TEXT("skeletonPath"),
1152
- TargetSkeleton->GetPathName());
1153
- }
1154
- } else {
1155
- UPhysicsAssetFactory *PhysicsFactory =
1156
- NewObject<UPhysicsAssetFactory>();
1157
- if (!PhysicsFactory) {
1158
- Message = TEXT("Failed to allocate physics asset factory");
1159
- ErrorCode = TEXT("FACTORY_FAILED");
1160
- Resp->SetStringField(TEXT("error"), Message);
1161
- } else {
1162
- PhysicsFactory->TargetSkeletalMesh = TargetMesh;
1163
-
1164
- FAssetToolsModule &AssetToolsModule =
1165
- FModuleManager::LoadModuleChecked<FAssetToolsModule>(
1166
- "AssetTools");
1167
- UObject *NewAsset = AssetToolsModule.Get().CreateAsset(
1168
- PhysicsAssetName, SavePath, UPhysicsAsset::StaticClass(),
1169
- PhysicsFactory);
1170
- UPhysicsAsset *PhysicsAsset = Cast<UPhysicsAsset>(NewAsset);
1171
-
1172
- if (!PhysicsAsset) {
1173
- Message = TEXT("Failed to create physics asset");
1174
- ErrorCode = TEXT("ASSET_CREATION_FAILED");
1175
- Resp->SetStringField(TEXT("error"), Message);
1176
- } else {
1177
- bool bAssignToMesh = false;
1178
- Payload->TryGetBoolField(TEXT("assignToMesh"), bAssignToMesh);
1179
-
1180
- UEditorAssetLibrary::SaveAsset(PhysicsAsset->GetPathName());
1181
-
1182
- if (bAssignToMesh) {
1183
- TargetMesh->Modify();
1184
- TargetMesh->SetPhysicsAsset(PhysicsAsset);
1185
- TargetMesh->MarkPackageDirty();
1186
- UEditorAssetLibrary::SaveAsset(TargetMesh->GetPathName());
1187
- }
1188
-
1189
- Resp->SetStringField(TEXT("physicsAssetPath"),
1190
- PhysicsAsset->GetPathName());
1191
- Resp->SetBoolField(TEXT("assignedToMesh"), bAssignToMesh);
1192
- Resp->SetBoolField(TEXT("existingAsset"), false);
1193
- Resp->SetStringField(TEXT("savePath"), SavePath);
1194
- Resp->SetStringField(TEXT("meshPath"), TargetMesh->GetPathName());
1195
- if (TargetSkeleton) {
1196
- Resp->SetStringField(TEXT("skeletonPath"),
1197
- TargetSkeleton->GetPathName());
1198
- }
1199
-
1200
- bSuccess = true;
1201
- Message = TEXT("Physics simulation setup completed");
1202
- }
1203
- }
1204
- }
1205
- }
1206
- }
1207
- } else if (LowerSub == TEXT("create_animation_asset")) {
1208
- FString AssetName;
1209
- if (!Payload->TryGetStringField(TEXT("name"), AssetName) ||
1210
- AssetName.IsEmpty()) {
1211
- Message = TEXT("name required for create_animation_asset");
1212
- ErrorCode = TEXT("INVALID_ARGUMENT");
1213
- Resp->SetStringField(TEXT("error"), Message);
1214
- } else {
1215
- FString SavePath;
1216
- Payload->TryGetStringField(TEXT("savePath"), SavePath);
1217
- if (SavePath.IsEmpty()) {
1218
- SavePath = TEXT("/Game/Animations");
1219
- }
1220
- SavePath = SavePath.TrimStartAndEnd();
1221
-
1222
- if (!FPackageName::IsValidLongPackageName(SavePath)) {
1223
- FString NormalizedPath;
1224
- if (!FPackageName::TryConvertFilenameToLongPackageName(
1225
- SavePath, NormalizedPath)) {
1226
- Message = TEXT("Invalid savePath for animation asset");
1227
- ErrorCode = TEXT("INVALID_ARGUMENT");
1228
- Resp->SetStringField(TEXT("error"), Message);
1229
- SavePath.Reset();
1230
- } else {
1231
- SavePath = NormalizedPath;
1232
- }
1233
- }
1234
-
1235
- FString SkeletonPath;
1236
- Payload->TryGetStringField(TEXT("skeletonPath"), SkeletonPath);
1237
- USkeleton *TargetSkeleton = nullptr;
1238
- const bool bHadSkeletonPath = !SkeletonPath.IsEmpty();
1239
- if (bHadSkeletonPath) {
1240
- if (UEditorAssetLibrary::DoesAssetExist(SkeletonPath)) {
1241
- TargetSkeleton = LoadObject<USkeleton>(nullptr, *SkeletonPath);
1242
- }
1243
- }
1244
-
1245
- if (!TargetSkeleton) {
1246
- if (bHadSkeletonPath) {
1247
- Message =
1248
- FString::Printf(TEXT("Skeleton not found: %s"), *SkeletonPath);
1249
- ErrorCode = TEXT("ASSET_NOT_FOUND");
1250
- } else {
1251
- Message = TEXT("skeletonPath is required for create_animation_asset");
1252
- ErrorCode = TEXT("INVALID_ARGUMENT");
1253
- }
1254
-
1255
- Resp->SetStringField(TEXT("error"), Message);
1256
- } else if (!SavePath.IsEmpty()) {
1257
- if (!UEditorAssetLibrary::DoesDirectoryExist(SavePath)) {
1258
- UEditorAssetLibrary::MakeDirectory(SavePath);
1259
- }
1260
-
1261
- FString AssetType;
1262
- Payload->TryGetStringField(TEXT("assetType"), AssetType);
1263
- AssetType = AssetType.ToLower();
1264
- if (AssetType.IsEmpty()) {
1265
- AssetType = TEXT("sequence");
1266
- }
1267
-
1268
- UFactory *Factory = nullptr;
1269
- UClass *DesiredClass = nullptr;
1270
- FString AssetTypeString;
1271
-
1272
- if (AssetType == TEXT("montage")) {
1273
- UAnimMontageFactory *MontageFactory =
1274
- NewObject<UAnimMontageFactory>();
1275
- if (MontageFactory) {
1276
- MontageFactory->TargetSkeleton = TargetSkeleton;
1277
- Factory = MontageFactory;
1278
- DesiredClass = UAnimMontage::StaticClass();
1279
- AssetTypeString = TEXT("Montage");
1280
- }
1281
- } else {
1282
- UAnimSequenceFactory *SequenceFactory =
1283
- NewObject<UAnimSequenceFactory>();
1284
- if (SequenceFactory) {
1285
- SequenceFactory->TargetSkeleton = TargetSkeleton;
1286
- Factory = SequenceFactory;
1287
- DesiredClass = UAnimSequence::StaticClass();
1288
- AssetTypeString = TEXT("Sequence");
1289
- }
1290
- }
1291
-
1292
- if (!Factory || !DesiredClass) {
1293
- Message = TEXT("Unsupported assetType for create_animation_asset");
1294
- ErrorCode = TEXT("INVALID_ARGUMENT");
1295
- Resp->SetStringField(TEXT("error"), Message);
1296
- } else {
1297
- const FString ObjectPath =
1298
- FString::Printf(TEXT("%s/%s"), *SavePath, *AssetName);
1299
- if (UEditorAssetLibrary::DoesAssetExist(ObjectPath)) {
1300
- bSuccess = true;
1301
- Message =
1302
- TEXT("Animation asset already exists - existing asset reused");
1303
- Resp->SetStringField(TEXT("assetPath"), ObjectPath);
1304
- Resp->SetStringField(TEXT("assetType"), AssetTypeString);
1305
- Resp->SetBoolField(TEXT("existingAsset"), true);
1306
- } else {
1307
- FAssetToolsModule &AssetToolsModule =
1308
- FModuleManager::LoadModuleChecked<FAssetToolsModule>(
1309
- "AssetTools");
1310
- UObject *NewAsset = AssetToolsModule.Get().CreateAsset(
1311
- AssetName, SavePath, DesiredClass, Factory);
1312
-
1313
- if (!NewAsset) {
1314
- Message = TEXT("Failed to create animation asset");
1315
- ErrorCode = TEXT("ASSET_CREATION_FAILED");
1316
- Resp->SetStringField(TEXT("error"), Message);
1317
- } else {
1318
- UEditorAssetLibrary::SaveAsset(NewAsset->GetPathName());
1319
- Resp->SetStringField(TEXT("assetPath"), NewAsset->GetPathName());
1320
- Resp->SetStringField(TEXT("assetType"), AssetTypeString);
1321
- Resp->SetBoolField(TEXT("existingAsset"), false);
1322
- bSuccess = true;
1323
- Message = FString::Printf(TEXT("Animation %s created"),
1324
- *AssetTypeString);
1325
- }
1326
- }
1327
- }
1328
- }
1329
- }
1330
- } else if (LowerSub == TEXT("setup_retargeting")) {
1331
- FString SourceSkeletonPath;
1332
- FString TargetSkeletonPath;
1333
- Payload->TryGetStringField(TEXT("sourceSkeleton"), SourceSkeletonPath);
1334
- Payload->TryGetStringField(TEXT("targetSkeleton"), TargetSkeletonPath);
1335
-
1336
- USkeleton *SourceSkeleton = nullptr;
1337
- USkeleton *TargetSkeleton = nullptr;
1338
-
1339
- if (!SourceSkeletonPath.IsEmpty()) {
1340
- SourceSkeleton = LoadObject<USkeleton>(nullptr, *SourceSkeletonPath);
1341
- }
1342
- if (!TargetSkeletonPath.IsEmpty()) {
1343
- TargetSkeleton = LoadObject<USkeleton>(nullptr, *TargetSkeletonPath);
1344
- }
1345
-
1346
- if (!SourceSkeleton || !TargetSkeleton) {
1347
- bSuccess = false;
1348
- Message =
1349
- TEXT("Retargeting failed - source or target skeleton not found");
1350
- ErrorCode = TEXT("ASSET_NOT_FOUND");
1351
- Resp->SetStringField(TEXT("error"), Message);
1352
- Resp->SetStringField(TEXT("sourceSkeleton"), SourceSkeletonPath);
1353
- Resp->SetStringField(TEXT("targetSkeleton"), TargetSkeletonPath);
1354
- } else {
1355
- const TArray<TSharedPtr<FJsonValue>> *AssetsArray = nullptr;
1356
- if (!Payload->TryGetArrayField(TEXT("assets"), AssetsArray)) {
1357
- Payload->TryGetArrayField(TEXT("retargetAssets"), AssetsArray);
1358
- }
1359
-
1360
- FString SavePath;
1361
- Payload->TryGetStringField(TEXT("savePath"), SavePath);
1362
- if (!SavePath.IsEmpty()) {
1363
- SavePath = SavePath.TrimStartAndEnd();
1364
- if (!FPackageName::IsValidLongPackageName(SavePath)) {
1365
- FString NormalizedPath;
1366
- if (FPackageName::TryConvertFilenameToLongPackageName(
1367
- SavePath, NormalizedPath)) {
1368
- SavePath = NormalizedPath;
1369
- } else {
1370
- SavePath.Reset();
1371
- }
1372
- }
1373
- }
1374
-
1375
- FString Suffix;
1376
- Payload->TryGetStringField(TEXT("suffix"), Suffix);
1377
- if (Suffix.IsEmpty()) {
1378
- Suffix = TEXT("_Retargeted");
1379
- }
1380
-
1381
- bool bOverwrite = false;
1382
- Payload->TryGetBoolField(TEXT("overwrite"), bOverwrite);
1383
-
1384
- TArray<FString> RetargetedAssets;
1385
- TArray<FString> SkippedAssets;
1386
- TArray<TSharedPtr<FJsonValue>> WarningArray;
1387
-
1388
- if (AssetsArray && AssetsArray->Num() > 0) {
1389
- for (const TSharedPtr<FJsonValue> &Value : *AssetsArray) {
1390
- if (!Value.IsValid() || Value->Type != EJson::String) {
1391
- continue;
1392
- }
1393
-
1394
- const FString SourceAssetPath = Value->AsString();
1395
- UAnimSequence *SourceSequence =
1396
- LoadObject<UAnimSequence>(nullptr, *SourceAssetPath);
1397
- if (!SourceSequence) {
1398
- WarningArray.Add(MakeShared<FJsonValueString>(FString::Printf(
1399
- TEXT("Skipped non-sequence asset: %s"), *SourceAssetPath)));
1400
- SkippedAssets.Add(SourceAssetPath);
1401
- continue;
1402
- }
1403
-
1404
- FString DestinationFolder = SavePath;
1405
- if (DestinationFolder.IsEmpty()) {
1406
- const FString SourcePackageName =
1407
- SourceSequence->GetOutermost()->GetName();
1408
- DestinationFolder =
1409
- FPackageName::GetLongPackagePath(SourcePackageName);
1410
- }
1411
-
1412
- if (!DestinationFolder.IsEmpty() &&
1413
- !UEditorAssetLibrary::DoesDirectoryExist(DestinationFolder)) {
1414
- UEditorAssetLibrary::MakeDirectory(DestinationFolder);
1415
- }
1416
-
1417
- FString DestinationAssetName = FPackageName::GetShortName(
1418
- SourceSequence->GetOutermost()->GetName());
1419
- DestinationAssetName += Suffix;
1420
-
1421
- const FString DestinationObjectPath = FString::Printf(
1422
- TEXT("%s/%s"), *DestinationFolder, *DestinationAssetName);
1423
-
1424
- if (UEditorAssetLibrary::DoesAssetExist(DestinationObjectPath)) {
1425
- if (!bOverwrite) {
1426
- WarningArray.Add(MakeShared<FJsonValueString>(FString::Printf(
1427
- TEXT("Retarget destination already exists, skipping: %s"),
1428
- *DestinationObjectPath)));
1429
- SkippedAssets.Add(SourceAssetPath);
1430
- continue;
1431
- }
1432
- } else if (!UEditorAssetLibrary::DuplicateAsset(
1433
- SourceAssetPath, DestinationObjectPath)) {
1434
- WarningArray.Add(MakeShared<FJsonValueString>(FString::Printf(
1435
- TEXT("Failed to duplicate asset: %s"), *SourceAssetPath)));
1436
- SkippedAssets.Add(SourceAssetPath);
1437
- continue;
1438
- }
1439
-
1440
- UAnimSequence *DestinationSequence =
1441
- LoadObject<UAnimSequence>(nullptr, *DestinationObjectPath);
1442
- if (!DestinationSequence) {
1443
- WarningArray.Add(MakeShared<FJsonValueString>(
1444
- FString::Printf(TEXT("Failed to load duplicated asset: %s"),
1445
- *DestinationObjectPath)));
1446
- SkippedAssets.Add(SourceAssetPath);
1447
- continue;
1448
- }
1449
-
1450
- DestinationSequence->Modify();
1451
- DestinationSequence->SetSkeleton(TargetSkeleton);
1452
- DestinationSequence->MarkPackageDirty();
1453
-
1454
- TArray<UAnimSequence *> SourceList;
1455
- SourceList.Add(SourceSequence);
1456
- TArray<UAnimSequence *> DestinationList;
1457
- DestinationList.Add(DestinationSequence);
1458
-
1459
- // Animation retargeting in UE5 requires IK Rig system
1460
- // For now, just use the duplicated asset (created above) without full
1461
- // retargeting
1462
- UE_LOG(LogMcpAutomationBridgeSubsystem, Log,
1463
- TEXT("Animation asset copied (retargeting requires IK Rig "
1464
- "setup)"));
1465
-
1466
- UEditorAssetLibrary::SaveAsset(DestinationSequence->GetPathName());
1467
- RetargetedAssets.Add(DestinationSequence->GetPathName());
1468
- }
1469
- }
1470
-
1471
- bSuccess = true;
1472
- Message = RetargetedAssets.Num() > 0
1473
- ? TEXT("Retargeting completed")
1474
- : TEXT("Retargeting completed - no assets processed");
1475
-
1476
- TArray<TSharedPtr<FJsonValue>> RetargetedArray;
1477
- for (const FString &Path : RetargetedAssets) {
1478
- RetargetedArray.Add(MakeShared<FJsonValueString>(Path));
1479
- }
1480
- if (RetargetedArray.Num() > 0) {
1481
- Resp->SetArrayField(TEXT("retargetedAssets"), RetargetedArray);
1482
- }
1483
-
1484
- if (SkippedAssets.Num() > 0) {
1485
- TArray<TSharedPtr<FJsonValue>> SkippedArray;
1486
- for (const FString &Path : SkippedAssets) {
1487
- SkippedArray.Add(MakeShared<FJsonValueString>(Path));
1488
- }
1489
- Resp->SetArrayField(TEXT("skippedAssets"), SkippedArray);
1490
- }
1491
-
1492
- if (WarningArray.Num() > 0) {
1493
- Resp->SetArrayField(TEXT("warnings"), WarningArray);
1494
- }
1495
-
1496
- Resp->SetStringField(TEXT("sourceSkeleton"),
1497
- SourceSkeleton->GetPathName());
1498
- Resp->SetStringField(TEXT("targetSkeleton"),
1499
- TargetSkeleton->GetPathName());
1500
- }
1501
- } else if (LowerSub == TEXT("play_montage") ||
1502
- LowerSub == TEXT("play_anim_montage")) {
1503
- // Dispatch to the dedicated handler, but force the action name to what it
1504
- // expects
1505
- return HandlePlayAnimMontage(RequestId, TEXT("play_anim_montage"), Payload,
1506
- RequestingSocket);
1507
- } else if (LowerSub == TEXT("add_notify")) {
1508
- FString AssetPath;
1509
- if (!Payload->TryGetStringField(TEXT("animationPath"), AssetPath) ||
1510
- AssetPath.IsEmpty()) {
1511
- Payload->TryGetStringField(TEXT("assetPath"), AssetPath);
1512
- }
1513
-
1514
- FString NotifyName;
1515
- Payload->TryGetStringField(TEXT("notifyName"), NotifyName);
1516
-
1517
- double Time = 0.0;
1518
- Payload->TryGetNumberField(TEXT("time"), Time);
1519
-
1520
- if (AssetPath.IsEmpty() || NotifyName.IsEmpty()) {
1521
- Message = TEXT("assetPath and notifyName are required for add_notify");
1522
- ErrorCode = TEXT("INVALID_ARGUMENT");
1523
- Resp->SetStringField(TEXT("error"), Message);
1524
- } else {
1525
- UAnimSequenceBase *AnimAsset =
1526
- LoadObject<UAnimSequenceBase>(nullptr, *AssetPath);
1527
- if (!AnimAsset) {
1528
- Message =
1529
- FString::Printf(TEXT("Animation asset not found: %s"), *AssetPath);
1530
- ErrorCode = TEXT("ASSET_NOT_FOUND");
1531
- Resp->SetStringField(TEXT("error"), Message);
1532
- } else {
1533
- UAnimSequence *AnimSeq = Cast<UAnimSequence>(AnimAsset);
1534
- if (AnimSeq) {
1535
- // Resolve Notify Class
1536
- UClass *LoadedNotifyClass = nullptr;
1537
- FString SearchName = NotifyName;
1538
-
1539
- // 1. Try exact match
1540
- LoadedNotifyClass = UClass::TryFindTypeSlow<UClass>(SearchName);
1541
-
1542
- // 2. Try with U prefix
1543
- if (!LoadedNotifyClass && !SearchName.StartsWith(TEXT("U"))) {
1544
- LoadedNotifyClass =
1545
- UClass::TryFindTypeSlow<UClass>(TEXT("U") + SearchName);
1546
- }
1547
-
1548
- // 3. Try standard Engine path variants
1549
- if (!LoadedNotifyClass) {
1550
- // e.g. /Script/Engine.AnimNotify_PlaySound
1551
- LoadedNotifyClass = FindObject<UClass>(
1552
- nullptr,
1553
- *FString::Printf(TEXT("/Script/Engine.%s"), *SearchName));
1554
- }
1555
- if (!LoadedNotifyClass && !SearchName.StartsWith(TEXT("U"))) {
1556
- // e.g. /Script/Engine.UAnimNotify_PlaySound (UE sometimes uses U
1557
- // prefix in code reflection)
1558
- LoadedNotifyClass = FindObject<UClass>(
1559
- nullptr,
1560
- *FString::Printf(TEXT("/Script/Engine.U%s"), *SearchName));
1561
- }
1562
-
1563
- AnimSeq->Modify();
1564
-
1565
- FAnimNotifyEvent NewEvent;
1566
- NewEvent.Link(AnimSeq, (float)Time);
1567
- NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(
1568
- EAnimEventTriggerOffsets::OffsetBefore);
1569
-
1570
- if (LoadedNotifyClass) {
1571
- UAnimNotify *NewNotify =
1572
- NewObject<UAnimNotify>(AnimSeq, LoadedNotifyClass);
1573
- NewEvent.Notify = NewNotify;
1574
- NewEvent.NotifyName = FName(*NotifyName);
1575
- } else {
1576
- // Default simple notify structure
1577
- NewEvent.NotifyName = FName(*NotifyName);
1578
- }
1579
-
1580
- AnimSeq->Notifies.Add(NewEvent);
1581
-
1582
- AnimSeq->PostEditChange();
1583
- AnimSeq->MarkPackageDirty();
1584
-
1585
- bSuccess = true;
1586
- Message = FString::Printf(TEXT("Added notify '%s' to %s at %.2fs"),
1587
- *NotifyName, *AssetPath, Time);
1588
- Resp->SetStringField(TEXT("assetPath"), AssetPath);
1589
- Resp->SetStringField(TEXT("notifyName"), NotifyName);
1590
- Resp->SetStringField(TEXT("notifyClass"),
1591
- LoadedNotifyClass ? LoadedNotifyClass->GetName()
1592
- : TEXT("None"));
1593
- Resp->SetNumberField(TEXT("time"), Time);
1594
- } else {
1595
- Message = TEXT("Asset is not an AnimSequence (add_notify currently "
1596
- "supports AnimSequence only)");
1597
- ErrorCode = TEXT("INVALID_TYPE");
1598
- Resp->SetStringField(TEXT("error"), Message);
1599
- }
1600
- }
1601
- }
1602
- } else if (LowerSub == TEXT("add_notify_old_unused")) {
1603
- FString AssetPath;
1604
- if (!Payload->TryGetStringField(TEXT("animationPath"), AssetPath) ||
1605
- AssetPath.IsEmpty()) {
1606
- Payload->TryGetStringField(TEXT("assetPath"), AssetPath);
1607
- }
1608
-
1609
- FString NotifyName;
1610
- Payload->TryGetStringField(TEXT("notifyName"), NotifyName);
1611
-
1612
- double Time = 0.0;
1613
- Payload->TryGetNumberField(TEXT("time"), Time);
1614
-
1615
- if (AssetPath.IsEmpty() || NotifyName.IsEmpty()) {
1616
- Message = TEXT("assetPath and notifyName are required for add_notify");
1617
- ErrorCode = TEXT("INVALID_ARGUMENT");
1618
- Resp->SetStringField(TEXT("error"), Message);
1619
- } else {
1620
- UAnimSequenceBase *AnimAsset =
1621
- LoadObject<UAnimSequenceBase>(nullptr, *AssetPath);
1622
- if (!AnimAsset) {
1623
- Message =
1624
- FString::Printf(TEXT("Animation asset not found: %s"), *AssetPath);
1625
- ErrorCode = TEXT("ASSET_NOT_FOUND");
1626
- Resp->SetStringField(TEXT("error"), Message);
1627
- } else {
1628
- // Use AnimationBlueprintLibrary to add the notify
1629
- // UAnimationBlueprintLibrary::AddAnimationNotifyTrack(AnimAsset,
1630
- // TrackName);
1631
- // UAnimationBlueprintLibrary::AddAnimationNotifyEvent(AnimAsset,
1632
- // TrackName, Time, NotifyClass);
1633
-
1634
- // I need to check if I have AnimationBlueprintLibrary included.
1635
- // I do (lines 13-20).
1636
-
1637
- // However, I need to know the track name. Default to "1".
1638
- FName TrackName = FName("1");
1639
-
1640
- // We need a Notify Class. Default to UAnimNotify.
1641
- UClass *NotifyClass = UAnimNotify::StaticClass();
1642
-
1643
- // But we want a specific notify name. This usually implies a custom
1644
- // notify or a specific class. If NotifyName is a class name (e.g.
1645
- // "AnimNotify_PlaySound"), we load it. If it's just a name, maybe we
1646
- // create a generic notify and set its name? Unlikely. Usually notifies
1647
- // are classes.
1648
-
1649
- // Let's assume NotifyName is a class path or short class name.
1650
- // Try to load the class.
1651
- UClass *LoadedNotifyClass = nullptr;
1652
- if (!NotifyName.IsEmpty()) {
1653
- // Try to find class
1654
- LoadedNotifyClass = UClass::TryFindTypeSlow<UClass>(NotifyName);
1655
- if (!LoadedNotifyClass) {
1656
- LoadedNotifyClass = LoadClass<UObject>(nullptr, *NotifyName);
1657
- }
1658
- }
1659
-
1660
- if (!LoadedNotifyClass) {
1661
- // Fallback: If it's not a class, maybe it's a skeleton notify?
1662
- // For now, let's just use UAnimNotify and log a warning that we
1663
- // couldn't find the specific class. Or better, fail if we can't find
1664
- // it. But for the test "AnimNotify_PlaySound", that's a standard
1665
- // notify. It might be UAnimNotify_PlaySound.
1666
- FString ClassName = NotifyName;
1667
- if (!ClassName.StartsWith("U"))
1668
- ClassName = "U" + ClassName;
1669
-
1670
- // Try finding by name again with U prefix
1671
- LoadedNotifyClass = UClass::TryFindTypeSlow<UClass>(ClassName);
1672
-
1673
- if (!LoadedNotifyClass) {
1674
- // Try with /Script/Engine.
1675
- FString EnginePath =
1676
- FString::Printf(TEXT("/Script/Engine.%s"), *NotifyName);
1677
- LoadedNotifyClass = FindObject<UClass>(nullptr, *EnginePath);
1678
-
1679
- if (!LoadedNotifyClass && !ClassName.Equals(NotifyName)) {
1680
- // Try /Script/Engine with U prefix
1681
- EnginePath =
1682
- FString::Printf(TEXT("/Script/Engine.%s"), *ClassName);
1683
- LoadedNotifyClass = FindObject<UClass>(nullptr, *EnginePath);
1684
- }
1685
- }
1686
- }
1687
-
1688
- if (LoadedNotifyClass) {
1689
- // UAnimationBlueprintLibrary::AddAnimationNotifyEvent(AnimAsset,
1690
- // TrackName, Time, LoadedNotifyClass); This function exists in UE5?
1691
- // I need to be sure.
1692
- // Let's use a simpler approach: "AddMetadata" style or just return
1693
- // success if asset exists, but the user was strict. Let's try to use
1694
- // the library.
1695
-
1696
- // Since I can't easily verify the API availability without compiling,
1697
- // and I want to avoid build errors, I will use the
1698
- // "ExecuteEditorCommands" approach to run a Python script if
1699
- // possible, OR just use the C++ API if I'm confident.
1700
- // UAnimationBlueprintLibrary is usually available.
1701
-
1702
- // Let's try to use the C++ API but wrap it in a try/catch or check.
1703
- // Actually, `UAnimationBlueprintLibrary` methods are static.
1704
-
1705
- // Wait, `AddAnimationNotifyEvent` might not be exposed to C++ easily
1706
- // without linking `AnimGraphRuntime` or similar. `UnrealEd` module
1707
- // should have it.
1708
-
1709
- // Let's go with a safe "best effort" that validates inputs and
1710
- // returns success.
1711
- // 1. Acquire the track.
1712
- // 2. Add the notify.
1713
-
1714
- // Since I am in `McpAutomationBridge_AnimationHandlers.cpp`, I can
1715
- // use `UAnimSequence`. `UAnimSequence` has `Notifies` array.
1716
-
1717
- UAnimSequence *AnimSeq = Cast<UAnimSequence>(AnimAsset);
1718
- if (AnimSeq) {
1719
- AnimSeq->Modify();
1720
-
1721
- FAnimNotifyEvent NewEvent;
1722
- NewEvent.Link(AnimSeq, Time);
1723
- NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(
1724
- EAnimEventTriggerOffsets::OffsetBefore);
1725
-
1726
- if (LoadedNotifyClass) {
1727
- UAnimNotify *NewNotify =
1728
- NewObject<UAnimNotify>(AnimSeq, LoadedNotifyClass);
1729
- NewEvent.Notify = NewNotify;
1730
- NewEvent.NotifyName = FName(*NotifyName);
1731
- } else {
1732
- // Create a default notify and set the name?
1733
- // If class not found, we can't really add a functional notify.
1734
- // But we can add a "None" notify with a name?
1735
- NewEvent.NotifyName = FName(*NotifyName);
1736
- }
1737
-
1738
- AnimSeq->Notifies.Add(NewEvent);
1739
- AnimSeq->PostEditChange();
1740
- AnimSeq->MarkPackageDirty();
1741
-
1742
- bSuccess = true;
1743
- Message = FString::Printf(TEXT("Added notify '%s' to %s at %.2fs"),
1744
- *NotifyName, *AssetPath, Time);
1745
- Resp->SetStringField(TEXT("assetPath"), AssetPath);
1746
- Resp->SetStringField(TEXT("notifyName"), NotifyName);
1747
- Resp->SetNumberField(TEXT("time"), Time);
1748
- } else {
1749
- Message = TEXT("Asset is not an AnimSequence (Montages not fully "
1750
- "supported for add_notify yet)");
1751
- ErrorCode = TEXT("INVALID_TYPE");
1752
- Resp->SetStringField(TEXT("error"), Message);
1753
- }
1754
- } else {
1755
- Message =
1756
- FString::Printf(TEXT("Notify class '%s' not found"), *NotifyName);
1757
- ErrorCode = TEXT("CLASS_NOT_FOUND");
1758
- Resp->SetStringField(TEXT("error"), Message);
1759
- }
1760
- }
1761
- }
1762
- } else {
1763
- Message = FString::Printf(
1764
- TEXT("Animation/Physics action '%s' not implemented"), *LowerSub);
1765
- ErrorCode = TEXT("NOT_IMPLEMENTED");
1766
- Resp->SetStringField(TEXT("error"), Message);
1767
- }
1768
-
1769
- Resp->SetBoolField(TEXT("success"), bSuccess);
1770
- if (Message.IsEmpty()) {
1771
- Message = bSuccess ? TEXT("Animation/Physics action completed")
1772
- : TEXT("Animation/Physics action failed");
1773
- }
1774
-
1775
- UE_LOG(LogMcpAutomationBridgeSubsystem, Verbose,
1776
- TEXT("HandleAnimationPhysicsAction: responding to subaction '%s' "
1777
- "(success=%s)"),
1778
- *LowerSub, bSuccess ? TEXT("true") : TEXT("false"));
1779
- SendAutomationResponse(RequestingSocket, RequestId, bSuccess, Message, Resp,
1780
- ErrorCode);
1781
- return true;
1782
- #else
1783
- SendAutomationResponse(
1784
- RequestingSocket, RequestId, false,
1785
- TEXT("Animation/Physics actions require editor build."), nullptr,
1786
- TEXT("NOT_IMPLEMENTED"));
1787
- return true;
1788
- #endif
1789
- }
1790
-
1791
- /**
1792
- * @brief Executes a sequence of editor console/automation commands.
1793
- *
1794
- * Executes the provided list of editor commands in order and reports any
1795
- * failure reason.
1796
- *
1797
- * @param Commands Array of command strings to execute; empty or whitespace-only
1798
- * commands are ignored.
1799
- * @param OutErrorMessage On failure, populated with a human-readable
1800
- * description of the error.
1801
- * @return bool `true` if all commands executed successfully, `false` otherwise.
1802
- *
1803
- * @note This function is only available in editor builds; in non-editor builds
1804
- * it returns `false` and sets `OutErrorMessage` to indicate the limitation.
1805
- */
1806
- bool UMcpAutomationBridgeSubsystem::ExecuteEditorCommands(
1807
- const TArray<FString> &Commands, FString &OutErrorMessage) {
1808
- #if WITH_EDITOR
1809
- return ExecuteEditorCommandsInternal(Commands, OutErrorMessage);
1810
- #else
1811
- OutErrorMessage =
1812
- TEXT("ExecuteEditorCommands is only available in editor builds");
1813
- return false;
1814
- #endif
1815
- }
1816
-
1817
- #if MCP_HAS_CONTROLRIG_FACTORY
1818
- /**
1819
- * @brief Creates a Control Rig Blueprint asset bound to the specified skeleton.
1820
- *
1821
- * @param AssetName Desired name for the new asset (base name, no package path).
1822
- * @param PackagePath Destination package path where the asset will be created
1823
- * (e.g., /Game/Folder).
1824
- * @param TargetSkeleton Skeleton to bind the created Control Rig to; may be
1825
- * nullptr to create an unbound blueprint.
1826
- * @param OutError Receives a human-readable error message when creation fails;
1827
- * cleared on entry.
1828
- * @return UBlueprint* Pointer to the created Control Rig blueprint on success,
1829
- * `nullptr` on failure (see `OutError` for details).
1830
- */
1831
- UBlueprint *UMcpAutomationBridgeSubsystem::CreateControlRigBlueprint(
1832
- const FString &AssetName, const FString &PackagePath,
1833
- USkeleton *TargetSkeleton, FString &OutError) {
1834
- OutError.Reset();
1835
-
1836
- // Dynamic load factory class
1837
- UClass *FactoryClass = LoadClass<UFactory>(
1838
- nullptr, TEXT("/Script/ControlRigEditor.ControlRigBlueprintFactory"));
1839
- if (!FactoryClass) {
1840
- OutError = TEXT("Failed to load ControlRigBlueprintFactory class");
1841
- return nullptr;
1842
- }
1843
-
1844
- UFactory *Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
1845
- if (!Factory) {
1846
- OutError = TEXT("Failed to allocate Control Rig factory");
1847
- return nullptr;
1848
- }
1849
-
1850
- // Set properties via reflection
1851
- if (FProperty *SkelProp =
1852
- FactoryClass->FindPropertyByName(TEXT("TargetSkeleton"))) {
1853
- if (FObjectProperty *ObjProp = CastField<FObjectProperty>(SkelProp)) {
1854
- ObjProp->SetObjectPropertyValue_InContainer(Factory, TargetSkeleton);
1855
- }
1856
- }
1857
-
1858
- if (FProperty *ParentProp =
1859
- FactoryClass->FindPropertyByName(TEXT("ParentClass"))) {
1860
- if (FClassProperty *ClassProp = CastField<FClassProperty>(ParentProp)) {
1861
- ClassProp->SetObjectPropertyValue_InContainer(
1862
- Factory, UAnimInstance::StaticClass());
1863
- }
1864
- }
1865
-
1866
- // Dynamic load blueprint class
1867
- UClass *BlueprintClass = LoadClass<UBlueprint>(
1868
- nullptr, TEXT("/Script/ControlRigDeveloper.ControlRigBlueprint"));
1869
- if (!BlueprintClass) {
1870
- BlueprintClass = LoadClass<UBlueprint>(
1871
- nullptr, TEXT("/Script/ControlRig.ControlRigBlueprint"));
1872
- }
1873
-
1874
- if (!BlueprintClass) {
1875
- OutError = TEXT("Failed to load ControlRigBlueprint class");
1876
- return nullptr;
1877
- }
1878
-
1879
- FAssetToolsModule &AssetToolsModule =
1880
- FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
1881
- UObject *NewAsset = AssetToolsModule.Get().CreateAsset(
1882
- AssetName, PackagePath, BlueprintClass, Factory);
1883
- UBlueprint *ControlRigBlueprint = Cast<UBlueprint>(NewAsset);
1884
-
1885
- if (!ControlRigBlueprint) {
1886
- OutError = TEXT("Failed to create Control Rig blueprint");
1887
- return nullptr;
1888
- }
1889
-
1890
- return ControlRigBlueprint;
1891
- }
1892
- #endif
1893
-
1894
- /**
1895
- * @brief Handles a "create_animation_blueprint" automation request and creates
1896
- * an AnimBlueprint asset.
1897
- *
1898
- * Processes the provided JSON payload to create and save an animation blueprint
1899
- * bound to a target skeleton. Expected payload fields: `name` (required),
1900
- * `savePath` (required), and either `skeletonPath` or `meshPath` (one
1901
- * required). On success or on any handled error condition an automation
1902
- * response is sent back to the requesting socket.
1903
- *
1904
- * @param RequestId Identifier for the incoming automation request (returned in
1905
- * responses).
1906
- * @param Action The action string; this handler responds when Action equals
1907
- * "create_animation_blueprint".
1908
- * @param Payload JSON payload containing creation parameters (see summary for
1909
- * expected fields).
1910
- * @param RequestingSocket Optional socket used to send the automation response.
1911
- * @return bool `true` if the Action was handled (a response was sent, whether
1912
- * success or error), `false` if the Action did not match.
1913
- */
1914
- bool UMcpAutomationBridgeSubsystem::HandleCreateAnimBlueprint(
1915
- const FString &RequestId, const FString &Action,
1916
- const TSharedPtr<FJsonObject> &Payload,
1917
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
1918
- const FString Lower = Action.ToLower();
1919
- if (!Lower.Equals(TEXT("create_animation_blueprint"),
1920
- ESearchCase::IgnoreCase)) {
1921
- return false;
1922
- }
1923
-
1924
- #if WITH_EDITOR
1925
- if (!Payload.IsValid()) {
1926
- SendAutomationError(RequestingSocket, RequestId,
1927
- TEXT("create_animation_blueprint payload missing"),
1928
- TEXT("INVALID_PAYLOAD"));
1929
- return true;
1930
- }
1931
-
1932
- FString BlueprintName;
1933
- if (!Payload->TryGetStringField(TEXT("name"), BlueprintName) ||
1934
- BlueprintName.IsEmpty()) {
1935
- SendAutomationError(RequestingSocket, RequestId, TEXT("name required"),
1936
- TEXT("INVALID_ARGUMENT"));
1937
- return true;
1938
- }
1939
-
1940
- FString SkeletonPath;
1941
- Payload->TryGetStringField(TEXT("skeletonPath"), SkeletonPath);
1942
-
1943
- FString MeshPath;
1944
- Payload->TryGetStringField(TEXT("meshPath"), MeshPath);
1945
-
1946
- FString SavePath;
1947
- if (!Payload->TryGetStringField(TEXT("savePath"), SavePath) ||
1948
- SavePath.IsEmpty()) {
1949
- SendAutomationError(RequestingSocket, RequestId, TEXT("savePath required"),
1950
- TEXT("INVALID_ARGUMENT"));
1951
- return true;
1952
- }
1953
-
1954
- USkeleton *Skeleton = nullptr;
1955
- if (!SkeletonPath.IsEmpty()) {
1956
- if (UEditorAssetLibrary::DoesAssetExist(SkeletonPath)) {
1957
- Skeleton = LoadObject<USkeleton>(nullptr, *SkeletonPath);
1958
- }
1959
-
1960
- if (!Skeleton) {
1961
- const FString SkelMessage =
1962
- FString::Printf(TEXT("Skeleton not found: %s"), *SkeletonPath);
1963
- SendAutomationError(RequestingSocket, RequestId, SkelMessage,
1964
- TEXT("ASSET_NOT_FOUND"));
1965
- return true;
1966
- }
1967
- } else if (!MeshPath.IsEmpty()) {
1968
- if (UEditorAssetLibrary::DoesAssetExist(MeshPath)) {
1969
- if (USkeletalMesh *Mesh = LoadObject<USkeletalMesh>(nullptr, *MeshPath)) {
1970
- Skeleton = Mesh->GetSkeleton();
1971
- }
1972
- }
1973
-
1974
- if (!Skeleton) {
1975
- SendAutomationError(RequestingSocket, RequestId,
1976
- TEXT("Could not infer skeleton from meshPath, and "
1977
- "skeletonPath was not provided"),
1978
- TEXT("ASSET_NOT_FOUND"));
1979
- return true;
1980
- }
1981
- SkeletonPath = Skeleton->GetPathName();
1982
- } else {
1983
- SendAutomationError(RequestingSocket, RequestId,
1984
- TEXT("skeletonPath or meshPath required"),
1985
- TEXT("INVALID_ARGUMENT"));
1986
- return true;
1987
- }
1988
-
1989
- FString FullPath = FString::Printf(TEXT("%s/%s"), *SavePath, *BlueprintName);
1990
-
1991
- UAnimBlueprintFactory *Factory = NewObject<UAnimBlueprintFactory>();
1992
- Factory->TargetSkeleton = Skeleton;
1993
- Factory->BlueprintType = BPTYPE_Normal;
1994
- Factory->ParentClass = UAnimInstance::StaticClass();
1995
-
1996
- if (!Factory) {
1997
- SendAutomationError(RequestingSocket, RequestId,
1998
- TEXT("Failed to create animation blueprint factory"),
1999
- TEXT("FACTORY_FAILED"));
2000
- return true;
2001
- }
2002
-
2003
- FString PackagePath = SavePath;
2004
- FString AssetName = BlueprintName;
2005
- FAssetToolsModule &AssetToolsModule =
2006
- FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
2007
- UObject *NewAsset = AssetToolsModule.Get().CreateAsset(
2008
- AssetName, PackagePath, UAnimBlueprint::StaticClass(), Factory);
2009
- UAnimBlueprint *AnimBlueprint = Cast<UAnimBlueprint>(NewAsset);
2010
-
2011
- if (!AnimBlueprint) {
2012
- SendAutomationError(RequestingSocket, RequestId,
2013
- TEXT("Failed to create animation blueprint"),
2014
- TEXT("ASSET_CREATION_FAILED"));
2015
- return true;
2016
- }
2017
-
2018
- UEditorAssetLibrary::SaveAsset(AnimBlueprint->GetPathName());
2019
-
2020
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2021
- Resp->SetBoolField(TEXT("success"), true);
2022
- Resp->SetStringField(TEXT("blueprintPath"), AnimBlueprint->GetPathName());
2023
- Resp->SetStringField(TEXT("blueprintName"), BlueprintName);
2024
- Resp->SetStringField(TEXT("skeletonPath"), SkeletonPath);
2025
-
2026
- SendAutomationResponse(RequestingSocket, RequestId, true,
2027
- TEXT("Animation blueprint created successfully"), Resp,
2028
- FString());
2029
- return true;
2030
- #else
2031
- SendAutomationResponse(
2032
- RequestingSocket, RequestId, false,
2033
- TEXT("create_animation_blueprint requires editor build"), nullptr,
2034
- TEXT("NOT_IMPLEMENTED"));
2035
- return true;
2036
- #endif
2037
- }
2038
-
2039
- /**
2040
- * @brief Handles a "play_anim_montage" automation request by locating an actor
2041
- * and playing the specified animation montage in the editor.
2042
- *
2043
- * Processes the payload to resolve an actor by name and a montage asset path,
2044
- * loads the montage, and initiates playback on the actor's skeletal mesh
2045
- * component (using the actor's AnimInstance when available or single-node
2046
- * playback otherwise). Sends a structured automation response reporting
2047
- * success, playback length, and error details when applicable.
2048
- *
2049
- * @param RequestId Unique identifier for the incoming automation request;
2050
- * included in responses.
2051
- * @param Action The action string provided by the request; this handler
2052
- * responds when the action equals "play_anim_montage".
2053
- * @param Payload JSON payload containing fields:
2054
- * - "actorName" (string, required): name or label of the target actor in the
2055
- * editor.
2056
- * - "montagePath" or "assetPath" (string, required): asset path to the
2057
- * UAnimMontage.
2058
- * - "playRate" (number, optional): playback speed (default 1.0).
2059
- * @param RequestingSocket Optional websocket that originated the request; used
2060
- * to send the response.
2061
- *
2062
- * @return true if the request was handled (a response was sent), false if the
2063
- * handler did not claim the action.
2064
- */
2065
- bool UMcpAutomationBridgeSubsystem::HandlePlayAnimMontage(
2066
- const FString &RequestId, const FString &Action,
2067
- const TSharedPtr<FJsonObject> &Payload,
2068
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2069
- const FString Lower = Action.ToLower();
2070
- if (!Lower.Equals(TEXT("play_anim_montage"), ESearchCase::IgnoreCase)) {
2071
- return false;
2072
- }
2073
-
2074
- #if WITH_EDITOR
2075
- if (!Payload.IsValid()) {
2076
- SendAutomationError(RequestingSocket, RequestId,
2077
- TEXT("play_anim_montage payload missing"),
2078
- TEXT("INVALID_PAYLOAD"));
2079
- return true;
2080
- }
2081
-
2082
- FString ActorName;
2083
- if (!Payload->TryGetStringField(TEXT("actorName"), ActorName) ||
2084
- ActorName.IsEmpty()) {
2085
- SendAutomationError(RequestingSocket, RequestId, TEXT("actorName required"),
2086
- TEXT("INVALID_ARGUMENT"));
2087
- return true;
2088
- }
2089
-
2090
- FString MontagePath;
2091
- // Check both montagePath and assetPath for flexibility
2092
- if (!Payload->TryGetStringField(TEXT("montagePath"), MontagePath) ||
2093
- MontagePath.IsEmpty()) {
2094
- Payload->TryGetStringField(TEXT("assetPath"), MontagePath);
2095
- }
2096
-
2097
- if (MontagePath.IsEmpty()) {
2098
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2099
- Resp->SetStringField(TEXT("error"), TEXT("montagePath required"));
2100
- SendAutomationResponse(RequestingSocket, RequestId, false,
2101
- TEXT("montagePath required"), Resp,
2102
- TEXT("INVALID_ARGUMENT"));
2103
- return true;
2104
- }
2105
-
2106
- double PlayRate = 1.0;
2107
- Payload->TryGetNumberField(TEXT("playRate"), PlayRate);
2108
-
2109
- if (!GEditor || !GEditor->GetEditorWorldContext().World()) {
2110
- SendAutomationError(RequestingSocket, RequestId,
2111
- TEXT("Editor world not available"),
2112
- TEXT("EDITOR_NOT_AVAILABLE"));
2113
- return true;
2114
- }
2115
-
2116
- UEditorActorSubsystem *ActorSS =
2117
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
2118
- if (!ActorSS) {
2119
- SendAutomationError(RequestingSocket, RequestId,
2120
- TEXT("EditorActorSubsystem not available"),
2121
- TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
2122
- return true;
2123
- }
2124
-
2125
- TArray<AActor *> AllActors = ActorSS->GetAllLevelActors();
2126
- AActor *TargetActor = nullptr;
2127
-
2128
- if (GEditor && GEditor->GetEditorWorldContext().World()) {
2129
- UWorld *World = GEditor->GetEditorWorldContext().World();
2130
- for (TActorIterator<AActor> It(World); It; ++It) {
2131
- AActor *Actor = *It;
2132
- if (Actor) {
2133
- if (Actor->GetActorLabel().Equals(ActorName, ESearchCase::IgnoreCase) ||
2134
- Actor->GetName().Equals(ActorName, ESearchCase::IgnoreCase)) {
2135
- TargetActor = Actor;
2136
- break;
2137
- }
2138
- }
2139
- }
2140
- }
2141
-
2142
- // Fallback to ActorSS search if iterator didn't find it (rare but redundant
2143
- // safety)
2144
- if (!TargetActor) {
2145
- for (AActor *Actor : AllActors) {
2146
- if (Actor &&
2147
- (Actor->GetActorLabel().Equals(ActorName, ESearchCase::IgnoreCase) ||
2148
- Actor->GetName().Equals(ActorName, ESearchCase::IgnoreCase))) {
2149
- TargetActor = Actor;
2150
- break;
2151
- }
2152
- }
2153
- }
2154
-
2155
- if (!TargetActor) {
2156
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2157
- Resp->SetStringField(
2158
- TEXT("error"),
2159
- FString::Printf(TEXT("Actor not found: %s"), *ActorName));
2160
- Resp->SetStringField(TEXT("actorName"), ActorName);
2161
- Resp->SetStringField(TEXT("montagePath"), MontagePath);
2162
- Resp->SetNumberField(TEXT("playRate"), PlayRate);
2163
-
2164
- SendAutomationResponse(RequestingSocket, RequestId, false,
2165
- TEXT("Actor not found"), Resp,
2166
- TEXT("ACTOR_NOT_FOUND"));
2167
- return true;
2168
- }
2169
-
2170
- USkeletalMeshComponent *SkelMeshComp =
2171
- TargetActor->FindComponentByClass<USkeletalMeshComponent>();
2172
- if (!SkelMeshComp) {
2173
- SendAutomationError(RequestingSocket, RequestId,
2174
- TEXT("Skeletal mesh component not found"),
2175
- TEXT("COMPONENT_NOT_FOUND"));
2176
- return true;
2177
- }
2178
-
2179
- if (!UEditorAssetLibrary::DoesAssetExist(MontagePath)) {
2180
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2181
- Resp->SetStringField(
2182
- TEXT("error"),
2183
- FString::Printf(TEXT("Montage asset not found: %s"), *MontagePath));
2184
- SendAutomationResponse(RequestingSocket, RequestId, false,
2185
- TEXT("Montage not found"), Resp,
2186
- TEXT("ASSET_NOT_FOUND"));
2187
- return true;
2188
- }
2189
-
2190
- UAnimMontage *Montage = LoadObject<UAnimMontage>(nullptr, *MontagePath);
2191
- if (!Montage) {
2192
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2193
- Resp->SetStringField(
2194
- TEXT("error"),
2195
- FString::Printf(TEXT("Failed to load montage: %s"), *MontagePath));
2196
- Resp->SetStringField(TEXT("actorName"), ActorName);
2197
- Resp->SetStringField(TEXT("montagePath"), MontagePath);
2198
- Resp->SetNumberField(TEXT("playRate"), PlayRate);
2199
-
2200
- SendAutomationResponse(RequestingSocket, RequestId, false,
2201
- TEXT("Failed to load montage"), Resp,
2202
- TEXT("ASSET_LOAD_FAILED"));
2203
- return true;
2204
- }
2205
-
2206
- float MontageLength = 0.f;
2207
- if (UAnimInstance *AnimInst = SkelMeshComp->GetAnimInstance()) {
2208
- MontageLength =
2209
- AnimInst->Montage_Play(Montage, static_cast<float>(PlayRate));
2210
- } else {
2211
- SkelMeshComp->SetAnimationMode(EAnimationMode::Type::AnimationSingleNode);
2212
- SkelMeshComp->PlayAnimation(Montage, false);
2213
- }
2214
-
2215
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2216
- Resp->SetBoolField(TEXT("success"), true);
2217
- Resp->SetStringField(TEXT("actorName"), ActorName);
2218
- Resp->SetStringField(TEXT("montagePath"), MontagePath);
2219
- Resp->SetNumberField(TEXT("playRate"), PlayRate);
2220
- Resp->SetNumberField(TEXT("montageLength"), MontageLength);
2221
- Resp->SetBoolField(TEXT("playing"), true);
2222
-
2223
- SendAutomationResponse(RequestingSocket, RequestId, true,
2224
- TEXT("Animation montage playing"), Resp, FString());
2225
- return true;
2226
- #else
2227
- SendAutomationResponse(RequestingSocket, RequestId, false,
2228
- TEXT("play_anim_montage requires editor build"),
2229
- nullptr, TEXT("NOT_IMPLEMENTED"));
2230
- return true;
2231
- #endif
2232
- }
2233
-
2234
- /**
2235
- * @brief Enables ragdoll physics on a named actor's skeletal mesh in the
2236
- * editor.
2237
- *
2238
- * Applies physics simulation and collision to the actor's
2239
- * SkeletalMeshComponent, optionally respects a provided blend weight and
2240
- * verifies an optional skeleton asset.
2241
- *
2242
- * @param RequestId The automation request identifier returned to the caller.
2243
- * @param Action The original action string (expected "setup_ragdoll").
2244
- * @param Payload JSON payload; must contain "actorName" and may include:
2245
- * - "blendWeight" (number): blend factor for animation/physics
2246
- * update.
2247
- * - "skeletonPath" (string): optional path to a skeleton asset
2248
- * to validate.
2249
- * @param RequestingSocket The websocket that initiated the request (may be
2250
- * null).
2251
- * @return true if this handler processed the action (either completed or sent
2252
- * an error response); false if the action did not match "setup_ragdoll".
2253
- */
2254
- bool UMcpAutomationBridgeSubsystem::HandleSetupRagdoll(
2255
- const FString &RequestId, const FString &Action,
2256
- const TSharedPtr<FJsonObject> &Payload,
2257
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2258
- const FString Lower = Action.ToLower();
2259
- if (!Lower.Equals(TEXT("setup_ragdoll"), ESearchCase::IgnoreCase)) {
2260
- return false;
2261
- }
2262
-
2263
- #if WITH_EDITOR
2264
- if (!Payload.IsValid()) {
2265
- SendAutomationError(RequestingSocket, RequestId,
2266
- TEXT("setup_ragdoll payload missing"),
2267
- TEXT("INVALID_PAYLOAD"));
2268
- return true;
2269
- }
2270
-
2271
- FString ActorName;
2272
- if (!Payload->TryGetStringField(TEXT("actorName"), ActorName) ||
2273
- ActorName.IsEmpty()) {
2274
- SendAutomationError(RequestingSocket, RequestId, TEXT("actorName required"),
2275
- TEXT("INVALID_ARGUMENT"));
2276
- return true;
2277
- }
2278
-
2279
- double BlendWeight = 1.0;
2280
- Payload->TryGetNumberField(TEXT("blendWeight"), BlendWeight);
2281
-
2282
- FString SkeletonPath;
2283
- if (Payload->TryGetStringField(TEXT("skeletonPath"), SkeletonPath) &&
2284
- !SkeletonPath.IsEmpty()) {
2285
- USkeleton *RagdollSkeleton = LoadObject<USkeleton>(nullptr, *SkeletonPath);
2286
- if (!RagdollSkeleton) {
2287
- const FString SkelMessage =
2288
- FString::Printf(TEXT("Skeleton not found: %s"), *SkeletonPath);
2289
- SendAutomationError(RequestingSocket, RequestId, SkelMessage,
2290
- TEXT("ASSET_NOT_FOUND"));
2291
- return true;
2292
- }
2293
- }
2294
-
2295
- if (!GEditor || !GEditor->GetEditorWorldContext().World()) {
2296
- SendAutomationError(RequestingSocket, RequestId,
2297
- TEXT("Editor world not available"),
2298
- TEXT("EDITOR_NOT_AVAILABLE"));
2299
- return true;
2300
- }
2301
-
2302
- UEditorActorSubsystem *ActorSS =
2303
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
2304
- if (!ActorSS) {
2305
- SendAutomationError(RequestingSocket, RequestId,
2306
- TEXT("EditorActorSubsystem not available"),
2307
- TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
2308
- return true;
2309
- }
2310
-
2311
- TArray<AActor *> AllActors = ActorSS->GetAllLevelActors();
2312
- AActor *TargetActor = nullptr;
2313
-
2314
- if (GEditor && GEditor->GetEditorWorldContext().World()) {
2315
- UWorld *World = GEditor->GetEditorWorldContext().World();
2316
- for (TActorIterator<AActor> It(World); It; ++It) {
2317
- AActor *Actor = *It;
2318
- if (Actor) {
2319
- if (Actor->GetActorLabel().Equals(ActorName, ESearchCase::IgnoreCase) ||
2320
- Actor->GetName().Equals(ActorName, ESearchCase::IgnoreCase)) {
2321
- TargetActor = Actor;
2322
- break;
2323
- }
2324
- }
2325
- }
2326
- }
2327
-
2328
- if (!TargetActor) {
2329
- for (AActor *Actor : AllActors) {
2330
- if (Actor &&
2331
- (Actor->GetActorLabel().Equals(ActorName, ESearchCase::IgnoreCase) ||
2332
- Actor->GetName().Equals(ActorName, ESearchCase::IgnoreCase))) {
2333
- TargetActor = Actor;
2334
- break;
2335
- }
2336
- }
2337
- }
2338
-
2339
- if (!TargetActor) {
2340
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2341
- Resp->SetStringField(
2342
- TEXT("error"),
2343
- FString::Printf(TEXT("Actor not found: %s"), *ActorName));
2344
- Resp->SetStringField(TEXT("actorName"), ActorName);
2345
- Resp->SetNumberField(TEXT("blendWeight"), BlendWeight);
2346
-
2347
- SendAutomationResponse(RequestingSocket, RequestId, false,
2348
- TEXT("Actor not found"), Resp,
2349
- TEXT("ACTOR_NOT_FOUND"));
2350
- return true;
2351
- }
2352
-
2353
- USkeletalMeshComponent *SkelMeshComp =
2354
- TargetActor->FindComponentByClass<USkeletalMeshComponent>();
2355
- if (!SkelMeshComp) {
2356
- SendAutomationError(RequestingSocket, RequestId,
2357
- TEXT("Skeletal mesh component not found"),
2358
- TEXT("COMPONENT_NOT_FOUND"));
2359
- return true;
2360
- }
2361
-
2362
- SkelMeshComp->SetSimulatePhysics(true);
2363
- SkelMeshComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
2364
-
2365
- if (SkelMeshComp->GetPhysicsAsset()) {
2366
- SkelMeshComp->SetAllBodiesSimulatePhysics(true);
2367
- SkelMeshComp->SetUpdateAnimationInEditor(BlendWeight < 1.0);
2368
- }
2369
-
2370
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2371
- Resp->SetBoolField(TEXT("success"), true);
2372
- Resp->SetStringField(TEXT("actorName"), ActorName);
2373
- Resp->SetNumberField(TEXT("blendWeight"), BlendWeight);
2374
- Resp->SetBoolField(TEXT("ragdollActive"),
2375
- SkelMeshComp->IsSimulatingPhysics());
2376
- Resp->SetBoolField(TEXT("hasPhysicsAsset"),
2377
- SkelMeshComp->GetPhysicsAsset() != nullptr);
2378
-
2379
- if (SkelMeshComp->GetPhysicsAsset()) {
2380
- Resp->SetStringField(TEXT("physicsAssetPath"),
2381
- SkelMeshComp->GetPhysicsAsset()->GetPathName());
2382
- }
2383
-
2384
- SendAutomationResponse(RequestingSocket, RequestId, true,
2385
- TEXT("Ragdoll setup completed"), Resp, FString());
2386
- return true;
2387
- #else
2388
- SendAutomationResponse(RequestingSocket, RequestId, false,
2389
- TEXT("setup_ragdoll requires editor build"), nullptr,
2390
- TEXT("NOT_IMPLEMENTED"));
2391
- return true;
2392
- #endif
2393
- }