ue-mcp 1.2.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/action-class.js +56 -3
- package/dist/action-class.js.map +1 -1
- package/dist/bridge-timeouts.d.ts +64 -0
- package/dist/bridge-timeouts.js +118 -0
- package/dist/bridge-timeouts.js.map +1 -0
- package/dist/bridge.js +10 -2
- package/dist/bridge.js.map +1 -1
- package/dist/editor-control.d.ts +72 -5
- package/dist/editor-control.js +164 -140
- package/dist/editor-control.js.map +1 -1
- package/dist/editor-target.d.ts +55 -1
- package/dist/editor-target.js +107 -10
- package/dist/editor-target.js.map +1 -1
- package/dist/engine-observer.d.ts +59 -5
- package/dist/engine-observer.js +249 -29
- package/dist/engine-observer.js.map +1 -1
- package/dist/engine-root.d.ts +91 -0
- package/dist/engine-root.js +448 -0
- package/dist/engine-root.js.map +1 -0
- package/dist/epic-enrich.js +16 -1
- package/dist/epic-enrich.js.map +1 -1
- package/dist/feedback-cli.js +4 -0
- package/dist/feedback-cli.js.map +1 -1
- package/dist/feedback-deferred.d.ts +9 -0
- package/dist/feedback-deferred.js +2 -0
- package/dist/feedback-deferred.js.map +1 -1
- package/dist/feedback-fallback.d.ts +99 -0
- package/dist/feedback-fallback.js +173 -0
- package/dist/feedback-fallback.js.map +1 -0
- package/dist/flow/write-methods.js +20 -0
- package/dist/flow/write-methods.js.map +1 -1
- package/dist/lean-context.js +8 -3
- package/dist/lean-context.js.map +1 -1
- package/dist/locking.js +11 -2
- package/dist/locking.js.map +1 -1
- package/dist/project.d.ts +11 -0
- package/dist/project.js +23 -2
- package/dist/project.js.map +1 -1
- package/dist/python-gate.d.ts +95 -0
- package/dist/python-gate.js +175 -0
- package/dist/python-gate.js.map +1 -0
- package/dist/tool-counts.json +16 -16
- package/dist/tools/animation.js +288 -24
- package/dist/tools/animation.js.map +1 -1
- package/dist/tools/asset.js +79 -14
- package/dist/tools/asset.js.map +1 -1
- package/dist/tools/audio.js +59 -5
- package/dist/tools/audio.js.map +1 -1
- package/dist/tools/blueprint.js +29 -12
- package/dist/tools/blueprint.js.map +1 -1
- package/dist/tools/chooser.js +4 -1
- package/dist/tools/chooser.js.map +1 -1
- package/dist/tools/editor.js +81 -43
- package/dist/tools/editor.js.map +1 -1
- package/dist/tools/feedback.js +214 -48
- package/dist/tools/feedback.js.map +1 -1
- package/dist/tools/foliage.js +27 -1
- package/dist/tools/foliage.js.map +1 -1
- package/dist/tools/gameplay.js +38 -11
- package/dist/tools/gameplay.js.map +1 -1
- package/dist/tools/gas.js +17 -5
- package/dist/tools/gas.js.map +1 -1
- package/dist/tools/landscape.js +22 -7
- package/dist/tools/landscape.js.map +1 -1
- package/dist/tools/level.js +322 -51
- package/dist/tools/level.js.map +1 -1
- package/dist/tools/material.js +10 -4
- package/dist/tools/material.js.map +1 -1
- package/dist/tools/niagara.js +3 -2
- package/dist/tools/niagara.js.map +1 -1
- package/dist/tools/pcg.js +6 -5
- package/dist/tools/pcg.js.map +1 -1
- package/dist/tools/project.js +47 -20
- package/dist/tools/project.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.js +46 -3
- package/dist/types.js.map +1 -1
- package/dist/ue-mcp.default.yml +493 -125
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
- package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
- package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
- package/skills/ue-mcp-animation/SKILL.md +154 -0
- package/skills/ue-mcp-blueprint/SKILL.md +71 -0
package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
// Split from BlueprintHandlers.cpp to keep that file under 3k lines. All
|
|
2
|
+
// functions below are still members of FBlueprintHandlers - this file is a
|
|
3
|
+
// translation-unit partition, not a new class. Handler registration stays in
|
|
4
|
+
// BlueprintHandlers.cpp::RegisterHandlers.
|
|
5
|
+
//
|
|
6
|
+
// #945: project-wide search for authored function CALL SITES.
|
|
7
|
+
//
|
|
8
|
+
// A Blueprint audit ("who still calls this deprecated function", "which tasks
|
|
9
|
+
// call FinishExecute") previously meant enumerating every Blueprint, listing
|
|
10
|
+
// every graph, and reading or T3D-exporting each one. On a real project that is
|
|
11
|
+
// thousands of bridge calls, megabytes of payload, and enough traffic to
|
|
12
|
+
// overwhelm the bridge. This action does the whole sweep in one call.
|
|
13
|
+
//
|
|
14
|
+
// Being fast is the reason it exists, so the work is shed in this order:
|
|
15
|
+
//
|
|
16
|
+
// 1. The Asset Registry narrows to Blueprint assets under one directory,
|
|
17
|
+
// which costs no package loads at all.
|
|
18
|
+
// 2. When the declaring package of every requested function can be resolved,
|
|
19
|
+
// the registry's dependency graph rules out every candidate that does not
|
|
20
|
+
// reference that package. A Blueprint that never references /Script/AIModule
|
|
21
|
+
// cannot contain a call to a function declared there, so it is never
|
|
22
|
+
// loaded. This is where the bulk of a large project disappears.
|
|
23
|
+
// 3. Only what survives is loaded and walked.
|
|
24
|
+
//
|
|
25
|
+
// Step 2 is reported (narrowedByRegistry, blueprintsSkippedByRegistry) and can
|
|
26
|
+
// be switched off with narrowByRegistry=false, because a filter that silently
|
|
27
|
+
// drops a hit is worse than a slow search. It is also skipped automatically
|
|
28
|
+
// when any requested name has no resolvable declaring class, since the filter
|
|
29
|
+
// would then have nothing to match on for that name.
|
|
30
|
+
|
|
31
|
+
#include "BlueprintHandlers.h"
|
|
32
|
+
#include "BlueprintHandlers_Internal.h"
|
|
33
|
+
#include "HandlerRegistry.h"
|
|
34
|
+
#include "HandlerUtils.h"
|
|
35
|
+
#include "Engine/Blueprint.h"
|
|
36
|
+
#include "Engine/World.h"
|
|
37
|
+
#include "Engine/LevelScriptBlueprint.h"
|
|
38
|
+
#include "EdGraph/EdGraph.h"
|
|
39
|
+
#include "EdGraph/EdGraphPin.h"
|
|
40
|
+
#include "EdGraph/EdGraphNode.h"
|
|
41
|
+
#include "K2Node_CallFunction.h"
|
|
42
|
+
#include "K2Node_CallParentFunction.h"
|
|
43
|
+
#include "EdGraphSchema_K2.h"
|
|
44
|
+
#include "AssetRegistry/AssetRegistryModule.h"
|
|
45
|
+
#include "AssetRegistry/IAssetRegistry.h"
|
|
46
|
+
#include "AssetRegistry/ARFilter.h"
|
|
47
|
+
#include "AssetRegistry/AssetData.h"
|
|
48
|
+
#include "UObject/UObjectIterator.h"
|
|
49
|
+
#include "UObject/TopLevelAssetPath.h"
|
|
50
|
+
#include "UObject/UnrealType.h"
|
|
51
|
+
#include "Dom/JsonObject.h"
|
|
52
|
+
#include "Dom/JsonValue.h"
|
|
53
|
+
|
|
54
|
+
namespace
|
|
55
|
+
{
|
|
56
|
+
// Bounds. Every one of these exists because this action runs against an
|
|
57
|
+
// unknown-sized project and a caller cannot know in advance how big the
|
|
58
|
+
// answer is. Each is reported back when it bites.
|
|
59
|
+
constexpr int32 MaxRequestedFunctionNames = 50;
|
|
60
|
+
constexpr int32 DefaultCallSiteLimit = 200;
|
|
61
|
+
constexpr int32 MaxCallSiteLimit = 1000;
|
|
62
|
+
constexpr int32 DefaultMaxBlueprints = 2000;
|
|
63
|
+
constexpr int32 MaxMaxBlueprints = 20000;
|
|
64
|
+
constexpr int32 MaxCollectedHits = 5000;
|
|
65
|
+
|
|
66
|
+
// A pin default is only meaningful when nothing is wired into the pin: a
|
|
67
|
+
// literal is ignored the moment the pin is linked. Reporting linked pins as
|
|
68
|
+
// defaults would put a stale value in an audit.
|
|
69
|
+
bool IsReportablePinDefault(const UEdGraphPin* Pin)
|
|
70
|
+
{
|
|
71
|
+
if (!Pin || Pin->Direction != EGPD_Input) return false;
|
|
72
|
+
if (Pin->bHidden || Pin->bOrphanedPin) return false;
|
|
73
|
+
if (Pin->LinkedTo.Num() > 0) return false;
|
|
74
|
+
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) return false;
|
|
75
|
+
return !Pin->DefaultValue.IsEmpty()
|
|
76
|
+
|| !Pin->DefaultTextValue.IsEmpty()
|
|
77
|
+
|| Pin->DefaultObject != nullptr;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
TSharedPtr<FJsonObject> DescribePinDefault(const UEdGraphPin* Pin)
|
|
81
|
+
{
|
|
82
|
+
TSharedPtr<FJsonObject> PinObj = MakeShared<FJsonObject>();
|
|
83
|
+
PinObj->SetStringField(TEXT("name"), Pin->PinName.ToString());
|
|
84
|
+
PinObj->SetStringField(TEXT("type"), Pin->PinType.PinCategory.ToString());
|
|
85
|
+
if (!Pin->DefaultValue.IsEmpty())
|
|
86
|
+
{
|
|
87
|
+
PinObj->SetStringField(TEXT("defaultValue"), Pin->DefaultValue);
|
|
88
|
+
}
|
|
89
|
+
if (!Pin->DefaultTextValue.IsEmpty())
|
|
90
|
+
{
|
|
91
|
+
PinObj->SetStringField(TEXT("defaultTextValue"), Pin->DefaultTextValue.ToString());
|
|
92
|
+
}
|
|
93
|
+
if (Pin->DefaultObject)
|
|
94
|
+
{
|
|
95
|
+
PinObj->SetStringField(TEXT("defaultObject"), Pin->DefaultObject->GetPathName());
|
|
96
|
+
}
|
|
97
|
+
return PinObj;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
TSharedPtr<FJsonObject> DescribeNeighbourNode(const UEdGraphNode* Node)
|
|
101
|
+
{
|
|
102
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
103
|
+
Obj->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
|
104
|
+
Obj->SetStringField(TEXT("title"), Node->GetNodeTitle(ENodeTitleType::ListView).ToString());
|
|
105
|
+
Obj->SetStringField(TEXT("class"), Node->GetClass()->GetName());
|
|
106
|
+
return Obj;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The nodes immediately upstream and downstream, split by whether the link
|
|
110
|
+
// is execution or data. Enough to see the shape a call sits in without
|
|
111
|
+
// pulling the whole graph across.
|
|
112
|
+
void AppendNeighbours(const TSharedPtr<FJsonObject>& HitObj, const UEdGraphNode* Node)
|
|
113
|
+
{
|
|
114
|
+
TArray<TSharedPtr<FJsonValue>> ExecIn, ExecOut, DataIn, DataOut;
|
|
115
|
+
|
|
116
|
+
for (const UEdGraphPin* Pin : Node->Pins)
|
|
117
|
+
{
|
|
118
|
+
if (!Pin) continue;
|
|
119
|
+
const bool bExec = Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec;
|
|
120
|
+
for (const UEdGraphPin* Linked : Pin->LinkedTo)
|
|
121
|
+
{
|
|
122
|
+
if (!Linked || !Linked->GetOwningNodeUnchecked()) continue;
|
|
123
|
+
const UEdGraphNode* Other = Linked->GetOwningNodeUnchecked();
|
|
124
|
+
if (Other == Node) continue;
|
|
125
|
+
// Deduplicate per direction+kind bucket rather than globally:
|
|
126
|
+
// the same node can legitimately be both a data source and an
|
|
127
|
+
// exec successor, and both facts are worth reporting.
|
|
128
|
+
const bool bIncoming = Pin->Direction == EGPD_Input;
|
|
129
|
+
TArray<TSharedPtr<FJsonValue>>& Bucket =
|
|
130
|
+
bExec ? (bIncoming ? ExecIn : ExecOut) : (bIncoming ? DataIn : DataOut);
|
|
131
|
+
bool bAlready = false;
|
|
132
|
+
for (const TSharedPtr<FJsonValue>& Existing : Bucket)
|
|
133
|
+
{
|
|
134
|
+
const TSharedPtr<FJsonObject> ExistingObj = Existing->AsObject();
|
|
135
|
+
if (ExistingObj.IsValid() &&
|
|
136
|
+
ExistingObj->GetStringField(TEXT("nodeId")) == Other->NodeGuid.ToString())
|
|
137
|
+
{
|
|
138
|
+
bAlready = true;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (bAlready) continue;
|
|
143
|
+
Bucket.Add(MakeShared<FJsonValueObject>(DescribeNeighbourNode(Other)));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
TSharedPtr<FJsonObject> Neighbours = MakeShared<FJsonObject>();
|
|
148
|
+
Neighbours->SetArrayField(TEXT("execIn"), ExecIn);
|
|
149
|
+
Neighbours->SetArrayField(TEXT("execOut"), ExecOut);
|
|
150
|
+
Neighbours->SetArrayField(TEXT("dataIn"), DataIn);
|
|
151
|
+
Neighbours->SetArrayField(TEXT("dataOut"), DataOut);
|
|
152
|
+
HitObj->SetObjectField(TEXT("neighbours"), Neighbours);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Which packages declare the requested functions. This is the input to the
|
|
156
|
+
// registry narrowing: a Blueprint that does not depend on any of them
|
|
157
|
+
// cannot call any of them. bOutAllResolved is false as soon as one
|
|
158
|
+
// requested name has no declaring class anywhere in the loaded type system,
|
|
159
|
+
// which disables the narrowing rather than letting it drop that name's hits.
|
|
160
|
+
void CollectDeclaringPackages(
|
|
161
|
+
const TArray<FString>& FunctionNames,
|
|
162
|
+
UClass* FilterClass,
|
|
163
|
+
TSet<FName>& OutPackages,
|
|
164
|
+
bool& bOutAllResolved)
|
|
165
|
+
{
|
|
166
|
+
bOutAllResolved = true;
|
|
167
|
+
|
|
168
|
+
if (FilterClass)
|
|
169
|
+
{
|
|
170
|
+
// A class filter is the strongest narrowing there is: every hit has
|
|
171
|
+
// to be declared on it or a subclass, and a subclass lives in the
|
|
172
|
+
// same package or in one that already depends on it.
|
|
173
|
+
if (UPackage* Package = FilterClass->GetOutermost())
|
|
174
|
+
{
|
|
175
|
+
OutPackages.Add(Package->GetFName());
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
TArray<FName> WantedNames;
|
|
181
|
+
WantedNames.Reserve(FunctionNames.Num());
|
|
182
|
+
for (const FString& Name : FunctionNames)
|
|
183
|
+
{
|
|
184
|
+
WantedNames.Add(FName(*Name));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
TArray<bool> Found;
|
|
188
|
+
Found.Init(false, WantedNames.Num());
|
|
189
|
+
|
|
190
|
+
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
|
|
191
|
+
{
|
|
192
|
+
UClass* Class = *ClassIt;
|
|
193
|
+
if (!Class) continue;
|
|
194
|
+
for (int32 Index = 0; Index < WantedNames.Num(); ++Index)
|
|
195
|
+
{
|
|
196
|
+
// ExcludeSuper: we want the class that DECLARES the function,
|
|
197
|
+
// because that is the package a caller records a dependency on.
|
|
198
|
+
if (!Class->FindFunctionByName(WantedNames[Index], EIncludeSuperFlag::ExcludeSuper)) continue;
|
|
199
|
+
Found[Index] = true;
|
|
200
|
+
if (UPackage* Package = Class->GetOutermost())
|
|
201
|
+
{
|
|
202
|
+
OutPackages.Add(Package->GetFName());
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (bool bWasFound : Found)
|
|
208
|
+
{
|
|
209
|
+
if (!bWasFound)
|
|
210
|
+
{
|
|
211
|
+
bOutAllResolved = false;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
TSharedPtr<FJsonValue> FBlueprintHandlers::SearchCallSites(const TSharedPtr<FJsonObject>& Params)
|
|
219
|
+
{
|
|
220
|
+
// ── Arguments ───────────────────────────────────────────────────────────
|
|
221
|
+
const TArray<TSharedPtr<FJsonValue>>* NamesArray = nullptr;
|
|
222
|
+
if (!Params->TryGetArrayField(TEXT("functionNames"), NamesArray) || !NamesArray)
|
|
223
|
+
{
|
|
224
|
+
return MCPError(TEXT("Missing 'functionNames' (string array of function names to find call sites for)"));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
TArray<FString> FunctionNames;
|
|
228
|
+
TSet<FString> WantedLower;
|
|
229
|
+
for (const TSharedPtr<FJsonValue>& Value : *NamesArray)
|
|
230
|
+
{
|
|
231
|
+
FString Name;
|
|
232
|
+
if (!Value.IsValid() || !Value->TryGetString(Name)) continue;
|
|
233
|
+
Name.TrimStartAndEndInline();
|
|
234
|
+
if (Name.IsEmpty()) continue;
|
|
235
|
+
if (WantedLower.Contains(Name.ToLower())) continue;
|
|
236
|
+
FunctionNames.Add(Name);
|
|
237
|
+
WantedLower.Add(Name.ToLower());
|
|
238
|
+
}
|
|
239
|
+
if (FunctionNames.Num() == 0)
|
|
240
|
+
{
|
|
241
|
+
return MCPError(TEXT("'functionNames' is empty - name at least one function to find call sites for"));
|
|
242
|
+
}
|
|
243
|
+
if (FunctionNames.Num() > MaxRequestedFunctionNames)
|
|
244
|
+
{
|
|
245
|
+
return MCPError(FString::Printf(
|
|
246
|
+
TEXT("'functionNames' has %d entries, which is over the %d limit. Split the audit into batches."),
|
|
247
|
+
FunctionNames.Num(), MaxRequestedFunctionNames));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const FString ClassName = OptionalString(Params, TEXT("className"), TEXT(""));
|
|
251
|
+
UClass* FilterClass = nullptr;
|
|
252
|
+
if (!ClassName.IsEmpty())
|
|
253
|
+
{
|
|
254
|
+
FilterClass = MCPResolveClass(ClassName);
|
|
255
|
+
if (!FilterClass)
|
|
256
|
+
{
|
|
257
|
+
// Silently matching nothing would read as "no call sites", which is
|
|
258
|
+
// the wrong answer to a typo in an audit.
|
|
259
|
+
return MCPClassNotFoundError(ClassName);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
FString Directory = OptionalString(Params, TEXT("directory"), TEXT("/Game"));
|
|
264
|
+
Directory.TrimStartAndEndInline();
|
|
265
|
+
if (Directory.IsEmpty()) Directory = TEXT("/Game");
|
|
266
|
+
while (Directory.Len() > 1 && Directory.EndsWith(TEXT("/")))
|
|
267
|
+
{
|
|
268
|
+
Directory.LeftChopInline(1);
|
|
269
|
+
}
|
|
270
|
+
if (!Directory.StartsWith(TEXT("/")))
|
|
271
|
+
{
|
|
272
|
+
return MCPError(FString::Printf(
|
|
273
|
+
TEXT("'directory' must be a mount-rooted content path such as /Game or /Game/AI, got '%s'"), *Directory));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const bool bIncludeNestedGraphs = OptionalBool(Params, TEXT("includeNestedGraphs"), true);
|
|
277
|
+
// Off by default: it loads map packages, which is far more expensive than
|
|
278
|
+
// loading Blueprints and cannot be narrowed by the registry the same way.
|
|
279
|
+
const bool bIncludeLevelScripts = OptionalBool(Params, TEXT("includeLevelScripts"), false);
|
|
280
|
+
const bool bIncludeNeighbours = OptionalBool(Params, TEXT("includeNeighbours"), false);
|
|
281
|
+
const bool bNarrowByRegistry = OptionalBool(Params, TEXT("narrowByRegistry"), true);
|
|
282
|
+
const bool bDumpToFile = OptionalBool(Params, TEXT("dumpToFile"), false);
|
|
283
|
+
const FString OutputPath = OptionalString(Params, TEXT("outputPath"), TEXT(""));
|
|
284
|
+
|
|
285
|
+
const int32 Offset = FMath::Max(0, OptionalInt(Params, TEXT("offset"), 0));
|
|
286
|
+
const int32 Limit = FMath::Clamp(
|
|
287
|
+
OptionalInt(Params, TEXT("limit"), DefaultCallSiteLimit), 1, MaxCallSiteLimit);
|
|
288
|
+
const int32 MaxBlueprints = FMath::Clamp(
|
|
289
|
+
OptionalInt(Params, TEXT("maxBlueprints"), DefaultMaxBlueprints), 1, MaxMaxBlueprints);
|
|
290
|
+
|
|
291
|
+
// ── Registry pass: candidates, then narrowing ───────────────────────────
|
|
292
|
+
FAssetRegistryModule& AssetRegistryModule =
|
|
293
|
+
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
294
|
+
IAssetRegistry& Registry = AssetRegistryModule.Get();
|
|
295
|
+
|
|
296
|
+
// An audit run while the registry is still scanning would silently miss
|
|
297
|
+
// every asset it has not reached yet, and report the shortfall as "no call
|
|
298
|
+
// sites". Waiting is the correct trade here: this handler carries its own
|
|
299
|
+
// long timeout precisely because it is allowed to be slow, and a wrong
|
|
300
|
+
// answer to "does anything still call this" is worse than a slow one.
|
|
301
|
+
const bool bWaitedForRegistry = Registry.IsLoadingAssets();
|
|
302
|
+
if (bWaitedForRegistry)
|
|
303
|
+
{
|
|
304
|
+
Registry.WaitForCompletion();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
FARFilter Filter;
|
|
308
|
+
Filter.PackagePaths.Add(FName(*Directory));
|
|
309
|
+
Filter.bRecursivePaths = true;
|
|
310
|
+
Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
|
|
311
|
+
// Anim and widget Blueprints are UBlueprint subclasses holding callable
|
|
312
|
+
// graphs, so the search would miss them without recursion. Level scripts
|
|
313
|
+
// are NOT covered by this filter: they are subobjects of a map package, not
|
|
314
|
+
// assets of their own, so the registry never lists one. includeLevelScripts
|
|
315
|
+
// below sweeps the World assets for them separately (#942).
|
|
316
|
+
Filter.bRecursiveClasses = true;
|
|
317
|
+
|
|
318
|
+
TArray<FAssetData> Candidates;
|
|
319
|
+
Registry.GetAssets(Filter, Candidates);
|
|
320
|
+
const int32 BlueprintsInDirectory = Candidates.Num();
|
|
321
|
+
|
|
322
|
+
TSet<FName> DeclaringPackages;
|
|
323
|
+
bool bAllNamesResolved = false;
|
|
324
|
+
CollectDeclaringPackages(FunctionNames, FilterClass, DeclaringPackages, bAllNamesResolved);
|
|
325
|
+
if (FilterClass)
|
|
326
|
+
{
|
|
327
|
+
// A resolved class filter always gives a usable package to match on.
|
|
328
|
+
bAllNamesResolved = true;
|
|
329
|
+
}
|
|
330
|
+
const bool bNarrowing = bNarrowByRegistry && bAllNamesResolved && DeclaringPackages.Num() > 0;
|
|
331
|
+
|
|
332
|
+
int32 SkippedByRegistry = 0;
|
|
333
|
+
TArray<FAssetData> ToScan;
|
|
334
|
+
ToScan.Reserve(Candidates.Num());
|
|
335
|
+
for (const FAssetData& Candidate : Candidates)
|
|
336
|
+
{
|
|
337
|
+
if (bNarrowing)
|
|
338
|
+
{
|
|
339
|
+
// A Blueprint that declares the function itself records no
|
|
340
|
+
// dependency on its own package, so that case is checked directly.
|
|
341
|
+
bool bKeep = DeclaringPackages.Contains(Candidate.PackageName);
|
|
342
|
+
if (!bKeep)
|
|
343
|
+
{
|
|
344
|
+
TArray<FName> Dependencies;
|
|
345
|
+
Registry.GetDependencies(
|
|
346
|
+
Candidate.PackageName, Dependencies, UE::AssetRegistry::EDependencyCategory::Package);
|
|
347
|
+
for (const FName& Dependency : Dependencies)
|
|
348
|
+
{
|
|
349
|
+
if (DeclaringPackages.Contains(Dependency))
|
|
350
|
+
{
|
|
351
|
+
bKeep = true;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (!bKeep)
|
|
357
|
+
{
|
|
358
|
+
++SkippedByRegistry;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
ToScan.Add(Candidate);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const bool bTruncatedAtMaxBlueprints = ToScan.Num() > MaxBlueprints;
|
|
366
|
+
if (bTruncatedAtMaxBlueprints)
|
|
367
|
+
{
|
|
368
|
+
ToScan.SetNum(MaxBlueprints);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ── Load and walk ───────────────────────────────────────────────────────
|
|
372
|
+
TArray<TSharedPtr<FJsonValue>> Hits;
|
|
373
|
+
TArray<FString> FailedToLoad;
|
|
374
|
+
int32 BlueprintsLoaded = 0;
|
|
375
|
+
int32 GraphsScanned = 0;
|
|
376
|
+
int32 NodesScanned = 0;
|
|
377
|
+
bool bTruncatedAtMaxHits = false;
|
|
378
|
+
|
|
379
|
+
// One walk, used for Blueprint assets and for the level scripts below, so
|
|
380
|
+
// the two report identical hit records rather than nearly identical ones.
|
|
381
|
+
auto ScanBlueprint = [&](UBlueprint* Blueprint, const FName PackageName)
|
|
382
|
+
{
|
|
383
|
+
// Selectors are always computed over EVERY graph, even when nested
|
|
384
|
+
// graphs are not being scanned, so a reported selector always matches
|
|
385
|
+
// what list_graphs would say about the same Blueprint.
|
|
386
|
+
TArray<UEdGraph*> AllGraphs;
|
|
387
|
+
Blueprint->GetAllGraphs(AllGraphs);
|
|
388
|
+
TMap<FString, int32> NameCounts;
|
|
389
|
+
CountGraphNames(AllGraphs, NameCounts);
|
|
390
|
+
TMap<FString, int32> SeenCounts;
|
|
391
|
+
|
|
392
|
+
for (UEdGraph* Graph : AllGraphs)
|
|
393
|
+
{
|
|
394
|
+
if (!Graph) continue;
|
|
395
|
+
const FString GraphName = Graph->GetName();
|
|
396
|
+
const int32 DuplicateIndex = SeenCounts.FindOrAdd(GraphName)++;
|
|
397
|
+
const FString Selector =
|
|
398
|
+
MakeGraphSelector(GraphName, DuplicateIndex, NameCounts.FindRef(GraphName));
|
|
399
|
+
|
|
400
|
+
// A top-level graph is owned by the Blueprint. Collapsed graphs,
|
|
401
|
+
// state machine graphs and transition graphs hang off a node or a
|
|
402
|
+
// parent graph instead, which is what "nested" means here.
|
|
403
|
+
const bool bNested = Graph->GetOuter() != Blueprint;
|
|
404
|
+
if (bNested && !bIncludeNestedGraphs) continue;
|
|
405
|
+
|
|
406
|
+
++GraphsScanned;
|
|
407
|
+
for (UEdGraphNode* Node : Graph->Nodes)
|
|
408
|
+
{
|
|
409
|
+
if (!Node) continue;
|
|
410
|
+
++NodesScanned;
|
|
411
|
+
|
|
412
|
+
UK2Node_CallFunction* Call = Cast<UK2Node_CallFunction>(Node);
|
|
413
|
+
if (!Call) continue;
|
|
414
|
+
|
|
415
|
+
// The member name survives even when the target function no
|
|
416
|
+
// longer resolves, which is exactly the case an audit hunting
|
|
417
|
+
// a removed function cares about most.
|
|
418
|
+
const FString MemberName = Call->FunctionReference.GetMemberName().ToString();
|
|
419
|
+
UFunction* Target = Call->GetTargetFunction();
|
|
420
|
+
const FString ResolvedName = Target ? Target->GetName() : FString();
|
|
421
|
+
|
|
422
|
+
if (!WantedLower.Contains(MemberName.ToLower()) &&
|
|
423
|
+
(ResolvedName.IsEmpty() || !WantedLower.Contains(ResolvedName.ToLower())))
|
|
424
|
+
{
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
UClass* DeclaringClass = Target
|
|
429
|
+
? Target->GetOwnerClass()
|
|
430
|
+
: Call->FunctionReference.GetMemberParentClass();
|
|
431
|
+
if (FilterClass && (!DeclaringClass || !DeclaringClass->IsChildOf(FilterClass)))
|
|
432
|
+
{
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
TSharedPtr<FJsonObject> HitObj = MakeShared<FJsonObject>();
|
|
437
|
+
HitObj->SetStringField(TEXT("assetPath"), Blueprint->GetPathName());
|
|
438
|
+
HitObj->SetStringField(TEXT("packageName"), PackageName.ToString());
|
|
439
|
+
HitObj->SetBoolField(TEXT("levelScript"), Blueprint->IsA<ULevelScriptBlueprint>());
|
|
440
|
+
HitObj->SetStringField(TEXT("graphName"), GraphName);
|
|
441
|
+
HitObj->SetStringField(TEXT("graphSelector"), Selector);
|
|
442
|
+
HitObj->SetStringField(TEXT("graphObjectPath"), Graph->GetPathName());
|
|
443
|
+
HitObj->SetBoolField(TEXT("nestedGraph"), bNested);
|
|
444
|
+
HitObj->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
|
445
|
+
HitObj->SetStringField(TEXT("nodeTitle"), Node->GetNodeTitle(ENodeTitleType::ListView).ToString());
|
|
446
|
+
HitObj->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName());
|
|
447
|
+
HitObj->SetStringField(TEXT("memberName"), MemberName);
|
|
448
|
+
if (!ResolvedName.IsEmpty())
|
|
449
|
+
{
|
|
450
|
+
HitObj->SetStringField(TEXT("resolvedFunction"), ResolvedName);
|
|
451
|
+
}
|
|
452
|
+
else
|
|
453
|
+
{
|
|
454
|
+
// An unresolved target is a finding in its own right: the
|
|
455
|
+
// node is authored but the function it names is gone.
|
|
456
|
+
HitObj->SetBoolField(TEXT("unresolvedTarget"), true);
|
|
457
|
+
}
|
|
458
|
+
if (DeclaringClass)
|
|
459
|
+
{
|
|
460
|
+
HitObj->SetStringField(TEXT("declaringClass"), DeclaringClass->GetName());
|
|
461
|
+
HitObj->SetStringField(TEXT("declaringClassPath"), DeclaringClass->GetPathName());
|
|
462
|
+
}
|
|
463
|
+
if (Node->IsA<UK2Node_CallParentFunction>())
|
|
464
|
+
{
|
|
465
|
+
HitObj->SetBoolField(TEXT("parentCall"), true);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
TArray<TSharedPtr<FJsonValue>> PinDefaults;
|
|
469
|
+
for (const UEdGraphPin* Pin : Node->Pins)
|
|
470
|
+
{
|
|
471
|
+
if (IsReportablePinDefault(Pin))
|
|
472
|
+
{
|
|
473
|
+
PinDefaults.Add(MakeShared<FJsonValueObject>(DescribePinDefault(Pin)));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
HitObj->SetArrayField(TEXT("pinDefaults"), PinDefaults);
|
|
477
|
+
|
|
478
|
+
if (bIncludeNeighbours)
|
|
479
|
+
{
|
|
480
|
+
AppendNeighbours(HitObj, Node);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
Hits.Add(MakeShared<FJsonValueObject>(HitObj));
|
|
484
|
+
if (Hits.Num() >= MaxCollectedHits)
|
|
485
|
+
{
|
|
486
|
+
bTruncatedAtMaxHits = true;
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
for (const FAssetData& Candidate : ToScan)
|
|
494
|
+
{
|
|
495
|
+
if (bTruncatedAtMaxHits) break;
|
|
496
|
+
|
|
497
|
+
UBlueprint* Blueprint = LoadAssetByPath<UBlueprint>(Candidate.GetObjectPathString());
|
|
498
|
+
if (!Blueprint)
|
|
499
|
+
{
|
|
500
|
+
if (FailedToLoad.Num() < 25)
|
|
501
|
+
{
|
|
502
|
+
FailedToLoad.Add(Candidate.GetObjectPathString());
|
|
503
|
+
}
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
++BlueprintsLoaded;
|
|
507
|
+
ScanBlueprint(Blueprint, Candidate.PackageName);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// #942 + #945: level scripts hold authored call nodes like any other graph,
|
|
511
|
+
// but they are subobjects of a map package rather than assets, so the
|
|
512
|
+
// Blueprint filter above cannot see them. Opt in, because reaching one
|
|
513
|
+
// means loading the whole map: a World asset carries every actor in it.
|
|
514
|
+
int32 WorldsInDirectory = 0;
|
|
515
|
+
int32 LevelScriptsScanned = 0;
|
|
516
|
+
if (bIncludeLevelScripts && !bTruncatedAtMaxHits)
|
|
517
|
+
{
|
|
518
|
+
FARFilter WorldFilter;
|
|
519
|
+
WorldFilter.PackagePaths.Add(FName(*Directory));
|
|
520
|
+
WorldFilter.bRecursivePaths = true;
|
|
521
|
+
WorldFilter.ClassPaths.Add(UWorld::StaticClass()->GetClassPathName());
|
|
522
|
+
|
|
523
|
+
TArray<FAssetData> Worlds;
|
|
524
|
+
Registry.GetAssets(WorldFilter, Worlds);
|
|
525
|
+
WorldsInDirectory = Worlds.Num();
|
|
526
|
+
|
|
527
|
+
for (const FAssetData& World : Worlds)
|
|
528
|
+
{
|
|
529
|
+
if (bTruncatedAtMaxHits) break;
|
|
530
|
+
if (LevelScriptsScanned >= MaxBlueprints) break;
|
|
531
|
+
|
|
532
|
+
// LoadBlueprint resolves a World path to its level script, which is
|
|
533
|
+
// the same alias read/list_graphs/read_graph accept (#942).
|
|
534
|
+
UBlueprint* LevelScript = LoadBlueprint(World.GetObjectPathString());
|
|
535
|
+
if (!LevelScript) continue;
|
|
536
|
+
++LevelScriptsScanned;
|
|
537
|
+
ScanBlueprint(LevelScript, World.PackageName);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ── Response ────────────────────────────────────────────────────────────
|
|
542
|
+
auto BuildResult = [&](int32 SliceStart, int32 SliceEnd) -> TSharedPtr<FJsonObject>
|
|
543
|
+
{
|
|
544
|
+
TArray<TSharedPtr<FJsonValue>> Slice;
|
|
545
|
+
for (int32 Index = SliceStart; Index < SliceEnd; ++Index)
|
|
546
|
+
{
|
|
547
|
+
Slice.Add(Hits[Index]);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
TSharedPtr<FJsonObject> Obj = MCPSuccess();
|
|
551
|
+
TArray<TSharedPtr<FJsonValue>> RequestedNames;
|
|
552
|
+
for (const FString& Name : FunctionNames)
|
|
553
|
+
{
|
|
554
|
+
RequestedNames.Add(MakeShared<FJsonValueString>(Name));
|
|
555
|
+
}
|
|
556
|
+
Obj->SetArrayField(TEXT("functionNames"), RequestedNames);
|
|
557
|
+
if (FilterClass)
|
|
558
|
+
{
|
|
559
|
+
Obj->SetStringField(TEXT("className"), FilterClass->GetPathName());
|
|
560
|
+
}
|
|
561
|
+
Obj->SetStringField(TEXT("directory"), Directory);
|
|
562
|
+
Obj->SetBoolField(TEXT("includeNestedGraphs"), bIncludeNestedGraphs);
|
|
563
|
+
Obj->SetBoolField(TEXT("includeLevelScripts"), bIncludeLevelScripts);
|
|
564
|
+
Obj->SetArrayField(TEXT("callSites"), Slice);
|
|
565
|
+
Obj->SetNumberField(TEXT("returned"), Slice.Num());
|
|
566
|
+
Obj->SetNumberField(TEXT("total"), Hits.Num());
|
|
567
|
+
Obj->SetNumberField(TEXT("offset"), SliceStart);
|
|
568
|
+
Obj->SetNumberField(TEXT("limit"), SliceEnd - SliceStart);
|
|
569
|
+
Obj->SetBoolField(TEXT("hasMore"), SliceEnd < Hits.Num());
|
|
570
|
+
Obj->SetNumberField(TEXT("nextOffset"), SliceEnd < Hits.Num() ? SliceEnd : -1);
|
|
571
|
+
|
|
572
|
+
// The work that was done and the work that was avoided, so a caller can
|
|
573
|
+
// tell a genuinely empty result from an over-eager filter.
|
|
574
|
+
TSharedPtr<FJsonObject> Stats = MakeShared<FJsonObject>();
|
|
575
|
+
Stats->SetNumberField(TEXT("blueprintsInDirectory"), BlueprintsInDirectory);
|
|
576
|
+
Stats->SetNumberField(TEXT("blueprintsSkippedByRegistry"), SkippedByRegistry);
|
|
577
|
+
Stats->SetNumberField(TEXT("blueprintsConsidered"), ToScan.Num());
|
|
578
|
+
Stats->SetNumberField(TEXT("blueprintsLoaded"), BlueprintsLoaded);
|
|
579
|
+
Stats->SetNumberField(TEXT("graphsScanned"), GraphsScanned);
|
|
580
|
+
Stats->SetNumberField(TEXT("nodesScanned"), NodesScanned);
|
|
581
|
+
Stats->SetBoolField(TEXT("narrowedByRegistry"), bNarrowing);
|
|
582
|
+
Stats->SetBoolField(TEXT("waitedForAssetRegistryScan"), bWaitedForRegistry);
|
|
583
|
+
Stats->SetBoolField(TEXT("includedLevelScripts"), bIncludeLevelScripts);
|
|
584
|
+
if (bIncludeLevelScripts)
|
|
585
|
+
{
|
|
586
|
+
Stats->SetNumberField(TEXT("worldsInDirectory"), WorldsInDirectory);
|
|
587
|
+
Stats->SetNumberField(TEXT("levelScriptsScanned"), LevelScriptsScanned);
|
|
588
|
+
}
|
|
589
|
+
if (!bNarrowing && bNarrowByRegistry)
|
|
590
|
+
{
|
|
591
|
+
Stats->SetStringField(TEXT("narrowingSkippedReason"),
|
|
592
|
+
DeclaringPackages.Num() == 0
|
|
593
|
+
? TEXT("no declaring class was found for the requested function names, so every Blueprint under the directory was loaded")
|
|
594
|
+
: TEXT("at least one requested function name has no declaring class in the loaded type system, so narrowing would have dropped its call sites"));
|
|
595
|
+
}
|
|
596
|
+
if (FailedToLoad.Num() > 0)
|
|
597
|
+
{
|
|
598
|
+
TArray<TSharedPtr<FJsonValue>> FailedArray;
|
|
599
|
+
for (const FString& Failed : FailedToLoad)
|
|
600
|
+
{
|
|
601
|
+
FailedArray.Add(MakeShared<FJsonValueString>(Failed));
|
|
602
|
+
}
|
|
603
|
+
Stats->SetArrayField(TEXT("failedToLoad"), FailedArray);
|
|
604
|
+
}
|
|
605
|
+
Obj->SetObjectField(TEXT("stats"), Stats);
|
|
606
|
+
|
|
607
|
+
if (bTruncatedAtMaxBlueprints)
|
|
608
|
+
{
|
|
609
|
+
Obj->SetBoolField(TEXT("truncatedAtMaxBlueprints"), true);
|
|
610
|
+
Obj->SetNumberField(TEXT("maxBlueprints"), MaxBlueprints);
|
|
611
|
+
}
|
|
612
|
+
if (bTruncatedAtMaxHits)
|
|
613
|
+
{
|
|
614
|
+
Obj->SetBoolField(TEXT("truncatedAtMaxHits"), true);
|
|
615
|
+
Obj->SetNumberField(TEXT("maxHits"), MaxCollectedHits);
|
|
616
|
+
}
|
|
617
|
+
return Obj;
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const int32 SliceStart = FMath::Min(Offset, Hits.Num());
|
|
621
|
+
const int32 SliceEnd = FMath::Min(SliceStart + Limit, Hits.Num());
|
|
622
|
+
|
|
623
|
+
if (bDumpToFile)
|
|
624
|
+
{
|
|
625
|
+
// Same convention as read_graph: the file holds the whole result set,
|
|
626
|
+
// the response holds where to find it.
|
|
627
|
+
const TSharedPtr<FJsonObject> DumpResult = BuildResult(0, Hits.Num());
|
|
628
|
+
FString ResolvedDumpPath;
|
|
629
|
+
FString DumpError;
|
|
630
|
+
if (!WriteJsonObjectToFile(DumpResult, OutputPath, Directory, TEXT("call_sites"), ResolvedDumpPath, DumpError))
|
|
631
|
+
{
|
|
632
|
+
return MCPError(DumpError);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
auto Result = MCPSuccess();
|
|
636
|
+
Result->SetStringField(TEXT("directory"), Directory);
|
|
637
|
+
Result->SetBoolField(TEXT("dumpedToFile"), true);
|
|
638
|
+
Result->SetStringField(TEXT("outputPath"), ResolvedDumpPath);
|
|
639
|
+
Result->SetNumberField(TEXT("total"), Hits.Num());
|
|
640
|
+
Result->SetObjectField(TEXT("stats"), DumpResult->GetObjectField(TEXT("stats")));
|
|
641
|
+
if (bTruncatedAtMaxBlueprints) Result->SetBoolField(TEXT("truncatedAtMaxBlueprints"), true);
|
|
642
|
+
if (bTruncatedAtMaxHits) Result->SetBoolField(TEXT("truncatedAtMaxHits"), true);
|
|
643
|
+
return MCPResult(Result);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return MCPResult(BuildResult(SliceStart, SliceEnd));
|
|
647
|
+
}
|
|
648
|
+
|
|
@@ -1215,7 +1215,11 @@ TSharedPtr<FJsonObject> FDemoHandlers::StepLevelSequence()
|
|
|
1215
1215
|
UMovieScene* MovieScene = Seq->GetMovieScene();
|
|
1216
1216
|
if (MovieScene)
|
|
1217
1217
|
{
|
|
1218
|
-
|
|
1218
|
+
// The demo owns this label, so a single match is expected. Taking the
|
|
1219
|
+
// first of several would still be a guess, so it takes none (#983).
|
|
1220
|
+
TArray<AActor*> HeroMatches;
|
|
1221
|
+
MCPCollectActorsByToken(World, TEXT("Demo_HeroSphere"), EMCPActorMatch::Label, HeroMatches);
|
|
1222
|
+
AActor* HeroSphere = HeroMatches.Num() == 1 ? HeroMatches[0] : nullptr;
|
|
1219
1223
|
if (HeroSphere)
|
|
1220
1224
|
{
|
|
1221
1225
|
FGuid BindingGuid = MovieScene->AddPossessable(
|