unreal-engine-mcp-server 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +269 -22
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -72
- 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 -604
- 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 +5475 -1627
- package/dist/tools/consolidated-tool-definitions.js +829 -482
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1009
- 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 +45 -0
- package/dist/tools/logs.js +210 -0
- 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 +195 -11
- 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 -649
- 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 -500
- package/src/tools/consolidated-tool-handlers.ts +272 -1122
- 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 +219 -0
- 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 +250 -13
- 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 -572
- 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/dist/tools/landscape.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { bestEffortInterpretedText, coerceBoolean, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
|
|
2
1
|
import { ensureVector3 } from '../utils/validation.js';
|
|
3
|
-
import { escapePythonString } from '../utils/python.js';
|
|
4
2
|
export class LandscapeTools {
|
|
5
3
|
bridge;
|
|
6
|
-
|
|
4
|
+
automationBridge;
|
|
5
|
+
constructor(bridge, automationBridge) {
|
|
7
6
|
this.bridge = bridge;
|
|
7
|
+
this.automationBridge = automationBridge;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
setAutomationBridge(automationBridge) { this.automationBridge = automationBridge; }
|
|
10
10
|
async createLandscape(params) {
|
|
11
11
|
const name = params.name?.trim();
|
|
12
12
|
if (!name) {
|
|
@@ -24,270 +24,50 @@ export class LandscapeTools {
|
|
|
24
24
|
error: 'Landscape sizeY must be a positive number'
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
if (!this.automationBridge) {
|
|
28
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
29
|
+
}
|
|
27
30
|
const [locX, locY, locZ] = ensureVector3(params.location ?? [0, 0, 0], 'landscape location');
|
|
28
31
|
const sectionsPerComponent = Math.max(1, Math.floor(params.sectionsPerComponent ?? 1));
|
|
29
32
|
const quadsPerSection = Math.max(1, Math.floor(params.quadsPerSection ?? 63));
|
|
30
|
-
const componentCount = Math.max(1, Math.floor(params.componentCount ?? 1));
|
|
31
|
-
const defaultSize = 1000;
|
|
32
|
-
const scaleX = params.sizeX ? Math.max(0.1, params.sizeX / defaultSize) : 1;
|
|
33
|
-
const scaleY = params.sizeY ? Math.max(0.1, params.sizeY / defaultSize) : 1;
|
|
34
|
-
const escapedName = escapePythonString(name);
|
|
35
|
-
const escapedMaterial = params.materialPath && params.materialPath.trim().length > 0
|
|
36
|
-
? escapePythonString(params.materialPath.trim())
|
|
37
|
-
: '';
|
|
38
|
-
const runtimeGridFlag = params.runtimeGrid ? 'True' : 'False';
|
|
39
|
-
const spatiallyLoadedFlag = params.isSpatiallyLoaded ? 'True' : 'False';
|
|
40
|
-
const runtimeGridValue = params.runtimeGrid ? escapePythonString(params.runtimeGrid.trim()) : '';
|
|
41
|
-
const dataLayerNames = Array.isArray(params.dataLayers)
|
|
42
|
-
? params.dataLayers
|
|
43
|
-
.map(layer => layer?.trim())
|
|
44
|
-
.filter((layer) => Boolean(layer))
|
|
45
|
-
.map(layer => escapePythonString(layer))
|
|
46
|
-
: [];
|
|
47
|
-
const pythonScript = `
|
|
48
|
-
import unreal
|
|
49
|
-
import json
|
|
50
|
-
|
|
51
|
-
result = {
|
|
52
|
-
"success": False,
|
|
53
|
-
"message": "",
|
|
54
|
-
"error": "",
|
|
55
|
-
"warnings": [],
|
|
56
|
-
"details": [],
|
|
57
|
-
"landscapeName": "",
|
|
58
|
-
"landscapeActor": "",
|
|
59
|
-
"worldPartition": False,
|
|
60
|
-
"runtimeGridRequested": ${runtimeGridFlag},
|
|
61
|
-
"spatiallyLoaded": ${spatiallyLoadedFlag}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
66
|
-
world = editor_subsystem.get_editor_world() if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world') else None
|
|
67
|
-
data_layer_manager = None
|
|
68
|
-
world_partition = None
|
|
69
|
-
if world:
|
|
70
|
-
# Try multiple methods to access World Partition (UE 5.6+)
|
|
71
|
-
try:
|
|
72
|
-
# Method 1: Try get_world_partition() if it exists
|
|
73
|
-
if hasattr(world, 'get_world_partition'):
|
|
74
|
-
world_partition = world.get_world_partition()
|
|
75
|
-
except (AttributeError, Exception):
|
|
76
|
-
pass
|
|
77
|
-
|
|
78
|
-
if not world_partition:
|
|
79
|
-
try:
|
|
80
|
-
# Method 2: Try WorldPartitionSubsystem
|
|
81
|
-
wp_subsystem = unreal.get_editor_subsystem(unreal.WorldPartitionSubsystem)
|
|
82
|
-
if wp_subsystem:
|
|
83
|
-
world_partition = wp_subsystem.get_world_partition(world)
|
|
84
|
-
except (AttributeError, Exception):
|
|
85
|
-
pass
|
|
86
|
-
|
|
87
|
-
if not world_partition:
|
|
88
|
-
try:
|
|
89
|
-
# Method 3: Check if world has world_partition property
|
|
90
|
-
if hasattr(world, 'world_partition'):
|
|
91
|
-
world_partition = world.world_partition
|
|
92
|
-
except (AttributeError, Exception):
|
|
93
|
-
pass
|
|
94
|
-
|
|
95
|
-
result["worldPartition"] = world_partition is not None
|
|
96
|
-
|
|
97
|
-
if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
98
|
-
try:
|
|
99
|
-
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
100
|
-
except Exception as dlm_error:
|
|
101
|
-
result["warnings"].append(f"Data layer manager unavailable: {dlm_error}")
|
|
102
|
-
|
|
103
|
-
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
104
|
-
if not actor_subsystem:
|
|
105
|
-
result["error"] = "EditorActorSubsystem unavailable"
|
|
106
|
-
else:
|
|
107
|
-
existing = None
|
|
108
|
-
try:
|
|
109
|
-
for actor in actor_subsystem.get_all_level_actors():
|
|
110
|
-
if actor and actor.get_actor_label() == "${escapedName}":
|
|
111
|
-
existing = actor
|
|
112
|
-
break
|
|
113
|
-
except Exception as scan_error:
|
|
114
|
-
result["warnings"].append(f"Actor scan failed: {scan_error}")
|
|
115
|
-
|
|
116
|
-
if existing:
|
|
117
|
-
result["success"] = True
|
|
118
|
-
result["message"] = "Landscape already exists"
|
|
119
|
-
result["landscapeName"] = existing.get_actor_label()
|
|
120
|
-
try:
|
|
121
|
-
result["landscapeActor"] = existing.get_path_name()
|
|
122
|
-
except Exception:
|
|
123
|
-
pass
|
|
124
|
-
else:
|
|
125
|
-
landscape_class = getattr(unreal, "Landscape", None)
|
|
126
|
-
if not landscape_class:
|
|
127
|
-
result["error"] = "Landscape class unavailable"
|
|
128
|
-
else:
|
|
129
|
-
location = unreal.Vector(${locX}, ${locY}, ${locZ})
|
|
130
|
-
rotation = unreal.Rotator(0.0, 0.0, 0.0)
|
|
131
|
-
landscape_actor = actor_subsystem.spawn_actor_from_class(landscape_class, location, rotation)
|
|
132
|
-
if not landscape_actor:
|
|
133
|
-
result["error"] = "Failed to spawn landscape actor"
|
|
134
|
-
else:
|
|
135
|
-
# Set label first
|
|
136
|
-
try:
|
|
137
|
-
landscape_actor.set_actor_label("${escapedName}", True)
|
|
138
|
-
except TypeError:
|
|
139
|
-
landscape_actor.set_actor_label("${escapedName}")
|
|
140
|
-
except Exception as label_error:
|
|
141
|
-
result["warnings"].append(f"Failed to set landscape label: {label_error}")
|
|
142
|
-
|
|
143
|
-
# Fix component registration by forcing re-registration
|
|
144
|
-
# This addresses the "RegisterComponentWithWorld: Trying to register component with IsValid() == false" warning
|
|
145
|
-
try:
|
|
146
|
-
# Get landscape components and re-register them
|
|
147
|
-
landscape_components = landscape_actor.get_components_by_class(unreal.LandscapeComponent)
|
|
148
|
-
if landscape_components:
|
|
149
|
-
for component in landscape_components:
|
|
150
|
-
if hasattr(component, 'register_component'):
|
|
151
|
-
try:
|
|
152
|
-
component.register_component()
|
|
153
|
-
except Exception:
|
|
154
|
-
pass
|
|
155
|
-
else:
|
|
156
|
-
# If no components yet, this is expected for LandscapePlaceholder
|
|
157
|
-
# The landscape needs to be "finalized" via editor tools or console commands
|
|
158
|
-
result["details"].append("Landscape placeholder created - finalize via editor for full functionality")
|
|
159
|
-
except Exception as comp_error:
|
|
160
|
-
# Component registration is best-effort; not critical
|
|
161
|
-
result["details"].append(f"Component registration attempted (editor finalization may be needed)")
|
|
162
|
-
|
|
163
|
-
try:
|
|
164
|
-
landscape_actor.set_actor_scale3d(unreal.Vector(${scaleX.toFixed(4)}, ${scaleY.toFixed(4)}, 1.0))
|
|
165
|
-
result["details"].append(f"Actor scale set to (${scaleX.toFixed(2)}, ${scaleY.toFixed(2)}, 1.0)")
|
|
166
|
-
except Exception as scale_error:
|
|
167
|
-
result["warnings"].append(f"Failed to set landscape scale: {scale_error}")
|
|
168
|
-
|
|
169
|
-
# Workaround for LandscapeEditorSubsystem Python API limitation
|
|
170
|
-
# Use direct property manipulation instead
|
|
171
|
-
landscape_configured = False
|
|
172
|
-
try:
|
|
173
|
-
# Try LandscapeEditorSubsystem if available (may not be in Python API)
|
|
174
|
-
landscape_editor = unreal.get_editor_subsystem(unreal.LandscapeEditorSubsystem)
|
|
175
|
-
if landscape_editor:
|
|
176
|
-
try:
|
|
177
|
-
landscape_editor.set_component_size(${sectionsPerComponent}, ${quadsPerSection})
|
|
178
|
-
landscape_editor.set_component_count(${componentCount}, ${componentCount})
|
|
179
|
-
result["details"].append(f"Component size ${sectionsPerComponent}x${quadsPerSection}, count ${componentCount}x${componentCount}")
|
|
180
|
-
landscape_configured = True
|
|
181
|
-
except Exception as config_error:
|
|
182
|
-
result["details"].append(f"LandscapeEditorSubsystem method limited: {config_error}")
|
|
183
|
-
except (AttributeError, Exception):
|
|
184
|
-
# Expected - LandscapeEditorSubsystem not available in Python API
|
|
185
|
-
pass
|
|
186
|
-
|
|
187
|
-
# Fallback: Configure via properties if subsystem not available
|
|
188
|
-
if not landscape_configured:
|
|
189
|
-
try:
|
|
190
|
-
# Set component properties directly
|
|
191
|
-
if hasattr(landscape_actor, 'set_editor_property'):
|
|
192
|
-
# Note: These properties may not be directly editable post-spawn
|
|
193
|
-
# This is documented UE limitation - landscape config is best done via editor tools
|
|
194
|
-
result["details"].append(f"Landscape spawned (config via editor tools recommended for ${sectionsPerComponent}x${quadsPerSection} components)")
|
|
195
|
-
except Exception:
|
|
196
|
-
pass
|
|
197
|
-
|
|
198
|
-
${escapedMaterial ? `try:
|
|
199
|
-
material = unreal.EditorAssetLibrary.load_asset("${escapedMaterial}")
|
|
200
|
-
if material:
|
|
201
|
-
try:
|
|
202
|
-
landscape_actor.set_landscape_material(material)
|
|
203
|
-
except Exception:
|
|
204
|
-
landscape_actor.editor_set_landscape_material(material)
|
|
205
|
-
result["details"].append("Landscape material applied")
|
|
206
|
-
else:
|
|
207
|
-
result["warnings"].append("Landscape material asset not found: ${escapedMaterial}")
|
|
208
|
-
except Exception as material_error:
|
|
209
|
-
result["warnings"].append(f"Failed to apply landscape material: {material_error}")
|
|
210
|
-
` : ''}
|
|
211
|
-
${runtimeGridValue ? `if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
212
|
-
try:
|
|
213
|
-
unreal.WorldPartitionBlueprintLibrary.set_actor_runtime_grid(landscape_actor, "${runtimeGridValue}")
|
|
214
|
-
result["details"].append("Runtime grid assigned: ${runtimeGridValue}")
|
|
215
|
-
except Exception as grid_error:
|
|
216
|
-
result["warnings"].append(f"Failed to assign runtime grid: {grid_error}")
|
|
217
|
-
` : ''}
|
|
218
|
-
${params.isSpatiallyLoaded ? `if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
219
|
-
try:
|
|
220
|
-
unreal.WorldPartitionBlueprintLibrary.set_actor_spatially_loaded(landscape_actor, True)
|
|
221
|
-
result["details"].append("Actor marked as spatially loaded")
|
|
222
|
-
except Exception as spatial_error:
|
|
223
|
-
result["warnings"].append(f"Failed to mark as spatially loaded: {spatial_error}")
|
|
224
|
-
` : ''}
|
|
225
|
-
${dataLayerNames.length ? `if result["worldPartition"] and data_layer_manager:
|
|
226
|
-
for layer_name in ${JSON.stringify(dataLayerNames)}:
|
|
227
|
-
try:
|
|
228
|
-
data_layer = data_layer_manager.get_data_layer(layer_name)
|
|
229
|
-
if data_layer:
|
|
230
|
-
unreal.WorldPartitionBlueprintLibrary.add_actor_to_data_layer(landscape_actor, data_layer)
|
|
231
|
-
result["details"].append(f"Added to data layer {layer_name}")
|
|
232
|
-
else:
|
|
233
|
-
result["warnings"].append(f"Data layer not found: {layer_name}")
|
|
234
|
-
except Exception as data_layer_error:
|
|
235
|
-
result["warnings"].append(f"Failed to assign data layer {layer_name}: {data_layer_error}")
|
|
236
|
-
` : ''}
|
|
237
|
-
|
|
238
|
-
try:
|
|
239
|
-
result["landscapeName"] = landscape_actor.get_actor_label()
|
|
240
|
-
result["landscapeActor"] = landscape_actor.get_path_name()
|
|
241
|
-
except Exception:
|
|
242
|
-
pass
|
|
243
|
-
|
|
244
|
-
result["success"] = True
|
|
245
|
-
result["message"] = "Landscape actor created"
|
|
246
|
-
except Exception as e:
|
|
247
|
-
result["error"] = str(e)
|
|
248
|
-
|
|
249
|
-
if result.get("success"):
|
|
250
|
-
result.pop("error", None)
|
|
251
|
-
else:
|
|
252
|
-
if not result.get("error"):
|
|
253
|
-
result["error"] = "Failed to create landscape actor"
|
|
254
|
-
if not result.get("message"):
|
|
255
|
-
result["message"] = result["error"]
|
|
256
|
-
|
|
257
|
-
if not result.get("warnings"):
|
|
258
|
-
result.pop("warnings", None)
|
|
259
|
-
if not result.get("details"):
|
|
260
|
-
result.pop("details", None)
|
|
261
|
-
|
|
262
|
-
print("RESULT:" + json.dumps(result))
|
|
263
|
-
`.trim();
|
|
264
33
|
try {
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
34
|
+
const componentsX = Math.max(1, Math.floor((params.componentCount ?? Math.max(1, Math.floor((params.sizeX ?? 1000) / 1000)))));
|
|
35
|
+
const componentsY = Math.max(1, Math.floor((params.componentCount ?? Math.max(1, Math.floor((params.sizeY ?? 1000) / 1000)))));
|
|
36
|
+
const quadsPerComponent = quadsPerSection;
|
|
37
|
+
const payload = {
|
|
38
|
+
name,
|
|
39
|
+
x: locX,
|
|
40
|
+
y: locY,
|
|
41
|
+
z: locZ,
|
|
42
|
+
componentsX,
|
|
43
|
+
componentsY,
|
|
44
|
+
quadsPerComponent,
|
|
45
|
+
sectionsPerComponent,
|
|
46
|
+
materialPath: params.materialPath || ''
|
|
47
|
+
};
|
|
48
|
+
const response = await this.automationBridge.sendAutomationRequest('create_landscape', payload, {
|
|
49
|
+
timeoutMs: 60000
|
|
269
50
|
});
|
|
270
|
-
if (
|
|
51
|
+
if (response.success === false) {
|
|
271
52
|
return {
|
|
272
53
|
success: false,
|
|
273
|
-
error:
|
|
54
|
+
error: response.error || response.message || 'Failed to create landscape actor'
|
|
274
55
|
};
|
|
275
56
|
}
|
|
276
57
|
const result = {
|
|
277
58
|
success: true,
|
|
278
|
-
message:
|
|
279
|
-
landscapeName:
|
|
280
|
-
worldPartition:
|
|
59
|
+
message: response.message || 'Landscape actor created',
|
|
60
|
+
landscapeName: response.landscapeName || name,
|
|
61
|
+
worldPartition: response.worldPartition ?? params.enableWorldPartition ?? false
|
|
281
62
|
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
result.landscapeActor = actorPath;
|
|
63
|
+
if (response.landscapeActor) {
|
|
64
|
+
result.landscapeActor = response.landscapeActor;
|
|
285
65
|
}
|
|
286
|
-
if (
|
|
287
|
-
result.warnings =
|
|
66
|
+
if (response.warnings) {
|
|
67
|
+
result.warnings = response.warnings;
|
|
288
68
|
}
|
|
289
|
-
if (
|
|
290
|
-
result.details =
|
|
69
|
+
if (response.details) {
|
|
70
|
+
result.details = response.details;
|
|
291
71
|
}
|
|
292
72
|
if (params.runtimeGrid) {
|
|
293
73
|
result.runtimeGrid = params.runtimeGrid;
|
|
@@ -300,78 +80,202 @@ print("RESULT:" + json.dumps(result))
|
|
|
300
80
|
catch (error) {
|
|
301
81
|
return {
|
|
302
82
|
success: false,
|
|
303
|
-
error: `Failed to create landscape actor: ${error}`
|
|
83
|
+
error: `Failed to create landscape actor: ${error instanceof Error ? error.message : String(error)}`
|
|
304
84
|
};
|
|
305
85
|
}
|
|
306
86
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
87
|
+
async sculptLandscape(params) {
|
|
88
|
+
const [x, y, z] = ensureVector3(params.location ?? [0, 0, 0], 'sculpt location');
|
|
89
|
+
const tool = (params.tool || '').trim();
|
|
90
|
+
const lowerTool = tool.toLowerCase();
|
|
91
|
+
const validTools = new Set(['sculpt', 'smooth', 'flatten', 'ramp', 'erosion', 'hydro', 'noise', 'raise', 'lower']);
|
|
92
|
+
const isValidTool = lowerTool.length > 0 && validTools.has(lowerTool);
|
|
93
|
+
if (!isValidTool) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: `Invalid sculpt tool: ${params.tool}`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (!this.automationBridge) {
|
|
100
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
101
|
+
}
|
|
102
|
+
const payload = {
|
|
103
|
+
landscapeName: params.landscapeName?.trim(),
|
|
104
|
+
toolMode: tool,
|
|
105
|
+
brushRadius: params.brushSize ?? params.radius ?? 1000,
|
|
106
|
+
brushFalloff: params.brushFalloff ?? 0.5,
|
|
107
|
+
strength: params.strength ?? 0.1,
|
|
108
|
+
location: { x, y, z }
|
|
109
|
+
};
|
|
110
|
+
const response = await this.automationBridge.sendAutomationRequest('sculpt_landscape', payload);
|
|
111
|
+
if (!response.success) {
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: response.error || 'Failed to sculpt landscape'
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
message: `Sculpting applied to ${params.landscapeName}`,
|
|
120
|
+
details: response
|
|
121
|
+
};
|
|
310
122
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
123
|
+
async paintLandscape(params) {
|
|
124
|
+
if (!this.automationBridge) {
|
|
125
|
+
throw new Error('Automation Bridge not available.');
|
|
126
|
+
}
|
|
127
|
+
const [x, y] = ensureVector3(params.position, 'paint position');
|
|
128
|
+
const radius = params.brushSize ?? params.radius ?? 1000;
|
|
129
|
+
const minX = Math.floor(x - radius);
|
|
130
|
+
const maxX = Math.floor(x + radius);
|
|
131
|
+
const minY = Math.floor(y - radius);
|
|
132
|
+
const maxY = Math.floor(y + radius);
|
|
133
|
+
const payload = {
|
|
134
|
+
landscapeName: params.landscapeName?.trim(),
|
|
135
|
+
layerName: params.layerName?.trim(),
|
|
136
|
+
region: { minX, minY, maxX, maxY },
|
|
137
|
+
strength: params.strength ?? 1.0
|
|
138
|
+
};
|
|
139
|
+
const response = await this.automationBridge.sendAutomationRequest('paint_landscape_layer', payload);
|
|
140
|
+
if (!response.success) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
error: response.error || 'Failed to paint landscape layer'
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
success: true,
|
|
148
|
+
message: `Painted layer ${params.layerName}`,
|
|
149
|
+
details: response
|
|
150
|
+
};
|
|
314
151
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
commands.push(`AddLandscapeLayer ${params.landscapeName} ${params.layerName}`);
|
|
319
|
-
if (params.weightMapPath) {
|
|
320
|
-
commands.push(`SetLayerWeightMap ${params.layerName} ${params.weightMapPath}`);
|
|
152
|
+
async createProceduralTerrain(params) {
|
|
153
|
+
if (!this.automationBridge) {
|
|
154
|
+
throw new Error('Automation Bridge not available. Procedural terrain creation requires plugin support.');
|
|
321
155
|
}
|
|
322
|
-
|
|
323
|
-
|
|
156
|
+
try {
|
|
157
|
+
const payload = {
|
|
158
|
+
name: params.name,
|
|
159
|
+
location: params.location || [0, 0, 0],
|
|
160
|
+
sizeX: params.sizeX || 2000,
|
|
161
|
+
sizeY: params.sizeY || 2000,
|
|
162
|
+
subdivisions: params.subdivisions || 50,
|
|
163
|
+
heightFunction: params.heightFunction || 'math.sin(x/100) * 50 + math.cos(y/100) * 30',
|
|
164
|
+
material: params.material,
|
|
165
|
+
...params.settings
|
|
166
|
+
};
|
|
167
|
+
const response = await this.automationBridge.sendAutomationRequest('create_procedural_terrain', payload, {
|
|
168
|
+
timeoutMs: 120000
|
|
169
|
+
});
|
|
170
|
+
if (response.success === false) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: response.error || response.message || 'Failed to create procedural terrain',
|
|
174
|
+
message: response.message || 'Failed to create procedural terrain'
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const result = response.result;
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
message: response.message || `Created procedural terrain '${params.name}'`,
|
|
181
|
+
actorName: result?.actor_name,
|
|
182
|
+
vertices: result?.vertices,
|
|
183
|
+
triangles: result?.triangles,
|
|
184
|
+
size: result?.size,
|
|
185
|
+
subdivisions: result?.subdivisions,
|
|
186
|
+
details: result
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
error: `Failed to create procedural terrain: ${error instanceof Error ? error.message : String(error)}`
|
|
193
|
+
};
|
|
324
194
|
}
|
|
325
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
326
|
-
return { success: true, message: `Layer ${params.layerName} added to landscape` };
|
|
327
195
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
commands.push(`CreateLandscapeSpline ${params.landscapeName} ${params.splineName}`);
|
|
332
|
-
for (const point of params.points) {
|
|
333
|
-
commands.push(`AddSplinePoint ${params.splineName} ${point.join(' ')}`);
|
|
196
|
+
async createLandscapeGrassType(params) {
|
|
197
|
+
if (!this.automationBridge) {
|
|
198
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
334
199
|
}
|
|
335
|
-
|
|
336
|
-
|
|
200
|
+
const name = typeof params.name === 'string' ? params.name.trim() : '';
|
|
201
|
+
if (!name) {
|
|
202
|
+
return { success: false, error: 'Grass type name is required' };
|
|
337
203
|
}
|
|
338
|
-
|
|
339
|
-
|
|
204
|
+
const meshPathRaw = typeof params.meshPath === 'string' && params.meshPath.trim().length > 0
|
|
205
|
+
? params.meshPath.trim()
|
|
206
|
+
: (typeof params.path === 'string' && params.path.trim().length > 0
|
|
207
|
+
? params.path.trim()
|
|
208
|
+
: (typeof params.staticMesh === 'string' && params.staticMesh.trim().length > 0
|
|
209
|
+
? params.staticMesh.trim()
|
|
210
|
+
: ''));
|
|
211
|
+
if (!meshPathRaw) {
|
|
212
|
+
return { success: false, error: 'meshPath is required to create a landscape grass type' };
|
|
340
213
|
}
|
|
341
|
-
|
|
342
|
-
|
|
214
|
+
try {
|
|
215
|
+
const response = await this.automationBridge.sendAutomationRequest('create_landscape_grass_type', {
|
|
216
|
+
name,
|
|
217
|
+
meshPath: meshPathRaw,
|
|
218
|
+
density: params.density || 1.0,
|
|
219
|
+
minScale: params.minScale || 0.8,
|
|
220
|
+
maxScale: params.maxScale || 1.2
|
|
221
|
+
}, { timeoutMs: 90000 });
|
|
222
|
+
if (response && response.success === false) {
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: response.error || response.message || 'Failed to create landscape grass type'
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const result = response.result;
|
|
229
|
+
return {
|
|
230
|
+
success: true,
|
|
231
|
+
message: response?.message || `Landscape grass type '${name}' created`,
|
|
232
|
+
assetPath: result?.asset_path || response?.assetPath || response?.asset_path
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
error: `Failed to create landscape grass type: ${error instanceof Error ? error.message : String(error)}`
|
|
239
|
+
};
|
|
343
240
|
}
|
|
344
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
345
|
-
return { success: true, message: `Landscape spline ${params.splineName} created` };
|
|
346
|
-
}
|
|
347
|
-
// Import heightmap
|
|
348
|
-
async importHeightmap(params) {
|
|
349
|
-
const scale = params.scale || [100, 100, 100];
|
|
350
|
-
const command = `ImportLandscapeHeightmap ${params.landscapeName} ${params.heightmapPath} ${scale.join(' ')}`;
|
|
351
|
-
return this.bridge.executeConsoleCommand(command);
|
|
352
|
-
}
|
|
353
|
-
// Export heightmap
|
|
354
|
-
async exportHeightmap(params) {
|
|
355
|
-
const format = params.format || 'PNG';
|
|
356
|
-
const command = `ExportLandscapeHeightmap ${params.landscapeName} ${params.exportPath} ${format}`;
|
|
357
|
-
return this.bridge.executeConsoleCommand(command);
|
|
358
241
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
if (
|
|
363
|
-
|
|
242
|
+
async setLandscapeMaterial(params) {
|
|
243
|
+
const landscapeName = typeof params.landscapeName === 'string' ? params.landscapeName.trim() : '';
|
|
244
|
+
const materialPath = typeof params.materialPath === 'string' ? params.materialPath.trim() : '';
|
|
245
|
+
if (!landscapeName) {
|
|
246
|
+
return { success: false, error: 'Landscape name is required' };
|
|
364
247
|
}
|
|
365
|
-
if (
|
|
366
|
-
|
|
248
|
+
if (!materialPath) {
|
|
249
|
+
return { success: false, error: 'materialPath is required' };
|
|
367
250
|
}
|
|
368
|
-
if (
|
|
369
|
-
|
|
251
|
+
if (!this.automationBridge) {
|
|
252
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const response = await this.automationBridge.sendAutomationRequest('set_landscape_material', {
|
|
256
|
+
landscapeName,
|
|
257
|
+
materialPath
|
|
258
|
+
}, { timeoutMs: 60000 });
|
|
259
|
+
if (response && response.success === false) {
|
|
260
|
+
return {
|
|
261
|
+
success: false,
|
|
262
|
+
error: response.error || response.message || 'Failed to set landscape material'
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
success: true,
|
|
267
|
+
message: response?.message || `Landscape material set on '${landscapeName}'`,
|
|
268
|
+
landscapeName: response?.landscapeName || landscapeName,
|
|
269
|
+
materialPath: response?.materialPath || materialPath
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
return {
|
|
274
|
+
success: false,
|
|
275
|
+
error: `Failed to set landscape material: ${error instanceof Error ? error.message : String(error)}`
|
|
276
|
+
};
|
|
370
277
|
}
|
|
371
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
372
|
-
return { success: true, message: 'Landscape LOD settings updated' };
|
|
373
278
|
}
|
|
374
|
-
// Create landscape grass
|
|
375
279
|
async createLandscapeGrass(params) {
|
|
376
280
|
const commands = [];
|
|
377
281
|
commands.push(`CreateLandscapeGrass ${params.landscapeName} ${params.grassType}`);
|
|
@@ -387,7 +291,6 @@ print("RESULT:" + json.dumps(result))
|
|
|
387
291
|
await this.bridge.executeConsoleCommands(commands);
|
|
388
292
|
return { success: true, message: `Grass type ${params.grassType} created on landscape` };
|
|
389
293
|
}
|
|
390
|
-
// Landscape collision
|
|
391
294
|
async updateLandscapeCollision(params) {
|
|
392
295
|
const commands = [];
|
|
393
296
|
if (params.collisionMipLevel !== undefined) {
|
|
@@ -400,7 +303,6 @@ print("RESULT:" + json.dumps(result))
|
|
|
400
303
|
await this.bridge.executeConsoleCommands(commands);
|
|
401
304
|
return { success: true, message: 'Landscape collision updated' };
|
|
402
305
|
}
|
|
403
|
-
// Retopologize landscape
|
|
404
306
|
async retopologizeLandscape(params) {
|
|
405
307
|
const commands = [];
|
|
406
308
|
if (params.targetTriangleCount !== undefined) {
|
|
@@ -413,7 +315,6 @@ print("RESULT:" + json.dumps(result))
|
|
|
413
315
|
await this.bridge.executeConsoleCommands(commands);
|
|
414
316
|
return { success: true, message: 'Landscape retopologized' };
|
|
415
317
|
}
|
|
416
|
-
// Create water body
|
|
417
318
|
async createWaterBody(params) {
|
|
418
319
|
const loc = params.location || [0, 0, 0];
|
|
419
320
|
const size = params.size || [1000, 1000];
|
|
@@ -421,111 +322,39 @@ print("RESULT:" + json.dumps(result))
|
|
|
421
322
|
const command = `CreateWaterBody ${params.type} ${params.name} ${loc.join(' ')} ${size.join(' ')} ${depth}`;
|
|
422
323
|
return this.bridge.executeConsoleCommand(command);
|
|
423
324
|
}
|
|
424
|
-
// World Partition support for landscapes (UE 5.6)
|
|
425
325
|
async configureWorldPartition(params) {
|
|
326
|
+
if (!this.automationBridge) {
|
|
327
|
+
throw new Error('Automation Bridge not available. World Partition operations require plugin support.');
|
|
328
|
+
}
|
|
426
329
|
try {
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
actors = []
|
|
436
|
-
try:
|
|
437
|
-
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
438
|
-
if actor_subsystem and hasattr(actor_subsystem, 'get_all_level_actors'):
|
|
439
|
-
actors = actor_subsystem.get_all_level_actors()
|
|
440
|
-
except Exception:
|
|
441
|
-
actors = []
|
|
442
|
-
landscape = None
|
|
443
|
-
|
|
444
|
-
for actor in actors:
|
|
445
|
-
if actor.get_name() == "${params.landscapeName}" or actor.get_actor_label() == "${params.landscapeName}":
|
|
446
|
-
if isinstance(actor, unreal.LandscapeProxy) or isinstance(actor, unreal.Landscape):
|
|
447
|
-
landscape = actor
|
|
448
|
-
break
|
|
449
|
-
|
|
450
|
-
if landscape:
|
|
451
|
-
changes_made = []
|
|
452
|
-
|
|
453
|
-
# Configure spatial loading (UE 5.6)
|
|
454
|
-
if ${params.enableSpatialLoading !== undefined ? 'True' : 'False'}:
|
|
455
|
-
try:
|
|
456
|
-
landscape.set_editor_property('is_spatially_loaded', ${params.enableSpatialLoading || false})
|
|
457
|
-
changes_made.append("Spatial loading: ${params.enableSpatialLoading}")
|
|
458
|
-
except:
|
|
459
|
-
pass
|
|
460
|
-
|
|
461
|
-
# Set runtime grid (UE 5.6 World Partition)
|
|
462
|
-
if "${params.runtimeGrid || ''}":
|
|
463
|
-
try:
|
|
464
|
-
landscape.set_editor_property('runtime_grid', unreal.Name("${params.runtimeGrid}"))
|
|
465
|
-
changes_made.append("Runtime grid: ${params.runtimeGrid}")
|
|
466
|
-
except:
|
|
467
|
-
pass
|
|
468
|
-
|
|
469
|
-
# Configure data layers (UE 5.6)
|
|
470
|
-
if ${params.dataLayers ? 'True' : 'False'}:
|
|
471
|
-
try:
|
|
472
|
-
# Try modern subsystem first
|
|
473
|
-
try:
|
|
474
|
-
world = None
|
|
475
|
-
editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
476
|
-
if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world'):
|
|
477
|
-
world = editor_subsystem.get_editor_world()
|
|
478
|
-
if world is None:
|
|
479
|
-
world = unreal.EditorSubsystemLibrary.get_editor_world()
|
|
480
|
-
except Exception:
|
|
481
|
-
world = unreal.EditorSubsystemLibrary.get_editor_world()
|
|
482
|
-
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
483
|
-
if data_layer_manager:
|
|
484
|
-
# Note: Full data layer API requires additional setup
|
|
485
|
-
changes_made.append("Data layers: Requires manual configuration")
|
|
486
|
-
except:
|
|
487
|
-
pass
|
|
488
|
-
|
|
489
|
-
if changes_made:
|
|
490
|
-
result = {
|
|
491
|
-
'success': True,
|
|
492
|
-
'message': 'World Partition configured',
|
|
493
|
-
'changes': changes_made
|
|
494
|
-
}
|
|
495
|
-
else:
|
|
496
|
-
result = {
|
|
497
|
-
'success': False,
|
|
498
|
-
'error': 'No World Partition changes applied'
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
except Exception as e:
|
|
502
|
-
result = {'success': False, 'error': str(e)}
|
|
503
|
-
|
|
504
|
-
print('RESULT:' + json.dumps(result))
|
|
505
|
-
`.trim();
|
|
506
|
-
const response = await this.bridge.executePython(pythonScript);
|
|
507
|
-
const interpreted = interpretStandardResult(response, {
|
|
508
|
-
successMessage: 'World Partition configuration attempted',
|
|
509
|
-
failureMessage: 'World Partition configuration failed'
|
|
330
|
+
const response = await this.automationBridge.sendAutomationRequest('configure_landscape_world_partition', {
|
|
331
|
+
landscapeName: params.landscapeName,
|
|
332
|
+
enableSpatialLoading: params.enableSpatialLoading,
|
|
333
|
+
runtimeGrid: params.runtimeGrid || '',
|
|
334
|
+
dataLayers: params.dataLayers || [],
|
|
335
|
+
streamingDistance: params.streamingDistance
|
|
336
|
+
}, {
|
|
337
|
+
timeoutMs: 60000
|
|
510
338
|
});
|
|
511
|
-
if (
|
|
512
|
-
return
|
|
339
|
+
if (response.success === false) {
|
|
340
|
+
return {
|
|
341
|
+
success: false,
|
|
342
|
+
error: response.error || response.message || 'World Partition configuration failed'
|
|
343
|
+
};
|
|
513
344
|
}
|
|
514
345
|
return {
|
|
515
|
-
success:
|
|
516
|
-
|
|
517
|
-
|
|
346
|
+
success: true,
|
|
347
|
+
message: response.message || 'World Partition configured',
|
|
348
|
+
changes: response.changes
|
|
518
349
|
};
|
|
519
350
|
}
|
|
520
351
|
catch (err) {
|
|
521
|
-
return { success: false, error: `Failed to configure World Partition: ${err}` };
|
|
352
|
+
return { success: false, error: `Failed to configure World Partition: ${err instanceof Error ? err.message : String(err)}` };
|
|
522
353
|
}
|
|
523
354
|
}
|
|
524
|
-
// Set landscape data layers (UE 5.6)
|
|
525
355
|
async setDataLayers(params) {
|
|
526
356
|
try {
|
|
527
357
|
const commands = [];
|
|
528
|
-
// Use console commands for data layer management
|
|
529
358
|
if (params.operation === 'set' || params.operation === 'add') {
|
|
530
359
|
for (const layerName of params.dataLayerNames) {
|
|
531
360
|
commands.push(`wp.Runtime.SetDataLayerRuntimeState Loaded ${layerName}`);
|
|
@@ -536,7 +365,6 @@ print('RESULT:' + json.dumps(result))
|
|
|
536
365
|
commands.push(`wp.Runtime.SetDataLayerRuntimeState Unloaded ${layerName}`);
|
|
537
366
|
}
|
|
538
367
|
}
|
|
539
|
-
// Execute commands
|
|
540
368
|
await this.bridge.executeConsoleCommands(commands);
|
|
541
369
|
return {
|
|
542
370
|
success: true,
|
|
@@ -548,18 +376,15 @@ print('RESULT:' + json.dumps(result))
|
|
|
548
376
|
return { success: false, error: `Failed to manage data layers: ${err}` };
|
|
549
377
|
}
|
|
550
378
|
}
|
|
551
|
-
// Configure landscape streaming cells (UE 5.6 World Partition)
|
|
552
379
|
async configureStreamingCells(params) {
|
|
553
380
|
const commands = [];
|
|
554
|
-
// World Partition runtime commands
|
|
555
381
|
if (params.loadingRange !== undefined) {
|
|
556
382
|
commands.push(`wp.Runtime.OverrideRuntimeSpatialHashLoadingRange -grid=0 -range=${params.loadingRange}`);
|
|
557
383
|
}
|
|
558
384
|
if (params.enableHLOD !== undefined) {
|
|
559
385
|
commands.push(`wp.Runtime.HLOD ${params.enableHLOD ? '1' : '0'}`);
|
|
560
386
|
}
|
|
561
|
-
|
|
562
|
-
commands.push('wp.Runtime.ToggleDrawRuntimeHash2D'); // Show 2D grid
|
|
387
|
+
commands.push('wp.Runtime.ToggleDrawRuntimeHash2D');
|
|
563
388
|
try {
|
|
564
389
|
await this.bridge.executeConsoleCommands(commands);
|
|
565
390
|
return {
|
|
@@ -576,5 +401,51 @@ print('RESULT:' + json.dumps(result))
|
|
|
576
401
|
return { success: false, error: `Failed to configure streaming cells: ${err}` };
|
|
577
402
|
}
|
|
578
403
|
}
|
|
404
|
+
async modifyHeightmap(params) {
|
|
405
|
+
if (!this.automationBridge) {
|
|
406
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
407
|
+
}
|
|
408
|
+
const { landscapeName, heightData, minX, minY, maxX, maxY } = params;
|
|
409
|
+
if (!landscapeName) {
|
|
410
|
+
return { success: false, error: 'Landscape name is required' };
|
|
411
|
+
}
|
|
412
|
+
if (!heightData || !Array.isArray(heightData) || heightData.length === 0) {
|
|
413
|
+
return { success: false, error: 'heightData array is required' };
|
|
414
|
+
}
|
|
415
|
+
const width = maxX - minX + 1;
|
|
416
|
+
const height = maxY - minY + 1;
|
|
417
|
+
if (heightData.length !== width * height) {
|
|
418
|
+
return {
|
|
419
|
+
success: false,
|
|
420
|
+
error: `Height data length (${heightData.length}) does not match region dimensions (${width}x${height} = ${width * height})`
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
const response = await this.automationBridge.sendAutomationRequest('modify_heightmap', {
|
|
425
|
+
landscapeName,
|
|
426
|
+
heightData,
|
|
427
|
+
minX,
|
|
428
|
+
minY,
|
|
429
|
+
maxX,
|
|
430
|
+
maxY,
|
|
431
|
+
updateNormals: params.updateNormals ?? true
|
|
432
|
+
}, {
|
|
433
|
+
timeoutMs: 60000
|
|
434
|
+
});
|
|
435
|
+
if (response.success === false) {
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
error: response.error || response.message || 'Failed to modify heightmap'
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
success: true,
|
|
443
|
+
message: response.message || 'Heightmap modified successfully'
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
return { success: false, error: `Failed to modify heightmap: ${err instanceof Error ? err.message : String(err)}` };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
579
450
|
}
|
|
580
451
|
//# sourceMappingURL=landscape.js.map
|