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