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
@@ -20,6 +20,29 @@
20
20
 
21
21
  namespace
22
22
  {
23
+ /**
24
+ * #933: the world a trace runs in, taken from the request's own `world`
25
+ * (editor|pie|game|auto) plus `pieInstance`, through ResolveWorldFromParams -
26
+ * the one resolver every other world-scoped action in this category already
27
+ * goes through. Written once here because all three trace actions need it and
28
+ * a second copy of the resolution is how the two would drift apart again.
29
+ *
30
+ * On failure OutError carries the response to return, so the caller does not
31
+ * restate the message and the three actions cannot disagree about it.
32
+ */
33
+ static UWorld* ResolveTraceWorld(const TSharedPtr<FJsonObject>& Params, TSharedPtr<FJsonValue>& OutError)
34
+ {
35
+ const FString Scope = OptionalString(Params, TEXT("world"), TEXT("editor"));
36
+ UWorld* World = ResolveWorldFromParams(Params, *Scope);
37
+ if (!World)
38
+ {
39
+ OutError = MCPError(FString::Printf(
40
+ TEXT("World not available for scope '%s'. world='pie' needs a running PIE session; see editor(list_pie_instances)."),
41
+ *Scope));
42
+ }
43
+ return World;
44
+ }
45
+
23
46
  /**
24
47
  * Channel display names come from the project's collision settings, so a project
25
48
  * that renamed GameTraceChannel1 to "Weapon" can be traced by that name. The
@@ -90,6 +113,20 @@ namespace
90
113
  return FString::Join(Names, TEXT(", "));
91
114
  }
92
115
 
116
+ /** Which world answered: "editor", "pie", "game" or "other". */
117
+ static FString DescribeTracedWorld(const UWorld* World)
118
+ {
119
+ if (!World) return TEXT("none");
120
+ switch (World->WorldType)
121
+ {
122
+ case EWorldType::Editor: return TEXT("editor");
123
+ case EWorldType::EditorPreview: return TEXT("editorPreview");
124
+ case EWorldType::PIE: return TEXT("pie");
125
+ case EWorldType::Game: return TEXT("game");
126
+ default: return TEXT("other");
127
+ }
128
+ }
129
+
93
130
  static void EmitHitFields(TSharedPtr<FJsonObject> Result, const FHitResult& Hit)
94
131
  {
95
132
  AActor* HitActor = Hit.GetActor();
@@ -97,6 +134,9 @@ namespace
97
134
  if (HitActor)
98
135
  {
99
136
  Result->SetStringField(TEXT("actorLabel"), HitActor->GetActorLabel());
137
+ // #983: a trace hit is exactly where a caller needs the precise
138
+ // selector, since it did not choose the actor at all.
139
+ Result->SetStringField(TEXT("actorPath"), HitActor->GetPathName());
100
140
  Result->SetStringField(TEXT("actorClass"), HitActor->GetClass()->GetName());
101
141
  }
102
142
  if (HitComp)
@@ -115,92 +155,170 @@ namespace
115
155
  if (Hit.BoneName != NAME_None) Result->SetStringField(TEXT("boneName"), Hit.BoneName.ToString());
116
156
  if (Hit.PhysMaterial.IsValid()) Result->SetStringField(TEXT("physicalMaterial"), Hit.PhysMaterial->GetPathName());
117
157
  }
158
+
159
+ /** One line_trace. Shared by the single-item handler and bulk_line_trace. */
160
+ static TSharedPtr<FJsonValue> ExecuteLineTrace(UWorld* World, const TSharedPtr<FJsonObject>& Params)
161
+ {
162
+ if (!Params->HasField(TEXT("start")))
163
+ {
164
+ return MCPError(TEXT("Missing 'start' vector"));
165
+ }
166
+ const FVector Start = OptionalVec3(Params, TEXT("start"));
167
+ FVector End;
168
+ if (Params->HasField(TEXT("end")))
169
+ {
170
+ End = OptionalVec3(Params, TEXT("end"));
171
+ }
172
+ else if (Params->HasField(TEXT("direction")))
173
+ {
174
+ FVector Dir = OptionalVec3(Params, TEXT("direction"));
175
+ if (!Dir.Normalize())
176
+ {
177
+ return MCPError(TEXT("'direction' must be a non-zero vector"));
178
+ }
179
+ const double Distance = OptionalNumber(Params, TEXT("distance"), 200000.0);
180
+ End = Start + Dir * Distance;
181
+ }
182
+ else
183
+ {
184
+ return MCPError(TEXT("Pass either 'end' (Vec3) or 'direction' (Vec3) + 'distance?'"));
185
+ }
186
+
187
+ // Gameplay traces run against simple collision unless they ask otherwise, so a
188
+ // trace taken here to verify in-game behaviour has to do the same by default.
189
+ // Complex geometry and its simple hull can be far apart, and a per-triangle hit
190
+ // the running game never produces reads as a confirmed impact point.
191
+ const bool bTraceComplex = OptionalBool(Params, TEXT("traceComplex"), false);
192
+
193
+ // Visibility is the editor picking channel. A gameplay trace usually runs on
194
+ // another one, and blocking differs per channel, so the channel has to be
195
+ // selectable for the result to mean anything about the game.
196
+ ECollisionChannel Channel = ECC_Visibility;
197
+ FString ChannelName = TEXT("Visibility");
198
+ const FString RequestedChannel = OptionalString(Params, TEXT("channel"));
199
+ if (!RequestedChannel.IsEmpty() && !ResolveTraceChannel(RequestedChannel, Channel, ChannelName))
200
+ {
201
+ return MCPError(FString::Printf(
202
+ TEXT("Unknown collision channel '%s'. Available channels: %s"),
203
+ *RequestedChannel, *DescribeTraceChannels()));
204
+ }
205
+
206
+ FCollisionQueryParams Query(SCENE_QUERY_STAT(MCPLineTrace), bTraceComplex);
207
+ Query.bReturnPhysicalMaterial = true;
208
+ Query.bReturnFaceIndex = bTraceComplex;
209
+
210
+ const TArray<TSharedPtr<FJsonValue>>* IgnoreArr = nullptr;
211
+ if (Params->TryGetArrayField(TEXT("ignoreActors"), IgnoreArr) && IgnoreArr)
212
+ {
213
+ for (const TSharedPtr<FJsonValue>& V : *IgnoreArr)
214
+ {
215
+ FString Label;
216
+ if (!V->TryGetString(Label)) continue;
217
+ // #983: an ignore list is the plural case. A label naming
218
+ // three actors ignores all three, which is what the caller
219
+ // meant and what ignoring one of them silently was not.
220
+ TArray<AActor*> Matches;
221
+ MCPCollectActorsByToken(World, Label, EMCPActorMatch::LabelNameOrPath, Matches);
222
+ for (AActor* A : Matches) Query.AddIgnoredActor(A);
223
+ }
224
+ }
225
+
226
+ FHitResult Hit;
227
+ const bool bHit = World->LineTraceSingleByChannel(Hit, Start, End, Channel, Query);
228
+
229
+ auto Result = MCPSuccess();
230
+ Result->SetBoolField(TEXT("hit"), bHit);
231
+ Result->SetObjectField(TEXT("start"), MCPVec3ToJsonObject(Start));
232
+ Result->SetObjectField(TEXT("end"), MCPVec3ToJsonObject(End));
233
+ // Report the collision semantics the result was produced under, so a caller
234
+ // comparing against the game can see which one it got.
235
+ Result->SetBoolField(TEXT("traceComplex"), bTraceComplex);
236
+ Result->SetStringField(TEXT("channel"), ChannelName);
237
+ // #933: name the world that actually answered. A caller comparing a PIE
238
+ // trace against an editor trace has no other way to tell them apart, and
239
+ // silently answering from the wrong one is the bug this closes.
240
+ Result->SetStringField(TEXT("world"), DescribeTracedWorld(World));
241
+ if (bHit) EmitHitFields(Result, Hit);
242
+ return MCPResult(Result);
243
+ }
118
244
  }
119
245
 
120
246
 
247
+ // #933: `world` selects the world the trace runs against, the same way every
248
+ // other PIE-aware action in this category does. REQUIRE_EDITOR_WORLD ignored the
249
+ // parameter, so a trace asked for during PIE answered from the editor world and
250
+ // reported the pre-play geometry as though it were the running game's. That is
251
+ // the worst shape of wrong answer: nothing about it looks wrong.
121
252
  TSharedPtr<FJsonValue> FLevelHandlers::LineTrace(const TSharedPtr<FJsonObject>& Params)
122
253
  {
123
- REQUIRE_EDITOR_WORLD(World);
254
+ TSharedPtr<FJsonValue> WorldError;
255
+ UWorld* World = ResolveTraceWorld(Params, WorldError);
256
+ if (!World) return WorldError;
257
+ return ExecuteLineTrace(World, Params);
258
+ }
124
259
 
125
- const FVector Start = OptionalVec3(Params, TEXT("start"));
126
- FVector End;
127
- if (Params->HasField(TEXT("end")))
128
- {
129
- End = OptionalVec3(Params, TEXT("end"));
130
- }
131
- else if (Params->HasField(TEXT("direction")))
260
+
261
+ TSharedPtr<FJsonValue> FLevelHandlers::BulkLineTrace(const TSharedPtr<FJsonObject>& Params)
262
+ {
263
+ // #933: one world for the whole batch, chosen by the top-level `world`. A
264
+ // per-item scope would let one batch straddle two worlds and report the
265
+ // results in one array as though they were comparable.
266
+ TSharedPtr<FJsonValue> WorldError;
267
+ UWorld* World = ResolveTraceWorld(Params, WorldError);
268
+ if (!World) return WorldError;
269
+
270
+ constexpr int32 MaxBulkLineTraces = 256;
271
+ const TArray<TSharedPtr<FJsonValue>>* Traces = nullptr;
272
+ if (!Params->TryGetArrayField(TEXT("traces"), Traces) || !Traces)
132
273
  {
133
- FVector Dir = OptionalVec3(Params, TEXT("direction"));
134
- if (!Dir.Normalize())
135
- {
136
- return MCPError(TEXT("'direction' must be a non-zero vector"));
137
- }
138
- const double Distance = OptionalNumber(Params, TEXT("distance"), 200000.0);
139
- End = Start + Dir * Distance;
274
+ return MCPError(TEXT("Missing 'traces' array"));
140
275
  }
141
- else
276
+ if (Traces->Num() == 0)
142
277
  {
143
- return MCPError(TEXT("Pass either 'end' (Vec3) or 'direction' (Vec3) + 'distance?'"));
278
+ return MCPError(TEXT("'traces' must contain at least one line trace"));
144
279
  }
145
-
146
- // Gameplay traces run against simple collision unless they ask otherwise, so a
147
- // trace taken here to verify in-game behaviour has to do the same by default.
148
- // Complex geometry and its simple hull can be far apart, and a per-triangle hit
149
- // the running game never produces reads as a confirmed impact point.
150
- const bool bTraceComplex = OptionalBool(Params, TEXT("traceComplex"), false);
151
-
152
- // Visibility is the editor picking channel. A gameplay trace usually runs on
153
- // another one, and blocking differs per channel, so the channel has to be
154
- // selectable for the result to mean anything about the game.
155
- ECollisionChannel Channel = ECC_Visibility;
156
- FString ChannelName = TEXT("Visibility");
157
- const FString RequestedChannel = OptionalString(Params, TEXT("channel"));
158
- if (!RequestedChannel.IsEmpty() && !ResolveTraceChannel(RequestedChannel, Channel, ChannelName))
280
+ if (Traces->Num() > MaxBulkLineTraces)
159
281
  {
160
282
  return MCPError(FString::Printf(
161
- TEXT("Unknown collision channel '%s'. Available channels: %s"),
162
- *RequestedChannel, *DescribeTraceChannels()));
283
+ TEXT("'traces' exceeds the maximum batch size of %d (received %d)"),
284
+ MaxBulkLineTraces, Traces->Num()));
163
285
  }
164
286
 
165
- FCollisionQueryParams Query(SCENE_QUERY_STAT(MCPLineTrace), bTraceComplex);
166
- Query.bReturnPhysicalMaterial = true;
167
- Query.bReturnFaceIndex = bTraceComplex;
168
-
169
- const TArray<TSharedPtr<FJsonValue>>* IgnoreArr = nullptr;
170
- if (Params->TryGetArrayField(TEXT("ignoreActors"), IgnoreArr) && IgnoreArr)
287
+ TArray<TSharedPtr<FJsonValue>> Results;
288
+ Results.Reserve(Traces->Num());
289
+ for (int32 Index = 0; Index < Traces->Num(); ++Index)
171
290
  {
172
- for (const TSharedPtr<FJsonValue>& V : *IgnoreArr)
291
+ const TSharedPtr<FJsonValue>& Entry = (*Traces)[Index];
292
+ const TSharedPtr<FJsonObject> Item = Entry.IsValid() ? Entry->AsObject() : nullptr;
293
+ if (!Item.IsValid())
173
294
  {
174
- FString Label;
175
- if (!V->TryGetString(Label)) continue;
176
- if (AActor* A = FindActorByLabel(World, Label)) Query.AddIgnoredActor(A);
295
+ Results.Add(MCPError(FString::Printf(TEXT("traces[%d] must be an object"), Index)));
296
+ continue;
177
297
  }
298
+ Results.Add(ExecuteLineTrace(World, Item));
178
299
  }
179
300
 
180
- FHitResult Hit;
181
- const bool bHit = World->LineTraceSingleByChannel(Hit, Start, End, Channel, Query);
182
-
183
301
  auto Result = MCPSuccess();
184
- Result->SetBoolField(TEXT("hit"), bHit);
185
- Result->SetObjectField(TEXT("start"), MCPVec3ToJsonObject(Start));
186
- Result->SetObjectField(TEXT("end"), MCPVec3ToJsonObject(End));
187
- // Report the collision semantics the result was produced under, so a caller
188
- // comparing against the game can see which one it got.
189
- Result->SetBoolField(TEXT("traceComplex"), bTraceComplex);
190
- Result->SetStringField(TEXT("channel"), ChannelName);
191
- if (bHit) EmitHitFields(Result, Hit);
302
+ Result->SetArrayField(TEXT("results"), Results);
303
+ Result->SetNumberField(TEXT("count"), Results.Num());
192
304
  return MCPResult(Result);
193
305
  }
194
306
 
195
307
 
196
308
  TSharedPtr<FJsonValue> FLevelHandlers::SnapActorToFloor(const TSharedPtr<FJsonObject>& Params)
197
309
  {
198
- REQUIRE_EDITOR_WORLD(World);
310
+ // #933: same defect as line_trace. The actor is looked up in this world and
311
+ // the downward trace runs in it, so both halves have to agree on which one.
312
+ TSharedPtr<FJsonValue> WorldError;
313
+ UWorld* World = ResolveTraceWorld(Params, WorldError);
314
+ if (!World) return WorldError;
199
315
  FString ActorLabel;
200
- if (auto Err = RequireString(Params, TEXT("actorLabel"), ActorLabel)) return Err;
316
+ if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
201
317
 
202
- AActor* Actor = FindActorByLabel(World, ActorLabel);
203
- if (!Actor) return MCPError(FString::Printf(TEXT("Actor not found: %s"), *ActorLabel));
318
+ TSharedPtr<FJsonValue> ActorErr;
319
+ AActor* Actor = MCPResolveActor(World, Params, ActorErr);
320
+ if (!Actor) return ActorErr;
321
+ ActorLabel = Actor->GetActorLabel();
204
322
 
205
323
  const double Offset = OptionalNumber(Params, TEXT("floorOffset"), 0.0);
206
324
  const double MaxDistance = OptionalNumber(Params, TEXT("maxDistance"), 100000.0);
@@ -231,6 +349,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SnapActorToFloor(const TSharedPtr<FJsonOb
231
349
  auto Result = MCPSuccess();
232
350
  MCPSetUpdated(Result);
233
351
  Result->SetStringField(TEXT("actorLabel"), ActorLabel);
352
+ Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
234
353
  Result->SetObjectField(TEXT("from"), MCPVec3ToJsonObject(PrevLoc));
235
354
  Result->SetObjectField(TEXT("to"), MCPVec3ToJsonObject(NewLoc));
236
355
  Result->SetObjectField(TEXT("impactPoint"), MCPVec3ToJsonObject(Hit.ImpactPoint));
@@ -239,6 +358,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SnapActorToFloor(const TSharedPtr<FJsonOb
239
358
 
240
359
  TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
241
360
  Payload->SetStringField(TEXT("actorLabel"), ActorLabel);
361
+ Payload->SetStringField(TEXT("actorPath"), Actor->GetPathName());
242
362
  TSharedPtr<FJsonObject> Loc = MakeShared<FJsonObject>();
243
363
  Loc->SetNumberField(TEXT("x"), PrevLoc.X);
244
364
  Loc->SetNumberField(TEXT("y"), PrevLoc.Y);
@@ -0,0 +1,337 @@
1
+ // Transient verification actors (#956).
2
+ //
3
+ // Verifying anything that only exists on a live actor (a GAS attribute set, a
4
+ // component's runtime state) needs a subject, and there was no way to make one
5
+ // without leaving it behind. Spawning a normal actor to check something and
6
+ // then forgetting to delete it is #966's exact shape: a capture action left a
7
+ // SceneCapture2D in the map and it got committed.
8
+ //
9
+ // So these actors are transient by construction rather than by discipline:
10
+ //
11
+ // RF_Transient means the package serializer skips them, so a save cannot
12
+ // write one into the map even if the caller never destroys it.
13
+ // bTemporaryEditorActor marks the actor as an editor preview actor, which
14
+ // keeps it out of the level's persistent actor list.
15
+ // bCreateActorPackage is off, so a World Partition map does not mint an
16
+ // external actor package for it, which is the artefact that would actually
17
+ // reach source control.
18
+ // The label is set with bMarkDirty=false, because SetActorLabel dirties the
19
+ // map by default and a verification spawn that dirties the map has already
20
+ // failed at its one job.
21
+ //
22
+ // Every one of them also carries a marker tag, so they can be listed and so
23
+ // destroy refuses anything it did not create.
24
+
25
+ #include "LevelHandlers.h"
26
+
27
+ #include "Components/ActorComponent.h"
28
+ #include "Dom/JsonObject.h"
29
+ #include "Dom/JsonValue.h"
30
+ #include "Editor.h"
31
+ #include "Engine/World.h"
32
+ #include "EngineUtils.h"
33
+ #include "GameFramework/Actor.h"
34
+ #include "HandlerEditorState.h"
35
+ #include "HandlerRegistry.h"
36
+ #include "HandlerUtils.h"
37
+
38
+ namespace
39
+ {
40
+ /** The marker every actor spawned by this action carries. Listing and
41
+ * destruction both key on it, so nothing else can be destroyed by
42
+ * accident and nothing spawned here can be missed. */
43
+ const TCHAR* const MCPTransientActorTag = TEXT("UEMCP_TransientVerification");
44
+
45
+ constexpr int32 MCPTransientMaxListed = 500;
46
+
47
+ TSharedPtr<FJsonObject> MCPDescribeTransientActor(AActor* Actor)
48
+ {
49
+ TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
50
+ Object->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
51
+ Object->SetStringField(TEXT("actorName"), Actor->GetName());
52
+ Object->SetStringField(TEXT("actorPath"), Actor->GetPathName());
53
+ Object->SetStringField(TEXT("actorClass"), Actor->GetClass()->GetPathName());
54
+ Object->SetBoolField(TEXT("transient"), Actor->HasAnyFlags(RF_Transient));
55
+ Object->SetObjectField(TEXT("location"), MCPVec3ToJsonObject(Actor->GetActorLocation()));
56
+ return Object;
57
+ }
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // spawn_transient_actor (#956)
62
+ // ---------------------------------------------------------------------------
63
+ TSharedPtr<FJsonValue> FLevelHandlers::SpawnTransientActor(const TSharedPtr<FJsonObject>& Params)
64
+ {
65
+ MCP_CHECK_GAME_THREAD();
66
+
67
+ const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
68
+ UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
69
+ if (!World)
70
+ {
71
+ return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
72
+ }
73
+
74
+ FString ActorClassSpec;
75
+ if (auto Err = RequireString(Params, TEXT("actorClass"), ActorClassSpec)) return Err;
76
+ UClass* ActorClass = MCPResolveClassOfType(ActorClassSpec, AActor::StaticClass(), true);
77
+ if (!ActorClass)
78
+ {
79
+ return MCPClassNotFoundError(ActorClassSpec, TEXT("actorClass"));
80
+ }
81
+ if (auto Err = MCPCheckClassUsable(ActorClassSpec, ActorClass, AActor::StaticClass()))
82
+ {
83
+ return Err;
84
+ }
85
+
86
+ const FVector Location = OptionalVec3(Params, TEXT("location"), FVector::ZeroVector);
87
+ const FRotator Rotation = OptionalRotator(Params, TEXT("rotation"), FRotator::ZeroRotator);
88
+ const FVector Scale = OptionalVec3(Params, TEXT("scale"), FVector::OneVector);
89
+ const FString Label = OptionalString(Params, TEXT("label"));
90
+ const bool bHideFromOutliner = OptionalBool(Params, TEXT("hideFromOutliner"), false);
91
+
92
+ // How far to take the actor towards a running state. The editor world has
93
+ // not begun play, so a spawned actor gets neither InitializeComponent nor
94
+ // BeginPlay, and anything that does its setup there (a GAS ability system
95
+ // component's default subobject scan, for instance) is not ready to be
96
+ // read. Each level is opt-in because each does more to the editor world.
97
+ const FString Initialize = OptionalString(Params, TEXT("initialize"), TEXT("construction")).ToLower();
98
+ if (Initialize != TEXT("none") && Initialize != TEXT("construction") && Initialize != TEXT("beginplay"))
99
+ {
100
+ return MCPError(TEXT("'initialize' must be 'none', 'construction' (default) or 'beginPlay'"));
101
+ }
102
+
103
+ TArray<FString> DirtyBefore;
104
+ MCPEditorState::CollectDirtyEditorPackageNames(DirtyBefore);
105
+
106
+ FActorSpawnParameters SpawnParameters;
107
+ SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
108
+ // The transiency contract, stated three ways so no single engine path can
109
+ // quietly persist this actor.
110
+ SpawnParameters.ObjectFlags |= RF_Transient;
111
+ #if WITH_EDITOR
112
+ SpawnParameters.bTemporaryEditorActor = true;
113
+ SpawnParameters.bHideFromSceneOutliner = bHideFromOutliner;
114
+ SpawnParameters.bCreateActorPackage = false;
115
+ #endif
116
+
117
+ AActor* Actor = World->SpawnActor<AActor>(
118
+ ActorClass, FTransform(Rotation, Location, Scale), SpawnParameters);
119
+ if (!Actor)
120
+ {
121
+ return MCPError(FString::Printf(
122
+ TEXT("Failed to spawn a transient %s"), *ActorClass->GetName()));
123
+ }
124
+
125
+ // Belt and braces: SpawnActor does not always propagate ObjectFlags to
126
+ // every path, and the transiency claim in the response has to be true.
127
+ Actor->SetFlags(RF_Transient);
128
+ Actor->Tags.AddUnique(FName(MCPTransientActorTag));
129
+ if (!Label.IsEmpty())
130
+ {
131
+ // bMarkDirty=false. The default marks the map package dirty, and a
132
+ // verification spawn that dirties the map has failed at its one job.
133
+ Actor->SetActorLabel(Label, /*bMarkDirty*/ false);
134
+ }
135
+
136
+ FString InitializeNote;
137
+ #if WITH_EDITOR
138
+ if (Initialize == TEXT("construction"))
139
+ {
140
+ Actor->RerunConstructionScripts();
141
+ InitializeNote = TEXT("Construction scripts ran. Components exist and their editable defaults are applied, but InitializeComponent and BeginPlay have NOT run, because the editor world has not begun play.");
142
+ }
143
+ else if (Initialize == TEXT("beginplay"))
144
+ {
145
+ Actor->RerunConstructionScripts();
146
+ Actor->RegisterAllComponents();
147
+ Actor->DispatchBeginPlay();
148
+ InitializeNote = TEXT("Components were registered and the begin-play cycle was dispatched on this actor, so InitializeComponent and BeginPlay ran. This is what a component that does its setup in BeginPlay needs before it can be read. It runs on this actor only, in a world that has not begun play, so anything depending on other actors or on world subsystems may still be absent.");
149
+ }
150
+ else
151
+ {
152
+ InitializeNote = TEXT("initialize='none': the actor exists with its default subobjects and nothing else has been run on it.");
153
+ }
154
+ #else
155
+ InitializeNote = TEXT("Non-editor build: no construction or begin-play cycle was run.");
156
+ #endif
157
+
158
+ TArray<TSharedPtr<FJsonValue>> Components;
159
+ for (UActorComponent* Component : Actor->GetComponents())
160
+ {
161
+ if (!Component) continue;
162
+ TSharedPtr<FJsonObject> ComponentObject = MakeShared<FJsonObject>();
163
+ ComponentObject->SetStringField(TEXT("name"), Component->GetName());
164
+ ComponentObject->SetStringField(TEXT("class"), Component->GetClass()->GetName());
165
+ ComponentObject->SetBoolField(TEXT("registered"), Component->IsRegistered());
166
+ // The read side of #956 needs this: a component whose setup happens in
167
+ // InitializeComponent is not ready until this is true.
168
+ ComponentObject->SetBoolField(TEXT("initialized"), Component->HasBeenInitialized());
169
+ Components.Add(MakeShared<FJsonValueObject>(ComponentObject));
170
+ }
171
+
172
+ auto Result = MCPSuccess();
173
+ MCPSetCreated(Result);
174
+ Result->SetObjectField(TEXT("actor"), MCPDescribeTransientActor(Actor));
175
+ Result->SetStringField(TEXT("initialize"), Initialize);
176
+ Result->SetStringField(TEXT("initializeNote"), InitializeNote);
177
+ Result->SetArrayField(TEXT("components"), Components);
178
+ Result->SetStringField(TEXT("cleanupNote"), FString::Printf(
179
+ TEXT("Destroy this with level(destroy_transient_actor, actorPath:'%s') when you are done. It is RF_Transient and a save cannot write it into the map, but it stays in the open world until it is destroyed or the map is reloaded."),
180
+ *Actor->GetPathName()));
181
+
182
+ // The #966-shaped guarantee, asserted rather than promised: if spawning
183
+ // this dirtied anything, the caller is told which package.
184
+ TArray<FString> DirtyAfter;
185
+ MCPEditorState::CollectDirtyEditorPackageNames(DirtyAfter);
186
+ for (const FString& Already : DirtyBefore)
187
+ {
188
+ DirtyAfter.Remove(Already);
189
+ }
190
+ Result->SetBoolField(TEXT("dirtiedPackages"), DirtyAfter.Num() > 0);
191
+ if (DirtyAfter.Num() > 0)
192
+ {
193
+ Result->SetArrayField(TEXT("dirtyPackages"), MCPStringListToJson(DirtyAfter));
194
+ Result->SetStringField(TEXT("dirtyWarning"),
195
+ TEXT("This spawn dirtied a package, which it is not supposed to do. Destroy the actor and do not save until you have checked what changed."));
196
+ }
197
+
198
+ TSharedPtr<FJsonObject> Rollback = MakeShared<FJsonObject>();
199
+ Rollback->SetStringField(TEXT("actorPath"), Actor->GetPathName());
200
+ MCPSetRollback(Result, TEXT("destroy_transient_actor"), Rollback);
201
+ return MCPResult(Result);
202
+ }
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // destroy_transient_actor (#956)
206
+ // ---------------------------------------------------------------------------
207
+ TSharedPtr<FJsonValue> FLevelHandlers::DestroyTransientActor(const TSharedPtr<FJsonObject>& Params)
208
+ {
209
+ MCP_CHECK_GAME_THREAD();
210
+
211
+ const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
212
+ UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
213
+ if (!World)
214
+ {
215
+ return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
216
+ }
217
+
218
+ const FString ActorPath = OptionalString(Params, TEXT("actorPath"));
219
+ const FString ActorLabel = OptionalString(Params, TEXT("actorLabel"));
220
+ const bool bAll = OptionalBool(Params, TEXT("all"), false);
221
+ if (ActorPath.IsEmpty() && ActorLabel.IsEmpty() && !bAll)
222
+ {
223
+ return MCPError(TEXT("Pass 'actorPath', 'actorLabel', or all=true to destroy every transient verification actor in this world"));
224
+ }
225
+
226
+ const FName MarkerTag(MCPTransientActorTag);
227
+ TArray<AActor*> Targets;
228
+ TArray<FString> RefusedNonTransient;
229
+
230
+ for (TActorIterator<AActor> It(World); It; ++It)
231
+ {
232
+ AActor* Actor = *It;
233
+ if (!Actor) continue;
234
+
235
+ bool bSelected = bAll;
236
+ if (!bSelected && !ActorPath.IsEmpty()) bSelected = Actor->GetPathName() == ActorPath;
237
+ if (!bSelected && !ActorLabel.IsEmpty()) bSelected = Actor->GetActorLabel() == ActorLabel;
238
+ if (!bSelected) continue;
239
+
240
+ // The guardrail. This action destroys things, and a label or a path is
241
+ // easy to get wrong, so it will only destroy an actor it created. A
242
+ // normal placed actor selected by mistake is refused by name rather
243
+ // than deleted.
244
+ if (!Actor->Tags.Contains(MarkerTag) || !Actor->HasAnyFlags(RF_Transient))
245
+ {
246
+ if (!bAll)
247
+ {
248
+ RefusedNonTransient.Add(FString::Printf(
249
+ TEXT("%s (%s)"), *Actor->GetActorLabel(), *Actor->GetClass()->GetName()));
250
+ }
251
+ continue;
252
+ }
253
+ Targets.Add(Actor);
254
+ }
255
+
256
+ auto Result = MCPSuccess();
257
+ if (!RefusedNonTransient.IsEmpty())
258
+ {
259
+ Result->SetBoolField(TEXT("success"), false);
260
+ Result->SetStringField(TEXT("error"),
261
+ TEXT("Refusing to destroy an actor this action did not create. Only actors spawned by level(spawn_transient_actor), which carry the UEMCP_TransientVerification tag and RF_Transient, can be destroyed here. Use level(delete_actor) or level(delete_actors) for a real level actor."));
262
+ Result->SetArrayField(TEXT("refused"), MCPStringListToJson(RefusedNonTransient));
263
+ return MCPResult(Result);
264
+ }
265
+
266
+ TArray<FString> Destroyed;
267
+ TArray<FString> Failed;
268
+ for (AActor* Actor : Targets)
269
+ {
270
+ const FString Description = FString::Printf(
271
+ TEXT("%s (%s)"), *Actor->GetActorLabel(), *Actor->GetClass()->GetName());
272
+ if (World->DestroyActor(Actor))
273
+ {
274
+ Destroyed.Add(Description);
275
+ }
276
+ else
277
+ {
278
+ Failed.Add(Description);
279
+ }
280
+ }
281
+
282
+ Result->SetBoolField(TEXT("success"), Failed.IsEmpty());
283
+ if (!Failed.IsEmpty())
284
+ {
285
+ Result->SetStringField(TEXT("error"), FString::Printf(
286
+ TEXT("%d transient actor(s) refused to be destroyed; see failed."), Failed.Num()));
287
+ }
288
+ Result->SetNumberField(TEXT("matched"), Targets.Num());
289
+ Result->SetNumberField(TEXT("destroyed"), Destroyed.Num());
290
+ Result->SetArrayField(TEXT("destroyedActors"), MCPStringListToJson(Destroyed));
291
+ Result->SetArrayField(TEXT("failed"), MCPStringListToJson(Failed));
292
+ if (Targets.IsEmpty())
293
+ {
294
+ Result->SetStringField(TEXT("zeroMatchNote"),
295
+ TEXT("Nothing matched. A transient verification actor does not survive a map reload or an editor restart, so it may simply be gone already. level(list_transient_actors) shows what is still there."));
296
+ }
297
+ return MCPResult(Result);
298
+ }
299
+
300
+ // ---------------------------------------------------------------------------
301
+ // list_transient_actors (#956)
302
+ // ---------------------------------------------------------------------------
303
+ TSharedPtr<FJsonValue> FLevelHandlers::ListTransientActors(const TSharedPtr<FJsonObject>& Params)
304
+ {
305
+ MCP_CHECK_GAME_THREAD();
306
+
307
+ const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
308
+ UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
309
+ if (!World)
310
+ {
311
+ return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
312
+ }
313
+
314
+ const FName MarkerTag(MCPTransientActorTag);
315
+ TArray<TSharedPtr<FJsonValue>> Actors;
316
+ int32 Total = 0;
317
+ for (TActorIterator<AActor> It(World); It; ++It)
318
+ {
319
+ AActor* Actor = *It;
320
+ if (!Actor || !Actor->Tags.Contains(MarkerTag)) continue;
321
+ ++Total;
322
+ if (Actors.Num() < MCPTransientMaxListed)
323
+ {
324
+ Actors.Add(MakeShared<FJsonValueObject>(MCPDescribeTransientActor(Actor)));
325
+ }
326
+ }
327
+
328
+ auto Result = MCPSuccess();
329
+ Result->SetStringField(TEXT("worldName"), World->GetName());
330
+ Result->SetNumberField(TEXT("total"), Total);
331
+ Result->SetNumberField(TEXT("returned"), Actors.Num());
332
+ Result->SetBoolField(TEXT("truncated"), Actors.Num() < Total);
333
+ Result->SetArrayField(TEXT("actors"), Actors);
334
+ Result->SetStringField(TEXT("note"),
335
+ TEXT("These are RF_Transient verification actors spawned by level(spawn_transient_actor). A save cannot write them into the map, and they do not survive a map reload, but they are in the open world until destroyed."));
336
+ return MCPResult(Result);
337
+ }