730 lines
26 KiB
C++
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);
|
|
}
|
|
};
|