ue-mcp 1.2.4 → 1.3.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 (243) hide show
  1. package/README.md +4 -3
  2. package/dist/action-class.js +56 -3
  3. package/dist/action-class.js.map +1 -1
  4. package/dist/bridge-timeouts.d.ts +64 -0
  5. package/dist/bridge-timeouts.js +118 -0
  6. package/dist/bridge-timeouts.js.map +1 -0
  7. package/dist/bridge.js +10 -2
  8. package/dist/bridge.js.map +1 -1
  9. package/dist/editor-control.d.ts +72 -5
  10. package/dist/editor-control.js +164 -140
  11. package/dist/editor-control.js.map +1 -1
  12. package/dist/editor-target.d.ts +55 -1
  13. package/dist/editor-target.js +107 -10
  14. package/dist/editor-target.js.map +1 -1
  15. package/dist/engine-observer.d.ts +59 -5
  16. package/dist/engine-observer.js +249 -29
  17. package/dist/engine-observer.js.map +1 -1
  18. package/dist/engine-root.d.ts +91 -0
  19. package/dist/engine-root.js +448 -0
  20. package/dist/engine-root.js.map +1 -0
  21. package/dist/epic-enrich.js +16 -1
  22. package/dist/epic-enrich.js.map +1 -1
  23. package/dist/feedback-cli.js +4 -0
  24. package/dist/feedback-cli.js.map +1 -1
  25. package/dist/feedback-deferred.d.ts +9 -0
  26. package/dist/feedback-deferred.js +2 -0
  27. package/dist/feedback-deferred.js.map +1 -1
  28. package/dist/feedback-fallback.d.ts +99 -0
  29. package/dist/feedback-fallback.js +173 -0
  30. package/dist/feedback-fallback.js.map +1 -0
  31. package/dist/flow/write-methods.js +20 -0
  32. package/dist/flow/write-methods.js.map +1 -1
  33. package/dist/lean-context.js +8 -3
  34. package/dist/lean-context.js.map +1 -1
  35. package/dist/locking.js +11 -2
  36. package/dist/locking.js.map +1 -1
  37. package/dist/project.d.ts +11 -0
  38. package/dist/project.js +23 -2
  39. package/dist/project.js.map +1 -1
  40. package/dist/python-gate.d.ts +95 -0
  41. package/dist/python-gate.js +175 -0
  42. package/dist/python-gate.js.map +1 -0
  43. package/dist/tool-counts.json +16 -16
  44. package/dist/tools/animation.js +288 -24
  45. package/dist/tools/animation.js.map +1 -1
  46. package/dist/tools/asset.js +79 -14
  47. package/dist/tools/asset.js.map +1 -1
  48. package/dist/tools/audio.js +59 -5
  49. package/dist/tools/audio.js.map +1 -1
  50. package/dist/tools/blueprint.js +29 -12
  51. package/dist/tools/blueprint.js.map +1 -1
  52. package/dist/tools/chooser.js +4 -1
  53. package/dist/tools/chooser.js.map +1 -1
  54. package/dist/tools/editor.js +81 -43
  55. package/dist/tools/editor.js.map +1 -1
  56. package/dist/tools/feedback.js +214 -48
  57. package/dist/tools/feedback.js.map +1 -1
  58. package/dist/tools/foliage.js +27 -1
  59. package/dist/tools/foliage.js.map +1 -1
  60. package/dist/tools/gameplay.js +38 -11
  61. package/dist/tools/gameplay.js.map +1 -1
  62. package/dist/tools/gas.js +17 -5
  63. package/dist/tools/gas.js.map +1 -1
  64. package/dist/tools/landscape.js +22 -7
  65. package/dist/tools/landscape.js.map +1 -1
  66. package/dist/tools/level.js +322 -51
  67. package/dist/tools/level.js.map +1 -1
  68. package/dist/tools/material.js +10 -4
  69. package/dist/tools/material.js.map +1 -1
  70. package/dist/tools/niagara.js +3 -2
  71. package/dist/tools/niagara.js.map +1 -1
  72. package/dist/tools/pcg.js +6 -5
  73. package/dist/tools/pcg.js.map +1 -1
  74. package/dist/tools/project.js +47 -20
  75. package/dist/tools/project.js.map +1 -1
  76. package/dist/types.d.ts +21 -0
  77. package/dist/types.js +46 -3
  78. package/dist/types.js.map +1 -1
  79. package/dist/ue-mcp.default.yml +493 -125
  80. package/package.json +2 -2
  81. package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
  82. package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
  83. package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
  84. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
  85. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
  86. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
  87. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
  141. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
  142. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
  143. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
  144. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
  145. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
  146. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
  147. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
  148. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
  149. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
  150. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
  151. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
  152. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
  153. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
  154. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
  155. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
  156. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
  157. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
  158. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
  159. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
  160. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
  161. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
  162. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
  163. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
  164. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
  165. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
  166. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
  167. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
  168. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
  169. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
  170. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
  171. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
  172. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
  173. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
  174. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
  175. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
  176. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
  177. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
  178. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
  179. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
  180. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
  181. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
  182. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
  183. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
  184. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
  185. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
  186. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
  187. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
  188. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
  189. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
  190. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
  191. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
  192. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
  193. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
  194. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
  195. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
  196. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
  197. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
  198. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
  199. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
  200. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
  201. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
  202. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
  203. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
  204. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
  205. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
  206. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
  207. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
  208. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
  209. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
  210. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
  211. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
  212. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
  213. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
  214. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
  215. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
  216. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
  217. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
  218. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
  219. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
  220. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
  221. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
  222. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
  223. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
  224. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
  225. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
  226. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
  227. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
  228. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
  229. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
  230. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
  231. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
  232. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
  233. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
  234. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
  235. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
  236. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
  237. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  238. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
  239. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
  240. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
  241. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
  242. package/skills/ue-mcp-animation/SKILL.md +154 -0
  243. package/skills/ue-mcp-blueprint/SKILL.md +71 -0
@@ -26,6 +26,10 @@ void FFoliageHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
26
26
  Registry.RegisterHandler(TEXT("get_foliage_type_settings"), &GetFoliageSettings);
27
27
  Registry.RegisterHandler(TEXT("set_foliage_type_settings"), &SetFoliageTypeSettings);
28
28
  Registry.RegisterHandler(TEXT("create_foliage_type"), &CreateFoliageType);
29
+ // #988: predicate-driven batch. Scanning and saving hundreds of FoliageType
30
+ // assets outlives the default handler timeout.
31
+ Registry.RegisterHandlerWithTimeout(
32
+ TEXT("batch_set_foliage_settings_where"), &BatchSetFoliageSettingsWhere, 300.0f);
29
33
  }
30
34
 
31
35
  TSharedPtr<FJsonValue> FFoliageHandlers::ListFoliageTypes(const TSharedPtr<FJsonObject>& Params)
@@ -150,7 +154,12 @@ TSharedPtr<FJsonValue> FFoliageHandlers::SampleFoliage(const TSharedPtr<FJsonObj
150
154
  TSharedPtr<FJsonValue> FFoliageHandlers::GetFoliageSettings(const TSharedPtr<FJsonObject>& Params)
151
155
  {
152
156
  FString FoliageTypePath;
153
- if (auto Err = RequireString(Params, TEXT("foliageTypePath"), FoliageTypePath)) return Err;
157
+ if (!Params->TryGetStringField(TEXT("foliageTypePath"), FoliageTypePath) || FoliageTypePath.IsEmpty())
158
+ {
159
+ // #988: a caller who reaches this error usually wants the whole set,
160
+ // not one asset, so the error names the action that answers that.
161
+ return MCPError(TEXT("Missing required parameter 'foliageTypePath'. To read or filter many types at once use foliage(list_types) or asset(bulk_read_properties); to WRITE settings to every type matching a predicate use foliage(batch_set_settings_where)."));
162
+ }
154
163
 
155
164
  UFoliageType* FoliageType = LoadObject<UFoliageType>(nullptr, *FoliageTypePath);
156
165
  if (!FoliageType)
@@ -227,7 +236,7 @@ TSharedPtr<FJsonValue> FFoliageHandlers::SetFoliageTypeSettings(const TSharedPtr
227
236
  }
228
237
  if (FoliageTypePath.IsEmpty())
229
238
  {
230
- return MCPError(TEXT("Missing 'foliageTypePath' or 'foliageTypeName' parameter"));
239
+ return MCPError(TEXT("Missing 'foliageTypePath' or 'foliageTypeName' parameter. To write the same settings to every type matching a predicate on an existing value, use foliage(batch_set_settings_where) instead of calling this once per asset (#988)."));
231
240
  }
232
241
 
233
242
  const TSharedPtr<FJsonObject>* SettingsObj = nullptr;
@@ -15,4 +15,7 @@ private:
15
15
  static TSharedPtr<FJsonValue> GetFoliageSettings(const TSharedPtr<FJsonObject>& Params);
16
16
  static TSharedPtr<FJsonValue> SetFoliageTypeSettings(const TSharedPtr<FJsonObject>& Params);
17
17
  static TSharedPtr<FJsonValue> CreateFoliageType(const TSharedPtr<FJsonObject>& Params);
18
+ // #988: write settings to every FoliageType matching a predicate on an
19
+ // EXISTING property value. Lives in FoliageHandlers_Batch.cpp.
20
+ static TSharedPtr<FJsonValue> BatchSetFoliageSettingsWhere(const TSharedPtr<FJsonObject>& Params);
18
21
  };
@@ -0,0 +1,419 @@
1
+ // foliage(batch_set_settings_where) (#988).
2
+ //
3
+ // The reporter needed include_in_hlod=false on every FoliageType whose cull
4
+ // distance was set, which was 36 of 201. foliage(set_settings) is one asset per
5
+ // call, and there was no way to express the predicate at all, so the match had
6
+ // to be computed client-side from 201 separate reads and then written back with
7
+ // 36 more calls.
8
+ //
9
+ // The predicate runs here, over the CURRENT value of a property on each
10
+ // candidate, using the same operator vocabulary as level(query_components) and
11
+ // asset(bulk_read_properties). Translation-unit partition of FFoliageHandlers;
12
+ // registration stays in FoliageHandlers.cpp.
13
+
14
+ #include "FoliageHandlers.h"
15
+
16
+ #include "AssetRegistry/AssetRegistryModule.h"
17
+ #include "AssetRegistry/IAssetRegistry.h"
18
+ #include "Dom/JsonObject.h"
19
+ #include "Dom/JsonValue.h"
20
+ #include "Editor.h"
21
+ #include "Engine/World.h"
22
+ #include "EngineUtils.h"
23
+ #include "FoliageType.h"
24
+ #include "HandlerJsonProperty.h"
25
+ #include "HandlerQuery.h"
26
+ #include "HandlerRegistry.h"
27
+ #include "HandlerUtils.h"
28
+ #include "InstancedFoliageActor.h"
29
+ #include "UObject/Package.h"
30
+
31
+ namespace
32
+ {
33
+ constexpr int32 MCPFoliageBatchMaxTypes = 2000;
34
+ constexpr int32 MCPFoliageBatchMaxPredicates = 16;
35
+ constexpr int32 MCPFoliageBatchMaxSettings = 32;
36
+ constexpr int32 MCPFoliageBatchMaxResultRows = 500;
37
+
38
+ /** One candidate, with everything needed to report it whether or not it
39
+ * ends up matching. */
40
+ struct FMCPFoliageBatchCandidate
41
+ {
42
+ UFoliageType* Type = nullptr;
43
+ FString Path;
44
+ FString Name;
45
+ FString Source;
46
+ };
47
+
48
+ void MCPFoliageBatchAddCandidate(
49
+ TArray<FMCPFoliageBatchCandidate>& Candidates,
50
+ TSet<FString>& Seen,
51
+ UFoliageType* Type,
52
+ const TCHAR* Source)
53
+ {
54
+ if (!Type)
55
+ {
56
+ return;
57
+ }
58
+ const FString Path = Type->GetPathName();
59
+ if (Seen.Contains(Path))
60
+ {
61
+ return;
62
+ }
63
+ Seen.Add(Path);
64
+ FMCPFoliageBatchCandidate Candidate;
65
+ Candidate.Type = Type;
66
+ Candidate.Path = Path;
67
+ Candidate.Name = Type->GetName();
68
+ Candidate.Source = Source;
69
+ Candidates.Add(MoveTemp(Candidate));
70
+ }
71
+ }
72
+
73
+ TSharedPtr<FJsonValue> FFoliageHandlers::BatchSetFoliageSettingsWhere(const TSharedPtr<FJsonObject>& Params)
74
+ {
75
+ MCP_CHECK_GAME_THREAD();
76
+
77
+ // ── What to write ───────────────────────────────────────────────────────
78
+ const TSharedPtr<FJsonObject>* SettingsObject = nullptr;
79
+ if (!Params->TryGetObjectField(TEXT("settings"), SettingsObject) ||
80
+ !SettingsObject || !(*SettingsObject).IsValid() || (*SettingsObject)->Values.Num() == 0)
81
+ {
82
+ return MCPError(TEXT("Missing 'settings' (an object of propertyName -> value; dotted paths such as 'CullDistance.Max' are supported)"));
83
+ }
84
+ if ((*SettingsObject)->Values.Num() > MCPFoliageBatchMaxSettings)
85
+ {
86
+ return MCPError(FString::Printf(
87
+ TEXT("'settings' exceeds the maximum of %d entries"), MCPFoliageBatchMaxSettings));
88
+ }
89
+
90
+ // ── The predicate, which is the point of the action ─────────────────────
91
+ TArray<MCPQuery::FPredicate> Predicates;
92
+ {
93
+ const TArray<TSharedPtr<FJsonValue>>* WhereValues = nullptr;
94
+ Params->TryGetArrayField(TEXT("where"), WhereValues);
95
+ FString ParseError;
96
+ if (!MCPQuery::ParsePredicates(WhereValues, MCPFoliageBatchMaxPredicates, Predicates, ParseError))
97
+ {
98
+ return MCPError(ParseError);
99
+ }
100
+ }
101
+ if (Predicates.IsEmpty())
102
+ {
103
+ // Without a predicate this is "write these settings to every foliage
104
+ // type in the project", which is not what anybody means by a
105
+ // conditional batch and is not worth guessing at.
106
+ return MCPError(TEXT("Missing 'where' ([{field, op, value}] over the current property values, e.g. [{field:'props.CullDistance.Max', op:'gt', value:0}]). Use foliage(set_settings) for a single known asset."));
107
+ }
108
+ const FString WhereMode = OptionalString(Params, TEXT("whereMode"), TEXT("all")).ToLower();
109
+ if (WhereMode != TEXT("all") && WhereMode != TEXT("any"))
110
+ {
111
+ return MCPError(TEXT("'whereMode' must be either 'all' or 'any'"));
112
+ }
113
+
114
+ // Properties the predicate reads. Derived from the predicate fields so a
115
+ // caller does not have to list them twice, plus anything explicitly asked
116
+ // for so the report can show context it does not filter on.
117
+ TArray<FString> ReadPaths;
118
+ for (MCPQuery::FPredicate& Predicate : Predicates)
119
+ {
120
+ if (Predicate.Field.StartsWith(TEXT("props.")))
121
+ {
122
+ ReadPaths.AddUnique(Predicate.Field.RightChop(6));
123
+ continue;
124
+ }
125
+ // A bare field name is read as a property too, so
126
+ // {field:'CullDistance.Max'} and {field:'props.CullDistance.Max'} mean
127
+ // the same thing rather than one of them silently matching nothing.
128
+ ReadPaths.AddUnique(Predicate.Field);
129
+ Predicate.Field = FString::Printf(TEXT("props.%s"), *Predicate.Field);
130
+ }
131
+ {
132
+ const TArray<TSharedPtr<FJsonValue>>* ExtraValues = nullptr;
133
+ if (Params->TryGetArrayField(TEXT("propertyNames"), ExtraValues) && ExtraValues)
134
+ {
135
+ for (const FString& Path : JsonArrayToStringList(ExtraValues))
136
+ {
137
+ ReadPaths.AddUnique(Path);
138
+ }
139
+ }
140
+ }
141
+
142
+ const bool bDryRun = OptionalBool(Params, TEXT("dryRun"), true);
143
+ const bool bSave = OptionalBool(Params, TEXT("save"), true);
144
+ const int32 MaxTypes = FMath::Clamp(
145
+ OptionalInt(Params, TEXT("maxTypes"), MCPFoliageBatchMaxTypes), 1, MCPFoliageBatchMaxTypes);
146
+
147
+ // ── Candidates ──────────────────────────────────────────────────────────
148
+ TArray<FMCPFoliageBatchCandidate> Candidates;
149
+ TSet<FString> Seen;
150
+
151
+ TArray<FString> ExplicitPaths;
152
+ {
153
+ const TArray<TSharedPtr<FJsonValue>>* PathValues = nullptr;
154
+ if (Params->TryGetArrayField(TEXT("foliageTypePaths"), PathValues) && PathValues)
155
+ {
156
+ ExplicitPaths = JsonArrayToStringList(PathValues);
157
+ }
158
+ }
159
+ TArray<FString> UnloadablePaths;
160
+ for (const FString& Path : ExplicitPaths)
161
+ {
162
+ UFoliageType* Type = LoadObject<UFoliageType>(nullptr, *Path);
163
+ if (!Type)
164
+ {
165
+ UnloadablePaths.Add(Path);
166
+ continue;
167
+ }
168
+ MCPFoliageBatchAddCandidate(Candidates, Seen, Type, TEXT("explicit"));
169
+ }
170
+
171
+ const FString Directory = OptionalString(Params, TEXT("directory"));
172
+ const bool bFromLevel = OptionalBool(Params, TEXT("fromLevel"), false);
173
+ if (ExplicitPaths.IsEmpty() && Directory.IsEmpty() && !bFromLevel)
174
+ {
175
+ return MCPError(TEXT("Pass one of 'foliageTypePaths' (explicit), 'directory' (scan FoliageType assets) or fromLevel=true (types placed in the open level)"));
176
+ }
177
+
178
+ if (!Directory.IsEmpty())
179
+ {
180
+ FAssetRegistryModule& AssetRegistryModule =
181
+ FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
182
+ IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
183
+
184
+ FARFilter Filter;
185
+ Filter.PackagePaths.Add(FName(*Directory));
186
+ Filter.bRecursivePaths = OptionalBool(Params, TEXT("recursive"), true);
187
+ Filter.ClassPaths.Add(UFoliageType::StaticClass()->GetClassPathName());
188
+ Filter.bRecursiveClasses = true;
189
+
190
+ TArray<FAssetData> Found;
191
+ AssetRegistry.GetAssets(Filter, Found);
192
+ for (const FAssetData& Data : Found)
193
+ {
194
+ if (Candidates.Num() >= MaxTypes) break;
195
+ UFoliageType* Type = Cast<UFoliageType>(Data.GetAsset());
196
+ if (!Type)
197
+ {
198
+ UnloadablePaths.Add(Data.GetObjectPathString());
199
+ continue;
200
+ }
201
+ MCPFoliageBatchAddCandidate(Candidates, Seen, Type, TEXT("directory"));
202
+ }
203
+ }
204
+
205
+ if (bFromLevel)
206
+ {
207
+ if (UWorld* World = GetEditorWorld())
208
+ {
209
+ for (TActorIterator<AInstancedFoliageActor> It(World); It; ++It)
210
+ {
211
+ AInstancedFoliageActor* FoliageActor = *It;
212
+ if (!FoliageActor) continue;
213
+ for (const auto& Pair : FoliageActor->GetFoliageInfos())
214
+ {
215
+ if (Candidates.Num() >= MaxTypes) break;
216
+ MCPFoliageBatchAddCandidate(Candidates, Seen, Pair.Key, TEXT("level"));
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ Candidates.Sort([](const FMCPFoliageBatchCandidate& A, const FMCPFoliageBatchCandidate& B)
223
+ {
224
+ return A.Path < B.Path;
225
+ });
226
+
227
+ // ── Evaluate and write ──────────────────────────────────────────────────
228
+ TArray<TSharedPtr<FJsonValue>> Rows;
229
+ TArray<FString> MatchedPaths;
230
+ TArray<FString> SavedPaths;
231
+ int32 Matched = 0;
232
+ int32 Updated = 0;
233
+ int32 Failed = 0;
234
+ int32 SaveFailures = 0;
235
+
236
+ for (const FMCPFoliageBatchCandidate& Candidate : Candidates)
237
+ {
238
+ TSharedPtr<FJsonObject> Props = MakeShared<FJsonObject>();
239
+ TArray<FString> MissingProperties;
240
+ for (const FString& Path : ReadPaths)
241
+ {
242
+ FString ResolvedType;
243
+ const TSharedPtr<FJsonValue> Value =
244
+ MCPQuery::ReadDottedProperty(Candidate.Type, Path, ResolvedType);
245
+ if (!Value.IsValid())
246
+ {
247
+ MissingProperties.Add(Path);
248
+ continue;
249
+ }
250
+ Props->SetField(Path, Value);
251
+ }
252
+
253
+ TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
254
+ Row->SetStringField(TEXT("foliageTypePath"), Candidate.Path);
255
+ Row->SetStringField(TEXT("name"), Candidate.Name);
256
+ Row->SetStringField(TEXT("className"), Candidate.Type->GetClass()->GetName());
257
+ Row->SetStringField(TEXT("source"), Candidate.Source);
258
+ Row->SetObjectField(TEXT("props"), Props);
259
+ if (MissingProperties.Num() > 0)
260
+ {
261
+ // A property the predicate reads that this class does not have is
262
+ // reported rather than treated as a non-match, because "does not
263
+ // apply" and "did not match" are different answers.
264
+ Row->SetArrayField(TEXT("missingProperties"), MCPStringListToJson(MissingProperties));
265
+ }
266
+
267
+ if (!MCPQuery::EvaluateAll(Predicates, Row, WhereMode == TEXT("all")))
268
+ {
269
+ continue;
270
+ }
271
+ ++Matched;
272
+ MatchedPaths.Add(Candidate.Path);
273
+
274
+ TArray<TSharedPtr<FJsonValue>> SettingRows;
275
+ bool bTypeOk = true;
276
+ bool bTypeChanged = false;
277
+ for (const TPair<FString, TSharedPtr<FJsonValue>>& Pair : (*SettingsObject)->Values)
278
+ {
279
+ TSharedPtr<FJsonObject> SettingRow = MakeShared<FJsonObject>();
280
+ SettingRow->SetStringField(TEXT("propertyName"), Pair.Key);
281
+
282
+ FString ResolvedType;
283
+ const TSharedPtr<FJsonValue> Before =
284
+ MCPQuery::ReadDottedProperty(Candidate.Type, Pair.Key, ResolvedType);
285
+ if (Before.IsValid())
286
+ {
287
+ SettingRow->SetField(TEXT("previousValue"), Before);
288
+ }
289
+
290
+ if (bDryRun)
291
+ {
292
+ // The preview still resolves the path, so an unwritable
293
+ // property is reported now rather than after the commit.
294
+ SettingRow->SetBoolField(TEXT("ok"), Before.IsValid());
295
+ if (!Before.IsValid())
296
+ {
297
+ SettingRow->SetStringField(TEXT("error"), FString::Printf(
298
+ TEXT("Property '%s' does not resolve on %s"),
299
+ *Pair.Key, *Candidate.Type->GetClass()->GetName()));
300
+ bTypeOk = false;
301
+ }
302
+ SettingRows.Add(MakeShared<FJsonValueObject>(SettingRow));
303
+ continue;
304
+ }
305
+
306
+ FString Error;
307
+ const bool bOk = MCPJsonProperty::SetDottedPropertyFromJson(
308
+ Candidate.Type, Pair.Key, Pair.Value, Error);
309
+ SettingRow->SetBoolField(TEXT("ok"), bOk);
310
+ if (!bOk)
311
+ {
312
+ SettingRow->SetStringField(TEXT("error"), Error);
313
+ bTypeOk = false;
314
+ }
315
+ else
316
+ {
317
+ FString AfterType;
318
+ const TSharedPtr<FJsonValue> After =
319
+ MCPQuery::ReadDottedProperty(Candidate.Type, Pair.Key, AfterType);
320
+ if (After.IsValid())
321
+ {
322
+ SettingRow->SetField(TEXT("value"), After);
323
+ // Read back rather than echoing the request, so a value the
324
+ // property coerced is visible instead of assumed.
325
+ if (MCPQuery::ValueKey(Before) != MCPQuery::ValueKey(After))
326
+ {
327
+ bTypeChanged = true;
328
+ }
329
+ }
330
+ }
331
+ SettingRows.Add(MakeShared<FJsonValueObject>(SettingRow));
332
+ }
333
+
334
+ if (!bDryRun && bTypeOk)
335
+ {
336
+ Candidate.Type->MarkPackageDirty();
337
+ ++Updated;
338
+ if (bSave)
339
+ {
340
+ if (SaveAssetPackage(Candidate.Type))
341
+ {
342
+ SavedPaths.Add(Candidate.Path);
343
+ }
344
+ else
345
+ {
346
+ ++SaveFailures;
347
+ }
348
+ }
349
+ }
350
+ if (!bTypeOk) ++Failed;
351
+
352
+ if (Rows.Num() < MCPFoliageBatchMaxResultRows)
353
+ {
354
+ Row->SetBoolField(TEXT("ok"), bTypeOk);
355
+ Row->SetBoolField(TEXT("changed"), !bDryRun && bTypeChanged);
356
+ Row->SetArrayField(TEXT("settings"), SettingRows);
357
+ Rows.Add(MakeShared<FJsonValueObject>(Row));
358
+ }
359
+ }
360
+
361
+ auto Result = MCPSuccess();
362
+ Result->SetBoolField(TEXT("success"), Failed == 0 && UnloadablePaths.IsEmpty());
363
+ if (Failed > 0)
364
+ {
365
+ Result->SetStringField(TEXT("error"), FString::Printf(
366
+ TEXT("%d of %d matched foliage types had a setting that did not apply; see results[]."),
367
+ Failed, Matched));
368
+ }
369
+ else if (!UnloadablePaths.IsEmpty())
370
+ {
371
+ Result->SetStringField(TEXT("error"), FString::Printf(
372
+ TEXT("%d requested path(s) could not be loaded as a FoliageType; see unloadablePaths."),
373
+ UnloadablePaths.Num()));
374
+ }
375
+ Result->SetBoolField(TEXT("dryRun"), bDryRun);
376
+ Result->SetNumberField(TEXT("scanned"), Candidates.Num());
377
+ Result->SetNumberField(TEXT("matched"), Matched);
378
+ Result->SetNumberField(bDryRun ? TEXT("wouldUpdate") : TEXT("updated"), bDryRun ? Matched : Updated);
379
+ Result->SetNumberField(TEXT("failed"), Failed);
380
+ Result->SetArrayField(TEXT("matchedPaths"), MCPStringListToJson(MatchedPaths));
381
+ Result->SetArrayField(TEXT("unloadablePaths"), MCPStringListToJson(UnloadablePaths));
382
+ Result->SetNumberField(TEXT("returnedResults"), Rows.Num());
383
+ Result->SetBoolField(TEXT("resultsTruncated"), Rows.Num() < Matched);
384
+ Result->SetArrayField(TEXT("results"), Rows);
385
+
386
+ if (Candidates.IsEmpty())
387
+ {
388
+ Result->SetStringField(TEXT("zeroMatchNote"),
389
+ TEXT("No FoliageType was found to test. Check 'directory' against where the assets actually live, or pass fromLevel=true to take the types placed in the open level."));
390
+ }
391
+ else if (Matched == 0)
392
+ {
393
+ Result->SetStringField(TEXT("zeroMatchNote"), FString::Printf(
394
+ TEXT("%d foliage types were read and none matched the predicate. Note that a numeric comparison needs a numeric property: CullDistance is an interval, so the field is CullDistance.Max rather than CullDistance."),
395
+ Candidates.Num()));
396
+ }
397
+
398
+ if (!bDryRun)
399
+ {
400
+ Result->SetNumberField(TEXT("saved"), SavedPaths.Num());
401
+ Result->SetNumberField(TEXT("saveFailures"), SaveFailures);
402
+ Result->SetArrayField(TEXT("savedPaths"), MCPStringListToJson(SavedPaths));
403
+ if (Updated > 0)
404
+ {
405
+ MCPSetUpdated(Result);
406
+ }
407
+ if (!bSave && Updated > 0)
408
+ {
409
+ Result->SetStringField(TEXT("saveNote"),
410
+ TEXT("save=false: the FoliageType packages are dirty and NOT saved."));
411
+ }
412
+ }
413
+ else
414
+ {
415
+ Result->SetStringField(TEXT("dryRunNote"),
416
+ TEXT("dryRun defaults to TRUE for this action because it writes to many assets at once. Pass dryRun=false to commit."));
417
+ }
418
+ return MCPResult(Result);
419
+ }