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,509 @@
1
+ #pragma once
2
+
3
+ // Shared machinery for the UFUNCTION-invoking handlers (invoke_function,
4
+ // invoke_object_function, invoke_static_function).
5
+ //
6
+ // It lives in a header rather than a file-local helper in one of the handler
7
+ // .cpp files because the module is compiled as a unity build: two translation
8
+ // units that each defined a helper of this name would merge their anonymous
9
+ // namespaces and the second definition would be a redefinition.
10
+
11
+ #include "CoreMinimal.h"
12
+ #include "Containers/Ticker.h"
13
+ #include "Dom/JsonObject.h"
14
+ #include "Dom/JsonValue.h"
15
+ #include "UObject/Class.h"
16
+ #include "UObject/EnumProperty.h"
17
+ #include "UObject/Script.h"
18
+ #include "UObject/StrongObjectPtr.h"
19
+ #include "UObject/TextProperty.h"
20
+ #include "UObject/UnrealType.h"
21
+ #include "UObject/UObjectGlobals.h"
22
+ #include "UObject/WeakObjectPtr.h"
23
+ #include "UObject/WeakObjectPtrTemplates.h"
24
+
25
+ namespace MCPFunctionCall
26
+ {
27
+ /**
28
+ * Typed JSON for one value inside a container. Recursive, so a
29
+ * TArray<TArray<int32>> or a TMap<FName, TArray<FString>> comes back whole.
30
+ */
31
+ inline TSharedPtr<FJsonValue> ValueToJson(FProperty* Prop, const void* ValuePtr, UObject* Parent);
32
+
33
+ /** Leaf marshalling for container elements: everything that is not a container. */
34
+ inline TSharedPtr<FJsonValue> ElementToJson(FProperty* Prop, const void* ValuePtr, UObject* Parent)
35
+ {
36
+ if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop))
37
+ {
38
+ return MakeShared<FJsonValueBoolean>(BoolProp->GetPropertyValue(ValuePtr));
39
+ }
40
+ if (FStrProperty* StrProp = CastField<FStrProperty>(Prop))
41
+ {
42
+ return MakeShared<FJsonValueString>(StrProp->GetPropertyValue(ValuePtr));
43
+ }
44
+ if (FNameProperty* NameProp = CastField<FNameProperty>(Prop))
45
+ {
46
+ return MakeShared<FJsonValueString>(NameProp->GetPropertyValue(ValuePtr).ToString());
47
+ }
48
+ if (FTextProperty* TextProp = CastField<FTextProperty>(Prop))
49
+ {
50
+ return MakeShared<FJsonValueString>(TextProp->GetPropertyValue(ValuePtr).ToString());
51
+ }
52
+ // Enums report their name, not their ordinal: a bare number in a result
53
+ // is unreadable and a caller cannot map it back without the UEnum.
54
+ if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
55
+ {
56
+ const int64 Raw = EnumProp->GetUnderlyingProperty()->GetSignedIntPropertyValue(ValuePtr);
57
+ if (UEnum* Enum = EnumProp->GetEnum())
58
+ {
59
+ return MakeShared<FJsonValueString>(Enum->GetNameStringByValue(Raw));
60
+ }
61
+ return MakeShared<FJsonValueNumber>(static_cast<double>(Raw));
62
+ }
63
+ if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
64
+ {
65
+ const uint8 Raw = ByteProp->GetPropertyValue(ValuePtr);
66
+ if (UEnum* Enum = ByteProp->Enum)
67
+ {
68
+ return MakeShared<FJsonValueString>(Enum->GetNameStringByValue(static_cast<int64>(Raw)));
69
+ }
70
+ return MakeShared<FJsonValueNumber>(static_cast<double>(Raw));
71
+ }
72
+ if (FNumericProperty* NumProp = CastField<FNumericProperty>(Prop))
73
+ {
74
+ if (NumProp->IsFloatingPoint())
75
+ {
76
+ return MakeShared<FJsonValueNumber>(NumProp->GetFloatingPointPropertyValue(ValuePtr));
77
+ }
78
+ return MakeShared<FJsonValueNumber>(static_cast<double>(NumProp->GetSignedIntPropertyValue(ValuePtr)));
79
+ }
80
+ // Soft references before hard ones: a soft pointer derives from
81
+ // FObjectPropertyBase but answers null while its target is unloaded,
82
+ // which would report a live reference as None (#598).
83
+ if (FSoftObjectProperty* SoftProp = CastField<FSoftObjectProperty>(Prop))
84
+ {
85
+ const FString Path = SoftProp->GetPropertyValue(ValuePtr).ToString();
86
+ return MakeShared<FJsonValueString>(Path.IsEmpty() ? FString(TEXT("None")) : Path);
87
+ }
88
+ if (FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(Prop))
89
+ {
90
+ UObject* Referenced = ObjProp->GetObjectPropertyValue(ValuePtr);
91
+ if (!Referenced) return MakeShared<FJsonValueString>(TEXT("None"));
92
+ // The frame is raw bytes and invisible to GC, so an object the call
93
+ // destroyed would be dereferenced by the path read below.
94
+ if (!IsValid(Referenced)) return MakeShared<FJsonValueString>(TEXT("(collected during the call)"));
95
+ return MakeShared<FJsonValueString>(Referenced->GetPathName());
96
+ }
97
+
98
+ // Structs and anything else: export text, the spelling every other
99
+ // return value in this response already uses.
100
+ FString Exported;
101
+ Prop->ExportTextItem_Direct(Exported, ValuePtr, nullptr, Parent, PPF_None);
102
+ return MakeShared<FJsonValueString>(Exported);
103
+ }
104
+
105
+ inline TSharedPtr<FJsonValue> ValueToJson(FProperty* Prop, const void* ValuePtr, UObject* Parent)
106
+ {
107
+ if (!Prop || !ValuePtr) return MakeShared<FJsonValueNull>();
108
+
109
+ if (FArrayProperty* ArrayProp = CastField<FArrayProperty>(Prop))
110
+ {
111
+ FScriptArrayHelper Helper(ArrayProp, ValuePtr);
112
+ const int32 Count = Helper.Num();
113
+ TArray<TSharedPtr<FJsonValue>> Items;
114
+ Items.Reserve(Count);
115
+ for (int32 Index = 0; Index < Count; ++Index)
116
+ {
117
+ Items.Add(ValueToJson(ArrayProp->Inner, Helper.GetRawPtr(Index), Parent));
118
+ }
119
+ return MakeShared<FJsonValueArray>(Items);
120
+ }
121
+ if (FSetProperty* SetProp = CastField<FSetProperty>(Prop))
122
+ {
123
+ FScriptSetHelper Helper(SetProp, ValuePtr);
124
+ TArray<TSharedPtr<FJsonValue>> Items;
125
+ Items.Reserve(Helper.Num());
126
+ for (int32 Index = 0, MaxIndex = Helper.GetMaxIndex(); Index < MaxIndex; ++Index)
127
+ {
128
+ if (!Helper.IsValidIndex(Index)) continue;
129
+ Items.Add(ValueToJson(SetProp->ElementProp, Helper.GetElementPtr(Index), Parent));
130
+ }
131
+ return MakeShared<FJsonValueArray>(Items);
132
+ }
133
+ if (FMapProperty* MapProp = CastField<FMapProperty>(Prop))
134
+ {
135
+ // An array of {key,value} rather than a JSON object: a TMap key can
136
+ // be a struct or an enum, and collapsing it into an object key would
137
+ // lose that.
138
+ FScriptMapHelper Helper(MapProp, ValuePtr);
139
+ TArray<TSharedPtr<FJsonValue>> Pairs;
140
+ Pairs.Reserve(Helper.Num());
141
+ for (int32 Index = 0, MaxIndex = Helper.GetMaxIndex(); Index < MaxIndex; ++Index)
142
+ {
143
+ if (!Helper.IsValidIndex(Index)) continue;
144
+ TSharedPtr<FJsonObject> Pair = MakeShared<FJsonObject>();
145
+ Pair->SetField(TEXT("key"), ValueToJson(MapProp->KeyProp, Helper.GetKeyPtr(Index), Parent));
146
+ Pair->SetField(TEXT("value"), ValueToJson(MapProp->ValueProp, Helper.GetValuePtr(Index), Parent));
147
+ Pairs.Add(MakeShared<FJsonValueObject>(Pair));
148
+ }
149
+ return MakeShared<FJsonValueArray>(Pairs);
150
+ }
151
+
152
+ return ElementToJson(Prop, ValuePtr, Parent);
153
+ }
154
+
155
+ /** True for the three property kinds export text cannot carry back usefully. */
156
+ inline bool IsContainerProperty(FProperty* Prop)
157
+ {
158
+ return Prop && (Prop->IsA<FArrayProperty>() || Prop->IsA<FSetProperty>() || Prop->IsA<FMapProperty>());
159
+ }
160
+
161
+ /**
162
+ * #885: marshal one return or out parameter.
163
+ *
164
+ * A container becomes real JSON. Everything else keeps the export-text
165
+ * spelling every existing reader of returnValues parses, so this fixes the
166
+ * reported defect without moving the wire format underneath anyone: a
167
+ * TArray<FString> return arrived as an empty string, which made every
168
+ * array-returning accessor unreadable through the bridge.
169
+ */
170
+ inline TSharedPtr<FJsonValue> OutputToJson(FProperty* Prop, const void* ValuePtr, UObject* Parent)
171
+ {
172
+ if (!Prop || !ValuePtr) return MakeShared<FJsonValueNull>();
173
+ if (IsContainerProperty(Prop)) return ValueToJson(Prop, ValuePtr, Parent);
174
+
175
+ // Object out-params are checked before export: the frame is raw bytes
176
+ // and invisible to GC, so exporting one the call destroyed would
177
+ // dereference freed memory. "(collected during the call)" is distinct
178
+ // from "None" on purpose - reporting both as empty reads as a null
179
+ // return.
180
+ if (FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(Prop))
181
+ {
182
+ UObject* Referenced = ObjProp->GetObjectPropertyValue(ValuePtr);
183
+ if (!Referenced) return MakeShared<FJsonValueString>(TEXT("None"));
184
+ if (!IsValid(Referenced)) return MakeShared<FJsonValueString>(TEXT("(collected during the call)"));
185
+ }
186
+
187
+ FString Exported;
188
+ Prop->ExportTextItem_Direct(Exported, ValuePtr, nullptr, Parent, PPF_None);
189
+ return MakeShared<FJsonValueString>(Exported);
190
+ }
191
+
192
+ /**
193
+ * Read every return and out parameter of a called function out of its frame
194
+ * and onto OutVals. One implementation for all the invoke actions, so the
195
+ * wire format cannot diverge between them.
196
+ */
197
+ inline void WriteOutputs(const TSharedPtr<FJsonObject>& OutVals, UFunction* Func, void* ParamBuf, UObject* Parent)
198
+ {
199
+ if (!OutVals.IsValid() || !Func || !ParamBuf) return;
200
+ for (TFieldIterator<FProperty> It(Func); It && (It->PropertyFlags & CPF_Parm); ++It)
201
+ {
202
+ FProperty* Prop = *It;
203
+ if (!(Prop->PropertyFlags & (CPF_ReturnParm | CPF_OutParm))) continue;
204
+ OutVals->SetField(Prop->GetName(), OutputToJson(Prop, Prop->ContainerPtrToValuePtr<void>(ParamBuf), Parent));
205
+ }
206
+ }
207
+
208
+ /** Destroy every parameter value in a frame that InitializeValue_InContainer built. */
209
+ inline void DestroyFrame(UFunction* Func, void* ParamBuf)
210
+ {
211
+ if (!Func || !ParamBuf) return;
212
+ for (TFieldIterator<FProperty> It(Func); It && (It->PropertyFlags & CPF_Parm); ++It)
213
+ {
214
+ It->DestroyValue_InContainer(ParamBuf);
215
+ }
216
+ }
217
+
218
+ // ── Dotted-path call syntax (#969) ───────────────────────────────────────
219
+ //
220
+ // get_runtime_values evaluates dotted paths per matched actor or component.
221
+ // A read-only accessor keyed by an id (a tally by option id, a balance by
222
+ // currency id, an attribute by tag) is a very common shape and was
223
+ // unreachable, because only a zero-arg UFUNCTION was accepted. These parse
224
+ // the "Name(literal, literal)" form the old error message already implied
225
+ // was being read.
226
+
227
+ /**
228
+ * Split a dotted path into segments without breaking an argument list.
229
+ * "Comp.GetWeight(1.5)" is two segments, not three: the dot inside the
230
+ * parentheses belongs to the float literal. Empty segments are dropped,
231
+ * which is what ParseIntoArray did before this existed.
232
+ */
233
+ inline void SplitPathSegments(const FString& Path, TArray<FString>& OutSegments)
234
+ {
235
+ OutSegments.Reset();
236
+ int32 Depth = 0;
237
+ TCHAR Quote = 0;
238
+ FString Current;
239
+ for (int32 Index = 0; Index < Path.Len(); ++Index)
240
+ {
241
+ const TCHAR Ch = Path[Index];
242
+ if (Quote != 0)
243
+ {
244
+ Current.AppendChar(Ch);
245
+ if (Ch == Quote) Quote = 0;
246
+ continue;
247
+ }
248
+ if (Ch == TEXT('"') || Ch == TEXT('\''))
249
+ {
250
+ Quote = Ch;
251
+ Current.AppendChar(Ch);
252
+ continue;
253
+ }
254
+ if (Ch == TEXT('(') || Ch == TEXT('[') || Ch == TEXT('{'))
255
+ {
256
+ ++Depth;
257
+ Current.AppendChar(Ch);
258
+ continue;
259
+ }
260
+ if (Ch == TEXT(')') || Ch == TEXT(']') || Ch == TEXT('}'))
261
+ {
262
+ if (Depth > 0) --Depth;
263
+ Current.AppendChar(Ch);
264
+ continue;
265
+ }
266
+ if (Ch == TEXT('.') && Depth == 0)
267
+ {
268
+ OutSegments.Add(Current.TrimStartAndEnd());
269
+ Current.Reset();
270
+ continue;
271
+ }
272
+ Current.AppendChar(Ch);
273
+ }
274
+ OutSegments.Add(Current.TrimStartAndEnd());
275
+ OutSegments.RemoveAll([](const FString& Segment) { return Segment.IsEmpty(); });
276
+ }
277
+
278
+ /**
279
+ * Strip one level of matching quotes from a literal argument. Everything
280
+ * else is handed on verbatim: the value is coerced by the same property
281
+ * setter invoke_object_function's args go through, which already reads a
282
+ * string into an FName, an FString, a number, a bool or an enum.
283
+ */
284
+ inline FString UnquoteArgLiteral(const FString& Literal)
285
+ {
286
+ const FString Trimmed = Literal.TrimStartAndEnd();
287
+ if (Trimmed.Len() >= 2)
288
+ {
289
+ const TCHAR First = Trimmed[0];
290
+ if ((First == TEXT('"') || First == TEXT('\'')) && Trimmed[Trimmed.Len() - 1] == First)
291
+ {
292
+ return Trimmed.Mid(1, Trimmed.Len() - 2);
293
+ }
294
+ }
295
+ return Trimmed;
296
+ }
297
+
298
+ /**
299
+ * Read "Name(a, b)" into its name and its raw argument literals.
300
+ *
301
+ * bOutHasArgList separates "Name" (no list, the historical zero-arg form)
302
+ * from "Name()" (an explicitly empty list), so a caller can keep answering
303
+ * the old shape exactly as before. Returns false with OutError set when the
304
+ * parentheses do not balance or an argument is empty, because a silently
305
+ * mis-parsed key would read back somebody else's value.
306
+ */
307
+ inline bool ParseCallSegment(
308
+ const FString& Segment,
309
+ FString& OutName,
310
+ TArray<FString>& OutArgs,
311
+ bool& bOutHasArgList,
312
+ FString& OutError)
313
+ {
314
+ OutName = Segment.TrimStartAndEnd();
315
+ OutArgs.Reset();
316
+ bOutHasArgList = false;
317
+ OutError.Reset();
318
+
319
+ int32 Open = INDEX_NONE;
320
+ if (!Segment.FindChar(TEXT('('), Open))
321
+ {
322
+ return true;
323
+ }
324
+
325
+ int32 Depth = 0;
326
+ TCHAR Quote = 0;
327
+ int32 Close = INDEX_NONE;
328
+ for (int32 Index = Open; Index < Segment.Len(); ++Index)
329
+ {
330
+ const TCHAR Ch = Segment[Index];
331
+ if (Quote != 0)
332
+ {
333
+ if (Ch == Quote) Quote = 0;
334
+ continue;
335
+ }
336
+ if (Ch == TEXT('"') || Ch == TEXT('\'')) { Quote = Ch; continue; }
337
+ if (Ch == TEXT('(')) { ++Depth; continue; }
338
+ if (Ch == TEXT(')') && --Depth == 0) { Close = Index; break; }
339
+ }
340
+ if (Close != Segment.Len() - 1)
341
+ {
342
+ OutError = FString::Printf(
343
+ TEXT("'%s' opens an argument list that does not close at the end of the segment"), *Segment);
344
+ return false;
345
+ }
346
+
347
+ OutName = Segment.Left(Open).TrimStartAndEnd();
348
+ if (OutName.IsEmpty())
349
+ {
350
+ OutError = FString::Printf(TEXT("'%s' has an argument list with no function name in front of it"), *Segment);
351
+ return false;
352
+ }
353
+ bOutHasArgList = true;
354
+
355
+ const FString Inner = Segment.Mid(Open + 1, Close - Open - 1);
356
+ if (Inner.TrimStartAndEnd().IsEmpty())
357
+ {
358
+ return true;
359
+ }
360
+
361
+ Depth = 0;
362
+ Quote = 0;
363
+ FString Current;
364
+ for (int32 Index = 0; Index < Inner.Len(); ++Index)
365
+ {
366
+ const TCHAR Ch = Inner[Index];
367
+ if (Quote != 0)
368
+ {
369
+ Current.AppendChar(Ch);
370
+ if (Ch == Quote) Quote = 0;
371
+ continue;
372
+ }
373
+ if (Ch == TEXT('"') || Ch == TEXT('\'')) { Quote = Ch; Current.AppendChar(Ch); continue; }
374
+ if (Ch == TEXT('(') || Ch == TEXT('[') || Ch == TEXT('{')) { ++Depth; Current.AppendChar(Ch); continue; }
375
+ if (Ch == TEXT(')') || Ch == TEXT(']') || Ch == TEXT('}')) { if (Depth > 0) --Depth; Current.AppendChar(Ch); continue; }
376
+ if (Ch == TEXT(',') && Depth == 0)
377
+ {
378
+ OutArgs.Add(Current);
379
+ Current.Reset();
380
+ continue;
381
+ }
382
+ Current.AppendChar(Ch);
383
+ }
384
+ OutArgs.Add(Current);
385
+
386
+ for (int32 Index = 0; Index < OutArgs.Num(); ++Index)
387
+ {
388
+ OutArgs[Index] = UnquoteArgLiteral(OutArgs[Index]);
389
+ if (OutArgs[Index].IsEmpty())
390
+ {
391
+ OutError = FString::Printf(TEXT("'%s' has an empty argument at position %d"), *Segment, Index + 1);
392
+ OutArgs.Reset();
393
+ return false;
394
+ }
395
+ }
396
+ return true;
397
+ }
398
+
399
+ /** Render a callspace, which is a bitmask and can name more than one space. */
400
+ inline FString DescribeCallspace(int32 Callspace)
401
+ {
402
+ if (Callspace == FunctionCallspace::Absorbed) return TEXT("Absorbed");
403
+ TArray<FString> Parts;
404
+ if (Callspace & FunctionCallspace::Local) Parts.Add(TEXT("Local"));
405
+ if (Callspace & FunctionCallspace::Remote) Parts.Add(TEXT("Remote"));
406
+ if (Parts.Num() == 0) return FString::Printf(TEXT("Unknown(%d)"), Callspace);
407
+ return FString::Join(Parts, TEXT("+"));
408
+ }
409
+
410
+ /**
411
+ * #973: report a network call whose routing the editor script guard is about
412
+ * to override.
413
+ *
414
+ * FEditorScriptExecutionGuard sets GAllowActorScriptExecutionInEditor, and
415
+ * AActor::GetFunctionCallspace checks that global in its FIRST branch and
416
+ * answers Local unconditionally. So a correct UFUNCTION(Server) reached from
417
+ * a scripted call runs the server implementation locally on a client copy
418
+ * instead of being sent, which looks exactly like a broken RPC.
419
+ *
420
+ * Call this BEFORE opening the guard: that is the only moment the honest
421
+ * callspace is observable. Returns true when the function is a net function
422
+ * whose callspace is about to be forced to Local from something else, and
423
+ * writes the callspace that would otherwise have applied.
424
+ */
425
+ inline bool WouldForceNetCallspaceLocal(UObject* Target, UFunction* Func, FString& OutNaturalCallspace)
426
+ {
427
+ OutNaturalCallspace.Reset();
428
+ if (!Target || !Func) return false;
429
+ if (!Func->HasAnyFunctionFlags(FUNC_Net)) return false;
430
+
431
+ const int32 Callspace = Target->GetFunctionCallspace(Func, nullptr);
432
+ if (Callspace == FunctionCallspace::Local) return false;
433
+ OutNaturalCallspace = DescribeCallspace(Callspace);
434
+ return true;
435
+ }
436
+
437
+ /** Wording for the case above, so a false negative is visible in the result. */
438
+ inline FString DescribeForcedLocalCallspace(const FString& FunctionName, const FString& NaturalCallspace)
439
+ {
440
+ return FString::Printf(
441
+ TEXT("'%s' is a replicated UFUNCTION whose callspace here is %s, but a scripted call runs under the editor ")
442
+ TEXT("script-execution guard, which forces every actor callspace to Local. The implementation ran locally ")
443
+ TEXT("rather than being sent, so a refusal or a no-op here is not evidence that the RPC is broken. Pass ")
444
+ TEXT("deferToNextTick=true to run the call on the next engine tick, outside the guard, where it routes normally."),
445
+ *FunctionName, *NaturalCallspace);
446
+ }
447
+
448
+ /**
449
+ * #973: run the call on the next engine tick instead of inline.
450
+ *
451
+ * The guard's scope is the handler, so a call queued here happens after that
452
+ * scope has ended and GetFunctionCallspace answers honestly. Takes ownership
453
+ * of the already-marshalled parameter frame; the frame is destroyed after the
454
+ * call, or without calling if the target has gone by then.
455
+ *
456
+ * Nothing can be read back: the response is written before the call runs.
457
+ * That is the trade a caller opts into by asking for the deferral.
458
+ */
459
+ inline void DeferProcessEventToNextTick(UObject* Target, UFunction* Func, TArray<uint8>&& ParamBuf)
460
+ {
461
+ if (!Target || !Func) return;
462
+
463
+ TWeakObjectPtr<UObject> WeakTarget(Target);
464
+
465
+ // The frame is a raw byte buffer, so the garbage collector cannot see
466
+ // any UObject argument marshalled into it. Between this handler
467
+ // returning and the ticker firing there is a real window for an
468
+ // incremental GC, and a collected argument would be handed to
469
+ // ProcessEvent as a dangling pointer. Hold every object argument
470
+ // strongly for exactly that window.
471
+ TArray<TStrongObjectPtr<UObject>> HeldArgs;
472
+ for (TFieldIterator<FProperty> It(Func); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
473
+ {
474
+ if (It->HasAnyPropertyFlags(CPF_ReturnParm)) continue;
475
+ if (const FObjectPropertyBase* ObjProp = CastField<FObjectPropertyBase>(*It))
476
+ {
477
+ if (UObject* Arg = ObjProp->GetObjectPropertyValue(ObjProp->ContainerPtrToValuePtr<void>(ParamBuf.GetData())))
478
+ {
479
+ HeldArgs.Emplace(Arg);
480
+ }
481
+ }
482
+ }
483
+
484
+ // The UFunction is held strongly rather than weakly because it is what
485
+ // DestroyFrame walks to destruct the frame. If it were allowed to go
486
+ // (a Blueprint recompiled between queue and tick), every FString and
487
+ // TArray parameter in the buffer would leak instead.
488
+ TStrongObjectPtr<UFunction> HeldFunc(Func);
489
+ TSharedRef<TArray<uint8>> Frame = MakeShared<TArray<uint8>>(MoveTemp(ParamBuf));
490
+
491
+ FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda(
492
+ [WeakTarget, HeldFunc, HeldArgs, Frame](float) -> bool
493
+ {
494
+ UFunction* LiveFunc = HeldFunc.Get();
495
+ UObject* LiveTarget = WeakTarget.Get();
496
+ if (LiveTarget && LiveFunc && IsValid(LiveTarget))
497
+ {
498
+ // Deliberately NOT under FEditorScriptExecutionGuard: the
499
+ // whole point of the deferral is that the guard is closed.
500
+ LiveTarget->ProcessEvent(LiveFunc, Frame->GetData());
501
+ }
502
+ if (LiveFunc)
503
+ {
504
+ DestroyFrame(LiveFunc, Frame->GetData());
505
+ }
506
+ return false;
507
+ }), 0.0f);
508
+ }
509
+ }