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
@@ -0,0 +1,686 @@
1
+ // World Partition streaming settings and runtime cell transformers (#985).
2
+ //
3
+ // A city-scale World Partition map is tuned through three things the bridge
4
+ // could not reach at all:
5
+ //
6
+ // 1. The runtime hash's grid settings, CellSize and LoadingRange. This is the
7
+ // primary streaming knob on any WP map and there was no native read or
8
+ // write of it.
9
+ // 2. RuntimeCellsTransformerStack. The transformer object inside each entry is
10
+ // an INSTANCED subobject of the world partition, which is read-only through
11
+ // Python, so the reporter set it by hand in the editor UI. A manual fallback
12
+ // in the editor UI is exactly what this bridge exists to remove.
13
+ // 3. HLOD layer assignment per actor, in bulk. That one lives with the other
14
+ // selector-driven batch writes, in LevelHandlers_BatchWrite.cpp.
15
+ //
16
+ // These are actions in the `level` category rather than a new `worldpartition`
17
+ // category on purpose: a new category means a new tool, a new schema file and a
18
+ // new entry in every surface that enumerates categories, for three actions that
19
+ // are all about the level that is currently open. The level category already
20
+ // owns list_actor_descs and load_actor_descs, which are World Partition only for
21
+ // the same reason.
22
+ //
23
+ // Everything here works through REFLECTION rather than against the concrete
24
+ // runtime hash classes. Two reasons, and both are load-bearing. The grid
25
+ // settings live in different places on the two hashes Unreal ships
26
+ // (URuntimePartition under UWorldPartitionRuntimeHashSet, FSpatialHashRuntimeGrid
27
+ // under UWorldPartitionRuntimeSpatialHash), and a third could appear; and
28
+ // RuntimeCellsTransformerStack is a private UPROPERTY whose add helper is not
29
+ // guaranteed to be linkable from an external module on every engine version the
30
+ // plugin builds against. A reflected walk answers for all of them and cannot
31
+ // fail to link.
32
+ //
33
+ // Translation-unit partition of FLevelHandlers - registration lives in
34
+ // LevelHandlers.cpp::RegisterHandlers.
35
+
36
+ #include "LevelHandlers.h"
37
+ #include "HandlerRegistry.h"
38
+ #include "HandlerUtils.h"
39
+ #include "HandlerJsonProperty.h"
40
+
41
+ #include "Editor.h"
42
+ #include "Engine/World.h"
43
+ #include "ScopedTransaction.h"
44
+ #include "UObject/Package.h"
45
+ #include "UObject/UnrealType.h"
46
+ #include "UObject/UObjectGlobals.h"
47
+ // UWorldPartition forward-declares both of these, and the reads below convert
48
+ // them to UObject*, which needs the complete type.
49
+ #include "WorldPartition/HLOD/HLODLayer.h"
50
+ #include "WorldPartition/WorldPartition.h"
51
+ #include "WorldPartition/WorldPartitionRuntimeCellTransformer.h"
52
+ #include "WorldPartition/WorldPartitionRuntimeHash.h"
53
+ #include "Dom/JsonObject.h"
54
+ #include "Dom/JsonValue.h"
55
+
56
+ namespace
57
+ {
58
+ /** How deep the reflected walk looks for grid settings. */
59
+ constexpr int32 MCPWorldPartitionWalkDepth = 5;
60
+ /** Cap on discovered grids, so a pathological graph cannot run away. */
61
+ constexpr int32 MCPWorldPartitionMaxGrids = 64;
62
+
63
+ /** The world partition of the open map, or a reason there is none. */
64
+ UWorldPartition* MCPResolveWorldPartition(UWorld* World, FString& OutError)
65
+ {
66
+ if (!World)
67
+ {
68
+ OutError = TEXT("No editor world");
69
+ return nullptr;
70
+ }
71
+ UWorldPartition* WorldPartition = World->GetWorldPartition();
72
+ if (!WorldPartition)
73
+ {
74
+ OutError = FString::Printf(
75
+ TEXT("'%s' is not a World Partition map, so it has no runtime hash or cell transformer stack."),
76
+ *World->GetName());
77
+ return nullptr;
78
+ }
79
+ return WorldPartition;
80
+ }
81
+
82
+ /** Read any numeric property as a double, whatever its width or sign. */
83
+ bool MCPWPReadNumeric(const FProperty* Prop, const void* Container, double& OutValue)
84
+ {
85
+ const FNumericProperty* Numeric = CastField<FNumericProperty>(Prop);
86
+ if (!Numeric || !Container) return false;
87
+ const void* Addr = Prop->ContainerPtrToValuePtr<void>(Container);
88
+ OutValue = Numeric->IsFloatingPoint()
89
+ ? Numeric->GetFloatingPointPropertyValue(Addr)
90
+ : static_cast<double>(Numeric->GetSignedIntPropertyValue(Addr));
91
+ return true;
92
+ }
93
+
94
+ /** One discovered streaming grid, addressed by a dotted path a setter can reuse. */
95
+ struct FMCPWorldPartitionGrid
96
+ {
97
+ FString Path; // dotted, rooted at the world partition object
98
+ FString Name; // the grid's own Name / GridName, when it has one
99
+ FString TypeName; // the class or struct that carries the settings
100
+ double CellSize = 0.0;
101
+ double LoadingRange = 0.0;
102
+ };
103
+
104
+ /** The grid's own name field. Both shipped hashes have one, under two names. */
105
+ FString MCPWPReadGridName(const UStruct* Struct, const void* Container)
106
+ {
107
+ for (const TCHAR* Candidate : { TEXT("Name"), TEXT("GridName") })
108
+ {
109
+ if (const FNameProperty* NameProp =
110
+ CastField<FNameProperty>(Struct->FindPropertyByName(FName(Candidate))))
111
+ {
112
+ const FName Value = NameProp->GetPropertyValue_InContainer(Container);
113
+ if (!Value.IsNone()) return Value.ToString();
114
+ }
115
+ }
116
+ return FString();
117
+ }
118
+
119
+ /**
120
+ * Walk the reflected graph under the world partition looking for anything
121
+ * that declares both CellSize and LoadingRange. That pair IS the streaming
122
+ * grid, wherever the engine version happens to keep it.
123
+ *
124
+ * Object references are followed only into objects owned by the world
125
+ * partition, so the walk cannot wander out into referenced assets.
126
+ */
127
+ void MCPWPCollectGrids(
128
+ const UObject* Root,
129
+ const UStruct* Struct,
130
+ const void* Container,
131
+ const FString& PathPrefix,
132
+ int32 Depth,
133
+ TArray<FMCPWorldPartitionGrid>& OutGrids)
134
+ {
135
+ if (!Struct || !Container || Depth > MCPWorldPartitionWalkDepth) return;
136
+ if (OutGrids.Num() >= MCPWorldPartitionMaxGrids) return;
137
+
138
+ const FProperty* CellSizeProp = Struct->FindPropertyByName(TEXT("CellSize"));
139
+ const FProperty* LoadingRangeProp = Struct->FindPropertyByName(TEXT("LoadingRange"));
140
+ double CellSize = 0.0;
141
+ double LoadingRange = 0.0;
142
+ if (CellSizeProp && LoadingRangeProp &&
143
+ MCPWPReadNumeric(CellSizeProp, Container, CellSize) &&
144
+ MCPWPReadNumeric(LoadingRangeProp, Container, LoadingRange))
145
+ {
146
+ FMCPWorldPartitionGrid Grid;
147
+ Grid.Path = PathPrefix;
148
+ Grid.Name = MCPWPReadGridName(Struct, Container);
149
+ Grid.TypeName = Struct->GetName();
150
+ Grid.CellSize = CellSize;
151
+ Grid.LoadingRange = LoadingRange;
152
+ OutGrids.Add(Grid);
153
+ // A grid does not contain another grid, so this branch stops here.
154
+ return;
155
+ }
156
+
157
+ for (TFieldIterator<FProperty> It(Struct); It; ++It)
158
+ {
159
+ FProperty* Prop = *It;
160
+ if (!Prop) continue;
161
+ const FString Name = Prop->GetName();
162
+ const FString ChildPath = PathPrefix.IsEmpty() ? Name : (PathPrefix + TEXT(".") + Name);
163
+ const void* ValueAddr = Prop->ContainerPtrToValuePtr<void>(Container);
164
+
165
+ if (const FStructProperty* StructProp = CastField<FStructProperty>(Prop))
166
+ {
167
+ MCPWPCollectGrids(Root, StructProp->Struct, ValueAddr, ChildPath, Depth + 1, OutGrids);
168
+ continue;
169
+ }
170
+ if (const FObjectProperty* ObjectProp = CastField<FObjectProperty>(Prop))
171
+ {
172
+ // A class reference names a type, not an instance to walk into.
173
+ if (CastField<FClassProperty>(Prop)) continue;
174
+ UObject* Sub = ObjectProp->GetObjectPropertyValue(ValueAddr);
175
+ if (!Sub || !Sub->IsIn(Root)) continue;
176
+ MCPWPCollectGrids(Root, Sub->GetClass(), Sub, ChildPath, Depth + 1, OutGrids);
177
+ continue;
178
+ }
179
+ if (const FArrayProperty* ArrayProp = CastField<FArrayProperty>(Prop))
180
+ {
181
+ FScriptArrayHelper Helper(ArrayProp, ValueAddr);
182
+ for (int32 Index = 0; Index < Helper.Num(); ++Index)
183
+ {
184
+ const FString ElementPath = FString::Printf(TEXT("%s[%d]"), *ChildPath, Index);
185
+ const void* ElementAddr = Helper.GetRawPtr(Index);
186
+ if (const FStructProperty* InnerStruct = CastField<FStructProperty>(ArrayProp->Inner))
187
+ {
188
+ MCPWPCollectGrids(Root, InnerStruct->Struct, ElementAddr, ElementPath, Depth + 1, OutGrids);
189
+ }
190
+ else if (const FObjectProperty* InnerObject = CastField<FObjectProperty>(ArrayProp->Inner))
191
+ {
192
+ if (CastField<FClassProperty>(ArrayProp->Inner)) continue;
193
+ UObject* Sub = InnerObject->GetObjectPropertyValue(ElementAddr);
194
+ if (!Sub || !Sub->IsIn(Root)) continue;
195
+ MCPWPCollectGrids(Root, Sub->GetClass(), Sub, ElementPath, Depth + 1, OutGrids);
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ /** The RuntimeCellsTransformerStack array property, or null on an engine without it. */
203
+ FArrayProperty* MCPWPTransformerStackProperty()
204
+ {
205
+ return CastField<FArrayProperty>(
206
+ UWorldPartition::StaticClass()->FindPropertyByName(TEXT("RuntimeCellsTransformerStack")));
207
+ }
208
+
209
+ /** The Class and Instance members of one FRuntimeCellTransformerInstance. */
210
+ struct FMCPWPTransformerFields
211
+ {
212
+ FStructProperty* EntryProp = nullptr;
213
+ FObjectPropertyBase* ClassProp = nullptr;
214
+ FObjectProperty* InstanceProp = nullptr;
215
+ bool IsValid() const { return EntryProp && ClassProp && InstanceProp; }
216
+ };
217
+
218
+ FMCPWPTransformerFields MCPWPTransformerFields(FArrayProperty* ArrayProp)
219
+ {
220
+ FMCPWPTransformerFields Fields;
221
+ if (!ArrayProp) return Fields;
222
+ Fields.EntryProp = CastField<FStructProperty>(ArrayProp->Inner);
223
+ if (!Fields.EntryProp || !Fields.EntryProp->Struct) return Fields;
224
+ Fields.ClassProp = CastField<FObjectPropertyBase>(
225
+ Fields.EntryProp->Struct->FindPropertyByName(TEXT("Class")));
226
+ Fields.InstanceProp = CastField<FObjectProperty>(
227
+ Fields.EntryProp->Struct->FindPropertyByName(TEXT("Instance")));
228
+ return Fields;
229
+ }
230
+
231
+ /** Every EditAnywhere property on a transformer instance, with its value. */
232
+ TArray<TSharedPtr<FJsonValue>> MCPWPDescribeInstanceProperties(UObject* Instance)
233
+ {
234
+ TArray<TSharedPtr<FJsonValue>> Rows;
235
+ if (!Instance) return Rows;
236
+ for (TFieldIterator<FProperty> It(Instance->GetClass()); It; ++It)
237
+ {
238
+ FProperty* Prop = *It;
239
+ if (!Prop || !Prop->HasAnyPropertyFlags(CPF_Edit)) continue;
240
+ TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
241
+ Row->SetStringField(TEXT("name"), Prop->GetName());
242
+ Row->SetStringField(TEXT("type"), Prop->GetCPPType());
243
+ Row->SetField(TEXT("value"), MCPExportPropertyValue(Prop, Instance));
244
+ Rows.Add(MakeShared<FJsonValueObject>(Row));
245
+ }
246
+ return Rows;
247
+ }
248
+
249
+ /** The transformer stack as JSON, each entry addressable by its dotted path. */
250
+ TArray<TSharedPtr<FJsonValue>> MCPWPDescribeTransformerStack(UWorldPartition* WorldPartition)
251
+ {
252
+ TArray<TSharedPtr<FJsonValue>> Rows;
253
+ FArrayProperty* ArrayProp = MCPWPTransformerStackProperty();
254
+ const FMCPWPTransformerFields Fields = MCPWPTransformerFields(ArrayProp);
255
+ if (!Fields.IsValid()) return Rows;
256
+
257
+ FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(WorldPartition));
258
+ for (int32 Index = 0; Index < Helper.Num(); ++Index)
259
+ {
260
+ void* EntryAddr = Helper.GetRawPtr(Index);
261
+ UObject* ClassObject = Fields.ClassProp->GetObjectPropertyValue(
262
+ Fields.ClassProp->ContainerPtrToValuePtr<void>(EntryAddr));
263
+ UObject* Instance = Fields.InstanceProp->GetObjectPropertyValue(
264
+ Fields.InstanceProp->ContainerPtrToValuePtr<void>(EntryAddr));
265
+
266
+ TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
267
+ Row->SetNumberField(TEXT("index"), Index);
268
+ Row->SetStringField(TEXT("class"), ClassObject ? ClassObject->GetPathName() : FString());
269
+ // The path a caller passes to set_world_partition_settings to write
270
+ // a property on the instanced transformer object.
271
+ Row->SetStringField(TEXT("instancePath"),
272
+ FString::Printf(TEXT("RuntimeCellsTransformerStack[%d].Instance"), Index));
273
+ Row->SetBoolField(TEXT("hasInstance"), Instance != nullptr);
274
+ if (Instance)
275
+ {
276
+ Row->SetStringField(TEXT("instanceClass"), Instance->GetClass()->GetPathName());
277
+ Row->SetArrayField(TEXT("properties"), MCPWPDescribeInstanceProperties(Instance));
278
+ }
279
+ Rows.Add(MakeShared<FJsonValueObject>(Row));
280
+ }
281
+ return Rows;
282
+ }
283
+
284
+ /**
285
+ * Export a property from its VALUE address rather than from a container.
286
+ *
287
+ * A dotted path can end inside a struct element of an array, where the
288
+ * nearest UObject is not the property's container, so the usual
289
+ * container-based export would read the wrong address. Every setting this
290
+ * action writes is a scalar, which is what makes the direct export safe
291
+ * here: it is the one case where a fixed array would need the indexed form.
292
+ */
293
+ TSharedPtr<FJsonValue> MCPWPExportAtAddress(const FProperty* Prop, const void* ValueAddr)
294
+ {
295
+ FString Text;
296
+ if (Prop && ValueAddr)
297
+ {
298
+ Prop->ExportTextItem_Direct(Text, ValueAddr, nullptr, nullptr, PPF_None);
299
+ }
300
+ return MakeShared<FJsonValueString>(Text);
301
+ }
302
+
303
+ /** Mark the world's own package dirty after a world partition edit. */
304
+ void MCPWPMarkDirty(UWorld* World, UWorldPartition* WorldPartition)
305
+ {
306
+ if (WorldPartition) WorldPartition->PostEditChange();
307
+ if (World && World->GetOutermost()) World->GetOutermost()->MarkPackageDirty();
308
+ }
309
+ }
310
+
311
+
312
+ // level(get_world_partition_settings): the streaming knobs on the open map.
313
+ TSharedPtr<FJsonValue> FLevelHandlers::GetWorldPartitionSettings(const TSharedPtr<FJsonObject>& Params)
314
+ {
315
+ REQUIRE_EDITOR_WORLD(World);
316
+
317
+ FString ResolveError;
318
+ UWorldPartition* WorldPartition = MCPResolveWorldPartition(World, ResolveError);
319
+ if (!WorldPartition) return MCPError(ResolveError);
320
+
321
+ TArray<FMCPWorldPartitionGrid> Grids;
322
+ MCPWPCollectGrids(WorldPartition, WorldPartition->GetClass(), WorldPartition, FString(), 0, Grids);
323
+
324
+ TArray<TSharedPtr<FJsonValue>> GridRows;
325
+ for (const FMCPWorldPartitionGrid& Grid : Grids)
326
+ {
327
+ TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
328
+ Row->SetStringField(TEXT("name"), Grid.Name);
329
+ Row->SetStringField(TEXT("path"), Grid.Path);
330
+ Row->SetStringField(TEXT("type"), Grid.TypeName);
331
+ Row->SetNumberField(TEXT("cellSize"), Grid.CellSize);
332
+ Row->SetNumberField(TEXT("loadingRange"), Grid.LoadingRange);
333
+ GridRows.Add(MakeShared<FJsonValueObject>(Row));
334
+ }
335
+
336
+ auto Result = MCPSuccess();
337
+ Result->SetStringField(TEXT("world"), World->GetName());
338
+ Result->SetStringField(TEXT("worldPartition"), WorldPartition->GetPathName());
339
+ Result->SetStringField(TEXT("worldPartitionClass"), WorldPartition->GetClass()->GetPathName());
340
+ Result->SetBoolField(TEXT("enableStreaming"), WorldPartition->IsStreamingEnabled());
341
+ if (UObject* RuntimeHash = WorldPartition->RuntimeHash)
342
+ {
343
+ Result->SetStringField(TEXT("runtimeHashClass"), RuntimeHash->GetClass()->GetPathName());
344
+ }
345
+ if (UObject* DefaultHLOD = WorldPartition->GetDefaultHLODLayer())
346
+ {
347
+ Result->SetStringField(TEXT("defaultHLODLayer"), DefaultHLOD->GetPathName());
348
+ }
349
+ Result->SetArrayField(TEXT("grids"), GridRows);
350
+ Result->SetNumberField(TEXT("gridCount"), GridRows.Num());
351
+ Result->SetArrayField(TEXT("cellTransformers"), MCPWPDescribeTransformerStack(WorldPartition));
352
+ Result->SetStringField(TEXT("note"),
353
+ TEXT("Each grid's 'path' and each transformer's 'instancePath' are dotted paths rooted at the world partition object; pass either to level(set_world_partition_settings) in its 'settings' map to write anything they contain."));
354
+ return MCPResult(Result);
355
+ }
356
+
357
+
358
+ // level(set_world_partition_settings): write the streaming knobs, and anything
359
+ // else reachable from the world partition, including the properties of an
360
+ // INSTANCED runtime cell transformer, which is the object the reporter had to
361
+ // configure by hand in the editor UI.
362
+ TSharedPtr<FJsonValue> FLevelHandlers::SetWorldPartitionSettings(const TSharedPtr<FJsonObject>& Params)
363
+ {
364
+ REQUIRE_EDITOR_WORLD(World);
365
+
366
+ FString ResolveError;
367
+ UWorldPartition* WorldPartition = MCPResolveWorldPartition(World, ResolveError);
368
+ if (!WorldPartition) return MCPError(ResolveError);
369
+
370
+ // Every write is a dotted path rooted at the world partition. cellSize and
371
+ // loadingRange are the two that get a shorthand, because they are the
372
+ // primary streaming knob and asking a caller to spell out
373
+ // RuntimeHash.RuntimePartitions[0].MainLayer.CellSize for them would be a
374
+ // riddle rather than an interface.
375
+ TArray<TPair<FString, TSharedPtr<FJsonValue>>> Writes;
376
+
377
+ const bool bWantsCellSize = Params->HasField(TEXT("cellSize"));
378
+ const bool bWantsLoadingRange = Params->HasField(TEXT("loadingRange"));
379
+ if (bWantsCellSize || bWantsLoadingRange)
380
+ {
381
+ TArray<FMCPWorldPartitionGrid> Grids;
382
+ MCPWPCollectGrids(WorldPartition, WorldPartition->GetClass(), WorldPartition, FString(), 0, Grids);
383
+ if (Grids.Num() == 0)
384
+ {
385
+ return MCPError(TEXT("This world partition exposes no streaming grid with a CellSize and a LoadingRange. Read level(get_world_partition_settings) to see what it does expose."));
386
+ }
387
+
388
+ const FString WantedGrid = OptionalString(Params, TEXT("grid"));
389
+ const FString ExplicitPath = OptionalString(Params, TEXT("gridPath"));
390
+ const FMCPWorldPartitionGrid* Target = nullptr;
391
+ if (!ExplicitPath.IsEmpty())
392
+ {
393
+ Target = Grids.FindByPredicate([&ExplicitPath](const FMCPWorldPartitionGrid& G)
394
+ {
395
+ return G.Path.Equals(ExplicitPath, ESearchCase::IgnoreCase);
396
+ });
397
+ if (!Target)
398
+ {
399
+ return MCPError(FString::Printf(
400
+ TEXT("No streaming grid at path '%s'. Read level(get_world_partition_settings) for the paths this map has."),
401
+ *ExplicitPath));
402
+ }
403
+ }
404
+ else if (!WantedGrid.IsEmpty())
405
+ {
406
+ Target = Grids.FindByPredicate([&WantedGrid](const FMCPWorldPartitionGrid& G)
407
+ {
408
+ return G.Name.Equals(WantedGrid, ESearchCase::IgnoreCase);
409
+ });
410
+ if (!Target)
411
+ {
412
+ TArray<FString> Names;
413
+ for (const FMCPWorldPartitionGrid& G : Grids) Names.Add(G.Name.IsEmpty() ? G.Path : G.Name);
414
+ return MCPError(FString::Printf(
415
+ TEXT("No streaming grid named '%s'. This map has: [%s]"),
416
+ *WantedGrid, *FString::Join(Names, TEXT(", "))));
417
+ }
418
+ }
419
+ else if (Grids.Num() == 1)
420
+ {
421
+ Target = &Grids[0];
422
+ }
423
+ else
424
+ {
425
+ TArray<FString> Names;
426
+ for (const FMCPWorldPartitionGrid& G : Grids) Names.Add(G.Name.IsEmpty() ? G.Path : G.Name);
427
+ return MCPError(FString::Printf(
428
+ TEXT("This map has %d streaming grids; pass 'grid' (name) or 'gridPath' to choose. Available: [%s]"),
429
+ Grids.Num(), *FString::Join(Names, TEXT(", "))));
430
+ }
431
+
432
+ if (bWantsCellSize)
433
+ {
434
+ const double CellSize = OptionalNumber(Params, TEXT("cellSize"), 0.0);
435
+ if (CellSize <= 0.0) return MCPError(TEXT("'cellSize' must be positive (world centimetres per streaming cell)"));
436
+ Writes.Add(TPair<FString, TSharedPtr<FJsonValue>>(
437
+ Target->Path + TEXT(".CellSize"), MakeShared<FJsonValueNumber>(CellSize)));
438
+ }
439
+ if (bWantsLoadingRange)
440
+ {
441
+ const double LoadingRange = OptionalNumber(Params, TEXT("loadingRange"), 0.0);
442
+ if (LoadingRange <= 0.0) return MCPError(TEXT("'loadingRange' must be positive (world centimetres of streaming radius)"));
443
+ Writes.Add(TPair<FString, TSharedPtr<FJsonValue>>(
444
+ Target->Path + TEXT(".LoadingRange"), MakeShared<FJsonValueNumber>(LoadingRange)));
445
+ }
446
+ }
447
+
448
+ const TSharedPtr<FJsonObject>* SettingsObj = nullptr;
449
+ if (Params->TryGetObjectField(TEXT("settings"), SettingsObj) && SettingsObj && SettingsObj->IsValid())
450
+ {
451
+ for (const auto& Pair : (*SettingsObj)->Values)
452
+ {
453
+ Writes.Add(TPair<FString, TSharedPtr<FJsonValue>>(FString(*Pair.Key), Pair.Value));
454
+ }
455
+ }
456
+
457
+ if (Writes.Num() == 0)
458
+ {
459
+ return MCPError(TEXT("Nothing to write. Pass cellSize and/or loadingRange, or a 'settings' object of dotted path -> value rooted at the world partition (see level(get_world_partition_settings))."));
460
+ }
461
+
462
+ // Resolve every path before writing any of them. Streaming settings are the
463
+ // kind of thing a caller changes in one batch, and a half-applied batch
464
+ // leaves the map streaming on a mixture nobody asked for.
465
+ for (const TPair<FString, TSharedPtr<FJsonValue>>& Write : Writes)
466
+ {
467
+ FProperty* Prop = nullptr;
468
+ void* ValueAddr = nullptr;
469
+ UObject* Owner = nullptr;
470
+ FString PathError;
471
+ if (!MCPJsonProperty::ResolveDottedPath(WorldPartition, Write.Key, Prop, ValueAddr, Owner, PathError))
472
+ {
473
+ return MCPError(FString::Printf(
474
+ TEXT("'%s' does not resolve from the world partition (%s). Nothing was written. Read level(get_world_partition_settings) for the paths this map has."),
475
+ *Write.Key, *PathError));
476
+ }
477
+ }
478
+
479
+ TArray<TSharedPtr<FJsonValue>> Applied;
480
+ int32 FailureCount = 0;
481
+ {
482
+ const FScopedTransaction Transaction(FText::FromString(TEXT("MCP world partition settings")));
483
+ WorldPartition->Modify();
484
+
485
+ for (const TPair<FString, TSharedPtr<FJsonValue>>& Write : Writes)
486
+ {
487
+ TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
488
+ Entry->SetStringField(TEXT("path"), Write.Key);
489
+
490
+ FProperty* Prop = nullptr;
491
+ void* ValueAddr = nullptr;
492
+ UObject* Owner = nullptr;
493
+ FString PathError;
494
+ if (!MCPJsonProperty::ResolveDottedPath(WorldPartition, Write.Key, Prop, ValueAddr, Owner, PathError))
495
+ {
496
+ Entry->SetBoolField(TEXT("applied"), false);
497
+ Entry->SetStringField(TEXT("error"), PathError);
498
+ Applied.Add(MakeShared<FJsonValueObject>(Entry));
499
+ ++FailureCount;
500
+ continue;
501
+ }
502
+ // The leaf owner is what actually changes, and on a transformer
503
+ // instance that is a different object from the world partition.
504
+ if (Owner && Owner != WorldPartition) Owner->Modify();
505
+ Entry->SetField(TEXT("previousValue"), MCPWPExportAtAddress(Prop, ValueAddr));
506
+
507
+ FString SetError;
508
+ if (!MCPJsonProperty::SetJsonOnProperty(Prop, ValueAddr, Write.Value, SetError))
509
+ {
510
+ Entry->SetBoolField(TEXT("applied"), false);
511
+ Entry->SetStringField(TEXT("error"), SetError);
512
+ ++FailureCount;
513
+ }
514
+ else
515
+ {
516
+ Entry->SetBoolField(TEXT("applied"), true);
517
+ Entry->SetStringField(TEXT("owner"), Owner ? Owner->GetPathName() : FString());
518
+ // Read back rather than echo, so the response says what the
519
+ // object holds now.
520
+ Entry->SetField(TEXT("value"), MCPWPExportAtAddress(Prop, ValueAddr));
521
+ if (Owner) Owner->PostEditChange();
522
+ }
523
+ Applied.Add(MakeShared<FJsonValueObject>(Entry));
524
+ }
525
+
526
+ MCPWPMarkDirty(World, WorldPartition);
527
+ }
528
+
529
+ auto Result = MCPSuccess();
530
+ MCPSetUpdated(Result);
531
+ Result->SetStringField(TEXT("worldPartition"), WorldPartition->GetPathName());
532
+ Result->SetNumberField(TEXT("appliedCount"), Applied.Num() - FailureCount);
533
+ Result->SetNumberField(TEXT("failedCount"), FailureCount);
534
+ Result->SetArrayField(TEXT("writes"), Applied);
535
+ if (FailureCount > 0) Result->SetBoolField(TEXT("success"), false);
536
+ Result->SetStringField(TEXT("note"),
537
+ TEXT("Streaming settings only take effect for cells generated after the change, so rebuild the streaming data (or re-cook) before measuring. The map package is left dirty and is NOT saved."));
538
+ return MCPResult(Result);
539
+ }
540
+
541
+
542
+ // level(add_runtime_cell_transformer): append a transformer to
543
+ // RuntimeCellsTransformerStack and configure its instanced object in the same
544
+ // call. Adding the entry without being able to configure the instance would
545
+ // leave the manual editor step exactly where it was.
546
+ TSharedPtr<FJsonValue> FLevelHandlers::AddRuntimeCellTransformer(const TSharedPtr<FJsonObject>& Params)
547
+ {
548
+ REQUIRE_EDITOR_WORLD(World);
549
+
550
+ FString ResolveError;
551
+ UWorldPartition* WorldPartition = MCPResolveWorldPartition(World, ResolveError);
552
+ if (!WorldPartition) return MCPError(ResolveError);
553
+
554
+ FString ClassName;
555
+ if (auto Err = RequireStringAlt(Params, TEXT("transformerClass"), TEXT("className"), ClassName)) return Err;
556
+
557
+ // The base check and the concrete check both come from the shared guard, so
558
+ // a wrong class here reads the same way it does everywhere else in the
559
+ // bridge rather than growing its own phrasing.
560
+ UClass* TransformerClass = MCPResolveClass(ClassName);
561
+ if (auto Err = MCPCheckClassUsable(
562
+ ClassName, TransformerClass, UWorldPartitionRuntimeCellTransformer::StaticClass()))
563
+ {
564
+ return Err;
565
+ }
566
+
567
+ FArrayProperty* ArrayProp = MCPWPTransformerStackProperty();
568
+ const FMCPWPTransformerFields Fields = MCPWPTransformerFields(ArrayProp);
569
+ if (!Fields.IsValid())
570
+ {
571
+ return MCPError(TEXT("This engine's UWorldPartition does not expose RuntimeCellsTransformerStack with the Class and Instance members this action writes."));
572
+ }
573
+
574
+ const bool bSkipIfPresent = OptionalBool(Params, TEXT("skipIfPresent"), true);
575
+ FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(WorldPartition));
576
+
577
+ if (bSkipIfPresent)
578
+ {
579
+ for (int32 Index = 0; Index < Helper.Num(); ++Index)
580
+ {
581
+ void* EntryAddr = Helper.GetRawPtr(Index);
582
+ UObject* ExistingClass = Fields.ClassProp->GetObjectPropertyValue(
583
+ Fields.ClassProp->ContainerPtrToValuePtr<void>(EntryAddr));
584
+ if (ExistingClass == TransformerClass)
585
+ {
586
+ auto Existing = MCPSuccess();
587
+ MCPSetExisted(Existing);
588
+ Existing->SetNumberField(TEXT("index"), Index);
589
+ Existing->SetStringField(TEXT("class"), TransformerClass->GetPathName());
590
+ Existing->SetStringField(TEXT("instancePath"),
591
+ FString::Printf(TEXT("RuntimeCellsTransformerStack[%d].Instance"), Index));
592
+ Existing->SetStringField(TEXT("note"),
593
+ TEXT("A transformer of this class is already in the stack; nothing was added. Pass skipIfPresent=false to add a second one, or write its properties with level(set_world_partition_settings) using instancePath."));
594
+ return MCPResult(Existing);
595
+ }
596
+ }
597
+ }
598
+
599
+ // -1 (the default) appends. Any other position inserts there, because the
600
+ // stack is ordered and a transformer that has to run before another one has
601
+ // no other way to say so.
602
+ const int32 RequestedPosition = OptionalInt(Params, TEXT("position"), -1);
603
+ if (RequestedPosition < -1 || RequestedPosition > Helper.Num())
604
+ {
605
+ return MCPError(FString::Printf(
606
+ TEXT("'position' %d is out of range for a stack of %d entries (use -1 to append)"),
607
+ RequestedPosition, Helper.Num()));
608
+ }
609
+
610
+ const TSharedPtr<FJsonObject>* PropertiesField = nullptr;
611
+ TSharedPtr<FJsonObject> Properties;
612
+ if (Params->TryGetObjectField(TEXT("properties"), PropertiesField) && PropertiesField && (*PropertiesField).IsValid())
613
+ {
614
+ Properties = *PropertiesField;
615
+ }
616
+
617
+ int32 NewIndex = INDEX_NONE;
618
+ TArray<TSharedPtr<FJsonValue>> AppliedProperties;
619
+ TArray<FString> PropertyErrors;
620
+ {
621
+ const FScopedTransaction Transaction(FText::FromString(TEXT("MCP add runtime cell transformer")));
622
+ WorldPartition->Modify();
623
+
624
+ if (RequestedPosition < 0)
625
+ {
626
+ NewIndex = Helper.AddValue();
627
+ }
628
+ else
629
+ {
630
+ Helper.InsertValues(RequestedPosition, 1);
631
+ NewIndex = RequestedPosition;
632
+ }
633
+
634
+ void* EntryAddr = Helper.GetRawPtr(NewIndex);
635
+ Fields.ClassProp->SetObjectPropertyValue(
636
+ Fields.ClassProp->ContainerPtrToValuePtr<void>(EntryAddr), TransformerClass);
637
+
638
+ // The instanced object is the part Python could not write, so it is
639
+ // created here rather than left for the editor UI to fill in.
640
+ UObject* Instance = NewObject<UObject>(
641
+ WorldPartition, TransformerClass, NAME_None, RF_Transactional);
642
+ Fields.InstanceProp->SetObjectPropertyValue(
643
+ Fields.InstanceProp->ContainerPtrToValuePtr<void>(EntryAddr), Instance);
644
+
645
+ if (Instance && Properties.IsValid())
646
+ {
647
+ Instance->Modify();
648
+ for (const auto& Pair : Properties->Values)
649
+ {
650
+ const FString PropertyPath(*Pair.Key);
651
+ FString SetError;
652
+ if (MCPJsonProperty::SetDottedPropertyFromJson(Instance, PropertyPath, Pair.Value, SetError))
653
+ {
654
+ AppliedProperties.Add(MakeShared<FJsonValueString>(PropertyPath));
655
+ }
656
+ else
657
+ {
658
+ PropertyErrors.Add(FString::Printf(TEXT("%s: %s"), *PropertyPath, *SetError));
659
+ }
660
+ }
661
+ Instance->PostEditChange();
662
+ }
663
+
664
+ MCPWPMarkDirty(World, WorldPartition);
665
+ }
666
+
667
+ auto Result = MCPSuccess();
668
+ MCPSetCreated(Result);
669
+ Result->SetStringField(TEXT("worldPartition"), WorldPartition->GetPathName());
670
+ Result->SetNumberField(TEXT("index"), NewIndex);
671
+ Result->SetNumberField(TEXT("stackSize"), Helper.Num());
672
+ Result->SetStringField(TEXT("class"), TransformerClass->GetPathName());
673
+ Result->SetStringField(TEXT("instancePath"),
674
+ FString::Printf(TEXT("RuntimeCellsTransformerStack[%d].Instance"), NewIndex));
675
+ Result->SetArrayField(TEXT("appliedProperties"), AppliedProperties);
676
+ if (PropertyErrors.Num() > 0)
677
+ {
678
+ Result->SetBoolField(TEXT("success"), false);
679
+ Result->SetStringField(TEXT("error"), FString::Printf(
680
+ TEXT("The transformer was added but %d of its properties would not apply: %s"),
681
+ PropertyErrors.Num(), *FString::Join(PropertyErrors, TEXT("; "))));
682
+ }
683
+ Result->SetStringField(TEXT("note"),
684
+ TEXT("Transformers run when streaming cells are generated, so rebuild the streaming data before measuring. The map package is left dirty and is NOT saved."));
685
+ return MCPResult(Result);
686
+ }