ue-mcp 1.2.3 → 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 (255) 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/index.js +41 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/lean-context.js +8 -3
  36. package/dist/lean-context.js.map +1 -1
  37. package/dist/locking.js +11 -2
  38. package/dist/locking.js.map +1 -1
  39. package/dist/plugin/loader.d.ts +4 -0
  40. package/dist/plugin/loader.js +22 -8
  41. package/dist/plugin/loader.js.map +1 -1
  42. package/dist/plugin/manifest.d.ts +72 -52
  43. package/dist/plugin/manifest.js +118 -21
  44. package/dist/plugin/manifest.js.map +1 -1
  45. package/dist/plugin-cli.js +16 -3
  46. package/dist/plugin-cli.js.map +1 -1
  47. package/dist/project.d.ts +11 -0
  48. package/dist/project.js +23 -2
  49. package/dist/project.js.map +1 -1
  50. package/dist/python-gate.d.ts +95 -0
  51. package/dist/python-gate.js +175 -0
  52. package/dist/python-gate.js.map +1 -0
  53. package/dist/tool-counts.json +16 -16
  54. package/dist/tools/animation.js +288 -24
  55. package/dist/tools/animation.js.map +1 -1
  56. package/dist/tools/asset.js +79 -14
  57. package/dist/tools/asset.js.map +1 -1
  58. package/dist/tools/audio.js +59 -5
  59. package/dist/tools/audio.js.map +1 -1
  60. package/dist/tools/blueprint.js +29 -12
  61. package/dist/tools/blueprint.js.map +1 -1
  62. package/dist/tools/chooser.js +4 -1
  63. package/dist/tools/chooser.js.map +1 -1
  64. package/dist/tools/editor.js +81 -43
  65. package/dist/tools/editor.js.map +1 -1
  66. package/dist/tools/feedback.js +214 -48
  67. package/dist/tools/feedback.js.map +1 -1
  68. package/dist/tools/foliage.js +27 -1
  69. package/dist/tools/foliage.js.map +1 -1
  70. package/dist/tools/gameplay.js +38 -11
  71. package/dist/tools/gameplay.js.map +1 -1
  72. package/dist/tools/gas.js +17 -5
  73. package/dist/tools/gas.js.map +1 -1
  74. package/dist/tools/landscape.js +22 -7
  75. package/dist/tools/landscape.js.map +1 -1
  76. package/dist/tools/level.js +322 -51
  77. package/dist/tools/level.js.map +1 -1
  78. package/dist/tools/material.js +10 -4
  79. package/dist/tools/material.js.map +1 -1
  80. package/dist/tools/niagara.js +3 -2
  81. package/dist/tools/niagara.js.map +1 -1
  82. package/dist/tools/pcg.js +6 -5
  83. package/dist/tools/pcg.js.map +1 -1
  84. package/dist/tools/plugins.js +2 -0
  85. package/dist/tools/plugins.js.map +1 -1
  86. package/dist/tools/project.js +47 -20
  87. package/dist/tools/project.js.map +1 -1
  88. package/dist/types.d.ts +24 -0
  89. package/dist/types.js +46 -3
  90. package/dist/types.js.map +1 -1
  91. package/dist/ue-mcp.default.yml +493 -125
  92. package/package.json +2 -2
  93. package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
  94. package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
  95. package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
  141. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
  142. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
  143. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
  144. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
  145. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
  146. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
  147. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
  148. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
  149. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
  150. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
  151. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
  152. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
  153. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
  154. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
  155. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
  156. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
  157. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
  158. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
  159. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
  160. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
  161. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
  162. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
  163. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
  164. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
  165. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
  166. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
  167. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
  168. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
  169. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
  170. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
  171. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
  172. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
  173. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
  174. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
  175. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
  176. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
  177. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
  178. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
  179. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
  180. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
  181. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
  182. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
  183. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
  184. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
  185. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
  186. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
  187. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
  188. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
  189. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
  190. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
  191. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
  192. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
  193. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
  194. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
  195. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
  196. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
  197. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
  198. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
  199. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
  200. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
  201. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
  202. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
  203. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
  204. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
  205. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
  206. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
  207. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
  208. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
  209. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
  210. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
  211. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
  212. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
  213. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
  214. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
  215. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
  216. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
  217. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
  218. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
  219. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
  220. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
  221. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
  222. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
  223. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
  224. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
  225. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
  226. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
  227. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
  228. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
  229. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
  230. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
  231. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
  232. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
  233. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
  234. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
  235. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
  236. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
  237. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
  238. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
  239. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
  240. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
  241. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
  242. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
  243. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
  244. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
  245. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
  246. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
  247. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
  248. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
  249. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  250. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
  251. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
  252. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
  253. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
  254. package/skills/ue-mcp-animation/SKILL.md +154 -0
  255. package/skills/ue-mcp-blueprint/SKILL.md +71 -0
@@ -0,0 +1,467 @@
1
+ #include "MassHandlers.h"
2
+
3
+ #include "HandlerAssetCreate.h"
4
+ #include "HandlerJsonProperty.h"
5
+ #include "HandlerRegistry.h"
6
+ #include "HandlerUtils.h"
7
+
8
+ #include "AssetRegistry/AssetRegistryModule.h"
9
+ #include "Editor.h"
10
+ #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
11
+ #include "Engine/DataAsset.h"
12
+ #include "Misc/PackageName.h"
13
+ #include "ScopedTransaction.h"
14
+ #include "Misc/Paths.h"
15
+ #include "UObject/UObjectGlobals.h"
16
+ #include "UObject/UnrealType.h"
17
+
18
+ namespace
19
+ {
20
+ static const TCHAR* ConfigClassPath = TEXT("/Script/MassSpawner.MassEntityConfigAsset");
21
+ static const TCHAR* TraitBaseClassPath = TEXT("/Script/MassSpawner.MassEntityTraitBase");
22
+
23
+ UClass* ResolveClass(const FString& ClassPath)
24
+ {
25
+ if (ClassPath.IsEmpty()) return nullptr;
26
+ UClass* Result = LoadClass<UObject>(nullptr, *ClassPath);
27
+ if (!Result) Result = LoadObject<UClass>(nullptr, *ClassPath);
28
+ if (!Result)
29
+ {
30
+ FString ShortName = ClassPath;
31
+ ShortName.RemoveFromEnd(TEXT("_C"));
32
+ for (TObjectIterator<UClass> It; It; ++It)
33
+ {
34
+ if (It->GetName() == ShortName || It->GetName() == FPackageName::GetShortName(ClassPath))
35
+ {
36
+ Result = *It;
37
+ break;
38
+ }
39
+ }
40
+ }
41
+ return Result;
42
+ }
43
+
44
+ bool SplitAssetPath(const TSharedPtr<FJsonObject>& Params, FString& OutName, FString& OutPackagePath)
45
+ {
46
+ FString AssetPath;
47
+ Params->TryGetStringField(TEXT("assetPath"), AssetPath);
48
+ if (AssetPath.IsEmpty())
49
+ {
50
+ if (!Params->TryGetStringField(TEXT("name"), OutName) || OutName.IsEmpty()) return false;
51
+ OutPackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game"));
52
+ return !OutPackagePath.IsEmpty();
53
+ }
54
+
55
+ FString PackageName = AssetPath;
56
+ if (AssetPath.Contains(TEXT(".")))
57
+ {
58
+ PackageName = AssetPath.LeftChop(AssetPath.Len() - AssetPath.Find(TEXT("."), ESearchCase::CaseSensitive, ESearchDir::FromEnd));
59
+ }
60
+ OutName = FPackageName::GetShortName(PackageName);
61
+ OutPackagePath = FPaths::GetPath(PackageName);
62
+ return !OutName.IsEmpty() && !OutPackagePath.IsEmpty();
63
+ }
64
+
65
+ UObject* LoadConfigAsset(const FString& Name, const FString& PackagePath)
66
+ {
67
+ return LoadObject<UObject>(nullptr, *(PackagePath + TEXT("/") + Name + TEXT(".") + Name));
68
+ }
69
+
70
+ FProperty* FindTraitsProperty(UObject* ConfigAsset, void*& OutTraitsAddress)
71
+ {
72
+ OutTraitsAddress = nullptr;
73
+ if (!ConfigAsset) return nullptr;
74
+ FStructProperty* ConfigProperty = CastField<FStructProperty>(ConfigAsset->GetClass()->FindPropertyByName(TEXT("Config")));
75
+ if (!ConfigProperty) return nullptr;
76
+ void* ConfigAddress = ConfigProperty->ContainerPtrToValuePtr<void>(ConfigAsset);
77
+ FArrayProperty* TraitsProperty = CastField<FArrayProperty>(ConfigProperty->Struct->FindPropertyByName(TEXT("Traits")));
78
+ if (!TraitsProperty) return nullptr;
79
+ OutTraitsAddress = TraitsProperty->ContainerPtrToValuePtr<void>(ConfigAddress);
80
+ return TraitsProperty;
81
+ }
82
+
83
+ bool ReadTraitClasses(UObject* ConfigAsset, TArray<UClass*>& OutClasses, FString& OutError)
84
+ {
85
+ void* TraitsAddress = nullptr;
86
+ FArrayProperty* TraitsProperty = CastField<FArrayProperty>(FindTraitsProperty(ConfigAsset, TraitsAddress));
87
+ if (!TraitsProperty) { OutError = TEXT("Mass entity config does not expose Config.Traits"); return false; }
88
+ FObjectProperty* TraitObjectProperty = CastField<FObjectProperty>(TraitsProperty->Inner);
89
+ if (!TraitObjectProperty) { OutError = TEXT("Mass entity config Traits is not an object array"); return false; }
90
+
91
+ FScriptArrayHelper Helper(TraitsProperty, TraitsAddress);
92
+ OutClasses.Reserve(Helper.Num());
93
+ for (int32 Index = 0; Index < Helper.Num(); ++Index)
94
+ {
95
+ UObject* Trait = TraitObjectProperty->GetObjectPropertyValue(Helper.GetRawPtr(Index));
96
+ if (!Trait) { OutError = FString::Printf(TEXT("Config.Traits[%d] is null"), Index); return false; }
97
+ OutClasses.Add(Trait->GetClass());
98
+ }
99
+ return true;
100
+ }
101
+
102
+ bool AddTrait(UObject* ConfigAsset, UClass* TraitClass, UObject*& OutTrait, FString& OutError)
103
+ {
104
+ OutTrait = nullptr;
105
+ void* TraitsAddress = nullptr;
106
+ FArrayProperty* TraitsProperty = CastField<FArrayProperty>(FindTraitsProperty(ConfigAsset, TraitsAddress));
107
+ if (!TraitsProperty) { OutError = TEXT("Mass entity config does not expose Config.Traits"); return false; }
108
+ FObjectProperty* TraitObjectProperty = CastField<FObjectProperty>(TraitsProperty->Inner);
109
+ if (!TraitObjectProperty) { OutError = TEXT("Mass entity config Traits is not an object array"); return false; }
110
+
111
+ const FName TraitName = MakeUniqueObjectName(ConfigAsset, TraitClass, TraitClass->GetFName());
112
+ UObject* NewTrait = NewObject<UObject>(ConfigAsset, TraitClass, TraitName, RF_Transactional);
113
+ if (!NewTrait) { OutError = FString::Printf(TEXT("Failed to construct trait %s"), *TraitClass->GetPathName()); return false; }
114
+
115
+ FScriptArrayHelper Helper(TraitsProperty, TraitsAddress);
116
+ const int32 NewIndex = Helper.AddValue();
117
+ TraitObjectProperty->SetObjectPropertyValue(Helper.GetRawPtr(NewIndex), NewTrait);
118
+ OutTrait = NewTrait;
119
+ return true;
120
+ }
121
+
122
+ UObject* GetTraitAtIndex(UObject* ConfigAsset, int32 Index)
123
+ {
124
+ void* TraitsAddress = nullptr;
125
+ FArrayProperty* TraitsProperty = CastField<FArrayProperty>(FindTraitsProperty(ConfigAsset, TraitsAddress));
126
+ FObjectProperty* TraitObjectProperty = TraitsProperty ? CastField<FObjectProperty>(TraitsProperty->Inner) : nullptr;
127
+ if (!TraitsProperty || !TraitObjectProperty || Index < 0) return nullptr;
128
+ FScriptArrayHelper Helper(TraitsProperty, TraitsAddress);
129
+ return Index < Helper.Num() ? TraitObjectProperty->GetObjectPropertyValue(Helper.GetRawPtr(Index)) : nullptr;
130
+ }
131
+
132
+ TSharedPtr<FJsonValue> ValidateAndApplyProperties(UObject* Trait, const TSharedPtr<FJsonObject>& Properties, int32 TraitIndex, int32& OutSet)
133
+ {
134
+ if (!Properties.IsValid()) return nullptr;
135
+ for (const auto& Pair : Properties->Values)
136
+ {
137
+ // UE 5.8 stores JSON object keys in shared string storage; copy the
138
+ // key before passing it to the FString-based property-path helper.
139
+ const FString PropertyName(Pair.Key);
140
+ FString Error;
141
+ if (!MCPJsonProperty::SetDottedPropertyFromJson(Trait, PropertyName, Pair.Value, Error))
142
+ {
143
+ return MCPError(FString::Printf(TEXT("Trait %d property '%s' failed: %s"), TraitIndex, *PropertyName, *Error));
144
+ }
145
+ ++OutSet;
146
+ }
147
+ return nullptr;
148
+ }
149
+
150
+ TSharedPtr<FJsonValue> MakeExistingResult(UObject* Asset, const TArray<UClass*>& ExistingClasses, bool bUpdated, int32 PropertiesSet)
151
+ {
152
+ TSharedPtr<FJsonObject> Result = MCPSuccess();
153
+ MCPSetExisted(Result);
154
+ if (bUpdated) MCPSetUpdated(Result);
155
+ Result->SetStringField(TEXT("assetPath"), Asset->GetPathName());
156
+ Result->SetNumberField(TEXT("traits"), ExistingClasses.Num());
157
+ Result->SetNumberField(TEXT("propertiesSet"), PropertiesSet);
158
+ return MCPResult(Result);
159
+ }
160
+
161
+ void ReleasePreviews(TArray<UObject*>& Previews)
162
+ {
163
+ for (UObject* Preview : Previews)
164
+ {
165
+ if (Preview) Preview->RemoveFromRoot();
166
+ }
167
+ Previews.Reset();
168
+ }
169
+
170
+ FString ExportTraitProperty(const FProperty* Property, const UObject* Trait)
171
+ {
172
+ if (!Property || !Trait) return TEXT("");
173
+ if (const FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Property))
174
+ {
175
+ if (UObject* Value = ObjectProperty->GetObjectPropertyValue_InContainer(Trait))
176
+ {
177
+ return Value->GetPathName();
178
+ }
179
+ return TEXT("None");
180
+ }
181
+ if (const FSoftObjectProperty* SoftObjectProperty = CastField<FSoftObjectProperty>(Property))
182
+ {
183
+ return SoftObjectProperty->GetPropertyValue_InContainer(Trait).ToSoftObjectPath().ToString();
184
+ }
185
+
186
+ FString Exported;
187
+ const void* ValueAddress = Property->ContainerPtrToValuePtr<void>(Trait);
188
+ Property->ExportTextItem_Direct(Exported, ValueAddress, nullptr, nullptr, PPF_None);
189
+ return Exported;
190
+ }
191
+ }
192
+ void FMassHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
193
+ {
194
+ Registry.RegisterHandler(TEXT("ensure_mass_entity_config"), &EnsureEntityConfig);
195
+ Registry.RegisterHandler(TEXT("read_mass_entity_config"), &ReadEntityConfig);
196
+ }
197
+
198
+ TSharedPtr<FJsonValue> FMassHandlers::ReadEntityConfig(const TSharedPtr<FJsonObject>& Params)
199
+ {
200
+ FString AssetPath;
201
+ if (auto Err = RequireString(Params, TEXT("assetPath"), AssetPath)) return Err;
202
+
203
+ UClass* ConfigClass = ResolveClass(ConfigClassPath);
204
+ UClass* TraitBaseClass = ResolveClass(TraitBaseClassPath);
205
+ if (!ConfigClass || !TraitBaseClass)
206
+ {
207
+ return MCPError(TEXT("MassSpawner is unavailable; enable MassGameplay/MassSpawner before using read_mass_entity_config"));
208
+ }
209
+
210
+ UObject* Asset = LoadAssetByPath<UObject>(AssetPath);
211
+ if (!Asset) return MCPError(FString::Printf(TEXT("Mass entity config not found: %s"), *AssetPath));
212
+ if (!Asset->IsA(ConfigClass))
213
+ {
214
+ return MCPError(FString::Printf(TEXT("Asset is not a MassEntityConfigAsset: %s"), *Asset->GetPathName()));
215
+ }
216
+
217
+ TArray<UClass*> TraitClasses;
218
+ FString Error;
219
+ if (!ReadTraitClasses(Asset, TraitClasses, Error)) return MCPError(Error);
220
+
221
+ auto Result = MCPSuccess();
222
+ Result->SetStringField(TEXT("assetPath"), Asset->GetPathName());
223
+ Result->SetStringField(TEXT("assetClass"), Asset->GetClass()->GetPathName());
224
+
225
+ TArray<TSharedPtr<FJsonValue>> TraitClassNames;
226
+ TArray<TSharedPtr<FJsonValue>> TraitRecords;
227
+ for (int32 Index = 0; Index < TraitClasses.Num(); ++Index)
228
+ {
229
+ UObject* Trait = GetTraitAtIndex(Asset, Index);
230
+ if (!Trait || !Trait->GetClass()->IsChildOf(TraitBaseClass))
231
+ {
232
+ return MCPError(FString::Printf(TEXT("Config.Traits[%d] is not a UMassEntityTraitBase"), Index));
233
+ }
234
+
235
+ const FString ClassPath = Trait->GetClass()->GetPathName();
236
+ TraitClassNames.Add(MakeShared<FJsonValueString>(ClassPath));
237
+ auto Record = MakeShared<FJsonObject>();
238
+ Record->SetNumberField(TEXT("index"), Index);
239
+ Record->SetStringField(TEXT("classPath"), ClassPath);
240
+ Record->SetStringField(TEXT("className"), Trait->GetClass()->GetName());
241
+
242
+ auto Properties = MakeShared<FJsonObject>();
243
+ for (TFieldIterator<FProperty> PropertyIt(Trait->GetClass()); PropertyIt; ++PropertyIt)
244
+ {
245
+ const FProperty* Property = *PropertyIt;
246
+ if (!Property || Property->HasAnyPropertyFlags(CPF_Transient)) continue;
247
+ Properties->SetStringField(Property->GetName(), ExportTraitProperty(Property, Trait));
248
+ }
249
+ Record->SetObjectField(TEXT("properties"), Properties);
250
+ TraitRecords.Add(MakeShared<FJsonValueObject>(Record));
251
+ }
252
+ Result->SetArrayField(TEXT("traitClasses"), TraitClassNames);
253
+ Result->SetArrayField(TEXT("traits"), TraitRecords);
254
+ Result->SetNumberField(TEXT("traitCount"), TraitClasses.Num());
255
+ return MCPResult(Result);
256
+ }
257
+
258
+ TSharedPtr<FJsonValue> FMassHandlers::EnsureEntityConfig(const TSharedPtr<FJsonObject>& Params)
259
+ {
260
+ FString Name;
261
+ FString PackagePath;
262
+ if (!SplitAssetPath(Params, Name, PackagePath))
263
+ {
264
+ return MCPError(TEXT("Provide assetPath (/Game/Folder/Name[.Name]) or name plus packagePath"));
265
+ }
266
+ if (!FPackageName::IsValidLongPackageName(PackagePath))
267
+ {
268
+ return MCPError(FString::Printf(TEXT("Invalid package path: %s"), *PackagePath));
269
+ }
270
+
271
+ UClass* ConfigClass = ResolveClass(ConfigClassPath);
272
+ UClass* TraitBaseClass = ResolveClass(TraitBaseClassPath);
273
+ if (!ConfigClass || !TraitBaseClass)
274
+ {
275
+ return MCPError(TEXT("MassSpawner is unavailable; enable MassGameplay/MassSpawner before using ensure_mass_entity_config"));
276
+ }
277
+ if (!ConfigClass->IsChildOf(UDataAsset::StaticClass()) || !TraitBaseClass->IsChildOf(UObject::StaticClass()))
278
+ {
279
+ return MCPError(TEXT("MassSpawner classes are incompatible with this engine version"));
280
+ }
281
+
282
+ const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("update")).ToLower();
283
+ if (OnConflict != TEXT("skip") && OnConflict != TEXT("error") && OnConflict != TEXT("update"))
284
+ {
285
+ return MCPError(TEXT("onConflict must be one of: skip, error, update"));
286
+ }
287
+
288
+ const TSharedPtr<FJsonValue>* TraitsValue = Params->Values.Find(TEXT("traits"));
289
+ const TArray<TSharedPtr<FJsonValue>>* TraitArray = nullptr;
290
+ if (!TraitsValue || !(*TraitsValue).IsValid() || !(*TraitsValue)->TryGetArray(TraitArray) || !TraitArray)
291
+ {
292
+ return MCPError(TEXT("Missing required 'traits' array"));
293
+ }
294
+
295
+ struct FRequestedTrait { UClass* Class = nullptr; const TSharedPtr<FJsonObject>* Properties = nullptr; };
296
+ TArray<FRequestedTrait> Requested;
297
+ TSet<UClass*> SeenClasses;
298
+ for (int32 Index = 0; Index < TraitArray->Num(); ++Index)
299
+ {
300
+ const TSharedPtr<FJsonObject>* TraitObject = nullptr;
301
+ if (!(*TraitArray)[Index].IsValid() || !(*TraitArray)[Index]->TryGetObject(TraitObject) || !TraitObject || !(*TraitObject).IsValid())
302
+ {
303
+ return MCPError(FString::Printf(TEXT("traits[%d] must be an object"), Index));
304
+ }
305
+ FString ClassPath;
306
+ if (!(*TraitObject)->TryGetStringField(TEXT("class"), ClassPath) && !(*TraitObject)->TryGetStringField(TEXT("traitClass"), ClassPath))
307
+ {
308
+ return MCPError(FString::Printf(TEXT("traits[%d] requires class or traitClass"), Index));
309
+ }
310
+ UClass* TraitClass = ResolveClass(ClassPath);
311
+ if (!TraitClass || !TraitClass->IsChildOf(TraitBaseClass) || TraitClass->HasAnyClassFlags(CLASS_Abstract))
312
+ {
313
+ return MCPError(FString::Printf(TEXT("traits[%d] is not a concrete UMassEntityTraitBase: %s"), Index, *ClassPath));
314
+ }
315
+ if (SeenClasses.Contains(TraitClass))
316
+ {
317
+ return MCPError(FString::Printf(TEXT("traits[%d] duplicates trait class %s; ordered traits must be unique"), Index, *ClassPath));
318
+ }
319
+ SeenClasses.Add(TraitClass);
320
+ FRequestedTrait& Request = Requested.AddDefaulted_GetRef();
321
+ Request.Class = TraitClass;
322
+ (*TraitObject)->TryGetObjectField(TEXT("properties"), Request.Properties);
323
+ }
324
+
325
+ // Validate every property against a transient trait before loading or
326
+ // creating the real config. This is deliberately done for the whole list,
327
+ // not while appending traits, so a bad property late in the request cannot
328
+ // leave a partially authored asset behind.
329
+ TArray<UObject*> Previews;
330
+ Previews.Reserve(Requested.Num());
331
+ for (int32 Index = 0; Index < Requested.Num(); ++Index)
332
+ {
333
+ const FName PreviewName = MakeUniqueObjectName(GetTransientPackage(), Requested[Index].Class, Requested[Index].Class->GetFName());
334
+ UObject* Preview = NewObject<UObject>(GetTransientPackage(), Requested[Index].Class, PreviewName, RF_Transient);
335
+ if (!Preview)
336
+ {
337
+ ReleasePreviews(Previews);
338
+ return MCPError(FString::Printf(TEXT("Failed to construct transient preview for trait %d (%s)"), Index, *Requested[Index].Class->GetPathName()));
339
+ }
340
+ Preview->AddToRoot();
341
+ Previews.Add(Preview);
342
+ int32 IgnoredProperties = 0;
343
+ if (TSharedPtr<FJsonValue> Error = ValidateAndApplyProperties(Preview, Requested[Index].Properties ? *Requested[Index].Properties : nullptr, Index, IgnoredProperties))
344
+ {
345
+ ReleasePreviews(Previews);
346
+ return Error;
347
+ }
348
+ }
349
+ ReleasePreviews(Previews);
350
+
351
+ UObject* ExistingAsset = LoadConfigAsset(Name, PackagePath);
352
+ if (ExistingAsset && !ExistingAsset->IsA(ConfigClass))
353
+ {
354
+ return MCPError(FString::Printf(TEXT("Asset exists but is not a MassEntityConfigAsset: %s"), *ExistingAsset->GetPathName()));
355
+ }
356
+ if (ExistingAsset && OnConflict == TEXT("error"))
357
+ {
358
+ return MCPError(FString::Printf(TEXT("MassEntityConfigAsset '%s' already exists"), *ExistingAsset->GetPathName()));
359
+ }
360
+ if (ExistingAsset && OnConflict == TEXT("skip"))
361
+ {
362
+ TArray<UClass*> ExistingClasses; FString Error;
363
+ if (!ReadTraitClasses(ExistingAsset, ExistingClasses, Error)) return MCPError(Error);
364
+ return MakeExistingResult(ExistingAsset, ExistingClasses, false, 0);
365
+ }
366
+
367
+ bool bCreated = false;
368
+ if (!ExistingAsset)
369
+ {
370
+ auto Created = MCPCreateAssetIdempotent<UObject>(Name, PackagePath, TEXT("error"), TEXT("MassEntityConfigAsset"), ConfigClass, nullptr);
371
+ if (Created.EarlyReturn) return Created.EarlyReturn;
372
+ ExistingAsset = Created.Asset;
373
+ bCreated = true;
374
+ }
375
+
376
+ TArray<UClass*> ExistingClasses;
377
+ FString ReadError;
378
+ if (!ReadTraitClasses(ExistingAsset, ExistingClasses, ReadError)) return MCPError(ReadError);
379
+ if (ExistingClasses.Num() > Requested.Num())
380
+ {
381
+ return MCPError(TEXT("Existing config has more traits than requested; refusing destructive replacement"));
382
+ }
383
+ for (int32 Index = 0; Index < ExistingClasses.Num(); ++Index)
384
+ {
385
+ if (ExistingClasses[Index] != Requested[Index].Class)
386
+ {
387
+ return MCPError(FString::Printf(TEXT("Existing trait order conflicts at index %d; refusing destructive replacement"), Index));
388
+ }
389
+ }
390
+
391
+ // For an update, duplicate the existing instanced traits into the transient
392
+ // package as a second preflight. This catches property paths that are valid
393
+ // on the class but invalid for the currently-authored trait instance before
394
+ // Modify() or any real property write is issued.
395
+ if (!bCreated)
396
+ {
397
+ Previews.Reset();
398
+ for (int32 Index = 0; Index < Requested.Num(); ++Index)
399
+ {
400
+ UObject* SourceTrait = Index < ExistingClasses.Num() ? GetTraitAtIndex(ExistingAsset, Index) : nullptr;
401
+ UObject* Preview = SourceTrait
402
+ ? StaticDuplicateObject(SourceTrait, GetTransientPackage(), NAME_None, RF_Transient)
403
+ : NewObject<UObject>(GetTransientPackage(), Requested[Index].Class,
404
+ MakeUniqueObjectName(GetTransientPackage(), Requested[Index].Class, Requested[Index].Class->GetFName()), RF_Transient);
405
+ if (!Preview)
406
+ {
407
+ ReleasePreviews(Previews);
408
+ return MCPError(FString::Printf(TEXT("Failed to construct transient update preview for trait %d"), Index));
409
+ }
410
+ Preview->AddToRoot();
411
+ Previews.Add(Preview);
412
+ int32 IgnoredProperties = 0;
413
+ if (TSharedPtr<FJsonValue> Error = ValidateAndApplyProperties(Preview, Requested[Index].Properties ? *Requested[Index].Properties : nullptr, Index, IgnoredProperties))
414
+ {
415
+ ReleasePreviews(Previews);
416
+ return Error;
417
+ }
418
+ }
419
+ ReleasePreviews(Previews);
420
+ }
421
+
422
+ // AddTrait appends to the live Traits array and constructs an
423
+ // RF_Transactional subobject, so a failure partway through the loop used to
424
+ // leave traits 0..N-1 on the asset and return an error. Modify() alone did
425
+ // not help: with no FScopedTransaction open there is no transaction to
426
+ // record into, so undo could not recover it either, despite the comment
427
+ // that used to sit here claiming otherwise.
428
+ //
429
+ // Cancelling the transaction on any failure is what makes this all or
430
+ // nothing. The preflights above validate against a duplicated instance, so
431
+ // a property that only fails on the real object still reaches this loop.
432
+ const bool bShouldActuallyTransact = GEditor != nullptr;
433
+ FScopedTransaction Transaction(
434
+ NSLOCTEXT("UEMCP", "EnsureMassEntityConfig", "Author Mass Entity Config Traits"),
435
+ bShouldActuallyTransact);
436
+ ExistingAsset->Modify();
437
+ int32 PropertiesSet = 0;
438
+ for (int32 Index = 0; Index < Requested.Num(); ++Index)
439
+ {
440
+ UObject* Trait = nullptr;
441
+ if (Index < ExistingClasses.Num()) Trait = GetTraitAtIndex(ExistingAsset, Index);
442
+ else if (!AddTrait(ExistingAsset, Requested[Index].Class, Trait, ReadError))
443
+ {
444
+ Transaction.Cancel();
445
+ return MCPError(ReadError);
446
+ }
447
+ if (TSharedPtr<FJsonValue> Error = ValidateAndApplyProperties(Trait, Requested[Index].Properties ? *Requested[Index].Properties : nullptr, Index, PropertiesSet))
448
+ {
449
+ Transaction.Cancel();
450
+ return Error;
451
+ }
452
+ }
453
+
454
+ ExistingAsset->MarkPackageDirty();
455
+ if (!SaveAssetPackage(ExistingAsset))
456
+ {
457
+ return MCPError(FString::Printf(TEXT("Failed to save MassEntityConfigAsset: %s"), *ExistingAsset->GetPathName()));
458
+ }
459
+
460
+ TSharedPtr<FJsonObject> Result = MCPSuccess();
461
+ if (bCreated) MCPSetCreated(Result); else { MCPSetExisted(Result); MCPSetUpdated(Result); }
462
+ Result->SetStringField(TEXT("assetPath"), ExistingAsset->GetPathName());
463
+ Result->SetNumberField(TEXT("traits"), Requested.Num());
464
+ Result->SetNumberField(TEXT("propertiesSet"), PropertiesSet);
465
+ if (bCreated) MCPSetDeleteAssetRollback(Result, ExistingAsset->GetPathName());
466
+ return MCPResult(Result);
467
+ }
@@ -0,0 +1,22 @@
1
+ #pragma once
2
+
3
+ #include "CoreMinimal.h"
4
+ #include "Dom/JsonValue.h"
5
+
6
+ /**
7
+ * Editor-only authoring helpers for Mass entity configuration assets.
8
+ *
9
+ * The implementation intentionally resolves Mass classes by reflection. This
10
+ * keeps the bridge loadable on engine versions/projects where Mass is not
11
+ * enabled while still providing a strongly validated path when MassSpawner is
12
+ * present.
13
+ */
14
+ class FMassHandlers
15
+ {
16
+ public:
17
+ static void RegisterHandlers(class FMCPHandlerRegistry& Registry);
18
+
19
+ private:
20
+ static TSharedPtr<FJsonValue> EnsureEntityConfig(const TSharedPtr<FJsonObject>& Params);
21
+ static TSharedPtr<FJsonValue> ReadEntityConfig(const TSharedPtr<FJsonObject>& Params);
22
+ };