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.
Files changed (454) hide show
  1. package/.env.example +26 -0
  2. package/.env.production +38 -7
  3. package/.eslintrc.json +0 -54
  4. package/.eslintrc.override.json +8 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
  8. package/.github/copilot-instructions.md +478 -45
  9. package/.github/dependabot.yml +19 -0
  10. package/.github/labeler.yml +24 -0
  11. package/.github/labels.yml +70 -0
  12. package/.github/pull_request_template.md +42 -0
  13. package/.github/release-drafter-config.yml +51 -0
  14. package/.github/workflows/auto-merge.yml +38 -0
  15. package/.github/workflows/ci.yml +38 -0
  16. package/.github/workflows/dependency-review.yml +17 -0
  17. package/.github/workflows/gemini-issue-triage.yml +172 -0
  18. package/.github/workflows/greetings.yml +27 -0
  19. package/.github/workflows/labeler.yml +17 -0
  20. package/.github/workflows/links.yml +80 -0
  21. package/.github/workflows/pr-size-labeler.yml +137 -0
  22. package/.github/workflows/publish-mcp.yml +13 -7
  23. package/.github/workflows/release-drafter.yml +23 -0
  24. package/.github/workflows/release.yml +112 -0
  25. package/.github/workflows/semantic-pull-request.yml +35 -0
  26. package/.github/workflows/smoke-test.yml +36 -0
  27. package/.github/workflows/stale.yml +28 -0
  28. package/CHANGELOG.md +338 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/GEMINI.md +115 -0
  31. package/Public/Plugin_setup_guide.mp4 +0 -0
  32. package/README.md +189 -128
  33. package/claude_desktop_config_example.json +7 -6
  34. package/dist/automation/bridge.d.ts +50 -0
  35. package/dist/automation/bridge.js +452 -0
  36. package/dist/automation/connection-manager.d.ts +23 -0
  37. package/dist/automation/connection-manager.js +107 -0
  38. package/dist/automation/handshake.d.ts +11 -0
  39. package/dist/automation/handshake.js +89 -0
  40. package/dist/automation/index.d.ts +3 -0
  41. package/dist/automation/index.js +3 -0
  42. package/dist/automation/message-handler.d.ts +12 -0
  43. package/dist/automation/message-handler.js +149 -0
  44. package/dist/automation/request-tracker.d.ts +25 -0
  45. package/dist/automation/request-tracker.js +98 -0
  46. package/dist/automation/types.d.ts +130 -0
  47. package/dist/automation/types.js +2 -0
  48. package/dist/cli.js +32 -5
  49. package/dist/config.d.ts +26 -0
  50. package/dist/config.js +59 -0
  51. package/dist/constants.d.ts +16 -0
  52. package/dist/constants.js +16 -0
  53. package/dist/graphql/loaders.d.ts +64 -0
  54. package/dist/graphql/loaders.js +117 -0
  55. package/dist/graphql/resolvers.d.ts +268 -0
  56. package/dist/graphql/resolvers.js +746 -0
  57. package/dist/graphql/schema.d.ts +5 -0
  58. package/dist/graphql/schema.js +437 -0
  59. package/dist/graphql/server.d.ts +26 -0
  60. package/dist/graphql/server.js +117 -0
  61. package/dist/graphql/types.d.ts +9 -0
  62. package/dist/graphql/types.js +2 -0
  63. package/dist/handlers/resource-handlers.d.ts +20 -0
  64. package/dist/handlers/resource-handlers.js +180 -0
  65. package/dist/index.d.ts +33 -18
  66. package/dist/index.js +130 -619
  67. package/dist/resources/actors.d.ts +17 -12
  68. package/dist/resources/actors.js +56 -76
  69. package/dist/resources/assets.d.ts +6 -14
  70. package/dist/resources/assets.js +115 -147
  71. package/dist/resources/levels.d.ts +13 -13
  72. package/dist/resources/levels.js +25 -34
  73. package/dist/server/resource-registry.d.ts +20 -0
  74. package/dist/server/resource-registry.js +37 -0
  75. package/dist/server/tool-registry.d.ts +23 -0
  76. package/dist/server/tool-registry.js +322 -0
  77. package/dist/server-setup.d.ts +20 -0
  78. package/dist/server-setup.js +71 -0
  79. package/dist/services/health-monitor.d.ts +34 -0
  80. package/dist/services/health-monitor.js +105 -0
  81. package/dist/services/metrics-server.d.ts +11 -0
  82. package/dist/services/metrics-server.js +105 -0
  83. package/dist/tools/actors.d.ts +163 -9
  84. package/dist/tools/actors.js +356 -311
  85. package/dist/tools/animation.d.ts +135 -4
  86. package/dist/tools/animation.js +510 -411
  87. package/dist/tools/assets.d.ts +75 -29
  88. package/dist/tools/assets.js +265 -284
  89. package/dist/tools/audio.d.ts +102 -42
  90. package/dist/tools/audio.js +272 -685
  91. package/dist/tools/base-tool.d.ts +17 -0
  92. package/dist/tools/base-tool.js +46 -0
  93. package/dist/tools/behavior-tree.d.ts +94 -0
  94. package/dist/tools/behavior-tree.js +39 -0
  95. package/dist/tools/blueprint.d.ts +208 -126
  96. package/dist/tools/blueprint.js +685 -832
  97. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  98. package/dist/tools/consolidated-tool-definitions.js +829 -496
  99. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  100. package/dist/tools/consolidated-tool-handlers.js +198 -1027
  101. package/dist/tools/debug.d.ts +143 -85
  102. package/dist/tools/debug.js +234 -180
  103. package/dist/tools/dynamic-handler-registry.d.ts +13 -0
  104. package/dist/tools/dynamic-handler-registry.js +23 -0
  105. package/dist/tools/editor.d.ts +30 -83
  106. package/dist/tools/editor.js +247 -244
  107. package/dist/tools/engine.d.ts +10 -4
  108. package/dist/tools/engine.js +13 -5
  109. package/dist/tools/environment.d.ts +30 -0
  110. package/dist/tools/environment.js +267 -0
  111. package/dist/tools/foliage.d.ts +65 -99
  112. package/dist/tools/foliage.js +221 -331
  113. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  114. package/dist/tools/handlers/actor-handlers.js +227 -0
  115. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  116. package/dist/tools/handlers/animation-handlers.js +185 -0
  117. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  118. package/dist/tools/handlers/argument-helper.js +80 -0
  119. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  120. package/dist/tools/handlers/asset-handlers.js +496 -0
  121. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  122. package/dist/tools/handlers/audio-handlers.js +166 -0
  123. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  124. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  125. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  126. package/dist/tools/handlers/common-handlers.js +56 -0
  127. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  128. package/dist/tools/handlers/editor-handlers.js +119 -0
  129. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  130. package/dist/tools/handlers/effect-handlers.js +171 -0
  131. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  132. package/dist/tools/handlers/environment-handlers.js +170 -0
  133. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  134. package/dist/tools/handlers/graph-handlers.js +90 -0
  135. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  136. package/dist/tools/handlers/input-handlers.js +21 -0
  137. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  138. package/dist/tools/handlers/inspect-handlers.js +383 -0
  139. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  140. package/dist/tools/handlers/level-handlers.js +237 -0
  141. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  142. package/dist/tools/handlers/lighting-handlers.js +144 -0
  143. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  144. package/dist/tools/handlers/performance-handlers.js +130 -0
  145. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  146. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  147. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  148. package/dist/tools/handlers/sequence-handlers.js +376 -0
  149. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  150. package/dist/tools/handlers/system-handlers.js +506 -0
  151. package/dist/tools/input.d.ts +19 -0
  152. package/dist/tools/input.js +89 -0
  153. package/dist/tools/introspection.d.ts +103 -40
  154. package/dist/tools/introspection.js +425 -568
  155. package/dist/tools/landscape.d.ts +54 -93
  156. package/dist/tools/landscape.js +284 -409
  157. package/dist/tools/level.d.ts +66 -27
  158. package/dist/tools/level.js +647 -675
  159. package/dist/tools/lighting.d.ts +77 -38
  160. package/dist/tools/lighting.js +445 -943
  161. package/dist/tools/logs.d.ts +3 -3
  162. package/dist/tools/logs.js +5 -57
  163. package/dist/tools/materials.d.ts +91 -24
  164. package/dist/tools/materials.js +194 -118
  165. package/dist/tools/niagara.d.ts +149 -39
  166. package/dist/tools/niagara.js +267 -182
  167. package/dist/tools/performance.d.ts +27 -13
  168. package/dist/tools/performance.js +203 -122
  169. package/dist/tools/physics.d.ts +32 -77
  170. package/dist/tools/physics.js +175 -582
  171. package/dist/tools/property-dictionary.d.ts +13 -0
  172. package/dist/tools/property-dictionary.js +82 -0
  173. package/dist/tools/sequence.d.ts +85 -60
  174. package/dist/tools/sequence.js +208 -747
  175. package/dist/tools/tool-definition-utils.d.ts +59 -0
  176. package/dist/tools/tool-definition-utils.js +35 -0
  177. package/dist/tools/ui.d.ts +64 -34
  178. package/dist/tools/ui.js +134 -214
  179. package/dist/types/automation-responses.d.ts +115 -0
  180. package/dist/types/automation-responses.js +2 -0
  181. package/dist/types/env.d.ts +0 -3
  182. package/dist/types/env.js +0 -7
  183. package/dist/types/responses.d.ts +249 -0
  184. package/dist/types/responses.js +2 -0
  185. package/dist/types/tool-interfaces.d.ts +898 -0
  186. package/dist/types/tool-interfaces.js +2 -0
  187. package/dist/types/tool-types.d.ts +183 -19
  188. package/dist/types/tool-types.js +0 -4
  189. package/dist/unreal-bridge.d.ts +24 -131
  190. package/dist/unreal-bridge.js +364 -1506
  191. package/dist/utils/command-validator.d.ts +9 -0
  192. package/dist/utils/command-validator.js +68 -0
  193. package/dist/utils/elicitation.d.ts +1 -1
  194. package/dist/utils/elicitation.js +12 -15
  195. package/dist/utils/error-handler.d.ts +2 -51
  196. package/dist/utils/error-handler.js +11 -87
  197. package/dist/utils/ini-reader.d.ts +3 -0
  198. package/dist/utils/ini-reader.js +69 -0
  199. package/dist/utils/logger.js +9 -6
  200. package/dist/utils/normalize.d.ts +3 -0
  201. package/dist/utils/normalize.js +56 -0
  202. package/dist/utils/path-security.d.ts +2 -0
  203. package/dist/utils/path-security.js +24 -0
  204. package/dist/utils/response-factory.d.ts +7 -0
  205. package/dist/utils/response-factory.js +27 -0
  206. package/dist/utils/response-validator.d.ts +3 -24
  207. package/dist/utils/response-validator.js +130 -81
  208. package/dist/utils/result-helpers.d.ts +4 -5
  209. package/dist/utils/result-helpers.js +15 -16
  210. package/dist/utils/safe-json.js +5 -11
  211. package/dist/utils/unreal-command-queue.d.ts +24 -0
  212. package/dist/utils/unreal-command-queue.js +120 -0
  213. package/dist/utils/validation.d.ts +0 -40
  214. package/dist/utils/validation.js +1 -78
  215. package/dist/wasm/index.d.ts +70 -0
  216. package/dist/wasm/index.js +535 -0
  217. package/docs/GraphQL-API.md +888 -0
  218. package/docs/Migration-Guide-v0.5.0.md +684 -0
  219. package/docs/Roadmap.md +53 -0
  220. package/docs/WebAssembly-Integration.md +628 -0
  221. package/docs/editor-plugin-extension.md +370 -0
  222. package/docs/handler-mapping.md +242 -0
  223. package/docs/native-automation-progress.md +128 -0
  224. package/docs/testing-guide.md +423 -0
  225. package/mcp-config-example.json +6 -6
  226. package/package.json +67 -28
  227. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  228. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  272. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  273. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  274. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  275. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  276. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  277. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  278. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  279. package/scripts/check-unreal-connection.mjs +19 -0
  280. package/scripts/clean-tmp.js +23 -0
  281. package/scripts/patch-wasm.js +26 -0
  282. package/scripts/run-all-tests.mjs +136 -0
  283. package/scripts/smoke-test.ts +94 -0
  284. package/scripts/sync-mcp-plugin.js +143 -0
  285. package/scripts/test-no-plugin-alternates.mjs +113 -0
  286. package/scripts/validate-server.js +46 -0
  287. package/scripts/verify-automation-bridge.js +200 -0
  288. package/server.json +58 -21
  289. package/src/automation/bridge.ts +558 -0
  290. package/src/automation/connection-manager.ts +130 -0
  291. package/src/automation/handshake.ts +99 -0
  292. package/src/automation/index.ts +2 -0
  293. package/src/automation/message-handler.ts +167 -0
  294. package/src/automation/request-tracker.ts +123 -0
  295. package/src/automation/types.ts +107 -0
  296. package/src/cli.ts +33 -6
  297. package/src/config.ts +73 -0
  298. package/src/constants.ts +19 -0
  299. package/src/graphql/loaders.ts +244 -0
  300. package/src/graphql/resolvers.ts +1008 -0
  301. package/src/graphql/schema.ts +452 -0
  302. package/src/graphql/server.ts +156 -0
  303. package/src/graphql/types.ts +10 -0
  304. package/src/handlers/resource-handlers.ts +186 -0
  305. package/src/index.ts +166 -664
  306. package/src/resources/actors.ts +58 -76
  307. package/src/resources/assets.ts +148 -134
  308. package/src/resources/levels.ts +28 -33
  309. package/src/server/resource-registry.ts +47 -0
  310. package/src/server/tool-registry.ts +354 -0
  311. package/src/server-setup.ts +114 -0
  312. package/src/services/health-monitor.ts +132 -0
  313. package/src/services/metrics-server.ts +142 -0
  314. package/src/tools/actors.ts +426 -323
  315. package/src/tools/animation.ts +672 -461
  316. package/src/tools/assets.ts +364 -289
  317. package/src/tools/audio.ts +323 -766
  318. package/src/tools/base-tool.ts +52 -0
  319. package/src/tools/behavior-tree.ts +45 -0
  320. package/src/tools/blueprint.ts +792 -970
  321. package/src/tools/consolidated-tool-definitions.ts +993 -515
  322. package/src/tools/consolidated-tool-handlers.ts +258 -1146
  323. package/src/tools/debug.ts +292 -187
  324. package/src/tools/dynamic-handler-registry.ts +33 -0
  325. package/src/tools/editor.ts +329 -253
  326. package/src/tools/engine.ts +14 -3
  327. package/src/tools/environment.ts +281 -0
  328. package/src/tools/foliage.ts +330 -392
  329. package/src/tools/handlers/actor-handlers.ts +265 -0
  330. package/src/tools/handlers/animation-handlers.ts +237 -0
  331. package/src/tools/handlers/argument-helper.ts +142 -0
  332. package/src/tools/handlers/asset-handlers.ts +532 -0
  333. package/src/tools/handlers/audio-handlers.ts +194 -0
  334. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  335. package/src/tools/handlers/common-handlers.ts +87 -0
  336. package/src/tools/handlers/editor-handlers.ts +123 -0
  337. package/src/tools/handlers/effect-handlers.ts +220 -0
  338. package/src/tools/handlers/environment-handlers.ts +183 -0
  339. package/src/tools/handlers/graph-handlers.ts +116 -0
  340. package/src/tools/handlers/input-handlers.ts +28 -0
  341. package/src/tools/handlers/inspect-handlers.ts +450 -0
  342. package/src/tools/handlers/level-handlers.ts +252 -0
  343. package/src/tools/handlers/lighting-handlers.ts +147 -0
  344. package/src/tools/handlers/performance-handlers.ts +132 -0
  345. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  346. package/src/tools/handlers/sequence-handlers.ts +415 -0
  347. package/src/tools/handlers/system-handlers.ts +564 -0
  348. package/src/tools/input.ts +101 -0
  349. package/src/tools/introspection.ts +493 -584
  350. package/src/tools/landscape.ts +418 -507
  351. package/src/tools/level.ts +786 -708
  352. package/src/tools/lighting.ts +588 -984
  353. package/src/tools/logs.ts +9 -57
  354. package/src/tools/materials.ts +237 -121
  355. package/src/tools/niagara.ts +335 -168
  356. package/src/tools/performance.ts +320 -169
  357. package/src/tools/physics.ts +274 -613
  358. package/src/tools/property-dictionary.ts +98 -0
  359. package/src/tools/sequence.ts +276 -820
  360. package/src/tools/tool-definition-utils.ts +35 -0
  361. package/src/tools/ui.ts +205 -283
  362. package/src/types/automation-responses.ts +119 -0
  363. package/src/types/env.ts +0 -10
  364. package/src/types/responses.ts +355 -0
  365. package/src/types/tool-interfaces.ts +250 -0
  366. package/src/types/tool-types.ts +243 -21
  367. package/src/unreal-bridge.ts +460 -1550
  368. package/src/utils/command-validator.ts +76 -0
  369. package/src/utils/elicitation.ts +10 -7
  370. package/src/utils/error-handler.ts +14 -90
  371. package/src/utils/ini-reader.ts +86 -0
  372. package/src/utils/logger.ts +8 -3
  373. package/src/utils/normalize.test.ts +162 -0
  374. package/src/utils/normalize.ts +60 -0
  375. package/src/utils/path-security.ts +43 -0
  376. package/src/utils/response-factory.ts +44 -0
  377. package/src/utils/response-validator.ts +176 -56
  378. package/src/utils/result-helpers.ts +21 -19
  379. package/src/utils/safe-json.test.ts +90 -0
  380. package/src/utils/safe-json.ts +14 -11
  381. package/src/utils/unreal-command-queue.ts +152 -0
  382. package/src/utils/validation.test.ts +184 -0
  383. package/src/utils/validation.ts +4 -1
  384. package/src/wasm/index.ts +838 -0
  385. package/test-server.mjs +100 -0
  386. package/tests/run-unreal-tool-tests.mjs +242 -14
  387. package/tests/test-animation.mjs +369 -0
  388. package/tests/test-asset-advanced.mjs +82 -0
  389. package/tests/test-asset-errors.mjs +35 -0
  390. package/tests/test-asset-graph.mjs +311 -0
  391. package/tests/test-audio.mjs +417 -0
  392. package/tests/test-automation-timeouts.mjs +98 -0
  393. package/tests/test-behavior-tree.mjs +444 -0
  394. package/tests/test-blueprint-graph.mjs +410 -0
  395. package/tests/test-blueprint.mjs +577 -0
  396. package/tests/test-client-mode.mjs +86 -0
  397. package/tests/test-console-command.mjs +56 -0
  398. package/tests/test-control-actor.mjs +425 -0
  399. package/tests/test-control-editor.mjs +112 -0
  400. package/tests/test-graphql.mjs +372 -0
  401. package/tests/test-input.mjs +349 -0
  402. package/tests/test-inspect.mjs +302 -0
  403. package/tests/test-landscape.mjs +316 -0
  404. package/tests/test-lighting.mjs +428 -0
  405. package/tests/test-manage-asset.mjs +438 -0
  406. package/tests/test-manage-level.mjs +89 -0
  407. package/tests/test-materials.mjs +356 -0
  408. package/tests/test-niagara.mjs +185 -0
  409. package/tests/test-no-inline-python.mjs +122 -0
  410. package/tests/test-performance.mjs +539 -0
  411. package/tests/test-plugin-handshake.mjs +82 -0
  412. package/tests/test-runner.mjs +933 -0
  413. package/tests/test-sequence.mjs +104 -0
  414. package/tests/test-system.mjs +96 -0
  415. package/tests/test-wasm.mjs +283 -0
  416. package/tests/test-world-partition.mjs +215 -0
  417. package/tsconfig.json +3 -3
  418. package/vitest.config.ts +35 -0
  419. package/wasm/Cargo.lock +363 -0
  420. package/wasm/Cargo.toml +42 -0
  421. package/wasm/LICENSE +21 -0
  422. package/wasm/README.md +253 -0
  423. package/wasm/src/dependency_resolver.rs +377 -0
  424. package/wasm/src/lib.rs +153 -0
  425. package/wasm/src/property_parser.rs +271 -0
  426. package/wasm/src/transform_math.rs +396 -0
  427. package/wasm/tests/integration.rs +109 -0
  428. package/.github/workflows/smithery-build.yml +0 -29
  429. package/dist/prompts/index.d.ts +0 -21
  430. package/dist/prompts/index.js +0 -217
  431. package/dist/tools/build_environment_advanced.d.ts +0 -65
  432. package/dist/tools/build_environment_advanced.js +0 -633
  433. package/dist/tools/rc.d.ts +0 -110
  434. package/dist/tools/rc.js +0 -437
  435. package/dist/tools/visual.d.ts +0 -40
  436. package/dist/tools/visual.js +0 -282
  437. package/dist/utils/http.d.ts +0 -6
  438. package/dist/utils/http.js +0 -151
  439. package/dist/utils/python-output.d.ts +0 -18
  440. package/dist/utils/python-output.js +0 -290
  441. package/dist/utils/python.d.ts +0 -2
  442. package/dist/utils/python.js +0 -4
  443. package/dist/utils/stdio-redirect.d.ts +0 -2
  444. package/dist/utils/stdio-redirect.js +0 -20
  445. package/docs/unreal-tool-test-cases.md +0 -574
  446. package/smithery.yaml +0 -29
  447. package/src/prompts/index.ts +0 -249
  448. package/src/tools/build_environment_advanced.ts +0 -732
  449. package/src/tools/rc.ts +0 -515
  450. package/src/tools/visual.ts +0 -281
  451. package/src/utils/http.ts +0 -187
  452. package/src/utils/python-output.ts +0 -351
  453. package/src/utils/python.ts +0 -3
  454. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,11 +1,14 @@
1
1
  // Landscape tools for Unreal Engine with UE 5.6 World Partition support
2
2
  import { UnrealBridge } from '../unreal-bridge.js';
3
- import { bestEffortInterpretedText, coerceBoolean, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
3
+ import { AutomationBridge } from '../automation/index.js';
4
4
  import { ensureVector3 } from '../utils/validation.js';
5
- import { escapePythonString } from '../utils/python.js';
5
+ import { wasmIntegration } from '../wasm/index.js';
6
+ import { ILandscapeTools, StandardActionResponse } from '../types/tool-interfaces.js';
6
7
 
7
- export class LandscapeTools {
8
- constructor(private bridge: UnrealBridge) {}
8
+ export class LandscapeTools implements ILandscapeTools {
9
+ constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
10
+
11
+ setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
9
12
 
10
13
  // Create landscape with World Partition support (UE 5.6)
11
14
  async createLandscape(params: {
@@ -22,7 +25,7 @@ export class LandscapeTools {
22
25
  runtimeGrid?: string;
23
26
  isSpatiallyLoaded?: boolean;
24
27
  dataLayers?: string[];
25
- }) {
28
+ }): Promise<StandardActionResponse> {
26
29
  const name = params.name?.trim();
27
30
  if (!name) {
28
31
  return { success: false, error: 'Landscape name is required' };
@@ -40,281 +43,63 @@ export class LandscapeTools {
40
43
  };
41
44
  }
42
45
 
46
+ if (!this.automationBridge) {
47
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
48
+ }
49
+
43
50
  const [locX, locY, locZ] = ensureVector3(params.location ?? [0, 0, 0], 'landscape location');
51
+ // Use WASM vectorAdd for landscape location processing
52
+ const zeroVector: [number, number, number] = [0, 0, 0];
53
+ const processedLocation = wasmIntegration.vectorAdd(zeroVector, [locX, locY, locZ]);
54
+ console.error('[WASM] Using vectorAdd for landscape positioning');
44
55
  const sectionsPerComponent = Math.max(1, Math.floor(params.sectionsPerComponent ?? 1));
45
56
  const quadsPerSection = Math.max(1, Math.floor(params.quadsPerSection ?? 63));
46
- const componentCount = Math.max(1, Math.floor(params.componentCount ?? 1));
47
-
48
- const defaultSize = 1000;
49
- const scaleX = params.sizeX ? Math.max(0.1, params.sizeX / defaultSize) : 1;
50
- const scaleY = params.sizeY ? Math.max(0.1, params.sizeY / defaultSize) : 1;
51
-
52
- const escapedName = escapePythonString(name);
53
- const escapedMaterial =
54
- params.materialPath && params.materialPath.trim().length > 0
55
- ? escapePythonString(params.materialPath.trim())
56
- : '';
57
-
58
- const runtimeGridFlag = params.runtimeGrid ? 'True' : 'False';
59
- const spatiallyLoadedFlag = params.isSpatiallyLoaded ? 'True' : 'False';
60
- const runtimeGridValue = params.runtimeGrid ? escapePythonString(params.runtimeGrid.trim()) : '';
61
- const dataLayerNames = Array.isArray(params.dataLayers)
62
- ? params.dataLayers
63
- .map(layer => layer?.trim())
64
- .filter((layer): layer is string => Boolean(layer))
65
- .map(layer => escapePythonString(layer))
66
- : [];
67
-
68
- const pythonScript = `
69
- import unreal
70
- import json
71
-
72
- result = {
73
- "success": False,
74
- "message": "",
75
- "error": "",
76
- "warnings": [],
77
- "details": [],
78
- "landscapeName": "",
79
- "landscapeActor": "",
80
- "worldPartition": False,
81
- "runtimeGridRequested": ${runtimeGridFlag},
82
- "spatiallyLoaded": ${spatiallyLoadedFlag}
83
- }
84
-
85
- try:
86
- editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
87
- world = editor_subsystem.get_editor_world() if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world') else None
88
- data_layer_manager = None
89
- world_partition = None
90
- if world:
91
- # Try multiple methods to access World Partition (UE 5.6+)
92
- try:
93
- # Method 1: Try get_world_partition() if it exists
94
- if hasattr(world, 'get_world_partition'):
95
- world_partition = world.get_world_partition()
96
- except (AttributeError, Exception):
97
- pass
98
-
99
- if not world_partition:
100
- try:
101
- # Method 2: Try WorldPartitionSubsystem
102
- wp_subsystem = unreal.get_editor_subsystem(unreal.WorldPartitionSubsystem)
103
- if wp_subsystem:
104
- world_partition = wp_subsystem.get_world_partition(world)
105
- except (AttributeError, Exception):
106
- pass
107
-
108
- if not world_partition:
109
- try:
110
- # Method 3: Check if world has world_partition property
111
- if hasattr(world, 'world_partition'):
112
- world_partition = world.world_partition
113
- except (AttributeError, Exception):
114
- pass
115
-
116
- result["worldPartition"] = world_partition is not None
117
-
118
- if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
119
- try:
120
- data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
121
- except Exception as dlm_error:
122
- result["warnings"].append(f"Data layer manager unavailable: {dlm_error}")
123
-
124
- actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
125
- if not actor_subsystem:
126
- result["error"] = "EditorActorSubsystem unavailable"
127
- else:
128
- existing = None
129
- try:
130
- for actor in actor_subsystem.get_all_level_actors():
131
- if actor and actor.get_actor_label() == "${escapedName}":
132
- existing = actor
133
- break
134
- except Exception as scan_error:
135
- result["warnings"].append(f"Actor scan failed: {scan_error}")
136
-
137
- if existing:
138
- result["success"] = True
139
- result["message"] = "Landscape already exists"
140
- result["landscapeName"] = existing.get_actor_label()
141
- try:
142
- result["landscapeActor"] = existing.get_path_name()
143
- except Exception:
144
- pass
145
- else:
146
- landscape_class = getattr(unreal, "Landscape", None)
147
- if not landscape_class:
148
- result["error"] = "Landscape class unavailable"
149
- else:
150
- location = unreal.Vector(${locX}, ${locY}, ${locZ})
151
- rotation = unreal.Rotator(0.0, 0.0, 0.0)
152
- landscape_actor = actor_subsystem.spawn_actor_from_class(landscape_class, location, rotation)
153
- if not landscape_actor:
154
- result["error"] = "Failed to spawn landscape actor"
155
- else:
156
- # Set label first
157
- try:
158
- landscape_actor.set_actor_label("${escapedName}", True)
159
- except TypeError:
160
- landscape_actor.set_actor_label("${escapedName}")
161
- except Exception as label_error:
162
- result["warnings"].append(f"Failed to set landscape label: {label_error}")
163
-
164
- # Fix component registration by forcing re-registration
165
- # This addresses the "RegisterComponentWithWorld: Trying to register component with IsValid() == false" warning
166
- try:
167
- # Get landscape components and re-register them
168
- landscape_components = landscape_actor.get_components_by_class(unreal.LandscapeComponent)
169
- if landscape_components:
170
- for component in landscape_components:
171
- if hasattr(component, 'register_component'):
172
- try:
173
- component.register_component()
174
- except Exception:
175
- pass
176
- else:
177
- # If no components yet, this is expected for LandscapePlaceholder
178
- # The landscape needs to be "finalized" via editor tools or console commands
179
- result["details"].append("Landscape placeholder created - finalize via editor for full functionality")
180
- except Exception as comp_error:
181
- # Component registration is best-effort; not critical
182
- result["details"].append(f"Component registration attempted (editor finalization may be needed)")
183
-
184
- try:
185
- landscape_actor.set_actor_scale3d(unreal.Vector(${scaleX.toFixed(4)}, ${scaleY.toFixed(4)}, 1.0))
186
- result["details"].append(f"Actor scale set to (${scaleX.toFixed(2)}, ${scaleY.toFixed(2)}, 1.0)")
187
- except Exception as scale_error:
188
- result["warnings"].append(f"Failed to set landscape scale: {scale_error}")
189
-
190
- # Workaround for LandscapeEditorSubsystem Python API limitation
191
- # Use direct property manipulation instead
192
- landscape_configured = False
193
- try:
194
- # Try LandscapeEditorSubsystem if available (may not be in Python API)
195
- landscape_editor = unreal.get_editor_subsystem(unreal.LandscapeEditorSubsystem)
196
- if landscape_editor:
197
- try:
198
- landscape_editor.set_component_size(${sectionsPerComponent}, ${quadsPerSection})
199
- landscape_editor.set_component_count(${componentCount}, ${componentCount})
200
- result["details"].append(f"Component size ${sectionsPerComponent}x${quadsPerSection}, count ${componentCount}x${componentCount}")
201
- landscape_configured = True
202
- except Exception as config_error:
203
- result["details"].append(f"LandscapeEditorSubsystem method limited: {config_error}")
204
- except (AttributeError, Exception):
205
- # Expected - LandscapeEditorSubsystem not available in Python API
206
- pass
207
-
208
- # Fallback: Configure via properties if subsystem not available
209
- if not landscape_configured:
210
- try:
211
- # Set component properties directly
212
- if hasattr(landscape_actor, 'set_editor_property'):
213
- # Note: These properties may not be directly editable post-spawn
214
- # This is documented UE limitation - landscape config is best done via editor tools
215
- result["details"].append(f"Landscape spawned (config via editor tools recommended for ${sectionsPerComponent}x${quadsPerSection} components)")
216
- except Exception:
217
- pass
218
-
219
- ${escapedMaterial ? `try:
220
- material = unreal.EditorAssetLibrary.load_asset("${escapedMaterial}")
221
- if material:
222
- try:
223
- landscape_actor.set_landscape_material(material)
224
- except Exception:
225
- landscape_actor.editor_set_landscape_material(material)
226
- result["details"].append("Landscape material applied")
227
- else:
228
- result["warnings"].append("Landscape material asset not found: ${escapedMaterial}")
229
- except Exception as material_error:
230
- result["warnings"].append(f"Failed to apply landscape material: {material_error}")
231
- ` : ''}
232
- ${runtimeGridValue ? `if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
233
- try:
234
- unreal.WorldPartitionBlueprintLibrary.set_actor_runtime_grid(landscape_actor, "${runtimeGridValue}")
235
- result["details"].append("Runtime grid assigned: ${runtimeGridValue}")
236
- except Exception as grid_error:
237
- result["warnings"].append(f"Failed to assign runtime grid: {grid_error}")
238
- ` : ''}
239
- ${params.isSpatiallyLoaded ? `if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
240
- try:
241
- unreal.WorldPartitionBlueprintLibrary.set_actor_spatially_loaded(landscape_actor, True)
242
- result["details"].append("Actor marked as spatially loaded")
243
- except Exception as spatial_error:
244
- result["warnings"].append(f"Failed to mark as spatially loaded: {spatial_error}")
245
- ` : ''}
246
- ${dataLayerNames.length ? `if result["worldPartition"] and data_layer_manager:
247
- for layer_name in ${JSON.stringify(dataLayerNames)}:
248
- try:
249
- data_layer = data_layer_manager.get_data_layer(layer_name)
250
- if data_layer:
251
- unreal.WorldPartitionBlueprintLibrary.add_actor_to_data_layer(landscape_actor, data_layer)
252
- result["details"].append(f"Added to data layer {layer_name}")
253
- else:
254
- result["warnings"].append(f"Data layer not found: {layer_name}")
255
- except Exception as data_layer_error:
256
- result["warnings"].append(f"Failed to assign data layer {layer_name}: {data_layer_error}")
257
- ` : ''}
258
-
259
- try:
260
- result["landscapeName"] = landscape_actor.get_actor_label()
261
- result["landscapeActor"] = landscape_actor.get_path_name()
262
- except Exception:
263
- pass
264
-
265
- result["success"] = True
266
- result["message"] = "Landscape actor created"
267
- except Exception as e:
268
- result["error"] = str(e)
269
-
270
- if result.get("success"):
271
- result.pop("error", None)
272
- else:
273
- if not result.get("error"):
274
- result["error"] = "Failed to create landscape actor"
275
- if not result.get("message"):
276
- result["message"] = result["error"]
277
-
278
- if not result.get("warnings"):
279
- result.pop("warnings", None)
280
- if not result.get("details"):
281
- result.pop("details", None)
282
-
283
- print("RESULT:" + json.dumps(result))
284
- `.trim();
285
57
 
286
58
  try {
287
- const response = await this.bridge.executePython(pythonScript);
288
- const interpreted = interpretStandardResult(response, {
289
- successMessage: 'Landscape actor created',
290
- failureMessage: 'Failed to create landscape actor'
59
+ // Map to plugin-native payload shape
60
+ const componentsX = Math.max(1, Math.floor((params.componentCount ?? Math.max(1, Math.floor((params.sizeX ?? 1000) / 1000)))));
61
+ const componentsY = Math.max(1, Math.floor((params.componentCount ?? Math.max(1, Math.floor((params.sizeY ?? 1000) / 1000)))));
62
+ const quadsPerComponent = quadsPerSection; // Plugin uses quadsPerComponent
63
+
64
+ const payload: Record<string, unknown> = {
65
+ name,
66
+ x: processedLocation[0],
67
+ y: processedLocation[1],
68
+ z: processedLocation[2],
69
+ componentsX,
70
+ componentsY,
71
+ quadsPerComponent,
72
+ sectionsPerComponent,
73
+ materialPath: params.materialPath || ''
74
+ };
75
+
76
+ const response = await this.automationBridge.sendAutomationRequest('create_landscape', payload, {
77
+ timeoutMs: 60000
291
78
  });
292
79
 
293
- if (!interpreted.success) {
80
+ if (response.success === false) {
294
81
  return {
295
82
  success: false,
296
- error: interpreted.error || interpreted.message
83
+ error: response.error || response.message || 'Failed to create landscape actor'
297
84
  };
298
85
  }
299
86
 
300
87
  const result: Record<string, unknown> = {
301
88
  success: true,
302
- message: interpreted.message,
303
- landscapeName: coerceString(interpreted.payload.landscapeName) ?? name,
304
- worldPartition: coerceBoolean(interpreted.payload.worldPartition)
89
+ message: response.message || 'Landscape actor created',
90
+ landscapeName: response.landscapeName || name,
91
+ worldPartition: response.worldPartition ?? params.enableWorldPartition ?? false
305
92
  };
306
93
 
307
- const actorPath = coerceString(interpreted.payload.landscapeActor);
308
- if (actorPath) {
309
- result.landscapeActor = actorPath;
94
+ if (response.landscapeActor) {
95
+ result.landscapeActor = response.landscapeActor;
310
96
  }
311
- if (interpreted.warnings?.length) {
312
- result.warnings = interpreted.warnings;
97
+ if (response.warnings) {
98
+ result.warnings = response.warnings;
313
99
  }
314
- if (interpreted.details?.length) {
315
- result.details = interpreted.details;
100
+ if (response.details) {
101
+ result.details = response.details;
316
102
  }
317
-
318
103
  if (params.runtimeGrid) {
319
104
  result.runtimeGrid = params.runtimeGrid;
320
105
  }
@@ -322,145 +107,278 @@ print("RESULT:" + json.dumps(result))
322
107
  result.spatiallyLoaded = params.isSpatiallyLoaded;
323
108
  }
324
109
 
325
- return result;
110
+ return result as StandardActionResponse;
326
111
  } catch (error) {
327
112
  return {
328
113
  success: false,
329
- error: `Failed to create landscape actor: ${error}`
114
+ error: `Failed to create landscape actor: ${error instanceof Error ? error.message : String(error)}`
330
115
  };
331
116
  }
332
117
  }
333
118
 
119
+
334
120
  // Sculpt landscape
335
- async sculptLandscape(_params: {
121
+ async sculptLandscape(params: {
336
122
  landscapeName: string;
337
- tool: 'Sculpt' | 'Smooth' | 'Flatten' | 'Ramp' | 'Erosion' | 'Hydro' | 'Noise' | 'Retopologize';
123
+ tool: string;
338
124
  brushSize?: number;
339
125
  brushFalloff?: number;
340
126
  strength?: number;
341
- position?: [number, number, number];
342
- }) {
343
- return { success: false, error: 'sculptLandscape not implemented via Remote Control. Requires Landscape editor tools.' };
127
+ location?: [number, number, number];
128
+ radius?: number;
129
+ }): Promise<StandardActionResponse> {
130
+ const [x, y, z] = ensureVector3(params.location ?? [0, 0, 0], 'sculpt location');
131
+
132
+ const tool = (params.tool || '').trim();
133
+ const lowerTool = tool.toLowerCase();
134
+ const validTools = new Set(['sculpt', 'smooth', 'flatten', 'ramp', 'erosion', 'hydro', 'noise', 'raise', 'lower']);
135
+ const isValidTool = lowerTool.length > 0 && validTools.has(lowerTool);
136
+
137
+ if (!isValidTool) {
138
+ return {
139
+ success: false,
140
+ error: `Invalid sculpt tool: ${params.tool}`
141
+ };
142
+ }
143
+
144
+ if (!this.automationBridge) {
145
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
146
+ }
147
+
148
+ const payload = {
149
+ landscapeName: params.landscapeName?.trim(),
150
+ toolMode: tool, // Map 'tool' to 'toolMode'
151
+ brushRadius: params.brushSize ?? params.radius ?? 1000,
152
+ brushFalloff: params.brushFalloff ?? 0.5,
153
+ strength: params.strength ?? 0.1,
154
+ location: { x, y, z }
155
+ };
156
+
157
+ const response = await this.automationBridge.sendAutomationRequest('sculpt_landscape', payload);
158
+
159
+ if (!response.success) {
160
+ return {
161
+ success: false,
162
+ error: response.error || 'Failed to sculpt landscape'
163
+ };
164
+ }
165
+
166
+ return {
167
+ success: true,
168
+ message: `Sculpting applied to ${params.landscapeName}`,
169
+ details: response
170
+ } as StandardActionResponse;
344
171
  }
345
172
 
346
173
  // Paint landscape
347
- async paintLandscape(_params: {
174
+ async paintLandscape(params: {
348
175
  landscapeName: string;
349
176
  layerName: string;
350
177
  position: [number, number, number];
351
178
  brushSize?: number;
352
179
  strength?: number;
353
180
  targetValue?: number;
354
- }) {
355
- return { success: false, error: 'paintLandscape not implemented via Remote Control. Requires Landscape editor tools.' };
356
- }
181
+ radius?: number;
182
+ density?: number;
183
+ }): Promise<StandardActionResponse> {
184
+ if (!this.automationBridge) {
185
+ throw new Error('Automation Bridge not available.');
186
+ }
357
187
 
358
- // Add landscape layer
359
- async addLandscapeLayer(params: {
360
- landscapeName: string;
361
- layerName: string;
362
- weightMapPath?: string;
363
- blendMode?: 'Weight' | 'Alpha';
364
- }) {
365
- const commands: string[] = [];
366
-
367
- commands.push(`AddLandscapeLayer ${params.landscapeName} ${params.layerName}`);
368
-
369
- if (params.weightMapPath) {
370
- commands.push(`SetLayerWeightMap ${params.layerName} ${params.weightMapPath}`);
371
- }
372
-
373
- if (params.blendMode) {
374
- commands.push(`SetLayerBlendMode ${params.layerName} ${params.blendMode}`);
375
- }
376
-
377
- await this.bridge.executeConsoleCommands(commands);
378
-
379
- return { success: true, message: `Layer ${params.layerName} added to landscape` };
380
- }
188
+ const [x, y] = ensureVector3(params.position, 'paint position');
189
+ const radius = params.brushSize ?? params.radius ?? 1000;
381
190
 
382
- // Create landscape spline
383
- async createLandscapeSpline(params: {
384
- landscapeName: string;
385
- splineName: string;
386
- points: Array<[number, number, number]>;
387
- width?: number;
388
- falloffWidth?: number;
389
- meshPath?: string;
390
- }) {
391
- const commands: string[] = [];
392
-
393
- commands.push(`CreateLandscapeSpline ${params.landscapeName} ${params.splineName}`);
394
-
395
- for (const point of params.points) {
396
- commands.push(`AddSplinePoint ${params.splineName} ${point.join(' ')}`);
397
- }
398
-
399
- if (params.width !== undefined) {
400
- commands.push(`SetSplineWidth ${params.splineName} ${params.width}`);
401
- }
402
-
403
- if (params.falloffWidth !== undefined) {
404
- commands.push(`SetSplineFalloffWidth ${params.splineName} ${params.falloffWidth}`);
405
- }
406
-
407
- if (params.meshPath) {
408
- commands.push(`SetSplineMesh ${params.splineName} ${params.meshPath}`);
409
- }
410
-
411
- await this.bridge.executeConsoleCommands(commands);
412
-
413
- return { success: true, message: `Landscape spline ${params.splineName} created` };
191
+ // Map brush to a square region for now as C++ only supports region fill
192
+ const minX = Math.floor(x - radius);
193
+ const maxX = Math.floor(x + radius);
194
+ const minY = Math.floor(y - radius);
195
+ const maxY = Math.floor(y + radius);
196
+
197
+ const payload = {
198
+ landscapeName: params.landscapeName?.trim(),
199
+ layerName: params.layerName?.trim(),
200
+ region: { minX, minY, maxX, maxY },
201
+ strength: params.strength ?? 1.0
202
+ };
203
+
204
+ const response = await this.automationBridge.sendAutomationRequest('paint_landscape_layer', payload);
205
+
206
+ if (!response.success) {
207
+ return {
208
+ success: false,
209
+ error: response.error || 'Failed to paint landscape layer'
210
+ };
211
+ }
212
+
213
+ return {
214
+ success: true,
215
+ message: `Painted layer ${params.layerName}`,
216
+ details: response
217
+ } as StandardActionResponse;
414
218
  }
415
219
 
416
- // Import heightmap
417
- async importHeightmap(params: {
418
- landscapeName: string;
419
- heightmapPath: string;
420
- scale?: [number, number, number];
421
- }) {
422
- const scale = params.scale || [100, 100, 100];
423
- const command = `ImportLandscapeHeightmap ${params.landscapeName} ${params.heightmapPath} ${scale.join(' ')}`;
424
-
425
- return this.bridge.executeConsoleCommand(command);
220
+ // Create procedural terrain using ProceduralMeshComponent
221
+ async createProceduralTerrain(params: {
222
+ name: string;
223
+ location?: [number, number, number];
224
+ sizeX?: number;
225
+ sizeY?: number;
226
+ subdivisions?: number;
227
+ heightFunction?: string; // Expression for height calculation
228
+ material?: string;
229
+ settings?: Record<string, unknown>;
230
+ }): Promise<StandardActionResponse> {
231
+ if (!this.automationBridge) {
232
+ throw new Error('Automation Bridge not available. Procedural terrain creation requires plugin support.');
233
+ }
234
+
235
+ try {
236
+ // Combine specific params with generic settings
237
+ const payload = {
238
+ name: params.name,
239
+ location: params.location || [0, 0, 0],
240
+ sizeX: params.sizeX || 2000,
241
+ sizeY: params.sizeY || 2000,
242
+ subdivisions: params.subdivisions || 50,
243
+ heightFunction: params.heightFunction || 'math.sin(x/100) * 50 + math.cos(y/100) * 30',
244
+ material: params.material,
245
+ ...params.settings
246
+ };
247
+
248
+ const response = await this.automationBridge.sendAutomationRequest('create_procedural_terrain', payload, {
249
+ timeoutMs: 120000 // 2 minutes for mesh generation
250
+ });
251
+
252
+ if (response.success === false) {
253
+ return {
254
+ success: false,
255
+ error: response.error || response.message || 'Failed to create procedural terrain',
256
+ message: response.message || 'Failed to create procedural terrain'
257
+ };
258
+ }
259
+
260
+ const result = response.result as any;
261
+ return {
262
+ success: true,
263
+ message: response.message || `Created procedural terrain '${params.name}'`,
264
+ actorName: result?.actor_name,
265
+ vertices: result?.vertices,
266
+ triangles: result?.triangles,
267
+ size: result?.size,
268
+ subdivisions: result?.subdivisions,
269
+ details: result
270
+ } as StandardActionResponse;
271
+ } catch (error) {
272
+ return {
273
+ success: false,
274
+ error: `Failed to create procedural terrain: ${error instanceof Error ? error.message : String(error)}`
275
+ };
276
+ }
426
277
  }
427
278
 
428
- // Export heightmap
429
- async exportHeightmap(params: {
430
- landscapeName: string;
431
- exportPath: string;
432
- format?: 'PNG' | 'RAW';
433
- }) {
434
- const format = params.format || 'PNG';
435
- const command = `ExportLandscapeHeightmap ${params.landscapeName} ${params.exportPath} ${format}`;
436
-
437
- return this.bridge.executeConsoleCommand(command);
279
+ // Create a LandscapeGrassType asset via AutomationBridge
280
+ async createLandscapeGrassType(params: {
281
+ name: string;
282
+ meshPath: string; // Normalized parameter name (was path/staticMesh/meshPath)
283
+ density?: number;
284
+ minScale?: number;
285
+ maxScale?: number;
286
+ path?: string; // Legacy support
287
+ staticMesh?: string; // Legacy support
288
+ }): Promise<StandardActionResponse> {
289
+ if (!this.automationBridge) {
290
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
291
+ }
292
+
293
+ const name = typeof params.name === 'string' ? params.name.trim() : '';
294
+ if (!name) {
295
+ return { success: false, error: 'Grass type name is required' };
296
+ }
297
+
298
+ // Accept mesh path from multiple fields for compatibility
299
+ const meshPathRaw = typeof params.meshPath === 'string' && params.meshPath.trim().length > 0
300
+ ? params.meshPath.trim()
301
+ : (typeof params.path === 'string' && params.path.trim().length > 0
302
+ ? params.path.trim()
303
+ : (typeof params.staticMesh === 'string' && params.staticMesh.trim().length > 0
304
+ ? params.staticMesh.trim()
305
+ : ''));
306
+
307
+ if (!meshPathRaw) {
308
+ return { success: false, error: 'meshPath is required to create a landscape grass type' };
309
+ }
310
+
311
+ try {
312
+ const response: any = await this.automationBridge.sendAutomationRequest('create_landscape_grass_type', {
313
+ name,
314
+ meshPath: meshPathRaw,
315
+ density: params.density || 1.0,
316
+ minScale: params.minScale || 0.8,
317
+ maxScale: params.maxScale || 1.2
318
+ }, { timeoutMs: 90000 });
319
+
320
+ if (response && response.success === false) {
321
+ return {
322
+ success: false,
323
+ error: response.error || response.message || 'Failed to create landscape grass type'
324
+ };
325
+ }
326
+
327
+ const result = response.result as any;
328
+ return {
329
+ success: true,
330
+ message: response?.message || `Landscape grass type '${name}' created`,
331
+ assetPath: result?.asset_path || response?.assetPath || response?.asset_path
332
+ } as StandardActionResponse;
333
+ } catch (error) {
334
+ return {
335
+ success: false,
336
+ error: `Failed to create landscape grass type: ${error instanceof Error ? error.message : String(error)}`
337
+ };
338
+ }
438
339
  }
439
340
 
440
- // Set landscape LOD
441
- async setLandscapeLOD(params: {
442
- landscapeName: string;
443
- lodBias?: number;
444
- forcedLOD?: number;
445
- lodDistribution?: number;
446
- }) {
447
- const commands: string[] = [];
448
-
449
- if (params.lodBias !== undefined) {
450
- commands.push(`SetLandscapeLODBias ${params.landscapeName} ${params.lodBias}`);
451
- }
452
-
453
- if (params.forcedLOD !== undefined) {
454
- commands.push(`SetLandscapeForcedLOD ${params.landscapeName} ${params.forcedLOD}`);
455
- }
456
-
457
- if (params.lodDistribution !== undefined) {
458
- commands.push(`SetLandscapeLODDistribution ${params.landscapeName} ${params.lodDistribution}`);
459
- }
460
-
461
- await this.bridge.executeConsoleCommands(commands);
462
-
463
- return { success: true, message: 'Landscape LOD settings updated' };
341
+ // Set the material used by an existing landscape actor
342
+ async setLandscapeMaterial(params: { landscapeName: string; materialPath: string }): Promise<StandardActionResponse> {
343
+ const landscapeName = typeof params.landscapeName === 'string' ? params.landscapeName.trim() : '';
344
+ const materialPath = typeof params.materialPath === 'string' ? params.materialPath.trim() : '';
345
+
346
+ if (!landscapeName) {
347
+ return { success: false, error: 'Landscape name is required' };
348
+ }
349
+ if (!materialPath) {
350
+ return { success: false, error: 'materialPath is required' };
351
+ }
352
+
353
+ if (!this.automationBridge) {
354
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
355
+ }
356
+
357
+ try {
358
+ const response: any = await this.automationBridge.sendAutomationRequest('set_landscape_material', {
359
+ landscapeName,
360
+ materialPath
361
+ }, { timeoutMs: 60000 });
362
+
363
+ if (response && response.success === false) {
364
+ return {
365
+ success: false,
366
+ error: response.error || response.message || 'Failed to set landscape material'
367
+ };
368
+ }
369
+
370
+ return {
371
+ success: true,
372
+ message: response?.message || `Landscape material set on '${landscapeName}'`,
373
+ landscapeName: response?.landscapeName || landscapeName,
374
+ materialPath: response?.materialPath || materialPath
375
+ } as StandardActionResponse;
376
+ } catch (error) {
377
+ return {
378
+ success: false,
379
+ error: `Failed to set landscape material: ${error instanceof Error ? error.message : String(error)}`
380
+ };
381
+ }
464
382
  }
465
383
 
466
384
  // Create landscape grass
@@ -471,25 +389,25 @@ print("RESULT:" + json.dumps(result))
471
389
  minScale?: number;
472
390
  maxScale?: number;
473
391
  randomRotation?: boolean;
474
- }) {
475
- const commands: string[] = [];
476
-
392
+ }): Promise<StandardActionResponse> {
393
+ const commands: string[] = [];
394
+
477
395
  commands.push(`CreateLandscapeGrass ${params.landscapeName} ${params.grassType}`);
478
-
396
+
479
397
  if (params.density !== undefined) {
480
398
  commands.push(`SetGrassDensity ${params.grassType} ${params.density}`);
481
399
  }
482
-
400
+
483
401
  if (params.minScale !== undefined && params.maxScale !== undefined) {
484
402
  commands.push(`SetGrassScale ${params.grassType} ${params.minScale} ${params.maxScale}`);
485
403
  }
486
-
404
+
487
405
  if (params.randomRotation !== undefined) {
488
406
  commands.push(`SetGrassRandomRotation ${params.grassType} ${params.randomRotation}`);
489
407
  }
490
-
408
+
491
409
  await this.bridge.executeConsoleCommands(commands);
492
-
410
+
493
411
  return { success: true, message: `Grass type ${params.grassType} created on landscape` };
494
412
  }
495
413
 
@@ -498,21 +416,21 @@ print("RESULT:" + json.dumps(result))
498
416
  landscapeName: string;
499
417
  collisionMipLevel?: number;
500
418
  simpleCollision?: boolean;
501
- }) {
502
- const commands: string[] = [];
503
-
419
+ }): Promise<StandardActionResponse> {
420
+ const commands: string[] = [];
421
+
504
422
  if (params.collisionMipLevel !== undefined) {
505
423
  commands.push(`SetLandscapeCollisionMipLevel ${params.landscapeName} ${params.collisionMipLevel}`);
506
424
  }
507
-
425
+
508
426
  if (params.simpleCollision !== undefined) {
509
427
  commands.push(`SetLandscapeSimpleCollision ${params.landscapeName} ${params.simpleCollision}`);
510
428
  }
511
-
429
+
512
430
  commands.push(`UpdateLandscapeCollision ${params.landscapeName}`);
513
-
431
+
514
432
  await this.bridge.executeConsoleCommands(commands);
515
-
433
+
516
434
  return { success: true, message: 'Landscape collision updated' };
517
435
  }
518
436
 
@@ -521,21 +439,21 @@ print("RESULT:" + json.dumps(result))
521
439
  landscapeName: string;
522
440
  targetTriangleCount?: number;
523
441
  preserveDetails?: boolean;
524
- }) {
525
- const commands: string[] = [];
526
-
442
+ }): Promise<StandardActionResponse> {
443
+ const commands: string[] = [];
444
+
527
445
  if (params.targetTriangleCount !== undefined) {
528
446
  commands.push(`SetRetopologizeTarget ${params.targetTriangleCount}`);
529
447
  }
530
-
448
+
531
449
  if (params.preserveDetails !== undefined) {
532
450
  commands.push(`SetRetopologizePreserveDetails ${params.preserveDetails}`);
533
451
  }
534
-
452
+
535
453
  commands.push(`RetopologizeLandscape ${params.landscapeName}`);
536
-
454
+
537
455
  await this.bridge.executeConsoleCommands(commands);
538
-
456
+
539
457
  return { success: true, message: 'Landscape retopologized' };
540
458
  }
541
459
 
@@ -546,13 +464,13 @@ print("RESULT:" + json.dumps(result))
546
464
  location?: [number, number, number];
547
465
  size?: [number, number];
548
466
  depth?: number;
549
- }) {
467
+ }): Promise<StandardActionResponse> {
550
468
  const loc = params.location || [0, 0, 0];
551
469
  const size = params.size || [1000, 1000];
552
470
  const depth = params.depth || 100;
553
-
471
+
554
472
  const command = `CreateWaterBody ${params.type} ${params.name} ${loc.join(' ')} ${size.join(' ')} ${depth}`;
555
-
473
+
556
474
  return this.bridge.executeConsoleCommand(command);
557
475
  }
558
476
 
@@ -563,105 +481,36 @@ print("RESULT:" + json.dumps(result))
563
481
  runtimeGrid?: string;
564
482
  dataLayers?: string[];
565
483
  streamingDistance?: number;
566
- }) {
567
- try {
568
- const pythonScript = `
569
- import unreal
570
- import json
571
-
572
- result = {'success': False, 'error': 'Landscape not found'}
573
-
574
- try:
575
- # Get the landscape actor using modern EditorActorSubsystem
576
- actors = []
577
- try:
578
- actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
579
- if actor_subsystem and hasattr(actor_subsystem, 'get_all_level_actors'):
580
- actors = actor_subsystem.get_all_level_actors()
581
- except Exception:
582
- actors = []
583
- landscape = None
584
-
585
- for actor in actors:
586
- if actor.get_name() == "${params.landscapeName}" or actor.get_actor_label() == "${params.landscapeName}":
587
- if isinstance(actor, unreal.LandscapeProxy) or isinstance(actor, unreal.Landscape):
588
- landscape = actor
589
- break
590
-
591
- if landscape:
592
- changes_made = []
593
-
594
- # Configure spatial loading (UE 5.6)
595
- if ${params.enableSpatialLoading !== undefined ? 'True' : 'False'}:
596
- try:
597
- landscape.set_editor_property('is_spatially_loaded', ${params.enableSpatialLoading || false})
598
- changes_made.append("Spatial loading: ${params.enableSpatialLoading}")
599
- except:
600
- pass
601
-
602
- # Set runtime grid (UE 5.6 World Partition)
603
- if "${params.runtimeGrid || ''}":
604
- try:
605
- landscape.set_editor_property('runtime_grid', unreal.Name("${params.runtimeGrid}"))
606
- changes_made.append("Runtime grid: ${params.runtimeGrid}")
607
- except:
608
- pass
609
-
610
- # Configure data layers (UE 5.6)
611
- if ${params.dataLayers ? 'True' : 'False'}:
612
- try:
613
- # Try modern subsystem first
614
- try:
615
- world = None
616
- editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
617
- if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world'):
618
- world = editor_subsystem.get_editor_world()
619
- if world is None:
620
- world = unreal.EditorSubsystemLibrary.get_editor_world()
621
- except Exception:
622
- world = unreal.EditorSubsystemLibrary.get_editor_world()
623
- data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
624
- if data_layer_manager:
625
- # Note: Full data layer API requires additional setup
626
- changes_made.append("Data layers: Requires manual configuration")
627
- except:
628
- pass
629
-
630
- if changes_made:
631
- result = {
632
- 'success': True,
633
- 'message': 'World Partition configured',
634
- 'changes': changes_made
635
- }
636
- else:
637
- result = {
638
- 'success': False,
639
- 'error': 'No World Partition changes applied'
640
- }
641
-
642
- except Exception as e:
643
- result = {'success': False, 'error': str(e)}
644
-
645
- print('RESULT:' + json.dumps(result))
646
- `.trim();
484
+ }): Promise<StandardActionResponse> {
485
+ if (!this.automationBridge) {
486
+ throw new Error('Automation Bridge not available. World Partition operations require plugin support.');
487
+ }
647
488
 
648
- const response = await this.bridge.executePython(pythonScript);
649
- const interpreted = interpretStandardResult(response, {
650
- successMessage: 'World Partition configuration attempted',
651
- failureMessage: 'World Partition configuration failed'
652
- });
489
+ try {
490
+ const response = await this.automationBridge.sendAutomationRequest('configure_landscape_world_partition', {
491
+ landscapeName: params.landscapeName,
492
+ enableSpatialLoading: params.enableSpatialLoading,
493
+ runtimeGrid: params.runtimeGrid || '',
494
+ dataLayers: params.dataLayers || [],
495
+ streamingDistance: params.streamingDistance
496
+ }, {
497
+ timeoutMs: 60000
498
+ });
653
499
 
654
- if (interpreted.success) {
655
- return interpreted.payload as any;
656
- }
500
+ if (response.success === false) {
501
+ return {
502
+ success: false,
503
+ error: response.error || response.message || 'World Partition configuration failed'
504
+ };
505
+ }
657
506
 
658
- return {
659
- success: false,
660
- error: interpreted.error ?? 'World Partition configuration failed',
661
- details: bestEffortInterpretedText(interpreted)
662
- };
507
+ return {
508
+ success: true,
509
+ message: response.message || 'World Partition configured',
510
+ changes: response.changes
511
+ } as StandardActionResponse;
663
512
  } catch (err) {
664
- return { success: false, error: `Failed to configure World Partition: ${err}` };
513
+ return { success: false, error: `Failed to configure World Partition: ${err instanceof Error ? err.message : String(err)}` };
665
514
  }
666
515
  }
667
516
 
@@ -670,10 +519,10 @@ print('RESULT:' + json.dumps(result))
670
519
  landscapeName: string;
671
520
  dataLayerNames: string[];
672
521
  operation: 'add' | 'remove' | 'set';
673
- }) {
522
+ }): Promise<StandardActionResponse> {
674
523
  try {
675
524
  const commands = [];
676
-
525
+
677
526
  // Use console commands for data layer management
678
527
  if (params.operation === 'set' || params.operation === 'add') {
679
528
  for (const layerName of params.dataLayerNames) {
@@ -684,15 +533,15 @@ print('RESULT:' + json.dumps(result))
684
533
  commands.push(`wp.Runtime.SetDataLayerRuntimeState Unloaded ${layerName}`);
685
534
  }
686
535
  }
687
-
536
+
688
537
  // Execute commands
689
538
  await this.bridge.executeConsoleCommands(commands);
690
-
691
- return {
692
- success: true,
539
+
540
+ return {
541
+ success: true,
693
542
  message: `Data layers ${params.operation === 'add' ? 'added' : params.operation === 'remove' ? 'removed' : 'set'} for landscape`,
694
543
  layers: params.dataLayerNames
695
- };
544
+ } as StandardActionResponse;
696
545
  } catch (err) {
697
546
  return { success: false, error: `Failed to manage data layers: ${err}` };
698
547
  }
@@ -704,35 +553,97 @@ print('RESULT:' + json.dumps(result))
704
553
  cellSize?: number;
705
554
  loadingRange?: number;
706
555
  enableHLOD?: boolean;
707
- }) {
556
+ }): Promise<StandardActionResponse> {
708
557
  const commands = [];
709
-
558
+
710
559
  // World Partition runtime commands
711
560
  if (params.loadingRange !== undefined) {
712
561
  commands.push(`wp.Runtime.OverrideRuntimeSpatialHashLoadingRange -grid=0 -range=${params.loadingRange}`);
713
562
  }
714
-
563
+
715
564
  if (params.enableHLOD !== undefined) {
716
565
  commands.push(`wp.Runtime.HLOD ${params.enableHLOD ? '1' : '0'}`);
717
566
  }
718
-
567
+
719
568
  // Debug visualization commands
720
569
  commands.push('wp.Runtime.ToggleDrawRuntimeHash2D'); // Show 2D grid
721
-
570
+
722
571
  try {
723
572
  await this.bridge.executeConsoleCommands(commands);
724
-
725
- return {
726
- success: true,
573
+
574
+ return {
575
+ success: true,
727
576
  message: 'Streaming cells configured for World Partition',
728
577
  settings: {
729
578
  cellSize: params.cellSize,
730
579
  loadingRange: params.loadingRange,
731
580
  hlod: params.enableHLOD
732
581
  }
733
- };
582
+ } as StandardActionResponse;
734
583
  } catch (err) {
735
584
  return { success: false, error: `Failed to configure streaming cells: ${err}` };
736
585
  }
737
586
  }
587
+
588
+ // Modify landscape heightmap
589
+ async modifyHeightmap(params: {
590
+ landscapeName: string;
591
+ heightData: number[];
592
+ minX: number;
593
+ minY: number;
594
+ maxX: number;
595
+ maxY: number;
596
+ updateNormals?: boolean;
597
+ }): Promise<StandardActionResponse> {
598
+ if (!this.automationBridge) {
599
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
600
+ }
601
+
602
+ const { landscapeName, heightData, minX, minY, maxX, maxY } = params;
603
+
604
+ if (!landscapeName) {
605
+ return { success: false, error: 'Landscape name is required' };
606
+ }
607
+ if (!heightData || !Array.isArray(heightData) || heightData.length === 0) {
608
+ return { success: false, error: 'heightData array is required' };
609
+ }
610
+
611
+ const width = maxX - minX + 1;
612
+ const height = maxY - minY + 1;
613
+ if (heightData.length !== width * height) {
614
+ return {
615
+ success: false,
616
+ error: `Height data length (${heightData.length}) does not match region dimensions (${width}x${height} = ${width * height})`
617
+ };
618
+ }
619
+
620
+ try {
621
+ const response = await this.automationBridge.sendAutomationRequest('modify_heightmap', {
622
+ landscapeName,
623
+ heightData,
624
+ minX,
625
+ minY,
626
+ maxX,
627
+ maxY,
628
+ updateNormals: params.updateNormals ?? true
629
+ }, {
630
+ timeoutMs: 60000
631
+ });
632
+
633
+ if (response.success === false) {
634
+ return {
635
+ success: false,
636
+ error: response.error || response.message || 'Failed to modify heightmap'
637
+ };
638
+ }
639
+
640
+ return {
641
+ success: true,
642
+ message: response.message || 'Heightmap modified successfully'
643
+ } as StandardActionResponse;
644
+ } catch (err) {
645
+ return { success: false, error: `Failed to modify heightmap: ${err instanceof Error ? err.message : String(err)}` };
646
+ }
647
+ }
738
648
  }
649
+