ue-mcp 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/index.js +41 -2
- package/dist/index.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/plugin/loader.d.ts +4 -0
- package/dist/plugin/loader.js +22 -8
- package/dist/plugin/loader.js.map +1 -1
- package/dist/plugin/manifest.d.ts +72 -52
- package/dist/plugin/manifest.js +118 -21
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin-cli.js +16 -3
- package/dist/plugin-cli.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/plugins.js +2 -0
- package/dist/tools/plugins.js.map +1 -1
- package/dist/tools/project.js +47 -20
- package/dist/tools/project.js.map +1 -1
- package/dist/types.d.ts +24 -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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#include "HandlerUtils.h"
|
|
9
9
|
#include "HandlerAssetCreate.h"
|
|
10
10
|
#include "HandlerJsonProperty.h"
|
|
11
|
+
#include "JsonSerializer.h"
|
|
11
12
|
#include "AssetToolsModule.h"
|
|
12
13
|
#include "IAssetTools.h"
|
|
13
14
|
#include "AssetImportTask.h"
|
|
@@ -143,11 +144,13 @@ namespace
|
|
|
143
144
|
TSharedPtr<FJsonValue> LoadCurveTable(const TSharedPtr<FJsonObject>& Params, UCurveTable*& OutTable, FString& OutAssetPath)
|
|
144
145
|
{
|
|
145
146
|
if (auto Err = RequireStringAlt(Params, TEXT("path"), TEXT("assetPath"), OutAssetPath)) return Err;
|
|
146
|
-
|
|
147
|
+
TSharedPtr<FJsonValue> LoadError;
|
|
148
|
+
UObject* Asset = MCPRequireAssetObject(OutAssetPath, LoadError);
|
|
149
|
+
if (!Asset) return LoadError;
|
|
147
150
|
OutTable = Cast<UCurveTable>(Asset);
|
|
148
151
|
if (!OutTable)
|
|
149
152
|
{
|
|
150
|
-
return
|
|
153
|
+
return MCPAssetWrongTypeError(OutAssetPath, Asset, TEXT("CurveTable"));
|
|
151
154
|
}
|
|
152
155
|
return nullptr;
|
|
153
156
|
}
|
|
@@ -333,7 +336,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::ImportSkeletalMesh(const TSharedPtr<FJson
|
|
|
333
336
|
FString SkeletonPath;
|
|
334
337
|
if (Params->TryGetStringField(TEXT("skeletonPath"), SkeletonPath) && !SkeletonPath.IsEmpty())
|
|
335
338
|
{
|
|
336
|
-
USkeleton* Skeleton =
|
|
339
|
+
USkeleton* Skeleton = LoadAssetByPath<USkeleton>(SkeletonPath);
|
|
337
340
|
if (Skeleton)
|
|
338
341
|
{
|
|
339
342
|
ImportUI->Skeleton = Skeleton;
|
|
@@ -470,10 +473,10 @@ TSharedPtr<FJsonValue> FAssetHandlers::ImportAnimation(const TSharedPtr<FJsonObj
|
|
|
470
473
|
return MCPError(FString::Printf(TEXT("File not found: %s"), *FileName));
|
|
471
474
|
}
|
|
472
475
|
|
|
473
|
-
USkeleton* Skeleton =
|
|
476
|
+
USkeleton* Skeleton = LoadAssetByPath<USkeleton>(SkeletonPath);
|
|
474
477
|
if (!Skeleton)
|
|
475
478
|
{
|
|
476
|
-
return
|
|
479
|
+
return MCPAssetLoadError(SkeletonPath, TEXT("Skeleton"));
|
|
477
480
|
}
|
|
478
481
|
|
|
479
482
|
UFbxFactory* FbxFactory = NewObject<UFbxFactory>();
|
|
@@ -577,16 +580,14 @@ TSharedPtr<FJsonValue> FAssetHandlers::ListTextureProperties(const TSharedPtr<FJ
|
|
|
577
580
|
FString AssetPath;
|
|
578
581
|
if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
|
|
579
582
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
return MCPError(FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
|
|
584
|
-
}
|
|
583
|
+
TSharedPtr<FJsonValue> LoadError;
|
|
584
|
+
UObject* Asset = MCPRequireAssetObject(AssetPath, LoadError);
|
|
585
|
+
if (!Asset) return LoadError;
|
|
585
586
|
|
|
586
587
|
UTexture2D* Texture = Cast<UTexture2D>(Asset);
|
|
587
588
|
if (!Texture)
|
|
588
589
|
{
|
|
589
|
-
return
|
|
590
|
+
return MCPAssetWrongTypeError(AssetPath, Asset, TEXT("Texture2D"));
|
|
590
591
|
}
|
|
591
592
|
|
|
592
593
|
auto Result = MCPSuccess();
|
|
@@ -662,16 +663,14 @@ TSharedPtr<FJsonValue> FAssetHandlers::SetTextureProperties(const TSharedPtr<FJs
|
|
|
662
663
|
FString AssetPath;
|
|
663
664
|
if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
|
|
664
665
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
return MCPError(FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
|
|
669
|
-
}
|
|
666
|
+
TSharedPtr<FJsonValue> LoadError;
|
|
667
|
+
UObject* Asset = MCPRequireAssetObject(AssetPath, LoadError);
|
|
668
|
+
if (!Asset) return LoadError;
|
|
670
669
|
|
|
671
670
|
UTexture2D* Texture = Cast<UTexture2D>(Asset);
|
|
672
671
|
if (!Texture)
|
|
673
672
|
{
|
|
674
|
-
return
|
|
673
|
+
return MCPAssetWrongTypeError(AssetPath, Asset, TEXT("Texture2D"));
|
|
675
674
|
}
|
|
676
675
|
|
|
677
676
|
// Capture previous values for self-inverse rollback. Use reflection paths
|
|
@@ -1062,7 +1061,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::CreateCurveTable(const TSharedPtr<FJsonOb
|
|
|
1062
1061
|
{
|
|
1063
1062
|
FString ExistingAssetPath;
|
|
1064
1063
|
ExistingObj->TryGetStringField(TEXT("path"), ExistingAssetPath);
|
|
1065
|
-
if (UCurveTable* Existing =
|
|
1064
|
+
if (UCurveTable* Existing = LoadAssetByPath<UCurveTable>(ExistingAssetPath))
|
|
1066
1065
|
{
|
|
1067
1066
|
ExistingObj->SetStringField(TEXT("assetPath"), Existing->GetPathName());
|
|
1068
1067
|
ExistingObj->SetStringField(TEXT("curveType"), CurveTableModeName(Existing->GetCurveTableMode()));
|
|
@@ -1552,7 +1551,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::CreateDataTable(const TSharedPtr<FJsonObj
|
|
|
1552
1551
|
|
|
1553
1552
|
// Find the row struct type
|
|
1554
1553
|
UScriptStruct* ScriptStruct = nullptr;
|
|
1555
|
-
ScriptStruct =
|
|
1554
|
+
ScriptStruct = LoadAssetByPath<UScriptStruct>(RowStruct);
|
|
1556
1555
|
if (!ScriptStruct)
|
|
1557
1556
|
{
|
|
1558
1557
|
for (TObjectIterator<UScriptStruct> It; It; ++It)
|
|
@@ -1583,7 +1582,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::CreateDataTable(const TSharedPtr<FJsonObj
|
|
|
1583
1582
|
{
|
|
1584
1583
|
FString ExistingAssetPath;
|
|
1585
1584
|
ExistingObj->TryGetStringField(TEXT("path"), ExistingAssetPath);
|
|
1586
|
-
if (UDataTable* Existing =
|
|
1585
|
+
if (UDataTable* Existing = LoadAssetByPath<UDataTable>(ExistingAssetPath))
|
|
1587
1586
|
{
|
|
1588
1587
|
ExistingObj->SetStringField(TEXT("assetPath"), Existing->GetPathName());
|
|
1589
1588
|
ExistingObj->SetStringField(TEXT("rowStruct"), Existing->RowStruct ? Existing->RowStruct->GetName() : TEXT(""));
|
|
@@ -1612,22 +1611,40 @@ TSharedPtr<FJsonValue> FAssetHandlers::CreateDataTable(const TSharedPtr<FJsonObj
|
|
|
1612
1611
|
}
|
|
1613
1612
|
|
|
1614
1613
|
|
|
1615
|
-
|
|
1614
|
+
namespace
|
|
1616
1615
|
{
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1616
|
+
// #930: resolve the DataTable parameter the way asset(read) resolves any
|
|
1617
|
+
// asset, so the type-specific actions succeed on every path form the
|
|
1618
|
+
// generic reader already opens. Returns a ready-to-return error, or an
|
|
1619
|
+
// unset pointer on success with OutAssetPath and OutTable filled in.
|
|
1620
|
+
//
|
|
1621
|
+
// The "not a DataTable" message names the class that was found, because
|
|
1622
|
+
// the old wording was also what a caller saw when the path resolved to
|
|
1623
|
+
// nothing at all.
|
|
1624
|
+
TSharedPtr<FJsonValue> LoadDataTableParam(
|
|
1625
|
+
const TSharedPtr<FJsonObject>& Params,
|
|
1626
|
+
FString& OutAssetPath,
|
|
1627
|
+
UDataTable*& OutTable)
|
|
1622
1628
|
{
|
|
1623
|
-
|
|
1624
|
-
}
|
|
1629
|
+
if (auto Err = RequireStringAlt(Params, TEXT("path"), TEXT("assetPath"), OutAssetPath)) return Err;
|
|
1625
1630
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1631
|
+
TSharedPtr<FJsonValue> LoadError;
|
|
1632
|
+
UObject* Asset = MCPRequireAssetObject(OutAssetPath, LoadError);
|
|
1633
|
+
if (!Asset) return LoadError;
|
|
1634
|
+
OutTable = Cast<UDataTable>(Asset);
|
|
1635
|
+
if (!OutTable)
|
|
1636
|
+
{
|
|
1637
|
+
return MCPAssetWrongTypeError(OutAssetPath, Asset, TEXT("DataTable"));
|
|
1638
|
+
}
|
|
1639
|
+
return nullptr;
|
|
1630
1640
|
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
TSharedPtr<FJsonValue> FAssetHandlers::ReadDataTable(const TSharedPtr<FJsonObject>& Params)
|
|
1644
|
+
{
|
|
1645
|
+
FString AssetPath;
|
|
1646
|
+
UDataTable* DataTable = nullptr;
|
|
1647
|
+
if (auto Err = LoadDataTableParam(Params, AssetPath, DataTable)) return Err;
|
|
1631
1648
|
|
|
1632
1649
|
FString RowFilter = OptionalString(Params, TEXT("rowFilter"));
|
|
1633
1650
|
|
|
@@ -1697,19 +1714,8 @@ TSharedPtr<FJsonValue> FAssetHandlers::ReadDataTable(const TSharedPtr<FJsonObjec
|
|
|
1697
1714
|
TSharedPtr<FJsonValue> FAssetHandlers::ReimportDataTable(const TSharedPtr<FJsonObject>& Params)
|
|
1698
1715
|
{
|
|
1699
1716
|
FString AssetPath;
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
|
|
1703
|
-
if (!Asset)
|
|
1704
|
-
{
|
|
1705
|
-
return MCPError(FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
UDataTable* DataTable = Cast<UDataTable>(Asset);
|
|
1709
|
-
if (!DataTable)
|
|
1710
|
-
{
|
|
1711
|
-
return MCPError(FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
|
|
1712
|
-
}
|
|
1717
|
+
UDataTable* DataTable = nullptr;
|
|
1718
|
+
if (auto Err = LoadDataTableParam(Params, AssetPath, DataTable)) return Err;
|
|
1713
1719
|
|
|
1714
1720
|
// Get JSON string from either inline jsonString or from a file path
|
|
1715
1721
|
FString JsonString;
|
|
@@ -1767,7 +1773,8 @@ TSharedPtr<FJsonValue> FAssetHandlers::ReimportDataTable(const TSharedPtr<FJsonO
|
|
|
1767
1773
|
TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableRow(const TSharedPtr<FJsonObject>& Params)
|
|
1768
1774
|
{
|
|
1769
1775
|
FString AssetPath;
|
|
1770
|
-
|
|
1776
|
+
UDataTable* DataTable = nullptr;
|
|
1777
|
+
if (auto Err = LoadDataTableParam(Params, AssetPath, DataTable)) return Err;
|
|
1771
1778
|
FString RowName;
|
|
1772
1779
|
if (auto Err = RequireString(Params, TEXT("rowName"), RowName)) return Err;
|
|
1773
1780
|
|
|
@@ -1785,12 +1792,6 @@ TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableRow(const TSharedPtr<FJsonObj
|
|
|
1785
1792
|
return MCPError(TEXT("Missing 'row' (or 'fields'/'data') JSON object with the row struct fields"));
|
|
1786
1793
|
}
|
|
1787
1794
|
|
|
1788
|
-
UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
|
|
1789
|
-
UDataTable* DataTable = Cast<UDataTable>(Asset);
|
|
1790
|
-
if (!DataTable)
|
|
1791
|
-
{
|
|
1792
|
-
return MCPError(FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
|
|
1793
|
-
}
|
|
1794
1795
|
const UScriptStruct* RowStruct = DataTable->GetRowStruct();
|
|
1795
1796
|
if (!RowStruct)
|
|
1796
1797
|
{
|
|
@@ -1798,35 +1799,19 @@ TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableRow(const TSharedPtr<FJsonObj
|
|
|
1798
1799
|
}
|
|
1799
1800
|
|
|
1800
1801
|
const FName RowKey(*RowName);
|
|
1801
|
-
const
|
|
1802
|
-
const bool bExisted =
|
|
1803
|
-
|
|
1804
|
-
//
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
RowStruct->ExportText(PrevExport, PrevPtr, PrevPtr, nullptr, PPF_None, nullptr);
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
// Allocate a row buffer and apply fields via MCPJsonProperty so dicts/
|
|
1813
|
-
// arrays/asset paths/gameplay tags all work.
|
|
1814
|
-
const int32 StructSize = RowStruct->GetStructureSize();
|
|
1815
|
-
const int32 MinAlign = RowStruct->GetMinAlignment();
|
|
1816
|
-
uint8* NewRow = (uint8*)FMemory::Malloc(StructSize, MinAlign);
|
|
1817
|
-
RowStruct->InitializeStruct(NewRow);
|
|
1818
|
-
|
|
1819
|
-
// Seed from the prior row so partial JSON only updates the named fields.
|
|
1820
|
-
if (bExisted)
|
|
1821
|
-
{
|
|
1822
|
-
uint8* PrevPtr = *RowMap.Find(RowKey);
|
|
1823
|
-
RowStruct->CopyScriptStruct(NewRow, PrevPtr);
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
FString SetErr;
|
|
1827
|
-
bool bOk = true;
|
|
1802
|
+
uint8* const* ExistingRow = DataTable->GetRowMap().Find(RowKey);
|
|
1803
|
+
const bool bExisted = ExistingRow != nullptr && *ExistingRow != nullptr;
|
|
1804
|
+
|
|
1805
|
+
// Resolve every named field before a single byte is written. An unknown
|
|
1806
|
+
// field name used to be discovered half way through the loop, after
|
|
1807
|
+
// earlier fields had already been applied.
|
|
1808
|
+
TArray<TPair<FProperty*, TSharedPtr<FJsonValue>>> Writes;
|
|
1809
|
+
Writes.Reserve((*RowObj)->Values.Num());
|
|
1828
1810
|
for (const auto& Pair : (*RowObj)->Values)
|
|
1829
1811
|
{
|
|
1812
|
+
// FJsonObject::Values is not keyed by FString on UE 5.8, so the key has
|
|
1813
|
+
// to be materialised before it can be compared with a property name.
|
|
1814
|
+
// FString(*Pair.Key) is the idiom the rest of this module already uses.
|
|
1830
1815
|
const FString FieldName(*Pair.Key);
|
|
1831
1816
|
FProperty* FieldProp = nullptr;
|
|
1832
1817
|
for (TFieldIterator<FProperty> It(RowStruct); It; ++It)
|
|
@@ -1839,31 +1824,151 @@ TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableRow(const TSharedPtr<FJsonObj
|
|
|
1839
1824
|
}
|
|
1840
1825
|
if (!FieldProp)
|
|
1841
1826
|
{
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
break;
|
|
1827
|
+
return MCPError(FString::Printf(
|
|
1828
|
+
TEXT("row struct '%s' has no field '%s'"), *RowStruct->GetName(), *Pair.Key));
|
|
1845
1829
|
}
|
|
1846
|
-
|
|
1830
|
+
Writes.Emplace(FieldProp, Pair.Value);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
const int32 StructSize = RowStruct->GetStructureSize();
|
|
1834
|
+
const int32 MinAlign = RowStruct->GetMinAlignment();
|
|
1835
|
+
|
|
1836
|
+
// Snapshot the prior row: it is both the rollback record handed back to
|
|
1837
|
+
// the caller and the copy this handler restores if a field write fails
|
|
1838
|
+
// part way through.
|
|
1839
|
+
//
|
|
1840
|
+
// The rollback record carries the prior values of exactly the fields
|
|
1841
|
+
// about to change, as a row payload for set_datatable_row, which is a
|
|
1842
|
+
// method that exists and now merges in place. It used to name
|
|
1843
|
+
// set_datatable_row_raw, which is registered nowhere, so a flow running
|
|
1844
|
+
// with rollback_on_failure could not restore a row at all; and it carried
|
|
1845
|
+
// whole-struct export text produced with the value as its own defaults,
|
|
1846
|
+
// which exports every field as unchanged and yields "()".
|
|
1847
|
+
TSharedPtr<FJsonObject> PrevFields = MakeShared<FJsonObject>();
|
|
1848
|
+
uint8* Backup = nullptr;
|
|
1849
|
+
if (bExisted)
|
|
1850
|
+
{
|
|
1851
|
+
Backup = (uint8*)FMemory::Malloc(StructSize, MinAlign);
|
|
1852
|
+
RowStruct->InitializeStruct(Backup);
|
|
1853
|
+
RowStruct->CopyScriptStruct(Backup, *ExistingRow);
|
|
1854
|
+
for (const TPair<FProperty*, TSharedPtr<FJsonValue>>& Write : Writes)
|
|
1855
|
+
{
|
|
1856
|
+
const void* FieldAddr = Write.Key->ContainerPtrToValuePtr<void>(Backup);
|
|
1857
|
+
PrevFields->SetField(
|
|
1858
|
+
Write.Key->GetAuthoredName(),
|
|
1859
|
+
FMCPJsonSerializer::SerializeValue(FieldAddr, Write.Key));
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// #929: an existing row is edited in the memory the table already owns.
|
|
1864
|
+
// Only the named fields are touched, so every other field keeps the exact
|
|
1865
|
+
// bytes it had, whether or not its UPROPERTY declares a default.
|
|
1866
|
+
//
|
|
1867
|
+
// The previous shape reallocated the row (RemoveRow followed by AddRow)
|
|
1868
|
+
// and relied on a CopyScriptStruct seed to carry the untouched fields
|
|
1869
|
+
// across. That seed is one line away from being lost, it moved the row to
|
|
1870
|
+
// the end of the row map on every edit, and UDataTable::RemoveRow closes
|
|
1871
|
+
// an FScopedDataTableChange whose destructor calls
|
|
1872
|
+
// HandleDataTableChanged(NAME_None), which runs
|
|
1873
|
+
// FTableRowBase::OnDataTableChanged against *every* row in the table
|
|
1874
|
+
// rather than the one being edited. A single-cell write should not reach
|
|
1875
|
+
// the other rows at all.
|
|
1876
|
+
uint8* RowData = bExisted ? *ExistingRow : (uint8*)FMemory::Malloc(StructSize, MinAlign);
|
|
1877
|
+
if (!bExisted)
|
|
1878
|
+
{
|
|
1879
|
+
RowStruct->InitializeStruct(RowData);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
FString SetErr;
|
|
1883
|
+
bool bOk = true;
|
|
1884
|
+
for (const TPair<FProperty*, TSharedPtr<FJsonValue>>& Write : Writes)
|
|
1885
|
+
{
|
|
1886
|
+
void* FieldAddr = Write.Key->ContainerPtrToValuePtr<void>(RowData);
|
|
1847
1887
|
FString E;
|
|
1848
|
-
if (!MCPJsonProperty::SetJsonOnProperty(
|
|
1888
|
+
if (!MCPJsonProperty::SetJsonOnProperty(Write.Key, FieldAddr, Write.Value, E))
|
|
1849
1889
|
{
|
|
1850
|
-
SetErr = FString::Printf(TEXT("%s: %s"), *
|
|
1890
|
+
SetErr = FString::Printf(TEXT("%s: %s"), *Write.Key->GetAuthoredName(), *E);
|
|
1851
1891
|
bOk = false;
|
|
1852
1892
|
break;
|
|
1853
1893
|
}
|
|
1854
1894
|
}
|
|
1855
1895
|
if (!bOk)
|
|
1856
1896
|
{
|
|
1857
|
-
|
|
1858
|
-
|
|
1897
|
+
if (bExisted)
|
|
1898
|
+
{
|
|
1899
|
+
// Put the row back exactly as it was found.
|
|
1900
|
+
RowStruct->CopyScriptStruct(RowData, Backup);
|
|
1901
|
+
RowStruct->DestroyStruct(Backup);
|
|
1902
|
+
FMemory::Free(Backup);
|
|
1903
|
+
}
|
|
1904
|
+
else
|
|
1905
|
+
{
|
|
1906
|
+
RowStruct->DestroyStruct(RowData);
|
|
1907
|
+
FMemory::Free(RowData);
|
|
1908
|
+
}
|
|
1859
1909
|
return MCPError(SetErr);
|
|
1860
1910
|
}
|
|
1861
1911
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1912
|
+
if (!bExisted)
|
|
1913
|
+
{
|
|
1914
|
+
// AddRow copies the buffer and owns the copy from here on.
|
|
1915
|
+
DataTable->AddRow(RowKey, *reinterpret_cast<FTableRowBase*>(RowData));
|
|
1916
|
+
RowStruct->DestroyStruct(RowData);
|
|
1917
|
+
FMemory::Free(RowData);
|
|
1918
|
+
RowData = nullptr;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// #935: read the row back out of the table and check that every field the
|
|
1922
|
+
// caller named holds what was asked for, before anything is saved. This
|
|
1923
|
+
// reads the table's own memory, not the buffer that was written, so a
|
|
1924
|
+
// value lost in the copy into the table is caught here too.
|
|
1925
|
+
uint8* const* StoredRow = DataTable->GetRowMap().Find(RowKey);
|
|
1926
|
+
if (!StoredRow || !*StoredRow)
|
|
1927
|
+
{
|
|
1928
|
+
if (bExisted)
|
|
1929
|
+
{
|
|
1930
|
+
RowStruct->DestroyStruct(Backup);
|
|
1931
|
+
FMemory::Free(Backup);
|
|
1932
|
+
}
|
|
1933
|
+
return MCPError(FString::Printf(
|
|
1934
|
+
TEXT("Row '%s' is missing from '%s' after the write. Nothing was saved."),
|
|
1935
|
+
*RowName, *AssetPath));
|
|
1936
|
+
}
|
|
1937
|
+
for (const TPair<FProperty*, TSharedPtr<FJsonValue>>& Write : Writes)
|
|
1938
|
+
{
|
|
1939
|
+
const void* FieldAddr = Write.Key->ContainerPtrToValuePtr<void>(*StoredRow);
|
|
1940
|
+
FString Detail;
|
|
1941
|
+
if (MCPJsonProperty::VerifyJsonOnProperty(Write.Key, FieldAddr, Write.Value, Detail))
|
|
1942
|
+
{
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// Undo the whole call. A verified-bad write is not a partial success.
|
|
1947
|
+
if (bExisted)
|
|
1948
|
+
{
|
|
1949
|
+
RowStruct->CopyScriptStruct(*StoredRow, Backup);
|
|
1950
|
+
RowStruct->DestroyStruct(Backup);
|
|
1951
|
+
FMemory::Free(Backup);
|
|
1952
|
+
DataTable->HandleDataTableChanged(RowKey);
|
|
1953
|
+
}
|
|
1954
|
+
else
|
|
1955
|
+
{
|
|
1956
|
+
DataTable->RemoveRow(RowKey);
|
|
1957
|
+
}
|
|
1958
|
+
return MCPError(FString::Printf(
|
|
1959
|
+
TEXT("Field '%s' on row '%s' did not store the requested value: %s. The row was left unchanged."),
|
|
1960
|
+
*Write.Key->GetAuthoredName(), *RowName, *Detail));
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
if (bExisted)
|
|
1964
|
+
{
|
|
1965
|
+
RowStruct->DestroyStruct(Backup);
|
|
1966
|
+
FMemory::Free(Backup);
|
|
1967
|
+
Backup = nullptr;
|
|
1968
|
+
// The table's own row memory was edited, so name the row that changed
|
|
1969
|
+
// and only its hook runs.
|
|
1970
|
+
DataTable->HandleDataTableChanged(RowKey);
|
|
1971
|
+
}
|
|
1867
1972
|
|
|
1868
1973
|
DataTable->MarkPackageDirty();
|
|
1869
1974
|
UEditorAssetLibrary::SaveLoadedAsset(DataTable, /*bOnlyIfIsDirty*/ true);
|
|
@@ -1880,8 +1985,8 @@ TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableRow(const TSharedPtr<FJsonObj
|
|
|
1880
1985
|
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
1881
1986
|
Payload->SetStringField(TEXT("assetPath"), AssetPath);
|
|
1882
1987
|
Payload->SetStringField(TEXT("rowName"), RowName);
|
|
1883
|
-
Payload->
|
|
1884
|
-
MCPSetRollback(Result, TEXT("
|
|
1988
|
+
Payload->SetObjectField(TEXT("row"), PrevFields);
|
|
1989
|
+
MCPSetRollback(Result, TEXT("set_datatable_row"), Payload);
|
|
1885
1990
|
}
|
|
1886
1991
|
else
|
|
1887
1992
|
{
|
|
@@ -1899,16 +2004,11 @@ TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableRow(const TSharedPtr<FJsonObj
|
|
|
1899
2004
|
TSharedPtr<FJsonValue> FAssetHandlers::RemoveDataTableRow(const TSharedPtr<FJsonObject>& Params)
|
|
1900
2005
|
{
|
|
1901
2006
|
FString AssetPath;
|
|
1902
|
-
|
|
2007
|
+
UDataTable* DataTable = nullptr;
|
|
2008
|
+
if (auto Err = LoadDataTableParam(Params, AssetPath, DataTable)) return Err;
|
|
1903
2009
|
FString RowName;
|
|
1904
2010
|
if (auto Err = RequireString(Params, TEXT("rowName"), RowName)) return Err;
|
|
1905
2011
|
|
|
1906
|
-
UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
|
|
1907
|
-
UDataTable* DataTable = Cast<UDataTable>(Asset);
|
|
1908
|
-
if (!DataTable)
|
|
1909
|
-
{
|
|
1910
|
-
return MCPError(FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
|
|
1911
|
-
}
|
|
1912
2012
|
const FName RowKey(*RowName);
|
|
1913
2013
|
if (!DataTable->GetRowMap().Contains(RowKey))
|
|
1914
2014
|
{
|
|
@@ -1936,12 +2036,11 @@ TSharedPtr<FJsonValue> FAssetHandlers::RemoveDataTableRow(const TSharedPtr<FJson
|
|
|
1936
2036
|
TSharedPtr<FJsonValue> FAssetHandlers::GetDataTableRow(const TSharedPtr<FJsonObject>& Params)
|
|
1937
2037
|
{
|
|
1938
2038
|
FString AssetPath;
|
|
1939
|
-
|
|
2039
|
+
UDataTable* DataTable = nullptr;
|
|
2040
|
+
if (auto Err = LoadDataTableParam(Params, AssetPath, DataTable)) return Err;
|
|
1940
2041
|
FString RowName;
|
|
1941
2042
|
if (auto Err = RequireString(Params, TEXT("rowName"), RowName)) return Err;
|
|
1942
2043
|
|
|
1943
|
-
UDataTable* DataTable = Cast<UDataTable>(UEditorAssetLibrary::LoadAsset(AssetPath));
|
|
1944
|
-
if (!DataTable) return MCPError(FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
|
|
1945
2044
|
const UScriptStruct* RowStruct = DataTable->GetRowStruct();
|
|
1946
2045
|
if (!RowStruct) return MCPError(TEXT("DataTable has no row struct"));
|
|
1947
2046
|
|
|
@@ -1968,8 +2067,8 @@ TSharedPtr<FJsonValue> FAssetHandlers::GetDataTableRow(const TSharedPtr<FJsonObj
|
|
|
1968
2067
|
}
|
|
1969
2068
|
|
|
1970
2069
|
// #535: write a single field on a single row. Thin wrapper over the row-merge
|
|
1971
|
-
// upsert (SetDataTableRow), which
|
|
1972
|
-
// cell changes. Params: assetPath, rowName, fieldName, value.
|
|
2070
|
+
// upsert (SetDataTableRow), which edits the existing row in place so only the
|
|
2071
|
+
// named cell changes. Params: assetPath, rowName, fieldName, value.
|
|
1973
2072
|
TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableCell(const TSharedPtr<FJsonObject>& Params)
|
|
1974
2073
|
{
|
|
1975
2074
|
FString FieldName;
|
|
@@ -1983,11 +2082,10 @@ TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableCell(const TSharedPtr<FJsonOb
|
|
|
1983
2082
|
// Build a one-field row object and delegate to the row upsert, which
|
|
1984
2083
|
// requires the row to exist for a cell edit (no accidental row creation).
|
|
1985
2084
|
FString AssetPath;
|
|
1986
|
-
|
|
2085
|
+
UDataTable* DataTable = nullptr;
|
|
2086
|
+
if (auto Err = LoadDataTableParam(Params, AssetPath, DataTable)) return Err;
|
|
1987
2087
|
FString RowName;
|
|
1988
2088
|
if (auto Err = RequireString(Params, TEXT("rowName"), RowName)) return Err;
|
|
1989
|
-
UDataTable* DataTable = Cast<UDataTable>(UEditorAssetLibrary::LoadAsset(AssetPath));
|
|
1990
|
-
if (!DataTable) return MCPError(FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
|
|
1991
2089
|
if (!DataTable->GetRowMap().Contains(FName(*RowName)))
|
|
1992
2090
|
{
|
|
1993
2091
|
return MCPError(FString::Printf(TEXT("Row not found: %s (use set_datatable_row to create it)"), *RowName));
|
|
@@ -2007,14 +2105,13 @@ TSharedPtr<FJsonValue> FAssetHandlers::SetDataTableCell(const TSharedPtr<FJsonOb
|
|
|
2007
2105
|
TSharedPtr<FJsonValue> FAssetHandlers::RenameDataTableRow(const TSharedPtr<FJsonObject>& Params)
|
|
2008
2106
|
{
|
|
2009
2107
|
FString AssetPath;
|
|
2010
|
-
|
|
2108
|
+
UDataTable* DataTable = nullptr;
|
|
2109
|
+
if (auto Err = LoadDataTableParam(Params, AssetPath, DataTable)) return Err;
|
|
2011
2110
|
FString OldName;
|
|
2012
2111
|
if (auto Err = RequireStringAlt(Params, TEXT("oldName"), TEXT("rowName"), OldName)) return Err;
|
|
2013
2112
|
FString NewName;
|
|
2014
2113
|
if (auto Err = RequireString(Params, TEXT("newName"), NewName)) return Err;
|
|
2015
2114
|
|
|
2016
|
-
UDataTable* DataTable = Cast<UDataTable>(UEditorAssetLibrary::LoadAsset(AssetPath));
|
|
2017
|
-
if (!DataTable) return MCPError(FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
|
|
2018
2115
|
const UScriptStruct* RowStruct = DataTable->GetRowStruct();
|
|
2019
2116
|
if (!RowStruct) return MCPError(TEXT("DataTable has no row struct"));
|
|
2020
2117
|
|
|
@@ -2063,7 +2160,8 @@ TSharedPtr<FJsonValue> FAssetHandlers::RenameDataTableRow(const TSharedPtr<FJson
|
|
|
2063
2160
|
TSharedPtr<FJsonValue> FAssetHandlers::FillDataTableFromJson(const TSharedPtr<FJsonObject>& Params)
|
|
2064
2161
|
{
|
|
2065
2162
|
FString AssetPath;
|
|
2066
|
-
|
|
2163
|
+
UDataTable* DataTable = nullptr;
|
|
2164
|
+
if (auto Err = LoadDataTableParam(Params, AssetPath, DataTable)) return Err;
|
|
2067
2165
|
|
|
2068
2166
|
const TSharedPtr<FJsonObject>* RowsObj = nullptr;
|
|
2069
2167
|
TSharedPtr<FJsonObject> ParsedRows;
|
|
@@ -2085,8 +2183,6 @@ TSharedPtr<FJsonValue> FAssetHandlers::FillDataTableFromJson(const TSharedPtr<FJ
|
|
|
2085
2183
|
return MCPError(TEXT("Missing 'rows' object (or 'jsonString') mapping rowName -> {field: value}"));
|
|
2086
2184
|
}
|
|
2087
2185
|
|
|
2088
|
-
UDataTable* DataTable = Cast<UDataTable>(UEditorAssetLibrary::LoadAsset(AssetPath));
|
|
2089
|
-
if (!DataTable) return MCPError(FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
|
|
2090
2186
|
const UScriptStruct* RowStruct = DataTable->GetRowStruct();
|
|
2091
2187
|
if (!RowStruct) return MCPError(TEXT("DataTable has no row struct"));
|
|
2092
2188
|
|
|
@@ -2132,16 +2228,14 @@ namespace
|
|
|
2132
2228
|
{
|
|
2133
2229
|
if (auto Err = RequireStringAlt(Params, TEXT("path"), TEXT("assetPath"), OutAssetPath)) return Err;
|
|
2134
2230
|
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
return MCPError(FString::Printf(TEXT("Asset not found: %s"), *OutAssetPath));
|
|
2139
|
-
}
|
|
2231
|
+
TSharedPtr<FJsonValue> LoadError;
|
|
2232
|
+
UObject* Asset = MCPRequireAssetObject(OutAssetPath, LoadError);
|
|
2233
|
+
if (!Asset) return LoadError;
|
|
2140
2234
|
|
|
2141
2235
|
OutStringTable = Cast<UStringTable>(Asset);
|
|
2142
2236
|
if (!OutStringTable)
|
|
2143
2237
|
{
|
|
2144
|
-
return
|
|
2238
|
+
return MCPAssetWrongTypeError(OutAssetPath, Asset, TEXT("StringTable"));
|
|
2145
2239
|
}
|
|
2146
2240
|
return nullptr;
|
|
2147
2241
|
}
|
|
@@ -2222,7 +2316,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::CreateStringTable(const TSharedPtr<FJsonO
|
|
|
2222
2316
|
{
|
|
2223
2317
|
FString ExistingAssetPath;
|
|
2224
2318
|
ExistingObj->TryGetStringField(TEXT("path"), ExistingAssetPath);
|
|
2225
|
-
if (UStringTable* Existing =
|
|
2319
|
+
if (UStringTable* Existing = LoadAssetByPath<UStringTable>(ExistingAssetPath))
|
|
2226
2320
|
{
|
|
2227
2321
|
SetStringTableInfoFields(ExistingObj, Existing);
|
|
2228
2322
|
}
|
|
@@ -2481,6 +2575,256 @@ TSharedPtr<FJsonValue> FAssetHandlers::ImportStringTable(const TSharedPtr<FJsonO
|
|
|
2481
2575
|
return MCPResult(Result);
|
|
2482
2576
|
}
|
|
2483
2577
|
|
|
2578
|
+
// ---------------------------------------------------------------------------
|
|
2579
|
+
// import_stringtable_csv (#978)
|
|
2580
|
+
//
|
|
2581
|
+
// String Tables could be created and inspected but not refreshed from a CSV
|
|
2582
|
+
// source with any confidence, so a user kept the CSV as the canonical copy and
|
|
2583
|
+
// ran a custom editor script instead. This is the same engine importer
|
|
2584
|
+
// FStringTable exposes, wrapped so the caller gets the three things the script
|
|
2585
|
+
// was written to provide: the CSV is proved to parse and to carry the expected
|
|
2586
|
+
// keys BEFORE the asset is touched, the result names exactly which keys were
|
|
2587
|
+
// added, updated and removed, and the save is reported rather than assumed.
|
|
2588
|
+
// ---------------------------------------------------------------------------
|
|
2589
|
+
|
|
2590
|
+
namespace
|
|
2591
|
+
{
|
|
2592
|
+
/** Every key and source string in a table, as a plain map. */
|
|
2593
|
+
void MCPSnapshotStringTable(const UStringTable* Table, TMap<FString, FString>& Out)
|
|
2594
|
+
{
|
|
2595
|
+
Out.Reset();
|
|
2596
|
+
if (!Table) return;
|
|
2597
|
+
Table->GetStringTable()->EnumerateKeysAndSourceStrings(
|
|
2598
|
+
[&Out](const FTextKey& Key, const FString& SourceString) -> bool
|
|
2599
|
+
{
|
|
2600
|
+
Out.Add(Key.ToString(), SourceString);
|
|
2601
|
+
return true;
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
/** Run the engine's CSV importer over Table, on whichever engine this is. */
|
|
2606
|
+
bool MCPImportStringTableCsvFile(UStringTable* Table, const FString& FilePath)
|
|
2607
|
+
{
|
|
2608
|
+
if (!Table) return false;
|
|
2609
|
+
#if UE_MCP_HAS_5_8_API
|
|
2610
|
+
return Table->GetMutableStringTable()->ImportStringsFromCSVFile(FilePath);
|
|
2611
|
+
#else
|
|
2612
|
+
return Table->GetMutableStringTable()->ImportStrings(FilePath);
|
|
2613
|
+
#endif
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
TArray<TSharedPtr<FJsonValue>> MCPSortedKeysToJson(const TArray<FString>& Keys)
|
|
2617
|
+
{
|
|
2618
|
+
TArray<FString> Sorted = Keys;
|
|
2619
|
+
Sorted.Sort();
|
|
2620
|
+
TArray<TSharedPtr<FJsonValue>> Out;
|
|
2621
|
+
Out.Reserve(Sorted.Num());
|
|
2622
|
+
for (const FString& Key : Sorted) Out.Add(MakeShared<FJsonValueString>(Key));
|
|
2623
|
+
return Out;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
TSharedPtr<FJsonValue> FAssetHandlers::ImportStringTableCsv(const TSharedPtr<FJsonObject>& Params)
|
|
2628
|
+
{
|
|
2629
|
+
MCP_CHECK_GAME_THREAD();
|
|
2630
|
+
|
|
2631
|
+
FString AssetPath;
|
|
2632
|
+
UStringTable* StringTable = nullptr;
|
|
2633
|
+
if (auto Err = LoadStringTableAsset(Params, AssetPath, StringTable)) return Err;
|
|
2634
|
+
|
|
2635
|
+
if (MCPIsProtectedAssetPath(AssetPath))
|
|
2636
|
+
{
|
|
2637
|
+
return MCPError(FString::Printf(
|
|
2638
|
+
TEXT("'%s' is on a protected mount (/Engine/, /Script/, /Memory/, /Temp/), which the bridge never writes to."),
|
|
2639
|
+
*AssetPath));
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
FString CsvPath;
|
|
2643
|
+
if (auto Err = RequireStringAlt(Params, TEXT("csvPath"), TEXT("filePath"), CsvPath)) return Err;
|
|
2644
|
+
// A relative path is read against the project directory, not the editor's
|
|
2645
|
+
// working directory, which is not something a caller can predict.
|
|
2646
|
+
if (FPaths::IsRelative(CsvPath))
|
|
2647
|
+
{
|
|
2648
|
+
CsvPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir(), CsvPath);
|
|
2649
|
+
}
|
|
2650
|
+
if (!FPaths::FileExists(CsvPath))
|
|
2651
|
+
{
|
|
2652
|
+
return MCPError(FString::Printf(TEXT("CSV file not found: %s"), *CsvPath));
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
const bool bReplaceExisting = OptionalBool(Params, TEXT("replaceExisting"), false);
|
|
2656
|
+
const bool bRequireExactKeys = OptionalBool(Params, TEXT("requireExactKeys"), false);
|
|
2657
|
+
const bool bSave = OptionalBool(Params, TEXT("save"), true);
|
|
2658
|
+
|
|
2659
|
+
TArray<FString> ExpectedKeys;
|
|
2660
|
+
const TArray<TSharedPtr<FJsonValue>>* ExpectedField = nullptr;
|
|
2661
|
+
if (Params->TryGetArrayField(TEXT("expectedKeys"), ExpectedField) && ExpectedField)
|
|
2662
|
+
{
|
|
2663
|
+
ExpectedKeys = JsonArrayToStringList(ExpectedField);
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// Parse and validate against a throwaway table first, so a malformed CSV or
|
|
2667
|
+
// a key set that does not match expectations never reaches the asset.
|
|
2668
|
+
UStringTable* Staged = NewObject<UStringTable>(GetTransientPackage(), NAME_None, RF_Transient);
|
|
2669
|
+
if (!Staged)
|
|
2670
|
+
{
|
|
2671
|
+
return MCPError(TEXT("Could not create the staging StringTable used to validate the CSV."));
|
|
2672
|
+
}
|
|
2673
|
+
const FGCRootScope KeepStagedAlive(Staged);
|
|
2674
|
+
if (!MCPImportStringTableCsvFile(Staged, CsvPath))
|
|
2675
|
+
{
|
|
2676
|
+
return MCPError(FString::Printf(
|
|
2677
|
+
TEXT("The engine's String Table importer rejected '%s'. Nothing was changed. ")
|
|
2678
|
+
TEXT("The file must be a CSV whose first column is the entry key; the editor log carries the parse error."),
|
|
2679
|
+
*CsvPath));
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
TMap<FString, FString> Incoming;
|
|
2683
|
+
MCPSnapshotStringTable(Staged, Incoming);
|
|
2684
|
+
if (Incoming.Num() == 0)
|
|
2685
|
+
{
|
|
2686
|
+
return MCPError(FString::Printf(
|
|
2687
|
+
TEXT("'%s' parsed but carried no entries, so nothing was changed."), *CsvPath));
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
if (ExpectedKeys.Num() > 0)
|
|
2691
|
+
{
|
|
2692
|
+
TArray<FString> MissingKeys;
|
|
2693
|
+
for (const FString& Expected : ExpectedKeys)
|
|
2694
|
+
{
|
|
2695
|
+
if (!Incoming.Contains(Expected)) MissingKeys.Add(Expected);
|
|
2696
|
+
}
|
|
2697
|
+
TArray<FString> UnexpectedKeys;
|
|
2698
|
+
if (bRequireExactKeys)
|
|
2699
|
+
{
|
|
2700
|
+
for (const TPair<FString, FString>& Entry : Incoming)
|
|
2701
|
+
{
|
|
2702
|
+
if (!ExpectedKeys.Contains(Entry.Key)) UnexpectedKeys.Add(Entry.Key);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
if (MissingKeys.Num() > 0 || UnexpectedKeys.Num() > 0)
|
|
2706
|
+
{
|
|
2707
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
2708
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
2709
|
+
Obj->SetStringField(TEXT("error"), FString::Printf(
|
|
2710
|
+
TEXT("'%s' does not match the expected key set, so the String Table was not touched: %d expected key(s) missing%s."),
|
|
2711
|
+
*CsvPath, MissingKeys.Num(),
|
|
2712
|
+
bRequireExactKeys
|
|
2713
|
+
? *FString::Printf(TEXT(", %d unexpected"), UnexpectedKeys.Num())
|
|
2714
|
+
: TEXT("")));
|
|
2715
|
+
Obj->SetStringField(TEXT("reason"), TEXT("key_set_mismatch"));
|
|
2716
|
+
Obj->SetStringField(TEXT("assetPath"), AssetPath);
|
|
2717
|
+
Obj->SetStringField(TEXT("csvPath"), CsvPath);
|
|
2718
|
+
Obj->SetNumberField(TEXT("csvKeyCount"), Incoming.Num());
|
|
2719
|
+
Obj->SetArrayField(TEXT("missingKeys"), MCPSortedKeysToJson(MissingKeys));
|
|
2720
|
+
Obj->SetArrayField(TEXT("unexpectedKeys"), MCPSortedKeysToJson(UnexpectedKeys));
|
|
2721
|
+
return MakeShared<FJsonValueObject>(Obj);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
TMap<FString, FString> Before;
|
|
2726
|
+
MCPSnapshotStringTable(StringTable, Before);
|
|
2727
|
+
|
|
2728
|
+
// The merge runs first and the pruning second, so a parse that somehow
|
|
2729
|
+
// fails on the second pass cannot leave the table emptied.
|
|
2730
|
+
StringTable->Modify(true);
|
|
2731
|
+
if (!MCPImportStringTableCsvFile(StringTable, CsvPath))
|
|
2732
|
+
{
|
|
2733
|
+
return MCPError(FString::Printf(
|
|
2734
|
+
TEXT("'%s' parsed into the staging table but not into '%s'. The table is unchanged apart from any partial merge the importer applied; re-read it before retrying."),
|
|
2735
|
+
*CsvPath, *AssetPath));
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
TArray<FString> RemovedKeys;
|
|
2739
|
+
if (bReplaceExisting)
|
|
2740
|
+
{
|
|
2741
|
+
for (const TPair<FString, FString>& Entry : Before)
|
|
2742
|
+
{
|
|
2743
|
+
if (!Incoming.Contains(Entry.Key))
|
|
2744
|
+
{
|
|
2745
|
+
StringTable->GetMutableStringTable()->RemoveSourceString(FTextKey(Entry.Key));
|
|
2746
|
+
RemovedKeys.Add(Entry.Key);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
TMap<FString, FString> After;
|
|
2752
|
+
MCPSnapshotStringTable(StringTable, After);
|
|
2753
|
+
|
|
2754
|
+
TArray<FString> AddedKeys;
|
|
2755
|
+
TArray<FString> UpdatedKeys;
|
|
2756
|
+
for (const TPair<FString, FString>& Entry : After)
|
|
2757
|
+
{
|
|
2758
|
+
if (const FString* Previous = Before.Find(Entry.Key))
|
|
2759
|
+
{
|
|
2760
|
+
if (!Previous->Equals(Entry.Value, ESearchCase::CaseSensitive))
|
|
2761
|
+
{
|
|
2762
|
+
UpdatedKeys.Add(Entry.Key);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
else
|
|
2766
|
+
{
|
|
2767
|
+
AddedKeys.Add(Entry.Key);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
UPackage* Package = StringTable->GetOutermost();
|
|
2772
|
+
bool bPersisted = false;
|
|
2773
|
+
FString PersistReason;
|
|
2774
|
+
if (bSave)
|
|
2775
|
+
{
|
|
2776
|
+
bPersisted = SaveAssetPackage(StringTable);
|
|
2777
|
+
if (!bPersisted)
|
|
2778
|
+
{
|
|
2779
|
+
PersistReason = FString::Printf(
|
|
2780
|
+
TEXT("The editor refused to write '%s'. The entries are in memory only."),
|
|
2781
|
+
Package ? *Package->GetName() : *AssetPath);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
else
|
|
2785
|
+
{
|
|
2786
|
+
PersistReason = TEXT("save=false was requested, so the imported entries are in memory only until the package is saved.");
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
auto Result = MCPSuccess();
|
|
2790
|
+
MCPSetUpdated(Result);
|
|
2791
|
+
SetStringTableInfoFields(Result, StringTable);
|
|
2792
|
+
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
|
2793
|
+
Result->SetStringField(TEXT("csvPath"), CsvPath);
|
|
2794
|
+
Result->SetBoolField(TEXT("replaceExisting"), bReplaceExisting);
|
|
2795
|
+
Result->SetNumberField(TEXT("csvKeyCount"), Incoming.Num());
|
|
2796
|
+
Result->SetNumberField(TEXT("entryCountBefore"), Before.Num());
|
|
2797
|
+
Result->SetNumberField(TEXT("entryCountAfter"), After.Num());
|
|
2798
|
+
Result->SetArrayField(TEXT("addedKeys"), MCPSortedKeysToJson(AddedKeys));
|
|
2799
|
+
Result->SetArrayField(TEXT("updatedKeys"), MCPSortedKeysToJson(UpdatedKeys));
|
|
2800
|
+
Result->SetArrayField(TEXT("removedKeys"), MCPSortedKeysToJson(RemovedKeys));
|
|
2801
|
+
TArray<FString> AllKeys;
|
|
2802
|
+
After.GetKeys(AllKeys);
|
|
2803
|
+
Result->SetArrayField(TEXT("keys"), MCPSortedKeysToJson(AllKeys));
|
|
2804
|
+
if (ExpectedKeys.Num() > 0)
|
|
2805
|
+
{
|
|
2806
|
+
Result->SetNumberField(TEXT("expectedKeyCount"), ExpectedKeys.Num());
|
|
2807
|
+
Result->SetBoolField(TEXT("expectedKeysPresent"), true);
|
|
2808
|
+
}
|
|
2809
|
+
Result->SetBoolField(TEXT("persisted"), bPersisted);
|
|
2810
|
+
Result->SetBoolField(TEXT("saved"), bPersisted);
|
|
2811
|
+
if (Package)
|
|
2812
|
+
{
|
|
2813
|
+
Result->SetStringField(TEXT("packageName"), Package->GetName());
|
|
2814
|
+
Result->SetBoolField(TEXT("packageDirty"), Package->IsDirty());
|
|
2815
|
+
}
|
|
2816
|
+
if (!bPersisted)
|
|
2817
|
+
{
|
|
2818
|
+
Result->SetStringField(TEXT("persistError"), PersistReason);
|
|
2819
|
+
if (bSave)
|
|
2820
|
+
{
|
|
2821
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
2822
|
+
Result->SetStringField(TEXT("error"), PersistReason);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
return MCPResult(Result);
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2484
2828
|
// --- Reimport ---------------------------------------------------------
|
|
2485
2829
|
|
|
2486
2830
|
|
|
@@ -2491,11 +2835,9 @@ TSharedPtr<FJsonValue> FAssetHandlers::ReimportAsset(const TSharedPtr<FJsonObjec
|
|
|
2491
2835
|
FString AssetPath;
|
|
2492
2836
|
if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
|
|
2493
2837
|
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
return MCPError(FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
|
|
2498
|
-
}
|
|
2838
|
+
TSharedPtr<FJsonValue> LoadError;
|
|
2839
|
+
UObject* Asset = MCPRequireAssetObject(AssetPath, LoadError);
|
|
2840
|
+
if (!Asset) return LoadError;
|
|
2499
2841
|
|
|
2500
2842
|
// Optionally override the source file path
|
|
2501
2843
|
FString NewSourcePath;
|
|
@@ -2563,7 +2905,9 @@ TSharedPtr<FJsonValue> FAssetHandlers::ExportAsset(const TSharedPtr<FJsonObject>
|
|
|
2563
2905
|
FString OutputPath;
|
|
2564
2906
|
if (auto Err = RequireString(Params, TEXT("outputPath"), OutputPath)) return Err;
|
|
2565
2907
|
|
|
2566
|
-
|
|
2908
|
+
// #930: same resolution as asset(read), which opened assets this action
|
|
2909
|
+
// reported as missing.
|
|
2910
|
+
UObject* Asset = MCPLoadAssetObject(AssetPath);
|
|
2567
2911
|
if (!Asset)
|
|
2568
2912
|
{
|
|
2569
2913
|
return MCPError(FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
|
|
@@ -2613,8 +2957,8 @@ TSharedPtr<FJsonValue> FAssetHandlers::ExportTexture(const TSharedPtr<FJsonObjec
|
|
|
2613
2957
|
FString OutputPath;
|
|
2614
2958
|
if (auto Err = RequireStringAlt(Params, TEXT("outputPath"), TEXT("filePath"), OutputPath)) return Err;
|
|
2615
2959
|
|
|
2616
|
-
UTexture2D* Texture =
|
|
2617
|
-
if (!Texture) return
|
|
2960
|
+
UTexture2D* Texture = LoadAssetByPath<UTexture2D>(AssetPath);
|
|
2961
|
+
if (!Texture) return MCPAssetLoadError(AssetPath, TEXT("Texture2D"));
|
|
2618
2962
|
|
|
2619
2963
|
FString AbsPath = OutputPath;
|
|
2620
2964
|
if (FPaths::IsRelative(AbsPath)) AbsPath = FPaths::Combine(FPaths::ProjectDir(), AbsPath);
|
|
@@ -2651,10 +2995,10 @@ TSharedPtr<FJsonValue> FAssetHandlers::CompareTextures(const TSharedPtr<FJsonObj
|
|
|
2651
2995
|
if (auto Err = RequireStringAlt(Params, TEXT("assetPathA"), TEXT("a"), PathA)) return Err;
|
|
2652
2996
|
if (auto Err = RequireStringAlt(Params, TEXT("assetPathB"), TEXT("b"), PathB)) return Err;
|
|
2653
2997
|
|
|
2654
|
-
UTexture2D* A =
|
|
2655
|
-
UTexture2D* B =
|
|
2656
|
-
if (!A) return
|
|
2657
|
-
if (!B) return
|
|
2998
|
+
UTexture2D* A = LoadAssetByPath<UTexture2D>(PathA);
|
|
2999
|
+
UTexture2D* B = LoadAssetByPath<UTexture2D>(PathB);
|
|
3000
|
+
if (!A) return MCPAssetLoadError(PathA, TEXT("Texture2D"));
|
|
3001
|
+
if (!B) return MCPAssetLoadError(PathB, TEXT("Texture2D"));
|
|
2658
3002
|
|
|
2659
3003
|
const bool bSameDims = A->GetSizeX() == B->GetSizeX() && A->GetSizeY() == B->GetSizeY();
|
|
2660
3004
|
const bool bSameFormat = A->GetPixelFormat() == B->GetPixelFormat();
|