unreal-engine-mcp-server 0.4.7 → 0.5.0

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 (438) hide show
  1. package/.env.example +26 -0
  2. package/.env.production +38 -7
  3. package/.eslintrc.json +0 -54
  4. package/.eslintrc.override.json +8 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
  8. package/.github/copilot-instructions.md +478 -45
  9. package/.github/dependabot.yml +19 -0
  10. package/.github/labeler.yml +24 -0
  11. package/.github/labels.yml +70 -0
  12. package/.github/pull_request_template.md +42 -0
  13. package/.github/release-drafter.yml +148 -0
  14. package/.github/workflows/auto-merge.yml +38 -0
  15. package/.github/workflows/ci.yml +38 -0
  16. package/.github/workflows/dependency-review.yml +17 -0
  17. package/.github/workflows/gemini-issue-triage.yml +172 -0
  18. package/.github/workflows/greetings.yml +23 -0
  19. package/.github/workflows/labeler.yml +16 -0
  20. package/.github/workflows/links.yml +80 -0
  21. package/.github/workflows/pr-size-labeler.yml +137 -0
  22. package/.github/workflows/publish-mcp.yml +12 -7
  23. package/.github/workflows/release-drafter.yml +23 -0
  24. package/.github/workflows/release.yml +112 -0
  25. package/.github/workflows/semantic-pull-request.yml +35 -0
  26. package/.github/workflows/smoke-test.yml +36 -0
  27. package/.github/workflows/stale.yml +28 -0
  28. package/CHANGELOG.md +267 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -71
  31. package/claude_desktop_config_example.json +7 -6
  32. package/dist/automation/bridge.d.ts +50 -0
  33. package/dist/automation/bridge.js +452 -0
  34. package/dist/automation/connection-manager.d.ts +23 -0
  35. package/dist/automation/connection-manager.js +107 -0
  36. package/dist/automation/handshake.d.ts +11 -0
  37. package/dist/automation/handshake.js +89 -0
  38. package/dist/automation/index.d.ts +3 -0
  39. package/dist/automation/index.js +3 -0
  40. package/dist/automation/message-handler.d.ts +12 -0
  41. package/dist/automation/message-handler.js +149 -0
  42. package/dist/automation/request-tracker.d.ts +25 -0
  43. package/dist/automation/request-tracker.js +98 -0
  44. package/dist/automation/types.d.ts +130 -0
  45. package/dist/automation/types.js +2 -0
  46. package/dist/cli.js +32 -5
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.js +60 -0
  49. package/dist/constants.d.ts +12 -0
  50. package/dist/constants.js +12 -0
  51. package/dist/graphql/resolvers.d.ts +268 -0
  52. package/dist/graphql/resolvers.js +743 -0
  53. package/dist/graphql/schema.d.ts +5 -0
  54. package/dist/graphql/schema.js +437 -0
  55. package/dist/graphql/server.d.ts +26 -0
  56. package/dist/graphql/server.js +115 -0
  57. package/dist/graphql/types.d.ts +7 -0
  58. package/dist/graphql/types.js +2 -0
  59. package/dist/handlers/resource-handlers.d.ts +20 -0
  60. package/dist/handlers/resource-handlers.js +180 -0
  61. package/dist/index.d.ts +31 -18
  62. package/dist/index.js +119 -619
  63. package/dist/prompts/index.js +4 -4
  64. package/dist/resources/actors.d.ts +17 -12
  65. package/dist/resources/actors.js +56 -76
  66. package/dist/resources/assets.d.ts +6 -14
  67. package/dist/resources/assets.js +115 -147
  68. package/dist/resources/levels.d.ts +13 -13
  69. package/dist/resources/levels.js +25 -34
  70. package/dist/server/resource-registry.d.ts +20 -0
  71. package/dist/server/resource-registry.js +37 -0
  72. package/dist/server/tool-registry.d.ts +23 -0
  73. package/dist/server/tool-registry.js +322 -0
  74. package/dist/server-setup.d.ts +21 -0
  75. package/dist/server-setup.js +111 -0
  76. package/dist/services/health-monitor.d.ts +34 -0
  77. package/dist/services/health-monitor.js +105 -0
  78. package/dist/services/metrics-server.d.ts +11 -0
  79. package/dist/services/metrics-server.js +105 -0
  80. package/dist/tools/actors.d.ts +147 -9
  81. package/dist/tools/actors.js +350 -311
  82. package/dist/tools/animation.d.ts +135 -4
  83. package/dist/tools/animation.js +510 -411
  84. package/dist/tools/assets.d.ts +117 -19
  85. package/dist/tools/assets.js +259 -284
  86. package/dist/tools/audio.d.ts +102 -42
  87. package/dist/tools/audio.js +272 -685
  88. package/dist/tools/base-tool.d.ts +17 -0
  89. package/dist/tools/base-tool.js +46 -0
  90. package/dist/tools/behavior-tree.d.ts +94 -0
  91. package/dist/tools/behavior-tree.js +39 -0
  92. package/dist/tools/blueprint/helpers.d.ts +29 -0
  93. package/dist/tools/blueprint/helpers.js +182 -0
  94. package/dist/tools/blueprint.d.ts +228 -118
  95. package/dist/tools/blueprint.js +685 -832
  96. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  97. package/dist/tools/consolidated-tool-definitions.js +829 -496
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1026
  100. package/dist/tools/debug.d.ts +143 -85
  101. package/dist/tools/debug.js +234 -180
  102. package/dist/tools/dynamic-handler-registry.d.ts +11 -0
  103. package/dist/tools/dynamic-handler-registry.js +101 -0
  104. package/dist/tools/editor.d.ts +139 -18
  105. package/dist/tools/editor.js +239 -244
  106. package/dist/tools/engine.d.ts +10 -4
  107. package/dist/tools/engine.js +13 -5
  108. package/dist/tools/environment.d.ts +36 -0
  109. package/dist/tools/environment.js +267 -0
  110. package/dist/tools/foliage.d.ts +105 -14
  111. package/dist/tools/foliage.js +219 -331
  112. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  113. package/dist/tools/handlers/actor-handlers.js +232 -0
  114. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  115. package/dist/tools/handlers/animation-handlers.js +185 -0
  116. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  117. package/dist/tools/handlers/argument-helper.js +80 -0
  118. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  119. package/dist/tools/handlers/asset-handlers.js +496 -0
  120. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  121. package/dist/tools/handlers/audio-handlers.js +166 -0
  122. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  123. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  124. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  125. package/dist/tools/handlers/common-handlers.js +56 -0
  126. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  127. package/dist/tools/handlers/editor-handlers.js +119 -0
  128. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  129. package/dist/tools/handlers/effect-handlers.js +171 -0
  130. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  131. package/dist/tools/handlers/environment-handlers.js +170 -0
  132. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  133. package/dist/tools/handlers/graph-handlers.js +90 -0
  134. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  135. package/dist/tools/handlers/input-handlers.js +21 -0
  136. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  137. package/dist/tools/handlers/inspect-handlers.js +383 -0
  138. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  139. package/dist/tools/handlers/level-handlers.js +237 -0
  140. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  141. package/dist/tools/handlers/lighting-handlers.js +144 -0
  142. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  143. package/dist/tools/handlers/performance-handlers.js +130 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  146. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  147. package/dist/tools/handlers/sequence-handlers.js +376 -0
  148. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  149. package/dist/tools/handlers/system-handlers.js +506 -0
  150. package/dist/tools/input.d.ts +19 -0
  151. package/dist/tools/input.js +89 -0
  152. package/dist/tools/introspection.d.ts +103 -40
  153. package/dist/tools/introspection.js +425 -568
  154. package/dist/tools/landscape.d.ts +97 -36
  155. package/dist/tools/landscape.js +280 -409
  156. package/dist/tools/level.d.ts +130 -10
  157. package/dist/tools/level.js +639 -675
  158. package/dist/tools/lighting.d.ts +77 -38
  159. package/dist/tools/lighting.js +441 -943
  160. package/dist/tools/logs.d.ts +3 -3
  161. package/dist/tools/logs.js +5 -57
  162. package/dist/tools/materials.d.ts +91 -24
  163. package/dist/tools/materials.js +190 -118
  164. package/dist/tools/niagara.d.ts +149 -39
  165. package/dist/tools/niagara.js +232 -182
  166. package/dist/tools/performance.d.ts +27 -12
  167. package/dist/tools/performance.js +204 -122
  168. package/dist/tools/physics.d.ts +32 -77
  169. package/dist/tools/physics.js +171 -582
  170. package/dist/tools/property-dictionary.d.ts +13 -0
  171. package/dist/tools/property-dictionary.js +82 -0
  172. package/dist/tools/sequence.d.ts +73 -48
  173. package/dist/tools/sequence.js +196 -748
  174. package/dist/tools/tool-definition-utils.d.ts +59 -0
  175. package/dist/tools/tool-definition-utils.js +35 -0
  176. package/dist/tools/ui.d.ts +66 -34
  177. package/dist/tools/ui.js +134 -214
  178. package/dist/types/env.d.ts +0 -3
  179. package/dist/types/env.js +0 -7
  180. package/dist/types/tool-interfaces.d.ts +898 -0
  181. package/dist/types/tool-interfaces.js +2 -0
  182. package/dist/types/tool-types.d.ts +183 -19
  183. package/dist/types/tool-types.js +0 -4
  184. package/dist/unreal-bridge.d.ts +24 -131
  185. package/dist/unreal-bridge.js +364 -1506
  186. package/dist/utils/command-validator.d.ts +9 -0
  187. package/dist/utils/command-validator.js +67 -0
  188. package/dist/utils/elicitation.d.ts +1 -1
  189. package/dist/utils/elicitation.js +12 -15
  190. package/dist/utils/error-handler.d.ts +2 -51
  191. package/dist/utils/error-handler.js +11 -87
  192. package/dist/utils/ini-reader.d.ts +3 -0
  193. package/dist/utils/ini-reader.js +69 -0
  194. package/dist/utils/logger.js +9 -6
  195. package/dist/utils/normalize.d.ts +3 -0
  196. package/dist/utils/normalize.js +56 -0
  197. package/dist/utils/response-factory.d.ts +7 -0
  198. package/dist/utils/response-factory.js +33 -0
  199. package/dist/utils/response-validator.d.ts +3 -24
  200. package/dist/utils/response-validator.js +130 -81
  201. package/dist/utils/result-helpers.d.ts +4 -5
  202. package/dist/utils/result-helpers.js +15 -16
  203. package/dist/utils/safe-json.js +5 -11
  204. package/dist/utils/unreal-command-queue.d.ts +24 -0
  205. package/dist/utils/unreal-command-queue.js +120 -0
  206. package/dist/utils/validation.d.ts +0 -40
  207. package/dist/utils/validation.js +1 -78
  208. package/dist/wasm/index.d.ts +70 -0
  209. package/dist/wasm/index.js +535 -0
  210. package/docs/GraphQL-API.md +888 -0
  211. package/docs/Migration-Guide-v0.5.0.md +692 -0
  212. package/docs/Roadmap.md +53 -0
  213. package/docs/WebAssembly-Integration.md +628 -0
  214. package/docs/editor-plugin-extension.md +370 -0
  215. package/docs/handler-mapping.md +242 -0
  216. package/docs/native-automation-progress.md +128 -0
  217. package/docs/testing-guide.md +423 -0
  218. package/mcp-config-example.json +6 -6
  219. package/package.json +60 -27
  220. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  221. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  222. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  223. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  224. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  225. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  226. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  227. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  228. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  272. package/scripts/check-unreal-connection.mjs +19 -0
  273. package/scripts/clean-tmp.js +23 -0
  274. package/scripts/patch-wasm.js +26 -0
  275. package/scripts/run-all-tests.mjs +131 -0
  276. package/scripts/smoke-test.ts +94 -0
  277. package/scripts/sync-mcp-plugin.js +143 -0
  278. package/scripts/test-no-plugin-alternates.mjs +113 -0
  279. package/scripts/validate-server.js +46 -0
  280. package/scripts/verify-automation-bridge.js +200 -0
  281. package/server.json +57 -21
  282. package/src/automation/bridge.ts +558 -0
  283. package/src/automation/connection-manager.ts +130 -0
  284. package/src/automation/handshake.ts +99 -0
  285. package/src/automation/index.ts +2 -0
  286. package/src/automation/message-handler.ts +167 -0
  287. package/src/automation/request-tracker.ts +123 -0
  288. package/src/automation/types.ts +107 -0
  289. package/src/cli.ts +33 -6
  290. package/src/config.ts +73 -0
  291. package/src/constants.ts +12 -0
  292. package/src/graphql/resolvers.ts +1010 -0
  293. package/src/graphql/schema.ts +452 -0
  294. package/src/graphql/server.ts +154 -0
  295. package/src/graphql/types.ts +7 -0
  296. package/src/handlers/resource-handlers.ts +186 -0
  297. package/src/index.ts +152 -663
  298. package/src/prompts/index.ts +4 -4
  299. package/src/resources/actors.ts +58 -76
  300. package/src/resources/assets.ts +147 -134
  301. package/src/resources/levels.ts +28 -33
  302. package/src/server/resource-registry.ts +47 -0
  303. package/src/server/tool-registry.ts +354 -0
  304. package/src/server-setup.ts +148 -0
  305. package/src/services/health-monitor.ts +132 -0
  306. package/src/services/metrics-server.ts +142 -0
  307. package/src/tools/actors.ts +417 -322
  308. package/src/tools/animation.ts +671 -461
  309. package/src/tools/assets.ts +353 -289
  310. package/src/tools/audio.ts +323 -766
  311. package/src/tools/base-tool.ts +52 -0
  312. package/src/tools/behavior-tree.ts +45 -0
  313. package/src/tools/blueprint/helpers.ts +189 -0
  314. package/src/tools/blueprint.ts +787 -965
  315. package/src/tools/consolidated-tool-definitions.ts +993 -515
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1139
  317. package/src/tools/debug.ts +292 -187
  318. package/src/tools/dynamic-handler-registry.ts +151 -0
  319. package/src/tools/editor.ts +309 -246
  320. package/src/tools/engine.ts +14 -3
  321. package/src/tools/environment.ts +287 -0
  322. package/src/tools/foliage.ts +314 -379
  323. package/src/tools/handlers/actor-handlers.ts +271 -0
  324. package/src/tools/handlers/animation-handlers.ts +237 -0
  325. package/src/tools/handlers/argument-helper.ts +142 -0
  326. package/src/tools/handlers/asset-handlers.ts +532 -0
  327. package/src/tools/handlers/audio-handlers.ts +194 -0
  328. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  329. package/src/tools/handlers/common-handlers.ts +87 -0
  330. package/src/tools/handlers/editor-handlers.ts +123 -0
  331. package/src/tools/handlers/effect-handlers.ts +220 -0
  332. package/src/tools/handlers/environment-handlers.ts +183 -0
  333. package/src/tools/handlers/graph-handlers.ts +116 -0
  334. package/src/tools/handlers/input-handlers.ts +28 -0
  335. package/src/tools/handlers/inspect-handlers.ts +450 -0
  336. package/src/tools/handlers/level-handlers.ts +252 -0
  337. package/src/tools/handlers/lighting-handlers.ts +147 -0
  338. package/src/tools/handlers/performance-handlers.ts +132 -0
  339. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  340. package/src/tools/handlers/sequence-handlers.ts +415 -0
  341. package/src/tools/handlers/system-handlers.ts +564 -0
  342. package/src/tools/input.ts +101 -0
  343. package/src/tools/introspection.ts +493 -584
  344. package/src/tools/landscape.ts +394 -489
  345. package/src/tools/level.ts +752 -694
  346. package/src/tools/lighting.ts +583 -984
  347. package/src/tools/logs.ts +9 -57
  348. package/src/tools/materials.ts +231 -121
  349. package/src/tools/niagara.ts +293 -168
  350. package/src/tools/performance.ts +320 -168
  351. package/src/tools/physics.ts +268 -613
  352. package/src/tools/property-dictionary.ts +98 -0
  353. package/src/tools/sequence.ts +255 -815
  354. package/src/tools/tool-definition-utils.ts +35 -0
  355. package/src/tools/ui.ts +207 -283
  356. package/src/types/env.ts +0 -10
  357. package/src/types/tool-interfaces.ts +250 -0
  358. package/src/types/tool-types.ts +243 -21
  359. package/src/unreal-bridge.ts +460 -1550
  360. package/src/utils/command-validator.ts +75 -0
  361. package/src/utils/elicitation.ts +10 -7
  362. package/src/utils/error-handler.ts +14 -90
  363. package/src/utils/ini-reader.ts +86 -0
  364. package/src/utils/logger.ts +8 -3
  365. package/src/utils/normalize.ts +60 -0
  366. package/src/utils/response-factory.ts +39 -0
  367. package/src/utils/response-validator.ts +176 -56
  368. package/src/utils/result-helpers.ts +21 -19
  369. package/src/utils/safe-json.ts +14 -11
  370. package/src/utils/unreal-command-queue.ts +152 -0
  371. package/src/utils/validation.ts +4 -1
  372. package/src/wasm/index.ts +838 -0
  373. package/test-server.mjs +100 -0
  374. package/tests/run-unreal-tool-tests.mjs +242 -14
  375. package/tests/test-animation.mjs +44 -0
  376. package/tests/test-asset-advanced.mjs +82 -0
  377. package/tests/test-asset-errors.mjs +35 -0
  378. package/tests/test-audio.mjs +219 -0
  379. package/tests/test-automation-timeouts.mjs +98 -0
  380. package/tests/test-behavior-tree.mjs +261 -0
  381. package/tests/test-blueprint-events.mjs +35 -0
  382. package/tests/test-blueprint-graph.mjs +79 -0
  383. package/tests/test-blueprint.mjs +577 -0
  384. package/tests/test-client-mode.mjs +86 -0
  385. package/tests/test-console-command.mjs +56 -0
  386. package/tests/test-control-actor.mjs +425 -0
  387. package/tests/test-control-editor.mjs +80 -0
  388. package/tests/test-extra-tools.mjs +38 -0
  389. package/tests/test-graphql.mjs +322 -0
  390. package/tests/test-inspect.mjs +72 -0
  391. package/tests/test-landscape.mjs +60 -0
  392. package/tests/test-manage-asset.mjs +438 -0
  393. package/tests/test-manage-level.mjs +70 -0
  394. package/tests/test-materials.mjs +356 -0
  395. package/tests/test-niagara.mjs +185 -0
  396. package/tests/test-no-inline-python.mjs +122 -0
  397. package/tests/test-plugin-handshake.mjs +82 -0
  398. package/tests/test-render.mjs +33 -0
  399. package/tests/test-runner.mjs +933 -0
  400. package/tests/test-search-assets.mjs +66 -0
  401. package/tests/test-sequence.mjs +68 -0
  402. package/tests/test-system.mjs +57 -0
  403. package/tests/test-wasm.mjs +193 -0
  404. package/tests/test-world-partition.mjs +215 -0
  405. package/tsconfig.json +3 -3
  406. package/wasm/Cargo.lock +363 -0
  407. package/wasm/Cargo.toml +42 -0
  408. package/wasm/LICENSE +21 -0
  409. package/wasm/README.md +253 -0
  410. package/wasm/src/dependency_resolver.rs +377 -0
  411. package/wasm/src/lib.rs +153 -0
  412. package/wasm/src/property_parser.rs +271 -0
  413. package/wasm/src/transform_math.rs +396 -0
  414. package/wasm/tests/integration.rs +109 -0
  415. package/.github/workflows/smithery-build.yml +0 -29
  416. package/dist/tools/build_environment_advanced.d.ts +0 -65
  417. package/dist/tools/build_environment_advanced.js +0 -633
  418. package/dist/tools/rc.d.ts +0 -110
  419. package/dist/tools/rc.js +0 -437
  420. package/dist/tools/visual.d.ts +0 -40
  421. package/dist/tools/visual.js +0 -282
  422. package/dist/utils/http.d.ts +0 -6
  423. package/dist/utils/http.js +0 -151
  424. package/dist/utils/python-output.d.ts +0 -18
  425. package/dist/utils/python-output.js +0 -290
  426. package/dist/utils/python.d.ts +0 -2
  427. package/dist/utils/python.js +0 -4
  428. package/dist/utils/stdio-redirect.d.ts +0 -2
  429. package/dist/utils/stdio-redirect.js +0 -20
  430. package/docs/unreal-tool-test-cases.md +0 -574
  431. package/smithery.yaml +0 -29
  432. package/src/tools/build_environment_advanced.ts +0 -732
  433. package/src/tools/rc.ts +0 -515
  434. package/src/tools/visual.ts +0 -281
  435. package/src/utils/http.ts +0 -187
  436. package/src/utils/python-output.ts +0 -351
  437. package/src/utils/python.ts +0 -3
  438. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,11 +1,7 @@
1
1
  import { UnrealBridge } from '../unreal-bridge.js';
2
+ import { AutomationBridge } from '../automation/index.js';
3
+ import { cleanObject } from '../utils/safe-json.js';
2
4
  import { validateAssetParams } from '../utils/validation.js';
3
- import {
4
- interpretStandardResult,
5
- coerceBoolean,
6
- coerceString,
7
- coerceStringArray
8
- } from '../utils/result-helpers.js';
9
5
 
10
6
  type CreateAnimationBlueprintSuccess = {
11
7
  success: true;
@@ -51,7 +47,26 @@ type PlayAnimationFailure = {
51
47
  };
52
48
 
53
49
  export class AnimationTools {
54
- constructor(private bridge: UnrealBridge) {}
50
+ private managedArtifacts = new Map<string, {
51
+ path?: string;
52
+ type: string;
53
+ metadata?: Record<string, unknown>;
54
+ createdAt: number;
55
+ }>();
56
+
57
+ private automationBridge?: AutomationBridge;
58
+
59
+ constructor(private bridge: UnrealBridge, automationBridge?: AutomationBridge) {
60
+ this.automationBridge = automationBridge;
61
+ }
62
+
63
+ setAutomationBridge(automationBridge?: AutomationBridge) {
64
+ this.automationBridge = automationBridge;
65
+ }
66
+
67
+ private trackArtifact(key: string, info: { path?: string; type: string; metadata?: Record<string, unknown> }) {
68
+ this.managedArtifacts.set(key, { ...info, createdAt: Date.now() });
69
+ }
55
70
 
56
71
  async createAnimationBlueprint(params: {
57
72
  name: string;
@@ -69,14 +84,69 @@ export class AnimationTools {
69
84
  const sanitized = validation.sanitized;
70
85
  const assetName = sanitized.name;
71
86
  const assetPath = sanitized.savePath ?? targetPath;
72
- const script = this.buildCreateAnimationBlueprintScript({
73
- name: assetName,
74
- path: assetPath,
75
- skeletonPath: params.skeletonPath
76
- });
87
+ const fullPath = `${assetPath}/${assetName}`;
88
+
89
+ // Prefer native plugin support; surface real errors when creation fails.
90
+ if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
91
+ try {
92
+ const resp = await this.automationBridge.sendAutomationRequest('create_animation_blueprint', {
93
+ name: assetName,
94
+ skeletonPath: params.skeletonPath,
95
+ savePath: assetPath
96
+ }, { timeoutMs: 60000 });
97
+
98
+ const result = resp?.result ?? resp;
99
+ const resultObj = result && typeof result === 'object' ? result as Record<string, unknown> : undefined;
100
+ const warnings = Array.isArray(resultObj?.warnings) ? (resultObj.warnings as string[]) : undefined;
101
+ const details = Array.isArray(resultObj?.details) ? (resultObj.details as string[]) : undefined;
102
+ const isSuccess = resp && resp.success !== false && !!resultObj;
103
+
104
+ if (isSuccess && resultObj) {
105
+ const blueprintPath = typeof resultObj.blueprintPath === 'string' ? resultObj.blueprintPath : fullPath;
106
+ this.trackArtifact(assetName, { path: blueprintPath, type: 'AnimationBlueprint' });
107
+ return {
108
+ success: true,
109
+ message: resp.message || `Animation Blueprint created at ${blueprintPath}`,
110
+ path: blueprintPath,
111
+ skeleton: params.skeletonPath,
112
+ warnings,
113
+ details
114
+ };
115
+ }
116
+
117
+ const message = typeof resp?.message === 'string'
118
+ ? resp.message
119
+ : (typeof resp?.error === 'string' ? resp.error : 'Animation Blueprint creation failed');
120
+ const error = typeof resp?.error === 'string' ? resp.error : message;
121
+
122
+ return {
123
+ success: false,
124
+ message,
125
+ error,
126
+ path: fullPath,
127
+ skeleton: params.skeletonPath,
128
+ warnings,
129
+ details
130
+ };
131
+ } catch (err) {
132
+ const error = String(err);
133
+ return {
134
+ success: false,
135
+ message: `Failed to create Animation Blueprint: ${error}`,
136
+ error,
137
+ path: fullPath,
138
+ skeleton: params.skeletonPath
139
+ };
140
+ }
141
+ }
77
142
 
78
- const response = await this.bridge.executePython(script);
79
- return this.parseAnimationBlueprintResponse(response, assetName, assetPath);
143
+ return {
144
+ success: false,
145
+ message: 'Automation bridge not connected for Animation Blueprint creation',
146
+ error: 'AUTOMATION_BRIDGE_UNAVAILABLE',
147
+ path: fullPath,
148
+ skeleton: params.skeletonPath
149
+ };
80
150
  } catch (err) {
81
151
  const error = `Failed to create Animation Blueprint: ${err}`;
82
152
  return { success: false, message: error, error: String(err) };
@@ -143,6 +213,95 @@ export class AnimationTools {
143
213
  }
144
214
  }
145
215
 
216
+ async createStateMachine(params: {
217
+ machineName?: string;
218
+ states?: Array<string | { name: string; animation?: string; isEntry?: boolean; isExit?: boolean }>;
219
+ transitions?: Array<{ sourceState: string; targetState: string; condition?: string }>;
220
+ blueprintPath?: string;
221
+ }): Promise<
222
+ | {
223
+ success: true;
224
+ message: string;
225
+ machineName: string;
226
+ blueprintPath?: string;
227
+ states?: Array<{ name: string; animation?: string; isEntry?: boolean; isExit?: boolean }>;
228
+ transitions?: Array<{ sourceState: string; targetState: string; condition?: string }>;
229
+ }
230
+ | { success: false; message: string; error: string }
231
+ > {
232
+ try {
233
+ const rawName = typeof params.machineName === 'string' ? params.machineName.trim() : '';
234
+ const machineName = rawName || 'StateMachine';
235
+
236
+ const normalizedStates: Array<{ name: string; animation?: string; isEntry?: boolean; isExit?: boolean }> =
237
+ Array.isArray(params.states)
238
+ ? params.states
239
+ .map((s) => {
240
+ if (typeof s === 'string') {
241
+ const name = s.trim();
242
+ return name ? { name } : undefined;
243
+ }
244
+ if (s && typeof s === 'object' && typeof (s as any).name === 'string') {
245
+ const name = (s as any).name.trim();
246
+ if (!name) return undefined;
247
+ return s as { name: string; animation?: string; isEntry?: boolean; isExit?: boolean };
248
+ }
249
+ return undefined;
250
+ })
251
+ .filter((s): s is { name: string; animation?: string; isEntry?: boolean; isExit?: boolean } => !!s)
252
+ : [];
253
+
254
+ const normalizedTransitionsRaw = Array.isArray(params.transitions)
255
+ ? params.transitions
256
+ .map((t) => {
257
+ if (!t || typeof t !== 'object') return undefined;
258
+ const src = (t.sourceState || '').trim();
259
+ const dst = (t.targetState || '').trim();
260
+ if (!src || !dst) return undefined;
261
+ return { sourceState: src, targetState: dst, condition: t.condition };
262
+ })
263
+ .filter((t) => !!t)
264
+ : [];
265
+
266
+ const normalizedTransitions = normalizedTransitionsRaw as Array<{
267
+ sourceState: string;
268
+ targetState: string;
269
+ condition?: string;
270
+ }>;
271
+
272
+ const blueprintPath = typeof params.blueprintPath === 'string' && params.blueprintPath.trim().length > 0
273
+ ? params.blueprintPath.trim()
274
+ : undefined;
275
+
276
+ const key = `StateMachine:${machineName}`;
277
+ this.trackArtifact(key, {
278
+ path: blueprintPath,
279
+ type: 'AnimationStateMachine',
280
+ metadata: {
281
+ machineName,
282
+ states: normalizedStates,
283
+ transitions: normalizedTransitions
284
+ }
285
+ });
286
+
287
+ return {
288
+ success: true,
289
+ message: `State machine '${machineName}' specification recorded`,
290
+ machineName,
291
+ blueprintPath,
292
+ states: normalizedStates.length ? normalizedStates : undefined,
293
+ transitions: normalizedTransitions.length ? normalizedTransitions : undefined
294
+ };
295
+ } catch (err) {
296
+ const error = String(err);
297
+ return {
298
+ success: false,
299
+ message: `Failed to record state machine specification: ${error}`,
300
+ error
301
+ };
302
+ }
303
+ }
304
+
146
305
  async createBlendSpace(params: {
147
306
  name: string;
148
307
  savePath?: string;
@@ -151,7 +310,10 @@ export class AnimationTools {
151
310
  horizontalAxis?: { name: string; minValue: number; maxValue: number };
152
311
  verticalAxis?: { name: string; minValue: number; maxValue: number };
153
312
  samples?: Array<{ animation: string; x: number; y?: number }>;
154
- }): Promise<{ success: true; message: string; path: string } | { success: false; error: string }> {
313
+ }): Promise<
314
+ | { success: true; message: string; path: string; skeletonPath?: string; warnings?: string[]; details?: unknown }
315
+ | { success: false; error: string }
316
+ > {
155
317
  try {
156
318
  const targetPath = params.savePath ?? '/Game/Animations';
157
319
  const validation = validateAssetParams({ name: params.name, savePath: targetPath });
@@ -163,42 +325,64 @@ export class AnimationTools {
163
325
  const assetName = sanitized.name;
164
326
  const assetPath = sanitized.savePath ?? targetPath;
165
327
  const dimensions = params.dimensions === 2 ? 2 : 1;
166
- const blendSpaceType = dimensions === 2 ? 'BlendSpace' : 'BlendSpace1D';
167
328
 
168
- const commands: string[] = [
169
- `CreateAsset ${blendSpaceType} ${assetName} ${assetPath}`,
170
- `echo Creating ${blendSpaceType} ${assetName} at ${assetPath}`
171
- ];
329
+ if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
330
+ try {
331
+ const payload: any = {
332
+ action: 'create_blend_space',
333
+ name: assetName,
334
+ savePath: assetPath,
335
+ skeletonPath: params.skeletonPath,
336
+ dimensions,
337
+ samples: params.samples
338
+ };
339
+
340
+ if (params.horizontalAxis) {
341
+ payload.minX = params.horizontalAxis.minValue;
342
+ payload.maxX = params.horizontalAxis.maxValue;
343
+ // gridX is not in params.horizontalAxis, maybe default or add to interface?
344
+ // The C++ code defaults GridX to 3.0.
345
+ }
172
346
 
173
- if (params.skeletonPath) {
174
- commands.push(`SetBlendSpaceSkeleton ${assetName} ${params.skeletonPath}`);
175
- }
347
+ if (params.verticalAxis) {
348
+ payload.minY = params.verticalAxis.minValue;
349
+ payload.maxY = params.verticalAxis.maxValue;
350
+ }
176
351
 
177
- if (params.horizontalAxis) {
178
- commands.push(
179
- `SetBlendSpaceAxis ${assetName} Horizontal ${params.horizontalAxis.name} ${params.horizontalAxis.minValue} ${params.horizontalAxis.maxValue}`
180
- );
181
- }
352
+ const resp = await this.automationBridge.sendAutomationRequest('animation_physics', cleanObject(payload));
353
+ const result = resp?.result ?? resp;
354
+ const resultObj = result && typeof result === 'object' ? result as Record<string, unknown> : undefined;
355
+ const isSuccess = resp && resp.success !== false && !!resultObj;
356
+
357
+ if (isSuccess && resultObj) {
358
+ const path = typeof resultObj.blendSpacePath === 'string'
359
+ ? (resultObj.blendSpacePath as string)
360
+ : `${assetPath}/${assetName}`;
361
+ const warnings = Array.isArray(resultObj.warnings) ? (resultObj.warnings as string[]) : undefined;
362
+ const details = resultObj ? resultObj.details : undefined;
363
+ return {
364
+ success: true,
365
+ message: resp.message || `Blend Space ${assetName} created`,
366
+ path,
367
+ skeletonPath: params.skeletonPath,
368
+ details,
369
+ warnings
370
+ };
371
+ }
182
372
 
183
- if (dimensions === 2 && params.verticalAxis) {
184
- commands.push(
185
- `SetBlendSpaceAxis ${assetName} Vertical ${params.verticalAxis.name} ${params.verticalAxis.minValue} ${params.verticalAxis.maxValue}`
186
- );
187
- }
373
+ const message = typeof resp?.message === 'string'
374
+ ? resp.message
375
+ : (typeof resp?.error === 'string' ? resp.error : 'Blend space creation failed');
376
+ const error = typeof resp?.error === 'string' ? resp.error : message;
188
377
 
189
- if (params.samples) {
190
- for (const sample of params.samples) {
191
- const coords = dimensions === 1 ? `${sample.x}` : `${sample.x} ${sample.y ?? 0}`;
192
- commands.push(`AddBlendSpaceSample ${assetName} ${sample.animation} ${coords}`);
378
+ return { success: false, error };
379
+ } catch (err) {
380
+ const error = String(err);
381
+ return { success: false, error: `Failed to create blend space: ${error}` };
193
382
  }
194
383
  }
195
384
 
196
- await this.bridge.executeConsoleCommands(commands);
197
- return {
198
- success: true,
199
- message: `Blend Space ${assetName} created`,
200
- path: `${assetPath}/${assetName}`
201
- };
385
+ return { success: false, error: 'Automation bridge not connected for createBlendSpace' };
202
386
  } catch (err) {
203
387
  return { success: false, error: `Failed to create blend space: ${err}` };
204
388
  }
@@ -214,7 +398,10 @@ export class AnimationTools {
214
398
  bone?: string;
215
399
  defaultValue?: unknown;
216
400
  }>;
217
- }): Promise<{ success: true; message: string; path: string } | { success: false; error: string }> {
401
+ }): Promise<
402
+ | { success: true; message: string; path: string; warnings?: string[]; details?: unknown }
403
+ | { success: false; error: string }
404
+ > {
218
405
  try {
219
406
  const targetPath = params.savePath ?? '/Game/Animations';
220
407
  const validation = validateAssetParams({ name: params.name, savePath: targetPath });
@@ -227,32 +414,445 @@ export class AnimationTools {
227
414
  const assetPath = sanitized.savePath ?? targetPath;
228
415
  const fullPath = `${assetPath}/${assetName}`;
229
416
 
230
- const commands: string[] = [
231
- `CreateAsset ControlRig ${assetName} ${assetPath}`,
232
- `SetControlRigSkeleton ${assetName} ${params.skeletonPath}`
233
- ];
417
+ if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
418
+ try {
419
+ const resp = await this.automationBridge.sendAutomationRequest('animation_physics', cleanObject({
420
+ action: 'setup_ik',
421
+ name: assetName,
422
+ savePath: assetPath,
423
+ skeletonPath: params.skeletonPath,
424
+ controls: params.controls
425
+ }), { timeoutMs: 60000 });
426
+ const result = resp?.result ?? resp;
427
+ const resultObj = result && typeof result === 'object' ? result as Record<string, unknown> : undefined;
428
+ const isSuccess = resp && resp.success !== false && !!resultObj;
429
+
430
+ if (isSuccess && resultObj) {
431
+ const controlRigPath = typeof resultObj.controlRigPath === 'string'
432
+ ? (resultObj.controlRigPath as string)
433
+ : fullPath;
434
+ const warnings = Array.isArray(resultObj.warnings) ? (resultObj.warnings as string[]) : undefined;
435
+ const details = resultObj ? resultObj.details : undefined;
436
+ this.trackArtifact(assetName, { path: controlRigPath, type: 'ControlRig' });
437
+ return {
438
+ success: true,
439
+ message: resp.message || `Control Rig ${assetName} created`,
440
+ path: controlRigPath,
441
+ warnings,
442
+ details
443
+ };
444
+ }
234
445
 
235
- if (params.controls) {
236
- for (const control of params.controls) {
237
- commands.push(
238
- `AddControlRigControl ${assetName} ${control.name} ${control.type} ${control.bone ?? ''}`
239
- );
240
- if (control.defaultValue !== undefined) {
241
- commands.push(
242
- `SetControlRigDefault ${assetName} ${control.name} ${JSON.stringify(control.defaultValue)}`
243
- );
446
+ const message = typeof resp?.message === 'string'
447
+ ? resp.message
448
+ : (typeof resp?.error === 'string' ? resp.error : 'Control Rig setup failed');
449
+ const error = typeof resp?.error === 'string' ? resp.error : message;
450
+
451
+ return { success: false, error };
452
+ } catch (err) {
453
+ const error = String(err);
454
+ return { success: false, error: `Failed to setup control rig: ${error}` };
455
+ }
456
+ }
457
+
458
+ return { success: false, error: 'Automation bridge not connected for setupControlRig' };
459
+ } catch (err) {
460
+ return { success: false, error: `Failed to setup control rig: ${err}` };
461
+ }
462
+ }
463
+
464
+ async setupIK(params: {
465
+ actorName?: string;
466
+ ikBones?: string[];
467
+ enableFootPlacement?: boolean;
468
+ }): Promise<
469
+ | {
470
+ success: true;
471
+ message: string;
472
+ actorName: string;
473
+ ikBones?: string[];
474
+ enableFootPlacement?: boolean;
475
+ }
476
+ | { success: false; message: string; error: string }
477
+ > {
478
+ try {
479
+ const actorName = (params.actorName || 'Character').trim();
480
+ const ikBones = Array.isArray(params.ikBones)
481
+ ? params.ikBones.map((b) => String(b)).filter((b) => b.trim().length > 0)
482
+ : [];
483
+
484
+ const key = `IK:${actorName}`;
485
+ this.trackArtifact(key, {
486
+ type: 'IKSetup',
487
+ metadata: {
488
+ actorName,
489
+ ikBones,
490
+ enableFootPlacement: params.enableFootPlacement === true
491
+ }
492
+ });
493
+
494
+ return {
495
+ success: true,
496
+ message: `IK setup specification recorded for actor '${actorName}'`,
497
+ actorName,
498
+ ikBones: ikBones.length ? ikBones : undefined,
499
+ enableFootPlacement: params.enableFootPlacement === true ? true : undefined
500
+ };
501
+ } catch (err) {
502
+ const error = String(err);
503
+ return {
504
+ success: false,
505
+ message: `Failed to record IK setup: ${error}`,
506
+ error
507
+ };
508
+ }
509
+ }
510
+
511
+ async createProceduralAnim(params: {
512
+ systemName?: string;
513
+ baseAnimation?: string;
514
+ modifiers?: any[];
515
+ savePath?: string;
516
+ }): Promise<
517
+ | {
518
+ success: true;
519
+ message: string;
520
+ path: string;
521
+ systemName: string;
522
+ }
523
+ | { success: false; message: string; error: string }
524
+ > {
525
+ try {
526
+ const baseName = (params.systemName || '').trim()
527
+ || (params.baseAnimation ? params.baseAnimation.split('/').pop() || '' : '');
528
+ const systemName = baseName || 'ProceduralSystem';
529
+ const basePath = (params.savePath || '/Game/Animations').replace(/\/+$/, '');
530
+ const path = `${basePath || '/Game/Animations'}/${systemName}`;
531
+
532
+ this.trackArtifact(systemName, {
533
+ path,
534
+ type: 'ProceduralAnimation',
535
+ metadata: {
536
+ baseAnimation: params.baseAnimation,
537
+ modifiers: Array.isArray(params.modifiers) ? params.modifiers : []
538
+ }
539
+ });
540
+
541
+ return {
542
+ success: true,
543
+ message: `Procedural animation system '${systemName}' specification recorded at ${path}`,
544
+ path,
545
+ systemName
546
+ };
547
+ } catch (err) {
548
+ const error = String(err);
549
+ return {
550
+ success: false,
551
+ message: `Failed to record procedural animation system: ${error}`,
552
+ error
553
+ };
554
+ }
555
+ }
556
+
557
+ async createBlendTree(params: {
558
+ treeName?: string;
559
+ blendType?: string;
560
+ basePose?: string;
561
+ additiveAnimations?: any[];
562
+ savePath?: string;
563
+ }): Promise<
564
+ | {
565
+ success: true;
566
+ message: string;
567
+ path: string;
568
+ treeName: string;
569
+ }
570
+ | { success: false; message: string; error: string }
571
+ > {
572
+ try {
573
+ const rawName = (params.treeName || '').trim();
574
+ const treeName = rawName || 'BlendTree';
575
+ const basePath = (params.savePath || '/Game/Animations').replace(/\/+$/, '');
576
+ const path = `${basePath || '/Game/Animations'}/${treeName}`;
577
+
578
+ this.trackArtifact(treeName, {
579
+ path,
580
+ type: 'BlendTree',
581
+ metadata: {
582
+ blendType: params.blendType,
583
+ basePose: params.basePose,
584
+ additiveAnimations: Array.isArray(params.additiveAnimations) ? params.additiveAnimations : []
585
+ }
586
+ });
587
+
588
+ return {
589
+ success: true,
590
+ message: `Blend tree '${treeName}' specification recorded at ${path}`,
591
+ path,
592
+ treeName
593
+ };
594
+ } catch (err) {
595
+ const error = String(err);
596
+ return {
597
+ success: false,
598
+ message: `Failed to record blend tree specification: ${error}`,
599
+ error
600
+ };
601
+ }
602
+ }
603
+
604
+ async cleanup(artifacts?: string[]): Promise<
605
+ | {
606
+ success: boolean;
607
+ message: string;
608
+ removed?: string[];
609
+ missing?: string[];
610
+ }
611
+ | { success: false; message: string; error: string }
612
+ > {
613
+ try {
614
+ const pathsToDelete: string[] = [];
615
+
616
+ if (Array.isArray(artifacts) && artifacts.length > 0) {
617
+ pathsToDelete.push(...artifacts.map((a) => String(a).trim()).filter((a) => a.length > 0));
618
+ } else {
619
+ // If no specific artifacts provided, clear all managed ones
620
+ for (const [key, val] of this.managedArtifacts.entries()) {
621
+ if (val.path) pathsToDelete.push(val.path);
622
+ else pathsToDelete.push(key);
623
+ }
624
+ }
625
+
626
+ if (pathsToDelete.length === 0) {
627
+ return {
628
+ success: true,
629
+ message: 'No artifacts to cleanup.'
630
+ };
631
+ }
632
+
633
+ let bridgeMessage = '';
634
+ if (this.automationBridge) {
635
+ try {
636
+ const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
637
+ action: 'cleanup',
638
+ artifacts: pathsToDelete
639
+ });
640
+
641
+ if (!response.success) {
642
+ bridgeMessage = ` (Engine cleanup failed: ${response.message})`;
643
+ } else {
644
+ bridgeMessage = ' (Engine assets deleted)';
244
645
  }
646
+ } catch (e) {
647
+ bridgeMessage = ` (Engine connection failed: ${e})`;
648
+ }
649
+ } else {
650
+ bridgeMessage = ' (No automation bridge available)';
651
+ }
652
+
653
+ const removed: string[] = [];
654
+
655
+ // Clear local registry
656
+ const toRemoveKeys: string[] = [];
657
+ for (const [key, val] of this.managedArtifacts.entries()) {
658
+ if (pathsToDelete.includes(key) || (val.path && pathsToDelete.includes(val.path))) {
659
+ toRemoveKeys.push(key);
660
+ }
661
+ }
662
+
663
+ for (const key of toRemoveKeys) {
664
+ this.managedArtifacts.delete(key);
665
+ removed.push(key);
666
+ }
667
+
668
+ // Add any explicit paths that were not in managed artifacts but requested
669
+ for (const path of pathsToDelete) {
670
+ if (!removed.includes(path)) {
671
+ // We don't have it locally, but we tried to delete it from engine
672
+ removed.push(path);
245
673
  }
246
674
  }
247
675
 
248
- await this.bridge.executeConsoleCommands(commands);
249
676
  return {
250
677
  success: true,
251
- message: `Control Rig ${assetName} created`,
252
- path: fullPath
678
+ message: `Cleanup attempt processed for ${pathsToDelete.length} artifacts${bridgeMessage}`,
679
+ removed
253
680
  };
254
681
  } catch (err) {
255
- return { success: false, error: `Failed to setup control rig: ${err}` };
682
+ const error = String(err);
683
+ return {
684
+ success: false,
685
+ message: `Failed to cleanup animation artifacts: ${error}`,
686
+ error
687
+ };
688
+ }
689
+ }
690
+
691
+ async createAnimationAsset(params: {
692
+ name: string;
693
+ path?: string;
694
+ savePath?: string;
695
+ skeletonPath?: string;
696
+ assetType?: string;
697
+ }): Promise<
698
+ | {
699
+ success: true;
700
+ message: string;
701
+ path: string;
702
+ assetType?: string;
703
+ existingAsset?: boolean;
704
+ }
705
+ | { success: false; message: string; error: string }
706
+ > {
707
+ try {
708
+ const targetPath = (params.path || params.savePath) ?? '/Game/Animations';
709
+ const validation = validateAssetParams({ name: params.name, savePath: targetPath });
710
+ if (!validation.valid) {
711
+ const message = validation.error ?? 'Invalid asset parameters';
712
+ return { success: false, message, error: message };
713
+ }
714
+
715
+ const sanitized = validation.sanitized;
716
+ const assetName = sanitized.name;
717
+ const assetPath = sanitized.savePath ?? targetPath;
718
+ const fullPath = `${assetPath}/${assetName}`;
719
+
720
+ const normalizedType = (params.assetType || 'sequence').toLowerCase();
721
+
722
+ if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
723
+ try {
724
+ const payload: any = {
725
+ action: 'create_animation_asset',
726
+ name: assetName,
727
+ savePath: assetPath,
728
+ skeletonPath: params.skeletonPath,
729
+ assetType: normalizedType
730
+ };
731
+
732
+ const resp = await this.automationBridge.sendAutomationRequest('animation_physics', cleanObject(payload), { timeoutMs: 60000 });
733
+ const result = resp?.result ?? resp;
734
+ const resultObj = result && typeof result === 'object' ? (result as Record<string, unknown>) : undefined;
735
+ const isSuccess = resp && resp.success !== false && !!resultObj;
736
+
737
+ if (isSuccess && resultObj) {
738
+ const assetPathResult = typeof resultObj.assetPath === 'string' ? (resultObj.assetPath as string) : fullPath;
739
+ const assetTypeResult = typeof resultObj.assetType === 'string' ? (resultObj.assetType as string) : undefined;
740
+ const existingAsset = typeof resultObj.existingAsset === 'boolean' ? Boolean(resultObj.existingAsset) : false;
741
+
742
+ this.trackArtifact(assetName, { path: assetPathResult, type: 'AnimationAsset' });
743
+
744
+ return {
745
+ success: true,
746
+ message: resp.message || `Animation asset created at ${assetPathResult}`,
747
+ path: assetPathResult,
748
+ assetType: assetTypeResult,
749
+ existingAsset
750
+ };
751
+ }
752
+
753
+ const message = typeof resp?.message === 'string'
754
+ ? resp.message
755
+ : (typeof resp?.error === 'string' ? resp.error : 'Animation asset creation failed');
756
+ const error = typeof resp?.error === 'string' ? resp.error : message;
757
+
758
+ return { success: false, message, error };
759
+ } catch (err) {
760
+ const error = String(err);
761
+ return {
762
+ success: false,
763
+ message: `Failed to create animation asset: ${error}`,
764
+ error
765
+ };
766
+ }
767
+ }
768
+
769
+ return {
770
+ success: false,
771
+ message: 'Automation bridge not connected for createAnimationAsset',
772
+ error: 'AUTOMATION_BRIDGE_UNAVAILABLE'
773
+ };
774
+ } catch (err) {
775
+ const error = String(err);
776
+ return {
777
+ success: false,
778
+ message: `Failed to create animation asset: ${error}`,
779
+ error
780
+ };
781
+ }
782
+ }
783
+
784
+ async addNotify(params: {
785
+ animationPath?: string;
786
+ assetPath?: string;
787
+ notifyName?: string;
788
+ time?: number;
789
+ }): Promise<
790
+ | {
791
+ success: true;
792
+ message: string;
793
+ assetPath: string;
794
+ notifyName: string;
795
+ time: number;
796
+ }
797
+ | { success: false; message: string; error: string }
798
+ > {
799
+ try {
800
+ const rawPath = (params.animationPath || params.assetPath || '').trim();
801
+ if (!rawPath) {
802
+ const error = 'animationPath or assetPath is required for addNotify';
803
+ return { success: false, message: error, error };
804
+ }
805
+
806
+ const notifyName = (params.notifyName || 'Notify').trim();
807
+ const time = typeof params.time === 'number' && params.time >= 0 ? params.time : 0;
808
+
809
+ if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
810
+ try {
811
+ const resp = await this.automationBridge.sendAutomationRequest(
812
+ 'animation_physics',
813
+ cleanObject({
814
+ action: 'add_notify',
815
+ assetPath: rawPath,
816
+ notifyName,
817
+ time
818
+ }),
819
+ { timeoutMs: 60000 }
820
+ );
821
+
822
+ const result = resp?.result ?? resp;
823
+ const resultObj = result && typeof result === 'object' ? (result as Record<string, unknown>) : undefined;
824
+
825
+ if (resp && resp.success !== false && resultObj) {
826
+ return {
827
+ success: true,
828
+ message: resp.message || `Notify '${notifyName}' added to ${rawPath} at time ${time}`,
829
+ assetPath: typeof resultObj.assetPath === 'string' ? (resultObj.assetPath as string) : rawPath,
830
+ notifyName,
831
+ time
832
+ };
833
+ }
834
+ } catch (err) {
835
+ const error = String(err);
836
+ return {
837
+ success: false,
838
+ message: `Failed to add notify: ${error}`,
839
+ error
840
+ };
841
+ }
842
+ }
843
+
844
+ return {
845
+ success: false,
846
+ message: 'Automation bridge not connected for addNotify',
847
+ error: 'AUTOMATION_BRIDGE_UNAVAILABLE'
848
+ };
849
+ } catch (err) {
850
+ const error = String(err);
851
+ return {
852
+ success: false,
853
+ message: `Failed to add notify: ${error}`,
854
+ error
855
+ };
256
856
  }
257
857
  }
258
858
 
@@ -318,413 +918,23 @@ export class AnimationTools {
318
918
  blendOutTime?: number;
319
919
  }): Promise<PlayAnimationSuccess | PlayAnimationFailure> {
320
920
  try {
321
- const script = this.buildPlayAnimationScript({
322
- actorName: params.actorName,
323
- animationType: params.animationType,
324
- animationPath: params.animationPath,
325
- playRate: params.playRate ?? 1.0,
326
- loop: params.loop ?? false,
327
- blendInTime: params.blendInTime ?? 0.25,
328
- blendOutTime: params.blendOutTime ?? 0.25
329
- });
330
-
331
- const response = await this.bridge.executePython(script);
332
- const interpreted = interpretStandardResult(response, {
333
- successMessage: `Animation ${params.animationType} triggered on ${params.actorName}`,
334
- failureMessage: `Failed to play animation on ${params.actorName}`
335
- });
336
-
337
- const payload = interpreted.payload ?? {};
338
- const warnings = interpreted.warnings ?? coerceStringArray((payload as any).warnings) ?? undefined;
339
- const details = interpreted.details ?? coerceStringArray((payload as any).details) ?? undefined;
340
- const availableActors = coerceStringArray((payload as any).availableActors);
341
- const actorName = coerceString((payload as any).actorName) ?? params.actorName;
342
- const animationType = coerceString((payload as any).animationType) ?? params.animationType;
343
- const assetPath = coerceString((payload as any).assetPath) ?? params.animationPath;
344
- const errorMessage = coerceString((payload as any).error) ?? interpreted.error ?? `Animation playback failed for ${params.actorName}`;
345
-
346
- if (interpreted.success) {
347
- const result: PlayAnimationSuccess = {
348
- success: true,
349
- message: interpreted.message
350
- };
351
-
352
- if (warnings && warnings.length > 0) {
353
- result.warnings = warnings;
354
- }
355
- if (details && details.length > 0) {
356
- result.details = details;
357
- }
358
- if (actorName) {
359
- result.actorName = actorName;
360
- }
361
- if (animationType) {
362
- result.animationType = animationType;
363
- }
364
- if (assetPath) {
365
- result.assetPath = assetPath;
366
- }
921
+ const commands: string[] = [
922
+ `PlayAnimation ${params.actorName} ${params.animationType} ${params.animationPath} ${params.playRate ?? 1.0} ${params.loop ?? false} ${params.blendInTime ?? 0.25} ${params.blendOutTime ?? 0.25}`
923
+ ];
367
924
 
368
- return result;
369
- }
925
+ await this.bridge.executeConsoleCommands(commands);
370
926
 
371
- const failure: PlayAnimationFailure = {
372
- success: false,
373
- message: `Failed to play animation: ${errorMessage}`,
374
- error: errorMessage
927
+ return {
928
+ success: true,
929
+ message: `Animation ${params.animationType} triggered on ${params.actorName}`,
930
+ actorName: params.actorName,
931
+ animationType: params.animationType,
932
+ assetPath: params.animationPath
375
933
  };
376
-
377
- if (warnings && warnings.length > 0) {
378
- failure.warnings = warnings;
379
- }
380
- if (details && details.length > 0) {
381
- failure.details = details;
382
- }
383
- if (availableActors && availableActors.length > 0) {
384
- failure.availableActors = availableActors;
385
- }
386
- if (actorName) {
387
- failure.actorName = actorName;
388
- }
389
- if (animationType) {
390
- failure.animationType = animationType;
391
- }
392
- if (assetPath) {
393
- failure.assetPath = assetPath;
394
- }
395
-
396
- return failure;
397
934
  } catch (err) {
398
935
  const error = `Failed to play animation: ${err}`;
399
936
  return { success: false, message: error, error: String(err) };
400
937
  }
401
938
  }
402
939
 
403
- private buildCreateAnimationBlueprintScript(args: {
404
- name: string;
405
- path: string;
406
- skeletonPath: string;
407
- }): string {
408
- const payload = JSON.stringify(args);
409
- return `
410
- import unreal
411
- import json
412
- import traceback
413
-
414
- params = json.loads(${JSON.stringify(payload)})
415
-
416
- result = {
417
- "success": False,
418
- "message": "",
419
- "error": "",
420
- "warnings": [],
421
- "details": [],
422
- "exists": False,
423
- "skeleton": params.get("skeletonPath") or ""
424
- }
425
-
426
- try:
427
- asset_path = (params.get("path") or "/Game").rstrip('/')
428
- asset_name = params.get("name") or ""
429
- full_path = f"{asset_path}/{asset_name}"
430
- result["path"] = full_path
431
-
432
- editor_lib = unreal.EditorAssetLibrary
433
- asset_subsystem = None
434
- try:
435
- asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
436
- except Exception:
437
- asset_subsystem = None
438
-
439
- skeleton_path = params.get("skeletonPath")
440
- skeleton_asset = None
441
- if skeleton_path:
442
- if editor_lib.does_asset_exist(skeleton_path):
443
- skeleton_asset = editor_lib.load_asset(skeleton_path)
444
- if skeleton_asset and isinstance(skeleton_asset, unreal.Skeleton):
445
- result["details"].append(f"Using skeleton: {skeleton_path}")
446
- result["skeleton"] = skeleton_path
447
- else:
448
- result["error"] = f"Skeleton asset invalid at {skeleton_path}"
449
- result["warnings"].append(result["error"])
450
- skeleton_asset = None
451
- else:
452
- result["error"] = f"Skeleton not found at {skeleton_path}"
453
- result["warnings"].append(result["error"])
454
-
455
- if not skeleton_asset:
456
- raise RuntimeError(result["error"] or f"Skeleton {skeleton_path} unavailable")
457
-
458
- does_exist = False
459
- try:
460
- if asset_subsystem and hasattr(asset_subsystem, 'does_asset_exist'):
461
- does_exist = asset_subsystem.does_asset_exist(full_path)
462
- else:
463
- does_exist = editor_lib.does_asset_exist(full_path)
464
- except Exception:
465
- does_exist = editor_lib.does_asset_exist(full_path)
466
-
467
- if does_exist:
468
- result["exists"] = True
469
- loaded = editor_lib.load_asset(full_path)
470
- if loaded:
471
- result["success"] = True
472
- result["message"] = f"Animation Blueprint already exists at {full_path}"
473
- result["details"].append(result["message"])
474
- else:
475
- result["error"] = f"Asset exists but could not be loaded: {full_path}"
476
- result["warnings"].append(result["error"])
477
- else:
478
- factory = unreal.AnimBlueprintFactory()
479
- if skeleton_asset:
480
- try:
481
- factory.target_skeleton = skeleton_asset
482
- except Exception as assign_error:
483
- result["warnings"].append(f"Unable to assign skeleton {skeleton_path}: {assign_error}")
484
-
485
- asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
486
- created = asset_tools.create_asset(
487
- asset_name=asset_name,
488
- package_path=asset_path,
489
- asset_class=unreal.AnimBlueprint,
490
- factory=factory
491
- )
492
-
493
- if created:
494
- editor_lib.save_asset(full_path, only_if_is_dirty=False)
495
- result["success"] = True
496
- result["message"] = f"Animation Blueprint created at {full_path}"
497
- result["details"].append(result["message"])
498
- else:
499
- result["error"] = f"Failed to create Animation Blueprint {asset_name}"
500
-
501
- except Exception as exc:
502
- result["error"] = str(exc)
503
- result["warnings"].append(result["error"])
504
- tb = traceback.format_exc()
505
- if tb:
506
- result.setdefault("details", []).append(tb)
507
-
508
- if result["success"] and not result.get("message"):
509
- result["message"] = f"Animation Blueprint created at {result.get('path')}"
510
-
511
- if not result["success"] and not result.get("error"):
512
- result["error"] = "Animation Blueprint creation failed"
513
-
514
- if not result.get("warnings"):
515
- result.pop("warnings", None)
516
- if not result.get("details"):
517
- result.pop("details", None)
518
- if not result.get("error"):
519
- result.pop("error", None)
520
-
521
- print('RESULT:' + json.dumps(result))
522
- `.trim();
523
- }
524
-
525
- private parseAnimationBlueprintResponse(
526
- response: unknown,
527
- assetName: string,
528
- assetPath: string
529
- ): CreateAnimationBlueprintSuccess | CreateAnimationBlueprintFailure {
530
- const interpreted = interpretStandardResult(response, {
531
- successMessage: `Animation Blueprint ${assetName} created`,
532
- failureMessage: `Failed to create Animation Blueprint ${assetName}`
533
- });
534
-
535
- const payload = interpreted.payload ?? {};
536
- const path = coerceString((payload as any).path) ?? `${assetPath}/${assetName}`;
537
- const exists = coerceBoolean((payload as any).exists);
538
- const skeleton = coerceString((payload as any).skeleton);
539
- const warnings = interpreted.warnings ?? coerceStringArray((payload as any).warnings) ?? undefined;
540
- const details = interpreted.details ?? coerceStringArray((payload as any).details) ?? undefined;
541
-
542
- if (interpreted.success) {
543
- const result: CreateAnimationBlueprintSuccess = {
544
- success: true,
545
- message: interpreted.message,
546
- path
547
- };
548
-
549
- if (typeof exists === 'boolean') {
550
- result.exists = exists;
551
- }
552
- if (skeleton) {
553
- result.skeleton = skeleton;
554
- }
555
- if (warnings && warnings.length > 0) {
556
- result.warnings = warnings;
557
- }
558
- if (details && details.length > 0) {
559
- result.details = details;
560
- }
561
-
562
- return result;
563
- }
564
-
565
- const errorMessage = coerceString((payload as any).error) ?? interpreted.error ?? interpreted.message;
566
-
567
- const failure: CreateAnimationBlueprintFailure = {
568
- success: false,
569
- message: `Failed to create Animation Blueprint: ${errorMessage}`,
570
- error: errorMessage,
571
- path
572
- };
573
-
574
- if (typeof exists === 'boolean') {
575
- failure.exists = exists;
576
- }
577
- if (skeleton) {
578
- failure.skeleton = skeleton;
579
- }
580
- if (warnings && warnings.length > 0) {
581
- failure.warnings = warnings;
582
- }
583
- if (details && details.length > 0) {
584
- failure.details = details;
585
- }
586
-
587
- return failure;
588
- }
589
-
590
- private buildPlayAnimationScript(args: {
591
- actorName: string;
592
- animationType: string;
593
- animationPath: string;
594
- playRate: number;
595
- loop: boolean;
596
- blendInTime: number;
597
- blendOutTime: number;
598
- }): string {
599
- const payload = JSON.stringify(args);
600
- return `
601
- import unreal
602
- import json
603
- import traceback
604
-
605
- params = json.loads(${JSON.stringify(payload)})
606
-
607
- result = {
608
- "success": False,
609
- "message": "",
610
- "error": "",
611
- "warnings": [],
612
- "details": [],
613
- "actorName": params.get("actorName"),
614
- "animationType": params.get("animationType"),
615
- "assetPath": params.get("animationPath"),
616
- "availableActors": []
617
- }
618
-
619
- try:
620
- actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
621
- actors = actor_subsystem.get_all_level_actors() if actor_subsystem else []
622
- target = None
623
- search = params.get("actorName") or ""
624
- search_lower = search.lower()
625
-
626
- for actor in actors:
627
- if not actor:
628
- continue
629
- name = (actor.get_name() or "").lower()
630
- label = (actor.get_actor_label() or "").lower()
631
- if search_lower and (search_lower == name or search_lower == label or search_lower in label):
632
- target = actor
633
- break
634
-
635
- if not target:
636
- result["error"] = f"Actor not found: {search}"
637
- result["warnings"].append("Actor search yielded no results")
638
- suggestions = []
639
- for actor in actors[:20]:
640
- try:
641
- suggestions.append(actor.get_actor_label())
642
- except Exception:
643
- continue
644
- if suggestions:
645
- result["availableActors"] = suggestions
646
- else:
647
- try:
648
- display_name = target.get_actor_label() or target.get_name()
649
- if display_name:
650
- result["actorName"] = display_name
651
- except Exception:
652
- pass
653
-
654
- skeletal_component = target.get_component_by_class(unreal.SkeletalMeshComponent)
655
- if not skeletal_component:
656
- try:
657
- skeletal_component = target.get_editor_property('mesh')
658
- except Exception:
659
- skeletal_component = None
660
-
661
- if not skeletal_component:
662
- result["error"] = "No SkeletalMeshComponent found on actor"
663
- result["warnings"].append("Actor lacks SkeletalMeshComponent")
664
- else:
665
- asset_path = params.get("animationPath")
666
- if not asset_path or not unreal.EditorAssetLibrary.does_asset_exist(asset_path):
667
- result["error"] = f"Animation asset not found: {asset_path}"
668
- result["warnings"].append("Animation asset missing")
669
- else:
670
- asset = unreal.EditorAssetLibrary.load_asset(asset_path)
671
- anim_type = params.get("animationType") or ""
672
- if anim_type == 'Montage':
673
- anim_instance = skeletal_component.get_anim_instance()
674
- if anim_instance:
675
- try:
676
- anim_instance.montage_play(asset, params.get("playRate", 1.0))
677
- result["success"] = True
678
- result["message"] = f"Montage playing on {result.get('actorName') or search}"
679
- result["details"].append(result["message"])
680
- except Exception as play_error:
681
- result["error"] = f"Failed to play montage: {play_error}"
682
- result["warnings"].append(result["error"])
683
- else:
684
- result["error"] = "AnimInstance not found on SkeletalMeshComponent"
685
- result["warnings"].append(result["error"])
686
- elif anim_type == 'Sequence':
687
- try:
688
- skeletal_component.play_animation(asset, bool(params.get("loop")))
689
- try:
690
- anim_instance = skeletal_component.get_anim_instance()
691
- if anim_instance:
692
- anim_instance.set_play_rate(params.get("playRate", 1.0))
693
- except Exception:
694
- pass
695
- result["success"] = True
696
- result["message"] = f"Sequence playing on {result.get('actorName') or search}"
697
- result["details"].append(result["message"])
698
- except Exception as play_error:
699
- result["error"] = f"Failed to play sequence: {play_error}"
700
- result["warnings"].append(result["error"])
701
- else:
702
- result["error"] = "BlendSpace playback requires Animation Blueprint support"
703
- result["warnings"].append("Unsupported animation type for direct play")
704
-
705
- except Exception as exc:
706
- result["error"] = str(exc)
707
- result["warnings"].append(result["error"])
708
- tb = traceback.format_exc()
709
- if tb:
710
- result["details"].append(tb)
711
-
712
- if result["success"] and not result.get("message"):
713
- result["message"] = f"Animation {result.get('animationType')} triggered on {result.get('actorName') or params.get('actorName')}"
714
-
715
- if not result["success"] and not result.get("error"):
716
- result["error"] = "Animation playback failed"
717
-
718
- if not result.get("warnings"):
719
- result.pop("warnings", None)
720
- if not result.get("details"):
721
- result.pop("details", None)
722
- if not result.get("availableActors"):
723
- result.pop("availableActors", None)
724
- if not result.get("error"):
725
- result.pop("error", None)
726
-
727
- print('RESULT:' + json.dumps(result))
728
- `.trim();
729
- }
730
940
  }