Files
integration/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Read.h

730 lines
26 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPAssetFinder.h"
#include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "Engine/Level.h"
#include "Engine/LevelScriptBlueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "K2Node_CallFunction.h"
#include "K2Node_Event.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "K2Node_BreakStruct.h"
#include "K2Node_MakeStruct.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_EditablePinBase.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "MCPHandlers_Read.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS(meta=(ToolName="list_blueprint_assets"))
class UMCPHandler_List : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Substring filter for blueprint name or path"))
FString Filter;
UPROPERTY(meta=(Optional, Description="Filter by parent class name (substring match)"))
FString ParentClass;
UPROPERTY(meta=(Optional, Description="Type filter: 'all' (default), 'regular', or 'level'"))
FString Type;
virtual FString GetDescription() const override
{
return TEXT("List all Blueprint assets in the project, with optional filtering by name, parent class, or type.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
// type: "all" (default), "regular", "level"
bool bIncludeRegular = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("regular");
bool bIncludeLevel = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("level");
TArray<TSharedPtr<FJsonValue>> Entries;
if (bIncludeRegular)
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass()))
{
FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString();
if (!Filter.IsEmpty())
{
if (!Name.Contains(Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(Filter, ESearchCase::IgnoreCase))
{
continue;
}
}
FString ParentClassName;
Asset.GetTagValue(FName(TEXT("ParentClass")), ParentClassName);
// Tag stores full path — extract short name
int32 DotIndex;
if (ParentClassName.FindLastChar('.', DotIndex))
{
ParentClassName = ParentClassName.Mid(DotIndex + 1);
}
if (!ParentClass.IsEmpty())
{
if (!ParentClassName.Contains(ParentClass, ESearchCase::IgnoreCase))
{
continue;
}
}
TSharedRef<FJsonObject> Entry = MakeShared<FJsonObject>();
Entry->SetStringField(TEXT("name"), Name);
Entry->SetStringField(TEXT("path"), Path);
Entry->SetStringField(TEXT("parentClass"), ParentClassName);
Entries.Add(MakeShared<FJsonValueObject>(Entry));
}
// Also include level blueprints from maps
if (bIncludeLevel)
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UWorld::StaticClass()))
{
FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString();
if (!Filter.IsEmpty())
{
if (!Name.Contains(Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(Filter, ESearchCase::IgnoreCase))
{
continue;
}
}
// No parent class filter for level blueprints
if (!ParentClass.IsEmpty())
{
if (!FString(TEXT("LevelScriptActor")).Contains(ParentClass, ESearchCase::IgnoreCase))
{
continue;
}
}
TSharedRef<FJsonObject> Entry = MakeShared<FJsonObject>();
Entry->SetStringField(TEXT("name"), Name);
Entry->SetStringField(TEXT("path"), Path);
Entry->SetStringField(TEXT("parentClass"), TEXT("LevelScriptActor"));
Entry->SetBoolField(TEXT("isLevelBlueprint"), true);
Entries.Add(MakeShared<FJsonValueObject>(Entry));
}
Result->SetNumberField(TEXT("count"), Entries.Num());
Result->SetNumberField(TEXT("total"), UMCPAssetFinder::GetAssets(UBlueprint::StaticClass()).Num() + UMCPAssetFinder::GetAssets(UWorld::StaticClass()).Num());
Result->SetArrayField(TEXT("blueprints"), Entries);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS(meta=(ToolName="dump_blueprint"))
class UMCPHandler_GetBlueprint : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
virtual FString GetDescription() const override
{
return TEXT("Load and serialize a Blueprint, returning its full structure including graphs, variables, and components.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, Result);
if (!BP) return;
TSharedRef<FJsonObject> Tmp = MCPUtils::SerializeBlueprint(BP);
MCPUtils::CopyJsonFields(&*Tmp, Result);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS(meta=(ToolName="dump_blueprint_graph"))
class UMCPHandler_GetGraph : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Graph name to dump"))
FString Graph;
virtual FString GetDescription() const override
{
return TEXT("Dump the detailed node/pin structure of a specific graph within a Blueprint.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
// URL-decode graph name to handle spaces encoded as '+' or '%20'
FString DecodedGraphName = MCPUtils::UrlDecode(Graph);
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError);
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
TArray<UEdGraph*> AllGraphs;
BP->GetAllGraphs(AllGraphs);
for (UEdGraph* GraphObj : AllGraphs)
{
if (GraphObj && GraphObj->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase))
{
TSharedPtr<FJsonObject> GraphJson = MCPUtils::SerializeGraph(GraphObj);
if (GraphJson.IsValid())
{
MCPUtils::CopyJsonFields(GraphJson.Get(), Result);
return;
}
}
}
// Not found — list available graphs
TArray<TSharedPtr<FJsonValue>> GraphNames;
for (UEdGraph* GraphObj : AllGraphs)
{
if (GraphObj)
{
GraphNames.Add(MakeShared<FJsonValueString>(GraphObj->GetName()));
}
}
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName));
Result->SetArrayField(TEXT("availableGraphs"), GraphNames);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS(meta=(ToolName="search_within_blueprints"))
class UMCPHandler_Search : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Search query string to match against node titles, function names, event names, and variable names"))
FString Query;
UPROPERTY(meta=(Optional, Description="Filter results to blueprints whose path contains this substring"))
FString Path;
UPROPERTY(meta=(Optional, Description="Maximum number of results to return (default 50, max 200)"))
int32 MaxResults = 0;
virtual FString GetDescription() const override
{
return TEXT("Search across all Blueprint graphs for nodes matching a query string.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
int32 EffectiveMaxResults = (MaxResults > 0) ? FMath::Clamp(MaxResults, 1, 200) : 50;
// Build a combined list of all searchable blueprints (regular + level)
auto SearchBlueprint = [&](const FString& AssetName, const FString& AssetPath, UBlueprint* BP, TArray<TSharedPtr<FJsonValue>>& OutResults)
{
TArray<UEdGraph*> Graphs;
BP->GetAllGraphs(Graphs);
for (UEdGraph* GraphObj : Graphs)
{
if (!GraphObj || OutResults.Num() >= EffectiveMaxResults) break;
for (UEdGraphNode* Node : GraphObj->Nodes)
{
if (!Node || OutResults.Num() >= EffectiveMaxResults) break;
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
FString FuncName, EventName, VarName;
if (auto* CF = Cast<UK2Node_CallFunction>(Node))
{
FuncName = CF->FunctionReference.GetMemberName().ToString();
}
else if (auto* Ev = Cast<UK2Node_Event>(Node))
{
EventName = Ev->EventReference.GetMemberName().ToString();
}
else if (auto* CE = Cast<UK2Node_CustomEvent>(Node))
{
EventName = CE->CustomFunctionName.ToString();
}
else if (auto* VG = Cast<UK2Node_VariableGet>(Node))
{
VarName = VG->GetVarName().ToString();
}
else if (auto* VS = Cast<UK2Node_VariableSet>(Node))
{
VarName = VS->GetVarName().ToString();
}
bool bMatch = Title.Contains(Query, ESearchCase::IgnoreCase) ||
(!FuncName.IsEmpty() && FuncName.Contains(Query, ESearchCase::IgnoreCase)) ||
(!EventName.IsEmpty() && EventName.Contains(Query, ESearchCase::IgnoreCase)) ||
(!VarName.IsEmpty() && VarName.Contains(Query, ESearchCase::IgnoreCase));
if (bMatch)
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
R->SetStringField(TEXT("blueprint"), AssetName);
R->SetStringField(TEXT("blueprintPath"), AssetPath);
R->SetStringField(TEXT("graph"), GraphObj->GetName());
R->SetStringField(TEXT("nodeTitle"), Title);
R->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName());
if (!FuncName.IsEmpty()) R->SetStringField(TEXT("functionName"), FuncName);
if (!EventName.IsEmpty()) R->SetStringField(TEXT("eventName"), EventName);
if (!VarName.IsEmpty()) R->SetStringField(TEXT("variableName"), VarName);
OutResults.Add(MakeShared<FJsonValueObject>(R));
}
}
}
};
TArray<TSharedPtr<FJsonValue>> Results;
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass()))
{
if (Results.Num() >= EffectiveMaxResults) break;
FString AssetPath = Asset.PackageName.ToString();
if (!Path.IsEmpty() && !AssetPath.Contains(Path, ESearchCase::IgnoreCase))
{
continue;
}
UBlueprint* BP = Cast<UBlueprint>(const_cast<FAssetData&>(Asset).GetAsset());
if (!BP) continue;
SearchBlueprint(Asset.AssetName.ToString(), AssetPath, BP, Results);
}
// Also search level blueprints
for (const FAssetData& MapAsset : UMCPAssetFinder::GetAssets(UWorld::StaticClass()))
{
if (Results.Num() >= EffectiveMaxResults) break;
FString AssetPath = MapAsset.PackageName.ToString();
if (!Path.IsEmpty() && !AssetPath.Contains(Path, ESearchCase::IgnoreCase))
{
continue;
}
UWorld* World = Cast<UWorld>(MapAsset.GetAsset());
if (!World || !World->PersistentLevel) continue;
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(false);
if (!LevelBP) continue;
int32 BeforeCount = Results.Num();
SearchBlueprint(MapAsset.AssetName.ToString(), AssetPath, LevelBP, Results);
// Tag newly-added entries as level blueprint results
for (int32 i = BeforeCount; i < Results.Num(); ++i)
{
Results[i]->AsObject()->SetBoolField(TEXT("isLevelBlueprint"), true);
}
}
Result->SetStringField(TEXT("query"), Query);
Result->SetNumberField(TEXT("resultCount"), Results.Num());
Result->SetArrayField(TEXT("results"), Results);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ============================================================
// HandleTestSave — load a Blueprint and save it unmodified (diagnostic)
// ============================================================
UCLASS(meta=(ToolName="test_save_blueprint_package"))
class UMCPHandler_TestSave : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
virtual FString GetDescription() const override
{
return TEXT("Load a Blueprint and save it unmodified as a diagnostic test.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save requested for '%s'"), *Blueprint);
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError);
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"),
*BP->GetName(),
BP->GeneratedClass ? *BP->GeneratedClass->GetName() : TEXT("null"));
// Attempt save with NO modifications
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("packagePath"), BP->GetPackage()->GetName());
Result->SetBoolField(TEXT("hasGeneratedClass"), BP->GeneratedClass != nullptr);
Result->SetBoolField(TEXT("saved"), bSaved);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ============================================================
// HandleFindReferences — find all Blueprints referencing an asset
// ============================================================
UCLASS(meta=(ToolName="find_asset_references"))
class UMCPHandler_FindReferences : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Asset package path to find references for"))
FString AssetPath;
virtual FString GetDescription() const override
{
return TEXT("Find all assets that reference a given asset, categorized into Blueprint and non-Blueprint referencers.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
IAssetRegistry& Registry = *IAssetRegistry::Get();
TArray<FName> Referencers;
Registry.GetReferencers(FName(*AssetPath), Referencers);
// Build set of known Blueprint package names for filtering
TSet<FString> BlueprintPackages;
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass()))
{
BlueprintPackages.Add(Asset.PackageName.ToString());
}
TArray<TSharedPtr<FJsonValue>> BPRefs;
TArray<TSharedPtr<FJsonValue>> OtherRefs;
for (const FName& Ref : Referencers)
{
FString RefStr = Ref.ToString();
if (BlueprintPackages.Contains(RefStr))
{
BPRefs.Add(MakeShared<FJsonValueString>(RefStr));
}
else
{
OtherRefs.Add(MakeShared<FJsonValueString>(RefStr));
}
}
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetNumberField(TEXT("totalReferencers"), Referencers.Num());
Result->SetNumberField(TEXT("blueprintReferencerCount"), BPRefs.Num());
Result->SetArrayField(TEXT("blueprintReferencers"), BPRefs);
Result->SetNumberField(TEXT("otherReferencerCount"), OtherRefs.Num());
Result->SetArrayField(TEXT("otherReferencers"), OtherRefs);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ============================================================
// HandleSearchByType — find all usages of a type across blueprints
// ============================================================
UCLASS(meta=(ToolName="search_type_usage_in_blueprints"))
class UMCPHandler_SearchByType : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Type name to search for (e.g. 'FVector', 'MyStruct'). F/E/U prefix is stripped for matching."))
FString TypeName;
UPROPERTY(meta=(Optional, Description="Filter to blueprints whose name or path contains this substring"))
FString Filter;
UPROPERTY(meta=(Optional, Description="Maximum number of results to return (default 200, max 500)"))
int32 MaxResults = 0;
virtual FString GetDescription() const override
{
return TEXT("Search all Blueprints for usages of a specific type in variables, function parameters, struct nodes, and pin connections.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
FString DecodedTypeName = MCPUtils::UrlDecode(TypeName);
FString FilterStr = Filter.IsEmpty() ? FString() : MCPUtils::UrlDecode(Filter);
int32 EffectiveMaxResults = (MaxResults > 0) ? FMath::Clamp(MaxResults, 1, 500) : 200;
// Strip F/E/U prefix for comparison
FString TypeNameNoPrefix = DecodedTypeName;
if (TypeNameNoPrefix.StartsWith(TEXT("F")) || TypeNameNoPrefix.StartsWith(TEXT("E")) || TypeNameNoPrefix.StartsWith(TEXT("U")))
{
TypeNameNoPrefix = TypeNameNoPrefix.Mid(1);
}
auto MatchesType = [&DecodedTypeName, &TypeNameNoPrefix](const FString& TestType) -> bool
{
return TestType.Equals(DecodedTypeName, ESearchCase::IgnoreCase) ||
TestType.Equals(TypeNameNoPrefix, ESearchCase::IgnoreCase);
};
TArray<TSharedPtr<FJsonValue>> Results;
// Lambda that searches a single Blueprint for type usages
auto SearchOneBlueprint = [&](const FString& BPName, const FString& BPPath, UBlueprint* BP, bool bIsLevel)
{
// Check variables
for (const FBPVariableDescription& Var : BP->NewVariables)
{
if (Results.Num() >= EffectiveMaxResults) break;
FString VarSubtype;
if (Var.VarType.PinSubCategoryObject.IsValid())
{
VarSubtype = Var.VarType.PinSubCategoryObject->GetName();
}
if (MatchesType(VarSubtype) || MatchesType(Var.VarType.PinCategory.ToString()))
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
R->SetStringField(TEXT("blueprint"), BPName);
R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("variable"));
R->SetStringField(TEXT("location"), Var.VarName.ToString());
R->SetStringField(TEXT("currentType"), Var.VarType.PinCategory.ToString());
if (!VarSubtype.IsEmpty())
R->SetStringField(TEXT("currentSubtype"), VarSubtype);
if (bIsLevel)
R->SetBoolField(TEXT("isLevelBlueprint"), true);
Results.Add(MakeShared<FJsonValueObject>(R));
}
}
// Check graphs for function/event params, struct nodes, and pin connections
TArray<UEdGraph*> AllGraphs;
BP->GetAllGraphs(AllGraphs);
for (UEdGraph* GraphObj : AllGraphs)
{
if (!GraphObj || Results.Num() >= EffectiveMaxResults) break;
for (UEdGraphNode* Node : GraphObj->Nodes)
{
if (!Node || Results.Num() >= EffectiveMaxResults) break;
// Check FunctionEntry/CustomEvent parameters
if (auto* FuncEntry = Cast<UK2Node_FunctionEntry>(Node))
{
for (const TSharedPtr<FUserPinInfo>& PinInfo : FuncEntry->UserDefinedPins)
{
if (!PinInfo.IsValid()) continue;
FString ParamSubtype;
if (PinInfo->PinType.PinSubCategoryObject.IsValid())
ParamSubtype = PinInfo->PinType.PinSubCategoryObject->GetName();
if (MatchesType(ParamSubtype) || MatchesType(PinInfo->PinType.PinCategory.ToString()))
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
R->SetStringField(TEXT("blueprint"), BPName);
R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("functionParameter"));
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
*GraphObj->GetName(), *PinInfo->PinName.ToString()));
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString());
if (!ParamSubtype.IsEmpty())
R->SetStringField(TEXT("currentSubtype"), ParamSubtype);
if (bIsLevel)
R->SetBoolField(TEXT("isLevelBlueprint"), true);
Results.Add(MakeShared<FJsonValueObject>(R));
}
}
}
else if (auto* CustomEvent = Cast<UK2Node_CustomEvent>(Node))
{
for (const TSharedPtr<FUserPinInfo>& PinInfo : CustomEvent->UserDefinedPins)
{
if (!PinInfo.IsValid()) continue;
FString ParamSubtype;
if (PinInfo->PinType.PinSubCategoryObject.IsValid())
ParamSubtype = PinInfo->PinType.PinSubCategoryObject->GetName();
if (MatchesType(ParamSubtype) || MatchesType(PinInfo->PinType.PinCategory.ToString()))
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
R->SetStringField(TEXT("blueprint"), BPName);
R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("eventParameter"));
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
*CustomEvent->CustomFunctionName.ToString(), *PinInfo->PinName.ToString()));
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString());
if (!ParamSubtype.IsEmpty())
R->SetStringField(TEXT("currentSubtype"), ParamSubtype);
if (bIsLevel)
R->SetBoolField(TEXT("isLevelBlueprint"), true);
Results.Add(MakeShared<FJsonValueObject>(R));
}
}
}
// Check Break/Make struct nodes
else if (auto* BreakNode = Cast<UK2Node_BreakStruct>(Node))
{
if (BreakNode->StructType && MatchesType(BreakNode->StructType->GetName()))
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
R->SetStringField(TEXT("blueprint"), BPName);
R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("breakStruct"));
R->SetStringField(TEXT("location"), GraphObj->GetName());
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
R->SetStringField(TEXT("structType"), BreakNode->StructType->GetName());
if (bIsLevel)
R->SetBoolField(TEXT("isLevelBlueprint"), true);
Results.Add(MakeShared<FJsonValueObject>(R));
}
}
else if (auto* MakeNode = Cast<UK2Node_MakeStruct>(Node))
{
if (MakeNode->StructType && MatchesType(MakeNode->StructType->GetName()))
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
R->SetStringField(TEXT("blueprint"), BPName);
R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("makeStruct"));
R->SetStringField(TEXT("location"), GraphObj->GetName());
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
R->SetStringField(TEXT("structType"), MakeNode->StructType->GetName());
if (bIsLevel)
R->SetBoolField(TEXT("isLevelBlueprint"), true);
Results.Add(MakeShared<FJsonValueObject>(R));
}
}
// Check pin connections carrying the type
for (UEdGraphPin* Pin : Node->Pins)
{
if (!Pin || Pin->bHidden || Results.Num() >= EffectiveMaxResults) continue;
FString PinSubtype;
if (Pin->PinType.PinSubCategoryObject.IsValid())
PinSubtype = Pin->PinType.PinSubCategoryObject->GetName();
if (Pin->LinkedTo.Num() > 0 &&
(MatchesType(PinSubtype) || MatchesType(Pin->PinType.PinCategory.ToString())))
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
R->SetStringField(TEXT("blueprint"), BPName);
R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("pinConnection"));
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
*Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(),
*Pin->PinName.ToString()));
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
R->SetStringField(TEXT("graph"), GraphObj->GetName());
R->SetStringField(TEXT("pinType"), Pin->PinType.PinCategory.ToString());
if (!PinSubtype.IsEmpty())
R->SetStringField(TEXT("pinSubtype"), PinSubtype);
R->SetNumberField(TEXT("connectionCount"), Pin->LinkedTo.Num());
if (bIsLevel)
R->SetBoolField(TEXT("isLevelBlueprint"), true);
Results.Add(MakeShared<FJsonValueObject>(R));
}
}
}
}
};
// Search regular blueprints
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass()))
{
if (Results.Num() >= EffectiveMaxResults) break;
FString AssetPath = Asset.PackageName.ToString();
FString BPName = Asset.AssetName.ToString();
if (!FilterStr.IsEmpty() && !BPName.Contains(FilterStr, ESearchCase::IgnoreCase) &&
!AssetPath.Contains(FilterStr, ESearchCase::IgnoreCase))
{
continue;
}
UBlueprint* BP = Cast<UBlueprint>(const_cast<FAssetData&>(Asset).GetAsset());
if (!BP) continue;
SearchOneBlueprint(BPName, AssetPath, BP, false);
}
// Search level blueprints from maps
for (const FAssetData& MapAsset : UMCPAssetFinder::GetAssets(UWorld::StaticClass()))
{
if (Results.Num() >= EffectiveMaxResults) break;
FString AssetPath = MapAsset.PackageName.ToString();
FString MapName = MapAsset.AssetName.ToString();
if (!FilterStr.IsEmpty() && !MapName.Contains(FilterStr, ESearchCase::IgnoreCase) &&
!AssetPath.Contains(FilterStr, ESearchCase::IgnoreCase))
{
continue;
}
UWorld* World = Cast<UWorld>(MapAsset.GetAsset());
if (!World || !World->PersistentLevel) continue;
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(false);
if (!LevelBP) continue;
SearchOneBlueprint(MapName, AssetPath, LevelBP, true);
}
Result->SetStringField(TEXT("typeName"), DecodedTypeName);
Result->SetNumberField(TEXT("resultCount"), Results.Num());
Result->SetArrayField(TEXT("results"), Results);
}
};