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.
Files changed (438) 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.yml +148 -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 +23 -0
  19. package/.github/workflows/labeler.yml +16 -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 +12 -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 +269 -22
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -72
  31. package/claude_desktop_config_example.json +7 -6
  32. package/dist/automation/bridge.d.ts +50 -0
  33. package/dist/automation/bridge.js +452 -0
  34. package/dist/automation/connection-manager.d.ts +23 -0
  35. package/dist/automation/connection-manager.js +107 -0
  36. package/dist/automation/handshake.d.ts +11 -0
  37. package/dist/automation/handshake.js +89 -0
  38. package/dist/automation/index.d.ts +3 -0
  39. package/dist/automation/index.js +3 -0
  40. package/dist/automation/message-handler.d.ts +12 -0
  41. package/dist/automation/message-handler.js +149 -0
  42. package/dist/automation/request-tracker.d.ts +25 -0
  43. package/dist/automation/request-tracker.js +98 -0
  44. package/dist/automation/types.d.ts +130 -0
  45. package/dist/automation/types.js +2 -0
  46. package/dist/cli.js +32 -5
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.js +60 -0
  49. package/dist/constants.d.ts +12 -0
  50. package/dist/constants.js +12 -0
  51. package/dist/graphql/resolvers.d.ts +268 -0
  52. package/dist/graphql/resolvers.js +743 -0
  53. package/dist/graphql/schema.d.ts +5 -0
  54. package/dist/graphql/schema.js +437 -0
  55. package/dist/graphql/server.d.ts +26 -0
  56. package/dist/graphql/server.js +115 -0
  57. package/dist/graphql/types.d.ts +7 -0
  58. package/dist/graphql/types.js +2 -0
  59. package/dist/handlers/resource-handlers.d.ts +20 -0
  60. package/dist/handlers/resource-handlers.js +180 -0
  61. package/dist/index.d.ts +31 -18
  62. package/dist/index.js +119 -604
  63. package/dist/prompts/index.js +4 -4
  64. package/dist/resources/actors.d.ts +17 -12
  65. package/dist/resources/actors.js +56 -76
  66. package/dist/resources/assets.d.ts +6 -14
  67. package/dist/resources/assets.js +115 -147
  68. package/dist/resources/levels.d.ts +13 -13
  69. package/dist/resources/levels.js +25 -34
  70. package/dist/server/resource-registry.d.ts +20 -0
  71. package/dist/server/resource-registry.js +37 -0
  72. package/dist/server/tool-registry.d.ts +23 -0
  73. package/dist/server/tool-registry.js +322 -0
  74. package/dist/server-setup.d.ts +21 -0
  75. package/dist/server-setup.js +111 -0
  76. package/dist/services/health-monitor.d.ts +34 -0
  77. package/dist/services/health-monitor.js +105 -0
  78. package/dist/services/metrics-server.d.ts +11 -0
  79. package/dist/services/metrics-server.js +105 -0
  80. package/dist/tools/actors.d.ts +147 -9
  81. package/dist/tools/actors.js +350 -311
  82. package/dist/tools/animation.d.ts +135 -4
  83. package/dist/tools/animation.js +510 -411
  84. package/dist/tools/assets.d.ts +117 -19
  85. package/dist/tools/assets.js +259 -284
  86. package/dist/tools/audio.d.ts +102 -42
  87. package/dist/tools/audio.js +272 -685
  88. package/dist/tools/base-tool.d.ts +17 -0
  89. package/dist/tools/base-tool.js +46 -0
  90. package/dist/tools/behavior-tree.d.ts +94 -0
  91. package/dist/tools/behavior-tree.js +39 -0
  92. package/dist/tools/blueprint/helpers.d.ts +29 -0
  93. package/dist/tools/blueprint/helpers.js +182 -0
  94. package/dist/tools/blueprint.d.ts +228 -118
  95. package/dist/tools/blueprint.js +685 -832
  96. package/dist/tools/consolidated-tool-definitions.d.ts +5475 -1627
  97. package/dist/tools/consolidated-tool-definitions.js +829 -482
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1009
  100. package/dist/tools/debug.d.ts +143 -85
  101. package/dist/tools/debug.js +234 -180
  102. package/dist/tools/dynamic-handler-registry.d.ts +11 -0
  103. package/dist/tools/dynamic-handler-registry.js +101 -0
  104. package/dist/tools/editor.d.ts +139 -18
  105. package/dist/tools/editor.js +239 -244
  106. package/dist/tools/engine.d.ts +10 -4
  107. package/dist/tools/engine.js +13 -5
  108. package/dist/tools/environment.d.ts +36 -0
  109. package/dist/tools/environment.js +267 -0
  110. package/dist/tools/foliage.d.ts +105 -14
  111. package/dist/tools/foliage.js +219 -331
  112. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  113. package/dist/tools/handlers/actor-handlers.js +232 -0
  114. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  115. package/dist/tools/handlers/animation-handlers.js +185 -0
  116. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  117. package/dist/tools/handlers/argument-helper.js +80 -0
  118. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  119. package/dist/tools/handlers/asset-handlers.js +496 -0
  120. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  121. package/dist/tools/handlers/audio-handlers.js +166 -0
  122. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  123. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  124. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  125. package/dist/tools/handlers/common-handlers.js +56 -0
  126. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  127. package/dist/tools/handlers/editor-handlers.js +119 -0
  128. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  129. package/dist/tools/handlers/effect-handlers.js +171 -0
  130. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  131. package/dist/tools/handlers/environment-handlers.js +170 -0
  132. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  133. package/dist/tools/handlers/graph-handlers.js +90 -0
  134. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  135. package/dist/tools/handlers/input-handlers.js +21 -0
  136. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  137. package/dist/tools/handlers/inspect-handlers.js +383 -0
  138. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  139. package/dist/tools/handlers/level-handlers.js +237 -0
  140. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  141. package/dist/tools/handlers/lighting-handlers.js +144 -0
  142. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  143. package/dist/tools/handlers/performance-handlers.js +130 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  146. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  147. package/dist/tools/handlers/sequence-handlers.js +376 -0
  148. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  149. package/dist/tools/handlers/system-handlers.js +506 -0
  150. package/dist/tools/input.d.ts +19 -0
  151. package/dist/tools/input.js +89 -0
  152. package/dist/tools/introspection.d.ts +103 -40
  153. package/dist/tools/introspection.js +425 -568
  154. package/dist/tools/landscape.d.ts +97 -36
  155. package/dist/tools/landscape.js +280 -409
  156. package/dist/tools/level.d.ts +130 -10
  157. package/dist/tools/level.js +639 -675
  158. package/dist/tools/lighting.d.ts +77 -38
  159. package/dist/tools/lighting.js +441 -943
  160. package/dist/tools/logs.d.ts +45 -0
  161. package/dist/tools/logs.js +210 -0
  162. package/dist/tools/materials.d.ts +91 -24
  163. package/dist/tools/materials.js +190 -118
  164. package/dist/tools/niagara.d.ts +149 -39
  165. package/dist/tools/niagara.js +232 -182
  166. package/dist/tools/performance.d.ts +27 -12
  167. package/dist/tools/performance.js +204 -122
  168. package/dist/tools/physics.d.ts +32 -77
  169. package/dist/tools/physics.js +171 -582
  170. package/dist/tools/property-dictionary.d.ts +13 -0
  171. package/dist/tools/property-dictionary.js +82 -0
  172. package/dist/tools/sequence.d.ts +73 -48
  173. package/dist/tools/sequence.js +196 -748
  174. package/dist/tools/tool-definition-utils.d.ts +59 -0
  175. package/dist/tools/tool-definition-utils.js +35 -0
  176. package/dist/tools/ui.d.ts +66 -34
  177. package/dist/tools/ui.js +134 -214
  178. package/dist/types/env.d.ts +0 -3
  179. package/dist/types/env.js +0 -7
  180. package/dist/types/tool-interfaces.d.ts +898 -0
  181. package/dist/types/tool-interfaces.js +2 -0
  182. package/dist/types/tool-types.d.ts +195 -11
  183. package/dist/types/tool-types.js +0 -4
  184. package/dist/unreal-bridge.d.ts +24 -131
  185. package/dist/unreal-bridge.js +364 -1506
  186. package/dist/utils/command-validator.d.ts +9 -0
  187. package/dist/utils/command-validator.js +67 -0
  188. package/dist/utils/elicitation.d.ts +1 -1
  189. package/dist/utils/elicitation.js +12 -15
  190. package/dist/utils/error-handler.d.ts +2 -51
  191. package/dist/utils/error-handler.js +11 -87
  192. package/dist/utils/ini-reader.d.ts +3 -0
  193. package/dist/utils/ini-reader.js +69 -0
  194. package/dist/utils/logger.js +9 -6
  195. package/dist/utils/normalize.d.ts +3 -0
  196. package/dist/utils/normalize.js +56 -0
  197. package/dist/utils/response-factory.d.ts +7 -0
  198. package/dist/utils/response-factory.js +33 -0
  199. package/dist/utils/response-validator.d.ts +3 -24
  200. package/dist/utils/response-validator.js +130 -81
  201. package/dist/utils/result-helpers.d.ts +4 -5
  202. package/dist/utils/result-helpers.js +15 -16
  203. package/dist/utils/safe-json.js +5 -11
  204. package/dist/utils/unreal-command-queue.d.ts +24 -0
  205. package/dist/utils/unreal-command-queue.js +120 -0
  206. package/dist/utils/validation.d.ts +0 -40
  207. package/dist/utils/validation.js +1 -78
  208. package/dist/wasm/index.d.ts +70 -0
  209. package/dist/wasm/index.js +535 -0
  210. package/docs/GraphQL-API.md +888 -0
  211. package/docs/Migration-Guide-v0.5.0.md +692 -0
  212. package/docs/Roadmap.md +53 -0
  213. package/docs/WebAssembly-Integration.md +628 -0
  214. package/docs/editor-plugin-extension.md +370 -0
  215. package/docs/handler-mapping.md +242 -0
  216. package/docs/native-automation-progress.md +128 -0
  217. package/docs/testing-guide.md +423 -0
  218. package/mcp-config-example.json +6 -6
  219. package/package.json +60 -27
  220. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  221. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  222. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  223. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  224. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  225. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  226. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  227. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  228. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  272. package/scripts/check-unreal-connection.mjs +19 -0
  273. package/scripts/clean-tmp.js +23 -0
  274. package/scripts/patch-wasm.js +26 -0
  275. package/scripts/run-all-tests.mjs +131 -0
  276. package/scripts/smoke-test.ts +94 -0
  277. package/scripts/sync-mcp-plugin.js +143 -0
  278. package/scripts/test-no-plugin-alternates.mjs +113 -0
  279. package/scripts/validate-server.js +46 -0
  280. package/scripts/verify-automation-bridge.js +200 -0
  281. package/server.json +57 -21
  282. package/src/automation/bridge.ts +558 -0
  283. package/src/automation/connection-manager.ts +130 -0
  284. package/src/automation/handshake.ts +99 -0
  285. package/src/automation/index.ts +2 -0
  286. package/src/automation/message-handler.ts +167 -0
  287. package/src/automation/request-tracker.ts +123 -0
  288. package/src/automation/types.ts +107 -0
  289. package/src/cli.ts +33 -6
  290. package/src/config.ts +73 -0
  291. package/src/constants.ts +12 -0
  292. package/src/graphql/resolvers.ts +1010 -0
  293. package/src/graphql/schema.ts +452 -0
  294. package/src/graphql/server.ts +154 -0
  295. package/src/graphql/types.ts +7 -0
  296. package/src/handlers/resource-handlers.ts +186 -0
  297. package/src/index.ts +152 -649
  298. package/src/prompts/index.ts +4 -4
  299. package/src/resources/actors.ts +58 -76
  300. package/src/resources/assets.ts +147 -134
  301. package/src/resources/levels.ts +28 -33
  302. package/src/server/resource-registry.ts +47 -0
  303. package/src/server/tool-registry.ts +354 -0
  304. package/src/server-setup.ts +148 -0
  305. package/src/services/health-monitor.ts +132 -0
  306. package/src/services/metrics-server.ts +142 -0
  307. package/src/tools/actors.ts +417 -322
  308. package/src/tools/animation.ts +671 -461
  309. package/src/tools/assets.ts +353 -289
  310. package/src/tools/audio.ts +323 -766
  311. package/src/tools/base-tool.ts +52 -0
  312. package/src/tools/behavior-tree.ts +45 -0
  313. package/src/tools/blueprint/helpers.ts +189 -0
  314. package/src/tools/blueprint.ts +787 -965
  315. package/src/tools/consolidated-tool-definitions.ts +993 -500
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1122
  317. package/src/tools/debug.ts +292 -187
  318. package/src/tools/dynamic-handler-registry.ts +151 -0
  319. package/src/tools/editor.ts +309 -246
  320. package/src/tools/engine.ts +14 -3
  321. package/src/tools/environment.ts +287 -0
  322. package/src/tools/foliage.ts +314 -379
  323. package/src/tools/handlers/actor-handlers.ts +271 -0
  324. package/src/tools/handlers/animation-handlers.ts +237 -0
  325. package/src/tools/handlers/argument-helper.ts +142 -0
  326. package/src/tools/handlers/asset-handlers.ts +532 -0
  327. package/src/tools/handlers/audio-handlers.ts +194 -0
  328. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  329. package/src/tools/handlers/common-handlers.ts +87 -0
  330. package/src/tools/handlers/editor-handlers.ts +123 -0
  331. package/src/tools/handlers/effect-handlers.ts +220 -0
  332. package/src/tools/handlers/environment-handlers.ts +183 -0
  333. package/src/tools/handlers/graph-handlers.ts +116 -0
  334. package/src/tools/handlers/input-handlers.ts +28 -0
  335. package/src/tools/handlers/inspect-handlers.ts +450 -0
  336. package/src/tools/handlers/level-handlers.ts +252 -0
  337. package/src/tools/handlers/lighting-handlers.ts +147 -0
  338. package/src/tools/handlers/performance-handlers.ts +132 -0
  339. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  340. package/src/tools/handlers/sequence-handlers.ts +415 -0
  341. package/src/tools/handlers/system-handlers.ts +564 -0
  342. package/src/tools/input.ts +101 -0
  343. package/src/tools/introspection.ts +493 -584
  344. package/src/tools/landscape.ts +394 -489
  345. package/src/tools/level.ts +752 -694
  346. package/src/tools/lighting.ts +583 -984
  347. package/src/tools/logs.ts +219 -0
  348. package/src/tools/materials.ts +231 -121
  349. package/src/tools/niagara.ts +293 -168
  350. package/src/tools/performance.ts +320 -168
  351. package/src/tools/physics.ts +268 -613
  352. package/src/tools/property-dictionary.ts +98 -0
  353. package/src/tools/sequence.ts +255 -815
  354. package/src/tools/tool-definition-utils.ts +35 -0
  355. package/src/tools/ui.ts +207 -283
  356. package/src/types/env.ts +0 -10
  357. package/src/types/tool-interfaces.ts +250 -0
  358. package/src/types/tool-types.ts +250 -13
  359. package/src/unreal-bridge.ts +460 -1550
  360. package/src/utils/command-validator.ts +75 -0
  361. package/src/utils/elicitation.ts +10 -7
  362. package/src/utils/error-handler.ts +14 -90
  363. package/src/utils/ini-reader.ts +86 -0
  364. package/src/utils/logger.ts +8 -3
  365. package/src/utils/normalize.ts +60 -0
  366. package/src/utils/response-factory.ts +39 -0
  367. package/src/utils/response-validator.ts +176 -56
  368. package/src/utils/result-helpers.ts +21 -19
  369. package/src/utils/safe-json.ts +14 -11
  370. package/src/utils/unreal-command-queue.ts +152 -0
  371. package/src/utils/validation.ts +4 -1
  372. package/src/wasm/index.ts +838 -0
  373. package/test-server.mjs +100 -0
  374. package/tests/run-unreal-tool-tests.mjs +242 -14
  375. package/tests/test-animation.mjs +44 -0
  376. package/tests/test-asset-advanced.mjs +82 -0
  377. package/tests/test-asset-errors.mjs +35 -0
  378. package/tests/test-audio.mjs +219 -0
  379. package/tests/test-automation-timeouts.mjs +98 -0
  380. package/tests/test-behavior-tree.mjs +261 -0
  381. package/tests/test-blueprint-events.mjs +35 -0
  382. package/tests/test-blueprint-graph.mjs +79 -0
  383. package/tests/test-blueprint.mjs +577 -0
  384. package/tests/test-client-mode.mjs +86 -0
  385. package/tests/test-console-command.mjs +56 -0
  386. package/tests/test-control-actor.mjs +425 -0
  387. package/tests/test-control-editor.mjs +80 -0
  388. package/tests/test-extra-tools.mjs +38 -0
  389. package/tests/test-graphql.mjs +322 -0
  390. package/tests/test-inspect.mjs +72 -0
  391. package/tests/test-landscape.mjs +60 -0
  392. package/tests/test-manage-asset.mjs +438 -0
  393. package/tests/test-manage-level.mjs +70 -0
  394. package/tests/test-materials.mjs +356 -0
  395. package/tests/test-niagara.mjs +185 -0
  396. package/tests/test-no-inline-python.mjs +122 -0
  397. package/tests/test-plugin-handshake.mjs +82 -0
  398. package/tests/test-render.mjs +33 -0
  399. package/tests/test-runner.mjs +933 -0
  400. package/tests/test-search-assets.mjs +66 -0
  401. package/tests/test-sequence.mjs +68 -0
  402. package/tests/test-system.mjs +57 -0
  403. package/tests/test-wasm.mjs +193 -0
  404. package/tests/test-world-partition.mjs +215 -0
  405. package/tsconfig.json +3 -3
  406. package/wasm/Cargo.lock +363 -0
  407. package/wasm/Cargo.toml +42 -0
  408. package/wasm/LICENSE +21 -0
  409. package/wasm/README.md +253 -0
  410. package/wasm/src/dependency_resolver.rs +377 -0
  411. package/wasm/src/lib.rs +153 -0
  412. package/wasm/src/property_parser.rs +271 -0
  413. package/wasm/src/transform_math.rs +396 -0
  414. package/wasm/tests/integration.rs +109 -0
  415. package/.github/workflows/smithery-build.yml +0 -29
  416. package/dist/tools/build_environment_advanced.d.ts +0 -65
  417. package/dist/tools/build_environment_advanced.js +0 -633
  418. package/dist/tools/rc.d.ts +0 -110
  419. package/dist/tools/rc.js +0 -437
  420. package/dist/tools/visual.d.ts +0 -40
  421. package/dist/tools/visual.js +0 -282
  422. package/dist/utils/http.d.ts +0 -6
  423. package/dist/utils/http.js +0 -151
  424. package/dist/utils/python-output.d.ts +0 -18
  425. package/dist/utils/python-output.js +0 -290
  426. package/dist/utils/python.d.ts +0 -2
  427. package/dist/utils/python.js +0 -4
  428. package/dist/utils/stdio-redirect.d.ts +0 -2
  429. package/dist/utils/stdio-redirect.js +0 -20
  430. package/docs/unreal-tool-test-cases.md +0 -572
  431. package/smithery.yaml +0 -29
  432. package/src/tools/build_environment_advanced.ts +0 -732
  433. package/src/tools/rc.ts +0 -515
  434. package/src/tools/visual.ts +0 -281
  435. package/src/utils/http.ts +0 -187
  436. package/src/utils/python-output.ts +0 -351
  437. package/src/utils/python.ts +0 -3
  438. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,11 +1,12 @@
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';
6
5
 
7
6
  export class LandscapeTools {
8
- constructor(private bridge: UnrealBridge) {}
7
+ constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
8
+
9
+ setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
9
10
 
10
11
  // Create landscape with World Partition support (UE 5.6)
11
12
  async createLandscape(params: {
@@ -40,281 +41,59 @@ export class LandscapeTools {
40
41
  };
41
42
  }
42
43
 
44
+ if (!this.automationBridge) {
45
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
46
+ }
47
+
43
48
  const [locX, locY, locZ] = ensureVector3(params.location ?? [0, 0, 0], 'landscape location');
44
49
  const sectionsPerComponent = Math.max(1, Math.floor(params.sectionsPerComponent ?? 1));
45
50
  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
51
 
286
52
  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'
53
+ // Map to plugin-native payload shape
54
+ const componentsX = Math.max(1, Math.floor((params.componentCount ?? Math.max(1, Math.floor((params.sizeX ?? 1000) / 1000)))));
55
+ const componentsY = Math.max(1, Math.floor((params.componentCount ?? Math.max(1, Math.floor((params.sizeY ?? 1000) / 1000)))));
56
+ const quadsPerComponent = quadsPerSection; // Plugin uses quadsPerComponent
57
+
58
+ const payload: Record<string, unknown> = {
59
+ name,
60
+ x: locX,
61
+ y: locY,
62
+ z: locZ,
63
+ componentsX,
64
+ componentsY,
65
+ quadsPerComponent,
66
+ sectionsPerComponent,
67
+ materialPath: params.materialPath || ''
68
+ };
69
+
70
+ const response = await this.automationBridge.sendAutomationRequest('create_landscape', payload, {
71
+ timeoutMs: 60000
291
72
  });
292
73
 
293
- if (!interpreted.success) {
74
+ if (response.success === false) {
294
75
  return {
295
76
  success: false,
296
- error: interpreted.error || interpreted.message
77
+ error: response.error || response.message || 'Failed to create landscape actor'
297
78
  };
298
79
  }
299
80
 
300
81
  const result: Record<string, unknown> = {
301
82
  success: true,
302
- message: interpreted.message,
303
- landscapeName: coerceString(interpreted.payload.landscapeName) ?? name,
304
- worldPartition: coerceBoolean(interpreted.payload.worldPartition)
83
+ message: response.message || 'Landscape actor created',
84
+ landscapeName: response.landscapeName || name,
85
+ worldPartition: response.worldPartition ?? params.enableWorldPartition ?? false
305
86
  };
306
87
 
307
- const actorPath = coerceString(interpreted.payload.landscapeActor);
308
- if (actorPath) {
309
- result.landscapeActor = actorPath;
88
+ if (response.landscapeActor) {
89
+ result.landscapeActor = response.landscapeActor;
310
90
  }
311
- if (interpreted.warnings?.length) {
312
- result.warnings = interpreted.warnings;
91
+ if (response.warnings) {
92
+ result.warnings = response.warnings;
313
93
  }
314
- if (interpreted.details?.length) {
315
- result.details = interpreted.details;
94
+ if (response.details) {
95
+ result.details = response.details;
316
96
  }
317
-
318
97
  if (params.runtimeGrid) {
319
98
  result.runtimeGrid = params.runtimeGrid;
320
99
  }
@@ -326,141 +105,274 @@ print("RESULT:" + json.dumps(result))
326
105
  } catch (error) {
327
106
  return {
328
107
  success: false,
329
- error: `Failed to create landscape actor: ${error}`
108
+ error: `Failed to create landscape actor: ${error instanceof Error ? error.message : String(error)}`
330
109
  };
331
110
  }
332
111
  }
333
112
 
113
+
334
114
  // Sculpt landscape
335
- async sculptLandscape(_params: {
115
+ async sculptLandscape(params: {
336
116
  landscapeName: string;
337
- tool: 'Sculpt' | 'Smooth' | 'Flatten' | 'Ramp' | 'Erosion' | 'Hydro' | 'Noise' | 'Retopologize';
117
+ tool: string;
338
118
  brushSize?: number;
339
119
  brushFalloff?: number;
340
120
  strength?: number;
341
- position?: [number, number, number];
121
+ location?: [number, number, number];
122
+ radius?: number;
342
123
  }) {
343
- return { success: false, error: 'sculptLandscape not implemented via Remote Control. Requires Landscape editor tools.' };
124
+ const [x, y, z] = ensureVector3(params.location ?? [0, 0, 0], 'sculpt location');
125
+
126
+ const tool = (params.tool || '').trim();
127
+ const lowerTool = tool.toLowerCase();
128
+ const validTools = new Set(['sculpt', 'smooth', 'flatten', 'ramp', 'erosion', 'hydro', 'noise', 'raise', 'lower']);
129
+ const isValidTool = lowerTool.length > 0 && validTools.has(lowerTool);
130
+
131
+ if (!isValidTool) {
132
+ return {
133
+ success: false,
134
+ error: `Invalid sculpt tool: ${params.tool}`
135
+ };
136
+ }
137
+
138
+ if (!this.automationBridge) {
139
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
140
+ }
141
+
142
+ const payload = {
143
+ landscapeName: params.landscapeName?.trim(),
144
+ toolMode: tool, // Map 'tool' to 'toolMode'
145
+ brushRadius: params.brushSize ?? params.radius ?? 1000,
146
+ brushFalloff: params.brushFalloff ?? 0.5,
147
+ strength: params.strength ?? 0.1,
148
+ location: { x, y, z }
149
+ };
150
+
151
+ const response = await this.automationBridge.sendAutomationRequest('sculpt_landscape', payload);
152
+
153
+ if (!response.success) {
154
+ return {
155
+ success: false,
156
+ error: response.error || 'Failed to sculpt landscape'
157
+ };
158
+ }
159
+
160
+ return {
161
+ success: true,
162
+ message: `Sculpting applied to ${params.landscapeName}`,
163
+ details: response
164
+ };
344
165
  }
345
166
 
346
167
  // Paint landscape
347
- async paintLandscape(_params: {
168
+ async paintLandscape(params: {
348
169
  landscapeName: string;
349
170
  layerName: string;
350
171
  position: [number, number, number];
351
172
  brushSize?: number;
352
173
  strength?: number;
353
174
  targetValue?: number;
175
+ radius?: number;
176
+ density?: number;
354
177
  }) {
355
- return { success: false, error: 'paintLandscape not implemented via Remote Control. Requires Landscape editor tools.' };
356
- }
178
+ if (!this.automationBridge) {
179
+ throw new Error('Automation Bridge not available.');
180
+ }
357
181
 
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
- }
182
+ const [x, y] = ensureVector3(params.position, 'paint position');
183
+ const radius = params.brushSize ?? params.radius ?? 1000;
381
184
 
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` };
185
+ // Map brush to a square region for now as C++ only supports region fill
186
+ const minX = Math.floor(x - radius);
187
+ const maxX = Math.floor(x + radius);
188
+ const minY = Math.floor(y - radius);
189
+ const maxY = Math.floor(y + radius);
190
+
191
+ const payload = {
192
+ landscapeName: params.landscapeName?.trim(),
193
+ layerName: params.layerName?.trim(),
194
+ region: { minX, minY, maxX, maxY },
195
+ strength: params.strength ?? 1.0
196
+ };
197
+
198
+ const response = await this.automationBridge.sendAutomationRequest('paint_landscape_layer', payload);
199
+
200
+ if (!response.success) {
201
+ return {
202
+ success: false,
203
+ error: response.error || 'Failed to paint landscape layer'
204
+ };
205
+ }
206
+
207
+ return {
208
+ success: true,
209
+ message: `Painted layer ${params.layerName}`,
210
+ details: response
211
+ };
414
212
  }
415
213
 
416
- // Import heightmap
417
- async importHeightmap(params: {
418
- landscapeName: string;
419
- heightmapPath: string;
420
- scale?: [number, number, number];
214
+ // Create procedural terrain using ProceduralMeshComponent
215
+ async createProceduralTerrain(params: {
216
+ name: string;
217
+ location?: [number, number, number];
218
+ sizeX?: number;
219
+ sizeY?: number;
220
+ subdivisions?: number;
221
+ heightFunction?: string; // Expression for height calculation
222
+ material?: string;
223
+ settings?: Record<string, unknown>;
421
224
  }) {
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);
225
+ if (!this.automationBridge) {
226
+ throw new Error('Automation Bridge not available. Procedural terrain creation requires plugin support.');
227
+ }
228
+
229
+ try {
230
+ // Combine specific params with generic settings
231
+ const payload = {
232
+ name: params.name,
233
+ location: params.location || [0, 0, 0],
234
+ sizeX: params.sizeX || 2000,
235
+ sizeY: params.sizeY || 2000,
236
+ subdivisions: params.subdivisions || 50,
237
+ heightFunction: params.heightFunction || 'math.sin(x/100) * 50 + math.cos(y/100) * 30',
238
+ material: params.material,
239
+ ...params.settings
240
+ };
241
+
242
+ const response = await this.automationBridge.sendAutomationRequest('create_procedural_terrain', payload, {
243
+ timeoutMs: 120000 // 2 minutes for mesh generation
244
+ });
245
+
246
+ if (response.success === false) {
247
+ return {
248
+ success: false,
249
+ error: response.error || response.message || 'Failed to create procedural terrain',
250
+ message: response.message || 'Failed to create procedural terrain'
251
+ };
252
+ }
253
+
254
+ const result = response.result as any;
255
+ return {
256
+ success: true,
257
+ message: response.message || `Created procedural terrain '${params.name}'`,
258
+ actorName: result?.actor_name,
259
+ vertices: result?.vertices,
260
+ triangles: result?.triangles,
261
+ size: result?.size,
262
+ subdivisions: result?.subdivisions,
263
+ details: result
264
+ };
265
+ } catch (error) {
266
+ return {
267
+ success: false,
268
+ error: `Failed to create procedural terrain: ${error instanceof Error ? error.message : String(error)}`
269
+ };
270
+ }
426
271
  }
427
272
 
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);
273
+ // Create a LandscapeGrassType asset via AutomationBridge
274
+ async createLandscapeGrassType(params: {
275
+ name: string;
276
+ meshPath: string; // Normalized parameter name (was path/staticMesh/meshPath)
277
+ density?: number;
278
+ minScale?: number;
279
+ maxScale?: number;
280
+ path?: string; // Legacy support
281
+ staticMesh?: string; // Legacy support
282
+ }): Promise<any> {
283
+ if (!this.automationBridge) {
284
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
285
+ }
286
+
287
+ const name = typeof params.name === 'string' ? params.name.trim() : '';
288
+ if (!name) {
289
+ return { success: false, error: 'Grass type name is required' };
290
+ }
291
+
292
+ // Accept mesh path from multiple fields for compatibility
293
+ const meshPathRaw = typeof params.meshPath === 'string' && params.meshPath.trim().length > 0
294
+ ? params.meshPath.trim()
295
+ : (typeof params.path === 'string' && params.path.trim().length > 0
296
+ ? params.path.trim()
297
+ : (typeof params.staticMesh === 'string' && params.staticMesh.trim().length > 0
298
+ ? params.staticMesh.trim()
299
+ : ''));
300
+
301
+ if (!meshPathRaw) {
302
+ return { success: false, error: 'meshPath is required to create a landscape grass type' };
303
+ }
304
+
305
+ try {
306
+ const response: any = await this.automationBridge.sendAutomationRequest('create_landscape_grass_type', {
307
+ name,
308
+ meshPath: meshPathRaw,
309
+ density: params.density || 1.0,
310
+ minScale: params.minScale || 0.8,
311
+ maxScale: params.maxScale || 1.2
312
+ }, { timeoutMs: 90000 });
313
+
314
+ if (response && response.success === false) {
315
+ return {
316
+ success: false,
317
+ error: response.error || response.message || 'Failed to create landscape grass type'
318
+ };
319
+ }
320
+
321
+ const result = response.result as any;
322
+ return {
323
+ success: true,
324
+ message: response?.message || `Landscape grass type '${name}' created`,
325
+ assetPath: result?.asset_path || response?.assetPath || response?.asset_path
326
+ };
327
+ } catch (error) {
328
+ return {
329
+ success: false,
330
+ error: `Failed to create landscape grass type: ${error instanceof Error ? error.message : String(error)}`
331
+ };
332
+ }
438
333
  }
439
334
 
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}`);
335
+ // Set the material used by an existing landscape actor
336
+ async setLandscapeMaterial(params: { landscapeName: string; materialPath: string }): Promise<any> {
337
+ const landscapeName = typeof params.landscapeName === 'string' ? params.landscapeName.trim() : '';
338
+ const materialPath = typeof params.materialPath === 'string' ? params.materialPath.trim() : '';
339
+
340
+ if (!landscapeName) {
341
+ return { success: false, error: 'Landscape name is required' };
451
342
  }
452
-
453
- if (params.forcedLOD !== undefined) {
454
- commands.push(`SetLandscapeForcedLOD ${params.landscapeName} ${params.forcedLOD}`);
343
+ if (!materialPath) {
344
+ return { success: false, error: 'materialPath is required' };
455
345
  }
456
-
457
- if (params.lodDistribution !== undefined) {
458
- commands.push(`SetLandscapeLODDistribution ${params.landscapeName} ${params.lodDistribution}`);
346
+
347
+ if (!this.automationBridge) {
348
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
349
+ }
350
+
351
+ try {
352
+ const response: any = await this.automationBridge.sendAutomationRequest('set_landscape_material', {
353
+ landscapeName,
354
+ materialPath
355
+ }, { timeoutMs: 60000 });
356
+
357
+ if (response && response.success === false) {
358
+ return {
359
+ success: false,
360
+ error: response.error || response.message || 'Failed to set landscape material'
361
+ };
362
+ }
363
+
364
+ return {
365
+ success: true,
366
+ message: response?.message || `Landscape material set on '${landscapeName}'`,
367
+ landscapeName: response?.landscapeName || landscapeName,
368
+ materialPath: response?.materialPath || materialPath
369
+ };
370
+ } catch (error) {
371
+ return {
372
+ success: false,
373
+ error: `Failed to set landscape material: ${error instanceof Error ? error.message : String(error)}`
374
+ };
459
375
  }
460
-
461
- await this.bridge.executeConsoleCommands(commands);
462
-
463
- return { success: true, message: 'Landscape LOD settings updated' };
464
376
  }
465
377
 
466
378
  // Create landscape grass
@@ -472,24 +384,24 @@ print("RESULT:" + json.dumps(result))
472
384
  maxScale?: number;
473
385
  randomRotation?: boolean;
474
386
  }) {
475
- const commands: string[] = [];
476
-
387
+ const commands: string[] = [];
388
+
477
389
  commands.push(`CreateLandscapeGrass ${params.landscapeName} ${params.grassType}`);
478
-
390
+
479
391
  if (params.density !== undefined) {
480
392
  commands.push(`SetGrassDensity ${params.grassType} ${params.density}`);
481
393
  }
482
-
394
+
483
395
  if (params.minScale !== undefined && params.maxScale !== undefined) {
484
396
  commands.push(`SetGrassScale ${params.grassType} ${params.minScale} ${params.maxScale}`);
485
397
  }
486
-
398
+
487
399
  if (params.randomRotation !== undefined) {
488
400
  commands.push(`SetGrassRandomRotation ${params.grassType} ${params.randomRotation}`);
489
401
  }
490
-
402
+
491
403
  await this.bridge.executeConsoleCommands(commands);
492
-
404
+
493
405
  return { success: true, message: `Grass type ${params.grassType} created on landscape` };
494
406
  }
495
407
 
@@ -499,20 +411,20 @@ print("RESULT:" + json.dumps(result))
499
411
  collisionMipLevel?: number;
500
412
  simpleCollision?: boolean;
501
413
  }) {
502
- const commands: string[] = [];
503
-
414
+ const commands: string[] = [];
415
+
504
416
  if (params.collisionMipLevel !== undefined) {
505
417
  commands.push(`SetLandscapeCollisionMipLevel ${params.landscapeName} ${params.collisionMipLevel}`);
506
418
  }
507
-
419
+
508
420
  if (params.simpleCollision !== undefined) {
509
421
  commands.push(`SetLandscapeSimpleCollision ${params.landscapeName} ${params.simpleCollision}`);
510
422
  }
511
-
423
+
512
424
  commands.push(`UpdateLandscapeCollision ${params.landscapeName}`);
513
-
425
+
514
426
  await this.bridge.executeConsoleCommands(commands);
515
-
427
+
516
428
  return { success: true, message: 'Landscape collision updated' };
517
429
  }
518
430
 
@@ -522,20 +434,20 @@ print("RESULT:" + json.dumps(result))
522
434
  targetTriangleCount?: number;
523
435
  preserveDetails?: boolean;
524
436
  }) {
525
- const commands: string[] = [];
526
-
437
+ const commands: string[] = [];
438
+
527
439
  if (params.targetTriangleCount !== undefined) {
528
440
  commands.push(`SetRetopologizeTarget ${params.targetTriangleCount}`);
529
441
  }
530
-
442
+
531
443
  if (params.preserveDetails !== undefined) {
532
444
  commands.push(`SetRetopologizePreserveDetails ${params.preserveDetails}`);
533
445
  }
534
-
446
+
535
447
  commands.push(`RetopologizeLandscape ${params.landscapeName}`);
536
-
448
+
537
449
  await this.bridge.executeConsoleCommands(commands);
538
-
450
+
539
451
  return { success: true, message: 'Landscape retopologized' };
540
452
  }
541
453
 
@@ -550,9 +462,9 @@ print("RESULT:" + json.dumps(result))
550
462
  const loc = params.location || [0, 0, 0];
551
463
  const size = params.size || [1000, 1000];
552
464
  const depth = params.depth || 100;
553
-
465
+
554
466
  const command = `CreateWaterBody ${params.type} ${params.name} ${loc.join(' ')} ${size.join(' ')} ${depth}`;
555
-
467
+
556
468
  return this.bridge.executeConsoleCommand(command);
557
469
  }
558
470
 
@@ -564,104 +476,35 @@ print("RESULT:" + json.dumps(result))
564
476
  dataLayers?: string[];
565
477
  streamingDistance?: number;
566
478
  }) {
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();
479
+ if (!this.automationBridge) {
480
+ throw new Error('Automation Bridge not available. World Partition operations require plugin support.');
481
+ }
647
482
 
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
- });
483
+ try {
484
+ const response = await this.automationBridge.sendAutomationRequest('configure_landscape_world_partition', {
485
+ landscapeName: params.landscapeName,
486
+ enableSpatialLoading: params.enableSpatialLoading,
487
+ runtimeGrid: params.runtimeGrid || '',
488
+ dataLayers: params.dataLayers || [],
489
+ streamingDistance: params.streamingDistance
490
+ }, {
491
+ timeoutMs: 60000
492
+ });
653
493
 
654
- if (interpreted.success) {
655
- return interpreted.payload as any;
656
- }
494
+ if (response.success === false) {
495
+ return {
496
+ success: false,
497
+ error: response.error || response.message || 'World Partition configuration failed'
498
+ };
499
+ }
657
500
 
658
- return {
659
- success: false,
660
- error: interpreted.error ?? 'World Partition configuration failed',
661
- details: bestEffortInterpretedText(interpreted)
662
- };
501
+ return {
502
+ success: true,
503
+ message: response.message || 'World Partition configured',
504
+ changes: response.changes
505
+ };
663
506
  } catch (err) {
664
- return { success: false, error: `Failed to configure World Partition: ${err}` };
507
+ return { success: false, error: `Failed to configure World Partition: ${err instanceof Error ? err.message : String(err)}` };
665
508
  }
666
509
  }
667
510
 
@@ -673,7 +516,7 @@ print('RESULT:' + json.dumps(result))
673
516
  }) {
674
517
  try {
675
518
  const commands = [];
676
-
519
+
677
520
  // Use console commands for data layer management
678
521
  if (params.operation === 'set' || params.operation === 'add') {
679
522
  for (const layerName of params.dataLayerNames) {
@@ -684,12 +527,12 @@ print('RESULT:' + json.dumps(result))
684
527
  commands.push(`wp.Runtime.SetDataLayerRuntimeState Unloaded ${layerName}`);
685
528
  }
686
529
  }
687
-
530
+
688
531
  // Execute commands
689
532
  await this.bridge.executeConsoleCommands(commands);
690
-
691
- return {
692
- success: true,
533
+
534
+ return {
535
+ success: true,
693
536
  message: `Data layers ${params.operation === 'add' ? 'added' : params.operation === 'remove' ? 'removed' : 'set'} for landscape`,
694
537
  layers: params.dataLayerNames
695
538
  };
@@ -706,24 +549,24 @@ print('RESULT:' + json.dumps(result))
706
549
  enableHLOD?: boolean;
707
550
  }) {
708
551
  const commands = [];
709
-
552
+
710
553
  // World Partition runtime commands
711
554
  if (params.loadingRange !== undefined) {
712
555
  commands.push(`wp.Runtime.OverrideRuntimeSpatialHashLoadingRange -grid=0 -range=${params.loadingRange}`);
713
556
  }
714
-
557
+
715
558
  if (params.enableHLOD !== undefined) {
716
559
  commands.push(`wp.Runtime.HLOD ${params.enableHLOD ? '1' : '0'}`);
717
560
  }
718
-
561
+
719
562
  // Debug visualization commands
720
563
  commands.push('wp.Runtime.ToggleDrawRuntimeHash2D'); // Show 2D grid
721
-
564
+
722
565
  try {
723
566
  await this.bridge.executeConsoleCommands(commands);
724
-
725
- return {
726
- success: true,
567
+
568
+ return {
569
+ success: true,
727
570
  message: 'Streaming cells configured for World Partition',
728
571
  settings: {
729
572
  cellSize: params.cellSize,
@@ -735,4 +578,66 @@ print('RESULT:' + json.dumps(result))
735
578
  return { success: false, error: `Failed to configure streaming cells: ${err}` };
736
579
  }
737
580
  }
581
+
582
+ // Modify landscape heightmap
583
+ async modifyHeightmap(params: {
584
+ landscapeName: string;
585
+ heightData: number[];
586
+ minX: number;
587
+ minY: number;
588
+ maxX: number;
589
+ maxY: number;
590
+ updateNormals?: boolean;
591
+ }) {
592
+ if (!this.automationBridge) {
593
+ throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
594
+ }
595
+
596
+ const { landscapeName, heightData, minX, minY, maxX, maxY } = params;
597
+
598
+ if (!landscapeName) {
599
+ return { success: false, error: 'Landscape name is required' };
600
+ }
601
+ if (!heightData || !Array.isArray(heightData) || heightData.length === 0) {
602
+ return { success: false, error: 'heightData array is required' };
603
+ }
604
+
605
+ const width = maxX - minX + 1;
606
+ const height = maxY - minY + 1;
607
+ if (heightData.length !== width * height) {
608
+ return {
609
+ success: false,
610
+ error: `Height data length (${heightData.length}) does not match region dimensions (${width}x${height} = ${width * height})`
611
+ };
612
+ }
613
+
614
+ try {
615
+ const response = await this.automationBridge.sendAutomationRequest('modify_heightmap', {
616
+ landscapeName,
617
+ heightData,
618
+ minX,
619
+ minY,
620
+ maxX,
621
+ maxY,
622
+ updateNormals: params.updateNormals ?? true
623
+ }, {
624
+ timeoutMs: 60000
625
+ });
626
+
627
+ if (response.success === false) {
628
+ return {
629
+ success: false,
630
+ error: response.error || response.message || 'Failed to modify heightmap'
631
+ };
632
+ }
633
+
634
+ return {
635
+ success: true,
636
+ message: response.message || 'Heightmap modified successfully'
637
+ };
638
+ } catch (err) {
639
+ return { success: false, error: `Failed to modify heightmap: ${err instanceof Error ? err.message : String(err)}` };
640
+ }
641
+ }
738
642
  }
643
+