unreal-engine-mcp-server 0.4.7 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.yml +148 -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 +23 -0
- package/.github/workflows/labeler.yml +16 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +12 -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 +267 -31
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -71
- 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 +27 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +12 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +743 -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 +115 -0
- package/dist/graphql/types.d.ts +7 -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 +31 -18
- package/dist/index.js +119 -619
- package/dist/prompts/index.js +4 -4
- 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 +21 -0
- package/dist/server-setup.js +111 -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 +147 -9
- package/dist/tools/actors.js +350 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +117 -19
- package/dist/tools/assets.js +259 -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/helpers.d.ts +29 -0
- package/dist/tools/blueprint/helpers.js +182 -0
- package/dist/tools/blueprint.d.ts +228 -118
- 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 +211 -1026
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +11 -0
- package/dist/tools/dynamic-handler-registry.js +101 -0
- package/dist/tools/editor.d.ts +139 -18
- package/dist/tools/editor.js +239 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +36 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +105 -14
- package/dist/tools/foliage.js +219 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +232 -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 +97 -36
- package/dist/tools/landscape.js +280 -409
- package/dist/tools/level.d.ts +130 -10
- package/dist/tools/level.js +639 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +441 -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 +190 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +232 -182
- package/dist/tools/performance.d.ts +27 -12
- package/dist/tools/performance.js +204 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +171 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +73 -48
- package/dist/tools/sequence.js +196 -748
- 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 +66 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- 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 +67 -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/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +33 -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 +692 -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 +60 -27
- 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 +131 -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 +57 -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 +12 -0
- package/src/graphql/resolvers.ts +1010 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +154 -0
- package/src/graphql/types.ts +7 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +152 -663
- package/src/prompts/index.ts +4 -4
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +147 -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 +148 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +417 -322
- package/src/tools/animation.ts +671 -461
- package/src/tools/assets.ts +353 -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/helpers.ts +189 -0
- package/src/tools/blueprint.ts +787 -965
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +272 -1139
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +151 -0
- package/src/tools/editor.ts +309 -246
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +287 -0
- package/src/tools/foliage.ts +314 -379
- package/src/tools/handlers/actor-handlers.ts +271 -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 +394 -489
- package/src/tools/level.ts +752 -694
- package/src/tools/lighting.ts +583 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +231 -121
- package/src/tools/niagara.ts +293 -168
- package/src/tools/performance.ts +320 -168
- package/src/tools/physics.ts +268 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +255 -815
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +207 -283
- package/src/types/env.ts +0 -10
- 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 +75 -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.ts +60 -0
- package/src/utils/response-factory.ts +39 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -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 +44 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-audio.mjs +219 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +261 -0
- package/tests/test-blueprint-events.mjs +35 -0
- package/tests/test-blueprint-graph.mjs +79 -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 +80 -0
- package/tests/test-extra-tools.mjs +38 -0
- package/tests/test-graphql.mjs +322 -0
- package/tests/test-inspect.mjs +72 -0
- package/tests/test-landscape.mjs +60 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +70 -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-plugin-handshake.mjs +82 -0
- package/tests/test-render.mjs +33 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-search-assets.mjs +66 -0
- package/tests/test-sequence.mjs +68 -0
- package/tests/test-system.mjs +57 -0
- package/tests/test-wasm.mjs +193 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- 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/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/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/editor.ts
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseTool } from './base-tool.js';
|
|
2
|
+
import { IEditorTools } from '../types/tool-interfaces.js';
|
|
2
3
|
import { toVec3Object, toRotObject } from '../utils/normalize.js';
|
|
3
|
-
import {
|
|
4
|
+
import { DEFAULT_SCREENSHOT_RESOLUTION } from '../constants.js';
|
|
5
|
+
|
|
6
|
+
export class EditorTools extends BaseTool implements IEditorTools {
|
|
7
|
+
private cameraBookmarks = new Map<string, { location: [number, number, number]; rotation: [number, number, number]; savedAt: number }>();
|
|
8
|
+
private editorPreferences = new Map<string, Record<string, unknown>>();
|
|
9
|
+
private activeRecording?: { name?: string; options?: Record<string, unknown>; startedAt: number };
|
|
4
10
|
|
|
5
|
-
export class EditorTools {
|
|
6
|
-
constructor(private bridge: UnrealBridge) {}
|
|
7
|
-
|
|
8
11
|
async isInPIE(): Promise<boolean> {
|
|
9
12
|
try {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return out.includes('PIE_STATE:True');
|
|
13
|
+
const response = await this.sendAutomationRequest(
|
|
14
|
+
'check_pie_state',
|
|
15
|
+
{},
|
|
16
|
+
{ timeoutMs: 5000 }
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (response && response.success !== false) {
|
|
20
|
+
return response.isInPIE === true || response.result?.isInPIE === true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false;
|
|
22
24
|
} catch {
|
|
23
25
|
return false;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
async ensureNotInPIE(): Promise<void> {
|
|
28
30
|
if (await this.isInPIE()) {
|
|
29
31
|
await this.stopPlayInEditor();
|
|
@@ -32,70 +34,29 @@ else:
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
async playInEditor() {
|
|
37
|
+
async playInEditor(timeoutMs: number = 30000) {
|
|
36
38
|
try {
|
|
37
|
-
// Set tick rate to match UI play (60 fps for game mode)
|
|
38
|
-
await this.bridge.executeConsoleCommand('t.MaxFPS 60');
|
|
39
|
-
|
|
40
|
-
// Try Python first using the modern LevelEditorSubsystem
|
|
41
39
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if
|
|
48
|
-
|
|
49
|
-
was_playing = les.is_in_play_in_editor()
|
|
50
|
-
|
|
51
|
-
# Request PIE in the current viewport
|
|
52
|
-
les.editor_play_simulate()
|
|
53
|
-
|
|
54
|
-
# Wait for PIE to start with multiple checks
|
|
55
|
-
max_attempts = 10
|
|
56
|
-
for i in range(max_attempts):
|
|
57
|
-
time.sleep(0.2) # Wait 200ms between checks
|
|
58
|
-
is_playing = les.is_in_play_in_editor()
|
|
59
|
-
if is_playing and not was_playing:
|
|
60
|
-
# PIE has started
|
|
61
|
-
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
62
|
-
break
|
|
63
|
-
else:
|
|
64
|
-
# If we've waited 2 seconds total and PIE hasn't started,
|
|
65
|
-
# but the command was sent, assume it will start
|
|
66
|
-
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
67
|
-
else:
|
|
68
|
-
# If subsystem not available, report error
|
|
69
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
70
|
-
`.trim();
|
|
71
|
-
|
|
72
|
-
const resp: any = await this.bridge.executePython(pythonCmd);
|
|
73
|
-
const interpreted = interpretStandardResult(resp, {
|
|
74
|
-
successMessage: 'PIE started',
|
|
75
|
-
failureMessage: 'Failed to start PIE'
|
|
76
|
-
});
|
|
77
|
-
if (interpreted.success) {
|
|
78
|
-
const method = coerceString(interpreted.payload.method) ?? 'LevelEditorSubsystem';
|
|
79
|
-
return { success: true, message: `PIE started (via ${method})` };
|
|
40
|
+
const response = await this.sendAutomationRequest(
|
|
41
|
+
'control_editor',
|
|
42
|
+
{ action: 'play' },
|
|
43
|
+
{ timeoutMs }
|
|
44
|
+
);
|
|
45
|
+
if (response && response.success === true) {
|
|
46
|
+
return { success: true, message: response.message || 'PIE started' };
|
|
80
47
|
}
|
|
81
|
-
|
|
82
|
-
} catch (err) {
|
|
83
|
-
//
|
|
84
|
-
|
|
48
|
+
return { success: false, error: response?.error || response?.message || 'Failed to start PIE' };
|
|
49
|
+
} catch (err: any) {
|
|
50
|
+
// If it's a timeout, return error instead of falling back
|
|
51
|
+
if (err.message && /time.*out/i.test(err.message)) {
|
|
52
|
+
return { success: false, error: `Timeout waiting for PIE to start: ${err.message}` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Fallback to console commands if automation bridge is unavailable or fails (non-timeout)
|
|
56
|
+
await this.bridge.executeConsoleCommand('t.MaxFPS 60');
|
|
57
|
+
await this.bridge.executeConsoleCommand('PlayInViewport');
|
|
58
|
+
return { success: true, message: 'PIE start command sent' };
|
|
85
59
|
}
|
|
86
|
-
// Fallback to console command which is more reliable
|
|
87
|
-
await this.bridge.executeConsoleCommand('PlayInViewport');
|
|
88
|
-
|
|
89
|
-
// Wait a moment and verify PIE started
|
|
90
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
91
|
-
|
|
92
|
-
// Check if PIE is now active
|
|
93
|
-
const isPlaying = await this.isInPIE();
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
success: true,
|
|
97
|
-
message: isPlaying ? 'PIE started successfully' : 'PIE start command sent (may take a moment)'
|
|
98
|
-
};
|
|
99
60
|
} catch (err) {
|
|
100
61
|
return { success: false, error: `Failed to start PIE: ${err}` };
|
|
101
62
|
}
|
|
@@ -103,37 +64,26 @@ else:
|
|
|
103
64
|
|
|
104
65
|
async stopPlayInEditor() {
|
|
105
66
|
try {
|
|
106
|
-
// Try Python first using the modern LevelEditorSubsystem
|
|
107
67
|
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
les.editor_request_end_play() # Modern API method
|
|
114
|
-
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
115
|
-
else:
|
|
116
|
-
# If subsystem not available, report error
|
|
117
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
118
|
-
`.trim();
|
|
119
|
-
const resp: any = await this.bridge.executePython(pythonCmd);
|
|
120
|
-
const interpreted = interpretStandardResult(resp, {
|
|
121
|
-
successMessage: 'PIE stopped successfully',
|
|
122
|
-
failureMessage: 'Failed to stop PIE'
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
if (interpreted.success) {
|
|
126
|
-
const method = coerceString(interpreted.payload.method) ?? 'LevelEditorSubsystem';
|
|
127
|
-
return { success: true, message: `PIE stopped via ${method}` };
|
|
128
|
-
}
|
|
68
|
+
const response = await this.sendAutomationRequest(
|
|
69
|
+
'control_editor',
|
|
70
|
+
{ action: 'stop' },
|
|
71
|
+
{ timeoutMs: 30000 }
|
|
72
|
+
);
|
|
129
73
|
|
|
130
|
-
if (
|
|
131
|
-
return {
|
|
74
|
+
if (response.success !== false) {
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
message: response.message || 'PIE stopped successfully'
|
|
78
|
+
};
|
|
132
79
|
}
|
|
133
80
|
|
|
134
|
-
return {
|
|
135
|
-
|
|
136
|
-
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: response.error || response.message || 'Failed to stop PIE'
|
|
84
|
+
};
|
|
85
|
+
} catch (_pluginErr) {
|
|
86
|
+
// Fallback to console command if plugin fails
|
|
137
87
|
await this.bridge.executeConsoleCommand('stop');
|
|
138
88
|
return { success: true, message: 'PIE stopped via console command' };
|
|
139
89
|
}
|
|
@@ -141,26 +91,17 @@ else:
|
|
|
141
91
|
return { success: false, error: `Failed to stop PIE: ${err}` };
|
|
142
92
|
}
|
|
143
93
|
}
|
|
144
|
-
|
|
94
|
+
|
|
145
95
|
async pausePlayInEditor() {
|
|
146
96
|
try {
|
|
147
97
|
// Pause/Resume PIE
|
|
148
|
-
await this.bridge.
|
|
149
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
150
|
-
functionName: 'ExecuteConsoleCommand',
|
|
151
|
-
parameters: {
|
|
152
|
-
WorldContextObject: null,
|
|
153
|
-
Command: 'pause',
|
|
154
|
-
SpecificPlayer: null
|
|
155
|
-
},
|
|
156
|
-
generateTransaction: false
|
|
157
|
-
});
|
|
98
|
+
await this.bridge.executeConsoleCommand('pause');
|
|
158
99
|
return { success: true, message: 'PIE paused/resumed' };
|
|
159
100
|
} catch (err) {
|
|
160
101
|
return { success: false, error: `Failed to pause PIE: ${err}` };
|
|
161
102
|
}
|
|
162
103
|
}
|
|
163
|
-
|
|
104
|
+
|
|
164
105
|
// Alias for consistency with naming convention
|
|
165
106
|
async pauseInEditor() {
|
|
166
107
|
return this.pausePlayInEditor();
|
|
@@ -168,43 +109,41 @@ else:
|
|
|
168
109
|
|
|
169
110
|
async buildLighting() {
|
|
170
111
|
try {
|
|
171
|
-
// Use
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
import json
|
|
175
|
-
try:
|
|
176
|
-
# Use modern LevelEditorSubsystem API
|
|
177
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
178
|
-
if les:
|
|
179
|
-
# build_light_maps(quality, with_reflection_captures)
|
|
180
|
-
les.build_light_maps(unreal.LightingBuildQuality.QUALITY_HIGH, True)
|
|
181
|
-
print('RESULT:' + json.dumps({'success': True, 'message': 'Lighting build started via LevelEditorSubsystem'}))
|
|
182
|
-
else:
|
|
183
|
-
# If subsystem not available, report error
|
|
184
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
185
|
-
except Exception as e:
|
|
186
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
187
|
-
`.trim();
|
|
188
|
-
const resp: any = await this.bridge.executePython(py);
|
|
189
|
-
const interpreted = interpretStandardResult(resp, {
|
|
190
|
-
successMessage: 'Lighting build started',
|
|
191
|
-
failureMessage: 'Failed to build lighting'
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
if (interpreted.success) {
|
|
195
|
-
return { success: true, message: interpreted.message };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
success: false,
|
|
200
|
-
error: interpreted.error ?? 'Failed to build lighting',
|
|
201
|
-
details: bestEffortInterpretedText(interpreted)
|
|
202
|
-
};
|
|
112
|
+
// Use console command to build lighting
|
|
113
|
+
await this.bridge.executeConsoleCommand('BuildLighting');
|
|
114
|
+
return { success: true, message: 'Lighting build started' };
|
|
203
115
|
} catch (err) {
|
|
204
116
|
return { success: false, error: `Failed to build lighting: ${err}` };
|
|
205
117
|
}
|
|
206
118
|
}
|
|
207
119
|
|
|
120
|
+
private async getViewportCameraInfo(): Promise<{
|
|
121
|
+
success: boolean;
|
|
122
|
+
location?: [number, number, number];
|
|
123
|
+
rotation?: [number, number, number];
|
|
124
|
+
error?: string;
|
|
125
|
+
message?: string;
|
|
126
|
+
}> {
|
|
127
|
+
try {
|
|
128
|
+
const resp = await this.sendAutomationRequest(
|
|
129
|
+
'control_editor',
|
|
130
|
+
{ action: 'get_camera' },
|
|
131
|
+
{ timeoutMs: 3000 }
|
|
132
|
+
);
|
|
133
|
+
const result = resp?.result ?? resp;
|
|
134
|
+
const loc = result?.location ?? result?.camera?.location;
|
|
135
|
+
const rot = result?.rotation ?? result?.camera?.rotation;
|
|
136
|
+
const locArr: [number, number, number] | undefined = Array.isArray(loc) && loc.length === 3 ? [Number(loc[0]) || 0, Number(loc[1]) || 0, Number(loc[2]) || 0] : undefined;
|
|
137
|
+
const rotArr: [number, number, number] | undefined = Array.isArray(rot) && rot.length === 3 ? [Number(rot[0]) || 0, Number(rot[1]) || 0, Number(rot[2]) || 0] : undefined;
|
|
138
|
+
if (resp && resp.success !== false && locArr && rotArr) {
|
|
139
|
+
return { success: true, location: locArr, rotation: rotArr };
|
|
140
|
+
}
|
|
141
|
+
return { success: false, error: 'Failed to get camera information' };
|
|
142
|
+
} catch (err) {
|
|
143
|
+
return { success: false, error: `Camera query failed: ${err}` };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
208
147
|
async setViewportCamera(location?: { x: number; y: number; z: number } | [number, number, number] | null | undefined, rotation?: { pitch: number; yaw: number; roll: number } | [number, number, number] | null | undefined) {
|
|
209
148
|
// Special handling for when both location and rotation are missing/invalid
|
|
210
149
|
// Allow rotation-only updates
|
|
@@ -224,7 +163,7 @@ except Exception as e:
|
|
|
224
163
|
locObj.z = Math.max(-MAX_COORD, Math.min(MAX_COORD, locObj.z));
|
|
225
164
|
location = locObj as any;
|
|
226
165
|
}
|
|
227
|
-
|
|
166
|
+
|
|
228
167
|
// Validate rotation if provided
|
|
229
168
|
if (rotation !== undefined) {
|
|
230
169
|
if (rotation === null) {
|
|
@@ -240,119 +179,243 @@ except Exception as e:
|
|
|
240
179
|
rotObj.roll = ((rotObj.roll % 360) + 360) % 360;
|
|
241
180
|
rotation = rotObj as any;
|
|
242
181
|
}
|
|
243
|
-
|
|
182
|
+
|
|
183
|
+
// Use native control_editor.set_camera when available
|
|
244
184
|
try {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
# Use UnrealEditorSubsystem instead of deprecated EditorLevelLibrary
|
|
253
|
-
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
254
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
255
|
-
location = unreal.Vector(${(location as any).x}, ${(location as any).y}, ${(location as any).z})
|
|
256
|
-
rotation = unreal.Rotator(${rot.pitch}, ${rot.yaw}, ${rot.roll})
|
|
257
|
-
if ues:
|
|
258
|
-
ues.set_level_viewport_camera_info(location, rotation)
|
|
259
|
-
# Invalidate viewports to ensure visual update
|
|
260
|
-
try:
|
|
261
|
-
if les:
|
|
262
|
-
les.editor_invalidate_viewports()
|
|
263
|
-
except Exception:
|
|
264
|
-
pass
|
|
265
|
-
`.trim();
|
|
266
|
-
await this.bridge.executePython(pythonCmd);
|
|
267
|
-
return {
|
|
268
|
-
success: true,
|
|
269
|
-
message: 'Viewport camera positioned via UnrealEditorSubsystem'
|
|
270
|
-
};
|
|
271
|
-
} catch {
|
|
272
|
-
// Fallback to camera speed control
|
|
273
|
-
await this.bridge.executeConsoleCommand('camspeed 4');
|
|
274
|
-
return {
|
|
275
|
-
success: true,
|
|
276
|
-
message: 'Camera speed set. Use debug camera (toggledebugcamera) for manual positioning'
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
} else if (rotation) {
|
|
280
|
-
// Only rotation provided, try to set just rotation
|
|
281
|
-
try {
|
|
282
|
-
const pythonCmd = `
|
|
283
|
-
import unreal
|
|
284
|
-
# Use UnrealEditorSubsystem to read/write viewport camera
|
|
285
|
-
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
286
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
287
|
-
rotation = unreal.Rotator(${(rotation as any).pitch}, ${(rotation as any).yaw}, ${(rotation as any).roll})
|
|
288
|
-
if ues:
|
|
289
|
-
info = ues.get_level_viewport_camera_info()
|
|
290
|
-
if info is not None:
|
|
291
|
-
current_location, _ = info
|
|
292
|
-
ues.set_level_viewport_camera_info(current_location, rotation)
|
|
293
|
-
try:
|
|
294
|
-
if les:
|
|
295
|
-
les.editor_invalidate_viewports()
|
|
296
|
-
except Exception:
|
|
297
|
-
pass
|
|
298
|
-
`.trim();
|
|
299
|
-
await this.bridge.executePython(pythonCmd);
|
|
300
|
-
return {
|
|
301
|
-
success: true,
|
|
302
|
-
message: 'Viewport camera rotation set via UnrealEditorSubsystem'
|
|
303
|
-
};
|
|
304
|
-
} catch {
|
|
305
|
-
// Fallback
|
|
306
|
-
return {
|
|
307
|
-
success: true,
|
|
308
|
-
message: 'Camera rotation update attempted'
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
// Neither location nor rotation provided - this is valid, just no-op
|
|
313
|
-
return {
|
|
314
|
-
success: true,
|
|
315
|
-
message: 'No camera changes requested'
|
|
316
|
-
};
|
|
185
|
+
const resp = await this.sendAutomationRequest('control_editor', {
|
|
186
|
+
action: 'set_camera',
|
|
187
|
+
location: location as any,
|
|
188
|
+
rotation: rotation as any
|
|
189
|
+
}, { timeoutMs: 10000 });
|
|
190
|
+
if (resp && resp.success === true) {
|
|
191
|
+
return { success: true, message: resp.message || 'Camera set', location, rotation };
|
|
317
192
|
}
|
|
193
|
+
return { success: false, error: resp?.error || resp?.message || 'Failed to set camera' };
|
|
318
194
|
} catch (err) {
|
|
319
|
-
return { success: false, error: `
|
|
195
|
+
return { success: false, error: `Camera control failed: ${err}` };
|
|
320
196
|
}
|
|
321
197
|
}
|
|
322
|
-
|
|
198
|
+
|
|
323
199
|
async setCameraSpeed(speed: number) {
|
|
324
200
|
try {
|
|
325
|
-
await this.bridge.
|
|
326
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
327
|
-
functionName: 'ExecuteConsoleCommand',
|
|
328
|
-
parameters: {
|
|
329
|
-
WorldContextObject: null,
|
|
330
|
-
Command: `camspeed ${speed}`,
|
|
331
|
-
SpecificPlayer: null
|
|
332
|
-
},
|
|
333
|
-
generateTransaction: false
|
|
334
|
-
});
|
|
201
|
+
await this.bridge.executeConsoleCommand(`camspeed ${speed}`);
|
|
335
202
|
return { success: true, message: `Camera speed set to ${speed}` };
|
|
336
203
|
} catch (err) {
|
|
337
204
|
return { success: false, error: `Failed to set camera speed: ${err}` };
|
|
338
205
|
}
|
|
339
206
|
}
|
|
340
|
-
|
|
207
|
+
|
|
341
208
|
async setFOV(fov: number) {
|
|
342
209
|
try {
|
|
343
|
-
await this.bridge.
|
|
344
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
345
|
-
functionName: 'ExecuteConsoleCommand',
|
|
346
|
-
parameters: {
|
|
347
|
-
WorldContextObject: null,
|
|
348
|
-
Command: `fov ${fov}`,
|
|
349
|
-
SpecificPlayer: null
|
|
350
|
-
},
|
|
351
|
-
generateTransaction: false
|
|
352
|
-
});
|
|
210
|
+
await this.bridge.executeConsoleCommand(`fov ${fov}`);
|
|
353
211
|
return { success: true, message: `FOV set to ${fov}` };
|
|
354
212
|
} catch (err) {
|
|
355
213
|
return { success: false, error: `Failed to set FOV: ${err}` };
|
|
356
214
|
}
|
|
357
215
|
}
|
|
216
|
+
|
|
217
|
+
async takeScreenshot(filename?: string, resolution?: string) {
|
|
218
|
+
try {
|
|
219
|
+
if (resolution && !/^\d+x\d+$/.test(resolution)) {
|
|
220
|
+
return { success: false, error: 'Invalid resolution format. Use WxH (e.g. 1920x1080)' };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const sanitizedFilename = filename ? filename.replace(/[<>:*?"|]/g, '_') : `Screenshot_${Date.now()}`;
|
|
224
|
+
const resString = resolution || DEFAULT_SCREENSHOT_RESOLUTION;
|
|
225
|
+
const command = filename ? `highresshot ${resString} filename="${sanitizedFilename}"` : 'shot';
|
|
226
|
+
|
|
227
|
+
await this.bridge.executeConsoleCommand(command);
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
success: true,
|
|
231
|
+
message: `Screenshot captured: ${sanitizedFilename}`,
|
|
232
|
+
filename: sanitizedFilename,
|
|
233
|
+
command
|
|
234
|
+
};
|
|
235
|
+
} catch (err) {
|
|
236
|
+
return { success: false, error: `Failed to take screenshot: ${err}` };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async resumePlayInEditor() {
|
|
241
|
+
try {
|
|
242
|
+
// Use console command to toggle pause (resumes if paused)
|
|
243
|
+
await this.bridge.executeConsoleCommand('pause');
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
message: 'PIE resume toggled via pause command'
|
|
247
|
+
};
|
|
248
|
+
} catch (err) {
|
|
249
|
+
return { success: false, error: `Failed to resume PIE: ${err}` };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async stepPIEFrame(steps: number = 1) {
|
|
254
|
+
const clampedSteps = Number.isFinite(steps) ? Math.max(1, Math.floor(steps)) : 1;
|
|
255
|
+
try {
|
|
256
|
+
// Use console command to step frames
|
|
257
|
+
for (let index = 0; index < clampedSteps; index += 1) {
|
|
258
|
+
await this.bridge.executeConsoleCommand('Step=1');
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
message: `Advanced PIE by ${clampedSteps} frame(s)`,
|
|
263
|
+
steps: clampedSteps
|
|
264
|
+
};
|
|
265
|
+
} catch (err) {
|
|
266
|
+
return { success: false, error: `Failed to step PIE: ${err}` };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async startRecording(options?: { filename?: string; frameRate?: number; durationSeconds?: number; metadata?: Record<string, unknown> }) {
|
|
271
|
+
const startedAt = Date.now();
|
|
272
|
+
this.activeRecording = {
|
|
273
|
+
name: typeof options?.filename === 'string' ? options.filename.trim() : undefined,
|
|
274
|
+
options: options ? { ...options } : undefined,
|
|
275
|
+
startedAt
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
success: true as const,
|
|
280
|
+
message: 'Recording session started',
|
|
281
|
+
recording: {
|
|
282
|
+
name: this.activeRecording.name,
|
|
283
|
+
startedAt,
|
|
284
|
+
options: this.activeRecording.options
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async stopRecording() {
|
|
290
|
+
if (!this.activeRecording) {
|
|
291
|
+
return {
|
|
292
|
+
success: true as const,
|
|
293
|
+
message: 'No active recording session to stop'
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const stoppedRecording = this.activeRecording;
|
|
298
|
+
this.activeRecording = undefined;
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
success: true as const,
|
|
302
|
+
message: 'Recording session stopped',
|
|
303
|
+
recording: stoppedRecording
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async createCameraBookmark(name: string) {
|
|
308
|
+
const trimmedName = name.trim();
|
|
309
|
+
if (!trimmedName) {
|
|
310
|
+
return { success: false as const, error: 'bookmarkName is required' };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const cameraInfo = await this.getViewportCameraInfo();
|
|
314
|
+
if (!cameraInfo.success || !cameraInfo.location || !cameraInfo.rotation) {
|
|
315
|
+
return {
|
|
316
|
+
success: false as const,
|
|
317
|
+
error: cameraInfo.error || 'Failed to capture viewport camera'
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
this.cameraBookmarks.set(trimmedName, {
|
|
322
|
+
location: cameraInfo.location,
|
|
323
|
+
rotation: cameraInfo.rotation,
|
|
324
|
+
savedAt: Date.now()
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
success: true as const,
|
|
329
|
+
message: `Bookmark '${trimmedName}' saved`,
|
|
330
|
+
bookmark: {
|
|
331
|
+
name: trimmedName,
|
|
332
|
+
location: cameraInfo.location,
|
|
333
|
+
rotation: cameraInfo.rotation
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async jumpToCameraBookmark(name: string) {
|
|
339
|
+
const trimmedName = name.trim();
|
|
340
|
+
if (!trimmedName) {
|
|
341
|
+
return { success: false as const, error: 'bookmarkName is required' };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const bookmark = this.cameraBookmarks.get(trimmedName);
|
|
345
|
+
if (!bookmark) {
|
|
346
|
+
return {
|
|
347
|
+
success: false as const,
|
|
348
|
+
error: `Bookmark '${trimmedName}' not found`
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
await this.setViewportCamera(
|
|
353
|
+
{ x: bookmark.location[0], y: bookmark.location[1], z: bookmark.location[2] },
|
|
354
|
+
{ pitch: bookmark.rotation[0], yaw: bookmark.rotation[1], roll: bookmark.rotation[2] }
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
success: true as const,
|
|
359
|
+
message: `Jumped to bookmark '${trimmedName}'`
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>) {
|
|
364
|
+
const resolvedCategory = typeof category === 'string' && category.trim().length > 0 ? category.trim() : 'General';
|
|
365
|
+
const existing = this.editorPreferences.get(resolvedCategory) ?? {};
|
|
366
|
+
this.editorPreferences.set(resolvedCategory, { ...existing, ...preferences });
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
success: true as const,
|
|
370
|
+
message: `Preferences stored for ${resolvedCategory}`,
|
|
371
|
+
preferences: this.editorPreferences.get(resolvedCategory)
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async setViewportResolution(width: number, height: number) {
|
|
376
|
+
try {
|
|
377
|
+
// Clamp to reasonable limits
|
|
378
|
+
const clampedWidth = Math.max(320, Math.min(7680, width));
|
|
379
|
+
const clampedHeight = Math.max(240, Math.min(4320, height));
|
|
380
|
+
|
|
381
|
+
// Use console command directly instead of Python
|
|
382
|
+
const command = `r.SetRes ${clampedWidth}x${clampedHeight}`;
|
|
383
|
+
await this.bridge.executeConsoleCommand(command);
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
success: true,
|
|
387
|
+
message: `Viewport resolution set to ${clampedWidth}x${clampedHeight}`,
|
|
388
|
+
width: clampedWidth,
|
|
389
|
+
height: clampedHeight
|
|
390
|
+
};
|
|
391
|
+
} catch (err) {
|
|
392
|
+
return { success: false, error: `Failed to set viewport resolution: ${err}` };
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async executeConsoleCommand(command: string) {
|
|
397
|
+
try {
|
|
398
|
+
// Sanitize and validate command
|
|
399
|
+
if (!command || typeof command !== 'string') {
|
|
400
|
+
return { success: false, error: 'Invalid command: must be a non-empty string' };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (command.length > 1000) {
|
|
404
|
+
return {
|
|
405
|
+
success: false,
|
|
406
|
+
error: `Command too long (${command.length} chars). Maximum is 1000 characters.`
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const res = await this.bridge.executeConsoleCommand(command);
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
success: true,
|
|
414
|
+
message: `Console command executed: ${command}`,
|
|
415
|
+
output: res
|
|
416
|
+
};
|
|
417
|
+
} catch (err) {
|
|
418
|
+
return { success: false, error: `Failed to execute console command: ${err}` };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
358
421
|
}
|
package/src/tools/engine.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
import { AutomationBridge } from '../automation/index.js';
|
|
2
3
|
import { loadEnv } from '../types/env.js';
|
|
3
4
|
import { spawn } from 'child_process';
|
|
4
5
|
|
|
5
6
|
export class EngineTools {
|
|
6
7
|
private env = loadEnv();
|
|
7
|
-
constructor(private
|
|
8
|
+
constructor(_bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
9
|
+
|
|
10
|
+
setAutomationBridge(automationBridge?: AutomationBridge) {
|
|
11
|
+
this.automationBridge = automationBridge;
|
|
12
|
+
}
|
|
8
13
|
|
|
9
14
|
async launchEditor(params?: { editorExe?: string; projectPath?: string }) {
|
|
10
15
|
const exe = params?.editorExe || this.env.UE_EDITOR_EXE;
|
|
@@ -21,9 +26,15 @@ export class EngineTools {
|
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
async quitEditor() {
|
|
29
|
+
if (!this.automationBridge) {
|
|
30
|
+
return { success: false, error: 'AUTOMATION_BRIDGE_UNAVAILABLE', message: 'Automation bridge is not available for quit_editor' };
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
const resp: any = await this.automationBridge.sendAutomationRequest('quit_editor', {});
|
|
35
|
+
if (resp && resp.success === false) {
|
|
36
|
+
return { success: false, error: resp.error || resp.message || 'Quit request failed' };
|
|
37
|
+
}
|
|
27
38
|
return { success: true, message: 'Quit command sent' };
|
|
28
39
|
} catch (err: any) {
|
|
29
40
|
return { success: false, error: String(err?.message || err) };
|