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
@@ -73,6 +73,8 @@ void FGasHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
73
73
  Registry.RegisterHandler(TEXT("get_attribute"), &GetAttribute);
74
74
  Registry.RegisterHandler(TEXT("init_asc"), &InitAsc);
75
75
  Registry.RegisterHandler(TEXT("get_asc_state"), &GetAscState);
76
+ Registry.RegisterHandler(TEXT("get_live_attribute_value"), &GetLiveAttributeValue);
77
+ Registry.RegisterHandler(TEXT("set_live_attribute_value"), &SetLiveAttributeValue);
76
78
  }
77
79
 
78
80
  TSharedPtr<FJsonValue> FGasHandlers::CreateGasBlueprint(
@@ -5,6 +5,54 @@
5
5
  #include "Dom/JsonObject.h"
6
6
  #include "Templates/Function.h"
7
7
 
8
+ class AActor;
9
+ class UAbilitySystemComponent;
10
+ class UClass;
11
+
12
+ /**
13
+ * Attribute set registration on a live AbilitySystemComponent (#956).
14
+ *
15
+ * Declared here rather than kept file-local so the automation tests can pin
16
+ * the one invariant the live-attribute actions stand on, and so there is
17
+ * exactly one copy: the module is a unity build and a second file-local copy
18
+ * of a helper is a redefinition on some grouping.
19
+ */
20
+ namespace MCPGas
21
+ {
22
+
23
+ /**
24
+ * Register the actor's own UAttributeSet subobjects on its ASC, the way
25
+ * UAbilitySystemComponent::InitializeComponent does at BeginPlay.
26
+ *
27
+ * This exists because an actor spawned into the pure editor world never runs
28
+ * that scan: there is no BeginPlay and no InitializeComponent in a world that
29
+ * has not begun play. The ASC therefore has ZERO registered sets while the
30
+ * actor plainly owns one, and the obvious fallback at that point ("the ASC has
31
+ * none, so construct one") produces a SECOND, disconnected instance that no
32
+ * gameplay code will ever consult. Adopting the actor's existing subobject is
33
+ * the only answer that leaves ASC->GetSet<T>() pointing at the real one.
34
+ *
35
+ * Nested subobjects are included, unlike the engine's own scan, so a set
36
+ * created as a subobject of the ASC rather than of the actor is found too.
37
+ * Idempotent. Returns how many were newly registered.
38
+ */
39
+ int32 AdoptOwnerAttributeSets(
40
+ UAbilitySystemComponent* ASC,
41
+ AActor* Actor,
42
+ TArray<FString>& OutAdoptedClassNames);
43
+
44
+ /**
45
+ * The FGameplayAttributeData property named on one attribute set class.
46
+ * Accepts a bare property name ("Mana") and the qualified spellings
47
+ * ("MySet.Mana" / "MySet:Mana") the other GAS actions take. Null on a miss.
48
+ */
49
+ FStructProperty* FindAttributeDataProperty(UClass* SetClass, const FString& Name);
50
+
51
+ /** Every FGameplayAttributeData property name on a set class, comma separated. */
52
+ FString ListAttributeDataPropertyNames(UClass* SetClass);
53
+
54
+ }
55
+
8
56
  class FGasHandlers
9
57
  {
10
58
  public:
@@ -48,4 +96,16 @@ private:
48
96
  // #587: introspect a live ASC - granted ability specs (class, level, input,
49
97
  // active, dynamic tags) + owned gameplay tags.
50
98
  static TSharedPtr<FJsonValue> GetAscState(const TSharedPtr<FJsonObject>& Params);
99
+
100
+ // #956: read and write the CURRENT value of one FGameplayAttributeData on
101
+ // the attribute set instance actually REGISTERED on a live actor's ASC.
102
+ // get_attribute / set_attribute above only see sets the ASC already knows
103
+ // about, and an actor spawned into the editor world has none, because the
104
+ // DSO scan that registers them runs in InitializeComponent and a world that
105
+ // has not begun play never gets there. These two name the set explicitly
106
+ // and reach the registered instance, adopting the actor's own subobject
107
+ // when the ASC has not registered it yet. Implemented in
108
+ // GasHandlers_Runtime.cpp.
109
+ static TSharedPtr<FJsonValue> GetLiveAttributeValue(const TSharedPtr<FJsonObject>& Params);
110
+ static TSharedPtr<FJsonValue> SetLiveAttributeValue(const TSharedPtr<FJsonObject>& Params);
51
111
  };
@@ -16,6 +16,75 @@
16
16
  #include "GameplayEffect.h"
17
17
  #include "GameplayEffectTypes.h"
18
18
  #include "GameplayTagContainer.h"
19
+ #include "UObject/UObjectHash.h"
20
+ #include "UObject/UnrealType.h"
21
+
22
+ namespace MCPGas
23
+ {
24
+
25
+ int32 AdoptOwnerAttributeSets(
26
+ UAbilitySystemComponent* ASC,
27
+ AActor* Actor,
28
+ TArray<FString>& OutAdoptedClassNames)
29
+ {
30
+ if (!ASC || !Actor) return 0;
31
+
32
+ TArray<UObject*> Subobjects;
33
+ GetObjectsWithOuter(Actor, Subobjects);
34
+
35
+ int32 Adopted = 0;
36
+ for (UObject* Object : Subobjects)
37
+ {
38
+ UAttributeSet* Set = Cast<UAttributeSet>(Object);
39
+ if (!IsValid(Set)) continue;
40
+ // Already registered: leave the existing entry alone. Re-adding would
41
+ // not duplicate it, but skipping keeps the "newly registered" count
42
+ // honest, and that count is what the result reports.
43
+ if (ASC->GetSpawnedAttributes().Contains(Set)) continue;
44
+ ASC->AddSpawnedAttribute(Set);
45
+ OutAdoptedClassNames.Add(Set->GetClass()->GetName());
46
+ ++Adopted;
47
+ }
48
+ return Adopted;
49
+ }
50
+
51
+ FStructProperty* FindAttributeDataProperty(UClass* SetClass, const FString& Name)
52
+ {
53
+ if (!SetClass) return nullptr;
54
+ const FString SetName = SetClass->GetName();
55
+ for (TFieldIterator<FProperty> It(SetClass); It; ++It)
56
+ {
57
+ FStructProperty* SProp = CastField<FStructProperty>(*It);
58
+ if (!SProp || SProp->Struct != FGameplayAttributeData::StaticStruct()) continue;
59
+ const FString PropName = SProp->GetName();
60
+ if (PropName == Name
61
+ || (SetName + TEXT(".") + PropName) == Name
62
+ || (SetName + TEXT(":") + PropName) == Name)
63
+ {
64
+ return SProp;
65
+ }
66
+ }
67
+ return nullptr;
68
+ }
69
+
70
+ FString ListAttributeDataPropertyNames(UClass* SetClass)
71
+ {
72
+ TArray<FString> Names;
73
+ if (SetClass)
74
+ {
75
+ for (TFieldIterator<FProperty> It(SetClass); It; ++It)
76
+ {
77
+ FStructProperty* SProp = CastField<FStructProperty>(*It);
78
+ if (SProp && SProp->Struct == FGameplayAttributeData::StaticStruct())
79
+ {
80
+ Names.Add(SProp->GetName());
81
+ }
82
+ }
83
+ }
84
+ return Names.Num() > 0 ? FString::Join(Names, TEXT(", ")) : FString(TEXT("(none)"));
85
+ }
86
+
87
+ }
19
88
 
20
89
  namespace
21
90
  {
@@ -26,7 +95,7 @@ namespace
26
95
  return ResolveWorldScope(OptionalString(Params, TEXT("world"), TEXT("auto")));
27
96
  }
28
97
 
29
- // Find the actor (by label or name) in the resolved world and return its
98
+ // Find the actor in the resolved world and return its
30
99
  // AbilitySystemComponent. On any failure writes a structured error to
31
100
  // OutError and returns nullptr.
32
101
  UAbilitySystemComponent* ResolveASC(
@@ -35,7 +104,7 @@ namespace
35
104
  TSharedPtr<FJsonValue>& OutError)
36
105
  {
37
106
  FString ActorLabel;
38
- if (auto Err = RequireString(Params, TEXT("actorLabel"), ActorLabel))
107
+ if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel))
39
108
  {
40
109
  OutError = Err;
41
110
  return nullptr;
@@ -48,12 +117,17 @@ namespace
48
117
  return nullptr;
49
118
  }
50
119
 
51
- AActor* Actor = FindActorByLabelOrName(World, ActorLabel);
52
- if (!Actor)
53
- {
54
- OutError = MCPError(FString::Printf(TEXT("Actor not found: %s"), *ActorLabel));
55
- return nullptr;
56
- }
120
+ // #956: label, internal name, or full object path, in that fixed order.
121
+ // A verification actor spawned into the editor world often has no label
122
+ // worth guessing, so the path has to be a first-class way to name it.
123
+ // #983: actorPath is its own parameter now, and a label that names more
124
+ // than one actor is refused rather than answered from one of them.
125
+ FMCPActorSelector ActorSel;
126
+ ActorSel.Match = EMCPActorMatch::LabelNameOrPath;
127
+ ActorSel.WorldLabel = World->IsPlayInEditor() ? TEXT("PIE") : TEXT("editor");
128
+ AActor* Actor = MCPResolveActor(World, Params, OutError, ActorSel);
129
+ if (!Actor) return nullptr;
130
+ ActorLabel = Actor->GetActorLabel();
57
131
  OutActor = Actor;
58
132
 
59
133
  UAbilitySystemComponent* ASC =
@@ -109,6 +183,76 @@ namespace
109
183
  Obj->SetNumberField(TEXT("currentValue"), ASC->GetNumericAttribute(Attr));
110
184
  }
111
185
 
186
+ // ── Registered attribute sets (#956) ──────────────────────────────
187
+ //
188
+ // UAbilitySystemComponent::GetAttributeSet / GetSet<T>() search
189
+ // SpawnedAttributes, and SpawnedAttributes IS the registry the gameplay
190
+ // code consults. Nothing else is. An actor's own UPROPERTY pointer to a
191
+ // CreateDefaultSubobject-created set is NOT proof the ASC knows about it.
192
+ //
193
+ // Those two only agree once InitializeComponent has run. It is
194
+ // InitializeComponent that scans the owner's default subobjects and calls
195
+ // AddSpawnedAttribute on every UAttributeSet it finds, and an actor spawned
196
+ // into the pure editor world never reaches it: there is no BeginPlay and no
197
+ // InitializeComponent in a world that has not begun play. So in the editor
198
+ // world the ASC has ZERO registered sets while the actor plainly has one.
199
+ //
200
+ // The trap that follows, and the reason this comment is this long: the
201
+ // obvious fallback at that point is "the ASC has none, so make one", which
202
+ // constructs a SECOND, disconnected instance next to the actor's own. Every
203
+ // read and write then lands on an object no gameplay code will ever look
204
+ // at, no error is reported, and the verification says the change worked. So
205
+ // we adopt the actor's EXISTING subobject into the ASC instead, which is
206
+ // exactly what InitializeComponent would have done, and we only ever hand
207
+ // back what GetAttributeSet returns afterwards.
208
+
209
+ /**
210
+ * The instance the gameplay code consults, and nothing else.
211
+ *
212
+ * Always answered by ASC->GetAttributeSet (the non-template form of
213
+ * ASC->GetSet<T>()). When the ASC has not registered one and adoption is
214
+ * allowed, the actor's own subobject is registered first and the ASC is
215
+ * asked again, so the pointer that comes back is still the registered one.
216
+ * A new set is never constructed here.
217
+ */
218
+ UAttributeSet* ResolveRegisteredAttributeSet(
219
+ UAbilitySystemComponent* ASC,
220
+ AActor* Actor,
221
+ UClass* SetClass,
222
+ bool bAllowAdopt,
223
+ bool& bOutAdopted,
224
+ TArray<FString>& OutAdoptedClassNames,
225
+ TSharedPtr<FJsonValue>& OutError)
226
+ {
227
+ bOutAdopted = false;
228
+ if (const UAttributeSet* Registered = ASC->GetAttributeSet(SetClass))
229
+ {
230
+ // const_cast is the only way to a mutable registered set: the ASC
231
+ // hands out const pointers and keeps no non-const accessor. The
232
+ // object is not const, only the view of it.
233
+ return const_cast<UAttributeSet*>(Registered);
234
+ }
235
+
236
+ if (bAllowAdopt && MCPGas::AdoptOwnerAttributeSets(ASC, Actor, OutAdoptedClassNames) > 0)
237
+ {
238
+ if (const UAttributeSet* Registered = ASC->GetAttributeSet(SetClass))
239
+ {
240
+ bOutAdopted = true;
241
+ return const_cast<UAttributeSet*>(Registered);
242
+ }
243
+ }
244
+
245
+ OutError = MCPError(FString::Printf(
246
+ TEXT("No '%s' is registered on '%s' AbilitySystemComponent%s. ")
247
+ TEXT("A set the actor owns is only registered once InitializeComponent runs, which never happens in a world that has not begun play. ")
248
+ TEXT("Start PIE, or call gas(action=\"init_asc\") with attributeSet=\"%s\" to register it."),
249
+ *SetClass->GetName(),
250
+ *Actor->GetActorLabel(),
251
+ bAllowAdopt ? TEXT(", and the actor owns no instance of it either") : TEXT(" (registerOwnerSets was false)"),
252
+ *SetClass->GetName()));
253
+ return nullptr;
254
+ }
255
+
112
256
  // Resolve a UClass deriving from Base from a content path or short class name.
113
257
  // Handles native classes, Blueprint generated classes (path + "_C"), and a
114
258
  // Blueprint-asset fallback. Returns nullptr unless the result is a Base subclass.
@@ -298,6 +442,191 @@ TSharedPtr<FJsonValue> FGasHandlers::GetAttribute(const TSharedPtr<FJsonObject>&
298
442
  return MCPResult(Result);
299
443
  }
300
444
 
445
+ // ── Live attribute values on the REGISTERED set (#956) ───────────────────────
446
+ //
447
+ // gas(get_attribute) / gas(set_attribute) walk whatever the ASC already has
448
+ // registered and match by attribute name. That is the right shape once a game
449
+ // has begun play and its sets are registered. It answers nothing at all for an
450
+ // actor spawned into the editor world for a verification pass, because nothing
451
+ // is registered there yet.
452
+ //
453
+ // These two name the attribute set explicitly, resolve the instance THROUGH THE
454
+ // ASC, and register the actor's own subobject when the ASC has not (see the
455
+ // long note on ResolveRegisteredAttributeSet above for why constructing a new
456
+ // set instead is the trap that silently reads and writes an object no gameplay
457
+ // code consults). Both report the registered instance's object path, so a
458
+ // caller can prove which object was touched.
459
+
460
+ namespace
461
+ {
462
+ /** Params shared by both live-attribute actions. */
463
+ struct FLiveAttributeRequest
464
+ {
465
+ AActor* Actor = nullptr;
466
+ UAbilitySystemComponent* ASC = nullptr;
467
+ UAttributeSet* Set = nullptr;
468
+ FGameplayAttribute Attribute;
469
+ bool bAdopted = false;
470
+ TArray<FString> AdoptedClassNames;
471
+ };
472
+
473
+ /** Everything both actions do before they diverge. */
474
+ bool ResolveLiveAttributeRequest(
475
+ const TSharedPtr<FJsonObject>& Params,
476
+ FLiveAttributeRequest& Out,
477
+ TSharedPtr<FJsonValue>& OutError)
478
+ {
479
+ FString SetSpec;
480
+ if (auto Err = RequireString(Params, TEXT("attributeSet"), SetSpec))
481
+ {
482
+ OutError = Err;
483
+ return false;
484
+ }
485
+ FString AttrName;
486
+ if (auto Err = RequireString(Params, TEXT("attribute"), AttrName))
487
+ {
488
+ OutError = Err;
489
+ return false;
490
+ }
491
+
492
+ Out.ASC = ResolveASC(Params, Out.Actor, OutError);
493
+ if (!Out.ASC) return false;
494
+
495
+ UClass* SetClass = ResolveClassDeriving(SetSpec, UAttributeSet::StaticClass());
496
+ if (!SetClass)
497
+ {
498
+ OutError = MCPError(FString::Printf(
499
+ TEXT("AttributeSet class not found: %s (pass a content path or a class name)"), *SetSpec));
500
+ return false;
501
+ }
502
+
503
+ const bool bAllowAdopt = OptionalBool(Params, TEXT("registerOwnerSets"), true);
504
+ Out.Set = ResolveRegisteredAttributeSet(
505
+ Out.ASC, Out.Actor, SetClass, bAllowAdopt, Out.bAdopted, Out.AdoptedClassNames, OutError);
506
+ if (!Out.Set) return false;
507
+
508
+ // Match against the REGISTERED instance's class, not the requested one:
509
+ // the registered set may be a subclass of what the caller named, and its
510
+ // own properties are the ones the aggregator reads.
511
+ FStructProperty* AttrProp = MCPGas::FindAttributeDataProperty(Out.Set->GetClass(), AttrName);
512
+ if (!AttrProp)
513
+ {
514
+ OutError = MCPError(FString::Printf(
515
+ TEXT("Attribute '%s' not found on '%s'. Available: %s"),
516
+ *AttrName,
517
+ *Out.Set->GetClass()->GetName(),
518
+ *MCPGas::ListAttributeDataPropertyNames(Out.Set->GetClass())));
519
+ return false;
520
+ }
521
+ Out.Attribute = FGameplayAttribute(AttrProp);
522
+ return true;
523
+ }
524
+
525
+ /** Identity + both values of the attribute, read off the registered set. */
526
+ void WriteLiveAttributeFields(TSharedPtr<FJsonObject> Obj, const FLiveAttributeRequest& Req)
527
+ {
528
+ Obj->SetStringField(TEXT("actorLabel"), Req.Actor->GetActorLabel());
529
+ Obj->SetStringField(TEXT("actorPath"), Req.Actor->GetPathName());
530
+ Obj->SetStringField(TEXT("attributeSet"), Req.Set->GetClass()->GetName());
531
+ // The proof of which object was touched. A second, disconnected instance
532
+ // would show a different path here.
533
+ Obj->SetStringField(TEXT("attributeSetInstance"), Req.Set->GetPathName());
534
+ Obj->SetStringField(TEXT("attribute"), Req.Attribute.GetName());
535
+ Obj->SetBoolField(TEXT("registeredOnAsc"), true);
536
+ Obj->SetBoolField(TEXT("registeredByThisCall"), Req.bAdopted);
537
+ if (Req.AdoptedClassNames.Num() > 0)
538
+ {
539
+ TArray<TSharedPtr<FJsonValue>> Adopted;
540
+ for (const FString& Name : Req.AdoptedClassNames)
541
+ {
542
+ Adopted.Add(MakeShared<FJsonValueString>(Name));
543
+ }
544
+ Obj->SetArrayField(TEXT("registeredSets"), Adopted);
545
+ }
546
+
547
+ // Read straight off the registered instance. GetNumericValue is the
548
+ // engine's own accessor for the current value of the attribute data.
549
+ Obj->SetNumberField(TEXT("currentValue"), Req.Attribute.GetNumericValue(Req.Set));
550
+ if (const FGameplayAttributeData* Data = Req.Attribute.GetGameplayAttributeData(Req.Set))
551
+ {
552
+ Obj->SetNumberField(TEXT("baseValue"), Data->GetBaseValue());
553
+ }
554
+ // The aggregator's view of the same attribute. Identical to the values
555
+ // above until an active GameplayEffect is modifying it, and the pair is
556
+ // what tells you an effect really landed.
557
+ Obj->SetNumberField(TEXT("aggregatorBaseValue"), Req.ASC->GetNumericAttributeBase(Req.Attribute));
558
+ Obj->SetNumberField(TEXT("aggregatorCurrentValue"), Req.ASC->GetNumericAttribute(Req.Attribute));
559
+ }
560
+ }
561
+
562
+ TSharedPtr<FJsonValue> FGasHandlers::GetLiveAttributeValue(const TSharedPtr<FJsonObject>& Params)
563
+ {
564
+ MCP_CHECK_GAME_THREAD();
565
+
566
+ FLiveAttributeRequest Req;
567
+ TSharedPtr<FJsonValue> Err;
568
+ if (!ResolveLiveAttributeRequest(Params, Req, Err)) return Err;
569
+
570
+ auto Result = MCPSuccess();
571
+ WriteLiveAttributeFields(Result, Req);
572
+ return MCPResult(Result);
573
+ }
574
+
575
+ TSharedPtr<FJsonValue> FGasHandlers::SetLiveAttributeValue(const TSharedPtr<FJsonObject>& Params)
576
+ {
577
+ MCP_CHECK_GAME_THREAD();
578
+
579
+ double NewValue = 0.0;
580
+ if (!Params->TryGetNumberField(TEXT("value"), NewValue))
581
+ {
582
+ return MCPError(TEXT("Missing required parameter 'value'"));
583
+ }
584
+
585
+ // "current" writes the FGameplayAttributeData's current value in place,
586
+ // which is what verifying a mid-combat state needs. "base" goes through the
587
+ // ASC so the aggregator recomputes the current value from it, which is what
588
+ // a durable change needs. They are not interchangeable, so the caller says.
589
+ const FString ValueType = OptionalString(Params, TEXT("valueType"), TEXT("current")).ToLower();
590
+ if (ValueType != TEXT("current") && ValueType != TEXT("base"))
591
+ {
592
+ return MCPError(FString::Printf(
593
+ TEXT("Unknown valueType '%s'. Use \"current\" (write the attribute data in place) or \"base\" (write through the ASC aggregator)."),
594
+ *ValueType));
595
+ }
596
+
597
+ FLiveAttributeRequest Req;
598
+ TSharedPtr<FJsonValue> Err;
599
+ if (!ResolveLiveAttributeRequest(Params, Req, Err)) return Err;
600
+
601
+ const float PreviousCurrent = Req.Attribute.GetNumericValue(Req.Set);
602
+ const float PreviousBase = Req.ASC->GetNumericAttributeBase(Req.Attribute);
603
+
604
+ if (ValueType == TEXT("base"))
605
+ {
606
+ // Recalculates the current value through the aggregator, so active
607
+ // modifiers stay consistent.
608
+ Req.ASC->SetNumericAttributeBase(Req.Attribute, static_cast<float>(NewValue));
609
+ }
610
+ else
611
+ {
612
+ // SetNumericValueChecked takes a mutable reference because the set's
613
+ // PreAttributeChange is allowed to clamp the value, so the number that
614
+ // lands can differ from the number asked for. That is why the result
615
+ // reports what was actually stored rather than echoing the request.
616
+ float Applied = static_cast<float>(NewValue);
617
+ Req.Attribute.SetNumericValueChecked(Applied, Req.Set);
618
+ }
619
+
620
+ auto Result = MCPSuccess();
621
+ MCPSetUpdated(Result);
622
+ WriteLiveAttributeFields(Result, Req);
623
+ Result->SetStringField(TEXT("valueType"), ValueType);
624
+ Result->SetNumberField(TEXT("requestedValue"), NewValue);
625
+ Result->SetNumberField(TEXT("previousCurrentValue"), PreviousCurrent);
626
+ Result->SetNumberField(TEXT("previousBaseValue"), PreviousBase);
627
+ return MCPResult(Result);
628
+ }
629
+
301
630
  // #587 get_asc_state - introspect a live ASC: granted ability specs (class,
302
631
  // level, input id, active state, dynamic source tags) plus the ASC's owned
303
632
  // gameplay tags. The read half of #587 (input injection lives in pie-studio).
@@ -362,12 +691,21 @@ TSharedPtr<FJsonValue> FGasHandlers::InitAsc(const TSharedPtr<FJsonObject>& Para
362
691
  // correctly. Safe to call again; a game's own pawn may also init the ASC.
363
692
  ASC->InitAbilityActorInfo(Actor, Actor);
364
693
 
694
+ // #956: register the actor's OWN attribute set subobjects first, the way
695
+ // InitializeComponent would have at BeginPlay. Skipping this step and going
696
+ // straight to "the ASC has none, so construct one" is what produced a
697
+ // second, disconnected instance next to the actor's own: reads and writes
698
+ // then landed on an object the gameplay code never consults, and nothing
699
+ // reported an error. Adoption is idempotent and costs one hash walk.
700
+ TArray<FString> AdoptedSets;
701
+ MCPGas::AdoptOwnerAttributeSets(ASC, Actor, AdoptedSets);
702
+
365
703
  // Optionally guarantee an attribute set exists on the ASC. This is what lets
366
704
  // a bridge-authored test actor have live attributes without shipping an init
367
- // DataTable: spawn the set (with its default values) and register it if it
368
- // isn't already present. GetOrCreateAttributeSubobject is protected, so use
369
- // the public GetAttributeSet + AddSpawnedAttribute pair.
705
+ // DataTable. A set is only constructed when the actor genuinely owns none of
706
+ // that class, and the result says which of the two happened.
370
707
  FString CreatedSet;
708
+ bool bConstructedSet = false;
371
709
  const FString AttrSetSpec = OptionalString(Params, TEXT("attributeSet"));
372
710
  if (!AttrSetSpec.IsEmpty())
373
711
  {
@@ -383,6 +721,7 @@ TSharedPtr<FJsonValue> FGasHandlers::InitAsc(const TSharedPtr<FJsonObject>& Para
383
721
  UAttributeSet* NewSet = NewObject<UAttributeSet>(Actor, AttrSetClass);
384
722
  ASC->AddSpawnedAttribute(NewSet);
385
723
  CreatedSet = NewSet->GetClass()->GetName();
724
+ bConstructedSet = true;
386
725
  }
387
726
  else
388
727
  {
@@ -405,7 +744,19 @@ TSharedPtr<FJsonValue> FGasHandlers::InitAsc(const TSharedPtr<FJsonObject>& Para
405
744
  auto Result = MCPSuccess();
406
745
  Result->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
407
746
  Result->SetBoolField(TEXT("initialized"), true);
408
- if (!CreatedSet.IsEmpty()) Result->SetStringField(TEXT("attributeSet"), CreatedSet);
747
+ if (!CreatedSet.IsEmpty())
748
+ {
749
+ Result->SetStringField(TEXT("attributeSet"), CreatedSet);
750
+ // The caller needs to know which happened. A constructed set starts at
751
+ // its class defaults; an adopted one carries whatever the actor has.
752
+ Result->SetBoolField(TEXT("attributeSetConstructed"), bConstructedSet);
753
+ }
754
+ if (AdoptedSets.Num() > 0)
755
+ {
756
+ TArray<TSharedPtr<FJsonValue>> Adopted;
757
+ for (const FString& Name : AdoptedSets) Adopted.Add(MakeShared<FJsonValueString>(Name));
758
+ Result->SetArrayField(TEXT("registeredOwnerSets"), Adopted);
759
+ }
409
760
  Result->SetNumberField(TEXT("attributeCount"), AttrCount);
410
761
  return MCPResult(Result);
411
762
  }
@@ -0,0 +1,84 @@
1
+ #pragma once
2
+
3
+ // Editor-state helpers shared by more than one handler translation unit.
4
+ //
5
+ // The module is compiled as a unity build, so a file-local helper copied into a
6
+ // second .cpp becomes a redefinition the moment UBT groups the two files into
7
+ // one blob (error C2084), and the grouping is not stable across machines.
8
+ // Anything two handlers both need lives here, in a named namespace, as an
9
+ // inline function. It is a Private header rather than Public/HandlerUtils.h
10
+ // because it pulls in the editor file utilities, and HandlerUtils.h is also
11
+ // included by UE_MCP_BridgeStatus, which does not link UnrealEd.
12
+
13
+ #include "CoreMinimal.h"
14
+ #include "Dom/JsonObject.h"
15
+ #include "Dom/JsonValue.h"
16
+ #include "Editor.h"
17
+ #include "Engine/World.h"
18
+ #include "FileHelpers.h"
19
+ #include "Misc/PackageName.h"
20
+ #include "UObject/Package.h"
21
+
22
+ namespace MCPEditorState
23
+ {
24
+ /**
25
+ * Long package names of every dirty content or map package in the editor.
26
+ *
27
+ * Two handlers need this for the same reason: an operation that temporarily
28
+ * opens a different map has to refuse when the editor already has unsaved
29
+ * work, because loading another level either discards it or raises a modal
30
+ * save prompt the bridge cannot answer.
31
+ *
32
+ * `/Script/` packages are always dirty-ish bookkeeping and are excluded.
33
+ */
34
+ inline void CollectDirtyEditorPackageNames(TArray<FString>& OutPackageNames)
35
+ {
36
+ OutPackageNames.Reset();
37
+ TArray<UPackage*> DirtyPackages;
38
+ FEditorFileUtils::GetDirtyContentPackages(DirtyPackages);
39
+ FEditorFileUtils::GetDirtyWorldPackages(DirtyPackages);
40
+ for (UPackage* Package : DirtyPackages)
41
+ {
42
+ if (!Package || !Package->IsDirty())
43
+ {
44
+ continue;
45
+ }
46
+
47
+ const FString PackageName = Package->GetName();
48
+ if (PackageName.StartsWith(TEXT("/Script/")))
49
+ {
50
+ continue;
51
+ }
52
+
53
+ OutPackageNames.AddUnique(PackageName);
54
+ }
55
+ OutPackageNames.Sort();
56
+ }
57
+
58
+ /** Long package name of the map currently open in the editor, or empty. */
59
+ inline FString CurrentEditorLevelPackageName()
60
+ {
61
+ if (!GEditor)
62
+ {
63
+ return FString();
64
+ }
65
+ UWorld* World = GEditor->GetEditorWorldContext().World();
66
+ if (!World || !World->GetOutermost())
67
+ {
68
+ return FString();
69
+ }
70
+ return World->GetOutermost()->GetName();
71
+ }
72
+
73
+ /** Is this long package name an existing .umap on disk? */
74
+ inline bool IsExistingMapPackage(const FString& LevelPath)
75
+ {
76
+ if (LevelPath.IsEmpty() || !FPackageName::IsValidLongPackageName(LevelPath))
77
+ {
78
+ return false;
79
+ }
80
+ FString Filename;
81
+ return FPackageName::DoesPackageExist(LevelPath, &Filename) &&
82
+ Filename.EndsWith(FPackageName::GetMapPackageExtension(), ESearchCase::IgnoreCase);
83
+ }
84
+ }