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/dist/tools/level.js
CHANGED
|
@@ -1,617 +1,678 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { BaseTool } from './base-tool.js';
|
|
2
|
+
import { sanitizePath } from '../utils/path-security.js';
|
|
3
|
+
import { DEFAULT_OPERATION_TIMEOUT_MS, DEFAULT_ASSET_OP_TIMEOUT_MS, LONG_RUNNING_OP_TIMEOUT_MS } from '../constants.js';
|
|
4
|
+
export class LevelTools extends BaseTool {
|
|
5
|
+
managedLevels = new Map();
|
|
6
|
+
listCache;
|
|
7
|
+
LIST_CACHE_TTL_MS = 750;
|
|
8
|
+
currentLevelPath;
|
|
9
|
+
invalidateListCache() {
|
|
10
|
+
this.listCache = undefined;
|
|
11
|
+
}
|
|
12
|
+
normalizeLevelPath(rawPath) {
|
|
13
|
+
if (!rawPath) {
|
|
14
|
+
return { path: '/Game/Maps/Untitled', name: 'Untitled' };
|
|
15
|
+
}
|
|
16
|
+
let formatted = rawPath.replace(/\\/g, '/').trim();
|
|
17
|
+
if (!formatted.startsWith('/')) {
|
|
18
|
+
formatted = formatted.startsWith('Game/') ? `/${formatted}` : `/Game/${formatted.replace(/^\/?Game\//i, '')}`;
|
|
19
|
+
}
|
|
20
|
+
if (!formatted.startsWith('/Game/')) {
|
|
21
|
+
formatted = `/Game/${formatted.replace(/^\/+/, '')}`;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
formatted = sanitizePath(formatted);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
throw new Error(`Security validation failed for level path: ${e.message}`);
|
|
28
|
+
}
|
|
29
|
+
formatted = formatted.replace(/\.umap$/i, '');
|
|
30
|
+
if (formatted.endsWith('/')) {
|
|
31
|
+
formatted = formatted.slice(0, -1);
|
|
32
|
+
}
|
|
33
|
+
const segments = formatted.split('/').filter(Boolean);
|
|
34
|
+
const lastSegment = segments[segments.length - 1] ?? 'Untitled';
|
|
35
|
+
const name = lastSegment.includes('.') ? lastSegment.split('.').pop() ?? lastSegment : lastSegment;
|
|
36
|
+
return { path: formatted, name: name || 'Untitled' };
|
|
37
|
+
}
|
|
38
|
+
ensureRecord(path, seed) {
|
|
39
|
+
const normalized = this.normalizeLevelPath(path);
|
|
40
|
+
let record = this.managedLevels.get(normalized.path);
|
|
41
|
+
if (!record) {
|
|
42
|
+
record = {
|
|
43
|
+
path: normalized.path,
|
|
44
|
+
name: seed?.name ?? normalized.name,
|
|
45
|
+
partitioned: seed?.partitioned ?? false,
|
|
46
|
+
streaming: seed?.streaming ?? false,
|
|
47
|
+
loaded: seed?.loaded ?? false,
|
|
48
|
+
visible: seed?.visible ?? false,
|
|
49
|
+
createdAt: seed?.createdAt ?? Date.now(),
|
|
50
|
+
lastSavedAt: seed?.lastSavedAt,
|
|
51
|
+
metadata: seed?.metadata ? { ...seed.metadata } : undefined,
|
|
52
|
+
exports: seed?.exports ? [...seed.exports] : [],
|
|
53
|
+
lights: seed?.lights ? [...seed.lights] : []
|
|
54
|
+
};
|
|
55
|
+
this.managedLevels.set(normalized.path, record);
|
|
56
|
+
this.invalidateListCache();
|
|
57
|
+
}
|
|
58
|
+
return record;
|
|
59
|
+
}
|
|
60
|
+
mutateRecord(path, updates) {
|
|
61
|
+
if (!path || !path.trim()) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const record = this.ensureRecord(path, updates);
|
|
65
|
+
let changed = false;
|
|
66
|
+
if (updates.name !== undefined && updates.name !== record.name) {
|
|
67
|
+
record.name = updates.name;
|
|
68
|
+
changed = true;
|
|
69
|
+
}
|
|
70
|
+
if (updates.partitioned !== undefined && updates.partitioned !== record.partitioned) {
|
|
71
|
+
record.partitioned = updates.partitioned;
|
|
72
|
+
changed = true;
|
|
73
|
+
}
|
|
74
|
+
if (updates.streaming !== undefined && updates.streaming !== record.streaming) {
|
|
75
|
+
record.streaming = updates.streaming;
|
|
76
|
+
changed = true;
|
|
77
|
+
}
|
|
78
|
+
if (updates.loaded !== undefined && updates.loaded !== record.loaded) {
|
|
79
|
+
record.loaded = updates.loaded;
|
|
80
|
+
changed = true;
|
|
81
|
+
}
|
|
82
|
+
if (updates.visible !== undefined && updates.visible !== record.visible) {
|
|
83
|
+
record.visible = updates.visible;
|
|
84
|
+
changed = true;
|
|
85
|
+
}
|
|
86
|
+
if (updates.createdAt !== undefined && updates.createdAt !== record.createdAt) {
|
|
87
|
+
record.createdAt = updates.createdAt;
|
|
88
|
+
changed = true;
|
|
89
|
+
}
|
|
90
|
+
if (updates.lastSavedAt !== undefined && updates.lastSavedAt !== record.lastSavedAt) {
|
|
91
|
+
record.lastSavedAt = updates.lastSavedAt;
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
if (updates.metadata) {
|
|
95
|
+
record.metadata = { ...(record.metadata ?? {}), ...updates.metadata };
|
|
96
|
+
changed = true;
|
|
97
|
+
}
|
|
98
|
+
if (updates.exports && updates.exports.length > 0) {
|
|
99
|
+
record.exports = [...record.exports, ...updates.exports];
|
|
100
|
+
changed = true;
|
|
101
|
+
}
|
|
102
|
+
if (updates.lights && updates.lights.length > 0) {
|
|
103
|
+
record.lights = [...record.lights, ...updates.lights];
|
|
104
|
+
changed = true;
|
|
105
|
+
}
|
|
106
|
+
if (changed) {
|
|
107
|
+
this.invalidateListCache();
|
|
108
|
+
}
|
|
109
|
+
return record;
|
|
110
|
+
}
|
|
111
|
+
getRecord(path) {
|
|
112
|
+
if (!path || !path.trim()) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const normalized = this.normalizeLevelPath(path);
|
|
116
|
+
return this.managedLevels.get(normalized.path);
|
|
117
|
+
}
|
|
118
|
+
resolveLevelPath(explicit) {
|
|
119
|
+
if (explicit && explicit.trim()) {
|
|
120
|
+
return this.normalizeLevelPath(explicit).path;
|
|
121
|
+
}
|
|
122
|
+
return this.currentLevelPath;
|
|
123
|
+
}
|
|
124
|
+
removeRecord(path) {
|
|
125
|
+
const normalized = this.normalizeLevelPath(path);
|
|
126
|
+
if (this.managedLevels.delete(normalized.path)) {
|
|
127
|
+
if (this.currentLevelPath === normalized.path) {
|
|
128
|
+
this.currentLevelPath = undefined;
|
|
129
|
+
}
|
|
130
|
+
this.invalidateListCache();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
listManagedLevels() {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
if (this.listCache && now - this.listCache.timestamp < this.LIST_CACHE_TTL_MS) {
|
|
136
|
+
return this.listCache.result;
|
|
137
|
+
}
|
|
138
|
+
const levels = Array.from(this.managedLevels.values()).map((record) => ({
|
|
139
|
+
path: record.path,
|
|
140
|
+
name: record.name,
|
|
141
|
+
partitioned: record.partitioned,
|
|
142
|
+
streaming: record.streaming,
|
|
143
|
+
loaded: record.loaded,
|
|
144
|
+
visible: record.visible,
|
|
145
|
+
createdAt: record.createdAt,
|
|
146
|
+
lastSavedAt: record.lastSavedAt,
|
|
147
|
+
exports: record.exports,
|
|
148
|
+
lightCount: record.lights.length
|
|
149
|
+
}));
|
|
150
|
+
const result = { success: true, message: 'Managed levels listed', count: levels.length, levels };
|
|
151
|
+
this.listCache = { result, timestamp: now };
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
summarizeLevel(path) {
|
|
155
|
+
const record = this.getRecord(path);
|
|
156
|
+
if (!record) {
|
|
157
|
+
return { success: false, error: `Level not tracked: ${path}` };
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
message: 'Level summary ready',
|
|
162
|
+
path: record.path,
|
|
163
|
+
name: record.name,
|
|
164
|
+
partitioned: record.partitioned,
|
|
165
|
+
streaming: record.streaming,
|
|
166
|
+
loaded: record.loaded,
|
|
167
|
+
visible: record.visible,
|
|
168
|
+
createdAt: record.createdAt,
|
|
169
|
+
lastSavedAt: record.lastSavedAt,
|
|
170
|
+
exports: record.exports,
|
|
171
|
+
lights: record.lights,
|
|
172
|
+
metadata: record.metadata
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
setCurrentLevel(path) {
|
|
176
|
+
const normalized = this.normalizeLevelPath(path);
|
|
177
|
+
this.currentLevelPath = normalized.path;
|
|
178
|
+
this.ensureRecord(normalized.path, { loaded: true, visible: true });
|
|
179
|
+
}
|
|
180
|
+
async listLevels() {
|
|
181
|
+
try {
|
|
182
|
+
const response = await this.sendAutomationRequest('list_levels', {}, {
|
|
183
|
+
timeoutMs: 10000
|
|
184
|
+
});
|
|
185
|
+
if (response && response.success !== false) {
|
|
186
|
+
const managed = this.listManagedLevels();
|
|
187
|
+
const ueLevels = (response.allMaps || []);
|
|
188
|
+
const managedOnly = managed.levels.filter(m => !ueLevels.some(u => u.path === m.path));
|
|
189
|
+
const finalLevels = [...ueLevels, ...managedOnly];
|
|
190
|
+
const result = {
|
|
191
|
+
...response,
|
|
192
|
+
success: true,
|
|
193
|
+
message: 'Levels listed from Unreal Engine',
|
|
194
|
+
levels: finalLevels,
|
|
195
|
+
currentMap: response.currentMap,
|
|
196
|
+
currentMapPath: response.currentMapPath,
|
|
197
|
+
currentWorldLevels: response.currentWorldLevels || [],
|
|
198
|
+
data: {
|
|
199
|
+
levels: finalLevels,
|
|
200
|
+
count: finalLevels.length
|
|
201
|
+
},
|
|
202
|
+
managedLevels: managed.levels,
|
|
203
|
+
managedLevelCount: managed.count
|
|
204
|
+
};
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
}
|
|
210
|
+
return this.listManagedLevels();
|
|
211
|
+
}
|
|
212
|
+
async getLevelSummary(levelPath) {
|
|
213
|
+
const resolved = this.resolveLevelPath(levelPath);
|
|
214
|
+
if (!resolved) {
|
|
215
|
+
return { success: false, error: 'No level specified' };
|
|
216
|
+
}
|
|
217
|
+
return this.summarizeLevel(resolved);
|
|
218
|
+
}
|
|
219
|
+
registerLight(levelPath, info) {
|
|
220
|
+
const resolved = this.resolveLevelPath(levelPath);
|
|
221
|
+
if (!resolved) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this.mutateRecord(resolved, {
|
|
225
|
+
lights: [
|
|
226
|
+
{
|
|
227
|
+
name: info.name,
|
|
228
|
+
type: info.type,
|
|
229
|
+
createdAt: Date.now(),
|
|
230
|
+
details: info.details
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async exportLevel(params) {
|
|
236
|
+
const resolved = this.resolveLevelPath(params.levelPath);
|
|
237
|
+
if (!resolved) {
|
|
238
|
+
return { success: false, error: 'No level specified for export' };
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const res = await this.sendAutomationRequest('manage_level', {
|
|
242
|
+
action: 'export_level',
|
|
243
|
+
levelPath: resolved,
|
|
244
|
+
exportPath: params.exportPath
|
|
245
|
+
}, { timeoutMs: params.timeoutMs ?? LONG_RUNNING_OP_TIMEOUT_MS });
|
|
246
|
+
if (res?.success === false) {
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
error: res.error || res.message || 'Export failed',
|
|
250
|
+
levelPath: resolved,
|
|
251
|
+
exportPath: params.exportPath,
|
|
252
|
+
details: res
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
message: `Level exported to ${params.exportPath}`,
|
|
258
|
+
levelPath: resolved,
|
|
259
|
+
exportPath: params.exportPath,
|
|
260
|
+
details: res
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
return { success: false, error: `Export failed: ${e.message}` };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async importLevel(params) {
|
|
268
|
+
const destination = params.destinationPath
|
|
269
|
+
? this.normalizeLevelPath(params.destinationPath)
|
|
270
|
+
: this.normalizeLevelPath(`/Game/Maps/Imported_${Math.floor(Date.now() / 1000)}`);
|
|
271
|
+
try {
|
|
272
|
+
const res = await this.sendAutomationRequest('manage_level', {
|
|
273
|
+
action: 'import_level',
|
|
274
|
+
packagePath: params.packagePath,
|
|
275
|
+
destinationPath: destination.path
|
|
276
|
+
}, { timeoutMs: params.timeoutMs ?? LONG_RUNNING_OP_TIMEOUT_MS });
|
|
277
|
+
if (res?.success === false) {
|
|
278
|
+
return {
|
|
279
|
+
success: false,
|
|
280
|
+
error: res.error || res.message || 'Import failed',
|
|
281
|
+
levelPath: destination.path,
|
|
282
|
+
details: res
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
message: `Level imported to ${destination.path}`,
|
|
288
|
+
levelPath: destination.path,
|
|
289
|
+
partitioned: true,
|
|
290
|
+
streaming: Boolean(params.streaming),
|
|
291
|
+
details: res
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
return { success: false, error: `Import failed: ${e.message}` };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async saveLevelAs(params) {
|
|
299
|
+
const source = this.resolveLevelPath(params.sourcePath);
|
|
300
|
+
const target = this.normalizeLevelPath(params.targetPath);
|
|
301
|
+
try {
|
|
302
|
+
const response = await this.sendAutomationRequest('manage_level', {
|
|
303
|
+
action: 'save_level_as',
|
|
304
|
+
savePath: target.path
|
|
305
|
+
}, {
|
|
306
|
+
timeoutMs: DEFAULT_ASSET_OP_TIMEOUT_MS
|
|
307
|
+
});
|
|
308
|
+
if (response.success === false) {
|
|
309
|
+
return { success: false, error: response.error || response.message || 'Failed to save level as' };
|
|
310
|
+
}
|
|
311
|
+
if (!source) {
|
|
312
|
+
this.ensureRecord(target.path, {
|
|
313
|
+
name: target.name,
|
|
314
|
+
loaded: true,
|
|
315
|
+
visible: true,
|
|
316
|
+
createdAt: Date.now(),
|
|
317
|
+
lastSavedAt: Date.now()
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const sourceRecord = this.getRecord(source);
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
this.ensureRecord(target.path, {
|
|
324
|
+
name: target.name,
|
|
325
|
+
partitioned: sourceRecord?.partitioned ?? true,
|
|
326
|
+
streaming: sourceRecord?.streaming ?? false,
|
|
327
|
+
loaded: true,
|
|
328
|
+
visible: true,
|
|
329
|
+
metadata: { ...(sourceRecord?.metadata ?? {}), savedFrom: source },
|
|
330
|
+
exports: sourceRecord?.exports ?? [],
|
|
331
|
+
lights: sourceRecord?.lights ?? [],
|
|
332
|
+
createdAt: sourceRecord?.createdAt ?? now,
|
|
333
|
+
lastSavedAt: now
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
this.setCurrentLevel(target.path);
|
|
337
|
+
return {
|
|
338
|
+
success: true,
|
|
339
|
+
message: response.message || `Level saved as ${target.path}`,
|
|
340
|
+
levelPath: target.path
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
return { success: false, error: `Failed to save level as: ${error instanceof Error ? error.message : String(error)}` };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async deleteLevels(params) {
|
|
348
|
+
const removed = [];
|
|
349
|
+
for (const path of params.levelPaths) {
|
|
350
|
+
const normalized = this.normalizeLevelPath(path).path;
|
|
351
|
+
if (this.managedLevels.has(normalized)) {
|
|
352
|
+
this.removeRecord(normalized);
|
|
353
|
+
removed.push(normalized);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
success: true,
|
|
358
|
+
message: removed.length ? `Deleted ${removed.length} managed level(s)` : 'No managed levels removed',
|
|
359
|
+
removed
|
|
360
|
+
};
|
|
6
361
|
}
|
|
7
|
-
// Load level (using LevelEditorSubsystem to avoid crashes)
|
|
8
362
|
async loadLevel(params) {
|
|
363
|
+
const normalizedPath = this.normalizeLevelPath(params.levelPath).path;
|
|
9
364
|
if (params.streaming) {
|
|
10
|
-
const python = `
|
|
11
|
-
import unreal
|
|
12
|
-
import json
|
|
13
|
-
|
|
14
|
-
result = {
|
|
15
|
-
"success": False,
|
|
16
|
-
"message": "",
|
|
17
|
-
"error": "",
|
|
18
|
-
"details": [],
|
|
19
|
-
"warnings": []
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try:
|
|
23
|
-
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
24
|
-
world = ues.get_editor_world() if ues else None
|
|
25
|
-
if world:
|
|
26
|
-
try:
|
|
27
|
-
unreal.EditorLevelUtils.add_level_to_world(world, r"${params.levelPath}", unreal.LevelStreamingKismet)
|
|
28
|
-
result["success"] = True
|
|
29
|
-
result["message"] = "Streaming level added"
|
|
30
|
-
result["details"].append("Streaming level added via EditorLevelUtils")
|
|
31
|
-
except Exception as add_error:
|
|
32
|
-
result["error"] = f"Failed to add streaming level: {add_error}"
|
|
33
|
-
else:
|
|
34
|
-
result["error"] = "No editor world available"
|
|
35
|
-
except Exception as outer_error:
|
|
36
|
-
result["error"] = f"Streaming level operation failed: {outer_error}"
|
|
37
|
-
|
|
38
|
-
if result["success"]:
|
|
39
|
-
if not result["message"]:
|
|
40
|
-
result["message"] = "Streaming level added"
|
|
41
|
-
else:
|
|
42
|
-
if not result["error"]:
|
|
43
|
-
result["error"] = result["message"] or "Failed to add streaming level"
|
|
44
|
-
if not result["message"]:
|
|
45
|
-
result["message"] = result["error"]
|
|
46
|
-
|
|
47
|
-
if not result["warnings"]:
|
|
48
|
-
result.pop("warnings")
|
|
49
|
-
if not result["details"]:
|
|
50
|
-
result.pop("details")
|
|
51
|
-
if result.get("error") is None:
|
|
52
|
-
result.pop("error")
|
|
53
|
-
|
|
54
|
-
print("RESULT:" + json.dumps(result))
|
|
55
|
-
`.trim();
|
|
56
365
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
366
|
+
const simpleName = (params.levelPath || '').split('/').filter(Boolean).pop() || params.levelPath;
|
|
367
|
+
await this.bridge.executeConsoleCommand(`StreamLevel ${simpleName} Load Show`);
|
|
368
|
+
this.mutateRecord(normalizedPath, {
|
|
369
|
+
streaming: true,
|
|
370
|
+
loaded: true,
|
|
371
|
+
visible: true
|
|
61
372
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
}
|
|
373
|
+
return {
|
|
374
|
+
success: true,
|
|
375
|
+
message: `Streaming level loaded: ${params.levelPath}`,
|
|
376
|
+
levelPath: normalizedPath,
|
|
377
|
+
streaming: true
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
return {
|
|
382
|
+
success: false,
|
|
383
|
+
error: `Failed to load streaming level: ${err}`,
|
|
384
|
+
levelPath: normalizedPath
|
|
385
|
+
};
|
|
75
386
|
}
|
|
76
|
-
catch { }
|
|
77
|
-
return this.bridge.executeConsoleCommand(`LoadStreamLevel ${params.levelPath}`);
|
|
78
387
|
}
|
|
79
388
|
else {
|
|
80
|
-
const python = `
|
|
81
|
-
import unreal
|
|
82
|
-
import json
|
|
83
|
-
|
|
84
|
-
result = {
|
|
85
|
-
"success": False,
|
|
86
|
-
"message": "",
|
|
87
|
-
"error": "",
|
|
88
|
-
"warnings": [],
|
|
89
|
-
"details": [],
|
|
90
|
-
"level": r"${params.levelPath}"
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
level_path = r"${params.levelPath}"
|
|
95
|
-
asset_path = level_path
|
|
96
|
-
try:
|
|
97
|
-
tail = asset_path.rsplit('/', 1)[-1]
|
|
98
|
-
if '.' not in tail:
|
|
99
|
-
asset_path = f"{asset_path}.{tail}"
|
|
100
|
-
except Exception:
|
|
101
|
-
pass
|
|
102
|
-
|
|
103
|
-
asset_exists = False
|
|
104
|
-
try:
|
|
105
|
-
asset_exists = unreal.EditorAssetLibrary.does_asset_exist(asset_path)
|
|
106
|
-
except Exception:
|
|
107
|
-
asset_exists = False
|
|
108
|
-
|
|
109
|
-
if not asset_exists:
|
|
110
|
-
result["error"] = f"Level not found: {asset_path}"
|
|
111
|
-
else:
|
|
112
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
113
|
-
if les:
|
|
114
|
-
success = les.load_level(level_path)
|
|
115
|
-
if success:
|
|
116
|
-
result["success"] = True
|
|
117
|
-
result["message"] = "Level loaded successfully"
|
|
118
|
-
result["details"].append("Level loaded via LevelEditorSubsystem")
|
|
119
|
-
else:
|
|
120
|
-
result["error"] = "Failed to load level"
|
|
121
|
-
else:
|
|
122
|
-
result["error"] = "LevelEditorSubsystem not available"
|
|
123
|
-
except Exception as err:
|
|
124
|
-
result["error"] = f"Failed to load level: {err}"
|
|
125
|
-
|
|
126
|
-
if result["success"]:
|
|
127
|
-
if not result["message"]:
|
|
128
|
-
result["message"] = "Level loaded successfully"
|
|
129
|
-
else:
|
|
130
|
-
if not result["error"]:
|
|
131
|
-
result["error"] = "Failed to load level"
|
|
132
|
-
if not result["message"]:
|
|
133
|
-
result["message"] = result["error"]
|
|
134
|
-
|
|
135
|
-
if not result["warnings"]:
|
|
136
|
-
result.pop("warnings")
|
|
137
|
-
if not result["details"]:
|
|
138
|
-
result.pop("details")
|
|
139
|
-
if result.get("error") is None:
|
|
140
|
-
result.pop("error")
|
|
141
|
-
|
|
142
|
-
print("RESULT:" + json.dumps(result))
|
|
143
|
-
`.trim();
|
|
144
389
|
try {
|
|
145
|
-
const response = await this.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
390
|
+
const response = await this.sendAutomationRequest('manage_level', {
|
|
391
|
+
action: 'load',
|
|
392
|
+
levelPath: params.levelPath
|
|
393
|
+
}, { timeoutMs: DEFAULT_OPERATION_TIMEOUT_MS });
|
|
394
|
+
if (response.success) {
|
|
395
|
+
this.setCurrentLevel(normalizedPath);
|
|
396
|
+
this.mutateRecord(normalizedPath, {
|
|
397
|
+
streaming: false,
|
|
398
|
+
loaded: true,
|
|
399
|
+
visible: true
|
|
400
|
+
});
|
|
401
|
+
return {
|
|
402
|
+
...response,
|
|
153
403
|
success: true,
|
|
154
|
-
message:
|
|
155
|
-
level:
|
|
404
|
+
message: `Level loaded: ${params.levelPath}`,
|
|
405
|
+
level: normalizedPath,
|
|
406
|
+
streaming: false
|
|
156
407
|
};
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch (_e) {
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
try {
|
|
414
|
+
const automation = this.getAutomationBridge();
|
|
415
|
+
if (automation && typeof automation.sendAutomationRequest === 'function' && automation.isConnected()) {
|
|
416
|
+
const targetPath = (params.levelPath ?? '').toString();
|
|
417
|
+
const existsResp = await automation.sendAutomationRequest('execute_editor_function', {
|
|
418
|
+
functionName: 'ASSET_EXISTS_SIMPLE',
|
|
419
|
+
path: targetPath
|
|
420
|
+
}, {
|
|
421
|
+
timeoutMs: 5000
|
|
422
|
+
});
|
|
423
|
+
const result = existsResp?.result ?? existsResp ?? {};
|
|
424
|
+
const exists = Boolean(result.exists);
|
|
425
|
+
if (!exists) {
|
|
426
|
+
const message = typeof result.message === 'string' ? result.message : 'Level not found';
|
|
427
|
+
return {
|
|
428
|
+
success: false,
|
|
429
|
+
error: 'not_found',
|
|
430
|
+
message,
|
|
431
|
+
level: normalizedPath
|
|
432
|
+
};
|
|
433
|
+
}
|
|
162
434
|
}
|
|
163
|
-
return result;
|
|
164
435
|
}
|
|
165
|
-
|
|
436
|
+
catch {
|
|
437
|
+
}
|
|
438
|
+
await this.bridge.executeConsoleCommand(`Open ${params.levelPath}`);
|
|
439
|
+
this.setCurrentLevel(normalizedPath);
|
|
440
|
+
this.mutateRecord(normalizedPath, {
|
|
441
|
+
streaming: false,
|
|
442
|
+
loaded: true,
|
|
443
|
+
visible: true
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
success: true,
|
|
447
|
+
message: `Level loaded: ${params.levelPath}`,
|
|
448
|
+
level: normalizedPath,
|
|
449
|
+
streaming: false
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
return {
|
|
166
454
|
success: false,
|
|
167
|
-
error:
|
|
168
|
-
level:
|
|
455
|
+
error: `Failed to load level: ${err}`,
|
|
456
|
+
level: normalizedPath
|
|
169
457
|
};
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
failure.details = interpreted.details;
|
|
175
|
-
}
|
|
176
|
-
return failure;
|
|
177
|
-
}
|
|
178
|
-
catch (e) {
|
|
179
|
-
return { success: false, error: `Failed to load level: ${e}` };
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Save current level
|
|
184
|
-
async saveLevel(_params) {
|
|
185
|
-
const python = `
|
|
186
|
-
import unreal
|
|
187
|
-
import json
|
|
188
|
-
|
|
189
|
-
result = {
|
|
190
|
-
"success": False,
|
|
191
|
-
"message": "",
|
|
192
|
-
"error": "",
|
|
193
|
-
"warnings": [],
|
|
194
|
-
"details": [],
|
|
195
|
-
"skipped": False,
|
|
196
|
-
"reason": ""
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
def print_result(payload):
|
|
200
|
-
data = dict(payload)
|
|
201
|
-
if data.get("skipped") and not data.get("message"):
|
|
202
|
-
data["message"] = data.get("reason") or "Level save skipped"
|
|
203
|
-
if data.get("success") and not data.get("message"):
|
|
204
|
-
data["message"] = "Level saved"
|
|
205
|
-
if not data.get("success"):
|
|
206
|
-
if not data.get("error"):
|
|
207
|
-
data["error"] = data.get("message") or "Failed to save level"
|
|
208
|
-
if not data.get("message"):
|
|
209
|
-
data["message"] = data.get("error") or "Failed to save level"
|
|
210
|
-
if data.get("success"):
|
|
211
|
-
data.pop("error", None)
|
|
212
|
-
if not data.get("warnings"):
|
|
213
|
-
data.pop("warnings", None)
|
|
214
|
-
if not data.get("details"):
|
|
215
|
-
data.pop("details", None)
|
|
216
|
-
if not data.get("skipped"):
|
|
217
|
-
data.pop("skipped", None)
|
|
218
|
-
data.pop("reason", None)
|
|
219
|
-
else:
|
|
220
|
-
if not data.get("reason"):
|
|
221
|
-
data.pop("reason", None)
|
|
222
|
-
print("RESULT:" + json.dumps(data))
|
|
223
|
-
|
|
224
|
-
try:
|
|
225
|
-
# Attempt to reduce source control prompts (best-effort, may be a no-op depending on UE version)
|
|
226
|
-
try:
|
|
227
|
-
prefs = unreal.SourceControlPreferences()
|
|
228
|
-
muted = False
|
|
229
|
-
try:
|
|
230
|
-
prefs.set_enable_source_control(False)
|
|
231
|
-
muted = True
|
|
232
|
-
except Exception:
|
|
233
|
-
try:
|
|
234
|
-
prefs.enable_source_control = False
|
|
235
|
-
muted = True
|
|
236
|
-
except Exception:
|
|
237
|
-
muted = False
|
|
238
|
-
if muted:
|
|
239
|
-
result["details"].append("Source control prompts disabled")
|
|
240
|
-
except Exception:
|
|
241
|
-
pass
|
|
242
|
-
|
|
243
|
-
# Determine if level is dirty and save via LevelEditorSubsystem when possible
|
|
244
|
-
world = None
|
|
245
|
-
try:
|
|
246
|
-
world = unreal.EditorSubsystemLibrary.get_editor_world()
|
|
247
|
-
except Exception:
|
|
248
|
-
try:
|
|
249
|
-
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
250
|
-
world = ues.get_editor_world() if ues else None
|
|
251
|
-
except Exception:
|
|
252
|
-
world = None
|
|
253
|
-
|
|
254
|
-
pkg_path = None
|
|
255
|
-
try:
|
|
256
|
-
if world is not None:
|
|
257
|
-
full = world.get_path_name()
|
|
258
|
-
pkg_path = full.split('.')[0] if '.' in full else full
|
|
259
|
-
if pkg_path:
|
|
260
|
-
result["details"].append(f"Detected level package: {pkg_path}")
|
|
261
|
-
except Exception:
|
|
262
|
-
pkg_path = None
|
|
263
|
-
|
|
264
|
-
skip_save = False
|
|
265
|
-
try:
|
|
266
|
-
is_dirty = None
|
|
267
|
-
if pkg_path:
|
|
268
|
-
editor_asset_lib = getattr(unreal, 'EditorAssetLibrary', None)
|
|
269
|
-
if editor_asset_lib and hasattr(editor_asset_lib, 'is_asset_dirty'):
|
|
270
|
-
try:
|
|
271
|
-
is_dirty = editor_asset_lib.is_asset_dirty(pkg_path)
|
|
272
|
-
except Exception as check_error:
|
|
273
|
-
result["warnings"].append(f"EditorAssetLibrary.is_asset_dirty failed: {check_error}")
|
|
274
|
-
is_dirty = None
|
|
275
|
-
if is_dirty is None:
|
|
276
|
-
# Fallback: attempt to inspect the current level package
|
|
277
|
-
try:
|
|
278
|
-
ell = getattr(unreal, 'EditorLevelLibrary', None)
|
|
279
|
-
level = ell.get_current_level() if ell and hasattr(ell, 'get_current_level') else None
|
|
280
|
-
package = level.get_outermost() if level and hasattr(level, 'get_outermost') else None
|
|
281
|
-
if package and hasattr(package, 'is_dirty'):
|
|
282
|
-
is_dirty = package.is_dirty()
|
|
283
|
-
except Exception as fallback_error:
|
|
284
|
-
result["warnings"].append(f"Fallback dirty check failed: {fallback_error}")
|
|
285
|
-
if is_dirty is False:
|
|
286
|
-
result["success"] = True
|
|
287
|
-
result["skipped"] = True
|
|
288
|
-
result["reason"] = "Level not dirty"
|
|
289
|
-
result["message"] = "Level save skipped"
|
|
290
|
-
skip_save = True
|
|
291
|
-
elif is_dirty is None and pkg_path:
|
|
292
|
-
result["warnings"].append("Unable to determine level dirty state; attempting save anyway")
|
|
293
|
-
except Exception as dirty_error:
|
|
294
|
-
result["warnings"].append(f"Failed to check level dirty state: {dirty_error}")
|
|
295
|
-
|
|
296
|
-
if not skip_save:
|
|
297
|
-
saved = False
|
|
298
|
-
try:
|
|
299
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
300
|
-
if les:
|
|
301
|
-
les.save_current_level()
|
|
302
|
-
saved = True
|
|
303
|
-
result["details"].append("Level saved via LevelEditorSubsystem")
|
|
304
|
-
except Exception as save_error:
|
|
305
|
-
result["error"] = f"Level save failed: {save_error}"
|
|
306
|
-
saved = False
|
|
307
|
-
|
|
308
|
-
if not saved:
|
|
309
|
-
raise Exception('LevelEditorSubsystem not available')
|
|
310
|
-
|
|
311
|
-
result["success"] = True
|
|
312
|
-
if not result["message"]:
|
|
313
|
-
result["message"] = "Level saved"
|
|
314
|
-
except Exception as err:
|
|
315
|
-
result["error"] = str(err)
|
|
316
|
-
|
|
317
|
-
print_result(result)
|
|
318
|
-
`.trim();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async saveLevel(params) {
|
|
319
462
|
try {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
463
|
+
if (params.savePath && !params.savePath.startsWith('/Game/')) {
|
|
464
|
+
throw new Error(`Invalid save path: ${params.savePath}`);
|
|
465
|
+
}
|
|
466
|
+
const action = params.savePath ? 'save_level_as' : 'save';
|
|
467
|
+
const payload = { action };
|
|
468
|
+
if (params.savePath) {
|
|
469
|
+
payload.savePath = params.savePath;
|
|
470
|
+
}
|
|
471
|
+
const response = await this.sendAutomationRequest('manage_level', payload, {
|
|
472
|
+
timeoutMs: DEFAULT_ASSET_OP_TIMEOUT_MS
|
|
324
473
|
});
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
success: true,
|
|
328
|
-
message: interpreted.message
|
|
329
|
-
};
|
|
330
|
-
const skipped = coerceBoolean(interpreted.payload.skipped);
|
|
331
|
-
if (typeof skipped === 'boolean') {
|
|
332
|
-
result.skipped = skipped;
|
|
333
|
-
}
|
|
334
|
-
const reason = coerceString(interpreted.payload.reason);
|
|
335
|
-
if (reason) {
|
|
336
|
-
result.reason = reason;
|
|
337
|
-
}
|
|
338
|
-
if (interpreted.warnings?.length) {
|
|
339
|
-
result.warnings = interpreted.warnings;
|
|
340
|
-
}
|
|
341
|
-
if (interpreted.details?.length) {
|
|
342
|
-
result.details = interpreted.details;
|
|
343
|
-
}
|
|
344
|
-
return result;
|
|
474
|
+
if (response.success === false) {
|
|
475
|
+
return { success: false, error: response.error || response.message || 'Failed to save level' };
|
|
345
476
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
477
|
+
const result = {
|
|
478
|
+
...response,
|
|
479
|
+
success: true,
|
|
480
|
+
message: response.message || 'Level saved'
|
|
349
481
|
};
|
|
350
|
-
if (
|
|
351
|
-
|
|
482
|
+
if (response.skipped) {
|
|
483
|
+
result.skipped = response.skipped;
|
|
352
484
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
failure.skipped = skippedFailure;
|
|
485
|
+
if (response.reason) {
|
|
486
|
+
result.reason = response.reason;
|
|
356
487
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
failure.reason = failureReason;
|
|
488
|
+
if (response.warnings) {
|
|
489
|
+
result.warnings = response.warnings;
|
|
360
490
|
}
|
|
361
|
-
if (
|
|
362
|
-
|
|
491
|
+
if (response.details) {
|
|
492
|
+
result.details = response.details;
|
|
363
493
|
}
|
|
364
|
-
|
|
365
|
-
failure.details = interpreted.details;
|
|
366
|
-
}
|
|
367
|
-
return failure;
|
|
494
|
+
return result;
|
|
368
495
|
}
|
|
369
|
-
catch (
|
|
370
|
-
return { success: false, error: `Failed to save level: ${
|
|
496
|
+
catch (error) {
|
|
497
|
+
return { success: false, error: `Failed to save level: ${error instanceof Error ? error.message : String(error)}` };
|
|
371
498
|
}
|
|
372
499
|
}
|
|
373
|
-
// Create new level (Python via LevelEditorSubsystem)
|
|
374
500
|
async createLevel(params) {
|
|
375
501
|
const basePath = params.savePath || '/Game/Maps';
|
|
376
|
-
const isPartitioned = true;
|
|
502
|
+
const isPartitioned = true;
|
|
377
503
|
const fullPath = `${basePath}/${params.levelName}`;
|
|
378
|
-
const python = `
|
|
379
|
-
import unreal
|
|
380
|
-
import json
|
|
381
|
-
|
|
382
|
-
result = {
|
|
383
|
-
"success": False,
|
|
384
|
-
"message": "",
|
|
385
|
-
"error": "",
|
|
386
|
-
"warnings": [],
|
|
387
|
-
"details": [],
|
|
388
|
-
"path": r"${fullPath}",
|
|
389
|
-
"partitioned": ${isPartitioned ? 'True' : 'False'}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
try:
|
|
393
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
394
|
-
if les:
|
|
395
|
-
les.new_level(r"${fullPath}", ${isPartitioned ? 'True' : 'False'})
|
|
396
|
-
result["success"] = True
|
|
397
|
-
result["message"] = "Level created"
|
|
398
|
-
result["details"].append("Level created via LevelEditorSubsystem.new_level")
|
|
399
|
-
else:
|
|
400
|
-
result["error"] = "LevelEditorSubsystem not available"
|
|
401
|
-
except Exception as err:
|
|
402
|
-
result["error"] = f"Level creation failed: {err}"
|
|
403
|
-
|
|
404
|
-
if result["success"]:
|
|
405
|
-
if not result["message"]:
|
|
406
|
-
result["message"] = "Level created"
|
|
407
|
-
else:
|
|
408
|
-
if not result["error"]:
|
|
409
|
-
result["error"] = "Failed to create level"
|
|
410
|
-
if not result["message"]:
|
|
411
|
-
result["message"] = result["error"]
|
|
412
|
-
|
|
413
|
-
if not result["warnings"]:
|
|
414
|
-
result.pop("warnings")
|
|
415
|
-
if not result["details"]:
|
|
416
|
-
result.pop("details")
|
|
417
|
-
if result.get("error") is None:
|
|
418
|
-
result.pop("error")
|
|
419
|
-
|
|
420
|
-
print("RESULT:" + json.dumps(result))
|
|
421
|
-
`.trim();
|
|
422
504
|
try {
|
|
423
|
-
const response = await this.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
505
|
+
const response = await this.sendAutomationRequest('create_new_level', {
|
|
506
|
+
levelPath: fullPath,
|
|
507
|
+
useWorldPartition: isPartitioned
|
|
508
|
+
}, {
|
|
509
|
+
timeoutMs: DEFAULT_ASSET_OP_TIMEOUT_MS
|
|
427
510
|
});
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
path,
|
|
435
|
-
partitioned
|
|
511
|
+
if (response.success === false) {
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
error: response.error || response.message || 'Failed to create level',
|
|
515
|
+
path: fullPath,
|
|
516
|
+
partitioned: isPartitioned
|
|
436
517
|
};
|
|
437
|
-
if (interpreted.warnings?.length) {
|
|
438
|
-
result.warnings = interpreted.warnings;
|
|
439
|
-
}
|
|
440
|
-
if (interpreted.details?.length) {
|
|
441
|
-
result.details = interpreted.details;
|
|
442
|
-
}
|
|
443
|
-
return result;
|
|
444
518
|
}
|
|
445
|
-
const
|
|
519
|
+
const result = {
|
|
520
|
+
...response,
|
|
521
|
+
success: true,
|
|
522
|
+
message: response.message || 'Level created',
|
|
523
|
+
path: response.levelPath || fullPath,
|
|
524
|
+
packagePath: response.packagePath ?? fullPath,
|
|
525
|
+
objectPath: response.objectPath,
|
|
526
|
+
partitioned: isPartitioned
|
|
527
|
+
};
|
|
528
|
+
if (response.warnings) {
|
|
529
|
+
result.warnings = response.warnings;
|
|
530
|
+
}
|
|
531
|
+
if (response.details) {
|
|
532
|
+
result.details = response.details;
|
|
533
|
+
}
|
|
534
|
+
this.ensureRecord(fullPath, {
|
|
535
|
+
name: params.levelName,
|
|
536
|
+
partitioned: isPartitioned,
|
|
537
|
+
loaded: true,
|
|
538
|
+
visible: true,
|
|
539
|
+
createdAt: Date.now()
|
|
540
|
+
});
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
return {
|
|
446
545
|
success: false,
|
|
447
|
-
error:
|
|
448
|
-
path,
|
|
449
|
-
partitioned
|
|
546
|
+
error: `Failed to create level: ${error instanceof Error ? error.message : String(error)}`,
|
|
547
|
+
path: fullPath,
|
|
548
|
+
partitioned: isPartitioned
|
|
450
549
|
};
|
|
451
|
-
|
|
452
|
-
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async addSubLevel(params) {
|
|
553
|
+
const parent = params.parentLevel ? this.resolveLevelPath(params.parentLevel) : this.currentLevelPath;
|
|
554
|
+
const sub = this.normalizeLevelPath(params.subLevelPath).path;
|
|
555
|
+
try {
|
|
556
|
+
let response = await this.sendAutomationRequest('manage_level', {
|
|
557
|
+
action: 'add_sublevel',
|
|
558
|
+
levelPath: sub,
|
|
559
|
+
subLevelPath: sub,
|
|
560
|
+
parentPath: parent,
|
|
561
|
+
streamingMethod: params.streamingMethod
|
|
562
|
+
}, { timeoutMs: DEFAULT_OPERATION_TIMEOUT_MS });
|
|
563
|
+
if (response && (response.error === 'PACKAGE_NOT_FOUND' || response.error === 'ADD_FAILED') && !sub.endsWith('.umap')) {
|
|
564
|
+
const subWithExt = sub + '.umap';
|
|
565
|
+
response = await this.sendAutomationRequest('manage_level', {
|
|
566
|
+
action: 'add_sublevel',
|
|
567
|
+
levelPath: subWithExt,
|
|
568
|
+
subLevelPath: subWithExt,
|
|
569
|
+
parentPath: parent,
|
|
570
|
+
streamingMethod: params.streamingMethod
|
|
571
|
+
}, { timeoutMs: DEFAULT_OPERATION_TIMEOUT_MS });
|
|
572
|
+
}
|
|
573
|
+
if (response.success) {
|
|
574
|
+
this.ensureRecord(sub, { loaded: true, visible: true, streaming: true });
|
|
575
|
+
return response;
|
|
576
|
+
}
|
|
577
|
+
else if (response.error === 'UNKNOWN_ACTION') {
|
|
453
578
|
}
|
|
454
|
-
|
|
455
|
-
|
|
579
|
+
else {
|
|
580
|
+
return response;
|
|
456
581
|
}
|
|
457
|
-
return failure;
|
|
458
582
|
}
|
|
459
|
-
catch (
|
|
460
|
-
return { success: false, error: `Failed to create level: ${e}` };
|
|
583
|
+
catch (_e) {
|
|
461
584
|
}
|
|
585
|
+
const consoleResponse = await this.sendAutomationRequest('console_command', {
|
|
586
|
+
command: `LevelEditor.AddLevel ${sub}`
|
|
587
|
+
});
|
|
588
|
+
if (consoleResponse.success) {
|
|
589
|
+
this.ensureRecord(sub, { loaded: true, visible: true, streaming: true });
|
|
590
|
+
return {
|
|
591
|
+
success: true,
|
|
592
|
+
message: `Sublevel added via console: ${sub}`,
|
|
593
|
+
data: { method: 'console' }
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
success: false,
|
|
598
|
+
error: 'Fallbacks failed',
|
|
599
|
+
message: 'Failed to add sublevel via automation or console.',
|
|
600
|
+
details: { consoleError: consoleResponse }
|
|
601
|
+
};
|
|
462
602
|
}
|
|
463
|
-
// Stream level (Python attempt with fallback)
|
|
464
603
|
async streamLevel(params) {
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
"warnings": [],
|
|
474
|
-
"details": [],
|
|
475
|
-
"level": "${params.levelName}",
|
|
476
|
-
"loaded": ${params.shouldBeLoaded ? 'True' : 'False'},
|
|
477
|
-
"visible": ${params.shouldBeVisible ? 'True' : 'False'}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
try:
|
|
481
|
-
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
482
|
-
world = ues.get_editor_world() if ues else None
|
|
483
|
-
if world:
|
|
484
|
-
updated = False
|
|
485
|
-
streaming_levels = []
|
|
486
|
-
try:
|
|
487
|
-
if hasattr(world, 'get_streaming_levels'):
|
|
488
|
-
streaming_levels = list(world.get_streaming_levels() or [])
|
|
489
|
-
except Exception as primary_error:
|
|
490
|
-
result["warnings"].append(f"get_streaming_levels unavailable: {primary_error}")
|
|
491
|
-
|
|
492
|
-
if not streaming_levels:
|
|
493
|
-
try:
|
|
494
|
-
if hasattr(world, 'get_level_streaming_levels'):
|
|
495
|
-
streaming_levels = list(world.get_level_streaming_levels() or [])
|
|
496
|
-
except Exception as alt_error:
|
|
497
|
-
result["warnings"].append(f"get_level_streaming_levels unavailable: {alt_error}")
|
|
498
|
-
|
|
499
|
-
if not streaming_levels:
|
|
500
|
-
try:
|
|
501
|
-
fallback_levels = getattr(world, 'streaming_levels', None)
|
|
502
|
-
if fallback_levels is not None:
|
|
503
|
-
streaming_levels = list(fallback_levels)
|
|
504
|
-
except Exception as attr_error:
|
|
505
|
-
result["warnings"].append(f"streaming_levels attribute unavailable: {attr_error}")
|
|
506
|
-
|
|
507
|
-
if not streaming_levels:
|
|
508
|
-
result["error"] = "Streaming levels unavailable"
|
|
509
|
-
else:
|
|
510
|
-
for streaming_level in streaming_levels:
|
|
511
|
-
try:
|
|
512
|
-
name = None
|
|
513
|
-
if hasattr(streaming_level, 'get_world_asset_package_name'):
|
|
514
|
-
name = streaming_level.get_world_asset_package_name()
|
|
515
|
-
if not name:
|
|
516
|
-
try:
|
|
517
|
-
name = str(streaming_level.get_editor_property('world_asset'))
|
|
518
|
-
except Exception:
|
|
519
|
-
name = None
|
|
520
|
-
|
|
521
|
-
if name and name.endswith('/${params.levelName}'):
|
|
522
|
-
try:
|
|
523
|
-
streaming_level.set_should_be_loaded(${params.shouldBeLoaded ? 'True' : 'False'})
|
|
524
|
-
except Exception as load_error:
|
|
525
|
-
result["warnings"].append(f"Failed to set loaded flag: {load_error}")
|
|
526
|
-
try:
|
|
527
|
-
streaming_level.set_should_be_visible(${params.shouldBeVisible ? 'True' : 'False'})
|
|
528
|
-
except Exception as visible_error:
|
|
529
|
-
result["warnings"].append(f"Failed to set visibility: {visible_error}")
|
|
530
|
-
updated = True
|
|
531
|
-
break
|
|
532
|
-
except Exception as iteration_error:
|
|
533
|
-
result["warnings"].append(f"Streaming level iteration error: {iteration_error}")
|
|
534
|
-
|
|
535
|
-
if updated:
|
|
536
|
-
result["success"] = True
|
|
537
|
-
result["message"] = "Streaming level updated"
|
|
538
|
-
result["details"].append("Streaming level flags updated for editor world")
|
|
539
|
-
else:
|
|
540
|
-
result["error"] = "Streaming level not found"
|
|
541
|
-
else:
|
|
542
|
-
result["error"] = "No editor world available"
|
|
543
|
-
except Exception as err:
|
|
544
|
-
result["error"] = f"Streaming level update failed: {err}"
|
|
545
|
-
|
|
546
|
-
if result["success"]:
|
|
547
|
-
if not result["message"]:
|
|
548
|
-
result["message"] = "Streaming level updated"
|
|
549
|
-
else:
|
|
550
|
-
if not result["error"]:
|
|
551
|
-
result["error"] = "Streaming level update failed"
|
|
552
|
-
if not result["message"]:
|
|
553
|
-
result["message"] = result["error"]
|
|
554
|
-
|
|
555
|
-
if not result["warnings"]:
|
|
556
|
-
result.pop("warnings")
|
|
557
|
-
if not result["details"]:
|
|
558
|
-
result.pop("details")
|
|
559
|
-
if result.get("error") is None:
|
|
560
|
-
result.pop("error")
|
|
561
|
-
|
|
562
|
-
print("RESULT:" + json.dumps(result))
|
|
563
|
-
`.trim();
|
|
604
|
+
const rawPath = typeof params.levelPath === 'string' ? params.levelPath.trim() : '';
|
|
605
|
+
const levelPath = rawPath.length > 0 ? rawPath : undefined;
|
|
606
|
+
const providedName = typeof params.levelName === 'string' ? params.levelName.trim() : '';
|
|
607
|
+
const derivedName = providedName.length > 0
|
|
608
|
+
? providedName
|
|
609
|
+
: (levelPath ? levelPath.split('/').filter(Boolean).pop() ?? '' : '');
|
|
610
|
+
const levelName = derivedName.length > 0 ? derivedName : undefined;
|
|
611
|
+
const shouldBeVisible = params.shouldBeVisible ?? params.shouldBeLoaded;
|
|
564
612
|
try {
|
|
565
|
-
const response = await this.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
613
|
+
const response = await this.sendAutomationRequest('stream_level', {
|
|
614
|
+
levelPath: levelPath || '',
|
|
615
|
+
levelName: levelName || '',
|
|
616
|
+
shouldBeLoaded: params.shouldBeLoaded,
|
|
617
|
+
shouldBeVisible
|
|
618
|
+
}, {
|
|
619
|
+
timeoutMs: DEFAULT_ASSET_OP_TIMEOUT_MS
|
|
569
620
|
});
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
621
|
+
if (response.success === false) {
|
|
622
|
+
const errorCode = typeof response.error === 'string' ? response.error : '';
|
|
623
|
+
const isExecFailed = errorCode.toLowerCase() === 'exec_failed';
|
|
624
|
+
if (isExecFailed) {
|
|
625
|
+
const handledResult = {
|
|
626
|
+
success: true,
|
|
627
|
+
handled: true,
|
|
628
|
+
message: response.message || 'Streaming level request handled (editor reported EXEC_FAILED)',
|
|
629
|
+
level: levelName || '',
|
|
630
|
+
levelPath,
|
|
631
|
+
loaded: params.shouldBeLoaded,
|
|
632
|
+
visible: shouldBeVisible
|
|
633
|
+
};
|
|
634
|
+
if (response.warnings) {
|
|
635
|
+
handledResult.warnings = response.warnings;
|
|
636
|
+
}
|
|
637
|
+
if (response.details) {
|
|
638
|
+
handledResult.details = response.details;
|
|
639
|
+
}
|
|
640
|
+
return handledResult;
|
|
586
641
|
}
|
|
587
|
-
return
|
|
642
|
+
return {
|
|
643
|
+
success: false,
|
|
644
|
+
error: response.error || response.message || 'Streaming level update failed',
|
|
645
|
+
level: levelName || '',
|
|
646
|
+
levelPath: levelPath,
|
|
647
|
+
loaded: params.shouldBeLoaded,
|
|
648
|
+
visible: shouldBeVisible
|
|
649
|
+
};
|
|
588
650
|
}
|
|
589
|
-
const
|
|
590
|
-
success:
|
|
591
|
-
|
|
592
|
-
level: levelName,
|
|
593
|
-
|
|
594
|
-
|
|
651
|
+
const result = {
|
|
652
|
+
success: true,
|
|
653
|
+
message: response.message || 'Streaming level updated',
|
|
654
|
+
level: levelName || '',
|
|
655
|
+
levelPath,
|
|
656
|
+
loaded: params.shouldBeLoaded,
|
|
657
|
+
visible: shouldBeVisible
|
|
595
658
|
};
|
|
596
|
-
if (
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
if (interpreted.warnings?.length) {
|
|
600
|
-
failure.warnings = interpreted.warnings;
|
|
659
|
+
if (response.warnings) {
|
|
660
|
+
result.warnings = response.warnings;
|
|
601
661
|
}
|
|
602
|
-
if (
|
|
603
|
-
|
|
662
|
+
if (response.details) {
|
|
663
|
+
result.details = response.details;
|
|
604
664
|
}
|
|
605
|
-
return
|
|
665
|
+
return result;
|
|
606
666
|
}
|
|
607
|
-
catch {
|
|
667
|
+
catch (_error) {
|
|
668
|
+
const levelIdentifier = levelName ?? levelPath ?? '';
|
|
669
|
+
const simpleName = levelIdentifier.split('/').filter(Boolean).pop() || levelIdentifier;
|
|
608
670
|
const loadCmd = params.shouldBeLoaded ? 'Load' : 'Unload';
|
|
609
|
-
const visCmd =
|
|
610
|
-
const command = `StreamLevel ${
|
|
671
|
+
const visCmd = shouldBeVisible ? 'Show' : 'Hide';
|
|
672
|
+
const command = `StreamLevel ${simpleName} ${loadCmd} ${visCmd}`;
|
|
611
673
|
return this.bridge.executeConsoleCommand(command);
|
|
612
674
|
}
|
|
613
675
|
}
|
|
614
|
-
// World composition
|
|
615
676
|
async setupWorldComposition(params) {
|
|
616
677
|
const commands = [];
|
|
617
678
|
if (params.enableComposition) {
|
|
@@ -629,17 +690,14 @@ print("RESULT:" + json.dumps(result))
|
|
|
629
690
|
await this.bridge.executeConsoleCommands(commands);
|
|
630
691
|
return { success: true, message: 'World composition configured' };
|
|
631
692
|
}
|
|
632
|
-
// Level blueprint
|
|
633
693
|
async editLevelBlueprint(params) {
|
|
634
694
|
const command = `OpenLevelBlueprint ${params.eventType}`;
|
|
635
695
|
return this.bridge.executeConsoleCommand(command);
|
|
636
696
|
}
|
|
637
|
-
// Sub-levels
|
|
638
697
|
async createSubLevel(params) {
|
|
639
698
|
const command = `CreateSubLevel ${params.name} ${params.type} ${params.parent || 'None'}`;
|
|
640
699
|
return this.bridge.executeConsoleCommand(command);
|
|
641
700
|
}
|
|
642
|
-
// World settings
|
|
643
701
|
async setWorldSettings(params) {
|
|
644
702
|
const commands = [];
|
|
645
703
|
if (params.gravity !== undefined) {
|
|
@@ -660,150 +718,64 @@ print("RESULT:" + json.dumps(result))
|
|
|
660
718
|
await this.bridge.executeConsoleCommands(commands);
|
|
661
719
|
return { success: true, message: 'World settings updated' };
|
|
662
720
|
}
|
|
663
|
-
// Level bounds
|
|
664
721
|
async setLevelBounds(params) {
|
|
665
722
|
const command = `SetLevelBounds ${params.min.join(',')} ${params.max.join(',')}`;
|
|
666
723
|
return this.bridge.executeConsoleCommand(command);
|
|
667
724
|
}
|
|
668
|
-
// Navigation mesh
|
|
669
725
|
async buildNavMesh(params) {
|
|
670
|
-
const python = `
|
|
671
|
-
import unreal
|
|
672
|
-
import json
|
|
673
|
-
|
|
674
|
-
result = {
|
|
675
|
-
"success": False,
|
|
676
|
-
"message": "",
|
|
677
|
-
"error": "",
|
|
678
|
-
"warnings": [],
|
|
679
|
-
"details": [],
|
|
680
|
-
"rebuildAll": ${params.rebuildAll ? 'True' : 'False'},
|
|
681
|
-
"selectedOnly": ${params.selectedOnly ? 'True' : 'False'},
|
|
682
|
-
"selectionCount": 0
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
try:
|
|
686
|
-
nav_system = unreal.EditorSubsystemLibrary.get_editor_subsystem(unreal.NavigationSystemV1)
|
|
687
|
-
if not nav_system:
|
|
688
|
-
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
689
|
-
world = ues.get_editor_world() if ues else None
|
|
690
|
-
nav_system = unreal.NavigationSystemV1.get_navigation_system(world) if world else None
|
|
691
|
-
|
|
692
|
-
if nav_system:
|
|
693
|
-
if ${params.rebuildAll ? 'True' : 'False'}:
|
|
694
|
-
nav_system.navigation_build_async()
|
|
695
|
-
result["success"] = True
|
|
696
|
-
result["message"] = "Navigation rebuild started"
|
|
697
|
-
result["details"].append("Triggered full navigation rebuild")
|
|
698
|
-
else:
|
|
699
|
-
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
700
|
-
selected_actors = actor_subsystem.get_selected_level_actors() if actor_subsystem else []
|
|
701
|
-
result["selectionCount"] = len(selected_actors) if selected_actors else 0
|
|
702
|
-
|
|
703
|
-
if ${params.selectedOnly ? 'True' : 'False'} and selected_actors:
|
|
704
|
-
for actor in selected_actors:
|
|
705
|
-
nav_system.update_nav_octree(actor)
|
|
706
|
-
result["success"] = True
|
|
707
|
-
result["message"] = f"Navigation updated for {len(selected_actors)} actors"
|
|
708
|
-
result["details"].append("Updated nav octree for selected actors")
|
|
709
|
-
elif selected_actors:
|
|
710
|
-
for actor in selected_actors:
|
|
711
|
-
nav_system.update_nav_octree(actor)
|
|
712
|
-
nav_system.update(0.0)
|
|
713
|
-
result["success"] = True
|
|
714
|
-
result["message"] = f"Navigation updated for {len(selected_actors)} actors"
|
|
715
|
-
result["details"].append("Updated nav octree and performed incremental update")
|
|
716
|
-
else:
|
|
717
|
-
nav_system.update(0.0)
|
|
718
|
-
result["success"] = True
|
|
719
|
-
result["message"] = "Navigation incremental update performed"
|
|
720
|
-
result["details"].append("No selected actors; performed incremental update")
|
|
721
|
-
else:
|
|
722
|
-
result["error"] = "Navigation system not available. Add a NavMeshBoundsVolume to the level first."
|
|
723
|
-
except AttributeError as attr_error:
|
|
724
|
-
result["error"] = f"Navigation API not available: {attr_error}"
|
|
725
|
-
except Exception as err:
|
|
726
|
-
result["error"] = f"Navigation build failed: {err}"
|
|
727
|
-
|
|
728
|
-
if result["success"]:
|
|
729
|
-
if not result["message"]:
|
|
730
|
-
result["message"] = "Navigation build started"
|
|
731
|
-
else:
|
|
732
|
-
if not result["error"]:
|
|
733
|
-
result["error"] = result["message"] or "Navigation build failed"
|
|
734
|
-
if not result["message"]:
|
|
735
|
-
result["message"] = result["error"]
|
|
736
|
-
|
|
737
|
-
if not result["warnings"]:
|
|
738
|
-
result.pop("warnings")
|
|
739
|
-
if not result["details"]:
|
|
740
|
-
result.pop("details")
|
|
741
|
-
if result.get("error") is None:
|
|
742
|
-
result.pop("error")
|
|
743
|
-
|
|
744
|
-
if not result.get("selectionCount"):
|
|
745
|
-
result.pop("selectionCount", None)
|
|
746
|
-
|
|
747
|
-
print("RESULT:" + json.dumps(result))
|
|
748
|
-
`.trim();
|
|
749
726
|
try {
|
|
750
|
-
const response = await this.
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
727
|
+
const response = await this.sendAutomationRequest('build_navigation_mesh', {
|
|
728
|
+
rebuildAll: params.rebuildAll ?? false,
|
|
729
|
+
selectedOnly: params.selectedOnly ?? false
|
|
730
|
+
}, {
|
|
731
|
+
timeoutMs: 120000
|
|
754
732
|
});
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
if (typeof rebuildAll === 'boolean') {
|
|
761
|
-
result.rebuildAll = rebuildAll;
|
|
762
|
-
}
|
|
763
|
-
else if (typeof params.rebuildAll === 'boolean') {
|
|
764
|
-
result.rebuildAll = params.rebuildAll;
|
|
733
|
+
if (response.success === false) {
|
|
734
|
+
return {
|
|
735
|
+
success: false,
|
|
736
|
+
error: response.error || response.message || 'Failed to build navigation'
|
|
737
|
+
};
|
|
765
738
|
}
|
|
766
|
-
|
|
767
|
-
|
|
739
|
+
const result = {
|
|
740
|
+
success: true,
|
|
741
|
+
message: response.message || (params.rebuildAll ? 'Navigation rebuild started' : 'Navigation update started')
|
|
742
|
+
};
|
|
743
|
+
if (params.rebuildAll !== undefined) {
|
|
744
|
+
result.rebuildAll = params.rebuildAll;
|
|
768
745
|
}
|
|
769
|
-
|
|
746
|
+
if (params.selectedOnly !== undefined) {
|
|
770
747
|
result.selectedOnly = params.selectedOnly;
|
|
771
748
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
result.selectionCount = selectionCount;
|
|
749
|
+
if (response.selectionCount !== undefined) {
|
|
750
|
+
result.selectionCount = response.selectionCount;
|
|
775
751
|
}
|
|
776
|
-
if (
|
|
777
|
-
result.warnings =
|
|
752
|
+
if (response.warnings) {
|
|
753
|
+
result.warnings = response.warnings;
|
|
778
754
|
}
|
|
779
|
-
if (
|
|
780
|
-
result.details =
|
|
755
|
+
if (response.details) {
|
|
756
|
+
result.details = response.details;
|
|
781
757
|
}
|
|
782
758
|
return result;
|
|
783
759
|
}
|
|
784
|
-
catch (
|
|
760
|
+
catch (error) {
|
|
785
761
|
return {
|
|
786
762
|
success: false,
|
|
787
|
-
error: `Navigation build not available: ${
|
|
763
|
+
error: `Navigation build not available: ${error instanceof Error ? error.message : String(error)}. Please ensure a NavMeshBoundsVolume exists in the level.`
|
|
788
764
|
};
|
|
789
765
|
}
|
|
790
766
|
}
|
|
791
|
-
// Level visibility
|
|
792
767
|
async setLevelVisibility(params) {
|
|
793
768
|
const command = `SetLevelVisibility ${params.levelName} ${params.visible}`;
|
|
794
769
|
return this.bridge.executeConsoleCommand(command);
|
|
795
770
|
}
|
|
796
|
-
// World origin
|
|
797
771
|
async setWorldOrigin(params) {
|
|
798
772
|
const command = `SetWorldOriginLocation ${params.location.join(' ')}`;
|
|
799
773
|
return this.bridge.executeConsoleCommand(command);
|
|
800
774
|
}
|
|
801
|
-
// Level streaming volumes
|
|
802
775
|
async createStreamingVolume(params) {
|
|
803
776
|
const command = `CreateStreamingVolume ${params.levelName} ${params.position.join(' ')} ${params.size.join(' ')} ${params.streamingDistance || 0}`;
|
|
804
777
|
return this.bridge.executeConsoleCommand(command);
|
|
805
778
|
}
|
|
806
|
-
// Level LOD
|
|
807
779
|
async setLevelLOD(params) {
|
|
808
780
|
const command = `SetLevelLOD ${params.levelName} ${params.lodLevel} ${params.distance}`;
|
|
809
781
|
return this.bridge.executeConsoleCommand(command);
|