unreal-engine-mcp-server 0.4.7 → 0.5.1
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.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +27 -0
- package/.github/workflows/labeler.yml +17 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +13 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +338 -31
- package/CONTRIBUTING.md +140 -0
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +189 -128
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +26 -0
- package/dist/config.js +59 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +16 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +746 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +117 -0
- package/dist/graphql/types.d.ts +9 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +33 -18
- package/dist/index.js +130 -619
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +20 -0
- package/dist/server-setup.js +71 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +163 -9
- package/dist/tools/actors.js +356 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +75 -29
- package/dist/tools/assets.js +265 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint.d.ts +208 -126
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
- package/dist/tools/consolidated-tool-definitions.js +829 -496
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +198 -1027
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +13 -0
- package/dist/tools/dynamic-handler-registry.js +23 -0
- package/dist/tools/editor.d.ts +30 -83
- package/dist/tools/editor.js +247 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +30 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +65 -99
- package/dist/tools/foliage.js +221 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +227 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +54 -93
- package/dist/tools/landscape.js +284 -409
- package/dist/tools/level.d.ts +66 -27
- package/dist/tools/level.js +647 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +445 -943
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/logs.js +5 -57
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +194 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +267 -182
- package/dist/tools/performance.d.ts +27 -13
- package/dist/tools/performance.js +203 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +175 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +85 -60
- package/dist/tools/sequence.js +208 -747
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +64 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +183 -19
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +68 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +27 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +684 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +67 -28
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +136 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +58 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +19 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +1008 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +156 -0
- package/src/graphql/types.ts +10 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +166 -664
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +148 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +114 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +426 -323
- package/src/tools/animation.ts +672 -461
- package/src/tools/assets.ts +364 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint.ts +792 -970
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +258 -1146
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +33 -0
- package/src/tools/editor.ts +329 -253
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +281 -0
- package/src/tools/foliage.ts +330 -392
- package/src/tools/handlers/actor-handlers.ts +265 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +418 -507
- package/src/tools/level.ts +786 -708
- package/src/tools/lighting.ts +588 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +237 -121
- package/src/tools/niagara.ts +335 -168
- package/src/tools/performance.ts +320 -169
- package/src/tools/physics.ts +274 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +276 -820
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +205 -283
- package/src/types/automation-responses.ts +119 -0
- package/src/types/env.ts +0 -10
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +243 -21
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +76 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/normalize.ts +60 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +44 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.test.ts +184 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +369 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +417 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +444 -0
- package/tests/test-blueprint-graph.mjs +410 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +112 -0
- package/tests/test-graphql.mjs +372 -0
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +302 -0
- package/tests/test-landscape.mjs +316 -0
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +89 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-performance.mjs +539 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-sequence.mjs +104 -0
- package/tests/test-system.mjs +96 -0
- package/tests/test-wasm.mjs +283 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/vitest.config.ts +35 -0
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -574
- package/smithery.yaml +0 -29
- package/src/prompts/index.ts +0 -249
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/src/tools/audio.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
345
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
438
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
567
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
627
|
-
await this.
|
|
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(
|
|
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
|
-
|
|
645
|
-
|
|
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
|
-
|
|
665
|
-
await this.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
706
|
-
await this.
|
|
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(
|
|
271
|
+
// Push/Pop sound mix (requires C++ plugin)
|
|
272
|
+
async pushSoundMix(_params: {
|
|
714
273
|
mixName: string;
|
|
715
274
|
}) {
|
|
716
|
-
|
|
717
|
-
|
|
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(
|
|
292
|
+
async popSoundMix(_params: {
|
|
721
293
|
mixName: string;
|
|
722
294
|
}) {
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
799
|
-
|
|
336
|
+
pitch?: number;
|
|
337
|
+
startTime?: number;
|
|
338
|
+
attenuationPath?: string;
|
|
339
|
+
concurrencyPath?: string;
|
|
800
340
|
}) {
|
|
801
|
-
|
|
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
|
-
|
|
818
|
-
await this.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
850
|
-
await this.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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(
|
|
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
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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(
|
|
452
|
+
// Set doppler effect (requires C++ plugin)
|
|
453
|
+
async setDopplerEffect(_params: {
|
|
900
454
|
enabled: boolean;
|
|
901
455
|
scale?: number;
|
|
902
456
|
}) {
|
|
903
|
-
|
|
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
|
-
|
|
912
|
-
await this.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
937
|
-
await this.
|
|
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
|
}
|