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
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
#include "Components/AudioComponent.h"
|
|
15
15
|
#include "Sound/SoundBase.h"
|
|
16
16
|
#include "Components/InstancedStaticMeshComponent.h"
|
|
17
|
+
// #986: get_component_tree distinguishes HISM from plain ISM, because per
|
|
18
|
+
// instance culling and LOD change what an edit to one costs.
|
|
19
|
+
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
|
|
17
20
|
#include "Engine/StaticMesh.h"
|
|
18
21
|
#include "Engine/SkeletalMesh.h"
|
|
19
22
|
#include "Animation/SkeletalMeshActor.h"
|
|
@@ -86,6 +89,12 @@
|
|
|
86
89
|
#include "Engine/SkeletalMesh.h"
|
|
87
90
|
#include "Materials/MaterialInterface.h"
|
|
88
91
|
#include "HandlerJsonProperty.h"
|
|
92
|
+
#include "Engine/Blueprint.h"
|
|
93
|
+
#include "Engine/LevelScriptBlueprint.h"
|
|
94
|
+
#include "EdGraph/EdGraph.h"
|
|
95
|
+
#include "EdGraph/EdGraphNode.h"
|
|
96
|
+
#include "Kismet2/BlueprintEditorUtils.h"
|
|
97
|
+
#include "Kismet2/KismetEditorUtilities.h"
|
|
89
98
|
|
|
90
99
|
void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
91
100
|
{
|
|
@@ -98,6 +107,10 @@ void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
98
107
|
Registry.RegisterHandler(TEXT("get_component_tree"), &GetComponentTree);
|
|
99
108
|
Registry.RegisterHandler(TEXT("get_relative_transform"), &GetRelativeTransform);
|
|
100
109
|
Registry.RegisterHandler(TEXT("get_current_level"), &GetCurrentLevel);
|
|
110
|
+
// #964: LevelHandlers_Save.cpp. Saves through the same package path
|
|
111
|
+
// editor(save_dirty) uses, so the two cannot disagree about one package,
|
|
112
|
+
// and reports the package, the file and the engine's own reason on failure.
|
|
113
|
+
Registry.RegisterHandler(TEXT("save_level"), &SaveLevel);
|
|
101
114
|
Registry.RegisterHandler(TEXT("list_levels"), &ListLevels);
|
|
102
115
|
Registry.RegisterHandler(TEXT("get_selected_actors"), &GetSelectedActors);
|
|
103
116
|
Registry.RegisterHandler(TEXT("list_volumes"), &ListVolumes);
|
|
@@ -111,7 +124,9 @@ void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
111
124
|
Registry.RegisterHandler(TEXT("add_component_to_actor"), &AddComponentToActor);
|
|
112
125
|
Registry.RegisterHandler(TEXT("remove_component_from_actor"), &RemoveComponentFromActor);
|
|
113
126
|
Registry.RegisterHandler(TEXT("load_level"), &LoadLevel);
|
|
127
|
+
Registry.RegisterHandler(TEXT("clear_level_script"), &ClearLevelScript);
|
|
114
128
|
Registry.RegisterHandler(TEXT("set_component_property"), &SetComponentProperty);
|
|
129
|
+
Registry.RegisterHandler(TEXT("nudge_component"), &NudgeComponent);
|
|
115
130
|
Registry.RegisterHandler(TEXT("get_component_details"), &GetComponentDetails);
|
|
116
131
|
Registry.RegisterHandler(TEXT("set_actor_material"), &SetActorMaterial);
|
|
117
132
|
Registry.RegisterHandler(TEXT("set_volume_properties"), &SetVolumeProperties);
|
|
@@ -120,6 +135,12 @@ void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
120
135
|
Registry.RegisterHandler(TEXT("set_fog_properties"), &SetFogProperties);
|
|
121
136
|
Registry.RegisterHandler(TEXT("get_actors_by_class"), &GetActorsByClass);
|
|
122
137
|
Registry.RegisterHandler(TEXT("get_actors_by_component_class"), &GetActorsByComponentClass);
|
|
138
|
+
// Same budget as query_components, and for the same reason: this is a full
|
|
139
|
+
// TActorIterator pass on the game thread. It additionally sorts every actor
|
|
140
|
+
// by path name and sorts each actor's components, so it is the heavier of
|
|
141
|
+
// the two whole-map scans and had no business inheriting the 30 second
|
|
142
|
+
// default. Mirrored in src/bridge-timeouts.ts, which a parity test checks.
|
|
143
|
+
Registry.RegisterHandlerWithTimeout(TEXT("summarize_static_mesh_usage"), &SummarizeStaticMeshUsage, 300.0f);
|
|
123
144
|
Registry.RegisterHandler(TEXT("count_actors_by_class"), &CountActorsByClass);
|
|
124
145
|
Registry.RegisterHandler(TEXT("get_runtime_virtual_texture_summary"), &GetRVTSummary);
|
|
125
146
|
Registry.RegisterHandler(TEXT("set_water_body_property"), &SetWaterBodyProperty);
|
|
@@ -127,6 +148,7 @@ void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
127
148
|
Registry.RegisterHandler(TEXT("resolve_actor"), &ResolveActor);
|
|
128
149
|
Registry.RegisterHandler(TEXT("set_actor_property"), &SetActorProperty);
|
|
129
150
|
Registry.RegisterHandler(TEXT("line_trace"), &LineTrace);
|
|
151
|
+
Registry.RegisterHandler(TEXT("bulk_line_trace"), &BulkLineTrace);
|
|
130
152
|
// #453: per-actor motion snapshot for telemetry probes. Reads location,
|
|
131
153
|
// rotation, velocity, angular velocity, scale, and ground state in one
|
|
132
154
|
// call. Caller is expected to invoke at the desired sample interval.
|
|
@@ -139,6 +161,11 @@ void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
139
161
|
Registry.RegisterHandler(TEXT("get_instance_transforms"), &GetInstanceTransforms);
|
|
140
162
|
Registry.RegisterHandler(TEXT("update_instance_transform"), &UpdateInstanceTransform);
|
|
141
163
|
Registry.RegisterHandler(TEXT("remove_instance"), &RemoveInstance);
|
|
164
|
+
// Native bridge method only in this plugin-scoped change. It appears in
|
|
165
|
+
// get_bridge_capabilities.actions and can be called directly over JSON-RPC
|
|
166
|
+
// (or a UeMcpTask bridge.call). A first-class category action also requires a
|
|
167
|
+
// server schema wrapper, which intentionally lives outside this plugin.
|
|
168
|
+
Registry.RegisterHandlerWithTimeout(TEXT("snap_instances_to_surface"), &SnapInstancesToSurface, 300.0f);
|
|
142
169
|
// #696: enable + force-build Nanite on a static mesh.
|
|
143
170
|
Registry.RegisterHandler(TEXT("set_nanite_settings"), &SetNaniteSettings);
|
|
144
171
|
Registry.RegisterHandler(TEXT("get_nanite_info"), &GetNaniteInfo);
|
|
@@ -147,6 +174,11 @@ void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
147
174
|
Registry.RegisterHandler(TEXT("place_skeletal_actor"), &SpawnSkeletalMeshActor);
|
|
148
175
|
// #666: add a material blendable to a PostProcessVolume.
|
|
149
176
|
Registry.RegisterHandler(TEXT("add_post_process_blendable"), &AddPostProcessBlendable);
|
|
177
|
+
// #950: LevelHandlers_PostProcess.cpp. The value half and the bOverride_ half
|
|
178
|
+
// of FPostProcessSettings, written together.
|
|
179
|
+
Registry.RegisterHandler(TEXT("set_post_process_settings"), &SetPostProcessSettings);
|
|
180
|
+
Registry.RegisterHandler(TEXT("get_post_process_settings"), &GetPostProcessSettings);
|
|
181
|
+
Registry.RegisterHandler(TEXT("set_fixed_exposure"), &SetFixedExposure);
|
|
150
182
|
// #637: export a selected actor's mesh to FBX + metadata sidecar.
|
|
151
183
|
Registry.RegisterHandler(TEXT("export_actor_fbx"), &ExportActorFbx);
|
|
152
184
|
Registry.RegisterHandler(TEXT("snap_actor_to_floor"), &SnapActorToFloor);
|
|
@@ -155,6 +187,16 @@ void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
155
187
|
Registry.RegisterHandler(TEXT("set_actor_folder_path"), &SetActorFolderPath);
|
|
156
188
|
Registry.RegisterHandler(TEXT("list_actor_descs"), &ListActorDescs);
|
|
157
189
|
Registry.RegisterHandlerWithTimeout(TEXT("load_actor_descs"), &LoadActorDescs, 300.0f);
|
|
190
|
+
// #985: LevelHandlers_WorldPartitionSettings.cpp. The streaming knobs and
|
|
191
|
+
// the runtime cell transformer stack live with the other World Partition
|
|
192
|
+
// actions rather than in a category of their own.
|
|
193
|
+
Registry.RegisterHandler(TEXT("get_world_partition_settings"), &GetWorldPartitionSettings);
|
|
194
|
+
Registry.RegisterHandler(TEXT("set_world_partition_settings"), &SetWorldPartitionSettings);
|
|
195
|
+
Registry.RegisterHandler(TEXT("add_runtime_cell_transformer"), &AddRuntimeCellTransformer);
|
|
196
|
+
// #985: bulk HLOD layer assignment. A whole-map selector, so it takes the
|
|
197
|
+
// same 300 second budget as the other batch writes. Mirrored in
|
|
198
|
+
// src/bridge-timeouts.ts, which a parity test checks.
|
|
199
|
+
Registry.RegisterHandlerWithTimeout(TEXT("set_actor_hlod_layer"), &SetActorHLODLayer, 300.0f);
|
|
158
200
|
Registry.RegisterHandler(TEXT("add_actor_tag"), &AddActorTag);
|
|
159
201
|
Registry.RegisterHandler(TEXT("remove_actor_tag"), &RemoveActorTag);
|
|
160
202
|
Registry.RegisterHandler(TEXT("set_actor_tags"), &SetActorTags);
|
|
@@ -173,6 +215,31 @@ void FLevelHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
173
215
|
Registry.RegisterHandler(TEXT("spawn_grid"), &SpawnGrid);
|
|
174
216
|
Registry.RegisterHandler(TEXT("batch_translate"), &BatchTranslate);
|
|
175
217
|
Registry.RegisterHandler(TEXT("place_actors_batch"), &PlaceActorsBatch);
|
|
218
|
+
// #910/#943/#912: the general editor-side component query. A whole-map
|
|
219
|
+
// scan with a projection can take a while on a 4,000 actor level, so it
|
|
220
|
+
// gets its own timeout rather than the 30 second default.
|
|
221
|
+
Registry.RegisterHandlerWithTimeout(TEXT("query_components"), &QueryComponents, 300.0f);
|
|
222
|
+
// #984/#941/#907/#987: level-wide writes driven by an editor-side selector.
|
|
223
|
+
// Each can touch thousands of actors, so each gets its own timeout.
|
|
224
|
+
Registry.RegisterHandlerWithTimeout(TEXT("batch_set_actor_properties"), &BatchSetActorProperties, 300.0f);
|
|
225
|
+
Registry.RegisterHandlerWithTimeout(TEXT("bulk_set_component_property"), &BulkSetComponentProperty, 300.0f);
|
|
226
|
+
Registry.RegisterHandlerWithTimeout(TEXT("remove_components_by_class"), &RemoveComponentsByClass, 300.0f);
|
|
227
|
+
Registry.RegisterHandlerWithTimeout(TEXT("spawn_actors_batch"), &SpawnActorsBatch, 300.0f);
|
|
228
|
+
// #944/#915/#914: refresh state the editor is caching, and read the bounds
|
|
229
|
+
// a caller needs to check the result.
|
|
230
|
+
Registry.RegisterHandlerWithTimeout(TEXT("rerun_construction_scripts"), &RerunConstruction, 300.0f);
|
|
231
|
+
Registry.RegisterHandlerWithTimeout(TEXT("recreate_physics_state"), &RecreatePhysicsState, 300.0f);
|
|
232
|
+
Registry.RegisterHandler(TEXT("test_component_overlap"), &TestComponentOverlap);
|
|
233
|
+
// #911: BSP to StaticMesh. Generating meshes for hundreds of brushes takes
|
|
234
|
+
// far longer than the default handler timeout.
|
|
235
|
+
Registry.RegisterHandlerWithTimeout(TEXT("convert_brushes_to_static_mesh"), &ConvertBrushesToStaticMesh, 600.0f);
|
|
236
|
+
// #946: component-level material overrides on placed actors.
|
|
237
|
+
Registry.RegisterHandlerWithTimeout(TEXT("set_component_materials"), &SetComponentMaterials, 300.0f);
|
|
238
|
+
// #956: a transient verification subject, and the two actions that keep it
|
|
239
|
+
// from being left behind.
|
|
240
|
+
Registry.RegisterHandler(TEXT("spawn_transient_actor"), &SpawnTransientActor);
|
|
241
|
+
Registry.RegisterHandler(TEXT("destroy_transient_actor"), &DestroyTransientActor);
|
|
242
|
+
Registry.RegisterHandler(TEXT("list_transient_actors"), &ListTransientActors);
|
|
176
243
|
}
|
|
177
244
|
|
|
178
245
|
TSharedPtr<FJsonValue> FLevelHandlers::GetOutliner(const TSharedPtr<FJsonObject>& Params)
|
|
@@ -183,6 +250,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetOutliner(const TSharedPtr<FJsonObject>
|
|
|
183
250
|
|
|
184
251
|
FString ClassFilter = OptionalString(Params, TEXT("classFilter"));
|
|
185
252
|
FString NameFilter = OptionalString(Params, TEXT("nameFilter"));
|
|
253
|
+
// #911: classFilter has always been a case-sensitive substring on the class
|
|
254
|
+
// name, which cannot express "only this exact class". Combined with the
|
|
255
|
+
// folder filters below, that is what forced a get_actor_details round trip
|
|
256
|
+
// per entry to narrow a folder to one class.
|
|
257
|
+
const bool bExactClass = OptionalBool(Params, TEXT("exactClass"), false);
|
|
258
|
+
const FString FolderPathFilter = OptionalString(Params, TEXT("folderPath"));
|
|
259
|
+
const FString FolderPathPrefixFilter = OptionalString(Params, TEXT("folderPathPrefix"));
|
|
186
260
|
// Default 50 keeps us snappy on World Partition projects whose levels
|
|
187
261
|
// contain hundreds of streaming-proxy / HLOD actors. Callers who need the
|
|
188
262
|
// full list can pass a larger limit explicitly.
|
|
@@ -221,14 +295,36 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetOutliner(const TSharedPtr<FJsonObject>
|
|
|
221
295
|
|
|
222
296
|
FString ActorLabel = Actor->GetActorLabel();
|
|
223
297
|
|
|
224
|
-
if (!ClassFilter.IsEmpty()
|
|
298
|
+
if (!ClassFilter.IsEmpty())
|
|
225
299
|
{
|
|
226
|
-
|
|
300
|
+
const bool bClassMatches = bExactClass
|
|
301
|
+
? ActorClass.Equals(ClassFilter, ESearchCase::IgnoreCase)
|
|
302
|
+
: ActorClass.Contains(ClassFilter);
|
|
303
|
+
if (!bClassMatches)
|
|
304
|
+
{
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
227
307
|
}
|
|
228
308
|
if (!NameFilter.IsEmpty() && !ActorName.Contains(NameFilter) && !ActorLabel.Contains(NameFilter))
|
|
229
309
|
{
|
|
230
310
|
continue;
|
|
231
311
|
}
|
|
312
|
+
if (!FolderPathFilter.IsEmpty() || !FolderPathPrefixFilter.IsEmpty())
|
|
313
|
+
{
|
|
314
|
+
const FString Folder = Actor->GetFolderPath().ToString();
|
|
315
|
+
if (!FolderPathFilter.IsEmpty() && !Folder.Equals(FolderPathFilter, ESearchCase::IgnoreCase))
|
|
316
|
+
{
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
// A folder prefix matches the folder itself and everything nested
|
|
320
|
+
// under it, so "Gameplay" does not also match "GameplayOld".
|
|
321
|
+
if (!FolderPathPrefixFilter.IsEmpty() &&
|
|
322
|
+
!Folder.Equals(FolderPathPrefixFilter, ESearchCase::IgnoreCase) &&
|
|
323
|
+
!Folder.StartsWith(FolderPathPrefixFilter + TEXT("/"), ESearchCase::IgnoreCase))
|
|
324
|
+
{
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
232
328
|
|
|
233
329
|
#if WITH_EDITOR
|
|
234
330
|
const bool bEditorHidden = Actor->IsTemporarilyHiddenInEditor();
|
|
@@ -446,22 +542,29 @@ TSharedPtr<FJsonValue> FLevelHandlers::PlaceActor(const TSharedPtr<FJsonObject>&
|
|
|
446
542
|
|
|
447
543
|
TSharedPtr<FJsonValue> FLevelHandlers::DeleteActor(const TSharedPtr<FJsonObject>& Params)
|
|
448
544
|
{
|
|
449
|
-
FString
|
|
450
|
-
if (auto Err =
|
|
545
|
+
FString Selector;
|
|
546
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), Selector)) return Err;
|
|
451
547
|
|
|
452
548
|
REQUIRE_EDITOR_WORLD(World);
|
|
453
549
|
|
|
454
|
-
|
|
550
|
+
// #983: a duplicate label is refused rather than deleted at random. The
|
|
551
|
+
// miss stays idempotent, but "already deleted" would be a lie when three
|
|
552
|
+
// actors carry the label and all three are still there.
|
|
553
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
554
|
+
AActor* ActorToDelete = MCPResolveActor(World, Params, ActorErr);
|
|
555
|
+
if (!ActorToDelete && MCPIsAmbiguousActorError(ActorErr)) return ActorErr;
|
|
455
556
|
|
|
456
557
|
// Idempotent: deleting a non-existent actor is a no-op, not an error.
|
|
457
558
|
if (!ActorToDelete)
|
|
458
559
|
{
|
|
459
560
|
auto Result = MCPSuccess();
|
|
460
|
-
Result->SetStringField(TEXT("actorLabel"),
|
|
561
|
+
Result->SetStringField(TEXT("actorLabel"), Selector);
|
|
461
562
|
Result->SetBoolField(TEXT("alreadyDeleted"), true);
|
|
462
563
|
return MCPResult(Result);
|
|
463
564
|
}
|
|
464
565
|
|
|
566
|
+
const FString ActorLabel = ActorToDelete->GetActorLabel();
|
|
567
|
+
|
|
465
568
|
World->DestroyActor(ActorToDelete);
|
|
466
569
|
|
|
467
570
|
auto Result = MCPSuccess();
|
|
@@ -474,14 +577,8 @@ TSharedPtr<FJsonValue> FLevelHandlers::DeleteActor(const TSharedPtr<FJsonObject>
|
|
|
474
577
|
|
|
475
578
|
TSharedPtr<FJsonValue> FLevelHandlers::GetActorDetails(const TSharedPtr<FJsonObject>& Params)
|
|
476
579
|
{
|
|
477
|
-
FString
|
|
478
|
-
|
|
479
|
-
bool bHasLabel = Params->TryGetStringField(TEXT("actorLabel"), ActorLabel);
|
|
480
|
-
bool bHasPath = Params->TryGetStringField(TEXT("actorPath"), ActorPath);
|
|
481
|
-
if (!bHasLabel && !bHasPath)
|
|
482
|
-
{
|
|
483
|
-
return MCPError(TEXT("Missing 'actorLabel' or 'actorPath' parameter"));
|
|
484
|
-
}
|
|
580
|
+
FString Selector;
|
|
581
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), Selector)) return Err;
|
|
485
582
|
|
|
486
583
|
// World selection: "editor" (default) or "pie" (#111)
|
|
487
584
|
// #778: this hand-rolled loop took the FIRST PIE context, i.e. the server,
|
|
@@ -499,17 +596,26 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetActorDetails(const TSharedPtr<FJsonObj
|
|
|
499
596
|
if (!World) return MCPError(TEXT("No editor world available"));
|
|
500
597
|
}
|
|
501
598
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
599
|
+
// LabelOrName, not label alone: a caller often has an internal name rather
|
|
600
|
+
// than a label, because a PIE-spawned actor has no label worth guessing.
|
|
601
|
+
// The label tier is still exhausted first and the name tier refuses on
|
|
602
|
+
// ambiguity, so accepting the name cannot reintroduce a silent pick (#983).
|
|
603
|
+
FMCPActorSelector ActorSel;
|
|
604
|
+
ActorSel.Match = EMCPActorMatch::LabelOrName;
|
|
605
|
+
ActorSel.WorldLabel = World->IsGameWorld() ? TEXT("PIE") : TEXT("editor");
|
|
606
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
607
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr, ActorSel);
|
|
608
|
+
if (!Actor) return ActorErr;
|
|
507
609
|
|
|
508
610
|
auto Result = MCPSuccess();
|
|
509
611
|
Result->SetStringField(TEXT("label"), Actor->GetActorLabel());
|
|
510
612
|
Result->SetStringField(TEXT("name"), Actor->GetName());
|
|
511
613
|
Result->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
|
|
512
614
|
Result->SetStringField(TEXT("path"), Actor->GetPathName());
|
|
615
|
+
// #983: the same value under the name the selector uses, so the round trip
|
|
616
|
+
// back into any actor-targeting action is a copy of one field.
|
|
617
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
618
|
+
Result->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
513
619
|
Result->SetStringField(TEXT("folderPath"), Actor->GetFolderPath().ToString());
|
|
514
620
|
|
|
515
621
|
FVector Location = Actor->GetActorLocation();
|
|
@@ -567,10 +673,26 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetActorDetails(const TSharedPtr<FJsonObj
|
|
|
567
673
|
P->SetStringField(TEXT("name"), Prop->GetName());
|
|
568
674
|
P->SetStringField(TEXT("type"), Prop->GetCPPType());
|
|
569
675
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
676
|
+
// #927: a UPROPERTY declared as a C-style fixed array, `int32 Foo[3]`,
|
|
677
|
+
// is ONE FProperty with ArrayDim == 3, not three properties. Exporting
|
|
678
|
+
// it without an index writes element 0 and stops, and the value then
|
|
679
|
+
// reads as an ordinary scalar with the remaining elements invisible.
|
|
680
|
+
//
|
|
681
|
+
// This is a general serialization bug, not a navmesh one. It was
|
|
682
|
+
// noticed on RecastNavMesh's NavMeshResolutionParams, a three-element
|
|
683
|
+
// fixed array holding the Low, Default and High generation tiers, and
|
|
684
|
+
// reporting only the Low tier as if it were the whole property sent a
|
|
685
|
+
// user tuning cell sizes against numbers Recast was not using. Any
|
|
686
|
+
// fixed array on any class had the same problem.
|
|
687
|
+
//
|
|
688
|
+
// MCPExportPropertyValue returns a JSON array of one string per
|
|
689
|
+
// element when ArrayDim > 1 and a plain string otherwise, so the two
|
|
690
|
+
// cases stay distinguishable rather than being conflated.
|
|
691
|
+
P->SetField(TEXT("value"), MCPExportPropertyValue(Prop, Actor));
|
|
692
|
+
if (MCPPropertyIsFixedArray(Prop))
|
|
693
|
+
{
|
|
694
|
+
P->SetNumberField(TEXT("arrayDim"), Prop->ArrayDim);
|
|
695
|
+
}
|
|
574
696
|
PropsArr.Add(MakeShared<FJsonValueObject>(P));
|
|
575
697
|
}
|
|
576
698
|
Result->SetArrayField(TEXT("properties"), PropsArr);
|
|
@@ -596,14 +718,8 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetActorDetails(const TSharedPtr<FJsonObj
|
|
|
596
718
|
// - reflected UPROPERTY name/type/value when includeProperties=true
|
|
597
719
|
TSharedPtr<FJsonValue> FLevelHandlers::GetComponentTree(const TSharedPtr<FJsonObject>& Params)
|
|
598
720
|
{
|
|
599
|
-
FString
|
|
600
|
-
|
|
601
|
-
const bool bHasLabel = Params->TryGetStringField(TEXT("actorLabel"), ActorLabel);
|
|
602
|
-
const bool bHasPath = Params->TryGetStringField(TEXT("actorPath"), ActorPath);
|
|
603
|
-
if (!bHasLabel && !bHasPath)
|
|
604
|
-
{
|
|
605
|
-
return MCPError(TEXT("Missing 'actorLabel' or 'actorPath' parameter"));
|
|
606
|
-
}
|
|
721
|
+
FString Selector;
|
|
722
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), Selector)) return Err;
|
|
607
723
|
|
|
608
724
|
const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
|
|
609
725
|
UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
|
|
@@ -612,11 +728,16 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetComponentTree(const TSharedPtr<FJsonOb
|
|
|
612
728
|
return MCPError(FString::Printf(TEXT("World '%s' not available"), *WorldScope));
|
|
613
729
|
}
|
|
614
730
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
731
|
+
// LabelOrName, not label alone: a caller often has an internal name rather
|
|
732
|
+
// than a label, because a PIE-spawned actor has no label worth guessing.
|
|
733
|
+
// The label tier is still exhausted first and the name tier refuses on
|
|
734
|
+
// ambiguity, so accepting the name cannot reintroduce a silent pick (#983).
|
|
735
|
+
FMCPActorSelector ActorSel;
|
|
736
|
+
ActorSel.Match = EMCPActorMatch::LabelOrName;
|
|
737
|
+
ActorSel.WorldLabel = World->IsGameWorld() ? TEXT("PIE") : TEXT("editor");
|
|
738
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
739
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr, ActorSel);
|
|
740
|
+
if (!Actor) return ActorErr;
|
|
620
741
|
|
|
621
742
|
const bool bIncludeProperties = OptionalBool(Params, TEXT("includeProperties"));
|
|
622
743
|
const FString PropertyFilter = OptionalString(Params, TEXT("componentClass"));
|
|
@@ -728,6 +849,24 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetComponentTree(const TSharedPtr<FJsonOb
|
|
|
728
849
|
Mats.Add(MakeShared<FJsonValueString>(Mat ? Mat->GetPathName() : TEXT("")));
|
|
729
850
|
}
|
|
730
851
|
C->SetArrayField(TEXT("materials"), Mats);
|
|
852
|
+
|
|
853
|
+
// #986: an ISM/HISM reported its class and its mesh and not
|
|
854
|
+
// how many instances it holds, which is the one number that
|
|
855
|
+
// decides whether to touch it at all. A component with three
|
|
856
|
+
// instances and one with three hundred thousand looked
|
|
857
|
+
// identical here, so the decision was made blind or cost a
|
|
858
|
+
// separate get_instance_transforms dump of every transform.
|
|
859
|
+
if (UInstancedStaticMeshComponent* ISMC = Cast<UInstancedStaticMeshComponent>(SMC))
|
|
860
|
+
{
|
|
861
|
+
C->SetNumberField(TEXT("instanceCount"), ISMC->GetInstanceCount());
|
|
862
|
+
TSharedPtr<FJsonObject> Instanced = MakeShared<FJsonObject>();
|
|
863
|
+
// HISM culls and LODs per instance and ISM does not, so
|
|
864
|
+
// the distinction changes what an edit costs.
|
|
865
|
+
Instanced->SetBoolField(TEXT("hierarchical"),
|
|
866
|
+
ISMC->IsA<UHierarchicalInstancedStaticMeshComponent>());
|
|
867
|
+
Instanced->SetNumberField(TEXT("numCustomDataFloats"), ISMC->NumCustomDataFloats);
|
|
868
|
+
C->SetObjectField(TEXT("instanced"), Instanced);
|
|
869
|
+
}
|
|
731
870
|
}
|
|
732
871
|
else if (USkeletalMeshComponent* SKMC = Cast<USkeletalMeshComponent>(PC))
|
|
733
872
|
{
|
|
@@ -783,10 +922,15 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetComponentTree(const TSharedPtr<FJsonOb
|
|
|
783
922
|
TSharedPtr<FJsonObject> P = MakeShared<FJsonObject>();
|
|
784
923
|
P->SetStringField(TEXT("name"), Prop->GetName());
|
|
785
924
|
P->SetStringField(TEXT("type"), Prop->GetCPPType());
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
925
|
+
// #927: a fixed array is one FProperty with ArrayDim > 1, and
|
|
926
|
+
// exporting it without an index reports element 0 as though it
|
|
927
|
+
// were the whole value. Same helper as the actor dump, so the
|
|
928
|
+
// two cannot drift.
|
|
929
|
+
P->SetField(TEXT("value"), MCPExportPropertyValue(Prop, Comp));
|
|
930
|
+
if (MCPPropertyIsFixedArray(Prop))
|
|
931
|
+
{
|
|
932
|
+
P->SetNumberField(TEXT("arrayDim"), Prop->ArrayDim);
|
|
933
|
+
}
|
|
790
934
|
Props.Add(MakeShared<FJsonValueObject>(P));
|
|
791
935
|
}
|
|
792
936
|
C->SetArrayField(TEXT("properties"), Props);
|
|
@@ -797,6 +941,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetComponentTree(const TSharedPtr<FJsonOb
|
|
|
797
941
|
|
|
798
942
|
auto Result = MCPSuccess();
|
|
799
943
|
Result->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
944
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
800
945
|
Result->SetStringField(TEXT("actorClass"), Actor->GetClass()->GetName());
|
|
801
946
|
Result->SetNumberField(TEXT("componentCount"), CompArr.Num());
|
|
802
947
|
Result->SetArrayField(TEXT("components"), CompArr);
|
|
@@ -809,19 +954,30 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetComponentTree(const TSharedPtr<FJsonOb
|
|
|
809
954
|
// required execute_python with MathLibrary.inverse_transform_location.
|
|
810
955
|
TSharedPtr<FJsonValue> FLevelHandlers::GetRelativeTransform(const TSharedPtr<FJsonObject>& Params)
|
|
811
956
|
{
|
|
812
|
-
FString TargetLabel;
|
|
813
|
-
if (auto Err = RequireStringAlt(Params, TEXT("targetLabel"), TEXT("target"), TargetLabel)) return Err;
|
|
814
|
-
FString ReferenceLabel;
|
|
815
|
-
if (auto Err = RequireStringAlt(Params, TEXT("referenceLabel"), TEXT("reference"), ReferenceLabel)) return Err;
|
|
816
|
-
|
|
817
957
|
const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
|
|
818
958
|
UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
|
|
819
959
|
if (!World) return MCPError(FString::Printf(TEXT("World '%s' not available"), *WorldScope));
|
|
820
960
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
961
|
+
// #983: both ends take a path. Two duplicated labels would otherwise
|
|
962
|
+
// produce a relative transform between whichever pair the actor iterator
|
|
963
|
+
// reached first, which is exactly the number this action exists to trust.
|
|
964
|
+
FMCPActorSelector TargetSel;
|
|
965
|
+
TargetSel.LabelKey = TEXT("targetLabel");
|
|
966
|
+
TargetSel.PathKey = TEXT("targetPath");
|
|
967
|
+
TargetSel.AltLabelKey = TEXT("target");
|
|
968
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
969
|
+
AActor* TargetActor = MCPResolveActor(World, Params, ActorErr, TargetSel);
|
|
970
|
+
if (!TargetActor) return ActorErr;
|
|
971
|
+
|
|
972
|
+
FMCPActorSelector ReferenceSel;
|
|
973
|
+
ReferenceSel.LabelKey = TEXT("referenceLabel");
|
|
974
|
+
ReferenceSel.PathKey = TEXT("referencePath");
|
|
975
|
+
ReferenceSel.AltLabelKey = TEXT("reference");
|
|
976
|
+
AActor* ReferenceActor = MCPResolveActor(World, Params, ActorErr, ReferenceSel);
|
|
977
|
+
if (!ReferenceActor) return ActorErr;
|
|
978
|
+
|
|
979
|
+
const FString TargetLabel = TargetActor->GetActorLabel();
|
|
980
|
+
const FString ReferenceLabel = ReferenceActor->GetActorLabel();
|
|
825
981
|
|
|
826
982
|
const FTransform Target = TargetActor->GetActorTransform();
|
|
827
983
|
const FTransform Reference = ReferenceActor->GetActorTransform();
|
|
@@ -844,7 +1000,9 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetRelativeTransform(const TSharedPtr<FJs
|
|
|
844
1000
|
|
|
845
1001
|
auto Result = MCPSuccess();
|
|
846
1002
|
Result->SetStringField(TEXT("targetLabel"), TargetLabel);
|
|
1003
|
+
Result->SetStringField(TEXT("targetPath"), TargetActor->GetPathName());
|
|
847
1004
|
Result->SetStringField(TEXT("referenceLabel"), ReferenceLabel);
|
|
1005
|
+
Result->SetStringField(TEXT("referencePath"), ReferenceActor->GetPathName());
|
|
848
1006
|
Result->SetObjectField(TEXT("location"), MakeVec(Relative.GetLocation()));
|
|
849
1007
|
Result->SetObjectField(TEXT("rotation"), MakeRot(Relative.GetRotation().Rotator()));
|
|
850
1008
|
Result->SetObjectField(TEXT("scale"), MakeVec(Relative.GetScale3D()));
|
|
@@ -947,21 +1105,27 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetSelectedActors(const TSharedPtr<FJsonO
|
|
|
947
1105
|
}
|
|
948
1106
|
TSharedPtr<FJsonValue> FLevelHandlers::MoveActor(const TSharedPtr<FJsonObject>& Params)
|
|
949
1107
|
{
|
|
950
|
-
FString
|
|
951
|
-
if (auto Err =
|
|
1108
|
+
FString Selector;
|
|
1109
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), Selector)) return Err;
|
|
952
1110
|
|
|
953
1111
|
// #586: support the PIE world so a label from get_outliner {world:pie}
|
|
954
|
-
// resolves and the live actor moves.
|
|
955
|
-
//
|
|
1112
|
+
// resolves and the live actor moves. The resolver also matches the runtime
|
|
1113
|
+
// instance name PIE shows.
|
|
956
1114
|
const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
|
|
957
1115
|
UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
|
|
958
1116
|
if (!World) return MCPError(TEXT("World not available"));
|
|
959
1117
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1118
|
+
// LabelOrName, not label alone: a caller often has an internal name rather
|
|
1119
|
+
// than a label, because a PIE-spawned actor has no label worth guessing.
|
|
1120
|
+
// The label tier is still exhausted first and the name tier refuses on
|
|
1121
|
+
// ambiguity, so accepting the name cannot reintroduce a silent pick (#983).
|
|
1122
|
+
FMCPActorSelector ActorSel;
|
|
1123
|
+
ActorSel.Match = EMCPActorMatch::LabelOrName;
|
|
1124
|
+
ActorSel.WorldLabel = World->IsGameWorld() ? TEXT("PIE") : TEXT("editor");
|
|
1125
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
1126
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr, ActorSel);
|
|
1127
|
+
if (!Actor) return ActorErr;
|
|
1128
|
+
const FString ActorLabel = Actor->GetActorLabel();
|
|
965
1129
|
|
|
966
1130
|
// Capture previous transform for rollback.
|
|
967
1131
|
const FVector PreviousLocation = Actor->GetActorLocation();
|
|
@@ -987,9 +1151,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::MoveActor(const TSharedPtr<FJsonObject>&
|
|
|
987
1151
|
Result->SetObjectField(TEXT("rotation"), MCPRotatorToJsonObject(Actor->GetActorRotation()));
|
|
988
1152
|
Result->SetObjectField(TEXT("scale"), MCPVec3ToJsonObject(Actor->GetActorScale3D()));
|
|
989
1153
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1154
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
990
1155
|
|
|
991
|
-
// Self-inverse: call move_actor with previous transform.
|
|
1156
|
+
// Self-inverse: call move_actor with previous transform. #983: the undo
|
|
1157
|
+
// travels by path, so replaying it cannot land on a different actor that
|
|
1158
|
+
// happens to share the label.
|
|
992
1159
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
1160
|
+
Payload->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
993
1161
|
Payload->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
994
1162
|
Payload->SetObjectField(TEXT("location"), MCPVec3ToJsonObject(PreviousLocation));
|
|
995
1163
|
Payload->SetObjectField(TEXT("rotation"), MCPRotatorToJsonObject(PreviousRotation));
|
|
@@ -1004,23 +1172,27 @@ TSharedPtr<FJsonValue> FLevelHandlers::MoveActor(const TSharedPtr<FJsonObject>&
|
|
|
1004
1172
|
// reading two transforms and computing the look-at client-side.
|
|
1005
1173
|
TSharedPtr<FJsonValue> FLevelHandlers::AimActorAt(const TSharedPtr<FJsonObject>& Params)
|
|
1006
1174
|
{
|
|
1007
|
-
FString
|
|
1008
|
-
if (auto Err =
|
|
1175
|
+
FString Selector;
|
|
1176
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), Selector)) return Err;
|
|
1009
1177
|
|
|
1010
1178
|
FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
|
|
1011
1179
|
UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
|
|
1012
1180
|
if (!World) return MCPError(TEXT("World not available"));
|
|
1013
1181
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1182
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
1183
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
1184
|
+
if (!Actor) return ActorErr;
|
|
1185
|
+
const FString ActorLabel = Actor->GetActorLabel();
|
|
1016
1186
|
|
|
1017
1187
|
// Resolve the target point: an explicit target Vec3, or another actor's location.
|
|
1018
1188
|
FVector TargetLocation;
|
|
1019
|
-
|
|
1020
|
-
if (!TargetActorLabel.IsEmpty())
|
|
1189
|
+
if (Params->HasField(TEXT("targetActor")) || Params->HasField(TEXT("targetActorPath")))
|
|
1021
1190
|
{
|
|
1022
|
-
|
|
1023
|
-
|
|
1191
|
+
FMCPActorSelector TargetSel;
|
|
1192
|
+
TargetSel.LabelKey = TEXT("targetActor");
|
|
1193
|
+
TargetSel.PathKey = TEXT("targetActorPath");
|
|
1194
|
+
AActor* TargetActor = MCPResolveActor(World, Params, ActorErr, TargetSel);
|
|
1195
|
+
if (!TargetActor) return ActorErr;
|
|
1024
1196
|
TargetLocation = TargetActor->GetActorLocation();
|
|
1025
1197
|
}
|
|
1026
1198
|
else if (Params->HasField(TEXT("target")))
|
|
@@ -1029,7 +1201,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::AimActorAt(const TSharedPtr<FJsonObject>&
|
|
|
1029
1201
|
}
|
|
1030
1202
|
else
|
|
1031
1203
|
{
|
|
1032
|
-
return MCPError(TEXT("Supply 'target' (Vec3)
|
|
1204
|
+
return MCPError(TEXT("Supply 'target' (Vec3), 'targetActor' (label) or 'targetActorPath' (object path)"));
|
|
1033
1205
|
}
|
|
1034
1206
|
|
|
1035
1207
|
const FVector ActorLocation = Actor->GetActorLocation();
|
|
@@ -1048,11 +1220,14 @@ TSharedPtr<FJsonValue> FLevelHandlers::AimActorAt(const TSharedPtr<FJsonObject>&
|
|
|
1048
1220
|
auto Result = MCPSuccess();
|
|
1049
1221
|
MCPSetUpdated(Result);
|
|
1050
1222
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1223
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1051
1224
|
Result->SetObjectField(TEXT("rotation"), MCPRotatorToJsonObject(Actor->GetActorRotation()));
|
|
1052
1225
|
Result->SetObjectField(TEXT("target"), MCPVec3ToJsonObject(TargetLocation));
|
|
1053
1226
|
|
|
1054
|
-
// Rollback: restore the prior rotation via move_actor
|
|
1227
|
+
// Rollback: restore the prior rotation via move_actor, by path so the undo
|
|
1228
|
+
// cannot land on a namesake (#983).
|
|
1055
1229
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
1230
|
+
Payload->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1056
1231
|
Payload->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1057
1232
|
Payload->SetObjectField(TEXT("rotation"), MCPRotatorToJsonObject(PreviousRotation));
|
|
1058
1233
|
MCPSetRollback(Result, TEXT("move_actor"), Payload);
|
|
@@ -1091,10 +1266,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::NavProjectPoint(const TSharedPtr<FJsonObj
|
|
|
1091
1266
|
|
|
1092
1267
|
TSharedPtr<FJsonValue> FLevelHandlers::SelectActors(const TSharedPtr<FJsonObject>& Params)
|
|
1093
1268
|
{
|
|
1094
|
-
const TArray<TSharedPtr<FJsonValue
|
|
1095
|
-
|
|
1269
|
+
static const TArray<TSharedPtr<FJsonValue>> EmptySelection;
|
|
1270
|
+
const TArray<TSharedPtr<FJsonValue>>* ActorLabelsArray = &EmptySelection;
|
|
1271
|
+
const bool bHasLabels = Params->TryGetArrayField(TEXT("actorLabels"), ActorLabelsArray);
|
|
1272
|
+
if (!bHasLabels) ActorLabelsArray = &EmptySelection;
|
|
1273
|
+
if (!bHasLabels && !Params->HasField(TEXT("actorPaths")))
|
|
1096
1274
|
{
|
|
1097
|
-
return MCPError(TEXT("Missing 'actorLabels' parameter"));
|
|
1275
|
+
return MCPError(TEXT("Missing 'actorLabels' parameter (or 'actorPaths')"));
|
|
1098
1276
|
}
|
|
1099
1277
|
|
|
1100
1278
|
REQUIRE_EDITOR_WORLD(World);
|
|
@@ -1105,31 +1283,61 @@ TSharedPtr<FJsonValue> FLevelHandlers::SelectActors(const TSharedPtr<FJsonObject
|
|
|
1105
1283
|
TArray<TSharedPtr<FJsonValue>> SelectedArray;
|
|
1106
1284
|
TArray<TSharedPtr<FJsonValue>> NotFoundArray;
|
|
1107
1285
|
|
|
1286
|
+
// #983: selection is the plural case, so a label naming several actors
|
|
1287
|
+
// selects all of them rather than one at random. selectedPaths reports
|
|
1288
|
+
// exactly which, and is what a follow-up write should target.
|
|
1289
|
+
TArray<TSharedPtr<FJsonValue>> SelectedPathsArray;
|
|
1108
1290
|
for (const TSharedPtr<FJsonValue>& LabelValue : *ActorLabelsArray)
|
|
1109
1291
|
{
|
|
1110
1292
|
FString Label = LabelValue->AsString();
|
|
1111
|
-
|
|
1293
|
+
TArray<AActor*> Matches;
|
|
1294
|
+
MCPCollectActorsByToken(World, Label, EMCPActorMatch::Label, Matches);
|
|
1295
|
+
if (Matches.Num() == 0)
|
|
1296
|
+
{
|
|
1297
|
+
NotFoundArray.Add(MakeShared<FJsonValueString>(Label));
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
for (AActor* Match : Matches)
|
|
1112
1301
|
{
|
|
1113
1302
|
GEditor->SelectActor(Match, true, true, true);
|
|
1114
|
-
|
|
1303
|
+
SelectedPathsArray.Add(MakeShared<FJsonValueString>(Match->GetPathName()));
|
|
1115
1304
|
}
|
|
1116
|
-
|
|
1305
|
+
SelectedArray.Add(MakeShared<FJsonValueString>(Label));
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// An explicit path list selects exactly what it names, with no label
|
|
1309
|
+
// resolution in the way at all.
|
|
1310
|
+
const TArray<TSharedPtr<FJsonValue>>* ActorPathsArray = nullptr;
|
|
1311
|
+
if (Params->TryGetArrayField(TEXT("actorPaths"), ActorPathsArray))
|
|
1312
|
+
{
|
|
1313
|
+
for (const TSharedPtr<FJsonValue>& PathValue : *ActorPathsArray)
|
|
1117
1314
|
{
|
|
1118
|
-
|
|
1315
|
+
const FString Path = PathValue->AsString();
|
|
1316
|
+
if (AActor* Match = MCPFindActorByPath(World, Path))
|
|
1317
|
+
{
|
|
1318
|
+
GEditor->SelectActor(Match, true, true, true);
|
|
1319
|
+
SelectedPathsArray.Add(MakeShared<FJsonValueString>(Match->GetPathName()));
|
|
1320
|
+
SelectedArray.Add(MakeShared<FJsonValueString>(Match->GetActorLabel()));
|
|
1321
|
+
}
|
|
1322
|
+
else
|
|
1323
|
+
{
|
|
1324
|
+
NotFoundArray.Add(MakeShared<FJsonValueString>(Path));
|
|
1325
|
+
}
|
|
1119
1326
|
}
|
|
1120
1327
|
}
|
|
1121
1328
|
|
|
1122
1329
|
auto Result = MCPSuccess();
|
|
1123
1330
|
Result->SetArrayField(TEXT("selected"), SelectedArray);
|
|
1331
|
+
Result->SetArrayField(TEXT("selectedPaths"), SelectedPathsArray);
|
|
1124
1332
|
Result->SetArrayField(TEXT("notFound"), NotFoundArray);
|
|
1125
|
-
Result->SetNumberField(TEXT("selectedCount"),
|
|
1333
|
+
Result->SetNumberField(TEXT("selectedCount"), SelectedPathsArray.Num());
|
|
1126
1334
|
|
|
1127
1335
|
return MCPResult(Result);
|
|
1128
1336
|
}
|
|
1129
1337
|
TSharedPtr<FJsonValue> FLevelHandlers::AddComponentToActor(const TSharedPtr<FJsonObject>& Params)
|
|
1130
1338
|
{
|
|
1131
1339
|
FString ActorLabel;
|
|
1132
|
-
if (auto Err =
|
|
1340
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
1133
1341
|
|
|
1134
1342
|
FString ComponentClass;
|
|
1135
1343
|
if (auto Err = RequireString(Params, TEXT("componentClass"), ComponentClass)) return Err;
|
|
@@ -1141,11 +1349,10 @@ TSharedPtr<FJsonValue> FLevelHandlers::AddComponentToActor(const TSharedPtr<FJso
|
|
|
1141
1349
|
|
|
1142
1350
|
REQUIRE_EDITOR_WORLD(World);
|
|
1143
1351
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1352
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
1353
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
1354
|
+
if (!Actor) return ActorErr;
|
|
1355
|
+
ActorLabel = Actor->GetActorLabel();
|
|
1149
1356
|
|
|
1150
1357
|
// Idempotency: check for an existing component with the same name on the actor.
|
|
1151
1358
|
FName CompName = FName(*ComponentName);
|
|
@@ -1161,6 +1368,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::AddComponentToActor(const TSharedPtr<FJso
|
|
|
1161
1368
|
auto ExistingResult = MCPSuccess();
|
|
1162
1369
|
MCPSetExisted(ExistingResult);
|
|
1163
1370
|
ExistingResult->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1371
|
+
ExistingResult->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1164
1372
|
ExistingResult->SetStringField(TEXT("componentName"), ComponentName);
|
|
1165
1373
|
ExistingResult->SetStringField(TEXT("componentClass"), Existing->GetClass()->GetName());
|
|
1166
1374
|
return MCPResult(ExistingResult);
|
|
@@ -1210,10 +1418,12 @@ TSharedPtr<FJsonValue> FLevelHandlers::AddComponentToActor(const TSharedPtr<FJso
|
|
|
1210
1418
|
auto Result = MCPSuccess();
|
|
1211
1419
|
MCPSetCreated(Result);
|
|
1212
1420
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1421
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1213
1422
|
Result->SetStringField(TEXT("componentName"), ComponentName);
|
|
1214
1423
|
Result->SetStringField(TEXT("componentClass"), NewComponent->GetClass()->GetName());
|
|
1215
1424
|
|
|
1216
1425
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
1426
|
+
Payload->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1217
1427
|
Payload->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1218
1428
|
Payload->SetStringField(TEXT("componentName"), ComponentName);
|
|
1219
1429
|
MCPSetRollback(Result, TEXT("remove_component_from_actor"), Payload);
|
|
@@ -1225,14 +1435,16 @@ TSharedPtr<FJsonValue> FLevelHandlers::AddComponentToActor(const TSharedPtr<FJso
|
|
|
1225
1435
|
TSharedPtr<FJsonValue> FLevelHandlers::RemoveComponentFromActor(const TSharedPtr<FJsonObject>& Params)
|
|
1226
1436
|
{
|
|
1227
1437
|
FString ActorLabel;
|
|
1228
|
-
if (auto Err =
|
|
1438
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
1229
1439
|
FString ComponentName;
|
|
1230
1440
|
if (auto Err = RequireString(Params, TEXT("componentName"), ComponentName)) return Err;
|
|
1231
1441
|
|
|
1232
1442
|
REQUIRE_EDITOR_WORLD(World);
|
|
1233
1443
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1444
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
1445
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
1446
|
+
if (!Actor) return ActorErr;
|
|
1447
|
+
ActorLabel = Actor->GetActorLabel();
|
|
1236
1448
|
|
|
1237
1449
|
const FName CompName(*ComponentName);
|
|
1238
1450
|
UActorComponent* Target = nullptr;
|
|
@@ -1245,6 +1457,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::RemoveComponentFromActor(const TSharedPtr
|
|
|
1245
1457
|
{
|
|
1246
1458
|
auto Noop = MCPSuccess();
|
|
1247
1459
|
Noop->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1460
|
+
Noop->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1248
1461
|
Noop->SetStringField(TEXT("componentName"), ComponentName);
|
|
1249
1462
|
Noop->SetBoolField(TEXT("alreadyDeleted"), true);
|
|
1250
1463
|
return MCPResult(Noop);
|
|
@@ -1258,6 +1471,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::RemoveComponentFromActor(const TSharedPtr
|
|
|
1258
1471
|
|
|
1259
1472
|
auto Result = MCPSuccess();
|
|
1260
1473
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1474
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1261
1475
|
Result->SetStringField(TEXT("componentName"), ComponentName);
|
|
1262
1476
|
Result->SetStringField(TEXT("componentClass"), ComponentClass);
|
|
1263
1477
|
Result->SetBoolField(TEXT("deleted"), true);
|
|
@@ -1318,6 +1532,168 @@ TSharedPtr<FJsonValue> FLevelHandlers::LoadLevel(const TSharedPtr<FJsonObject>&
|
|
|
1318
1532
|
return MCPResult(Result);
|
|
1319
1533
|
}
|
|
1320
1534
|
|
|
1535
|
+
int32 FLevelHandlers::ClearBlueprintGraphNodes(
|
|
1536
|
+
UBlueprint* Blueprint,
|
|
1537
|
+
bool bDryRun,
|
|
1538
|
+
TArray<TSharedPtr<FJsonValue>>& OutGraphs)
|
|
1539
|
+
{
|
|
1540
|
+
if (!Blueprint)
|
|
1541
|
+
{
|
|
1542
|
+
return 0;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
TArray<UEdGraph*> Graphs;
|
|
1546
|
+
Blueprint->GetAllGraphs(Graphs);
|
|
1547
|
+
int32 NodeCount = 0;
|
|
1548
|
+
|
|
1549
|
+
for (UEdGraph* Graph : Graphs)
|
|
1550
|
+
{
|
|
1551
|
+
if (!Graph || Graph->Nodes.IsEmpty())
|
|
1552
|
+
{
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
TArray<TSharedPtr<FJsonValue>> Nodes;
|
|
1557
|
+
TArray<UEdGraphNode*> GraphNodes;
|
|
1558
|
+
GraphNodes.Reserve(Graph->Nodes.Num());
|
|
1559
|
+
for (UEdGraphNode* Node : Graph->Nodes)
|
|
1560
|
+
{
|
|
1561
|
+
GraphNodes.Add(Node);
|
|
1562
|
+
}
|
|
1563
|
+
if (!bDryRun)
|
|
1564
|
+
{
|
|
1565
|
+
Graph->Modify();
|
|
1566
|
+
}
|
|
1567
|
+
for (UEdGraphNode* Node : GraphNodes)
|
|
1568
|
+
{
|
|
1569
|
+
if (!Node)
|
|
1570
|
+
{
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
auto NodeJson = MakeShared<FJsonObject>();
|
|
1575
|
+
NodeJson->SetStringField(TEXT("name"), Node->GetName());
|
|
1576
|
+
NodeJson->SetStringField(TEXT("classPath"), Node->GetClass()->GetPathName());
|
|
1577
|
+
Nodes.Add(MakeShared<FJsonValueObject>(NodeJson));
|
|
1578
|
+
++NodeCount;
|
|
1579
|
+
|
|
1580
|
+
if (!bDryRun)
|
|
1581
|
+
{
|
|
1582
|
+
Node->Modify();
|
|
1583
|
+
FBlueprintEditorUtils::RemoveNode(Blueprint, Node, /*bDontRecompile*/ true);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
auto GraphJson = MakeShared<FJsonObject>();
|
|
1588
|
+
GraphJson->SetStringField(TEXT("name"), Graph->GetName());
|
|
1589
|
+
GraphJson->SetNumberField(TEXT("nodeCount"), Nodes.Num());
|
|
1590
|
+
GraphJson->SetArrayField(TEXT("nodes"), Nodes);
|
|
1591
|
+
OutGraphs.Add(MakeShared<FJsonValueObject>(GraphJson));
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
return NodeCount;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
TSharedPtr<FJsonValue> FLevelHandlers::ClearLevelScript(const TSharedPtr<FJsonObject>& Params)
|
|
1598
|
+
{
|
|
1599
|
+
if (!GEditor)
|
|
1600
|
+
{
|
|
1601
|
+
return MCPError(TEXT("GEditor not available"));
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
1605
|
+
if (!World || !World->PersistentLevel)
|
|
1606
|
+
{
|
|
1607
|
+
return MCPError(TEXT("No persistent editor level is loaded"));
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
const bool bDryRun = OptionalBool(Params, TEXT("dryRun"), true);
|
|
1611
|
+
const bool bSave = OptionalBool(Params, TEXT("save"), false);
|
|
1612
|
+
if (!bDryRun && bSave && World->GetOutermost()->IsDirty())
|
|
1613
|
+
{
|
|
1614
|
+
return MCPError(TEXT("Current level already has unsaved changes; save or discard them before clear_level_script with save=true"));
|
|
1615
|
+
}
|
|
1616
|
+
ULevelScriptBlueprint* LevelScript =
|
|
1617
|
+
World->PersistentLevel->GetLevelScriptBlueprint(/*bDontCreate*/ true);
|
|
1618
|
+
|
|
1619
|
+
TArray<TSharedPtr<FJsonValue>> Graphs;
|
|
1620
|
+
TArray<TSharedPtr<FJsonValue>> Variables;
|
|
1621
|
+
int32 NodeCount = 0;
|
|
1622
|
+
int32 VariableCount = 0;
|
|
1623
|
+
bool bCompileSucceeded = true;
|
|
1624
|
+
bool bSaved = false;
|
|
1625
|
+
|
|
1626
|
+
if (LevelScript)
|
|
1627
|
+
{
|
|
1628
|
+
TArray<FName> VariableNames;
|
|
1629
|
+
VariableNames.Reserve(LevelScript->NewVariables.Num());
|
|
1630
|
+
for (const FBPVariableDescription& Variable : LevelScript->NewVariables)
|
|
1631
|
+
{
|
|
1632
|
+
VariableNames.Add(Variable.VarName);
|
|
1633
|
+
Variables.Add(MakeShared<FJsonValueString>(Variable.VarName.ToString()));
|
|
1634
|
+
}
|
|
1635
|
+
VariableCount = VariableNames.Num();
|
|
1636
|
+
|
|
1637
|
+
if (bDryRun)
|
|
1638
|
+
{
|
|
1639
|
+
NodeCount = ClearBlueprintGraphNodes(LevelScript, true, Graphs);
|
|
1640
|
+
}
|
|
1641
|
+
else
|
|
1642
|
+
{
|
|
1643
|
+
const FScopedTransaction Transaction(
|
|
1644
|
+
NSLOCTEXT("UEMCPBridge", "ClearLevelScript", "MCP clear level script"));
|
|
1645
|
+
World->Modify();
|
|
1646
|
+
World->PersistentLevel->Modify();
|
|
1647
|
+
LevelScript->Modify();
|
|
1648
|
+
NodeCount = ClearBlueprintGraphNodes(LevelScript, false, Graphs);
|
|
1649
|
+
for (const FName VariableName : VariableNames)
|
|
1650
|
+
{
|
|
1651
|
+
FBlueprintEditorUtils::RemoveMemberVariable(LevelScript, VariableName);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (NodeCount > 0 || VariableCount > 0)
|
|
1655
|
+
{
|
|
1656
|
+
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(LevelScript);
|
|
1657
|
+
FKismetEditorUtilities::CompileBlueprint(LevelScript);
|
|
1658
|
+
bCompileSucceeded = LevelScript->Status != BS_Error;
|
|
1659
|
+
World->MarkPackageDirty();
|
|
1660
|
+
if (!bCompileSucceeded)
|
|
1661
|
+
{
|
|
1662
|
+
return MCPError(TEXT("Level script nodes were cleared but compilation failed; the level was not saved and the change can be undone"));
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
if (bSave && (NodeCount > 0 || VariableCount > 0))
|
|
1667
|
+
{
|
|
1668
|
+
ULevelEditorSubsystem* LevelEditorSubsystem =
|
|
1669
|
+
GEditor->GetEditorSubsystem<ULevelEditorSubsystem>();
|
|
1670
|
+
if (!LevelEditorSubsystem)
|
|
1671
|
+
{
|
|
1672
|
+
return MCPError(TEXT("LevelEditorSubsystem not available; level was changed but not saved"));
|
|
1673
|
+
}
|
|
1674
|
+
bSaved = LevelEditorSubsystem->SaveCurrentLevel();
|
|
1675
|
+
if (!bSaved)
|
|
1676
|
+
{
|
|
1677
|
+
return MCPError(TEXT("Level script was cleared and compiled, but the current level could not be saved"));
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
auto Result = MCPSuccess();
|
|
1684
|
+
Result->SetStringField(TEXT("levelPath"), World->GetOutermost()->GetName());
|
|
1685
|
+
Result->SetBoolField(TEXT("dryRun"), bDryRun);
|
|
1686
|
+
Result->SetBoolField(TEXT("hasLevelScript"), LevelScript != nullptr);
|
|
1687
|
+
Result->SetNumberField(TEXT("graphCount"), Graphs.Num());
|
|
1688
|
+
Result->SetNumberField(TEXT("nodeCount"), NodeCount);
|
|
1689
|
+
Result->SetArrayField(TEXT("graphs"), Graphs);
|
|
1690
|
+
Result->SetNumberField(TEXT("variableCount"), VariableCount);
|
|
1691
|
+
Result->SetArrayField(TEXT("variables"), Variables);
|
|
1692
|
+
Result->SetBoolField(TEXT("compileSucceeded"), bCompileSucceeded);
|
|
1693
|
+
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
1694
|
+
return MCPResult(Result);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1321
1697
|
// Resolve a component on a placed actor by name, case-insensitively, across
|
|
1322
1698
|
// all components GetComponents returns (which includes inherited/SCS
|
|
1323
1699
|
// components on placed Blueprint instances). Empty name -> root component.
|
|
@@ -1380,7 +1756,7 @@ static UActorComponent* FindNamedComponentOnActor(AActor* Actor, const FString&
|
|
|
1380
1756
|
TSharedPtr<FJsonValue> FLevelHandlers::SetComponentProperty(const TSharedPtr<FJsonObject>& Params)
|
|
1381
1757
|
{
|
|
1382
1758
|
FString ActorLabel;
|
|
1383
|
-
if (auto Err =
|
|
1759
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
1384
1760
|
|
|
1385
1761
|
FString ComponentName = OptionalString(Params, TEXT("componentName"));
|
|
1386
1762
|
|
|
@@ -1401,12 +1777,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetComponentProperty(const TSharedPtr<FJs
|
|
|
1401
1777
|
}
|
|
1402
1778
|
const bool bRuntimeWorld = World->IsGameWorld();
|
|
1403
1779
|
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1780
|
+
FMCPActorSelector ActorSel;
|
|
1781
|
+
ActorSel.Match = EMCPActorMatch::LabelNameOrPath;
|
|
1782
|
+
ActorSel.WorldLabel = bRuntimeWorld ? TEXT("PIE") : TEXT("editor");
|
|
1783
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
1784
|
+
AActor* TargetActor = MCPResolveActor(World, Params, ActorErr, ActorSel);
|
|
1785
|
+
if (!TargetActor) return ActorErr;
|
|
1786
|
+
ActorLabel = TargetActor->GetActorLabel();
|
|
1410
1787
|
|
|
1411
1788
|
UActorComponent* TargetComp = FindComponentOnActor(TargetActor, ComponentName);
|
|
1412
1789
|
if (!TargetComp)
|
|
@@ -1426,24 +1803,65 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetComponentProperty(const TSharedPtr<FJs
|
|
|
1426
1803
|
UStruct* CurrentStruct = TargetComp->GetClass();
|
|
1427
1804
|
void* CurrentContainer = TargetComp;
|
|
1428
1805
|
FProperty* Prop = nullptr;
|
|
1806
|
+
// #927: same fixed-array indexing as set_actor_property. A component
|
|
1807
|
+
// property declared `float Foo[4]` is one FProperty with ArrayDim 4, and
|
|
1808
|
+
// without an index every write lands on element 0.
|
|
1809
|
+
int32 LeafArrayIndex = 0;
|
|
1429
1810
|
for (int32 i = 0; i < PathParts.Num(); ++i)
|
|
1430
1811
|
{
|
|
1431
|
-
|
|
1812
|
+
FString Token = PathParts[i];
|
|
1813
|
+
int32 SegmentIndex = 0;
|
|
1814
|
+
bool bHasSegmentIndex = false;
|
|
1815
|
+
{
|
|
1816
|
+
int32 OpenBracket = INDEX_NONE;
|
|
1817
|
+
int32 CloseBracket = INDEX_NONE;
|
|
1818
|
+
if (Token.FindChar(TEXT('['), OpenBracket) &&
|
|
1819
|
+
Token.FindChar(TEXT(']'), CloseBracket) &&
|
|
1820
|
+
CloseBracket > OpenBracket)
|
|
1821
|
+
{
|
|
1822
|
+
SegmentIndex = FCString::Atoi(*Token.Mid(OpenBracket + 1, CloseBracket - OpenBracket - 1));
|
|
1823
|
+
Token = Token.Left(OpenBracket);
|
|
1824
|
+
bHasSegmentIndex = true;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
FProperty* SegmentProp = CurrentStruct->FindPropertyByName(FName(*Token));
|
|
1432
1829
|
if (!SegmentProp)
|
|
1433
1830
|
{
|
|
1434
|
-
return MCPError(FString::Printf(TEXT("Property '%s' not found at '%s'"), *
|
|
1831
|
+
return MCPError(FString::Printf(TEXT("Property '%s' not found at '%s'"), *Token, *PropertyName));
|
|
1832
|
+
}
|
|
1833
|
+
if (bHasSegmentIndex)
|
|
1834
|
+
{
|
|
1835
|
+
if (CastField<FArrayProperty>(SegmentProp))
|
|
1836
|
+
{
|
|
1837
|
+
return MCPError(FString::Printf(
|
|
1838
|
+
TEXT("'%s' is a TArray. Indexing a dynamic array is not supported here; use asset(set_property) for dotted TArray paths. An index on this action addresses a C-style fixed array such as `float Foo[4]`."),
|
|
1839
|
+
*Token));
|
|
1840
|
+
}
|
|
1841
|
+
if (SegmentProp->ArrayDim <= 1)
|
|
1842
|
+
{
|
|
1843
|
+
return MCPError(FString::Printf(
|
|
1844
|
+
TEXT("'%s' is not a fixed array, so it cannot be indexed [%d]"), *Token, SegmentIndex));
|
|
1845
|
+
}
|
|
1846
|
+
if (SegmentIndex < 0 || SegmentIndex >= SegmentProp->ArrayDim)
|
|
1847
|
+
{
|
|
1848
|
+
return MCPError(FString::Printf(
|
|
1849
|
+
TEXT("Index %d is out of range on '%s', which has ArrayDim %d"),
|
|
1850
|
+
SegmentIndex, *Token, SegmentProp->ArrayDim));
|
|
1851
|
+
}
|
|
1435
1852
|
}
|
|
1436
1853
|
if (i < PathParts.Num() - 1)
|
|
1437
1854
|
{
|
|
1438
1855
|
if (FStructProperty* SP = CastField<FStructProperty>(SegmentProp))
|
|
1439
1856
|
{
|
|
1440
|
-
CurrentContainer = SP->ContainerPtrToValuePtr<void>(CurrentContainer);
|
|
1857
|
+
CurrentContainer = SP->ContainerPtrToValuePtr<void>(CurrentContainer, SegmentIndex);
|
|
1441
1858
|
CurrentStruct = SP->Struct;
|
|
1442
1859
|
}
|
|
1443
1860
|
else if (FObjectProperty* OP = CastField<FObjectProperty>(SegmentProp))
|
|
1444
1861
|
{
|
|
1445
1862
|
// #305: descend through Instanced UObject sub-objects.
|
|
1446
|
-
UObject* SubObject = OP->GetObjectPropertyValue(
|
|
1863
|
+
UObject* SubObject = OP->GetObjectPropertyValue(
|
|
1864
|
+
OP->ContainerPtrToValuePtr<void>(CurrentContainer, SegmentIndex));
|
|
1447
1865
|
if (!SubObject)
|
|
1448
1866
|
{
|
|
1449
1867
|
return MCPError(FString::Printf(
|
|
@@ -1463,6 +1881,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetComponentProperty(const TSharedPtr<FJs
|
|
|
1463
1881
|
else
|
|
1464
1882
|
{
|
|
1465
1883
|
Prop = SegmentProp;
|
|
1884
|
+
LeafArrayIndex = SegmentIndex;
|
|
1466
1885
|
}
|
|
1467
1886
|
}
|
|
1468
1887
|
|
|
@@ -1472,7 +1891,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetComponentProperty(const TSharedPtr<FJs
|
|
|
1472
1891
|
return MCPError(TEXT("Missing 'value' parameter"));
|
|
1473
1892
|
}
|
|
1474
1893
|
|
|
1475
|
-
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(CurrentContainer);
|
|
1894
|
+
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(CurrentContainer, LeafArrayIndex);
|
|
1476
1895
|
|
|
1477
1896
|
// Capture previous value as a string for self-inverse rollback.
|
|
1478
1897
|
FString PreviousValueStr;
|
|
@@ -1509,9 +1928,21 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetComponentProperty(const TSharedPtr<FJs
|
|
|
1509
1928
|
// Skip obvious non-identifiers
|
|
1510
1929
|
if (Token != TEXT("True") && Token != TEXT("False") && Token != TEXT("None") && !Token.IsNumeric())
|
|
1511
1930
|
{
|
|
1512
|
-
|
|
1931
|
+
// #983: a duplicated label here would wire the
|
|
1932
|
+
// struct's object reference to whichever namesake
|
|
1933
|
+
// the iterator reached first, so it is refused.
|
|
1934
|
+
// An unmatched token is left alone, as before: it
|
|
1935
|
+
// is probably an enum literal, not an actor.
|
|
1936
|
+
TArray<AActor*> TokenMatches;
|
|
1937
|
+
MCPCollectActorsByToken(World, Token, EMCPActorMatch::Label, TokenMatches);
|
|
1938
|
+
if (TokenMatches.Num() > 1)
|
|
1513
1939
|
{
|
|
1514
|
-
|
|
1940
|
+
return MCPAmbiguousActorError(
|
|
1941
|
+
Token, TEXT("value"), TEXT("actorPath"), TEXT("editor label"), TokenMatches);
|
|
1942
|
+
}
|
|
1943
|
+
if (TokenMatches.Num() == 1)
|
|
1944
|
+
{
|
|
1945
|
+
Result.Append(TokenMatches[0]->GetPathName());
|
|
1515
1946
|
i = End;
|
|
1516
1947
|
goto AppendDone;
|
|
1517
1948
|
}
|
|
@@ -1571,12 +2002,15 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetComponentProperty(const TSharedPtr<FJs
|
|
|
1571
2002
|
auto Result = MCPSuccess();
|
|
1572
2003
|
MCPSetUpdated(Result);
|
|
1573
2004
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
2005
|
+
Result->SetStringField(TEXT("actorPath"), TargetActor->GetPathName());
|
|
1574
2006
|
Result->SetStringField(TEXT("componentClass"), TargetComp->GetClass()->GetName());
|
|
1575
2007
|
Result->SetStringField(TEXT("propertyName"), PropertyName);
|
|
1576
2008
|
Result->SetStringField(TEXT("previousValue"), PreviousValueStr);
|
|
1577
2009
|
|
|
1578
|
-
// Self-inverse: same handler with previous value as string
|
|
2010
|
+
// Self-inverse: same handler with previous value as string, addressed by
|
|
2011
|
+
// path so the undo cannot land on a namesake (#983).
|
|
1579
2012
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
2013
|
+
Payload->SetStringField(TEXT("actorPath"), TargetActor->GetPathName());
|
|
1580
2014
|
Payload->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1581
2015
|
if (!ComponentName.IsEmpty()) Payload->SetStringField(TEXT("componentName"), ComponentName);
|
|
1582
2016
|
Payload->SetStringField(TEXT("propertyName"), PropertyName);
|
|
@@ -1593,7 +2027,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetComponentProperty(const TSharedPtr<FJs
|
|
|
1593
2027
|
TSharedPtr<FJsonValue> FLevelHandlers::GetComponentDetails(const TSharedPtr<FJsonObject>& Params)
|
|
1594
2028
|
{
|
|
1595
2029
|
FString ActorLabel;
|
|
1596
|
-
if (auto Err =
|
|
2030
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
1597
2031
|
|
|
1598
2032
|
const FString ComponentName = OptionalString(Params, TEXT("componentName"));
|
|
1599
2033
|
// #584: optionally dump arbitrary UPROPERTY values (custom fields,
|
|
@@ -1608,11 +2042,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetComponentDetails(const TSharedPtr<FJso
|
|
|
1608
2042
|
UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
|
|
1609
2043
|
if (!World) return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
|
|
1610
2044
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
2045
|
+
FMCPActorSelector ActorSel;
|
|
2046
|
+
ActorSel.Match = EMCPActorMatch::LabelNameOrPath;
|
|
2047
|
+
ActorSel.WorldLabel = World->IsGameWorld() ? TEXT("PIE") : TEXT("editor");
|
|
2048
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
2049
|
+
AActor* TargetActor = MCPResolveActor(World, Params, ActorErr, ActorSel);
|
|
2050
|
+
if (!TargetActor) return ActorErr;
|
|
2051
|
+
ActorLabel = TargetActor->GetActorLabel();
|
|
1616
2052
|
|
|
1617
2053
|
auto DescribeComponent = [bIncludeValues, &PropFilter](UActorComponent* Comp) -> TSharedPtr<FJsonObject>
|
|
1618
2054
|
{
|
|
@@ -1656,9 +2092,9 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetComponentDetails(const TSharedPtr<FJso
|
|
|
1656
2092
|
FProperty* Prop = *It;
|
|
1657
2093
|
const FString PName = Prop->GetName();
|
|
1658
2094
|
if (PropFilter.Num() > 0 && !PropFilter.Contains(PName)) continue;
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
Values->
|
|
2095
|
+
// #927: fixed arrays come back as a JSON array of elements
|
|
2096
|
+
// rather than as element 0 wearing the whole property's name.
|
|
2097
|
+
Values->SetField(PName, MCPExportPropertyValue(Prop, Comp));
|
|
1662
2098
|
}
|
|
1663
2099
|
Obj->SetObjectField(TEXT("values"), Values);
|
|
1664
2100
|
}
|
|
@@ -1667,6 +2103,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetComponentDetails(const TSharedPtr<FJso
|
|
|
1667
2103
|
|
|
1668
2104
|
auto Result = MCPSuccess();
|
|
1669
2105
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
2106
|
+
Result->SetStringField(TEXT("actorPath"), TargetActor->GetPathName());
|
|
1670
2107
|
|
|
1671
2108
|
if (!ComponentName.IsEmpty())
|
|
1672
2109
|
{
|
|
@@ -1820,7 +2257,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetWorldSettings(const TSharedPtr<FJsonOb
|
|
|
1820
2257
|
TSharedPtr<FJsonValue> FLevelHandlers::SetActorMaterial(const TSharedPtr<FJsonObject>& Params)
|
|
1821
2258
|
{
|
|
1822
2259
|
FString ActorLabel;
|
|
1823
|
-
if (auto Err =
|
|
2260
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
1824
2261
|
|
|
1825
2262
|
FString MaterialPath;
|
|
1826
2263
|
if (auto Err = RequireString(Params, TEXT("materialPath"), MaterialPath)) return Err;
|
|
@@ -1829,11 +2266,10 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorMaterial(const TSharedPtr<FJsonOb
|
|
|
1829
2266
|
|
|
1830
2267
|
REQUIRE_EDITOR_WORLD(World);
|
|
1831
2268
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
}
|
|
2269
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
2270
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
2271
|
+
if (!Actor) return ActorErr;
|
|
2272
|
+
ActorLabel = Actor->GetActorLabel();
|
|
1837
2273
|
|
|
1838
2274
|
UMaterialInterface* Material = LoadObject<UMaterialInterface>(nullptr, *MaterialPath);
|
|
1839
2275
|
if (!Material)
|
|
@@ -1860,6 +2296,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorMaterial(const TSharedPtr<FJsonOb
|
|
|
1860
2296
|
auto Result = MCPSuccess();
|
|
1861
2297
|
MCPSetUpdated(Result);
|
|
1862
2298
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
2299
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1863
2300
|
Result->SetStringField(TEXT("materialPath"), MaterialPath);
|
|
1864
2301
|
Result->SetNumberField(TEXT("slotIndex"), SlotIndex);
|
|
1865
2302
|
Result->SetStringField(TEXT("previousMaterialPath"), PreviousMaterialPath);
|
|
@@ -1870,6 +2307,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorMaterial(const TSharedPtr<FJsonOb
|
|
|
1870
2307
|
if (!PreviousMaterialPath.IsEmpty())
|
|
1871
2308
|
{
|
|
1872
2309
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
2310
|
+
Payload->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
1873
2311
|
Payload->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
1874
2312
|
Payload->SetStringField(TEXT("materialPath"), PreviousMaterialPath);
|
|
1875
2313
|
Payload->SetNumberField(TEXT("slotIndex"), SlotIndex);
|
|
@@ -2119,7 +2557,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetRVTSummary(const TSharedPtr<FJsonObjec
|
|
|
2119
2557
|
TSharedPtr<FJsonValue> FLevelHandlers::SetWaterBodyProperty(const TSharedPtr<FJsonObject>& Params)
|
|
2120
2558
|
{
|
|
2121
2559
|
FString ActorLabel;
|
|
2122
|
-
if (auto Err =
|
|
2560
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
2123
2561
|
FString PropertyName;
|
|
2124
2562
|
if (auto Err = RequireString(Params, TEXT("propertyName"), PropertyName)) return Err;
|
|
2125
2563
|
|
|
@@ -2136,8 +2574,10 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetWaterBodyProperty(const TSharedPtr<FJs
|
|
|
2136
2574
|
|
|
2137
2575
|
REQUIRE_EDITOR_WORLD(World);
|
|
2138
2576
|
|
|
2139
|
-
|
|
2140
|
-
|
|
2577
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
2578
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
2579
|
+
if (!Actor) return ActorErr;
|
|
2580
|
+
ActorLabel = Actor->GetActorLabel();
|
|
2141
2581
|
|
|
2142
2582
|
UClass* WBClass = LoadClass<UActorComponent>(nullptr, TEXT("/Script/Water.WaterBodyComponent"));
|
|
2143
2583
|
if (!WBClass)
|
|
@@ -2170,6 +2610,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetWaterBodyProperty(const TSharedPtr<FJs
|
|
|
2170
2610
|
auto Result = MCPSuccess();
|
|
2171
2611
|
MCPSetUpdated(Result);
|
|
2172
2612
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
2613
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
2173
2614
|
Result->SetStringField(TEXT("componentName"), WBComp->GetName());
|
|
2174
2615
|
Result->SetStringField(TEXT("componentClass"), WBComp->GetClass()->GetName());
|
|
2175
2616
|
Result->SetStringField(TEXT("propertyName"), PropertyName);
|
|
@@ -2179,21 +2620,23 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetWaterBodyProperty(const TSharedPtr<FJs
|
|
|
2179
2620
|
|
|
2180
2621
|
// ─── #188 get_actor_bounds ──────────────────────────────────────────
|
|
2181
2622
|
// Returns the axis-aligned bounding box (origin + extent) for an actor
|
|
2182
|
-
//
|
|
2623
|
+
// named by its editor label or its object path.
|
|
2183
2624
|
TSharedPtr<FJsonValue> FLevelHandlers::GetActorBounds(const TSharedPtr<FJsonObject>& Params)
|
|
2184
2625
|
{
|
|
2185
2626
|
FString ActorLabel;
|
|
2186
|
-
if (auto Err =
|
|
2627
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
2187
2628
|
|
|
2188
2629
|
const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
|
|
2189
2630
|
UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
|
|
2190
2631
|
if (!World) return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
|
|
2191
2632
|
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2633
|
+
FMCPActorSelector ActorSel;
|
|
2634
|
+
ActorSel.Match = EMCPActorMatch::LabelNameOrPath;
|
|
2635
|
+
ActorSel.WorldLabel = World->IsGameWorld() ? TEXT("PIE") : TEXT("editor");
|
|
2636
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
2637
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr, ActorSel);
|
|
2638
|
+
if (!Actor) return ActorErr;
|
|
2639
|
+
ActorLabel = Actor->GetActorLabel();
|
|
2197
2640
|
|
|
2198
2641
|
FVector Origin;
|
|
2199
2642
|
FVector Extent;
|
|
@@ -2225,6 +2668,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetActorBounds(const TSharedPtr<FJsonObje
|
|
|
2225
2668
|
|
|
2226
2669
|
auto Result = MCPSuccess();
|
|
2227
2670
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
2671
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
2228
2672
|
Result->SetObjectField(TEXT("origin"), OriginObj);
|
|
2229
2673
|
Result->SetObjectField(TEXT("extent"), ExtentObj);
|
|
2230
2674
|
return MCPResult(Result);
|
|
@@ -2270,15 +2714,16 @@ TSharedPtr<FJsonValue> FLevelHandlers::ResolveActor(const TSharedPtr<FJsonObject
|
|
|
2270
2714
|
}
|
|
2271
2715
|
|
|
2272
2716
|
// #202/#230: generic per-instance UPROPERTY writer for level actors. Resolves
|
|
2273
|
-
// the actor by label, walks dotted property paths, and routes
|
|
2274
|
-
// through the recursive JSON setter so object refs / vectors /
|
|
2275
|
-
// structs all apply. The optional `force` flag flips off the
|
|
2276
|
-
// gate so per-instance overrides on EditDefaultsOnly
|
|
2277
|
-
// (the per-instance value always existed - the editor
|
|
2717
|
+
// the actor by label or object path, walks dotted property paths, and routes
|
|
2718
|
+
// the value through the recursive JSON setter so object refs / vectors /
|
|
2719
|
+
// nested structs all apply. The optional `force` flag flips off the
|
|
2720
|
+
// EditDefaultsOnly gate so per-instance overrides on EditDefaultsOnly
|
|
2721
|
+
// properties go through (the per-instance value always existed - the editor
|
|
2722
|
+
// UI just hides it).
|
|
2278
2723
|
TSharedPtr<FJsonValue> FLevelHandlers::SetActorProperty(const TSharedPtr<FJsonObject>& Params)
|
|
2279
2724
|
{
|
|
2280
2725
|
FString ActorLabel;
|
|
2281
|
-
if (auto Err =
|
|
2726
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
2282
2727
|
|
|
2283
2728
|
FString PropertyName;
|
|
2284
2729
|
if (auto Err = RequireString(Params, TEXT("propertyName"), PropertyName)) return Err;
|
|
@@ -2299,19 +2744,22 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorProperty(const TSharedPtr<FJsonOb
|
|
|
2299
2744
|
}
|
|
2300
2745
|
|
|
2301
2746
|
AActor* TargetActor = nullptr;
|
|
2302
|
-
const bool bWorldSettings =
|
|
2747
|
+
const bool bWorldSettings =
|
|
2748
|
+
ActorLabel.Equals(TEXT("WorldSettings"), ESearchCase::IgnoreCase)
|
|
2749
|
+
&& !Params->HasField(TEXT("actorPath"));
|
|
2303
2750
|
if (bWorldSettings)
|
|
2304
2751
|
{
|
|
2305
2752
|
TargetActor = World->GetWorldSettings();
|
|
2753
|
+
if (!TargetActor) return MCPError(TEXT("World settings not available"));
|
|
2306
2754
|
}
|
|
2307
2755
|
else
|
|
2308
2756
|
{
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2757
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
2758
|
+
FMCPActorSelector ActorSel;
|
|
2759
|
+
ActorSel.WorldLabel = World->IsGameWorld() ? TEXT("PIE") : TEXT("editor");
|
|
2760
|
+
TargetActor = MCPResolveActor(World, Params, ActorErr, ActorSel);
|
|
2761
|
+
if (!TargetActor) return ActorErr;
|
|
2762
|
+
ActorLabel = TargetActor->GetActorLabel();
|
|
2315
2763
|
}
|
|
2316
2764
|
|
|
2317
2765
|
TArray<FString> PathParts;
|
|
@@ -2321,15 +2769,62 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorProperty(const TSharedPtr<FJsonOb
|
|
|
2321
2769
|
UStruct* CurrentStruct = TargetActor->GetClass();
|
|
2322
2770
|
void* CurrentContainer = TargetActor;
|
|
2323
2771
|
FProperty* Prop = nullptr;
|
|
2772
|
+
// #927: the leaf element of a C-style fixed array, `int32 Foo[3]`. That is
|
|
2773
|
+
// ONE FProperty with ArrayDim == 3, so without an index the write lands on
|
|
2774
|
+
// element 0 and the other elements are unreachable. The read side has the
|
|
2775
|
+
// mirror of this bug; a read that shows three tiers and a write that can
|
|
2776
|
+
// only reach the first is not a usable pair.
|
|
2777
|
+
int32 LeafArrayIndex = 0;
|
|
2324
2778
|
for (int32 i = 0; i < PathParts.Num(); ++i)
|
|
2325
2779
|
{
|
|
2326
|
-
|
|
2327
|
-
|
|
2780
|
+
FString Token = PathParts[i];
|
|
2781
|
+
int32 SegmentIndex = 0;
|
|
2782
|
+
bool bHasSegmentIndex = false;
|
|
2783
|
+
{
|
|
2784
|
+
int32 OpenBracket = INDEX_NONE;
|
|
2785
|
+
int32 CloseBracket = INDEX_NONE;
|
|
2786
|
+
if (Token.FindChar(TEXT('['), OpenBracket) &&
|
|
2787
|
+
Token.FindChar(TEXT(']'), CloseBracket) &&
|
|
2788
|
+
CloseBracket > OpenBracket)
|
|
2789
|
+
{
|
|
2790
|
+
SegmentIndex = FCString::Atoi(*Token.Mid(OpenBracket + 1, CloseBracket - OpenBracket - 1));
|
|
2791
|
+
Token = Token.Left(OpenBracket);
|
|
2792
|
+
bHasSegmentIndex = true;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
FProperty* Seg = CurrentStruct->FindPropertyByName(FName(*Token));
|
|
2797
|
+
if (!Seg) return MCPError(FString::Printf(TEXT("Property '%s' not found at '%s'"), *Token, *PropertyName));
|
|
2798
|
+
|
|
2799
|
+
if (bHasSegmentIndex)
|
|
2800
|
+
{
|
|
2801
|
+
if (CastField<FArrayProperty>(Seg))
|
|
2802
|
+
{
|
|
2803
|
+
// A TArray element needs the shared resolver's array helper,
|
|
2804
|
+
// which this walker does not have. Say which action does
|
|
2805
|
+
// rather than writing element 0 and calling it a success.
|
|
2806
|
+
return MCPError(FString::Printf(
|
|
2807
|
+
TEXT("'%s' is a TArray. Indexing a dynamic array is not supported here; use asset(set_property) for dotted TArray paths. An index on this action addresses a C-style fixed array such as `int32 Foo[3]`."),
|
|
2808
|
+
*Token));
|
|
2809
|
+
}
|
|
2810
|
+
if (Seg->ArrayDim <= 1)
|
|
2811
|
+
{
|
|
2812
|
+
return MCPError(FString::Printf(
|
|
2813
|
+
TEXT("'%s' is not a fixed array, so it cannot be indexed [%d]"), *Token, SegmentIndex));
|
|
2814
|
+
}
|
|
2815
|
+
if (SegmentIndex < 0 || SegmentIndex >= Seg->ArrayDim)
|
|
2816
|
+
{
|
|
2817
|
+
return MCPError(FString::Printf(
|
|
2818
|
+
TEXT("Index %d is out of range on '%s', which has ArrayDim %d"),
|
|
2819
|
+
SegmentIndex, *Token, Seg->ArrayDim));
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2328
2823
|
if (i < PathParts.Num() - 1)
|
|
2329
2824
|
{
|
|
2330
2825
|
if (FStructProperty* SP = CastField<FStructProperty>(Seg))
|
|
2331
2826
|
{
|
|
2332
|
-
CurrentContainer = SP->ContainerPtrToValuePtr<void>(CurrentContainer);
|
|
2827
|
+
CurrentContainer = SP->ContainerPtrToValuePtr<void>(CurrentContainer, SegmentIndex);
|
|
2333
2828
|
CurrentStruct = SP->Struct;
|
|
2334
2829
|
}
|
|
2335
2830
|
// #305: descend through Instanced UObject sub-objects too. The path
|
|
@@ -2338,7 +2833,8 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorProperty(const TSharedPtr<FJsonOb
|
|
|
2338
2833
|
// rejection forced execute_python on every instanced-subobject write.
|
|
2339
2834
|
else if (FObjectProperty* OP = CastField<FObjectProperty>(Seg))
|
|
2340
2835
|
{
|
|
2341
|
-
UObject* SubObject = OP->GetObjectPropertyValue(
|
|
2836
|
+
UObject* SubObject = OP->GetObjectPropertyValue(
|
|
2837
|
+
OP->ContainerPtrToValuePtr<void>(CurrentContainer, SegmentIndex));
|
|
2342
2838
|
if (!SubObject)
|
|
2343
2839
|
{
|
|
2344
2840
|
return MCPError(FString::Printf(
|
|
@@ -2358,6 +2854,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorProperty(const TSharedPtr<FJsonOb
|
|
|
2358
2854
|
else
|
|
2359
2855
|
{
|
|
2360
2856
|
Prop = Seg;
|
|
2857
|
+
LeafArrayIndex = SegmentIndex;
|
|
2361
2858
|
}
|
|
2362
2859
|
}
|
|
2363
2860
|
|
|
@@ -2369,7 +2866,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorProperty(const TSharedPtr<FJsonOb
|
|
|
2369
2866
|
Prop->PropertyFlags &= ~CPF_DisableEditOnInstance;
|
|
2370
2867
|
}
|
|
2371
2868
|
|
|
2372
|
-
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(CurrentContainer);
|
|
2869
|
+
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(CurrentContainer, LeafArrayIndex);
|
|
2373
2870
|
|
|
2374
2871
|
FString PrevValue;
|
|
2375
2872
|
Prop->ExportText_Direct(PrevValue, ValuePtr, ValuePtr, TargetActor, PPF_None);
|
|
@@ -2377,15 +2874,30 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorProperty(const TSharedPtr<FJsonOb
|
|
|
2377
2874
|
TargetActor->Modify();
|
|
2378
2875
|
|
|
2379
2876
|
// If the JSON value is a string and the property is an object reference,
|
|
2380
|
-
// try resolving the string as an actor label
|
|
2381
|
-
// {value: "Hopper_01"} for AHopper* references
|
|
2877
|
+
// try resolving the string as an actor label or object path first, so
|
|
2878
|
+
// callers can write {value: "Hopper_01"} for AHopper* references and hand
|
|
2879
|
+
// back an actorPath when the label is not unique.
|
|
2382
2880
|
TSharedPtr<FJsonValue> Value = *ValueField;
|
|
2383
2881
|
if (Value->Type == EJson::String)
|
|
2384
2882
|
{
|
|
2385
2883
|
FString S = Value->AsString();
|
|
2386
2884
|
if (FObjectProperty* OP = CastField<FObjectProperty>(Prop))
|
|
2387
2885
|
{
|
|
2388
|
-
|
|
2886
|
+
// LabelNameOrPath, where this used to be label alone: the value
|
|
2887
|
+
// slot has to take an actorPath back, which is the whole point of
|
|
2888
|
+
// returning one. An actor object path contains ":PersistentLevel."
|
|
2889
|
+
// and so cannot collide with an asset path, and a value matching
|
|
2890
|
+
// nothing still falls through to the generic setter as before.
|
|
2891
|
+
TArray<AActor*> RefMatches;
|
|
2892
|
+
MCPCollectActorsByToken(World, S, EMCPActorMatch::LabelNameOrPath, RefMatches);
|
|
2893
|
+
// #983: wiring a reference to whichever namesake came first is the
|
|
2894
|
+
// silent wrong write this issue is about, so it is refused here too.
|
|
2895
|
+
if (RefMatches.Num() > 1)
|
|
2896
|
+
{
|
|
2897
|
+
Prop->PropertyFlags = OriginalFlags;
|
|
2898
|
+
return MCPAmbiguousActorError(S, TEXT("value"), TEXT("actorPath"), TEXT("editor label"), RefMatches);
|
|
2899
|
+
}
|
|
2900
|
+
AActor* RefActor = RefMatches.Num() == 1 ? RefMatches[0] : nullptr;
|
|
2389
2901
|
if (RefActor && RefActor->IsA(OP->PropertyClass))
|
|
2390
2902
|
{
|
|
2391
2903
|
OP->SetObjectPropertyValue(ValuePtr, RefActor);
|
|
@@ -2423,7 +2935,16 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorProperty(const TSharedPtr<FJsonOb
|
|
|
2423
2935
|
{
|
|
2424
2936
|
FString Label;
|
|
2425
2937
|
(*Items)[i]->TryGetString(Label);
|
|
2426
|
-
AActor
|
|
2938
|
+
TArray<AActor*> ElementMatches;
|
|
2939
|
+
MCPCollectActorsByToken(World, Label, EMCPActorMatch::LabelNameOrPath, ElementMatches);
|
|
2940
|
+
if (ElementMatches.Num() > 1)
|
|
2941
|
+
{
|
|
2942
|
+
// #983: one ambiguous entry poisons the whole array,
|
|
2943
|
+
// so the write is refused before any element lands.
|
|
2944
|
+
Prop->PropertyFlags = OriginalFlags;
|
|
2945
|
+
return MCPAmbiguousActorError(Label, TEXT("value"), TEXT("actorPath"), TEXT("editor label"), ElementMatches);
|
|
2946
|
+
}
|
|
2947
|
+
AActor* Ref = ElementMatches.Num() == 1 ? ElementMatches[0] : nullptr;
|
|
2427
2948
|
if (!Ref)
|
|
2428
2949
|
{
|
|
2429
2950
|
Prop->PropertyFlags = OriginalFlags;
|
|
@@ -2460,10 +2981,13 @@ WriteDone:
|
|
|
2460
2981
|
auto Result = MCPSuccess();
|
|
2461
2982
|
MCPSetUpdated(Result);
|
|
2462
2983
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
2984
|
+
Result->SetStringField(TEXT("actorPath"), TargetActor->GetPathName());
|
|
2463
2985
|
Result->SetStringField(TEXT("propertyName"), PropertyName);
|
|
2464
2986
|
Result->SetStringField(TEXT("previousValue"), PrevValue);
|
|
2465
2987
|
|
|
2988
|
+
// The undo travels by path so replaying it cannot land on a namesake (#983).
|
|
2466
2989
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
2990
|
+
if (!bWorldSettings) Payload->SetStringField(TEXT("actorPath"), TargetActor->GetPathName());
|
|
2467
2991
|
Payload->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
2468
2992
|
Payload->SetStringField(TEXT("propertyName"), PropertyName);
|
|
2469
2993
|
Payload->SetStringField(TEXT("value"), PrevValue);
|
|
@@ -2512,9 +3036,28 @@ TSharedPtr<FJsonValue> FLevelHandlers::ReadActorMotion(const TSharedPtr<FJsonObj
|
|
|
2512
3036
|
FString L; if (V->TryGetString(L) && !L.IsEmpty()) Labels.Add(L);
|
|
2513
3037
|
}
|
|
2514
3038
|
}
|
|
2515
|
-
|
|
3039
|
+
// #983: the same list spelled as object paths, which is what a caller
|
|
3040
|
+
// reaches for when several actors share a label. These are kept apart from
|
|
3041
|
+
// the label list and resolved by MCPFindActorByPath rather than folded into
|
|
3042
|
+
// the label token tier, so the export-text form and a case difference both
|
|
3043
|
+
// resolve here exactly as they do everywhere else.
|
|
3044
|
+
TArray<FString> Paths;
|
|
3045
|
+
FString SinglePath;
|
|
3046
|
+
if (Params->TryGetStringField(TEXT("actorPath"), SinglePath) && !SinglePath.IsEmpty())
|
|
2516
3047
|
{
|
|
2517
|
-
|
|
3048
|
+
Paths.Add(SinglePath);
|
|
3049
|
+
}
|
|
3050
|
+
const TArray<TSharedPtr<FJsonValue>>* PathsArr = nullptr;
|
|
3051
|
+
if (Params->TryGetArrayField(TEXT("actorPaths"), PathsArr) && PathsArr)
|
|
3052
|
+
{
|
|
3053
|
+
for (const TSharedPtr<FJsonValue>& V : *PathsArr)
|
|
3054
|
+
{
|
|
3055
|
+
FString P; if (V->TryGetString(P) && !P.IsEmpty()) Paths.Add(P);
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
if (Labels.Num() == 0 && Paths.Num() == 0)
|
|
3059
|
+
{
|
|
3060
|
+
return MCPError(TEXT("Pass at least one of 'actorLabel', 'actorLabels', 'actorPath' or 'actorPaths'"));
|
|
2518
3061
|
}
|
|
2519
3062
|
|
|
2520
3063
|
auto VecToJson = [](const FVector& V) -> TSharedPtr<FJsonObject>
|
|
@@ -2532,16 +3075,37 @@ TSharedPtr<FJsonValue> FLevelHandlers::ReadActorMotion(const TSharedPtr<FJsonObj
|
|
|
2532
3075
|
|
|
2533
3076
|
TArray<TSharedPtr<FJsonValue>> Samples;
|
|
2534
3077
|
TArray<TSharedPtr<FJsonValue>> Missing;
|
|
3078
|
+
TArray<AActor*> Targets;
|
|
2535
3079
|
for (const FString& Label : Labels)
|
|
2536
3080
|
{
|
|
2537
|
-
|
|
2538
|
-
|
|
3081
|
+
// #983: this is a read, and a plural one, so a label naming several
|
|
3082
|
+
// actors samples all of them rather than one at random. Each row
|
|
3083
|
+
// carries actorPath, which is what a follow-up write should target.
|
|
3084
|
+
TArray<AActor*> Matches;
|
|
3085
|
+
MCPCollectActorsByToken(TargetWorld, Label, EMCPActorMatch::LabelNameOrPath, Matches);
|
|
3086
|
+
if (Matches.Num() == 0)
|
|
2539
3087
|
{
|
|
2540
3088
|
Missing.Add(MakeShared<FJsonValueString>(Label));
|
|
2541
3089
|
continue;
|
|
2542
3090
|
}
|
|
3091
|
+
for (AActor* Match : Matches) Targets.AddUnique(Match);
|
|
3092
|
+
}
|
|
3093
|
+
for (const FString& Path : Paths)
|
|
3094
|
+
{
|
|
3095
|
+
if (AActor* ByPath = MCPFindActorByPath(TargetWorld, Path))
|
|
3096
|
+
{
|
|
3097
|
+
Targets.AddUnique(ByPath);
|
|
3098
|
+
}
|
|
3099
|
+
else
|
|
3100
|
+
{
|
|
3101
|
+
Missing.Add(MakeShared<FJsonValueString>(Path));
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
for (AActor* Actor : Targets)
|
|
3105
|
+
{
|
|
2543
3106
|
TSharedPtr<FJsonObject> S = MakeShared<FJsonObject>();
|
|
2544
|
-
S->SetStringField(TEXT("actorLabel"),
|
|
3107
|
+
S->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
3108
|
+
S->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
2545
3109
|
S->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
|
|
2546
3110
|
S->SetObjectField(TEXT("location"), VecToJson(Actor->GetActorLocation()));
|
|
2547
3111
|
S->SetObjectField(TEXT("rotation"), RotToJson(Actor->GetActorRotation()));
|
|
@@ -2599,10 +3163,12 @@ TSharedPtr<FJsonValue> FLevelHandlers::AddHismcInstances(const TSharedPtr<FJsonO
|
|
|
2599
3163
|
{
|
|
2600
3164
|
REQUIRE_EDITOR_WORLD(World);
|
|
2601
3165
|
FString ActorLabel;
|
|
2602
|
-
if (auto Err =
|
|
3166
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
2603
3167
|
|
|
2604
|
-
|
|
2605
|
-
|
|
3168
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
3169
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
3170
|
+
if (!Actor) return ActorErr;
|
|
3171
|
+
ActorLabel = Actor->GetActorLabel();
|
|
2606
3172
|
|
|
2607
3173
|
FString ComponentName = OptionalString(Params, TEXT("componentName"));
|
|
2608
3174
|
UInstancedStaticMeshComponent* ISMC = nullptr;
|
|
@@ -2683,6 +3249,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::AddHismcInstances(const TSharedPtr<FJsonO
|
|
|
2683
3249
|
auto Result = MCPSuccess();
|
|
2684
3250
|
MCPSetCreated(Result);
|
|
2685
3251
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
3252
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
2686
3253
|
Result->SetStringField(TEXT("componentName"), ISMC->GetName());
|
|
2687
3254
|
Result->SetStringField(TEXT("componentClass"), ISMC->GetClass()->GetName());
|
|
2688
3255
|
Result->SetNumberField(TEXT("addedCount"), AddedIndices.Num());
|
|
@@ -2714,9 +3281,11 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetInstanceTransforms(const TSharedPtr<FJ
|
|
|
2714
3281
|
{
|
|
2715
3282
|
REQUIRE_EDITOR_WORLD(World);
|
|
2716
3283
|
FString ActorLabel;
|
|
2717
|
-
if (auto Err =
|
|
2718
|
-
|
|
2719
|
-
|
|
3284
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
3285
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
3286
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
3287
|
+
if (!Actor) return ActorErr;
|
|
3288
|
+
ActorLabel = Actor->GetActorLabel();
|
|
2720
3289
|
|
|
2721
3290
|
const FString ComponentName = OptionalString(Params, TEXT("componentName"));
|
|
2722
3291
|
UInstancedStaticMeshComponent* ISMC = ResolveISMC(Actor, ComponentName);
|
|
@@ -2739,6 +3308,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::GetInstanceTransforms(const TSharedPtr<FJ
|
|
|
2739
3308
|
|
|
2740
3309
|
auto Result = MCPSuccess();
|
|
2741
3310
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
3311
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
2742
3312
|
Result->SetStringField(TEXT("componentName"), ISMC->GetName());
|
|
2743
3313
|
Result->SetNumberField(TEXT("count"), Count);
|
|
2744
3314
|
Result->SetBoolField(TEXT("worldSpace"), bWorldSpace);
|
|
@@ -2751,9 +3321,11 @@ TSharedPtr<FJsonValue> FLevelHandlers::UpdateInstanceTransform(const TSharedPtr<
|
|
|
2751
3321
|
{
|
|
2752
3322
|
REQUIRE_EDITOR_WORLD(World);
|
|
2753
3323
|
FString ActorLabel;
|
|
2754
|
-
if (auto Err =
|
|
2755
|
-
|
|
2756
|
-
|
|
3324
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
3325
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
3326
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
3327
|
+
if (!Actor) return ActorErr;
|
|
3328
|
+
ActorLabel = Actor->GetActorLabel();
|
|
2757
3329
|
|
|
2758
3330
|
const FString ComponentName = OptionalString(Params, TEXT("componentName"));
|
|
2759
3331
|
UInstancedStaticMeshComponent* ISMC = ResolveISMC(Actor, ComponentName);
|
|
@@ -2786,6 +3358,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::UpdateInstanceTransform(const TSharedPtr<
|
|
|
2786
3358
|
auto Result = MCPSuccess();
|
|
2787
3359
|
MCPSetUpdated(Result);
|
|
2788
3360
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
3361
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
2789
3362
|
Result->SetStringField(TEXT("componentName"), ISMC->GetName());
|
|
2790
3363
|
Result->SetNumberField(TEXT("index"), Index);
|
|
2791
3364
|
return MCPResult(Result);
|
|
@@ -2796,9 +3369,11 @@ TSharedPtr<FJsonValue> FLevelHandlers::RemoveInstance(const TSharedPtr<FJsonObje
|
|
|
2796
3369
|
{
|
|
2797
3370
|
REQUIRE_EDITOR_WORLD(World);
|
|
2798
3371
|
FString ActorLabel;
|
|
2799
|
-
if (auto Err =
|
|
2800
|
-
|
|
2801
|
-
|
|
3372
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
3373
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
3374
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
3375
|
+
if (!Actor) return ActorErr;
|
|
3376
|
+
ActorLabel = Actor->GetActorLabel();
|
|
2802
3377
|
|
|
2803
3378
|
const FString ComponentName = OptionalString(Params, TEXT("componentName"));
|
|
2804
3379
|
UInstancedStaticMeshComponent* ISMC = ResolveISMC(Actor, ComponentName);
|
|
@@ -2818,6 +3393,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::RemoveInstance(const TSharedPtr<FJsonObje
|
|
|
2818
3393
|
auto Result = MCPSuccess();
|
|
2819
3394
|
Result->SetBoolField(TEXT("removed"), bOk);
|
|
2820
3395
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
3396
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
2821
3397
|
Result->SetStringField(TEXT("componentName"), ISMC->GetName());
|
|
2822
3398
|
Result->SetNumberField(TEXT("remainingInstances"), ISMC->GetInstanceCount());
|
|
2823
3399
|
return MCPResult(Result);
|
|
@@ -2884,12 +3460,16 @@ TSharedPtr<FJsonValue> FLevelHandlers::ExportActorFbx(const TSharedPtr<FJsonObje
|
|
|
2884
3460
|
{
|
|
2885
3461
|
REQUIRE_EDITOR_WORLD(World);
|
|
2886
3462
|
FString ActorLabel;
|
|
2887
|
-
if (auto Err =
|
|
3463
|
+
if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
|
|
2888
3464
|
FString OutputPath;
|
|
2889
3465
|
if (auto Err = RequireStringAlt(Params, TEXT("outputPath"), TEXT("filePath"), OutputPath)) return Err;
|
|
2890
3466
|
|
|
2891
|
-
|
|
2892
|
-
|
|
3467
|
+
FMCPActorSelector ActorSel;
|
|
3468
|
+
ActorSel.Match = EMCPActorMatch::LabelNameOrPath;
|
|
3469
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
3470
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr, ActorSel);
|
|
3471
|
+
if (!Actor) return ActorErr;
|
|
3472
|
+
ActorLabel = Actor->GetActorLabel();
|
|
2893
3473
|
|
|
2894
3474
|
// Resolve the mesh asset from the actor (skeletal first, then static).
|
|
2895
3475
|
UObject* MeshAsset = nullptr;
|
|
@@ -2999,25 +3579,56 @@ TSharedPtr<FJsonValue> FLevelHandlers::SpawnSkeletalMeshActor(const TSharedPtr<F
|
|
|
2999
3579
|
if (!Actor) return MCPError(TEXT("Failed to spawn SkeletalMeshActor"));
|
|
3000
3580
|
if (!Label.IsEmpty()) Actor->SetActorLabel(Label);
|
|
3001
3581
|
|
|
3582
|
+
// #946: per-slot COMPONENT material overrides, reported rather than
|
|
3583
|
+
// applied silently. These write the component's OverrideMaterials, which
|
|
3584
|
+
// is a different thing from the mesh ASSET's own slots: the asset is
|
|
3585
|
+
// untouched here. For an actor that is already placed, or to apply one
|
|
3586
|
+
// material to every slot, use level(set_component_materials).
|
|
3587
|
+
TArray<TSharedPtr<FJsonValue>> MaterialResults;
|
|
3588
|
+
int32 FailedMaterials = 0;
|
|
3002
3589
|
USkeletalMeshComponent* Comp = Actor->GetSkeletalMeshComponent();
|
|
3003
3590
|
if (Comp)
|
|
3004
3591
|
{
|
|
3005
3592
|
Comp->SetSkeletalMeshAsset(Mesh);
|
|
3006
3593
|
|
|
3007
|
-
// Optional per-slot material overrides.
|
|
3008
3594
|
const TArray<TSharedPtr<FJsonValue>>* Mats = nullptr;
|
|
3009
3595
|
if (Params->TryGetArrayField(TEXT("materials"), Mats) && Mats)
|
|
3010
3596
|
{
|
|
3597
|
+
const int32 SlotCount = Comp->GetNumMaterials();
|
|
3011
3598
|
for (int32 i = 0; i < Mats->Num(); ++i)
|
|
3012
3599
|
{
|
|
3013
3600
|
FString MatPath;
|
|
3014
|
-
|
|
3601
|
+
const bool bHasPath =
|
|
3602
|
+
(*Mats)[i].IsValid() && (*Mats)[i]->TryGetString(MatPath) && !MatPath.IsEmpty();
|
|
3603
|
+
if (!bHasPath) continue;
|
|
3604
|
+
|
|
3605
|
+
TSharedPtr<FJsonObject> MatRow = MakeShared<FJsonObject>();
|
|
3606
|
+
MatRow->SetNumberField(TEXT("slotIndex"), i);
|
|
3607
|
+
MatRow->SetStringField(TEXT("materialPath"), MatPath);
|
|
3608
|
+
|
|
3609
|
+
// A slot index past the end, or a path that does not load,
|
|
3610
|
+
// used to be dropped on the floor. That reads as a successful
|
|
3611
|
+
// assignment that never happened.
|
|
3612
|
+
if (i >= SlotCount)
|
|
3015
3613
|
{
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3614
|
+
MatRow->SetBoolField(TEXT("ok"), false);
|
|
3615
|
+
MatRow->SetStringField(TEXT("error"), FString::Printf(
|
|
3616
|
+
TEXT("slot %d is past the mesh's %d material slots"), i, SlotCount));
|
|
3617
|
+
++FailedMaterials;
|
|
3618
|
+
}
|
|
3619
|
+
else if (UMaterialInterface* Mat = LoadAssetByPath<UMaterialInterface>(MatPath))
|
|
3620
|
+
{
|
|
3621
|
+
Comp->SetMaterial(i, Mat);
|
|
3622
|
+
MatRow->SetBoolField(TEXT("ok"), true);
|
|
3623
|
+
MatRow->SetStringField(TEXT("source"), TEXT("componentOverride"));
|
|
3624
|
+
}
|
|
3625
|
+
else
|
|
3626
|
+
{
|
|
3627
|
+
MatRow->SetBoolField(TEXT("ok"), false);
|
|
3628
|
+
MatRow->SetStringField(TEXT("error"), TEXT("material not found"));
|
|
3629
|
+
++FailedMaterials;
|
|
3020
3630
|
}
|
|
3631
|
+
MaterialResults.Add(MakeShared<FJsonValueObject>(MatRow));
|
|
3021
3632
|
}
|
|
3022
3633
|
}
|
|
3023
3634
|
|
|
@@ -3051,9 +3662,18 @@ TSharedPtr<FJsonValue> FLevelHandlers::SpawnSkeletalMeshActor(const TSharedPtr<F
|
|
|
3051
3662
|
|
|
3052
3663
|
auto Result = MCPSuccess();
|
|
3053
3664
|
MCPSetCreated(Result);
|
|
3665
|
+
Result->SetBoolField(TEXT("success"), FailedMaterials == 0);
|
|
3666
|
+
if (FailedMaterials > 0)
|
|
3667
|
+
{
|
|
3668
|
+
Result->SetStringField(TEXT("error"), FString::Printf(
|
|
3669
|
+
TEXT("The actor was spawned but %d material override(s) did not apply; see materials[]."),
|
|
3670
|
+
FailedMaterials));
|
|
3671
|
+
}
|
|
3054
3672
|
Result->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
3055
3673
|
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
3056
3674
|
Result->SetStringField(TEXT("skeletalMesh"), Mesh->GetPathName());
|
|
3675
|
+
Result->SetNumberField(TEXT("materialSlotCount"), Comp ? Comp->GetNumMaterials() : 0);
|
|
3676
|
+
Result->SetArrayField(TEXT("materials"), MaterialResults);
|
|
3057
3677
|
Result->SetObjectField(TEXT("location"), MCPVec3ToJsonObject(Loc));
|
|
3058
3678
|
Result->SetObjectField(TEXT("boxExtent"), MCPVec3ToJsonObject(BoxExtent));
|
|
3059
3679
|
TSharedPtr<FJsonObject> Rb = MakeShared<FJsonObject>();
|
|
@@ -3191,14 +3811,46 @@ TSharedPtr<FJsonValue> FLevelHandlers::DeleteActors(const TSharedPtr<FJsonObject
|
|
|
3191
3811
|
{
|
|
3192
3812
|
REQUIRE_EDITOR_WORLD(World);
|
|
3193
3813
|
|
|
3814
|
+
// #963: the filter names say what they match, and the two that were
|
|
3815
|
+
// previously reachable only through get_outliner's looser nameFilter are
|
|
3816
|
+
// now first class here. labelPrefix stays a CASE-SENSITIVE PREFIX over the
|
|
3817
|
+
// EDITOR LABEL, which is what it always was; labelContains and nameContains
|
|
3818
|
+
// are the substring forms, over the label and the internal name
|
|
3819
|
+
// respectively. Overloading one parameter to mean both is how a filter that
|
|
3820
|
+
// selects fifteen actors in one action selects none in another.
|
|
3194
3821
|
const FString LabelPrefix = OptionalString(Params, TEXT("labelPrefix"));
|
|
3822
|
+
const FString LabelContains = OptionalString(Params, TEXT("labelContains"));
|
|
3823
|
+
const FString NameContains = OptionalString(Params, TEXT("nameContains"));
|
|
3195
3824
|
const FString ClassName = OptionalString(Params, TEXT("className"));
|
|
3196
3825
|
const FString Tag = OptionalString(Params, TEXT("tag"));
|
|
3197
3826
|
const bool bDryRun = OptionalBool(Params, TEXT("dryRun"), false);
|
|
3198
3827
|
|
|
3199
|
-
|
|
3828
|
+
TArray<FString> ClassPathNeedles;
|
|
3829
|
+
const FString ClassPathContains = OptionalString(Params, TEXT("classPathContains"));
|
|
3830
|
+
if (!ClassPathContains.IsEmpty())
|
|
3200
3831
|
{
|
|
3201
|
-
|
|
3832
|
+
ClassPathNeedles.Add(ClassPathContains);
|
|
3833
|
+
}
|
|
3834
|
+
const TArray<TSharedPtr<FJsonValue>>* ClassPathAny = nullptr;
|
|
3835
|
+
if (Params.IsValid() && Params->TryGetArrayField(TEXT("classPathContainsAny"), ClassPathAny) && ClassPathAny)
|
|
3836
|
+
{
|
|
3837
|
+
for (const TSharedPtr<FJsonValue>& Value : *ClassPathAny)
|
|
3838
|
+
{
|
|
3839
|
+
FString Needle;
|
|
3840
|
+
if (Value.IsValid() && Value->TryGetString(Needle) && !Needle.IsEmpty())
|
|
3841
|
+
{
|
|
3842
|
+
ClassPathNeedles.Add(Needle);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
// #924 added the class-path filters and #963 added the label/name ones. Both
|
|
3848
|
+
// are live, so the guard has to accept either family; requiring only one
|
|
3849
|
+
// family's filters would make the other silently unusable.
|
|
3850
|
+
if (LabelPrefix.IsEmpty() && LabelContains.IsEmpty() && NameContains.IsEmpty() &&
|
|
3851
|
+
ClassName.IsEmpty() && Tag.IsEmpty() && ClassPathNeedles.Num() == 0)
|
|
3852
|
+
{
|
|
3853
|
+
return MCPError(TEXT("Provide at least one filter: labelPrefix (case-sensitive prefix over the editor label), labelContains (case-insensitive substring over the label), nameContains (case-insensitive substring over the internal name), className, tag, classPathContains, or classPathContainsAny"));
|
|
3202
3854
|
}
|
|
3203
3855
|
|
|
3204
3856
|
TArray<AActor*> Matches;
|
|
@@ -3207,19 +3859,37 @@ TSharedPtr<FJsonValue> FLevelHandlers::DeleteActors(const TSharedPtr<FJsonObject
|
|
|
3207
3859
|
AActor* A = *It;
|
|
3208
3860
|
if (!A) continue;
|
|
3209
3861
|
if (!LabelPrefix.IsEmpty() && !A->GetActorLabel().StartsWith(LabelPrefix)) continue;
|
|
3862
|
+
if (!LabelContains.IsEmpty() && !A->GetActorLabel().Contains(LabelContains, ESearchCase::IgnoreCase)) continue;
|
|
3863
|
+
if (!NameContains.IsEmpty() && !A->GetName().Contains(NameContains, ESearchCase::IgnoreCase)) continue;
|
|
3210
3864
|
if (!ClassName.IsEmpty())
|
|
3211
3865
|
{
|
|
3212
3866
|
const FString CName = A->GetClass()->GetName();
|
|
3213
3867
|
if (!CName.Contains(ClassName)) continue;
|
|
3214
3868
|
}
|
|
3215
3869
|
if (!Tag.IsEmpty() && !A->ActorHasTag(FName(*Tag))) continue;
|
|
3870
|
+
if (ClassPathNeedles.Num() > 0)
|
|
3871
|
+
{
|
|
3872
|
+
const FString ClassPath = A->GetClass()->GetPathName();
|
|
3873
|
+
bool bPathMatch = false;
|
|
3874
|
+
for (const FString& Needle : ClassPathNeedles)
|
|
3875
|
+
{
|
|
3876
|
+
if (ClassPath.Contains(Needle, ESearchCase::IgnoreCase))
|
|
3877
|
+
{
|
|
3878
|
+
bPathMatch = true;
|
|
3879
|
+
break;
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
if (!bPathMatch) continue;
|
|
3883
|
+
}
|
|
3216
3884
|
Matches.Add(A);
|
|
3217
3885
|
}
|
|
3218
3886
|
|
|
3219
3887
|
TArray<TSharedPtr<FJsonValue>> Labels;
|
|
3888
|
+
TArray<TSharedPtr<FJsonValue>> ClassPaths;
|
|
3220
3889
|
for (AActor* A : Matches)
|
|
3221
3890
|
{
|
|
3222
3891
|
Labels.Add(MakeShared<FJsonValueString>(A->GetActorLabel()));
|
|
3892
|
+
ClassPaths.Add(MakeShared<FJsonValueString>(A->GetClass()->GetPathName()));
|
|
3223
3893
|
}
|
|
3224
3894
|
|
|
3225
3895
|
int32 Deleted = 0;
|
|
@@ -3246,17 +3916,38 @@ TSharedPtr<FJsonValue> FLevelHandlers::DeleteActors(const TSharedPtr<FJsonObject
|
|
|
3246
3916
|
Result->SetNumberField(TEXT("matched"), Matches.Num());
|
|
3247
3917
|
Result->SetNumberField(TEXT("deleted"), Deleted);
|
|
3248
3918
|
Result->SetArrayField(TEXT("labels"), Labels);
|
|
3919
|
+
Result->SetArrayField(TEXT("classPaths"), ClassPaths);
|
|
3920
|
+
|
|
3921
|
+
// #963: a destructive action that matched nothing must not answer with a
|
|
3922
|
+
// bare success and a zero. A caller who trusts that concludes there is
|
|
3923
|
+
// nothing to delete and moves on, which is exactly what happened. Two
|
|
3924
|
+
// things can produce a wrong zero here, and the response now names both.
|
|
3925
|
+
if (Matches.IsEmpty())
|
|
3926
|
+
{
|
|
3927
|
+
Result->SetStringField(TEXT("zeroMatchNote"),
|
|
3928
|
+
TEXT("No actor matched. This is a filter result, not a statement that the actors do not exist."));
|
|
3929
|
+
const FString Needle = !LabelPrefix.IsEmpty()
|
|
3930
|
+
? LabelPrefix
|
|
3931
|
+
: (!LabelContains.IsEmpty() ? LabelContains : NameContains);
|
|
3932
|
+
if (const TSharedPtr<FJsonObject> Hint = MCPDescribeZeroActorMatch(World, Needle))
|
|
3933
|
+
{
|
|
3934
|
+
Result->SetObjectField(TEXT("zeroMatchHint"), Hint);
|
|
3935
|
+
}
|
|
3936
|
+
MCPNoteLoadedOnlyEnumeration(World, Result);
|
|
3937
|
+
}
|
|
3249
3938
|
return MCPResult(Result);
|
|
3250
3939
|
}
|
|
3251
3940
|
|
|
3252
3941
|
TSharedPtr<FJsonValue> FLevelHandlers::AddActorTag(const TSharedPtr<FJsonObject>& Params)
|
|
3253
3942
|
{
|
|
3254
3943
|
REQUIRE_EDITOR_WORLD(World);
|
|
3255
|
-
FString ActorLabel; if (auto E =
|
|
3944
|
+
FString ActorLabel; if (auto E = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return E;
|
|
3256
3945
|
FString Tag; if (auto E = RequireString(Params, TEXT("tag"), Tag)) return E;
|
|
3257
3946
|
|
|
3258
|
-
|
|
3259
|
-
|
|
3947
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
3948
|
+
AActor* A = MCPResolveActor(World, Params, ActorErr);
|
|
3949
|
+
if (!A) return ActorErr;
|
|
3950
|
+
ActorLabel = A->GetActorLabel();
|
|
3260
3951
|
|
|
3261
3952
|
const FName TagName(*Tag);
|
|
3262
3953
|
const bool bAlreadyHad = A->Tags.Contains(TagName);
|
|
@@ -3269,6 +3960,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::AddActorTag(const TSharedPtr<FJsonObject>
|
|
|
3269
3960
|
auto Result = MCPSuccess();
|
|
3270
3961
|
if (bAlreadyHad) MCPSetExisted(Result); else MCPSetUpdated(Result);
|
|
3271
3962
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
3963
|
+
Result->SetStringField(TEXT("actorPath"), A->GetPathName());
|
|
3272
3964
|
Result->SetStringField(TEXT("tag"), Tag);
|
|
3273
3965
|
TArray<TSharedPtr<FJsonValue>> TagsOut;
|
|
3274
3966
|
for (const FName& T : A->Tags) TagsOut.Add(MakeShared<FJsonValueString>(T.ToString()));
|
|
@@ -3279,11 +3971,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::AddActorTag(const TSharedPtr<FJsonObject>
|
|
|
3279
3971
|
TSharedPtr<FJsonValue> FLevelHandlers::RemoveActorTag(const TSharedPtr<FJsonObject>& Params)
|
|
3280
3972
|
{
|
|
3281
3973
|
REQUIRE_EDITOR_WORLD(World);
|
|
3282
|
-
FString ActorLabel; if (auto E =
|
|
3974
|
+
FString ActorLabel; if (auto E = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return E;
|
|
3283
3975
|
FString Tag; if (auto E = RequireString(Params, TEXT("tag"), Tag)) return E;
|
|
3284
3976
|
|
|
3285
|
-
|
|
3286
|
-
|
|
3977
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
3978
|
+
AActor* A = MCPResolveActor(World, Params, ActorErr);
|
|
3979
|
+
if (!A) return ActorErr;
|
|
3980
|
+
ActorLabel = A->GetActorLabel();
|
|
3287
3981
|
|
|
3288
3982
|
const FName TagName(*Tag);
|
|
3289
3983
|
const int32 Removed = A->Tags.Remove(TagName);
|
|
@@ -3296,6 +3990,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::RemoveActorTag(const TSharedPtr<FJsonObje
|
|
|
3296
3990
|
auto Result = MCPSuccess();
|
|
3297
3991
|
if (Removed == 0) MCPSetExisted(Result); else MCPSetUpdated(Result);
|
|
3298
3992
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
3993
|
+
Result->SetStringField(TEXT("actorPath"), A->GetPathName());
|
|
3299
3994
|
Result->SetStringField(TEXT("tag"), Tag);
|
|
3300
3995
|
Result->SetNumberField(TEXT("removed"), Removed);
|
|
3301
3996
|
return MCPResult(Result);
|
|
@@ -3304,10 +3999,12 @@ TSharedPtr<FJsonValue> FLevelHandlers::RemoveActorTag(const TSharedPtr<FJsonObje
|
|
|
3304
3999
|
TSharedPtr<FJsonValue> FLevelHandlers::SetActorTags(const TSharedPtr<FJsonObject>& Params)
|
|
3305
4000
|
{
|
|
3306
4001
|
REQUIRE_EDITOR_WORLD(World);
|
|
3307
|
-
FString ActorLabel; if (auto E =
|
|
4002
|
+
FString ActorLabel; if (auto E = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return E;
|
|
3308
4003
|
|
|
3309
|
-
|
|
3310
|
-
|
|
4004
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
4005
|
+
AActor* A = MCPResolveActor(World, Params, ActorErr);
|
|
4006
|
+
if (!A) return ActorErr;
|
|
4007
|
+
ActorLabel = A->GetActorLabel();
|
|
3311
4008
|
|
|
3312
4009
|
const TArray<TSharedPtr<FJsonValue>>* TagsArr = nullptr;
|
|
3313
4010
|
if (!Params->TryGetArrayField(TEXT("tags"), TagsArr) || !TagsArr)
|
|
@@ -3330,6 +4027,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorTags(const TSharedPtr<FJsonObject
|
|
|
3330
4027
|
auto Result = MCPSuccess();
|
|
3331
4028
|
MCPSetUpdated(Result);
|
|
3332
4029
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
4030
|
+
Result->SetStringField(TEXT("actorPath"), A->GetPathName());
|
|
3333
4031
|
TArray<TSharedPtr<FJsonValue>> Out;
|
|
3334
4032
|
for (const FName& T : A->Tags) Out.Add(MakeShared<FJsonValueString>(T.ToString()));
|
|
3335
4033
|
Result->SetArrayField(TEXT("tags"), Out);
|
|
@@ -3339,13 +4037,16 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorTags(const TSharedPtr<FJsonObject
|
|
|
3339
4037
|
TSharedPtr<FJsonValue> FLevelHandlers::ListActorTags(const TSharedPtr<FJsonObject>& Params)
|
|
3340
4038
|
{
|
|
3341
4039
|
REQUIRE_EDITOR_WORLD(World);
|
|
3342
|
-
FString ActorLabel; if (auto E =
|
|
4040
|
+
FString ActorLabel; if (auto E = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return E;
|
|
3343
4041
|
|
|
3344
|
-
|
|
3345
|
-
|
|
4042
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
4043
|
+
AActor* A = MCPResolveActor(World, Params, ActorErr);
|
|
4044
|
+
if (!A) return ActorErr;
|
|
4045
|
+
ActorLabel = A->GetActorLabel();
|
|
3346
4046
|
|
|
3347
4047
|
auto Result = MCPSuccess();
|
|
3348
4048
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
4049
|
+
Result->SetStringField(TEXT("actorPath"), A->GetPathName());
|
|
3349
4050
|
TArray<TSharedPtr<FJsonValue>> Out;
|
|
3350
4051
|
for (const FName& T : A->Tags) Out.Add(MakeShared<FJsonValueString>(T.ToString()));
|
|
3351
4052
|
Result->SetArrayField(TEXT("tags"), Out);
|
|
@@ -3357,13 +4058,21 @@ TSharedPtr<FJsonValue> FLevelHandlers::ListActorTags(const TSharedPtr<FJsonObjec
|
|
|
3357
4058
|
TSharedPtr<FJsonValue> FLevelHandlers::AttachActor(const TSharedPtr<FJsonObject>& Params)
|
|
3358
4059
|
{
|
|
3359
4060
|
REQUIRE_EDITOR_WORLD(World);
|
|
3360
|
-
FString ChildLabel; if (auto E =
|
|
3361
|
-
FString ParentLabel; if (auto E =
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
4061
|
+
FString ChildLabel; if (auto E = RequireStringAlt(Params, TEXT("childLabel"), TEXT("childPath"), ChildLabel)) return E;
|
|
4062
|
+
FString ParentLabel; if (auto E = RequireStringAlt(Params, TEXT("parentLabel"), TEXT("parentPath"), ParentLabel)) return E;
|
|
4063
|
+
|
|
4064
|
+
// #983: both ends of an attachment take a path. Attaching to whichever
|
|
4065
|
+
// namesake the iterator reached first is how a prop ends up parented to a
|
|
4066
|
+
// building at the other end of the map.
|
|
4067
|
+
FMCPActorSelector ChildSel; ChildSel.LabelKey = TEXT("childLabel"); ChildSel.PathKey = TEXT("childPath");
|
|
4068
|
+
FMCPActorSelector ParentSel; ParentSel.LabelKey = TEXT("parentLabel"); ParentSel.PathKey = TEXT("parentPath");
|
|
4069
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
4070
|
+
AActor* Child = MCPResolveActor(World, Params, ActorErr, ChildSel);
|
|
4071
|
+
if (!Child) return ActorErr;
|
|
4072
|
+
AActor* Parent = MCPResolveActor(World, Params, ActorErr, ParentSel);
|
|
4073
|
+
if (!Parent) return ActorErr;
|
|
4074
|
+
ChildLabel = Child->GetActorLabel();
|
|
4075
|
+
ParentLabel = Parent->GetActorLabel();
|
|
3367
4076
|
|
|
3368
4077
|
const FString RuleStr = OptionalString(Params, TEXT("attachRule"), TEXT("KeepWorld")).ToLower();
|
|
3369
4078
|
EAttachmentRule Loc = EAttachmentRule::KeepWorld;
|
|
@@ -3380,10 +4089,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::AttachActor(const TSharedPtr<FJsonObject>
|
|
|
3380
4089
|
MCPSetUpdated(Result);
|
|
3381
4090
|
Result->SetStringField(TEXT("childLabel"), ChildLabel);
|
|
3382
4091
|
Result->SetStringField(TEXT("parentLabel"), ParentLabel);
|
|
4092
|
+
Result->SetStringField(TEXT("childPath"), Child->GetPathName());
|
|
4093
|
+
Result->SetStringField(TEXT("parentPath"), Parent->GetPathName());
|
|
3383
4094
|
Result->SetBoolField(TEXT("attached"), bOk);
|
|
3384
4095
|
|
|
3385
4096
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
3386
4097
|
Payload->SetStringField(TEXT("childLabel"), ChildLabel);
|
|
4098
|
+
Payload->SetStringField(TEXT("childPath"), Child->GetPathName());
|
|
3387
4099
|
MCPSetRollback(Result, TEXT("detach_actor"), Payload);
|
|
3388
4100
|
return MCPResult(Result);
|
|
3389
4101
|
}
|
|
@@ -3391,10 +4103,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::AttachActor(const TSharedPtr<FJsonObject>
|
|
|
3391
4103
|
TSharedPtr<FJsonValue> FLevelHandlers::DetachActor(const TSharedPtr<FJsonObject>& Params)
|
|
3392
4104
|
{
|
|
3393
4105
|
REQUIRE_EDITOR_WORLD(World);
|
|
3394
|
-
FString ChildLabel; if (auto E =
|
|
4106
|
+
FString ChildLabel; if (auto E = RequireStringAlt(Params, TEXT("childLabel"), TEXT("childPath"), ChildLabel)) return E;
|
|
3395
4107
|
|
|
3396
|
-
|
|
3397
|
-
|
|
4108
|
+
FMCPActorSelector ChildSel; ChildSel.LabelKey = TEXT("childLabel"); ChildSel.PathKey = TEXT("childPath");
|
|
4109
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
4110
|
+
AActor* Child = MCPResolveActor(World, Params, ActorErr, ChildSel);
|
|
4111
|
+
if (!Child) return ActorErr;
|
|
4112
|
+
ChildLabel = Child->GetActorLabel();
|
|
3398
4113
|
|
|
3399
4114
|
Child->Modify();
|
|
3400
4115
|
Child->DetachFromActor(FDetachmentTransformRules(EDetachmentRule::KeepWorld, true));
|
|
@@ -3403,6 +4118,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::DetachActor(const TSharedPtr<FJsonObject>
|
|
|
3403
4118
|
auto Result = MCPSuccess();
|
|
3404
4119
|
MCPSetUpdated(Result);
|
|
3405
4120
|
Result->SetStringField(TEXT("childLabel"), ChildLabel);
|
|
4121
|
+
Result->SetStringField(TEXT("childPath"), Child->GetPathName());
|
|
3406
4122
|
Result->SetBoolField(TEXT("detached"), true);
|
|
3407
4123
|
return MCPResult(Result);
|
|
3408
4124
|
}
|
|
@@ -3413,13 +4129,21 @@ TSharedPtr<FJsonValue> FLevelHandlers::DetachActor(const TSharedPtr<FJsonObject>
|
|
|
3413
4129
|
TSharedPtr<FJsonValue> FLevelHandlers::AttachComponent(const TSharedPtr<FJsonObject>& Params)
|
|
3414
4130
|
{
|
|
3415
4131
|
REQUIRE_EDITOR_WORLD(World);
|
|
3416
|
-
FString ChildLabel; if (auto E =
|
|
3417
|
-
FString ParentLabel; if (auto E =
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
4132
|
+
FString ChildLabel; if (auto E = RequireStringAlt(Params, TEXT("childLabel"), TEXT("childPath"), ChildLabel)) return E;
|
|
4133
|
+
FString ParentLabel; if (auto E = RequireStringAlt(Params, TEXT("parentLabel"), TEXT("parentPath"), ParentLabel)) return E;
|
|
4134
|
+
|
|
4135
|
+
// #983: both ends of an attachment take a path. Attaching to whichever
|
|
4136
|
+
// namesake the iterator reached first is how a prop ends up parented to a
|
|
4137
|
+
// building at the other end of the map.
|
|
4138
|
+
FMCPActorSelector ChildSel; ChildSel.LabelKey = TEXT("childLabel"); ChildSel.PathKey = TEXT("childPath");
|
|
4139
|
+
FMCPActorSelector ParentSel; ParentSel.LabelKey = TEXT("parentLabel"); ParentSel.PathKey = TEXT("parentPath");
|
|
4140
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
4141
|
+
AActor* Child = MCPResolveActor(World, Params, ActorErr, ChildSel);
|
|
4142
|
+
if (!Child) return ActorErr;
|
|
4143
|
+
AActor* Parent = MCPResolveActor(World, Params, ActorErr, ParentSel);
|
|
4144
|
+
if (!Parent) return ActorErr;
|
|
4145
|
+
ChildLabel = Child->GetActorLabel();
|
|
4146
|
+
ParentLabel = Parent->GetActorLabel();
|
|
3423
4147
|
|
|
3424
4148
|
const FString ChildComponentSelector = OptionalString(Params, TEXT("childComponentName"));
|
|
3425
4149
|
const FString ParentComponentSelector = OptionalString(Params, TEXT("parentComponentName"));
|
|
@@ -3645,10 +4369,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::AttachComponent(const TSharedPtr<FJsonObj
|
|
|
3645
4369
|
TSharedPtr<FJsonValue> FLevelHandlers::DetachComponent(const TSharedPtr<FJsonObject>& Params)
|
|
3646
4370
|
{
|
|
3647
4371
|
REQUIRE_EDITOR_WORLD(World);
|
|
3648
|
-
FString ChildLabel; if (auto E =
|
|
4372
|
+
FString ChildLabel; if (auto E = RequireStringAlt(Params, TEXT("childLabel"), TEXT("childPath"), ChildLabel)) return E;
|
|
3649
4373
|
|
|
3650
|
-
|
|
3651
|
-
|
|
4374
|
+
FMCPActorSelector ChildSel; ChildSel.LabelKey = TEXT("childLabel"); ChildSel.PathKey = TEXT("childPath");
|
|
4375
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
4376
|
+
AActor* Child = MCPResolveActor(World, Params, ActorErr, ChildSel);
|
|
4377
|
+
if (!Child) return ActorErr;
|
|
4378
|
+
ChildLabel = Child->GetActorLabel();
|
|
3652
4379
|
|
|
3653
4380
|
const FString ChildComponentSelector = OptionalString(Params, TEXT("childComponentName"));
|
|
3654
4381
|
UActorComponent* ResolvedChildComponent = ChildComponentSelector.IsEmpty()
|
|
@@ -3708,11 +4435,13 @@ TSharedPtr<FJsonValue> FLevelHandlers::DetachComponent(const TSharedPtr<FJsonObj
|
|
|
3708
4435
|
TSharedPtr<FJsonValue> FLevelHandlers::SetActorMobility(const TSharedPtr<FJsonObject>& Params)
|
|
3709
4436
|
{
|
|
3710
4437
|
REQUIRE_EDITOR_WORLD(World);
|
|
3711
|
-
FString ActorLabel; if (auto E =
|
|
4438
|
+
FString ActorLabel; if (auto E = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return E;
|
|
3712
4439
|
FString MobilityStr; if (auto E = RequireString(Params, TEXT("mobility"), MobilityStr)) return E;
|
|
3713
4440
|
|
|
3714
|
-
|
|
3715
|
-
|
|
4441
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
4442
|
+
AActor* A = MCPResolveActor(World, Params, ActorErr);
|
|
4443
|
+
if (!A) return ActorErr;
|
|
4444
|
+
ActorLabel = A->GetActorLabel();
|
|
3716
4445
|
USceneComponent* Root = A->GetRootComponent();
|
|
3717
4446
|
if (!Root) return MCPError(FString::Printf(TEXT("Actor '%s' has no root component"), *ActorLabel));
|
|
3718
4447
|
|
|
@@ -3731,12 +4460,14 @@ TSharedPtr<FJsonValue> FLevelHandlers::SetActorMobility(const TSharedPtr<FJsonOb
|
|
|
3731
4460
|
auto Result = MCPSuccess();
|
|
3732
4461
|
MCPSetUpdated(Result);
|
|
3733
4462
|
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
4463
|
+
Result->SetStringField(TEXT("actorPath"), A->GetPathName());
|
|
3734
4464
|
Result->SetStringField(TEXT("mobility"), MobilityStr);
|
|
3735
4465
|
|
|
3736
4466
|
const TCHAR* PrevStr = Prev == EComponentMobility::Movable ? TEXT("movable")
|
|
3737
4467
|
: Prev == EComponentMobility::Stationary ? TEXT("stationary") : TEXT("static");
|
|
3738
4468
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
3739
4469
|
Payload->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
4470
|
+
Payload->SetStringField(TEXT("actorPath"), A->GetPathName());
|
|
3740
4471
|
Payload->SetStringField(TEXT("mobility"), PrevStr);
|
|
3741
4472
|
MCPSetRollback(Result, TEXT("set_actor_mobility"), Payload);
|
|
3742
4473
|
return MCPResult(Result);
|
|
@@ -4021,7 +4752,22 @@ TSharedPtr<FJsonValue> FLevelHandlers::BatchTranslate(const TSharedPtr<FJsonObje
|
|
|
4021
4752
|
{
|
|
4022
4753
|
FString S; if (V.IsValid() && V->TryGetString(S))
|
|
4023
4754
|
{
|
|
4024
|
-
|
|
4755
|
+
// #983: a batch is the plural case, so a label naming several
|
|
4756
|
+
// actors moves all of them rather than one at random.
|
|
4757
|
+
TArray<AActor*> Matches;
|
|
4758
|
+
MCPCollectActorsByToken(World, S, EMCPActorMatch::Label, Matches);
|
|
4759
|
+
for (AActor* Match : Matches) Targets.Add(Match);
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
const TArray<TSharedPtr<FJsonValue>>* PathArr = nullptr;
|
|
4764
|
+
if (Params->TryGetArrayField(TEXT("actorPaths"), PathArr) && PathArr)
|
|
4765
|
+
{
|
|
4766
|
+
for (const auto& V : *PathArr)
|
|
4767
|
+
{
|
|
4768
|
+
FString S; if (V.IsValid() && V->TryGetString(S))
|
|
4769
|
+
{
|
|
4770
|
+
if (AActor* A = MCPFindActorByPath(World, S)) Targets.Add(A);
|
|
4025
4771
|
}
|
|
4026
4772
|
}
|
|
4027
4773
|
}
|
|
@@ -4033,7 +4779,7 @@ TSharedPtr<FJsonValue> FLevelHandlers::BatchTranslate(const TSharedPtr<FJsonObje
|
|
|
4033
4779
|
if (It->ActorHasTag(TagName)) Targets.Add(*It);
|
|
4034
4780
|
}
|
|
4035
4781
|
}
|
|
4036
|
-
if (Targets.Num() == 0) return MCPError(TEXT("Provide actorLabels[] or tag matching at least one actor"));
|
|
4782
|
+
if (Targets.Num() == 0) return MCPError(TEXT("Provide actorLabels[], actorPaths[] or tag matching at least one actor"));
|
|
4037
4783
|
|
|
4038
4784
|
for (AActor* A : Targets)
|
|
4039
4785
|
{
|