unreal-engine-mcp-server 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +267 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -71
  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 -619
  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 +5462 -1781
  97. package/dist/tools/consolidated-tool-definitions.js +829 -496
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1026
  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 +3 -3
  161. package/dist/tools/logs.js +5 -57
  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 +183 -19
  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 -663
  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 -515
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1139
  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 +9 -57
  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 +243 -21
  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 -574
  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,9 +1,12 @@
1
1
  // Foliage tools for Unreal Engine
2
2
  import { UnrealBridge } from '../unreal-bridge.js';
3
- import { bestEffortInterpretedText, coerceBoolean, coerceNumber, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
3
+ import { AutomationBridge } from '../automation/index.js';
4
+ import { coerceBoolean, coerceNumber, coerceString } from '../utils/result-helpers.js';
4
5
 
5
6
  export class FoliageTools {
6
- constructor(private bridge: UnrealBridge) {}
7
+ constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
8
+
9
+ setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
7
10
 
8
11
  // NOTE: We intentionally avoid issuing Unreal console commands here because
9
12
  // they have proven unreliable and generate engine warnings (failed FindConsoleObject).
@@ -49,196 +52,60 @@ export class FoliageTools {
49
52
  return { success: false, error: errors.join('; ') };
50
53
  }
51
54
 
52
- const py = `
53
- import unreal, json
54
-
55
- name = ${JSON.stringify(name)}
56
- mesh_path = ${JSON.stringify(meshPath)}
57
- fallback_mesh = '/Engine/EngineMeshes/Sphere'
58
- package_path = '/Game/Foliage/Types'
59
-
60
- res = {'success': False, 'created': False, 'asset_path': '', 'used_mesh': '', 'exists_after': False, 'method': '', 'note': ''}
61
-
62
- try:
63
- # Ensure package directory
64
- try:
65
- if not unreal.EditorAssetLibrary.does_directory_exist(package_path):
66
- unreal.EditorAssetLibrary.make_directory(package_path)
67
- except Exception as e:
68
- res['note'] += f"; make_directory failed: {e}"
69
-
70
- # Load mesh or fallback
71
- mesh = None
72
- try:
73
- if unreal.EditorAssetLibrary.does_asset_exist(mesh_path):
74
- mesh = unreal.EditorAssetLibrary.load_asset(mesh_path)
75
- except Exception as e:
76
- res['note'] += f"; could not check/load mesh_path: {e}"
77
-
78
- if not mesh:
79
- mesh = unreal.EditorAssetLibrary.load_asset(fallback_mesh)
80
- res['note'] += '; fallback_mesh_used'
81
- if mesh:
82
- res['used_mesh'] = str(mesh.get_path_name())
83
-
84
- # Create FoliageType asset using proper UE5 API
85
- asset = None
86
- try:
87
- asset_path = f"{package_path}/{name}"
88
-
89
- # Check if asset already exists
90
- if unreal.EditorAssetLibrary.does_asset_exist(asset_path):
91
- asset = unreal.EditorAssetLibrary.load_asset(asset_path)
92
- res['note'] += '; loaded_existing'
93
- else:
94
- # Create FoliageType_InstancedStaticMesh using proper API
95
- try:
96
- asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
97
-
98
- # Try to create factory and set mesh property
99
- factory = None
100
- try:
101
- factory = unreal.FoliageType_InstancedStaticMeshFactory()
102
- # Try different property names for different UE versions
103
- try:
104
- factory.set_editor_property('mesh', mesh)
105
- except:
106
- try:
107
- factory.set_editor_property('static_mesh', mesh)
108
- except:
109
- try:
110
- factory.set_editor_property('source_mesh', mesh)
111
- except:
112
- pass # Factory will use default or no mesh
113
- except:
114
- res['note'] += '; factory_creation_failed'
115
- factory = None
116
-
117
- # Create the asset with or without factory
118
- if factory:
119
- asset = asset_tools.create_asset(
120
- asset_name=name,
121
- package_path=package_path,
122
- asset_class=unreal.FoliageType_InstancedStaticMesh,
123
- factory=factory
124
- )
125
- else:
126
- # Try without factory
127
- asset = asset_tools.create_asset(
128
- asset_name=name,
129
- package_path=package_path,
130
- asset_class=unreal.FoliageType_InstancedStaticMesh,
131
- factory=None
132
- )
133
-
134
- if asset:
135
- # Configure foliage properties
136
- asset.set_editor_property('mesh', mesh)
137
- if ${params.density !== undefined ? params.density : 1.0} >= 0:
138
- asset.set_editor_property('density', ${params.density !== undefined ? params.density : 1.0})
139
- if ${params.randomYaw === false ? 'False' : 'True'}:
140
- asset.set_editor_property('random_yaw', True)
141
- if ${params.alignToNormal === false ? 'False' : 'True'}:
142
- asset.set_editor_property('align_to_normal', True)
143
-
144
- # Set scale range
145
- min_scale = ${params.minScale || 0.8}
146
- max_scale = ${params.maxScale || 1.2}
147
- asset.set_editor_property('scale_x', (min_scale, max_scale))
148
- asset.set_editor_property('scale_y', (min_scale, max_scale))
149
- asset.set_editor_property('scale_z', (min_scale, max_scale))
150
-
151
- res['note'] += '; created_with_factory'
152
- else:
153
- res['note'] += '; factory_creation_failed'
154
- except AttributeError:
155
- # Fallback if factory doesn't exist - use base FoliageType
156
- try:
157
- asset = asset_tools.create_asset(
158
- asset_name=name,
159
- package_path=package_path,
160
- asset_class=unreal.FoliageType,
161
- factory=None
162
- )
163
- if asset:
164
- res['note'] += '; created_base_foliage_type'
165
- except Exception as e2:
166
- res['note'] += f"; base_creation_failed: {e2}"
167
- except Exception as e:
168
- res['note'] += f"; factory_creation_failed: {e}"
169
- asset = None
170
- except Exception as e:
171
- res['note'] += f"; create_asset failed: {e}"
172
- asset = None
173
-
174
- if asset and mesh:
175
- try:
176
- # Set the mesh property (different property names in different UE versions)
177
- try:
178
- asset.set_editor_property('mesh', mesh)
179
- except:
180
- try:
181
- asset.set_editor_property('static_mesh', mesh)
182
- except:
183
- pass
184
-
185
- # Save the asset
186
- unreal.EditorAssetLibrary.save_asset(asset.get_path_name())
187
- res['asset_path'] = str(asset.get_path_name())
188
- res['created'] = True
189
- res['method'] = 'FoliageType_InstancedStaticMesh'
190
- except Exception as e:
191
- res['note'] += f"; set/save asset failed: {e}"
192
- elif not asset:
193
- res['note'] += "; asset creation returned None"
194
- elif not mesh:
195
- res['note'] += "; mesh object is None, cannot assign to foliage type"
196
-
197
- # Verify existence
198
- res['exists_after'] = unreal.EditorAssetLibrary.does_asset_exist(res['asset_path']) if res['asset_path'] else False
199
- res['success'] = res['exists_after'] or res['created']
200
-
201
- except Exception as e:
202
- res['success'] = False
203
- res['note'] += f"; fatal: {e}"
204
-
205
- print('RESULT:' + json.dumps(res))
206
- `.trim();
207
-
208
- const pyResp = await this.bridge.executePython(py);
209
- const interpreted = interpretStandardResult(pyResp, {
210
- successMessage: `Foliage type '${name}' processed`,
211
- failureMessage: 'Add foliage type failed'
212
- });
213
-
214
- if (!interpreted.success) {
55
+ if (!this.automationBridge) {
56
+ throw new Error('Automation Bridge not available. Foliage operations require plugin support.');
57
+ }
58
+
59
+ try {
60
+ const base = meshPath.includes('.') ? meshPath : `${meshPath}.${meshPath.split('/').filter(Boolean).pop()}`;
61
+ const response = await this.automationBridge.sendAutomationRequest('add_foliage_type', {
62
+ name,
63
+ meshPath: base,
64
+ density: params.density ?? 100,
65
+ radius: params.radius ?? 0,
66
+ minScale: params.minScale ?? 1.0,
67
+ maxScale: params.maxScale ?? 1.0,
68
+ alignToNormal: params.alignToNormal ?? true,
69
+ randomYaw: params.randomYaw ?? true,
70
+ groundSlope: params.groundSlope ?? 45
71
+ }, {
72
+ timeoutMs: 60000
73
+ });
74
+
75
+ if (response.success === false) {
76
+ return {
77
+ success: false,
78
+ error: response.error || response.message || 'Add foliage type failed',
79
+ note: coerceString((response.result as any)?.note)
80
+ };
81
+ }
82
+
83
+ const payload = response.result as Record<string, unknown>;
84
+ const created = coerceBoolean(payload.created, false) ?? false;
85
+ const exists = coerceBoolean(payload.exists_after, false) ?? created;
86
+ const method = coerceString(payload.method) ?? 'Unknown';
87
+ const assetPath = coerceString(payload.asset_path);
88
+ const usedMesh = coerceString(payload.used_mesh);
89
+ const note = coerceString(payload.note);
90
+
91
+ return {
92
+ success: true,
93
+ created,
94
+ exists,
95
+ method,
96
+ assetPath,
97
+ usedMesh,
98
+ note,
99
+ message: exists
100
+ ? `Foliage type '${name}' ready (${method})`
101
+ : `Created foliage '${name}' but verification did not find it yet`
102
+ };
103
+ } catch (error) {
215
104
  return {
216
105
  success: false,
217
- error: coerceString(interpreted.payload.note) ?? interpreted.error ?? 'Add foliage type failed',
218
- note: coerceString(interpreted.payload.note) ?? bestEffortInterpretedText(interpreted)
106
+ error: `Failed to add foliage type: ${error instanceof Error ? error.message : String(error)}`
219
107
  };
220
108
  }
221
-
222
- const payload = interpreted.payload as Record<string, unknown>;
223
- const created = coerceBoolean(payload.created, false) ?? false;
224
- const exists = coerceBoolean(payload.exists_after, false) ?? created;
225
- const method = coerceString(payload.method) ?? 'Unknown';
226
- const assetPath = coerceString(payload.asset_path);
227
- const usedMesh = coerceString(payload.used_mesh);
228
- const note = coerceString(payload.note);
229
-
230
- return {
231
- success: true,
232
- created,
233
- exists,
234
- method,
235
- assetPath,
236
- usedMesh,
237
- note,
238
- message: exists
239
- ? `Foliage type '${name}' ready (${method})`
240
- : `Created foliage '${name}' but verification did not find it yet`
241
- };
242
109
  }
243
110
 
244
111
  // Paint foliage by placing HISM instances (editor-only)
@@ -251,7 +118,7 @@ print('RESULT:' + json.dumps(res))
251
118
  }) {
252
119
  const errors: string[] = [];
253
120
  const foliageType = String(params?.foliageType ?? '').trim();
254
- const pos = Array.isArray(params?.position) ? params.position : [0,0,0];
121
+ const pos = Array.isArray(params?.position) ? params.position : [0, 0, 0];
255
122
 
256
123
  if (!foliageType || foliageType.toLowerCase() === 'undefined' || foliageType.toLowerCase() === 'any') {
257
124
  errors.push(`Invalid foliageType: '${params?.foliageType}'`);
@@ -274,133 +141,94 @@ print('RESULT:' + json.dumps(res))
274
141
  return { success: false, error: errors.join('; ') };
275
142
  }
276
143
 
277
- const brush = Number.isFinite(params.brushSize as number) ? (params.brushSize as number) : 300;
278
- const py = `
279
- import unreal, json, random, math
280
-
281
- res = {'success': False, 'added': 0, 'actor': '', 'component': '', 'used_mesh': '', 'note': ''}
282
- foliage_type_name = ${JSON.stringify(foliageType)}
283
- px, py, pz = ${pos[0]}, ${pos[1]}, ${pos[2]}
284
- radius = float(${brush}) / 2.0
285
-
286
- try:
287
- actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
288
- if not actor_subsystem:
289
- raise RuntimeError('EditorActorSubsystem unavailable. Enable Editor Scripting Utilities plugin.')
290
-
291
- all_actors = actor_subsystem.get_all_level_actors()
292
-
293
- # Find or create a container actor using modern EditorActorSubsystem
294
- label = f"FoliageContainer_{foliage_type_name}"
295
- container = None
296
- for a in all_actors:
297
- try:
298
- if a and a.get_actor_label() == label:
299
- container = a
300
- break
301
- except Exception:
302
- pass
303
-
304
- if not container:
305
- container = actor_subsystem.spawn_actor_from_class(
306
- unreal.StaticMeshActor,
307
- unreal.Vector(px, py, pz)
308
- )
309
- if not container:
310
- raise RuntimeError('Failed to spawn foliage container actor via EditorActorSubsystem')
311
- try:
312
- container.set_actor_label(label)
313
- except Exception:
314
- pass
315
-
316
- # Resolve mesh from FoliageType asset
317
- mesh = None
318
- fol_asset_path = f"/Game/Foliage/Types/{foliage_type_name}.{foliage_type_name}"
319
- if unreal.EditorAssetLibrary.does_asset_exist(fol_asset_path):
320
- try:
321
- ft_asset = unreal.EditorAssetLibrary.load_asset(fol_asset_path)
322
- mesh = ft_asset.get_editor_property('mesh')
323
- except Exception:
324
- mesh = None
325
-
326
- if not mesh:
327
- mesh = unreal.EditorAssetLibrary.load_asset('/Engine/EngineMeshes/Sphere')
328
- res['note'] += '; used_fallback_mesh'
329
-
330
- if mesh:
331
- res['used_mesh'] = str(mesh.get_path_name())
332
-
333
- # Since HISM components and add_component don't work in this version,
334
- # spawn individual StaticMeshActors for each instance
335
- target_count = max(5, int(radius / 20.0))
336
- added = 0
337
- for i in range(target_count):
338
- ang = random.random() * math.tau
339
- r = random.random() * radius
340
- x, y, z = px + math.cos(ang) * r, py + math.sin(ang) * r, pz
341
- try:
342
- # Spawn static mesh actor at position using modern subsystem
343
- inst_actor = actor_subsystem.spawn_actor_from_class(
344
- unreal.StaticMeshActor,
345
- unreal.Vector(x, y, z),
346
- unreal.Rotator(0, random.random()*360.0, 0)
347
- )
348
- if inst_actor and mesh:
349
- # Set mesh on the actor's component
350
- try:
351
- mesh_comp = inst_actor.static_mesh_component
352
- if mesh_comp:
353
- mesh_comp.set_static_mesh(mesh)
354
- inst_actor.set_actor_label(f"{foliage_type_name}_instance_{i}")
355
- # Group under the container for organization
356
- inst_actor.attach_to_actor(container, "", unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False)
357
- added += 1
358
- except Exception as e:
359
- res['note'] += f"; instance_{i} setup failed: {e}"
360
- except Exception as e:
361
- res['note'] += f"; spawn instance_{i} failed: {e}"
362
-
363
- res['added'] = added
364
- res['actor'] = container.get_actor_label()
365
- res['component'] = 'StaticMeshActors' # Using actors instead of components
366
- res['success'] = True
367
- except Exception as e:
368
- res['success'] = False
369
- res['note'] += f"; fatal: {e}"
370
-
371
- print('RESULT:' + json.dumps(res))
372
- `.trim();
373
-
374
- const pyResp = await this.bridge.executePython(py);
375
- const interpreted = interpretStandardResult(pyResp, {
376
- successMessage: `Painted foliage for '${foliageType}'`,
377
- failureMessage: 'Paint foliage failed'
378
- });
379
-
380
- if (!interpreted.success) {
144
+ if (!this.automationBridge) {
145
+ throw new Error('Automation Bridge not available. Foliage operations require plugin support.');
146
+ }
147
+
148
+ try {
149
+ const typePath = foliageType.includes('/') ? foliageType : `/Game/Foliage/${foliageType}.${foliageType}`;
150
+ const response = await this.automationBridge.sendAutomationRequest('paint_foliage', {
151
+ foliageTypePath: typePath,
152
+ locations: [{ x: pos[0], y: pos[1], z: pos[2] }],
153
+ brushSize: Number.isFinite(params.brushSize as number) ? (params.brushSize as number) : 300,
154
+ paintDensity: params.paintDensity,
155
+ eraseMode: params.eraseMode
156
+ }, {
157
+ timeoutMs: 60000
158
+ });
159
+
160
+ if (response.success === false) {
161
+ return {
162
+ success: false,
163
+ error: response.error || response.message || 'Paint foliage failed',
164
+ note: coerceString((response.result as any)?.note)
165
+ };
166
+ }
167
+
168
+ const payload = response.result as Record<string, unknown>;
169
+ const added = coerceNumber(payload.instancesPlaced) ?? coerceNumber((payload as any)?.count) ?? 0;
170
+ const note = coerceString(payload.note);
171
+
172
+ return {
173
+ success: true,
174
+ added,
175
+ note,
176
+ message: `Painted ${added} instances for '${foliageType}' around (${pos[0]}, ${pos[1]}, ${pos[2]})`
177
+ };
178
+ } catch (error) {
381
179
  return {
382
180
  success: false,
383
- error: coerceString(interpreted.payload.note) ?? interpreted.error ?? 'Paint foliage failed',
384
- note: coerceString(interpreted.payload.note) ?? bestEffortInterpretedText(interpreted)
181
+ error: `Failed to paint foliage: ${error instanceof Error ? error.message : String(error)}`
385
182
  };
386
183
  }
184
+ }
387
185
 
388
- const payload = interpreted.payload as Record<string, unknown>;
389
- const added = coerceNumber(payload.added) ?? 0;
390
- const actor = coerceString(payload.actor);
391
- const component = coerceString(payload.component);
392
- const usedMesh = coerceString(payload.used_mesh);
393
- const note = coerceString(payload.note);
186
+ // Query foliage instances (plugin-native)
187
+ async getFoliageInstances(params: { foliageType?: string }) {
188
+ if (!this.automationBridge) {
189
+ throw new Error('Automation Bridge not available. Foliage operations require plugin support.');
190
+ }
191
+ try {
192
+ const typePath = params.foliageType ? (params.foliageType.includes('/') ? params.foliageType : `/Game/Foliage/${params.foliageType}.${params.foliageType}`) : undefined;
193
+ const response = await this.automationBridge.sendAutomationRequest('get_foliage_instances', {
194
+ foliageTypePath: typePath
195
+ }, { timeoutMs: 60000 });
196
+ if (response.success === false) {
197
+ return { success: false, error: response.error || response.message || 'Get foliage instances failed' };
198
+ }
199
+ const payload = response.result as Record<string, unknown>;
200
+ return {
201
+ success: true,
202
+ count: coerceNumber(payload.count) ?? 0,
203
+ instances: (payload.instances as any[]) ?? []
204
+ };
205
+ } catch (error) {
206
+ return { success: false, error: `Failed to get foliage instances: ${error instanceof Error ? error.message : String(error)}` };
207
+ }
208
+ }
394
209
 
395
- return {
396
- success: true,
397
- added,
398
- actor,
399
- component,
400
- usedMesh,
401
- note,
402
- message: `Painted ${added} instances for '${foliageType}' around (${pos[0]}, ${pos[1]}, ${pos[2]})`
403
- };
210
+ // Remove foliage (plugin-native)
211
+ async removeFoliage(params: { foliageType?: string; removeAll?: boolean }) {
212
+ if (!this.automationBridge) {
213
+ throw new Error('Automation Bridge not available. Foliage operations require plugin support.');
214
+ }
215
+ try {
216
+ const typePath = params.foliageType ? (params.foliageType.includes('/') ? params.foliageType : `/Game/Foliage/${params.foliageType}.${params.foliageType}`) : undefined;
217
+ const response = await this.automationBridge.sendAutomationRequest('remove_foliage', {
218
+ foliageTypePath: typePath,
219
+ removeAll: !!params.removeAll
220
+ }, { timeoutMs: 60000 });
221
+ if (response.success === false) {
222
+ return { success: false, error: response.error || response.message || 'Remove foliage failed' };
223
+ }
224
+ const payload = response.result as Record<string, unknown>;
225
+ return {
226
+ success: true,
227
+ instancesRemoved: coerceNumber(payload.instancesRemoved) ?? 0
228
+ };
229
+ } catch (error) {
230
+ return { success: false, error: `Failed to remove foliage: ${error instanceof Error ? error.message : String(error)}` };
231
+ }
404
232
  }
405
233
 
406
234
  // Create instanced mesh
@@ -415,26 +243,26 @@ print('RESULT:' + json.dumps(res))
415
243
  enableCulling?: boolean;
416
244
  cullDistance?: number;
417
245
  }) {
418
- const commands: string[] = [];
419
-
246
+ const commands: string[] = [];
247
+
420
248
  commands.push(`CreateInstancedStaticMesh ${params.name} ${params.meshPath}`);
421
-
249
+
422
250
  for (const instance of params.instances) {
423
251
  const rot = instance.rotation || [0, 0, 0];
424
252
  const scale = instance.scale || [1, 1, 1];
425
253
  commands.push(`AddInstance ${params.name} ${instance.position.join(' ')} ${rot.join(' ')} ${scale.join(' ')}`);
426
254
  }
427
-
255
+
428
256
  if (params.enableCulling !== undefined) {
429
257
  commands.push(`SetInstanceCulling ${params.name} ${params.enableCulling}`);
430
258
  }
431
-
259
+
432
260
  if (params.cullDistance !== undefined) {
433
261
  commands.push(`SetInstanceCullDistance ${params.name} ${params.cullDistance}`);
434
262
  }
435
-
263
+
436
264
  await this.bridge.executeConsoleCommands(commands);
437
-
265
+
438
266
  return { success: true, message: `Instanced mesh ${params.name} created with ${params.instances.length} instances` };
439
267
  }
440
268
 
@@ -444,51 +272,158 @@ print('RESULT:' + json.dumps(res))
444
272
  lodDistances?: number[];
445
273
  screenSize?: number[];
446
274
  }) {
447
- const commands: string[] = [];
448
-
275
+ const commands: string[] = [];
276
+
449
277
  if (params.lodDistances) {
450
278
  commands.push(`SetFoliageLODDistances ${params.foliageType} ${params.lodDistances.join(' ')}`);
451
279
  }
452
-
280
+
453
281
  if (params.screenSize) {
454
282
  commands.push(`SetFoliageLODScreenSize ${params.foliageType} ${params.screenSize.join(' ')}`);
455
283
  }
456
-
284
+
457
285
  await this.bridge.executeConsoleCommands(commands);
458
-
286
+
459
287
  return { success: true, message: 'Foliage LOD settings updated' };
460
288
  }
461
289
 
290
+ // Alias for addFoliageType to match interface/handler usage
291
+ async addFoliage(params: { foliageType: string; locations: Array<{ x: number; y: number; z: number }> }) {
292
+ // Delegate to paintFoliage which handles placing instances at locations
293
+ if (params.locations && params.locations.length > 0) {
294
+ if (!this.automationBridge) {
295
+ throw new Error('Automation Bridge not available.');
296
+ }
297
+
298
+ const response = await this.automationBridge.sendAutomationRequest('paint_foliage', {
299
+ foliageTypePath: params.foliageType.includes('/') ? params.foliageType : `/Game/Foliage/${params.foliageType}.${params.foliageType}`,
300
+ locations: params.locations,
301
+ brushSize: 0, // Exact placement
302
+ paintDensity: 1,
303
+ eraseMode: false
304
+ });
305
+
306
+ if (!response.success) {
307
+ return { success: false, error: response.error || 'Failed to add foliage instances' };
308
+ }
309
+
310
+ return { success: true, message: `Added ${params.locations.length} foliage instances` };
311
+ }
312
+
313
+ return { success: true, message: 'No locations provided for addFoliage' };
314
+ }
315
+
462
316
  // Create procedural foliage
463
317
  async createProceduralFoliage(params: {
464
- volumeName: string;
465
- position: [number, number, number];
466
- size: [number, number, number];
467
- foliageTypes: string[];
318
+ name: string;
319
+ bounds?: { location: { x: number; y: number; z: number }; size: { x: number; y: number; z: number } };
320
+ foliageTypes?: Array<{
321
+ meshPath: string;
322
+ density: number;
323
+ minScale?: number;
324
+ maxScale?: number;
325
+ alignToNormal?: boolean;
326
+ randomYaw?: boolean;
327
+ }>;
328
+ // Legacy params compatibility
329
+ volumeName?: string;
330
+ position?: [number, number, number];
331
+ size?: [number, number, number];
468
332
  seed?: number;
469
333
  tileSize?: number;
470
334
  }) {
471
- const commands: string[] = [];
472
-
473
- commands.push(`CreateProceduralFoliageVolume ${params.volumeName} ${params.position.join(' ')} ${params.size.join(' ')}`);
474
-
475
- for (const type of params.foliageTypes) {
476
- commands.push(`AddProceduralFoliageType ${params.volumeName} ${type}`);
477
- }
478
-
479
- if (params.seed !== undefined) {
480
- commands.push(`SetProceduralSeed ${params.volumeName} ${params.seed}`);
481
- }
482
-
483
- if (params.tileSize !== undefined) {
484
- commands.push(`SetProceduralTileSize ${params.volumeName} ${params.tileSize}`);
485
- }
486
-
487
- commands.push(`GenerateProceduralFoliage ${params.volumeName}`);
488
-
489
- await this.bridge.executeConsoleCommands(commands);
490
-
491
- return { success: true, message: `Procedural foliage volume ${params.volumeName} created` };
335
+ if (!this.automationBridge) {
336
+ throw new Error('Automation Bridge not available.');
337
+ }
338
+
339
+ const volName = params.volumeName || params.name || 'ProceduralFoliageVolume';
340
+ const loc = params.bounds?.location ? [params.bounds.location.x, params.bounds.location.y, params.bounds.location.z] : (params.position || [0, 0, 0]);
341
+ const size = params.bounds?.size ? [params.bounds.size.x, params.bounds.size.y, params.bounds.size.z] : (params.size || [1000, 1000, 100]);
342
+
343
+ // Normalize foliage types from both formats
344
+ const foliageTypes = Array.isArray(params.foliageTypes)
345
+ ? params.foliageTypes.map(t => {
346
+ if (typeof t === 'string') return { meshPath: t, density: 0.5 };
347
+ return t;
348
+ })
349
+ : [];
350
+
351
+ const payload = {
352
+ name: volName,
353
+ bounds: {
354
+ location: { x: loc[0], y: loc[1], z: loc[2] },
355
+ size: { x: size[0], y: size[1], z: size[2] }
356
+ },
357
+ foliageTypes,
358
+ seed: params.seed ?? 42,
359
+ tileSize: params.tileSize ?? 1000
360
+ };
361
+
362
+ const response = await this.automationBridge.sendAutomationRequest('create_procedural_foliage', payload);
363
+
364
+ if (!response.success) {
365
+ return {
366
+ success: false,
367
+ error: response.error || 'Failed to create procedural foliage'
368
+ };
369
+ }
370
+
371
+ const result = response.result as any;
372
+ return {
373
+ success: true,
374
+ message: `Procedural foliage volume ${volName} created`,
375
+ details: response,
376
+ volumeActor: result?.volume_actor,
377
+ spawnerPath: result?.spawner_path,
378
+ foliageTypesCount: result?.foliage_types_count
379
+ };
380
+ }
381
+
382
+ /**
383
+ * Add foliage instances using InstancedFoliageActor
384
+ * Direct instance placement approach
385
+ */
386
+ async addFoliageInstances(params: {
387
+ foliageType: string; // Path to FoliageType or mesh
388
+ transforms: Array<{
389
+ location: [number, number, number];
390
+ rotation?: [number, number, number];
391
+ scale?: [number, number, number];
392
+ }>;
393
+ }) {
394
+ if (!this.automationBridge) {
395
+ throw new Error('Automation Bridge not available. Foliage instance placement requires plugin support.');
396
+ }
397
+
398
+ try {
399
+ const typePath = params.foliageType.includes('/') ? params.foliageType : `/Game/Foliage/${params.foliageType}.${params.foliageType}`;
400
+ const response = await this.automationBridge.sendAutomationRequest('add_foliage_instances', {
401
+ foliageType: typePath,
402
+ transforms: params.transforms
403
+ }, {
404
+ timeoutMs: 120000 // 2 minutes for instance placement
405
+ });
406
+
407
+ if (response.success === false) {
408
+ return {
409
+ success: false,
410
+ error: response.error || response.message || 'Failed to add foliage instances',
411
+ message: response.message || 'Failed to add foliage instances'
412
+ };
413
+ }
414
+
415
+ const result = response.result as any;
416
+ return {
417
+ success: true,
418
+ message: response.message || `Added ${result?.instances_count || params.transforms.length} foliage instances`,
419
+ instancesCount: result?.instances_count
420
+ };
421
+ } catch (error) {
422
+ return {
423
+ success: false,
424
+ error: `Failed to add foliage instances: ${error instanceof Error ? error.message : String(error)}`
425
+ };
426
+ }
492
427
  }
493
428
 
494
429
  // Set foliage collision
@@ -498,22 +433,22 @@ print('RESULT:' + json.dumps(res))
498
433
  collisionProfile?: string;
499
434
  generateOverlapEvents?: boolean;
500
435
  }) {
501
- const commands: string[] = [];
502
-
436
+ const commands: string[] = [];
437
+
503
438
  if (params.collisionEnabled !== undefined) {
504
439
  commands.push(`SetFoliageCollision ${params.foliageType} ${params.collisionEnabled}`);
505
440
  }
506
-
441
+
507
442
  if (params.collisionProfile) {
508
443
  commands.push(`SetFoliageCollisionProfile ${params.foliageType} ${params.collisionProfile}`);
509
444
  }
510
-
445
+
511
446
  if (params.generateOverlapEvents !== undefined) {
512
447
  commands.push(`SetFoliageOverlapEvents ${params.foliageType} ${params.generateOverlapEvents}`);
513
448
  }
514
-
449
+
515
450
  await this.bridge.executeConsoleCommands(commands);
516
-
451
+
517
452
  return { success: true, message: 'Foliage collision settings updated' };
518
453
  }
519
454
 
@@ -529,26 +464,26 @@ print('RESULT:' + json.dumps(res))
529
464
  windStrength?: number;
530
465
  windSpeed?: number;
531
466
  }) {
532
- const commands: string[] = [];
533
-
467
+ const commands: string[] = [];
468
+
534
469
  commands.push(`CreateGrassSystem ${params.name}`);
535
-
470
+
536
471
  for (const grassType of params.grassTypes) {
537
472
  const minScale = grassType.minScale || 0.8;
538
473
  const maxScale = grassType.maxScale || 1.2;
539
474
  commands.push(`AddGrassType ${params.name} ${grassType.meshPath} ${grassType.density} ${minScale} ${maxScale}`);
540
475
  }
541
-
476
+
542
477
  if (params.windStrength !== undefined) {
543
478
  commands.push(`SetGrassWindStrength ${params.name} ${params.windStrength}`);
544
479
  }
545
-
480
+
546
481
  if (params.windSpeed !== undefined) {
547
482
  commands.push(`SetGrassWindSpeed ${params.name} ${params.windSpeed}`);
548
483
  }
549
-
484
+
550
485
  await this.bridge.executeConsoleCommands(commands);
551
-
486
+
552
487
  return { success: true, message: `Grass system ${params.name} created` };
553
488
  }
554
489
 
@@ -570,7 +505,7 @@ print('RESULT:' + json.dumps(res))
570
505
  selectAll?: boolean;
571
506
  }) {
572
507
  let command: string;
573
-
508
+
574
509
  if (params.selectAll) {
575
510
  command = `SelectAllFoliage ${params.foliageType}`;
576
511
  } else if (params.position && params.radius) {
@@ -578,7 +513,7 @@ print('RESULT:' + json.dumps(res))
578
513
  } else {
579
514
  command = `SelectFoliageType ${params.foliageType}`;
580
515
  }
581
-
516
+
582
517
  return this.bridge.executeConsoleCommand(command);
583
518
  }
584
519
 
@@ -589,20 +524,20 @@ print('RESULT:' + json.dumps(res))
589
524
  updateMesh?: boolean;
590
525
  newMeshPath?: string;
591
526
  }) {
592
- const commands: string[] = [];
593
-
527
+ const commands: string[] = [];
528
+
594
529
  if (params.updateTransforms) {
595
530
  commands.push(`UpdateFoliageTransforms ${params.foliageType}`);
596
531
  }
597
-
532
+
598
533
  if (params.updateMesh && params.newMeshPath) {
599
534
  commands.push(`UpdateFoliageMesh ${params.foliageType} ${params.newMeshPath}`);
600
535
  }
601
-
536
+
602
537
  commands.push(`RefreshFoliage ${params.foliageType}`);
603
-
538
+
604
539
  await this.bridge.executeConsoleCommands(commands);
605
-
540
+
606
541
  return { success: true, message: 'Foliage instances updated' };
607
542
  }
608
543
 
@@ -612,18 +547,18 @@ print('RESULT:' + json.dumps(res))
612
547
  spawnArea: 'Landscape' | 'StaticMesh' | 'BSP' | 'Foliage' | 'All';
613
548
  excludeAreas?: Array<[number, number, number, number]>; // [x, y, z, radius]
614
549
  }) {
615
- const commands: string[] = [];
616
-
550
+ const commands: string[] = [];
551
+
617
552
  commands.push(`CreateFoliageSpawner ${params.name} ${params.spawnArea}`);
618
-
553
+
619
554
  if (params.excludeAreas) {
620
555
  for (const area of params.excludeAreas) {
621
556
  commands.push(`AddFoliageExclusionArea ${params.name} ${area.join(' ')}`);
622
557
  }
623
558
  }
624
-
559
+
625
560
  await this.bridge.executeConsoleCommands(commands);
626
-
561
+
627
562
  return { success: true, message: `Foliage spawner ${params.name} created` };
628
563
  }
629
564
 
@@ -635,24 +570,24 @@ print('RESULT:' + json.dumps(res))
635
570
  reduceDrawCalls?: boolean;
636
571
  }) {
637
572
  const commands = [];
638
-
573
+
639
574
  if (params.mergeInstances) {
640
575
  commands.push('MergeFoliageInstances');
641
576
  }
642
-
577
+
643
578
  if (params.generateClusters) {
644
579
  const size = params.clusterSize || 100;
645
580
  commands.push(`GenerateFoliageClusters ${size}`);
646
581
  }
647
-
582
+
648
583
  if (params.reduceDrawCalls) {
649
584
  commands.push('OptimizeFoliageDrawCalls');
650
585
  }
651
-
586
+
652
587
  commands.push('RebuildFoliageTree');
653
-
588
+
654
589
  await this.bridge.executeConsoleCommands(commands);
655
-
590
+
656
591
  return { success: true, message: 'Foliage optimized' };
657
592
  }
658
593
  }