unreal-engine-mcp-server 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/dist/automation/bridge.d.ts.map +1 -0
  2. package/dist/automation/bridge.js.map +1 -0
  3. package/dist/automation/connection-manager.d.ts.map +1 -0
  4. package/dist/automation/connection-manager.js.map +1 -0
  5. package/dist/automation/handshake.d.ts.map +1 -0
  6. package/dist/automation/handshake.js.map +1 -0
  7. package/dist/automation/index.d.ts.map +1 -0
  8. package/dist/automation/index.js.map +1 -0
  9. package/dist/automation/message-handler.d.ts.map +1 -0
  10. package/dist/automation/message-handler.js.map +1 -0
  11. package/dist/automation/request-tracker.d.ts.map +1 -0
  12. package/dist/automation/request-tracker.js.map +1 -0
  13. package/dist/automation/types.d.ts.map +1 -0
  14. package/dist/automation/types.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +4 -3
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config/class-aliases.d.ts.map +1 -0
  19. package/dist/config/class-aliases.js.map +1 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js.map +1 -0
  24. package/dist/graphql/loaders.d.ts.map +1 -0
  25. package/dist/graphql/loaders.js.map +1 -0
  26. package/dist/graphql/resolvers.d.ts.map +1 -0
  27. package/dist/graphql/resolvers.js +29 -29
  28. package/dist/graphql/resolvers.js.map +1 -0
  29. package/dist/graphql/schema.d.ts.map +1 -0
  30. package/dist/graphql/schema.js.map +1 -0
  31. package/dist/graphql/server.d.ts.map +1 -0
  32. package/dist/graphql/server.js.map +1 -0
  33. package/dist/graphql/types.d.ts.map +1 -0
  34. package/dist/graphql/types.js.map +1 -0
  35. package/dist/handlers/resource-handlers.d.ts.map +1 -0
  36. package/dist/handlers/resource-handlers.js.map +1 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +64 -7
  40. package/dist/index.js.map +1 -0
  41. package/dist/resources/actors.d.ts.map +1 -0
  42. package/dist/resources/actors.js.map +1 -0
  43. package/dist/resources/assets.d.ts.map +1 -0
  44. package/dist/resources/assets.js +6 -4
  45. package/dist/resources/assets.js.map +1 -0
  46. package/dist/resources/levels.d.ts.map +1 -0
  47. package/dist/resources/levels.js.map +1 -0
  48. package/dist/server/resource-registry.d.ts.map +1 -0
  49. package/dist/server/resource-registry.js.map +1 -0
  50. package/dist/server/tool-registry.d.ts.map +1 -0
  51. package/dist/server/tool-registry.js.map +1 -0
  52. package/dist/server-setup.d.ts.map +1 -0
  53. package/dist/server-setup.js.map +1 -0
  54. package/dist/services/health-monitor.d.ts.map +1 -0
  55. package/dist/services/health-monitor.js.map +1 -0
  56. package/dist/services/metrics-server.d.ts.map +1 -0
  57. package/dist/services/metrics-server.js.map +1 -0
  58. package/dist/tools/actors.d.ts.map +1 -0
  59. package/dist/tools/actors.js +3 -1
  60. package/dist/tools/actors.js.map +1 -0
  61. package/dist/tools/animation.d.ts.map +1 -0
  62. package/dist/tools/animation.js +2 -2
  63. package/dist/tools/animation.js.map +1 -0
  64. package/dist/tools/assets.d.ts.map +1 -0
  65. package/dist/tools/assets.js.map +1 -0
  66. package/dist/tools/audio.d.ts.map +1 -0
  67. package/dist/tools/audio.js.map +1 -0
  68. package/dist/tools/base-tool.d.ts.map +1 -0
  69. package/dist/tools/base-tool.js.map +1 -0
  70. package/dist/tools/behavior-tree.d.ts.map +1 -0
  71. package/dist/tools/behavior-tree.js.map +1 -0
  72. package/dist/tools/blueprint.d.ts.map +1 -0
  73. package/dist/tools/blueprint.js +4 -2
  74. package/dist/tools/blueprint.js.map +1 -0
  75. package/dist/tools/consolidated-tool-definitions.d.ts.map +1 -0
  76. package/dist/tools/consolidated-tool-definitions.js.map +1 -0
  77. package/dist/tools/consolidated-tool-handlers.d.ts.map +1 -0
  78. package/dist/tools/consolidated-tool-handlers.js.map +1 -0
  79. package/dist/tools/debug.d.ts.map +1 -0
  80. package/dist/tools/debug.js +3 -1
  81. package/dist/tools/debug.js.map +1 -0
  82. package/dist/tools/dynamic-handler-registry.d.ts.map +1 -0
  83. package/dist/tools/dynamic-handler-registry.js +3 -1
  84. package/dist/tools/dynamic-handler-registry.js.map +1 -0
  85. package/dist/tools/editor.d.ts.map +1 -0
  86. package/dist/tools/editor.js +1 -1
  87. package/dist/tools/editor.js.map +1 -0
  88. package/dist/tools/engine.d.ts.map +1 -0
  89. package/dist/tools/engine.js.map +1 -0
  90. package/dist/tools/environment.d.ts.map +1 -0
  91. package/dist/tools/environment.js +2 -2
  92. package/dist/tools/environment.js.map +1 -0
  93. package/dist/tools/foliage.d.ts.map +1 -0
  94. package/dist/tools/foliage.js.map +1 -0
  95. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  96. package/dist/tools/handlers/actor-handlers.d.ts.map +1 -0
  97. package/dist/tools/handlers/actor-handlers.js +6 -5
  98. package/dist/tools/handlers/actor-handlers.js.map +1 -0
  99. package/dist/tools/handlers/animation-handlers.d.ts.map +1 -0
  100. package/dist/tools/handlers/animation-handlers.js.map +1 -0
  101. package/dist/tools/handlers/argument-helper.d.ts.map +1 -0
  102. package/dist/tools/handlers/argument-helper.js.map +1 -0
  103. package/dist/tools/handlers/asset-handlers.d.ts.map +1 -0
  104. package/dist/tools/handlers/asset-handlers.js +5 -1
  105. package/dist/tools/handlers/asset-handlers.js.map +1 -0
  106. package/dist/tools/handlers/audio-handlers.d.ts.map +1 -0
  107. package/dist/tools/handlers/audio-handlers.js.map +1 -0
  108. package/dist/tools/handlers/blueprint-handlers.d.ts.map +1 -0
  109. package/dist/tools/handlers/blueprint-handlers.js +2 -1
  110. package/dist/tools/handlers/blueprint-handlers.js.map +1 -0
  111. package/dist/tools/handlers/common-handlers.d.ts.map +1 -0
  112. package/dist/tools/handlers/common-handlers.js.map +1 -0
  113. package/dist/tools/handlers/editor-handlers.d.ts.map +1 -0
  114. package/dist/tools/handlers/editor-handlers.js +12 -2
  115. package/dist/tools/handlers/editor-handlers.js.map +1 -0
  116. package/dist/tools/handlers/effect-handlers.d.ts.map +1 -0
  117. package/dist/tools/handlers/effect-handlers.js.map +1 -0
  118. package/dist/tools/handlers/environment-handlers.d.ts.map +1 -0
  119. package/dist/tools/handlers/environment-handlers.js.map +1 -0
  120. package/dist/tools/handlers/graph-handlers.d.ts.map +1 -0
  121. package/dist/tools/handlers/graph-handlers.js +61 -1
  122. package/dist/tools/handlers/graph-handlers.js.map +1 -0
  123. package/dist/tools/handlers/input-handlers.d.ts.map +1 -0
  124. package/dist/tools/handlers/input-handlers.js.map +1 -0
  125. package/dist/tools/handlers/inspect-handlers.d.ts.map +1 -0
  126. package/dist/tools/handlers/inspect-handlers.js.map +1 -0
  127. package/dist/tools/handlers/level-handlers.d.ts.map +1 -0
  128. package/dist/tools/handlers/level-handlers.js.map +1 -0
  129. package/dist/tools/handlers/lighting-handlers.d.ts.map +1 -0
  130. package/dist/tools/handlers/lighting-handlers.js +23 -1
  131. package/dist/tools/handlers/lighting-handlers.js.map +1 -0
  132. package/dist/tools/handlers/performance-handlers.d.ts.map +1 -0
  133. package/dist/tools/handlers/performance-handlers.js +15 -2
  134. package/dist/tools/handlers/performance-handlers.js.map +1 -0
  135. package/dist/tools/handlers/pipeline-handlers.d.ts.map +1 -0
  136. package/dist/tools/handlers/pipeline-handlers.js.map +1 -0
  137. package/dist/tools/handlers/sequence-handlers.d.ts.map +1 -0
  138. package/dist/tools/handlers/sequence-handlers.js.map +1 -0
  139. package/dist/tools/handlers/system-handlers.d.ts.map +1 -0
  140. package/dist/tools/handlers/system-handlers.js +16 -1
  141. package/dist/tools/handlers/system-handlers.js.map +1 -0
  142. package/dist/tools/input.d.ts.map +1 -0
  143. package/dist/tools/input.js +3 -1
  144. package/dist/tools/input.js.map +1 -0
  145. package/dist/tools/introspection.d.ts.map +1 -0
  146. package/dist/tools/introspection.js.map +1 -0
  147. package/dist/tools/landscape.d.ts.map +1 -0
  148. package/dist/tools/landscape.js +3 -1
  149. package/dist/tools/landscape.js.map +1 -0
  150. package/dist/tools/level.d.ts.map +1 -0
  151. package/dist/tools/level.js.map +1 -0
  152. package/dist/tools/lighting.d.ts.map +1 -0
  153. package/dist/tools/lighting.js +3 -1
  154. package/dist/tools/lighting.js.map +1 -0
  155. package/dist/tools/logs.d.ts.map +1 -0
  156. package/dist/tools/logs.js.map +1 -0
  157. package/dist/tools/materials.d.ts.map +1 -0
  158. package/dist/tools/materials.js +3 -1
  159. package/dist/tools/materials.js.map +1 -0
  160. package/dist/tools/niagara.d.ts.map +1 -0
  161. package/dist/tools/niagara.js +7 -5
  162. package/dist/tools/niagara.js.map +1 -0
  163. package/dist/tools/performance.d.ts.map +1 -0
  164. package/dist/tools/performance.js.map +1 -0
  165. package/dist/tools/physics.d.ts.map +1 -0
  166. package/dist/tools/physics.js +9 -7
  167. package/dist/tools/physics.js.map +1 -0
  168. package/dist/tools/property-dictionary.d.ts.map +1 -0
  169. package/dist/tools/property-dictionary.js.map +1 -0
  170. package/dist/tools/sequence.d.ts.map +1 -0
  171. package/dist/tools/sequence.js +3 -1
  172. package/dist/tools/sequence.js.map +1 -0
  173. package/dist/tools/tool-definition-utils.d.ts.map +1 -0
  174. package/dist/tools/tool-definition-utils.js.map +1 -0
  175. package/dist/tools/ui.d.ts.map +1 -0
  176. package/dist/tools/ui.js +3 -1
  177. package/dist/tools/ui.js.map +1 -0
  178. package/dist/types/automation-responses.d.ts.map +1 -0
  179. package/dist/types/automation-responses.js.map +1 -0
  180. package/dist/types/env.d.ts.map +1 -0
  181. package/dist/types/env.js.map +1 -0
  182. package/dist/types/handler-types.d.ts.map +1 -0
  183. package/dist/types/handler-types.js.map +1 -0
  184. package/dist/types/tool-interfaces.d.ts.map +1 -0
  185. package/dist/types/tool-interfaces.js.map +1 -0
  186. package/dist/types/tool-types.d.ts.map +1 -0
  187. package/dist/types/tool-types.js.map +1 -0
  188. package/dist/unreal-bridge.d.ts +1 -0
  189. package/dist/unreal-bridge.d.ts.map +1 -0
  190. package/dist/unreal-bridge.js +8 -0
  191. package/dist/unreal-bridge.js.map +1 -0
  192. package/dist/utils/command-validator.d.ts.map +1 -0
  193. package/dist/utils/command-validator.js.map +1 -0
  194. package/dist/utils/elicitation.d.ts.map +1 -0
  195. package/dist/utils/elicitation.js.map +1 -0
  196. package/dist/utils/error-handler.d.ts.map +1 -0
  197. package/dist/utils/error-handler.js.map +1 -0
  198. package/dist/utils/ini-reader.d.ts.map +1 -0
  199. package/dist/utils/ini-reader.js.map +1 -0
  200. package/dist/utils/logger.d.ts.map +1 -0
  201. package/dist/utils/logger.js.map +1 -0
  202. package/dist/utils/normalize.d.ts.map +1 -0
  203. package/dist/utils/normalize.js.map +1 -0
  204. package/dist/utils/path-security.d.ts.map +1 -0
  205. package/dist/utils/path-security.js.map +1 -0
  206. package/dist/utils/response-factory.d.ts.map +1 -0
  207. package/dist/utils/response-factory.js +3 -1
  208. package/dist/utils/response-factory.js.map +1 -0
  209. package/dist/utils/response-validator.d.ts.map +1 -0
  210. package/dist/utils/response-validator.js.map +1 -0
  211. package/dist/utils/result-helpers.d.ts.map +1 -0
  212. package/dist/utils/result-helpers.js.map +1 -0
  213. package/dist/utils/safe-json.d.ts.map +1 -0
  214. package/dist/utils/safe-json.js.map +1 -0
  215. package/dist/utils/unreal-command-queue.d.ts.map +1 -0
  216. package/dist/utils/unreal-command-queue.js.map +1 -0
  217. package/dist/utils/validation.d.ts.map +1 -0
  218. package/dist/utils/validation.js.map +1 -0
  219. package/dist/wasm/index.d.ts.map +1 -0
  220. package/dist/wasm/index.js.map +1 -0
  221. package/package.json +12 -34
  222. package/server.json +2 -2
  223. package/.dockerignore +0 -57
  224. package/.env.example +0 -26
  225. package/.env.production +0 -61
  226. package/.eslintrc.json +0 -0
  227. package/.eslintrc.override.json +0 -8
  228. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -94
  229. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  230. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -56
  231. package/.github/copilot-instructions.md +0 -478
  232. package/.github/dependabot.yml +0 -19
  233. package/.github/labeler.yml +0 -24
  234. package/.github/labels.yml +0 -70
  235. package/.github/pull_request_template.md +0 -42
  236. package/.github/release-drafter-config.yml +0 -51
  237. package/.github/workflows/auto-merge.yml +0 -38
  238. package/.github/workflows/ci.yml +0 -38
  239. package/.github/workflows/dependency-review.yml +0 -17
  240. package/.github/workflows/gemini-issue-triage.yml +0 -172
  241. package/.github/workflows/greetings.yml +0 -27
  242. package/.github/workflows/labeler.yml +0 -17
  243. package/.github/workflows/links.yml +0 -80
  244. package/.github/workflows/pr-size-labeler.yml +0 -137
  245. package/.github/workflows/publish-mcp.yml +0 -79
  246. package/.github/workflows/release-drafter.yml +0 -24
  247. package/.github/workflows/release.yml +0 -112
  248. package/.github/workflows/semantic-pull-request.yml +0 -35
  249. package/.github/workflows/smoke-test.yml +0 -36
  250. package/.github/workflows/stale.yml +0 -28
  251. package/CONTRIBUTING.md +0 -140
  252. package/Dockerfile +0 -37
  253. package/GEMINI.md +0 -115
  254. package/Public/Plugin_setup_guide.mp4 +0 -0
  255. package/Public/icon.png +0 -0
  256. package/claude_desktop_config_example.json +0 -15
  257. package/dist/types/responses.d.ts +0 -249
  258. package/dist/types/responses.js +0 -2
  259. package/docs/GraphQL-API.md +0 -888
  260. package/docs/Migration-Guide-v0.5.0.md +0 -684
  261. package/docs/Roadmap.md +0 -53
  262. package/docs/WebAssembly-Integration.md +0 -628
  263. package/docs/editor-plugin-extension.md +0 -370
  264. package/docs/handler-mapping.md +0 -249
  265. package/docs/native-automation-progress.md +0 -128
  266. package/docs/testing-guide.md +0 -423
  267. package/eslint.config.mjs +0 -68
  268. package/mcp-config-example.json +0 -14
  269. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +0 -8
  270. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +0 -64
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +0 -189
  272. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +0 -22
  273. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +0 -30
  274. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +0 -1983
  275. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +0 -72
  276. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +0 -46
  277. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +0 -846
  278. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +0 -2393
  279. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +0 -300
  280. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +0 -2807
  281. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +0 -1087
  282. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +0 -488
  283. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +0 -643
  284. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +0 -31
  285. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +0 -1094
  286. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +0 -5750
  287. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +0 -152
  288. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +0 -2614
  289. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +0 -42
  290. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +0 -1237
  291. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +0 -1725
  292. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +0 -2265
  293. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +0 -954
  294. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +0 -209
  295. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +0 -41
  296. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +0 -1164
  297. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +0 -762
  298. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +0 -663
  299. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +0 -136
  300. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +0 -494
  301. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +0 -278
  302. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +0 -625
  303. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +0 -401
  304. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +0 -67
  305. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +0 -472
  306. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +0 -2634
  307. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +0 -189
  308. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +0 -917
  309. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +0 -39
  310. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +0 -2706
  311. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +0 -519
  312. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +0 -38
  313. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +0 -668
  314. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +0 -346
  315. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +0 -1345
  316. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +0 -149
  317. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -782
  318. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +0 -115
  319. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +0 -796
  320. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +0 -117
  321. package/scripts/check-unreal-connection.mjs +0 -19
  322. package/scripts/clean-tmp.js +0 -23
  323. package/scripts/patch-wasm.js +0 -26
  324. package/scripts/run-all-tests.mjs +0 -136
  325. package/scripts/smoke-test.ts +0 -94
  326. package/scripts/sync-mcp-plugin.js +0 -143
  327. package/scripts/test-no-plugin-alternates.mjs +0 -113
  328. package/scripts/validate-server.js +0 -46
  329. package/scripts/verify-automation-bridge.js +0 -200
  330. package/src/automation/bridge.ts +0 -630
  331. package/src/automation/connection-manager.ts +0 -148
  332. package/src/automation/handshake.ts +0 -99
  333. package/src/automation/index.ts +0 -2
  334. package/src/automation/message-handler.ts +0 -192
  335. package/src/automation/request-tracker.ts +0 -155
  336. package/src/automation/types.ts +0 -108
  337. package/src/cli.ts +0 -34
  338. package/src/config/class-aliases.ts +0 -65
  339. package/src/config.ts +0 -73
  340. package/src/constants.ts +0 -29
  341. package/src/graphql/loaders.ts +0 -244
  342. package/src/graphql/resolvers.ts +0 -1008
  343. package/src/graphql/schema.ts +0 -452
  344. package/src/graphql/server.ts +0 -156
  345. package/src/graphql/types.ts +0 -10
  346. package/src/handlers/resource-handlers.ts +0 -186
  347. package/src/index.ts +0 -243
  348. package/src/resources/actors.ts +0 -127
  349. package/src/resources/assets.ts +0 -286
  350. package/src/resources/levels.ts +0 -68
  351. package/src/server/resource-registry.ts +0 -47
  352. package/src/server/tool-registry.ts +0 -354
  353. package/src/server-setup.ts +0 -114
  354. package/src/services/health-monitor.ts +0 -132
  355. package/src/services/metrics-server.ts +0 -176
  356. package/src/tools/actors.ts +0 -564
  357. package/src/tools/animation.ts +0 -941
  358. package/src/tools/assets.ts +0 -394
  359. package/src/tools/audio.ts +0 -499
  360. package/src/tools/base-tool.ts +0 -52
  361. package/src/tools/behavior-tree.ts +0 -45
  362. package/src/tools/blueprint.ts +0 -940
  363. package/src/tools/consolidated-tool-definitions.ts +0 -1256
  364. package/src/tools/consolidated-tool-handlers.ts +0 -302
  365. package/src/tools/debug.ts +0 -622
  366. package/src/tools/dynamic-handler-registry.ts +0 -33
  367. package/src/tools/editor.ts +0 -435
  368. package/src/tools/engine.ts +0 -43
  369. package/src/tools/environment.ts +0 -281
  370. package/src/tools/foliage.ts +0 -596
  371. package/src/tools/handlers/actor-handlers.ts +0 -244
  372. package/src/tools/handlers/animation-handlers.ts +0 -237
  373. package/src/tools/handlers/argument-helper.ts +0 -142
  374. package/src/tools/handlers/asset-handlers.ts +0 -550
  375. package/src/tools/handlers/audio-handlers.ts +0 -194
  376. package/src/tools/handlers/blueprint-handlers.ts +0 -380
  377. package/src/tools/handlers/common-handlers.ts +0 -108
  378. package/src/tools/handlers/editor-handlers.ts +0 -124
  379. package/src/tools/handlers/effect-handlers.ts +0 -224
  380. package/src/tools/handlers/environment-handlers.ts +0 -183
  381. package/src/tools/handlers/graph-handlers.ts +0 -117
  382. package/src/tools/handlers/input-handlers.ts +0 -28
  383. package/src/tools/handlers/inspect-handlers.ts +0 -450
  384. package/src/tools/handlers/level-handlers.ts +0 -253
  385. package/src/tools/handlers/lighting-handlers.ts +0 -151
  386. package/src/tools/handlers/performance-handlers.ts +0 -132
  387. package/src/tools/handlers/pipeline-handlers.ts +0 -194
  388. package/src/tools/handlers/sequence-handlers.ts +0 -438
  389. package/src/tools/handlers/system-handlers.ts +0 -564
  390. package/src/tools/input.ts +0 -160
  391. package/src/tools/introspection.ts +0 -689
  392. package/src/tools/landscape.ts +0 -649
  393. package/src/tools/level.ts +0 -989
  394. package/src/tools/lighting.ts +0 -1052
  395. package/src/tools/logs.ts +0 -219
  396. package/src/tools/materials.ts +0 -295
  397. package/src/tools/niagara.ts +0 -485
  398. package/src/tools/performance.ts +0 -661
  399. package/src/tools/physics.ts +0 -679
  400. package/src/tools/property-dictionary.ts +0 -98
  401. package/src/tools/sequence.ts +0 -385
  402. package/src/tools/tool-definition-utils.ts +0 -35
  403. package/src/tools/ui.ts +0 -452
  404. package/src/types/automation-responses.ts +0 -119
  405. package/src/types/env.ts +0 -17
  406. package/src/types/handler-types.ts +0 -442
  407. package/src/types/responses.ts +0 -355
  408. package/src/types/tool-interfaces.ts +0 -250
  409. package/src/types/tool-types.ts +0 -575
  410. package/src/unreal-bridge.ts +0 -693
  411. package/src/utils/command-validator.ts +0 -139
  412. package/src/utils/elicitation.ts +0 -132
  413. package/src/utils/error-handler.ts +0 -287
  414. package/src/utils/ini-reader.ts +0 -86
  415. package/src/utils/logger.ts +0 -35
  416. package/src/utils/normalize.test.ts +0 -162
  417. package/src/utils/normalize.ts +0 -146
  418. package/src/utils/path-security.ts +0 -43
  419. package/src/utils/response-factory.ts +0 -44
  420. package/src/utils/response-validator.ts +0 -395
  421. package/src/utils/result-helpers.ts +0 -195
  422. package/src/utils/safe-json.test.ts +0 -90
  423. package/src/utils/safe-json.ts +0 -70
  424. package/src/utils/unreal-command-queue.ts +0 -166
  425. package/src/utils/validation.test.ts +0 -184
  426. package/src/utils/validation.ts +0 -312
  427. package/src/wasm/index.ts +0 -838
  428. package/test-server.mjs +0 -100
  429. package/tests/test-animation.mjs +0 -369
  430. package/tests/test-asset-advanced.mjs +0 -82
  431. package/tests/test-asset-graph.mjs +0 -311
  432. package/tests/test-audio.mjs +0 -417
  433. package/tests/test-automation-timeouts.mjs +0 -98
  434. package/tests/test-behavior-tree.mjs +0 -444
  435. package/tests/test-blueprint-graph.mjs +0 -410
  436. package/tests/test-blueprint.mjs +0 -577
  437. package/tests/test-client-mode.mjs +0 -86
  438. package/tests/test-console-command.mjs +0 -56
  439. package/tests/test-control-actor.mjs +0 -425
  440. package/tests/test-control-editor.mjs +0 -112
  441. package/tests/test-graphql.mjs +0 -372
  442. package/tests/test-input.mjs +0 -349
  443. package/tests/test-inspect.mjs +0 -302
  444. package/tests/test-landscape.mjs +0 -316
  445. package/tests/test-lighting.mjs +0 -428
  446. package/tests/test-manage-asset.mjs +0 -438
  447. package/tests/test-manage-level.mjs +0 -89
  448. package/tests/test-materials.mjs +0 -356
  449. package/tests/test-niagara.mjs +0 -185
  450. package/tests/test-no-inline-python.mjs +0 -122
  451. package/tests/test-performance.mjs +0 -539
  452. package/tests/test-plugin-handshake.mjs +0 -82
  453. package/tests/test-runner.mjs +0 -993
  454. package/tests/test-sequence.mjs +0 -104
  455. package/tests/test-system.mjs +0 -96
  456. package/tests/test-wasm.mjs +0 -283
  457. package/tests/test-world-partition.mjs +0 -215
  458. package/tsconfig.json +0 -56
  459. package/vitest.config.ts +0 -35
  460. package/wasm/Cargo.lock +0 -363
  461. package/wasm/Cargo.toml +0 -42
  462. package/wasm/LICENSE +0 -21
  463. package/wasm/README.md +0 -253
  464. package/wasm/src/dependency_resolver.rs +0 -377
  465. package/wasm/src/lib.rs +0 -153
  466. package/wasm/src/property_parser.rs +0 -271
  467. package/wasm/src/transform_math.rs +0 -396
  468. package/wasm/tests/integration.rs +0 -109
@@ -1,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
- }