MCPFetcher, and new code for FormatName

This commit is contained in:
2026-03-10 00:22:56 -04:00
parent 9329c8399b
commit 2a5833fe04
12 changed files with 592 additions and 230 deletions

View File

@@ -0,0 +1,239 @@
#include "MCPFetcher.h"
#include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "UObject/PropertyAccessUtil.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "Engine/World.h"
#include "Engine/LevelScriptBlueprint.h"
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
{
bError = true;
ErrorCB.SetError(Msg);
return *this;
}
MCPFetcher& MCPFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected)
{
bError = true;
if (ResultPin)
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is a pin, expected %s"), Walker, Expected));
else if (Obj)
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is %s, expected %s"), Walker, *Obj->GetClass()->GetName(), Expected));
else
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is null, expected %s"), Walker, Expected));
return *this;
}
MCPFetcher& MCPFetcher::Walk(const FString& Path)
{
if (bError) return *this;
// Split on commas
TArray<FString> Segments;
Path.ParseIntoArray(Segments, TEXT(","));
if (Segments.Num() == 0)
{
SetError(TEXT("Empty path"));
return *this;
}
// If no object yet, first segment is an asset path
int32 Start = 0;
if (!Obj && !ResultPin)
{
LoadUAsset(Segments[0]);
if (bError) return *this;
Start = 1;
}
// Walk each subsequent segment
for (int32 i = Start; i < Segments.Num(); i++)
{
FString Key, Value;
Segments[i].Split(TEXT(":"), &Key, &Value);
if (StrEq(Key, TEXT("graph"))) Graph(Value);
else if (StrEq(Key, TEXT("node"))) Node(Value);
else if (StrEq(Key, TEXT("pin"))) Pin(Value);
else if (StrEq(Key, TEXT("component"))) Component(Value);
else if (StrEq(Key, TEXT("property"))) Property(Value);
else if (StrEq(Key, TEXT("levelblueprint"))) LevelBlueprint();
else
{
SetError(FString::Printf(TEXT("Unknown walker '%s'"), *Key));
return *this;
}
if (bError) return *this;
}
return *this;
}
void MCPFetcher::LoadUAsset(const FString& PackagePath)
{
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
if (!Obj)
SetError(FString::Printf(TEXT("Could not load asset '%s'"), *PackagePath));
}
MCPFetcher& MCPFetcher::Graph(const FString& Value)
{
if (bError) return *this;
UBlueprint* BP = ::Cast<UBlueprint>(Obj);
if (!BP)
return TypeMismatch(TEXT("graph"), TEXT("Blueprint"));
TArray<UEdGraph*> Matches = MCPUtils::AllGraphsNamed(BP, Value);
if (Matches.Num() == 0)
return SetError(FString::Printf(TEXT("Graph '%s' not found in %s"), *Value, *BP->GetName()));
if (Matches.Num() > 1)
return SetError(FString::Printf(TEXT("Ambiguous graph '%s' in %s — %d matches"), *Value, *BP->GetName(), Matches.Num()));
SetObj(Matches[0]);
return *this;
}
MCPFetcher& MCPFetcher::Node(const FString& Value)
{
if (bError) return *this;
// If current object is a graph, search that graph
if (UEdGraph* G = ::Cast<UEdGraph>(Obj))
{
UEdGraphNode* Found = nullptr;
for (UEdGraphNode* N : G->Nodes)
{
if (!N || !MCPUtils::Identifies(Value, N))
continue;
if (Found)
return SetError(FString::Printf(TEXT("Ambiguous node '%s' in graph %s"), *Value, *G->GetName()));
Found = N;
}
if (!Found)
return SetError(FString::Printf(TEXT("Node '%s' not found in graph %s"), *Value, *G->GetName()));
SetObj(Found);
return *this;
}
// If current object is a blueprint, search all graphs
if (UBlueprint* BP = ::Cast<UBlueprint>(Obj))
{
UEdGraphNode* Found = nullptr;
for (UEdGraph* G : MCPUtils::AllGraphs(BP))
{
for (UEdGraphNode* N : G->Nodes)
{
if (!N || !MCPUtils::Identifies(Value, N))
continue;
if (Found)
return SetError(FString::Printf(TEXT("Ambiguous node '%s' in %s"), *Value, *BP->GetName()));
Found = N;
}
}
if (!Found)
return SetError(FString::Printf(TEXT("Node '%s' not found in %s"), *Value, *BP->GetName()));
SetObj(Found);
return *this;
}
return TypeMismatch(TEXT("node"), TEXT("graph or Blueprint"));
}
MCPFetcher& MCPFetcher::Pin(const FString& Value)
{
if (bError) return *this;
UEdGraphNode* N = ::Cast<UEdGraphNode>(Obj);
if (!N)
return TypeMismatch(TEXT("pin"), TEXT("node"));
UEdGraphPin* Found = nullptr;
for (UEdGraphPin *P : N->Pins)
{
if (!MCPUtils::Identifies(Value, P))
continue;
if (Found)
return SetError(FString::Printf(TEXT("Ambiguous pin '%s' on node %s"),
*Value, *MCPUtils::FormatName(N)));
Found = P;
}
if (!Found)
return SetError(FString::Printf(TEXT("Pin '%s' not found on node %s"),
*Value, *MCPUtils::FormatName(N)));
SetPin(Found);
return *this;
}
MCPFetcher& MCPFetcher::Component(const FString& Value)
{
if (bError) return *this;
UBlueprint* BP = ::Cast<UBlueprint>(Obj);
if (!BP)
return TypeMismatch(TEXT("component"), TEXT("Blueprint"));
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS)
return SetError(FString::Printf(TEXT("Blueprint %s has no SimpleConstructionScript (not an Actor Blueprint)"), *BP->GetName()));
FName SearchName(*Value);
for (USCS_Node* SCSNode : SCS->GetAllNodes())
{
if (SCSNode && SCSNode->GetVariableName() == SearchName)
{
SetObj(SCSNode->ComponentTemplate);
return *this;
}
}
return SetError(FString::Printf(TEXT("Component '%s' not found in %s"), *Value, *BP->GetName()));
}
MCPFetcher& MCPFetcher::Property(const FString& Value)
{
if (bError) return *this;
if (!Obj)
return TypeMismatch(TEXT("property"), TEXT("UObject"));
FProperty* Prop = Obj->GetClass()->FindPropertyByName(FName(*Value));
if (!Prop)
return SetError(FString::Printf(TEXT("Property '%s' not found on %s"), *Value, *Obj->GetClass()->GetName()));
FObjectProperty* ObjProp = CastField<FObjectProperty>(Prop);
if (!ObjProp)
return SetError(FString::Printf(TEXT("Property '%s' is not an object property (type: %s)"), *Value, *Prop->GetCPPType()));
UObject* PropValue = ObjProp->GetObjectPropertyValue(Prop->ContainerPtrToValuePtr<void>(Obj));
if (!PropValue)
return SetError(FString::Printf(TEXT("Property '%s' is null on %s"), *Value, *Obj->GetName()));
SetObj(PropValue);
return *this;
}
MCPFetcher& MCPFetcher::LevelBlueprint()
{
if (bError) return *this;
UWorld* World = ::Cast<UWorld>(Obj);
if (!World)
return TypeMismatch(TEXT("levelblueprint"), TEXT("World"));
if (!World->PersistentLevel)
return SetError(TEXT("World has no PersistentLevel"));
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
if (!LevelBP)
return SetError(TEXT("World has no level blueprint"));
SetObj(LevelBP);
return *this;
}