unreal-engine-mcp-server 0.4.6 → 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 +269 -22
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -72
  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 -604
  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 +5475 -1627
  97. package/dist/tools/consolidated-tool-definitions.js +829 -482
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1009
  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 +45 -0
  161. package/dist/tools/logs.js +210 -0
  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 +195 -11
  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 -649
  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 -500
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1122
  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 +219 -0
  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 +250 -13
  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 -572
  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,174 +1,19 @@
1
1
  // Audio tools for Unreal Engine
2
- import JSON5 from 'json5';
3
2
  import { UnrealBridge } from '../unreal-bridge.js';
4
- import { escapePythonString } from '../utils/python.js';
3
+ import { AutomationBridge } from '../automation/index.js';
5
4
 
6
5
  export class AudioTools {
7
- constructor(private bridge: UnrealBridge) {}
8
-
9
- private interpretResult(
10
- resp: unknown,
11
- defaults: { successMessage: string; failureMessage: string }
12
- ): { success: true; message: string; details?: string } | { success: false; error: string; details?: string } {
13
- const normalizePayload = (
14
- payload: Record<string, unknown>
15
- ): { success: true; message: string; details?: string } | { success: false; error: string; details?: string } => {
16
- const warningsValue = payload?.warnings;
17
- const warningsText = Array.isArray(warningsValue)
18
- ? (warningsValue.length > 0 ? warningsValue.join('; ') : undefined)
19
- : typeof warningsValue === 'string' && warningsValue.trim() !== ''
20
- ? warningsValue
21
- : undefined;
22
-
23
- if (payload?.success === true) {
24
- const message = typeof payload?.message === 'string' && payload.message.trim() !== ''
25
- ? payload.message
26
- : defaults.successMessage;
27
- return {
28
- success: true as const,
29
- message,
30
- details: warningsText
31
- };
32
- }
6
+ constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
33
7
 
34
- const error =
35
- (typeof payload?.error === 'string' && payload.error.trim() !== ''
36
- ? payload.error
37
- : undefined)
38
- ?? (typeof payload?.message === 'string' && payload.message.trim() !== ''
39
- ? payload.message
40
- : undefined)
41
- ?? defaults.failureMessage;
8
+ setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
42
9
 
43
- return {
44
- success: false as const,
45
- error,
46
- details: warningsText
47
- };
10
+ private validateAudioParams(volume?: number, pitch?: number) {
11
+ const v = volume ?? 1.0;
12
+ const p = pitch ?? 1.0;
13
+ return {
14
+ volume: Math.max(0.0, Math.min(v, 4.0)), // Clamp volume 0-4 (standard UE range support)
15
+ pitch: Math.max(0.01, Math.min(p, 4.0)) // Clamp pitch 0.01-4
48
16
  };
49
-
50
- if (resp && typeof resp === 'object') {
51
- const payload = resp as Record<string, unknown>;
52
- if ('success' in payload || 'error' in payload || 'message' in payload || 'warnings' in payload) {
53
- return normalizePayload(payload);
54
- }
55
- }
56
-
57
- const raw = typeof resp === 'string' ? resp : JSON.stringify(resp);
58
-
59
- const extractJson = (input: string): string | undefined => {
60
- const marker = 'RESULT:';
61
- const markerIndex = input.lastIndexOf(marker);
62
- if (markerIndex === -1) {
63
- return undefined;
64
- }
65
-
66
- const afterMarker = input.slice(markerIndex + marker.length);
67
- const firstBraceIndex = afterMarker.indexOf('{');
68
- if (firstBraceIndex === -1) {
69
- return undefined;
70
- }
71
-
72
- let depth = 0;
73
- let inString = false;
74
- let escapeNext = false;
75
-
76
- for (let i = firstBraceIndex; i < afterMarker.length; i++) {
77
- const char = afterMarker[i];
78
-
79
- if (inString) {
80
- if (escapeNext) {
81
- escapeNext = false;
82
- continue;
83
- }
84
- if (char === '\\') {
85
- escapeNext = true;
86
- continue;
87
- }
88
- if (char === '"') {
89
- inString = false;
90
- }
91
- continue;
92
- }
93
-
94
- if (char === '"') {
95
- inString = true;
96
- continue;
97
- }
98
-
99
- if (char === '{') {
100
- depth += 1;
101
- } else if (char === '}') {
102
- depth -= 1;
103
- if (depth === 0) {
104
- return afterMarker.slice(firstBraceIndex, i + 1);
105
- }
106
- }
107
- }
108
-
109
- const fallbackMatch = /\{[\s\S]*\}/.exec(afterMarker);
110
- return fallbackMatch ? fallbackMatch[0] : undefined;
111
- };
112
-
113
- const jsonPayload = extractJson(raw);
114
-
115
- if (jsonPayload) {
116
- const parseAttempts: Array<{ label: string; parser: () => unknown }> = [
117
- {
118
- label: 'json',
119
- parser: () => JSON.parse(jsonPayload)
120
- },
121
- {
122
- label: 'json5',
123
- parser: () => JSON5.parse(jsonPayload)
124
- }
125
- ];
126
-
127
- const sanitizedForJson5 = jsonPayload
128
- .replace(/\bTrue\b/g, 'true')
129
- .replace(/\bFalse\b/g, 'false')
130
- .replace(/\bNone\b/g, 'null');
131
-
132
- if (sanitizedForJson5 !== jsonPayload) {
133
- parseAttempts.push({ label: 'json5-sanitized', parser: () => JSON5.parse(sanitizedForJson5) });
134
- }
135
-
136
- const parseErrors: string[] = [];
137
-
138
- for (const attempt of parseAttempts) {
139
- try {
140
- const parsed = attempt.parser();
141
- if (parsed && typeof parsed === 'object') {
142
- return normalizePayload(parsed as Record<string, unknown>);
143
- }
144
- } catch (err) {
145
- parseErrors.push(`${attempt.label}: ${err instanceof Error ? err.message : String(err)}`);
146
- }
147
- }
148
-
149
- const errorMatch = /["']error["']\s*:\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)')/i.exec(jsonPayload);
150
- const messageMatch = /["']message["']\s*:\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)')/i.exec(jsonPayload);
151
- const fallbackText = errorMatch?.[1] ?? errorMatch?.[2] ?? messageMatch?.[1] ?? messageMatch?.[2];
152
- const errorText = fallbackText && fallbackText.trim().length > 0
153
- ? fallbackText.trim()
154
- : `${defaults.failureMessage}: ${parseErrors[0] ?? 'Unable to parse RESULT payload'}`;
155
-
156
- const snippet = jsonPayload.length > 240 ? `${jsonPayload.slice(0, 240)}…` : jsonPayload;
157
- const detailsParts: string[] = [];
158
- if (parseErrors.length > 0) {
159
- detailsParts.push(`Parse attempts failed: ${parseErrors.join('; ')}`);
160
- }
161
- detailsParts.push(`Raw payload: ${snippet}`);
162
- const detailsText = detailsParts.join(' | ');
163
-
164
- return {
165
- success: false as const,
166
- error: errorText,
167
- details: detailsText
168
- };
169
- }
170
-
171
- return { success: false as const, error: defaults.failureMessage };
172
17
  }
173
18
 
174
19
  // Create sound cue
@@ -183,166 +28,37 @@ export class AudioTools {
183
28
  attenuationSettings?: string;
184
29
  };
185
30
  }) {
186
- const escapePyString = (value: string) => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
187
- const toPyNumber = (value?: number) =>
188
- value === undefined || value === null || !Number.isFinite(value) ? 'None' : String(value);
189
- const toPyBool = (value?: boolean) =>
190
- value === undefined || value === null ? 'None' : value ? 'True' : 'False';
191
-
192
- const path = params.savePath || '/Game/Audio/Cues';
193
- const wavePath = params.wavePath || '';
194
- const attenuationPath = params.settings?.attenuationSettings || '';
195
- const volumeLiteral = toPyNumber(params.settings?.volume);
196
- const pitchLiteral = toPyNumber(params.settings?.pitch);
197
- const loopingLiteral = toPyBool(params.settings?.looping);
198
-
199
- const py = `
200
- import unreal
201
- import json
202
-
203
- name = r"${escapePyString(params.name)}"
204
- package_path = r"${escapePyString(path)}"
205
- wave_path = r"${escapePyString(wavePath)}"
206
- attenuation_path = r"${escapePyString(attenuationPath)}"
207
- attach_wave = ${params.wavePath ? 'True' : 'False'}
208
- volume_override = ${volumeLiteral}
209
- pitch_override = ${pitchLiteral}
210
- looping_override = ${loopingLiteral}
211
-
212
- result = {
213
- "success": False,
214
- "message": "",
215
- "error": "",
216
- "warnings": []
217
- }
31
+ if (!this.automationBridge) {
32
+ throw new Error('Automation Bridge not available. Audio operations require plugin support.');
33
+ }
34
+
35
+ const path = params.savePath || '/Game/Audio/Cues';
36
+ const { volume, pitch } = this.validateAudioParams(params.settings?.volume, params.settings?.pitch);
218
37
 
219
- try:
220
- asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
221
- if not asset_tools:
222
- result["error"] = "AssetToolsHelpers unavailable"
223
- raise SystemExit(0)
224
-
225
- factory = None
226
- try:
227
- factory = unreal.SoundCueFactoryNew()
228
- except Exception:
229
- factory = None
230
-
231
- if not factory:
232
- result["error"] = "SoundCueFactoryNew unavailable"
233
- raise SystemExit(0)
234
-
235
- package_path = package_path.rstrip('/') if package_path else package_path
236
-
237
- asset = asset_tools.create_asset(
238
- asset_name=name,
239
- package_path=package_path,
240
- asset_class=unreal.SoundCue,
241
- factory=factory
242
- )
243
-
244
- if not asset:
245
- result["error"] = "Failed to create SoundCue"
246
- raise SystemExit(0)
247
-
248
- asset_subsystem = None
249
- try:
250
- asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
251
- except Exception:
252
- asset_subsystem = None
253
-
254
- editor_library = unreal.EditorAssetLibrary
255
-
256
- if attach_wave:
257
- wave_exists = False
258
- try:
259
- if asset_subsystem and hasattr(asset_subsystem, "does_asset_exist"):
260
- wave_exists = asset_subsystem.does_asset_exist(wave_path)
261
- else:
262
- wave_exists = editor_library.does_asset_exist(wave_path)
263
- except Exception as existence_error:
264
- result["warnings"].append(f"Wave lookup failed: {existence_error}")
265
-
266
- if not wave_exists:
267
- result["warnings"].append(f"Wave asset not found: {wave_path}")
268
- else:
269
- try:
270
- if asset_subsystem and hasattr(asset_subsystem, "load_asset"):
271
- wave_asset = asset_subsystem.load_asset(wave_path)
272
- else:
273
- wave_asset = editor_library.load_asset(wave_path)
274
- if wave_asset:
275
- # Hooking up cue nodes via Python is non-trivial; surface warning for manual setup
276
- result["warnings"].append("Sound cue created without automatic wave node hookup")
277
- except Exception as wave_error:
278
- result["warnings"].append(f"Failed to load wave asset: {wave_error}")
279
-
280
- if volume_override is not None and hasattr(asset, "volume_multiplier"):
281
- asset.volume_multiplier = volume_override
282
- if pitch_override is not None and hasattr(asset, "pitch_multiplier"):
283
- asset.pitch_multiplier = pitch_override
284
- if looping_override is not None and hasattr(asset, "b_looping"):
285
- asset.b_looping = looping_override
286
-
287
- if attenuation_path:
288
- try:
289
- attenuation_asset = editor_library.load_asset(attenuation_path)
290
- if attenuation_asset:
291
- applied = False
292
- if hasattr(asset, "set_attenuation_settings"):
293
- try:
294
- asset.set_attenuation_settings(attenuation_asset)
295
- applied = True
296
- except Exception:
297
- applied = False
298
- if not applied and hasattr(asset, "attenuation_settings"):
299
- asset.attenuation_settings = attenuation_asset
300
- applied = True
301
- if not applied:
302
- result["warnings"].append("Attenuation asset loaded but could not be applied automatically")
303
- except Exception as attenuation_error:
304
- result["warnings"].append(f"Failed to apply attenuation: {attenuation_error}")
305
-
306
- try:
307
- save_target = f"{package_path}/{name}" if package_path else name
308
- if asset_subsystem and hasattr(asset_subsystem, "save_asset"):
309
- asset_subsystem.save_asset(save_target)
310
- else:
311
- editor_library.save_asset(save_target)
312
- except Exception as save_error:
313
- result["warnings"].append(f"Save failed: {save_error}")
314
-
315
- result["success"] = True
316
- result["message"] = "Sound cue created"
317
-
318
- except SystemExit:
319
- pass
320
- except Exception as error:
321
- result["error"] = str(error)
322
-
323
- finally:
324
- payload = dict(result)
325
- if payload.get("success"):
326
- if not payload.get("message"):
327
- payload["message"] = "Sound cue created"
328
- payload.pop("error", None)
329
- else:
330
- if not payload.get("error"):
331
- payload["error"] = payload.get("message") or "Failed to create SoundCue"
332
- if not payload.get("message"):
333
- payload["message"] = payload["error"]
334
- if not payload.get("warnings"):
335
- payload.pop("warnings", None)
336
- print('RESULT:' + json.dumps(payload))
337
- `.trim();
338
38
  try {
339
- const resp = await this.bridge.executePython(py);
340
- return this.interpretResult(resp, {
341
- successMessage: 'Sound cue created',
342
- failureMessage: 'Failed to create SoundCue'
39
+ const response = await this.automationBridge.sendAutomationRequest('create_sound_cue', {
40
+ name: params.name,
41
+ packagePath: path,
42
+ wavePath: params.wavePath,
43
+ attenuationPath: params.settings?.attenuationSettings,
44
+ volume,
45
+ pitch,
46
+ looping: params.settings?.looping
47
+ }, {
48
+ timeoutMs: 60000
343
49
  });
344
- } catch (e) {
345
- return { success: false, error: `Failed to create sound cue: ${e}` };
50
+
51
+ if (response.success === false) {
52
+ return { success: false, error: response.error || response.message || 'Failed to create SoundCue' };
53
+ }
54
+
55
+ return {
56
+ success: true,
57
+ message: response.message || 'Sound cue created',
58
+ ...(response.result || {})
59
+ };
60
+ } catch (error) {
61
+ return { success: false, error: `Failed to create sound cue: ${error instanceof Error ? error.message : String(error)}` };
346
62
  }
347
63
  }
348
64
 
@@ -350,93 +66,45 @@ finally:
350
66
  async playSoundAtLocation(params: {
351
67
  soundPath: string;
352
68
  location: [number, number, number];
69
+ rotation?: [number, number, number];
353
70
  volume?: number;
354
71
  pitch?: number;
355
72
  startTime?: number;
73
+ attenuationPath?: string;
74
+ concurrencyPath?: string;
356
75
  }) {
357
- const volume = params.volume ?? 1.0;
358
- const pitch = params.pitch ?? 1.0;
359
- const startTime = params.startTime ?? 0.0;
360
- const soundPath = params.soundPath ?? '';
361
-
362
- const py = `
363
- import unreal
364
- import json
365
-
366
- result = {
367
- "success": False,
368
- "message": "",
369
- "error": "",
370
- "warnings": []
371
- }
76
+ if (!this.automationBridge) {
77
+ throw new Error('Automation Bridge not available. Audio operations require plugin support.');
78
+ }
79
+
80
+ const { volume, pitch } = this.validateAudioParams(params.volume, params.pitch);
372
81
 
373
- try:
374
- path = "${escapePythonString(soundPath)}"
375
- if not unreal.EditorAssetLibrary.does_asset_exist(path):
376
- result["error"] = "Sound asset not found"
377
- raise SystemExit(0)
378
-
379
- snd = unreal.EditorAssetLibrary.load_asset(path)
380
- if not snd:
381
- result["error"] = f"Failed to load sound asset: {path}"
382
- raise SystemExit(0)
383
-
384
- world = None
385
- try:
386
- world = unreal.EditorUtilityLibrary.get_editor_world()
387
- except Exception:
388
- world = None
389
-
390
- if not world:
391
- editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
392
- if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world'):
393
- world = editor_subsystem.get_editor_world()
394
-
395
- if not world:
396
- try:
397
- world = unreal.EditorSubsystemLibrary.get_editor_world()
398
- except Exception:
399
- world = None
400
-
401
- if not world:
402
- result["error"] = "Unable to resolve editor world. Start PIE and ensure Editor Scripting Utilities is enabled."
403
- raise SystemExit(0)
404
-
405
- loc = unreal.Vector(${params.location[0]}, ${params.location[1]}, ${params.location[2]})
406
- rot = unreal.Rotator(0.0, 0.0, 0.0)
407
- unreal.GameplayStatics.spawn_sound_at_location(world, snd, loc, rot, ${volume}, ${pitch}, ${startTime})
408
-
409
- result["success"] = True
410
- result["message"] = "Sound played"
411
-
412
- except SystemExit:
413
- pass
414
- except Exception as e:
415
- result["error"] = str(e)
416
- finally:
417
- payload = dict(result)
418
- if payload.get("success"):
419
- if not payload.get("message"):
420
- payload["message"] = "Sound played"
421
- payload.pop("error", None)
422
- else:
423
- if not payload.get("error"):
424
- payload["error"] = payload.get("message") or "Failed to play sound"
425
- if not payload.get("message"):
426
- payload["message"] = payload["error"]
427
- if not payload.get("warnings"):
428
- payload.pop("warnings", None)
429
- print('RESULT:' + json.dumps(payload))
430
- `.trim();
431
82
  try {
432
- const resp = await this.bridge.executePythonWithResult(py);
433
- return this.interpretResult(resp, {
434
- successMessage: 'Sound played',
435
- failureMessage: 'Failed to play sound'
83
+ const response = await this.automationBridge.sendAutomationRequest('play_sound_at_location', {
84
+ soundPath: params.soundPath,
85
+ location: params.location,
86
+ rotation: params.rotation ?? [0, 0, 0],
87
+ volume,
88
+ pitch,
89
+ startTime: params.startTime ?? 0.0,
90
+ attenuationPath: params.attenuationPath,
91
+ concurrencyPath: params.concurrencyPath
92
+ }, {
93
+ timeoutMs: 30000
436
94
  });
437
- } catch (e) {
438
- return { success: false, error: `Failed to play sound: ${e}` };
439
- }
95
+
96
+ if (response.success === false) {
97
+ return { success: false, error: response.error || response.message || 'Failed to play sound' };
98
+ }
99
+
100
+ return {
101
+ success: true,
102
+ message: response.message || 'Sound played',
103
+ ...(response.result || {})
104
+ };
105
+ } catch (error) {
106
+ return { success: false, error: `Failed to play sound: ${error instanceof Error ? error.message : String(error)}` };
107
+ }
440
108
  }
441
109
 
442
110
  // Play sound 2D
@@ -446,192 +114,103 @@ finally:
446
114
  pitch?: number;
447
115
  startTime?: number;
448
116
  }) {
449
- const volume = params.volume ?? 1.0;
450
- const pitch = params.pitch ?? 1.0;
451
- const startTime = params.startTime ?? 0.0;
452
- const soundPath = params.soundPath ?? '';
453
-
454
- const py = `
455
- import unreal
456
- import json
457
-
458
- result = {
459
- "success": False,
460
- "message": "",
461
- "error": "",
462
- "warnings": []
463
- }
117
+ if (!this.automationBridge) {
118
+ throw new Error('Automation Bridge not available. Audio operations require plugin support.');
119
+ }
464
120
 
465
- try:
466
- path = "${escapePythonString(soundPath)}"
467
- if not unreal.EditorAssetLibrary.does_asset_exist(path):
468
- result["error"] = "Sound asset not found"
469
- raise SystemExit(0)
470
-
471
- snd = unreal.EditorAssetLibrary.load_asset(path)
472
- if not snd:
473
- result["error"] = f"Failed to load sound asset: {path}"
474
- raise SystemExit(0)
475
-
476
- world = None
477
- try:
478
- world = unreal.EditorUtilityLibrary.get_editor_world()
479
- except Exception:
480
- world = None
481
-
482
- if not world:
483
- editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
484
- if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world'):
485
- world = editor_subsystem.get_editor_world()
486
-
487
- if not world:
488
- try:
489
- world = unreal.EditorSubsystemLibrary.get_editor_world()
490
- except Exception:
491
- world = None
492
-
493
- if not world:
494
- result["error"] = "Unable to resolve editor world. Start PIE and ensure Editor Scripting Utilities is enabled."
495
- raise SystemExit(0)
496
-
497
- ok = False
498
- try:
499
- unreal.GameplayStatics.spawn_sound_2d(world, snd, ${volume}, ${pitch}, ${startTime})
500
- ok = True
501
- except AttributeError:
502
- try:
503
- unreal.GameplayStatics.play_sound_2d(world, snd, ${volume}, ${pitch}, ${startTime})
504
- ok = True
505
- except AttributeError:
506
- pass
507
-
508
- if not ok:
509
- cam_loc = unreal.Vector(0.0, 0.0, 0.0)
510
- try:
511
- editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
512
- if editor_subsystem and hasattr(editor_subsystem, 'get_level_viewport_camera_info'):
513
- info = editor_subsystem.get_level_viewport_camera_info()
514
- if isinstance(info, (list, tuple)) and len(info) > 0:
515
- cam_loc = info[0]
516
- except Exception:
517
- try:
518
- controller = world.get_first_player_controller()
519
- if controller:
520
- pawn = controller.get_pawn()
521
- if pawn:
522
- cam_loc = pawn.get_actor_location()
523
- except Exception:
524
- pass
525
-
526
- try:
527
- rot = unreal.Rotator(0.0, 0.0, 0.0)
528
- unreal.GameplayStatics.spawn_sound_at_location(world, snd, cam_loc, rot, ${volume}, ${pitch}, ${startTime})
529
- ok = True
530
- result["warnings"].append("Fell back to 3D playback at camera location")
531
- except Exception as location_error:
532
- result["warnings"].append(f"Failed fallback playback: {location_error}")
533
-
534
- if not ok:
535
- result["error"] = "Failed to play sound in 2D or fallback configuration"
536
- raise SystemExit(0)
537
-
538
- result["success"] = True
539
- result["message"] = "Sound2D played"
540
-
541
- except SystemExit:
542
- pass
543
- except Exception as e:
544
- result["error"] = str(e)
545
- finally:
546
- payload = dict(result)
547
- if payload.get("success"):
548
- if not payload.get("message"):
549
- payload["message"] = "Sound2D played"
550
- payload.pop("error", None)
551
- else:
552
- if not payload.get("error"):
553
- payload["error"] = payload.get("message") or "Failed to play sound2D"
554
- if not payload.get("message"):
555
- payload["message"] = payload["error"]
556
- if not payload.get("warnings"):
557
- payload.pop("warnings", None)
558
- print('RESULT:' + json.dumps(payload))
559
- `.trim();
560
121
  try {
561
- const resp = await this.bridge.executePythonWithResult(py);
562
- return this.interpretResult(resp, {
563
- successMessage: 'Sound2D played',
564
- failureMessage: 'Failed to play sound2D'
122
+ const response = await this.automationBridge.sendAutomationRequest('play_sound_2d', {
123
+ soundPath: params.soundPath,
124
+ volume: params.volume ?? 1.0,
125
+ pitch: params.pitch ?? 1.0,
126
+ startTime: params.startTime ?? 0.0
127
+ }, {
128
+ timeoutMs: 30000
565
129
  });
566
- } catch (e) {
567
- return { success: false, error: `Failed to play sound2D: ${e}` };
130
+
131
+ if (response.success === false) {
132
+ return { success: false, error: response.error || response.message || 'Failed to play 2D sound' };
133
+ }
134
+
135
+ return {
136
+ success: true,
137
+ message: response.message || '2D sound played',
138
+ ...(response.result || {})
139
+ };
140
+ } catch (error) {
141
+ return { success: false, error: `Failed to play 2D sound: ${error instanceof Error ? error.message : String(error)}` };
142
+ }
568
143
  }
144
+
145
+ // Convenience wrapper used by system_control: best-effort 2D sound playback
146
+ async playSound(soundPath: string, volume?: number, pitch?: number) {
147
+ return this.playSound2D({
148
+ soundPath,
149
+ volume,
150
+ pitch
151
+ });
569
152
  }
570
153
 
571
- // Create audio component
572
- async createAudioComponent(params: {
154
+ // Create audio component (requires C++ plugin)
155
+ async createAudioComponent(_params: {
573
156
  actorName: string;
574
157
  componentName: string;
575
158
  soundPath: string;
576
159
  autoPlay?: boolean;
577
160
  is3D?: boolean;
578
161
  }) {
579
- const commands = [];
580
-
581
- commands.push(`AddAudioComponent ${params.actorName} ${params.componentName} ${params.soundPath}`);
582
-
583
- if (params.autoPlay !== undefined) {
584
- commands.push(`SetAudioComponentAutoPlay ${params.actorName}.${params.componentName} ${params.autoPlay}`);
162
+ if (!this.automationBridge) {
163
+ throw new Error('Automation Bridge not available. Creating audio components requires plugin support.');
585
164
  }
586
-
587
- if (params.is3D !== undefined) {
588
- commands.push(`SetAudioComponent3D ${params.actorName}.${params.componentName} ${params.is3D}`);
589
- }
590
-
591
- for (const cmd of commands) {
592
- await this.bridge.executeConsoleCommand(cmd);
165
+
166
+ try {
167
+ const response = await this.automationBridge.sendAutomationRequest('create_audio_component', {
168
+ actorName: _params.actorName,
169
+ componentName: _params.componentName,
170
+ soundPath: _params.soundPath,
171
+ autoPlay: _params.autoPlay ?? false,
172
+ is3D: _params.is3D ?? true
173
+ });
174
+
175
+ return response.success
176
+ ? { success: true, message: response.message || 'Audio component created', ...(response.result || {}) }
177
+ : { success: false, error: response.error || response.message || 'Failed to create audio component' };
178
+ } catch (error) {
179
+ return { success: false, error: `Failed to create audio component: ${error instanceof Error ? error.message : String(error)}` };
593
180
  }
594
-
595
- return { success: true, message: `Audio component ${params.componentName} added to ${params.actorName}` };
596
181
  }
597
182
 
598
- // Set sound attenuation
599
- async setSoundAttenuation(params: {
183
+ // Set sound attenuation (requires C++ plugin)
184
+ async setSoundAttenuation(_params: {
600
185
  name: string;
601
186
  innerRadius?: number;
602
187
  falloffDistance?: number;
603
188
  attenuationShape?: 'Sphere' | 'Capsule' | 'Box' | 'Cone';
604
189
  falloffMode?: 'Linear' | 'Logarithmic' | 'Inverse' | 'LogReverse' | 'Natural';
605
190
  }) {
606
- const commands = [];
607
-
608
- commands.push(`CreateAttenuationSettings ${params.name}`);
609
-
610
- if (params.innerRadius !== undefined) {
611
- commands.push(`SetAttenuationInnerRadius ${params.name} ${params.innerRadius}`);
612
- }
613
-
614
- if (params.falloffDistance !== undefined) {
615
- commands.push(`SetAttenuationFalloffDistance ${params.name} ${params.falloffDistance}`);
616
- }
617
-
618
- if (params.attenuationShape) {
619
- commands.push(`SetAttenuationShape ${params.name} ${params.attenuationShape}`);
620
- }
621
-
622
- if (params.falloffMode) {
623
- commands.push(`SetAttenuationFalloffMode ${params.name} ${params.falloffMode}`);
191
+ if (!this.automationBridge) {
192
+ throw new Error('Automation Bridge not available. Setting sound attenuation requires plugin support.');
624
193
  }
625
-
626
- for (const cmd of commands) {
627
- await this.bridge.executeConsoleCommand(cmd);
194
+
195
+ try {
196
+ const response = await this.automationBridge.sendAutomationRequest('set_sound_attenuation', {
197
+ name: _params.name,
198
+ innerRadius: _params.innerRadius,
199
+ falloffDistance: _params.falloffDistance,
200
+ attenuationShape: _params.attenuationShape,
201
+ falloffMode: _params.falloffMode
202
+ });
203
+
204
+ return response.success
205
+ ? { success: true, message: response.message || 'Sound attenuation set', ...(response.result || {}) }
206
+ : { success: false, error: response.error || response.message || 'Failed to set sound attenuation' };
207
+ } catch (error) {
208
+ return { success: false, error: `Failed to set sound attenuation: ${error instanceof Error ? error.message : String(error)}` };
628
209
  }
629
-
630
- return { success: true, message: `Attenuation settings ${params.name} configured` };
631
210
  }
632
211
 
633
- // Create sound class
634
- async createSoundClass(params: {
212
+ // Create sound class (requires C++ plugin)
213
+ async createSoundClass(_params: {
635
214
  name: string;
636
215
  parentClass?: string;
637
216
  properties?: {
@@ -641,35 +220,27 @@ finally:
641
220
  attenuationDistanceScale?: number;
642
221
  };
643
222
  }) {
644
- const commands = [];
645
- const parent = params.parentClass || 'Master';
646
-
647
- commands.push(`CreateSoundClass ${params.name} ${parent}`);
648
-
649
- if (params.properties) {
650
- if (params.properties.volume !== undefined) {
651
- commands.push(`SetSoundClassVolume ${params.name} ${params.properties.volume}`);
652
- }
653
- if (params.properties.pitch !== undefined) {
654
- commands.push(`SetSoundClassPitch ${params.name} ${params.properties.pitch}`);
655
- }
656
- if (params.properties.lowPassFilterFrequency !== undefined) {
657
- commands.push(`SetSoundClassLowPassFilter ${params.name} ${params.properties.lowPassFilterFrequency}`);
658
- }
659
- if (params.properties.attenuationDistanceScale !== undefined) {
660
- commands.push(`SetSoundClassAttenuationScale ${params.name} ${params.properties.attenuationDistanceScale}`);
661
- }
223
+ if (!this.automationBridge) {
224
+ throw new Error('Automation Bridge not available. Creating sound classes requires plugin support.');
662
225
  }
663
-
664
- for (const cmd of commands) {
665
- await this.bridge.executeConsoleCommand(cmd);
226
+
227
+ try {
228
+ const response = await this.automationBridge.sendAutomationRequest('create_sound_class', {
229
+ name: _params.name,
230
+ parentClass: _params.parentClass,
231
+ properties: _params.properties
232
+ });
233
+
234
+ return response.success
235
+ ? { success: true, message: response.message || 'Sound class created', ...(response.result || {}) }
236
+ : { success: false, error: response.error || response.message || 'Failed to create sound class' };
237
+ } catch (error) {
238
+ return { success: false, error: `Failed to create sound class: ${error instanceof Error ? error.message : String(error)}` };
666
239
  }
667
-
668
- return { success: true, message: `Sound class ${params.name} created` };
669
240
  }
670
241
 
671
- // Create sound mix
672
- async createSoundMix(params: {
242
+ // Create sound mix (requires C++ plugin)
243
+ async createSoundMix(_params: {
673
244
  name: string;
674
245
  classAdjusters?: Array<{
675
246
  soundClass: string;
@@ -679,49 +250,63 @@ finally:
679
250
  fadeOutTime?: number;
680
251
  }>;
681
252
  }) {
682
- const commands = [];
683
-
684
- commands.push(`CreateSoundMix ${params.name}`);
685
-
686
- if (params.classAdjusters) {
687
- for (const adjuster of params.classAdjusters) {
688
- commands.push(`AddSoundMixClassAdjuster ${params.name} ${adjuster.soundClass}`);
689
-
690
- if (adjuster.volumeAdjuster !== undefined) {
691
- commands.push(`SetSoundMixVolume ${params.name} ${adjuster.soundClass} ${adjuster.volumeAdjuster}`);
692
- }
693
- if (adjuster.pitchAdjuster !== undefined) {
694
- commands.push(`SetSoundMixPitch ${params.name} ${adjuster.soundClass} ${adjuster.pitchAdjuster}`);
695
- }
696
- if (adjuster.fadeInTime !== undefined) {
697
- commands.push(`SetSoundMixFadeIn ${params.name} ${adjuster.soundClass} ${adjuster.fadeInTime}`);
698
- }
699
- if (adjuster.fadeOutTime !== undefined) {
700
- commands.push(`SetSoundMixFadeOut ${params.name} ${adjuster.soundClass} ${adjuster.fadeOutTime}`);
701
- }
702
- }
253
+ if (!this.automationBridge) {
254
+ throw new Error('Automation Bridge not available. Creating sound mixes requires plugin support.');
703
255
  }
704
-
705
- for (const cmd of commands) {
706
- await this.bridge.executeConsoleCommand(cmd);
256
+
257
+ try {
258
+ const response = await this.automationBridge.sendAutomationRequest('create_sound_mix', {
259
+ name: _params.name,
260
+ classAdjusters: _params.classAdjusters
261
+ });
262
+
263
+ return response.success
264
+ ? { success: true, message: response.message || 'Sound mix created', ...(response.result || {}) }
265
+ : { success: false, error: response.error || response.message || 'Failed to create sound mix' };
266
+ } catch (error) {
267
+ return { success: false, error: `Failed to create sound mix: ${error instanceof Error ? error.message : String(error)}` };
707
268
  }
708
-
709
- return { success: true, message: `Sound mix ${params.name} created` };
710
269
  }
711
270
 
712
- // Push/Pop sound mix
713
- async pushSoundMix(params: {
271
+ // Push/Pop sound mix (requires C++ plugin)
272
+ async pushSoundMix(_params: {
714
273
  mixName: string;
715
274
  }) {
716
- const command = `PushSoundMix ${params.mixName}`;
717
- return this.bridge.executeConsoleCommand(command);
275
+ if (!this.automationBridge) {
276
+ throw new Error('Automation Bridge not available. Pushing sound mixes requires plugin support.');
277
+ }
278
+
279
+ try {
280
+ const response = await this.automationBridge.sendAutomationRequest('push_sound_mix', {
281
+ mixName: _params.mixName
282
+ });
283
+
284
+ return response.success
285
+ ? { success: true, message: response.message || 'Sound mix pushed', ...(response.result || {}) }
286
+ : { success: false, error: response.error || response.message || 'Failed to push sound mix' };
287
+ } catch (error) {
288
+ return { success: false, error: `Failed to push sound mix: ${error instanceof Error ? error.message : String(error)}` };
289
+ }
718
290
  }
719
291
 
720
- async popSoundMix(params: {
292
+ async popSoundMix(_params: {
721
293
  mixName: string;
722
294
  }) {
723
- const command = `PopSoundMix ${params.mixName}`;
724
- return this.bridge.executeConsoleCommand(command);
295
+ if (!this.automationBridge) {
296
+ throw new Error('Automation Bridge not available. Popping sound mixes requires plugin support.');
297
+ }
298
+
299
+ try {
300
+ const response = await this.automationBridge.sendAutomationRequest('pop_sound_mix', {
301
+ mixName: _params.mixName
302
+ });
303
+
304
+ return response.success
305
+ ? { success: true, message: response.message || 'Sound mix popped', ...(response.result || {}) }
306
+ : { success: false, error: response.error || response.message || 'Failed to pop sound mix' };
307
+ } catch (error) {
308
+ return { success: false, error: `Failed to pop sound mix: ${error instanceof Error ? error.message : String(error)}` };
309
+ }
725
310
  }
726
311
 
727
312
  // Set master volume
@@ -730,99 +315,54 @@ finally:
730
315
  }) {
731
316
  // Clamp volume between 0 and 1
732
317
  const vol = Math.max(0.0, Math.min(1.0, params.volume));
733
-
318
+
734
319
  // Use the proper Unreal Engine audio command
735
320
  // Note: au.Master.Volume is the correct console variable for master volume
736
321
  const command = `au.Master.Volume ${vol}`;
737
-
322
+
738
323
  try {
739
324
  await this.bridge.executeConsoleCommand(command);
740
325
  return { success: true, message: `Master volume set to ${vol}` };
741
326
  } catch (e) {
742
- // Fallback to Python method if console command fails
743
- const py = `
744
- import unreal
745
- import json
746
- try:
747
- # Try using AudioMixerBlueprintLibrary if available
748
- try:
749
- unreal.AudioMixerBlueprintLibrary.set_overall_volume_multiplier(${vol})
750
- print('RESULT:' + json.dumps({'success': True}))
751
- except AttributeError:
752
- # Fallback to GameplayStatics method using modern subsystems
753
- try:
754
- # Try modern subsystem first
755
- try:
756
- editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
757
- if hasattr(editor_subsystem, 'get_editor_world'):
758
- world = editor_subsystem.get_editor_world()
759
- else:
760
- world = unreal.EditorLevelLibrary.get_editor_world()
761
- except Exception:
762
- world = unreal.EditorLevelLibrary.get_editor_world()
763
- unreal.GameplayStatics.set_global_pitch_modulation(world, 1.0, 0.0) # Reset pitch
764
- unreal.GameplayStatics.set_global_time_dilation(world, 1.0) # Reset time
765
- # Note: There's no direct master volume in GameplayStatics, use sound class
766
- print('RESULT:' + json.dumps({'success': False, 'error': 'Master volume control not available, use sound classes instead'}))
767
- except Exception as e2:
768
- print('RESULT:' + json.dumps({'success': False, 'error': str(e2)}))
769
- except Exception as e:
770
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
771
- `.trim();
772
-
773
- try {
774
- const resp = await this.bridge.executePython(py);
775
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
776
- const m = out.match(/RESULT:({.*})/);
777
- if (m) {
778
- try {
779
- const parsed = JSON.parse(m[1]);
780
- return parsed.success
781
- ? { success: true, message: `Master volume set to ${vol}` }
782
- : { success: false, error: parsed.error };
783
- } catch {}
784
- }
785
- return { success: true, message: 'Master volume set command executed' };
786
- } catch {
787
- return { success: false, error: `Failed to set master volume: ${e}` };
788
- }
327
+ return { success: false, error: `Failed to set master volume: ${e instanceof Error ? e.message : String(e)}` };
789
328
  }
790
329
  }
791
330
 
792
- // Create ambient sound
793
- async createAmbientSound(params: {
794
- name: string;
795
- location: [number, number, number];
331
+ // Create ambient sound (requires C++ plugin)
332
+ async createAmbientSound(_params: {
796
333
  soundPath: string;
334
+ location: [number, number, number];
797
335
  volume?: number;
798
- radius?: number;
799
- autoPlay?: boolean;
336
+ pitch?: number;
337
+ startTime?: number;
338
+ attenuationPath?: string;
339
+ concurrencyPath?: string;
800
340
  }) {
801
- const commands = [];
802
-
803
- commands.push(`SpawnAmbientSound ${params.name} ${params.location.join(' ')} ${params.soundPath}`);
804
-
805
- if (params.volume !== undefined) {
806
- commands.push(`SetAmbientVolume ${params.name} ${params.volume}`);
807
- }
808
-
809
- if (params.radius !== undefined) {
810
- commands.push(`SetAmbientRadius ${params.name} ${params.radius}`);
811
- }
812
-
813
- if (params.autoPlay !== undefined) {
814
- commands.push(`SetAmbientAutoPlay ${params.name} ${params.autoPlay}`);
341
+ if (!this.automationBridge) {
342
+ throw new Error('Automation Bridge not available. Creating ambient sounds requires plugin support.');
815
343
  }
816
-
817
- for (const cmd of commands) {
818
- await this.bridge.executeConsoleCommand(cmd);
344
+
345
+ try {
346
+ const response = await this.automationBridge.sendAutomationRequest('create_ambient_sound', {
347
+ soundPath: _params.soundPath,
348
+ location: _params.location,
349
+ volume: _params.volume ?? 1.0,
350
+ pitch: _params.pitch ?? 1.0,
351
+ startTime: _params.startTime ?? 0.0,
352
+ attenuationPath: _params.attenuationPath,
353
+ concurrencyPath: _params.concurrencyPath
354
+ });
355
+
356
+ return response.success
357
+ ? { success: true, message: response.message || 'Ambient sound created', ...(response.result || {}) }
358
+ : { success: false, error: response.error || response.message || 'Failed to create ambient sound' };
359
+ } catch (error) {
360
+ return { success: false, error: `Failed to create ambient sound: ${error instanceof Error ? error.message : String(error)}` };
819
361
  }
820
-
821
- return { success: true, message: `Ambient sound ${params.name} created` };
822
362
  }
823
363
 
824
- // Create reverb zone
825
- async createReverbZone(params: {
364
+ // Create reverb zone (requires C++ plugin)
365
+ async createReverbZone(_params: {
826
366
  name: string;
827
367
  location: [number, number, number];
828
368
  size: [number, number, number];
@@ -830,52 +370,51 @@ finally:
830
370
  volume?: number;
831
371
  fadeTime?: number;
832
372
  }) {
833
- const commands = [];
834
-
835
- commands.push(`CreateReverbVolume ${params.name} ${params.location.join(' ')} ${params.size.join(' ')}`);
836
-
837
- if (params.reverbEffect) {
838
- commands.push(`SetReverbEffect ${params.name} ${params.reverbEffect}`);
839
- }
840
-
841
- if (params.volume !== undefined) {
842
- commands.push(`SetReverbVolume ${params.name} ${params.volume}`);
843
- }
844
-
845
- if (params.fadeTime !== undefined) {
846
- commands.push(`SetReverbFadeTime ${params.name} ${params.fadeTime}`);
373
+ if (!this.automationBridge) {
374
+ throw new Error('Automation Bridge not available. Creating reverb zones requires plugin support.');
847
375
  }
848
-
849
- for (const cmd of commands) {
850
- await this.bridge.executeConsoleCommand(cmd);
376
+
377
+ try {
378
+ const response = await this.automationBridge.sendAutomationRequest('create_reverb_zone', {
379
+ name: _params.name,
380
+ location: _params.location,
381
+ size: _params.size,
382
+ reverbEffect: _params.reverbEffect,
383
+ volume: _params.volume,
384
+ fadeTime: _params.fadeTime
385
+ });
386
+
387
+ return response.success
388
+ ? { success: true, message: response.message || 'Reverb zone created', ...(response.result || {}) }
389
+ : { success: false, error: response.error || response.message || 'Failed to create reverb zone' };
390
+ } catch (error) {
391
+ return { success: false, error: `Failed to create reverb zone: ${error instanceof Error ? error.message : String(error)}` };
851
392
  }
852
-
853
- return { success: true, message: `Reverb zone ${params.name} created` };
854
393
  }
855
394
 
856
- // Audio analysis
857
- async enableAudioAnalysis(params: {
395
+ // Audio analysis (requires C++ plugin)
396
+ async enableAudioAnalysis(_params: {
858
397
  enabled: boolean;
859
398
  fftSize?: number;
860
399
  outputType?: 'Magnitude' | 'Decibel' | 'Normalized';
861
400
  }) {
862
- const commands = [];
863
-
864
- commands.push(`EnableAudioAnalysis ${params.enabled}`);
865
-
866
- if (params.enabled && params.fftSize) {
867
- commands.push(`SetFFTSize ${params.fftSize}`);
401
+ if (!this.automationBridge) {
402
+ throw new Error('Automation Bridge not available. Audio analysis controls require plugin support.');
868
403
  }
869
-
870
- if (params.enabled && params.outputType) {
871
- commands.push(`SetAudioAnalysisOutput ${params.outputType}`);
872
- }
873
-
874
- for (const cmd of commands) {
875
- await this.bridge.executeConsoleCommand(cmd);
404
+
405
+ try {
406
+ const response = await this.automationBridge.sendAutomationRequest('enable_audio_analysis', {
407
+ enabled: _params.enabled,
408
+ fftSize: _params.fftSize,
409
+ outputType: _params.outputType
410
+ });
411
+
412
+ return response.success
413
+ ? { success: true, message: response.message || `Audio analysis ${_params.enabled ? 'enabled' : 'disabled'}`, ...(response.result || {}) }
414
+ : { success: false, error: response.error || response.message || 'Failed to enable audio analysis' };
415
+ } catch (error) {
416
+ return { success: false, error: `Failed to enable audio analysis: ${error instanceof Error ? error.message : String(error)}` };
876
417
  }
877
-
878
- return { success: true, message: `Audio analysis ${params.enabled ? 'enabled' : 'disabled'}` };
879
418
  }
880
419
 
881
420
  // Stop all sounds
@@ -883,60 +422,78 @@ finally:
883
422
  return this.bridge.executeConsoleCommand('StopAllSounds');
884
423
  }
885
424
 
886
- // Fade sound
887
- async fadeSound(params: {
425
+ // Fade sound (requires C++ plugin)
426
+ async fadeSound(_params: {
888
427
  soundName: string;
889
428
  targetVolume: number;
890
429
  fadeTime: number;
891
430
  fadeType?: 'FadeIn' | 'FadeOut' | 'FadeTo';
892
431
  }) {
893
- const type = params.fadeType || 'FadeTo';
894
- const command = `${type}Sound ${params.soundName} ${params.targetVolume} ${params.fadeTime}`;
895
- return this.bridge.executeConsoleCommand(command);
432
+ if (!this.automationBridge) {
433
+ throw new Error('Automation Bridge not available. Fading sound requires plugin support.');
434
+ }
435
+
436
+ try {
437
+ const response = await this.automationBridge.sendAutomationRequest('fade_sound', {
438
+ soundName: _params.soundName,
439
+ targetVolume: _params.targetVolume,
440
+ fadeTime: _params.fadeTime,
441
+ fadeType: _params.fadeType || 'FadeTo'
442
+ });
443
+
444
+ return response.success
445
+ ? { success: true, message: response.message || 'Sound faded', ...(response.result || {}) }
446
+ : { success: false, error: response.error || response.message || 'Failed to fade sound' };
447
+ } catch (error) {
448
+ return { success: false, error: `Failed to fade sound: ${error instanceof Error ? error.message : String(error)}` };
449
+ }
896
450
  }
897
451
 
898
- // Set doppler effect
899
- async setDopplerEffect(params: {
452
+ // Set doppler effect (requires C++ plugin)
453
+ async setDopplerEffect(_params: {
900
454
  enabled: boolean;
901
455
  scale?: number;
902
456
  }) {
903
- const commands = [];
904
-
905
- commands.push(`EnableDoppler ${params.enabled}`);
906
-
907
- if (params.scale !== undefined) {
908
- commands.push(`SetDopplerScale ${params.scale}`);
457
+ if (!this.automationBridge) {
458
+ throw new Error('Automation Bridge not available. Doppler effect controls require plugin support.');
909
459
  }
910
-
911
- for (const cmd of commands) {
912
- await this.bridge.executeConsoleCommand(cmd);
460
+
461
+ try {
462
+ const response = await this.automationBridge.sendAutomationRequest('set_doppler_effect', {
463
+ enabled: _params.enabled,
464
+ scale: _params.scale ?? 1.0
465
+ });
466
+
467
+ return response.success
468
+ ? { success: true, message: response.message || `Doppler effect ${_params.enabled ? 'enabled' : 'disabled'}`, ...(response.result || {}) }
469
+ : { success: false, error: response.error || response.message || 'Failed to set doppler effect' };
470
+ } catch (error) {
471
+ return { success: false, error: `Failed to set doppler effect: ${error instanceof Error ? error.message : String(error)}` };
913
472
  }
914
-
915
- return { success: true, message: `Doppler effect ${params.enabled ? 'enabled' : 'disabled'}` };
916
473
  }
917
474
 
918
- // Audio occlusion
919
- async setAudioOcclusion(params: {
475
+ // Audio occlusion (requires C++ plugin)
476
+ async setAudioOcclusion(_params: {
920
477
  enabled: boolean;
921
478
  lowPassFilterFrequency?: number;
922
479
  volumeAttenuation?: number;
923
480
  }) {
924
- const commands = [];
925
-
926
- commands.push(`EnableAudioOcclusion ${params.enabled}`);
927
-
928
- if (params.lowPassFilterFrequency !== undefined) {
929
- commands.push(`SetOcclusionLowPassFilter ${params.lowPassFilterFrequency}`);
930
- }
931
-
932
- if (params.volumeAttenuation !== undefined) {
933
- commands.push(`SetOcclusionVolumeAttenuation ${params.volumeAttenuation}`);
481
+ if (!this.automationBridge) {
482
+ throw new Error('Automation Bridge not available. Audio occlusion controls require plugin support.');
934
483
  }
935
-
936
- for (const cmd of commands) {
937
- await this.bridge.executeConsoleCommand(cmd);
484
+
485
+ try {
486
+ const response = await this.automationBridge.sendAutomationRequest('set_audio_occlusion', {
487
+ enabled: _params.enabled,
488
+ lowPassFilterFrequency: _params.lowPassFilterFrequency,
489
+ volumeAttenuation: _params.volumeAttenuation
490
+ });
491
+
492
+ return response.success
493
+ ? { success: true, message: response.message || `Audio occlusion ${_params.enabled ? 'enabled' : 'disabled'}`, ...(response.result || {}) }
494
+ : { success: false, error: response.error || response.message || 'Failed to set audio occlusion' };
495
+ } catch (error) {
496
+ return { success: false, error: `Failed to set audio occlusion: ${error instanceof Error ? error.message : String(error)}` };
938
497
  }
939
-
940
- return { success: true, message: `Audio occlusion ${params.enabled ? 'enabled' : 'disabled'}` };
941
498
  }
942
499
  }