mirror of
https://github.com/kuhyx/praca_magisterska.git
synced 2026-07-04 13:23:05 +02:00
chore: added mcp server plugin to game
This commit is contained in:
parent
749568a469
commit
f7bac4cc6d
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,924 @@
|
||||
#include "Commands/UnrealMCPBlueprintNodeCommands.h"
|
||||
#include "Commands/UnrealMCPCommonUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "K2Node_Event.h"
|
||||
#include "K2Node_CallFunction.h"
|
||||
#include "K2Node_VariableGet.h"
|
||||
#include "K2Node_InputAction.h"
|
||||
#include "K2Node_Self.h"
|
||||
#include "Kismet2/BlueprintEditorUtils.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
#include "GameFramework/InputSettings.h"
|
||||
#include "Camera/CameraActor.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
|
||||
// Declare the log category
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogUnrealMCP, Log, All);
|
||||
|
||||
FUnrealMCPBlueprintNodeCommands::FUnrealMCPBlueprintNodeCommands()
|
||||
{
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
if (CommandType == TEXT("connect_blueprint_nodes"))
|
||||
{
|
||||
return HandleConnectBlueprintNodes(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("add_blueprint_get_self_component_reference"))
|
||||
{
|
||||
return HandleAddBlueprintGetSelfComponentReference(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("add_blueprint_event_node"))
|
||||
{
|
||||
return HandleAddBlueprintEvent(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("add_blueprint_function_node"))
|
||||
{
|
||||
return HandleAddBlueprintFunctionCall(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("add_blueprint_variable"))
|
||||
{
|
||||
return HandleAddBlueprintVariable(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("add_blueprint_input_action_node"))
|
||||
{
|
||||
return HandleAddBlueprintInputActionNode(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("add_blueprint_self_reference"))
|
||||
{
|
||||
return HandleAddBlueprintSelfReference(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("find_blueprint_nodes"))
|
||||
{
|
||||
return HandleFindBlueprintNodes(Params);
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown blueprint node command: %s"), *CommandType));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleConnectBlueprintNodes(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString SourceNodeId;
|
||||
if (!Params->TryGetStringField(TEXT("source_node_id"), SourceNodeId))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'source_node_id' parameter"));
|
||||
}
|
||||
|
||||
FString TargetNodeId;
|
||||
if (!Params->TryGetStringField(TEXT("target_node_id"), TargetNodeId))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'target_node_id' parameter"));
|
||||
}
|
||||
|
||||
FString SourcePinName;
|
||||
if (!Params->TryGetStringField(TEXT("source_pin"), SourcePinName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'source_pin' parameter"));
|
||||
}
|
||||
|
||||
FString TargetPinName;
|
||||
if (!Params->TryGetStringField(TEXT("target_pin"), TargetPinName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'target_pin' parameter"));
|
||||
}
|
||||
|
||||
// Find the blueprint
|
||||
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get the event graph
|
||||
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
|
||||
if (!EventGraph)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
|
||||
}
|
||||
|
||||
// Find the nodes
|
||||
UEdGraphNode* SourceNode = nullptr;
|
||||
UEdGraphNode* TargetNode = nullptr;
|
||||
for (UEdGraphNode* Node : EventGraph->Nodes)
|
||||
{
|
||||
if (Node->NodeGuid.ToString() == SourceNodeId)
|
||||
{
|
||||
SourceNode = Node;
|
||||
}
|
||||
else if (Node->NodeGuid.ToString() == TargetNodeId)
|
||||
{
|
||||
TargetNode = Node;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SourceNode || !TargetNode)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Source or target node not found"));
|
||||
}
|
||||
|
||||
// Connect the nodes
|
||||
if (FUnrealMCPCommonUtils::ConnectGraphNodes(EventGraph, SourceNode, SourcePinName, TargetNode, TargetPinName))
|
||||
{
|
||||
// Mark the blueprint as modified
|
||||
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("source_node_id"), SourceNodeId);
|
||||
ResultObj->SetStringField(TEXT("target_node_id"), TargetNodeId);
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to connect nodes"));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintGetSelfComponentReference(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString ComponentName;
|
||||
if (!Params->TryGetStringField(TEXT("component_name"), ComponentName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'component_name' parameter"));
|
||||
}
|
||||
|
||||
// Get position parameters (optional)
|
||||
FVector2D NodePosition(0.0f, 0.0f);
|
||||
if (Params->HasField(TEXT("node_position")))
|
||||
{
|
||||
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
|
||||
}
|
||||
|
||||
// Find the blueprint
|
||||
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get the event graph
|
||||
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
|
||||
if (!EventGraph)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
|
||||
}
|
||||
|
||||
// We'll skip component verification since the GetAllNodes API may have changed in UE5.5
|
||||
|
||||
// Create the variable get node directly
|
||||
UK2Node_VariableGet* GetComponentNode = NewObject<UK2Node_VariableGet>(EventGraph);
|
||||
if (!GetComponentNode)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create get component node"));
|
||||
}
|
||||
|
||||
// Set up the variable reference properly for UE5.5
|
||||
FMemberReference& VarRef = GetComponentNode->VariableReference;
|
||||
VarRef.SetSelfMember(FName(*ComponentName));
|
||||
|
||||
// Set node position
|
||||
GetComponentNode->NodePosX = NodePosition.X;
|
||||
GetComponentNode->NodePosY = NodePosition.Y;
|
||||
|
||||
// Add to graph
|
||||
EventGraph->AddNode(GetComponentNode);
|
||||
GetComponentNode->CreateNewGuid();
|
||||
GetComponentNode->PostPlacedNewNode();
|
||||
GetComponentNode->AllocateDefaultPins();
|
||||
|
||||
// Explicitly reconstruct node for UE5.5
|
||||
GetComponentNode->ReconstructNode();
|
||||
|
||||
// Mark the blueprint as modified
|
||||
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("node_id"), GetComponentNode->NodeGuid.ToString());
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintEvent(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString EventName;
|
||||
if (!Params->TryGetStringField(TEXT("event_name"), EventName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'event_name' parameter"));
|
||||
}
|
||||
|
||||
// Get position parameters (optional)
|
||||
FVector2D NodePosition(0.0f, 0.0f);
|
||||
if (Params->HasField(TEXT("node_position")))
|
||||
{
|
||||
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
|
||||
}
|
||||
|
||||
// Find the blueprint
|
||||
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get the event graph
|
||||
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
|
||||
if (!EventGraph)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
|
||||
}
|
||||
|
||||
// Create the event node
|
||||
UK2Node_Event* EventNode = FUnrealMCPCommonUtils::CreateEventNode(EventGraph, EventName, NodePosition);
|
||||
if (!EventNode)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create event node"));
|
||||
}
|
||||
|
||||
// Mark the blueprint as modified
|
||||
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("node_id"), EventNode->NodeGuid.ToString());
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintFunctionCall(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString FunctionName;
|
||||
if (!Params->TryGetStringField(TEXT("function_name"), FunctionName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'function_name' parameter"));
|
||||
}
|
||||
|
||||
// Get position parameters (optional)
|
||||
FVector2D NodePosition(0.0f, 0.0f);
|
||||
if (Params->HasField(TEXT("node_position")))
|
||||
{
|
||||
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
|
||||
}
|
||||
|
||||
// Check for target parameter (optional)
|
||||
FString Target;
|
||||
Params->TryGetStringField(TEXT("target"), Target);
|
||||
|
||||
// Find the blueprint
|
||||
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get the event graph
|
||||
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
|
||||
if (!EventGraph)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
|
||||
}
|
||||
|
||||
// Find the function
|
||||
UFunction* Function = nullptr;
|
||||
UK2Node_CallFunction* FunctionNode = nullptr;
|
||||
|
||||
// Add extensive logging for debugging
|
||||
UE_LOG(LogTemp, Display, TEXT("Looking for function '%s' in target '%s'"),
|
||||
*FunctionName, Target.IsEmpty() ? TEXT("Blueprint") : *Target);
|
||||
|
||||
// Check if we have a target class specified
|
||||
if (!Target.IsEmpty())
|
||||
{
|
||||
// Try to find the target class
|
||||
UClass* TargetClass = nullptr;
|
||||
|
||||
// First try without a prefix
|
||||
TargetClass = FindObject<UClass>(ANY_PACKAGE, *Target);
|
||||
UE_LOG(LogTemp, Display, TEXT("Tried to find class '%s': %s"),
|
||||
*Target, TargetClass ? TEXT("Found") : TEXT("Not found"));
|
||||
|
||||
// If not found, try with U prefix (common convention for UE classes)
|
||||
if (!TargetClass && !Target.StartsWith(TEXT("U")))
|
||||
{
|
||||
FString TargetWithPrefix = FString(TEXT("U")) + Target;
|
||||
TargetClass = FindObject<UClass>(ANY_PACKAGE, *TargetWithPrefix);
|
||||
UE_LOG(LogTemp, Display, TEXT("Tried to find class '%s': %s"),
|
||||
*TargetWithPrefix, TargetClass ? TEXT("Found") : TEXT("Not found"));
|
||||
}
|
||||
|
||||
// If still not found, try with common component names
|
||||
if (!TargetClass)
|
||||
{
|
||||
// Try some common component class names
|
||||
TArray<FString> PossibleClassNames;
|
||||
PossibleClassNames.Add(FString(TEXT("U")) + Target + TEXT("Component"));
|
||||
PossibleClassNames.Add(Target + TEXT("Component"));
|
||||
|
||||
for (const FString& ClassName : PossibleClassNames)
|
||||
{
|
||||
TargetClass = FindObject<UClass>(ANY_PACKAGE, *ClassName);
|
||||
if (TargetClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Found class using alternative name '%s'"), *ClassName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special case handling for common classes like UGameplayStatics
|
||||
if (!TargetClass && Target == TEXT("UGameplayStatics"))
|
||||
{
|
||||
// For UGameplayStatics, use a direct reference to known class
|
||||
TargetClass = FindObject<UClass>(ANY_PACKAGE, TEXT("UGameplayStatics"));
|
||||
if (!TargetClass)
|
||||
{
|
||||
// Try loading it from its known package
|
||||
TargetClass = LoadObject<UClass>(nullptr, TEXT("/Script/Engine.GameplayStatics"));
|
||||
UE_LOG(LogTemp, Display, TEXT("Explicitly loading GameplayStatics: %s"),
|
||||
TargetClass ? TEXT("Success") : TEXT("Failed"));
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a target class, look for the function there
|
||||
if (TargetClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Looking for function '%s' in class '%s'"),
|
||||
*FunctionName, *TargetClass->GetName());
|
||||
|
||||
// First try exact name
|
||||
Function = TargetClass->FindFunctionByName(*FunctionName);
|
||||
|
||||
// If not found, try class hierarchy
|
||||
UClass* CurrentClass = TargetClass;
|
||||
while (!Function && CurrentClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Searching in class: %s"), *CurrentClass->GetName());
|
||||
|
||||
// Try exact match
|
||||
Function = CurrentClass->FindFunctionByName(*FunctionName);
|
||||
|
||||
// Try case-insensitive match
|
||||
if (!Function)
|
||||
{
|
||||
for (TFieldIterator<UFunction> FuncIt(CurrentClass); FuncIt; ++FuncIt)
|
||||
{
|
||||
UFunction* AvailableFunc = *FuncIt;
|
||||
UE_LOG(LogTemp, Display, TEXT(" - Available function: %s"), *AvailableFunc->GetName());
|
||||
|
||||
if (AvailableFunc->GetName().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT(" - Found case-insensitive match: %s"), *AvailableFunc->GetName());
|
||||
Function = AvailableFunc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to parent class
|
||||
CurrentClass = CurrentClass->GetSuperClass();
|
||||
}
|
||||
|
||||
// Special handling for known functions
|
||||
if (!Function)
|
||||
{
|
||||
if (TargetClass->GetName() == TEXT("GameplayStatics") &&
|
||||
(FunctionName == TEXT("GetActorOfClass") || FunctionName.Equals(TEXT("GetActorOfClass"), ESearchCase::IgnoreCase)))
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Using special case handling for GameplayStatics::GetActorOfClass"));
|
||||
|
||||
// Create the function node directly
|
||||
FunctionNode = NewObject<UK2Node_CallFunction>(EventGraph);
|
||||
if (FunctionNode)
|
||||
{
|
||||
// Direct setup for known function
|
||||
FunctionNode->FunctionReference.SetExternalMember(
|
||||
FName(TEXT("GetActorOfClass")),
|
||||
TargetClass
|
||||
);
|
||||
|
||||
FunctionNode->NodePosX = NodePosition.X;
|
||||
FunctionNode->NodePosY = NodePosition.Y;
|
||||
EventGraph->AddNode(FunctionNode);
|
||||
FunctionNode->CreateNewGuid();
|
||||
FunctionNode->PostPlacedNewNode();
|
||||
FunctionNode->AllocateDefaultPins();
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("Created GetActorOfClass node directly"));
|
||||
|
||||
// List all pins
|
||||
for (UEdGraphPin* Pin : FunctionNode->Pins)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT(" - Pin: %s, Direction: %d, Category: %s"),
|
||||
*Pin->PinName.ToString(), (int32)Pin->Direction, *Pin->PinType.PinCategory.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we still haven't found the function, try in the blueprint's class
|
||||
if (!Function && !FunctionNode)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Trying to find function in blueprint class"));
|
||||
Function = Blueprint->GeneratedClass->FindFunctionByName(*FunctionName);
|
||||
}
|
||||
|
||||
// Create the function call node if we found the function
|
||||
if (Function && !FunctionNode)
|
||||
{
|
||||
FunctionNode = FUnrealMCPCommonUtils::CreateFunctionCallNode(EventGraph, Function, NodePosition);
|
||||
}
|
||||
|
||||
if (!FunctionNode)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Function not found: %s in target %s"), *FunctionName, Target.IsEmpty() ? TEXT("Blueprint") : *Target));
|
||||
}
|
||||
|
||||
// Set parameters if provided
|
||||
if (Params->HasField(TEXT("params")))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* ParamsObj;
|
||||
if (Params->TryGetObjectField(TEXT("params"), ParamsObj))
|
||||
{
|
||||
// Process parameters
|
||||
for (const TPair<FString, TSharedPtr<FJsonValue>>& Param : (*ParamsObj)->Values)
|
||||
{
|
||||
const FString& ParamName = Param.Key;
|
||||
const TSharedPtr<FJsonValue>& ParamValue = Param.Value;
|
||||
|
||||
// Find the parameter pin
|
||||
UEdGraphPin* ParamPin = FUnrealMCPCommonUtils::FindPin(FunctionNode, ParamName, EGPD_Input);
|
||||
if (ParamPin)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Found parameter pin '%s' of category '%s'"),
|
||||
*ParamName, *ParamPin->PinType.PinCategory.ToString());
|
||||
UE_LOG(LogTemp, Display, TEXT(" Current default value: '%s'"), *ParamPin->DefaultValue);
|
||||
if (ParamPin->PinType.PinSubCategoryObject.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT(" Pin subcategory: '%s'"),
|
||||
*ParamPin->PinType.PinSubCategoryObject->GetName());
|
||||
}
|
||||
|
||||
// Set parameter based on type
|
||||
if (ParamValue->Type == EJson::String)
|
||||
{
|
||||
FString StringVal = ParamValue->AsString();
|
||||
UE_LOG(LogTemp, Display, TEXT(" Setting string parameter '%s' to: '%s'"),
|
||||
*ParamName, *StringVal);
|
||||
|
||||
// Handle class reference parameters (e.g., ActorClass in GetActorOfClass)
|
||||
if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Class)
|
||||
{
|
||||
// For class references, we require the exact class name with proper prefix
|
||||
// - Actor classes must start with 'A' (e.g., ACameraActor)
|
||||
// - Non-actor classes must start with 'U' (e.g., UObject)
|
||||
const FString& ClassName = StringVal;
|
||||
|
||||
// TODO: This likely won't work in UE5.5+, so don't rely on it.
|
||||
UClass* Class = FindObject<UClass>(ANY_PACKAGE, *ClassName);
|
||||
|
||||
if (!Class)
|
||||
{
|
||||
Class = LoadObject<UClass>(nullptr, *ClassName);
|
||||
UE_LOG(LogUnrealMCP, Display, TEXT("FindObject<UClass> failed. Assuming soft path path: %s"), *ClassName);
|
||||
}
|
||||
|
||||
// If not found, try with Engine module path
|
||||
if (!Class)
|
||||
{
|
||||
FString EngineClassName = FString::Printf(TEXT("/Script/Engine.%s"), *ClassName);
|
||||
Class = LoadObject<UClass>(nullptr, *EngineClassName);
|
||||
UE_LOG(LogUnrealMCP, Display, TEXT("Trying Engine module path: %s"), *EngineClassName);
|
||||
}
|
||||
|
||||
if (!Class)
|
||||
{
|
||||
UE_LOG(LogUnrealMCP, Error, TEXT("Failed to find class '%s'. Make sure to use the exact class name with proper prefix (A for actors, U for non-actors)"), *ClassName);
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Failed to find class '%s'"), *ClassName));
|
||||
}
|
||||
|
||||
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(EventGraph->GetSchema());
|
||||
if (!K2Schema)
|
||||
{
|
||||
UE_LOG(LogUnrealMCP, Error, TEXT("Failed to get K2Schema"));
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get K2Schema"));
|
||||
}
|
||||
|
||||
K2Schema->TrySetDefaultObject(*ParamPin, Class);
|
||||
if (ParamPin->DefaultObject != Class)
|
||||
{
|
||||
UE_LOG(LogUnrealMCP, Error, TEXT("Failed to set class reference for pin '%s' to '%s'"), *ParamPin->PinName.ToString(), *ClassName);
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Failed to set class reference for pin '%s'"), *ParamPin->PinName.ToString()));
|
||||
}
|
||||
|
||||
UE_LOG(LogUnrealMCP, Log, TEXT("Successfully set class reference for pin '%s' to '%s'"), *ParamPin->PinName.ToString(), *ClassName);
|
||||
continue;
|
||||
}
|
||||
else if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Int)
|
||||
{
|
||||
// Ensure we're using an integer value (no decimal)
|
||||
int32 IntValue = FMath::RoundToInt(ParamValue->AsNumber());
|
||||
ParamPin->DefaultValue = FString::FromInt(IntValue);
|
||||
UE_LOG(LogTemp, Display, TEXT(" Set integer parameter '%s' to: %d (string: '%s')"),
|
||||
*ParamName, IntValue, *ParamPin->DefaultValue);
|
||||
}
|
||||
else if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Float)
|
||||
{
|
||||
// For other numeric types
|
||||
float FloatValue = ParamValue->AsNumber();
|
||||
ParamPin->DefaultValue = FString::SanitizeFloat(FloatValue);
|
||||
UE_LOG(LogTemp, Display, TEXT(" Set float parameter '%s' to: %f (string: '%s')"),
|
||||
*ParamName, FloatValue, *ParamPin->DefaultValue);
|
||||
}
|
||||
else if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean)
|
||||
{
|
||||
bool BoolValue = ParamValue->AsBool();
|
||||
ParamPin->DefaultValue = BoolValue ? TEXT("true") : TEXT("false");
|
||||
UE_LOG(LogTemp, Display, TEXT(" Set boolean parameter '%s' to: %s"),
|
||||
*ParamName, *ParamPin->DefaultValue);
|
||||
}
|
||||
else if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct && ParamPin->PinType.PinSubCategoryObject == TBaseStructure<FVector>::Get())
|
||||
{
|
||||
// Handle array parameters - like Vector parameters
|
||||
const TArray<TSharedPtr<FJsonValue>>* ArrayValue;
|
||||
if (ParamValue->TryGetArray(ArrayValue))
|
||||
{
|
||||
// Check if this could be a vector (array of 3 numbers)
|
||||
if (ArrayValue->Num() == 3)
|
||||
{
|
||||
// Create a proper vector string: (X=0.0,Y=0.0,Z=1000.0)
|
||||
float X = (*ArrayValue)[0]->AsNumber();
|
||||
float Y = (*ArrayValue)[1]->AsNumber();
|
||||
float Z = (*ArrayValue)[2]->AsNumber();
|
||||
|
||||
FString VectorString = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), X, Y, Z);
|
||||
ParamPin->DefaultValue = VectorString;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT(" Set vector parameter '%s' to: %s"),
|
||||
*ParamName, *VectorString);
|
||||
UE_LOG(LogTemp, Display, TEXT(" Final pin value: '%s'"),
|
||||
*ParamPin->DefaultValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Array parameter type not fully supported yet"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ParamValue->Type == EJson::Number)
|
||||
{
|
||||
// Handle integer vs float parameters correctly
|
||||
if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Int)
|
||||
{
|
||||
// Ensure we're using an integer value (no decimal)
|
||||
int32 IntValue = FMath::RoundToInt(ParamValue->AsNumber());
|
||||
ParamPin->DefaultValue = FString::FromInt(IntValue);
|
||||
UE_LOG(LogTemp, Display, TEXT(" Set integer parameter '%s' to: %d (string: '%s')"),
|
||||
*ParamName, IntValue, *ParamPin->DefaultValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For other numeric types
|
||||
float FloatValue = ParamValue->AsNumber();
|
||||
ParamPin->DefaultValue = FString::SanitizeFloat(FloatValue);
|
||||
UE_LOG(LogTemp, Display, TEXT(" Set float parameter '%s' to: %f (string: '%s')"),
|
||||
*ParamName, FloatValue, *ParamPin->DefaultValue);
|
||||
}
|
||||
}
|
||||
else if (ParamValue->Type == EJson::Boolean)
|
||||
{
|
||||
bool BoolValue = ParamValue->AsBool();
|
||||
ParamPin->DefaultValue = BoolValue ? TEXT("true") : TEXT("false");
|
||||
UE_LOG(LogTemp, Display, TEXT(" Set boolean parameter '%s' to: %s"),
|
||||
*ParamName, *ParamPin->DefaultValue);
|
||||
}
|
||||
else if (ParamValue->Type == EJson::Array)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT(" Processing array parameter '%s'"), *ParamName);
|
||||
// Handle array parameters - like Vector parameters
|
||||
const TArray<TSharedPtr<FJsonValue>>* ArrayValue;
|
||||
if (ParamValue->TryGetArray(ArrayValue))
|
||||
{
|
||||
// Check if this could be a vector (array of 3 numbers)
|
||||
if (ArrayValue->Num() == 3 &&
|
||||
(ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct) &&
|
||||
(ParamPin->PinType.PinSubCategoryObject == TBaseStructure<FVector>::Get()))
|
||||
{
|
||||
// Create a proper vector string: (X=0.0,Y=0.0,Z=1000.0)
|
||||
float X = (*ArrayValue)[0]->AsNumber();
|
||||
float Y = (*ArrayValue)[1]->AsNumber();
|
||||
float Z = (*ArrayValue)[2]->AsNumber();
|
||||
|
||||
FString VectorString = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), X, Y, Z);
|
||||
ParamPin->DefaultValue = VectorString;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT(" Set vector parameter '%s' to: %s"),
|
||||
*ParamName, *VectorString);
|
||||
UE_LOG(LogTemp, Display, TEXT(" Final pin value: '%s'"),
|
||||
*ParamPin->DefaultValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Array parameter type not fully supported yet"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add handling for other types as needed
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Parameter pin '%s' not found"), *ParamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the blueprint as modified
|
||||
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("node_id"), FunctionNode->NodeGuid.ToString());
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintVariable(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString VariableName;
|
||||
if (!Params->TryGetStringField(TEXT("variable_name"), VariableName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'variable_name' parameter"));
|
||||
}
|
||||
|
||||
FString VariableType;
|
||||
if (!Params->TryGetStringField(TEXT("variable_type"), VariableType))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'variable_type' parameter"));
|
||||
}
|
||||
|
||||
// Get optional parameters
|
||||
bool IsExposed = false;
|
||||
if (Params->HasField(TEXT("is_exposed")))
|
||||
{
|
||||
IsExposed = Params->GetBoolField(TEXT("is_exposed"));
|
||||
}
|
||||
|
||||
// Find the blueprint
|
||||
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Create variable based on type
|
||||
FEdGraphPinType PinType;
|
||||
|
||||
// Set up pin type based on variable_type string
|
||||
if (VariableType == TEXT("Boolean"))
|
||||
{
|
||||
PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
|
||||
}
|
||||
else if (VariableType == TEXT("Integer") || VariableType == TEXT("Int"))
|
||||
{
|
||||
PinType.PinCategory = UEdGraphSchema_K2::PC_Int;
|
||||
}
|
||||
else if (VariableType == TEXT("Float"))
|
||||
{
|
||||
PinType.PinCategory = UEdGraphSchema_K2::PC_Float;
|
||||
}
|
||||
else if (VariableType == TEXT("String"))
|
||||
{
|
||||
PinType.PinCategory = UEdGraphSchema_K2::PC_String;
|
||||
}
|
||||
else if (VariableType == TEXT("Vector"))
|
||||
{
|
||||
PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
|
||||
PinType.PinSubCategoryObject = TBaseStructure<FVector>::Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unsupported variable type: %s"), *VariableType));
|
||||
}
|
||||
|
||||
// Create the variable
|
||||
FBlueprintEditorUtils::AddMemberVariable(Blueprint, FName(*VariableName), PinType);
|
||||
|
||||
// Set variable properties
|
||||
FBPVariableDescription* NewVar = nullptr;
|
||||
for (FBPVariableDescription& Variable : Blueprint->NewVariables)
|
||||
{
|
||||
if (Variable.VarName == FName(*VariableName))
|
||||
{
|
||||
NewVar = &Variable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (NewVar)
|
||||
{
|
||||
// Set exposure in editor
|
||||
if (IsExposed)
|
||||
{
|
||||
NewVar->PropertyFlags |= CPF_Edit;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the blueprint as modified
|
||||
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("variable_name"), VariableName);
|
||||
ResultObj->SetStringField(TEXT("variable_type"), VariableType);
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintInputActionNode(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString ActionName;
|
||||
if (!Params->TryGetStringField(TEXT("action_name"), ActionName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'action_name' parameter"));
|
||||
}
|
||||
|
||||
// Get position parameters (optional)
|
||||
FVector2D NodePosition(0.0f, 0.0f);
|
||||
if (Params->HasField(TEXT("node_position")))
|
||||
{
|
||||
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
|
||||
}
|
||||
|
||||
// Find the blueprint
|
||||
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get the event graph
|
||||
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
|
||||
if (!EventGraph)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
|
||||
}
|
||||
|
||||
// Create the input action node
|
||||
UK2Node_InputAction* InputActionNode = FUnrealMCPCommonUtils::CreateInputActionNode(EventGraph, ActionName, NodePosition);
|
||||
if (!InputActionNode)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create input action node"));
|
||||
}
|
||||
|
||||
// Mark the blueprint as modified
|
||||
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("node_id"), InputActionNode->NodeGuid.ToString());
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintSelfReference(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
// Get position parameters (optional)
|
||||
FVector2D NodePosition(0.0f, 0.0f);
|
||||
if (Params->HasField(TEXT("node_position")))
|
||||
{
|
||||
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
|
||||
}
|
||||
|
||||
// Find the blueprint
|
||||
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get the event graph
|
||||
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
|
||||
if (!EventGraph)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
|
||||
}
|
||||
|
||||
// Create the self node
|
||||
UK2Node_Self* SelfNode = FUnrealMCPCommonUtils::CreateSelfReferenceNode(EventGraph, NodePosition);
|
||||
if (!SelfNode)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create self node"));
|
||||
}
|
||||
|
||||
// Mark the blueprint as modified
|
||||
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("node_id"), SelfNode->NodeGuid.ToString());
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleFindBlueprintNodes(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString NodeType;
|
||||
if (!Params->TryGetStringField(TEXT("node_type"), NodeType))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'node_type' parameter"));
|
||||
}
|
||||
|
||||
// Find the blueprint
|
||||
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get the event graph
|
||||
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
|
||||
if (!EventGraph)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
|
||||
}
|
||||
|
||||
// Create a JSON array for the node GUIDs
|
||||
TArray<TSharedPtr<FJsonValue>> NodeGuidArray;
|
||||
|
||||
// Filter nodes by the exact requested type
|
||||
if (NodeType == TEXT("Event"))
|
||||
{
|
||||
FString EventName;
|
||||
if (!Params->TryGetStringField(TEXT("event_name"), EventName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'event_name' parameter for Event node search"));
|
||||
}
|
||||
|
||||
// Look for nodes with exact event name (e.g., ReceiveBeginPlay)
|
||||
for (UEdGraphNode* Node : EventGraph->Nodes)
|
||||
{
|
||||
UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node);
|
||||
if (EventNode && EventNode->EventReference.GetMemberName() == FName(*EventName))
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Found event node with name %s: %s"), *EventName, *EventNode->NodeGuid.ToString());
|
||||
NodeGuidArray.Add(MakeShared<FJsonValueString>(EventNode->NodeGuid.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add other node types as needed (InputAction, etc.)
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetArrayField(TEXT("node_guids"), NodeGuidArray);
|
||||
|
||||
return ResultObj;
|
||||
}
|
||||
@ -0,0 +1,709 @@
|
||||
#include "Commands/UnrealMCPCommonUtils.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "K2Node_Event.h"
|
||||
#include "K2Node_CallFunction.h"
|
||||
#include "K2Node_VariableGet.h"
|
||||
#include "K2Node_VariableSet.h"
|
||||
#include "K2Node_InputAction.h"
|
||||
#include "K2Node_Self.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "Kismet2/BlueprintEditorUtils.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Components/LightComponent.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "Components/SceneComponent.h"
|
||||
#include "UObject/UObjectIterator.h"
|
||||
#include "Engine/Selection.h"
|
||||
#include "EditorAssetLibrary.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "BlueprintNodeSpawner.h"
|
||||
#include "BlueprintActionDatabase.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Dom/JsonValue.h"
|
||||
|
||||
// JSON Utilities
|
||||
TSharedPtr<FJsonObject> FUnrealMCPCommonUtils::CreateErrorResponse(const FString& Message)
|
||||
{
|
||||
TSharedPtr<FJsonObject> ResponseObject = MakeShared<FJsonObject>();
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), Message);
|
||||
return ResponseObject;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPCommonUtils::CreateSuccessResponse(const TSharedPtr<FJsonObject>& Data)
|
||||
{
|
||||
TSharedPtr<FJsonObject> ResponseObject = MakeShared<FJsonObject>();
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
|
||||
if (Data.IsValid())
|
||||
{
|
||||
ResponseObject->SetObjectField(TEXT("data"), Data);
|
||||
}
|
||||
|
||||
return ResponseObject;
|
||||
}
|
||||
|
||||
void FUnrealMCPCommonUtils::GetIntArrayFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName, TArray<int32>& OutArray)
|
||||
{
|
||||
OutArray.Reset();
|
||||
|
||||
if (!JsonObject->HasField(FieldName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
|
||||
if (JsonObject->TryGetArrayField(FieldName, JsonArray))
|
||||
{
|
||||
for (const TSharedPtr<FJsonValue>& Value : *JsonArray)
|
||||
{
|
||||
OutArray.Add((int32)Value->AsNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FUnrealMCPCommonUtils::GetFloatArrayFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName, TArray<float>& OutArray)
|
||||
{
|
||||
OutArray.Reset();
|
||||
|
||||
if (!JsonObject->HasField(FieldName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
|
||||
if (JsonObject->TryGetArrayField(FieldName, JsonArray))
|
||||
{
|
||||
for (const TSharedPtr<FJsonValue>& Value : *JsonArray)
|
||||
{
|
||||
OutArray.Add((float)Value->AsNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FVector2D FUnrealMCPCommonUtils::GetVector2DFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName)
|
||||
{
|
||||
FVector2D Result(0.0f, 0.0f);
|
||||
|
||||
if (!JsonObject->HasField(FieldName))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
|
||||
if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 2)
|
||||
{
|
||||
Result.X = (float)(*JsonArray)[0]->AsNumber();
|
||||
Result.Y = (float)(*JsonArray)[1]->AsNumber();
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
FVector FUnrealMCPCommonUtils::GetVectorFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName)
|
||||
{
|
||||
FVector Result(0.0f, 0.0f, 0.0f);
|
||||
|
||||
if (!JsonObject->HasField(FieldName))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
|
||||
if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 3)
|
||||
{
|
||||
Result.X = (float)(*JsonArray)[0]->AsNumber();
|
||||
Result.Y = (float)(*JsonArray)[1]->AsNumber();
|
||||
Result.Z = (float)(*JsonArray)[2]->AsNumber();
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
FRotator FUnrealMCPCommonUtils::GetRotatorFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName)
|
||||
{
|
||||
FRotator Result(0.0f, 0.0f, 0.0f);
|
||||
|
||||
if (!JsonObject->HasField(FieldName))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
|
||||
if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 3)
|
||||
{
|
||||
Result.Pitch = (float)(*JsonArray)[0]->AsNumber();
|
||||
Result.Yaw = (float)(*JsonArray)[1]->AsNumber();
|
||||
Result.Roll = (float)(*JsonArray)[2]->AsNumber();
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Blueprint Utilities
|
||||
UBlueprint* FUnrealMCPCommonUtils::FindBlueprint(const FString& BlueprintName)
|
||||
{
|
||||
return FindBlueprintByName(BlueprintName);
|
||||
}
|
||||
|
||||
UBlueprint* FUnrealMCPCommonUtils::FindBlueprintByName(const FString& BlueprintName)
|
||||
{
|
||||
FString AssetPath = TEXT("/Game/Blueprints/") + BlueprintName;
|
||||
return LoadObject<UBlueprint>(nullptr, *AssetPath);
|
||||
}
|
||||
|
||||
UEdGraph* FUnrealMCPCommonUtils::FindOrCreateEventGraph(UBlueprint* Blueprint)
|
||||
{
|
||||
if (!Blueprint)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Try to find the event graph
|
||||
for (UEdGraph* Graph : Blueprint->UbergraphPages)
|
||||
{
|
||||
if (Graph->GetName().Contains(TEXT("EventGraph")))
|
||||
{
|
||||
return Graph;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new event graph if none exists
|
||||
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FName(TEXT("EventGraph")), UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
||||
FBlueprintEditorUtils::AddUbergraphPage(Blueprint, NewGraph);
|
||||
return NewGraph;
|
||||
}
|
||||
|
||||
// Blueprint node utilities
|
||||
UK2Node_Event* FUnrealMCPCommonUtils::CreateEventNode(UEdGraph* Graph, const FString& EventName, const FVector2D& Position)
|
||||
{
|
||||
if (!Graph)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check for existing event node with this exact name
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node);
|
||||
if (EventNode && EventNode->EventReference.GetMemberName() == FName(*EventName))
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Using existing event node with name %s (ID: %s)"),
|
||||
*EventName, *EventNode->NodeGuid.ToString());
|
||||
return EventNode;
|
||||
}
|
||||
}
|
||||
|
||||
// No existing node found, create a new one
|
||||
UK2Node_Event* EventNode = nullptr;
|
||||
|
||||
// Find the function to create the event
|
||||
UClass* BlueprintClass = Blueprint->GeneratedClass;
|
||||
UFunction* EventFunction = BlueprintClass->FindFunctionByName(FName(*EventName));
|
||||
|
||||
if (EventFunction)
|
||||
{
|
||||
EventNode = NewObject<UK2Node_Event>(Graph);
|
||||
EventNode->EventReference.SetExternalMember(FName(*EventName), BlueprintClass);
|
||||
EventNode->NodePosX = Position.X;
|
||||
EventNode->NodePosY = Position.Y;
|
||||
Graph->AddNode(EventNode, true);
|
||||
EventNode->PostPlacedNewNode();
|
||||
EventNode->AllocateDefaultPins();
|
||||
UE_LOG(LogTemp, Display, TEXT("Created new event node with name %s (ID: %s)"),
|
||||
*EventName, *EventNode->NodeGuid.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to find function for event name: %s"), *EventName);
|
||||
}
|
||||
|
||||
return EventNode;
|
||||
}
|
||||
|
||||
UK2Node_CallFunction* FUnrealMCPCommonUtils::CreateFunctionCallNode(UEdGraph* Graph, UFunction* Function, const FVector2D& Position)
|
||||
{
|
||||
if (!Graph || !Function)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UK2Node_CallFunction* FunctionNode = NewObject<UK2Node_CallFunction>(Graph);
|
||||
FunctionNode->SetFromFunction(Function);
|
||||
FunctionNode->NodePosX = Position.X;
|
||||
FunctionNode->NodePosY = Position.Y;
|
||||
Graph->AddNode(FunctionNode, true);
|
||||
FunctionNode->CreateNewGuid();
|
||||
FunctionNode->PostPlacedNewNode();
|
||||
FunctionNode->AllocateDefaultPins();
|
||||
|
||||
return FunctionNode;
|
||||
}
|
||||
|
||||
UK2Node_VariableGet* FUnrealMCPCommonUtils::CreateVariableGetNode(UEdGraph* Graph, UBlueprint* Blueprint, const FString& VariableName, const FVector2D& Position)
|
||||
{
|
||||
if (!Graph || !Blueprint)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UK2Node_VariableGet* VariableGetNode = NewObject<UK2Node_VariableGet>(Graph);
|
||||
|
||||
FName VarName(*VariableName);
|
||||
FProperty* Property = FindFProperty<FProperty>(Blueprint->GeneratedClass, VarName);
|
||||
|
||||
if (Property)
|
||||
{
|
||||
VariableGetNode->VariableReference.SetFromField<FProperty>(Property, false);
|
||||
VariableGetNode->NodePosX = Position.X;
|
||||
VariableGetNode->NodePosY = Position.Y;
|
||||
Graph->AddNode(VariableGetNode, true);
|
||||
VariableGetNode->PostPlacedNewNode();
|
||||
VariableGetNode->AllocateDefaultPins();
|
||||
|
||||
return VariableGetNode;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UK2Node_VariableSet* FUnrealMCPCommonUtils::CreateVariableSetNode(UEdGraph* Graph, UBlueprint* Blueprint, const FString& VariableName, const FVector2D& Position)
|
||||
{
|
||||
if (!Graph || !Blueprint)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UK2Node_VariableSet* VariableSetNode = NewObject<UK2Node_VariableSet>(Graph);
|
||||
|
||||
FName VarName(*VariableName);
|
||||
FProperty* Property = FindFProperty<FProperty>(Blueprint->GeneratedClass, VarName);
|
||||
|
||||
if (Property)
|
||||
{
|
||||
VariableSetNode->VariableReference.SetFromField<FProperty>(Property, false);
|
||||
VariableSetNode->NodePosX = Position.X;
|
||||
VariableSetNode->NodePosY = Position.Y;
|
||||
Graph->AddNode(VariableSetNode, true);
|
||||
VariableSetNode->PostPlacedNewNode();
|
||||
VariableSetNode->AllocateDefaultPins();
|
||||
|
||||
return VariableSetNode;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UK2Node_InputAction* FUnrealMCPCommonUtils::CreateInputActionNode(UEdGraph* Graph, const FString& ActionName, const FVector2D& Position)
|
||||
{
|
||||
if (!Graph)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UK2Node_InputAction* InputActionNode = NewObject<UK2Node_InputAction>(Graph);
|
||||
InputActionNode->InputActionName = FName(*ActionName);
|
||||
InputActionNode->NodePosX = Position.X;
|
||||
InputActionNode->NodePosY = Position.Y;
|
||||
Graph->AddNode(InputActionNode, true);
|
||||
InputActionNode->CreateNewGuid();
|
||||
InputActionNode->PostPlacedNewNode();
|
||||
InputActionNode->AllocateDefaultPins();
|
||||
|
||||
return InputActionNode;
|
||||
}
|
||||
|
||||
UK2Node_Self* FUnrealMCPCommonUtils::CreateSelfReferenceNode(UEdGraph* Graph, const FVector2D& Position)
|
||||
{
|
||||
if (!Graph)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UK2Node_Self* SelfNode = NewObject<UK2Node_Self>(Graph);
|
||||
SelfNode->NodePosX = Position.X;
|
||||
SelfNode->NodePosY = Position.Y;
|
||||
Graph->AddNode(SelfNode, true);
|
||||
SelfNode->CreateNewGuid();
|
||||
SelfNode->PostPlacedNewNode();
|
||||
SelfNode->AllocateDefaultPins();
|
||||
|
||||
return SelfNode;
|
||||
}
|
||||
|
||||
bool FUnrealMCPCommonUtils::ConnectGraphNodes(UEdGraph* Graph, UEdGraphNode* SourceNode, const FString& SourcePinName,
|
||||
UEdGraphNode* TargetNode, const FString& TargetPinName)
|
||||
{
|
||||
if (!Graph || !SourceNode || !TargetNode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UEdGraphPin* SourcePin = FindPin(SourceNode, SourcePinName, EGPD_Output);
|
||||
UEdGraphPin* TargetPin = FindPin(TargetNode, TargetPinName, EGPD_Input);
|
||||
|
||||
if (SourcePin && TargetPin)
|
||||
{
|
||||
SourcePin->MakeLinkTo(TargetPin);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
UEdGraphPin* FUnrealMCPCommonUtils::FindPin(UEdGraphNode* Node, const FString& PinName, EEdGraphPinDirection Direction)
|
||||
{
|
||||
if (!Node)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Log all pins for debugging
|
||||
UE_LOG(LogTemp, Display, TEXT("FindPin: Looking for pin '%s' (Direction: %d) in node '%s'"),
|
||||
*PinName, (int32)Direction, *Node->GetName());
|
||||
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT(" - Available pin: '%s', Direction: %d, Category: %s"),
|
||||
*Pin->PinName.ToString(), (int32)Pin->Direction, *Pin->PinType.PinCategory.ToString());
|
||||
}
|
||||
|
||||
// First try exact match
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin->PinName.ToString() == PinName && (Direction == EGPD_MAX || Pin->Direction == Direction))
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT(" - Found exact matching pin: '%s'"), *Pin->PinName.ToString());
|
||||
return Pin;
|
||||
}
|
||||
}
|
||||
|
||||
// If no exact match and we're looking for a component reference, try case-insensitive match
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin->PinName.ToString().Equals(PinName, ESearchCase::IgnoreCase) &&
|
||||
(Direction == EGPD_MAX || Pin->Direction == Direction))
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT(" - Found case-insensitive matching pin: '%s'"), *Pin->PinName.ToString());
|
||||
return Pin;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're looking for a component output and didn't find it by name, try to find the first data output pin
|
||||
if (Direction == EGPD_Output && Cast<UK2Node_VariableGet>(Node) != nullptr)
|
||||
{
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT(" - Found fallback data output pin: '%s'"), *Pin->PinName.ToString());
|
||||
return Pin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Warning, TEXT(" - No matching pin found for '%s'"), *PinName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Actor utilities
|
||||
TSharedPtr<FJsonValue> FUnrealMCPCommonUtils::ActorToJson(AActor* Actor)
|
||||
{
|
||||
if (!Actor)
|
||||
{
|
||||
return MakeShared<FJsonValueNull>();
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> ActorObject = MakeShared<FJsonObject>();
|
||||
ActorObject->SetStringField(TEXT("name"), Actor->GetName());
|
||||
ActorObject->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
|
||||
|
||||
FVector Location = Actor->GetActorLocation();
|
||||
TArray<TSharedPtr<FJsonValue>> LocationArray;
|
||||
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.X));
|
||||
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.Y));
|
||||
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.Z));
|
||||
ActorObject->SetArrayField(TEXT("location"), LocationArray);
|
||||
|
||||
FRotator Rotation = Actor->GetActorRotation();
|
||||
TArray<TSharedPtr<FJsonValue>> RotationArray;
|
||||
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Pitch));
|
||||
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Yaw));
|
||||
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Roll));
|
||||
ActorObject->SetArrayField(TEXT("rotation"), RotationArray);
|
||||
|
||||
FVector Scale = Actor->GetActorScale3D();
|
||||
TArray<TSharedPtr<FJsonValue>> ScaleArray;
|
||||
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.X));
|
||||
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Y));
|
||||
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Z));
|
||||
ActorObject->SetArrayField(TEXT("scale"), ScaleArray);
|
||||
|
||||
return MakeShared<FJsonValueObject>(ActorObject);
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPCommonUtils::ActorToJsonObject(AActor* Actor, bool bDetailed)
|
||||
{
|
||||
if (!Actor)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> ActorObject = MakeShared<FJsonObject>();
|
||||
ActorObject->SetStringField(TEXT("name"), Actor->GetName());
|
||||
ActorObject->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
|
||||
|
||||
FVector Location = Actor->GetActorLocation();
|
||||
TArray<TSharedPtr<FJsonValue>> LocationArray;
|
||||
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.X));
|
||||
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.Y));
|
||||
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.Z));
|
||||
ActorObject->SetArrayField(TEXT("location"), LocationArray);
|
||||
|
||||
FRotator Rotation = Actor->GetActorRotation();
|
||||
TArray<TSharedPtr<FJsonValue>> RotationArray;
|
||||
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Pitch));
|
||||
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Yaw));
|
||||
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Roll));
|
||||
ActorObject->SetArrayField(TEXT("rotation"), RotationArray);
|
||||
|
||||
FVector Scale = Actor->GetActorScale3D();
|
||||
TArray<TSharedPtr<FJsonValue>> ScaleArray;
|
||||
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.X));
|
||||
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Y));
|
||||
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Z));
|
||||
ActorObject->SetArrayField(TEXT("scale"), ScaleArray);
|
||||
|
||||
return ActorObject;
|
||||
}
|
||||
|
||||
UK2Node_Event* FUnrealMCPCommonUtils::FindExistingEventNode(UEdGraph* Graph, const FString& EventName)
|
||||
{
|
||||
if (!Graph)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Look for existing event nodes
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node);
|
||||
if (EventNode && EventNode->EventReference.GetMemberName() == FName(*EventName))
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Found existing event node with name: %s"), *EventName);
|
||||
return EventNode;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool FUnrealMCPCommonUtils::SetObjectProperty(UObject* Object, const FString& PropertyName,
|
||||
const TSharedPtr<FJsonValue>& Value, FString& OutErrorMessage)
|
||||
{
|
||||
if (!Object)
|
||||
{
|
||||
OutErrorMessage = TEXT("Invalid object");
|
||||
return false;
|
||||
}
|
||||
|
||||
FProperty* Property = Object->GetClass()->FindPropertyByName(*PropertyName);
|
||||
if (!Property)
|
||||
{
|
||||
OutErrorMessage = FString::Printf(TEXT("Property not found: %s"), *PropertyName);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* PropertyAddr = Property->ContainerPtrToValuePtr<void>(Object);
|
||||
|
||||
// Handle different property types
|
||||
if (Property->IsA<FBoolProperty>())
|
||||
{
|
||||
((FBoolProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsBool());
|
||||
return true;
|
||||
}
|
||||
else if (Property->IsA<FIntProperty>())
|
||||
{
|
||||
int32 IntValue = static_cast<int32>(Value->AsNumber());
|
||||
FIntProperty* IntProperty = CastField<FIntProperty>(Property);
|
||||
if (IntProperty)
|
||||
{
|
||||
IntProperty->SetPropertyValue_InContainer(Object, IntValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Property->IsA<FFloatProperty>())
|
||||
{
|
||||
((FFloatProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsNumber());
|
||||
return true;
|
||||
}
|
||||
else if (Property->IsA<FStrProperty>())
|
||||
{
|
||||
((FStrProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsString());
|
||||
return true;
|
||||
}
|
||||
else if (Property->IsA<FByteProperty>())
|
||||
{
|
||||
FByteProperty* ByteProp = CastField<FByteProperty>(Property);
|
||||
UEnum* EnumDef = ByteProp ? ByteProp->GetIntPropertyEnum() : nullptr;
|
||||
|
||||
// If this is a TEnumAsByte property (has associated enum)
|
||||
if (EnumDef)
|
||||
{
|
||||
// Handle numeric value
|
||||
if (Value->Type == EJson::Number)
|
||||
{
|
||||
uint8 ByteValue = static_cast<uint8>(Value->AsNumber());
|
||||
ByteProp->SetPropertyValue(PropertyAddr, ByteValue);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to numeric value: %d"),
|
||||
*PropertyName, ByteValue);
|
||||
return true;
|
||||
}
|
||||
// Handle string enum value
|
||||
else if (Value->Type == EJson::String)
|
||||
{
|
||||
FString EnumValueName = Value->AsString();
|
||||
|
||||
// Try to convert numeric string to number first
|
||||
if (EnumValueName.IsNumeric())
|
||||
{
|
||||
uint8 ByteValue = FCString::Atoi(*EnumValueName);
|
||||
ByteProp->SetPropertyValue(PropertyAddr, ByteValue);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to numeric string value: %s -> %d"),
|
||||
*PropertyName, *EnumValueName, ByteValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle qualified enum names (e.g., "Player0" or "EAutoReceiveInput::Player0")
|
||||
if (EnumValueName.Contains(TEXT("::")))
|
||||
{
|
||||
EnumValueName.Split(TEXT("::"), nullptr, &EnumValueName);
|
||||
}
|
||||
|
||||
int64 EnumValue = EnumDef->GetValueByNameString(EnumValueName);
|
||||
if (EnumValue == INDEX_NONE)
|
||||
{
|
||||
// Try with full name as fallback
|
||||
EnumValue = EnumDef->GetValueByNameString(Value->AsString());
|
||||
}
|
||||
|
||||
if (EnumValue != INDEX_NONE)
|
||||
{
|
||||
ByteProp->SetPropertyValue(PropertyAddr, static_cast<uint8>(EnumValue));
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to name value: %s -> %lld"),
|
||||
*PropertyName, *EnumValueName, EnumValue);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log all possible enum values for debugging
|
||||
UE_LOG(LogTemp, Warning, TEXT("Could not find enum value for '%s'. Available options:"), *EnumValueName);
|
||||
for (int32 i = 0; i < EnumDef->NumEnums(); i++)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT(" - %s (value: %d)"),
|
||||
*EnumDef->GetNameStringByIndex(i), EnumDef->GetValueByIndex(i));
|
||||
}
|
||||
|
||||
OutErrorMessage = FString::Printf(TEXT("Could not find enum value for '%s'"), *EnumValueName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular byte property
|
||||
uint8 ByteValue = static_cast<uint8>(Value->AsNumber());
|
||||
ByteProp->SetPropertyValue(PropertyAddr, ByteValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Property->IsA<FEnumProperty>())
|
||||
{
|
||||
FEnumProperty* EnumProp = CastField<FEnumProperty>(Property);
|
||||
UEnum* EnumDef = EnumProp ? EnumProp->GetEnum() : nullptr;
|
||||
FNumericProperty* UnderlyingNumericProp = EnumProp ? EnumProp->GetUnderlyingProperty() : nullptr;
|
||||
|
||||
if (EnumDef && UnderlyingNumericProp)
|
||||
{
|
||||
// Handle numeric value
|
||||
if (Value->Type == EJson::Number)
|
||||
{
|
||||
int64 EnumValue = static_cast<int64>(Value->AsNumber());
|
||||
UnderlyingNumericProp->SetIntPropertyValue(PropertyAddr, EnumValue);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to numeric value: %lld"),
|
||||
*PropertyName, EnumValue);
|
||||
return true;
|
||||
}
|
||||
// Handle string enum value
|
||||
else if (Value->Type == EJson::String)
|
||||
{
|
||||
FString EnumValueName = Value->AsString();
|
||||
|
||||
// Try to convert numeric string to number first
|
||||
if (EnumValueName.IsNumeric())
|
||||
{
|
||||
int64 EnumValue = FCString::Atoi64(*EnumValueName);
|
||||
UnderlyingNumericProp->SetIntPropertyValue(PropertyAddr, EnumValue);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to numeric string value: %s -> %lld"),
|
||||
*PropertyName, *EnumValueName, EnumValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle qualified enum names
|
||||
if (EnumValueName.Contains(TEXT("::")))
|
||||
{
|
||||
EnumValueName.Split(TEXT("::"), nullptr, &EnumValueName);
|
||||
}
|
||||
|
||||
int64 EnumValue = EnumDef->GetValueByNameString(EnumValueName);
|
||||
if (EnumValue == INDEX_NONE)
|
||||
{
|
||||
// Try with full name as fallback
|
||||
EnumValue = EnumDef->GetValueByNameString(Value->AsString());
|
||||
}
|
||||
|
||||
if (EnumValue != INDEX_NONE)
|
||||
{
|
||||
UnderlyingNumericProp->SetIntPropertyValue(PropertyAddr, EnumValue);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to name value: %s -> %lld"),
|
||||
*PropertyName, *EnumValueName, EnumValue);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log all possible enum values for debugging
|
||||
UE_LOG(LogTemp, Warning, TEXT("Could not find enum value for '%s'. Available options:"), *EnumValueName);
|
||||
for (int32 i = 0; i < EnumDef->NumEnums(); i++)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT(" - %s (value: %d)"),
|
||||
*EnumDef->GetNameStringByIndex(i), EnumDef->GetValueByIndex(i));
|
||||
}
|
||||
|
||||
OutErrorMessage = FString::Printf(TEXT("Could not find enum value for '%s'"), *EnumValueName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutErrorMessage = FString::Printf(TEXT("Unsupported property type: %s for property %s"),
|
||||
*Property->GetClass()->GetName(), *PropertyName);
|
||||
return false;
|
||||
}
|
||||
@ -0,0 +1,600 @@
|
||||
#include "Commands/UnrealMCPEditorCommands.h"
|
||||
#include "Commands/UnrealMCPCommonUtils.h"
|
||||
#include "Editor.h"
|
||||
#include "EditorViewportClient.h"
|
||||
#include "LevelEditorViewport.h"
|
||||
#include "ImageUtils.h"
|
||||
#include "HighResScreenshot.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Engine/Selection.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Engine/StaticMeshActor.h"
|
||||
#include "Engine/DirectionalLight.h"
|
||||
#include "Engine/PointLight.h"
|
||||
#include "Engine/SpotLight.h"
|
||||
#include "Camera/CameraActor.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "EditorSubsystem.h"
|
||||
#include "Subsystems/EditorActorSubsystem.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
|
||||
FUnrealMCPEditorCommands::FUnrealMCPEditorCommands()
|
||||
{
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Actor manipulation commands
|
||||
if (CommandType == TEXT("get_actors_in_level"))
|
||||
{
|
||||
return HandleGetActorsInLevel(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("find_actors_by_name"))
|
||||
{
|
||||
return HandleFindActorsByName(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_actor") || CommandType == TEXT("create_actor"))
|
||||
{
|
||||
if (CommandType == TEXT("create_actor"))
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("'create_actor' command is deprecated and will be removed in a future version. Please use 'spawn_actor' instead."));
|
||||
}
|
||||
return HandleSpawnActor(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("delete_actor"))
|
||||
{
|
||||
return HandleDeleteActor(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("set_actor_transform"))
|
||||
{
|
||||
return HandleSetActorTransform(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("get_actor_properties"))
|
||||
{
|
||||
return HandleGetActorProperties(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("set_actor_property"))
|
||||
{
|
||||
return HandleSetActorProperty(Params);
|
||||
}
|
||||
// Blueprint actor spawning
|
||||
else if (CommandType == TEXT("spawn_blueprint_actor"))
|
||||
{
|
||||
return HandleSpawnBlueprintActor(Params);
|
||||
}
|
||||
// Editor viewport commands
|
||||
else if (CommandType == TEXT("focus_viewport"))
|
||||
{
|
||||
return HandleFocusViewport(Params);
|
||||
}
|
||||
else if (CommandType == TEXT("take_screenshot"))
|
||||
{
|
||||
return HandleTakeScreenshot(Params);
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown editor command: %s"), *CommandType));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetActorsInLevel(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
TArray<AActor*> AllActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> ActorArray;
|
||||
for (AActor* Actor : AllActors)
|
||||
{
|
||||
if (Actor)
|
||||
{
|
||||
ActorArray.Add(FUnrealMCPCommonUtils::ActorToJson(Actor));
|
||||
}
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetArrayField(TEXT("actors"), ActorArray);
|
||||
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleFindActorsByName(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
FString Pattern;
|
||||
if (!Params->TryGetStringField(TEXT("pattern"), Pattern))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'pattern' parameter"));
|
||||
}
|
||||
|
||||
TArray<AActor*> AllActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> MatchingActors;
|
||||
for (AActor* Actor : AllActors)
|
||||
{
|
||||
if (Actor && Actor->GetName().Contains(Pattern))
|
||||
{
|
||||
MatchingActors.Add(FUnrealMCPCommonUtils::ActorToJson(Actor));
|
||||
}
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetArrayField(TEXT("actors"), MatchingActors);
|
||||
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSpawnActor(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString ActorType;
|
||||
if (!Params->TryGetStringField(TEXT("type"), ActorType))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'type' parameter"));
|
||||
}
|
||||
|
||||
// Get actor name (required parameter)
|
||||
FString ActorName;
|
||||
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
||||
}
|
||||
|
||||
// Get optional transform parameters
|
||||
FVector Location(0.0f, 0.0f, 0.0f);
|
||||
FRotator Rotation(0.0f, 0.0f, 0.0f);
|
||||
FVector Scale(1.0f, 1.0f, 1.0f);
|
||||
|
||||
if (Params->HasField(TEXT("location")))
|
||||
{
|
||||
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
|
||||
}
|
||||
if (Params->HasField(TEXT("rotation")))
|
||||
{
|
||||
Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"));
|
||||
}
|
||||
if (Params->HasField(TEXT("scale")))
|
||||
{
|
||||
Scale = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"));
|
||||
}
|
||||
|
||||
// Create the actor based on type
|
||||
AActor* NewActor = nullptr;
|
||||
UWorld* World = GEditor->GetEditorWorldContext().World();
|
||||
|
||||
if (!World)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get editor world"));
|
||||
}
|
||||
|
||||
// Check if an actor with this name already exists
|
||||
TArray<AActor*> AllActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), AllActors);
|
||||
for (AActor* Actor : AllActors)
|
||||
{
|
||||
if (Actor && Actor->GetName() == ActorName)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor with name '%s' already exists"), *ActorName));
|
||||
}
|
||||
}
|
||||
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.Name = *ActorName;
|
||||
|
||||
if (ActorType == TEXT("StaticMeshActor"))
|
||||
{
|
||||
NewActor = World->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), Location, Rotation, SpawnParams);
|
||||
}
|
||||
else if (ActorType == TEXT("PointLight"))
|
||||
{
|
||||
NewActor = World->SpawnActor<APointLight>(APointLight::StaticClass(), Location, Rotation, SpawnParams);
|
||||
}
|
||||
else if (ActorType == TEXT("SpotLight"))
|
||||
{
|
||||
NewActor = World->SpawnActor<ASpotLight>(ASpotLight::StaticClass(), Location, Rotation, SpawnParams);
|
||||
}
|
||||
else if (ActorType == TEXT("DirectionalLight"))
|
||||
{
|
||||
NewActor = World->SpawnActor<ADirectionalLight>(ADirectionalLight::StaticClass(), Location, Rotation, SpawnParams);
|
||||
}
|
||||
else if (ActorType == TEXT("CameraActor"))
|
||||
{
|
||||
NewActor = World->SpawnActor<ACameraActor>(ACameraActor::StaticClass(), Location, Rotation, SpawnParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown actor type: %s"), *ActorType));
|
||||
}
|
||||
|
||||
if (NewActor)
|
||||
{
|
||||
// Set scale (since SpawnActor only takes location and rotation)
|
||||
FTransform Transform = NewActor->GetTransform();
|
||||
Transform.SetScale3D(Scale);
|
||||
NewActor->SetActorTransform(Transform);
|
||||
|
||||
// Return the created actor's details
|
||||
return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true);
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create actor"));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleDeleteActor(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
FString ActorName;
|
||||
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
||||
}
|
||||
|
||||
TArray<AActor*> AllActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
||||
|
||||
for (AActor* Actor : AllActors)
|
||||
{
|
||||
if (Actor && Actor->GetName() == ActorName)
|
||||
{
|
||||
// Store actor info before deletion for the response
|
||||
TSharedPtr<FJsonObject> ActorInfo = FUnrealMCPCommonUtils::ActorToJsonObject(Actor);
|
||||
|
||||
// Delete the actor
|
||||
Actor->Destroy();
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetObjectField(TEXT("deleted_actor"), ActorInfo);
|
||||
return ResultObj;
|
||||
}
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSetActorTransform(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get actor name
|
||||
FString ActorName;
|
||||
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
||||
}
|
||||
|
||||
// Find the actor
|
||||
AActor* TargetActor = nullptr;
|
||||
TArray<AActor*> AllActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
||||
|
||||
for (AActor* Actor : AllActors)
|
||||
{
|
||||
if (Actor && Actor->GetName() == ActorName)
|
||||
{
|
||||
TargetActor = Actor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TargetActor)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
|
||||
}
|
||||
|
||||
// Get transform parameters
|
||||
FTransform NewTransform = TargetActor->GetTransform();
|
||||
|
||||
if (Params->HasField(TEXT("location")))
|
||||
{
|
||||
NewTransform.SetLocation(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location")));
|
||||
}
|
||||
if (Params->HasField(TEXT("rotation")))
|
||||
{
|
||||
NewTransform.SetRotation(FQuat(FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"))));
|
||||
}
|
||||
if (Params->HasField(TEXT("scale")))
|
||||
{
|
||||
NewTransform.SetScale3D(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale")));
|
||||
}
|
||||
|
||||
// Set the new transform
|
||||
TargetActor->SetActorTransform(NewTransform);
|
||||
|
||||
// Return updated actor info
|
||||
return FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true);
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetActorProperties(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get actor name
|
||||
FString ActorName;
|
||||
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
||||
}
|
||||
|
||||
// Find the actor
|
||||
AActor* TargetActor = nullptr;
|
||||
TArray<AActor*> AllActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
||||
|
||||
for (AActor* Actor : AllActors)
|
||||
{
|
||||
if (Actor && Actor->GetName() == ActorName)
|
||||
{
|
||||
TargetActor = Actor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TargetActor)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
|
||||
}
|
||||
|
||||
// Always return detailed properties for this command
|
||||
return FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true);
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSetActorProperty(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get actor name
|
||||
FString ActorName;
|
||||
if (!Params->TryGetStringField(TEXT("name"), ActorName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
||||
}
|
||||
|
||||
// Find the actor
|
||||
AActor* TargetActor = nullptr;
|
||||
TArray<AActor*> AllActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
||||
|
||||
for (AActor* Actor : AllActors)
|
||||
{
|
||||
if (Actor && Actor->GetName() == ActorName)
|
||||
{
|
||||
TargetActor = Actor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TargetActor)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
|
||||
}
|
||||
|
||||
// Get property name
|
||||
FString PropertyName;
|
||||
if (!Params->TryGetStringField(TEXT("property_name"), PropertyName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_name' parameter"));
|
||||
}
|
||||
|
||||
// Get property value
|
||||
if (!Params->HasField(TEXT("property_value")))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_value' parameter"));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonValue> PropertyValue = Params->Values.FindRef(TEXT("property_value"));
|
||||
|
||||
// Set the property using our utility function
|
||||
FString ErrorMessage;
|
||||
if (FUnrealMCPCommonUtils::SetObjectProperty(TargetActor, PropertyName, PropertyValue, ErrorMessage))
|
||||
{
|
||||
// Property set successfully
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("actor"), ActorName);
|
||||
ResultObj->SetStringField(TEXT("property"), PropertyName);
|
||||
ResultObj->SetBoolField(TEXT("success"), true);
|
||||
|
||||
// Also include the full actor details
|
||||
ResultObj->SetObjectField(TEXT("actor_details"), FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true));
|
||||
return ResultObj;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSpawnBlueprintActor(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString ActorName;
|
||||
if (!Params->TryGetStringField(TEXT("actor_name"), ActorName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'actor_name' parameter"));
|
||||
}
|
||||
|
||||
// Find the blueprint
|
||||
if (BlueprintName.IsEmpty())
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Blueprint name is empty"));
|
||||
}
|
||||
|
||||
FString Root = TEXT("/Game/Blueprints/");
|
||||
FString AssetPath = Root + BlueprintName;
|
||||
|
||||
if (!FPackageName::DoesPackageExist(AssetPath))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint '%s' not found – it must reside under /Game/Blueprints"), *BlueprintName));
|
||||
}
|
||||
|
||||
UBlueprint* Blueprint = LoadObject<UBlueprint>(nullptr, *AssetPath);
|
||||
if (!Blueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get transform parameters
|
||||
FVector Location(0.0f, 0.0f, 0.0f);
|
||||
FRotator Rotation(0.0f, 0.0f, 0.0f);
|
||||
FVector Scale(1.0f, 1.0f, 1.0f);
|
||||
|
||||
if (Params->HasField(TEXT("location")))
|
||||
{
|
||||
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
|
||||
}
|
||||
if (Params->HasField(TEXT("rotation")))
|
||||
{
|
||||
Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"));
|
||||
}
|
||||
if (Params->HasField(TEXT("scale")))
|
||||
{
|
||||
Scale = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"));
|
||||
}
|
||||
|
||||
// Spawn the actor
|
||||
UWorld* World = GEditor->GetEditorWorldContext().World();
|
||||
if (!World)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get editor world"));
|
||||
}
|
||||
|
||||
FTransform SpawnTransform;
|
||||
SpawnTransform.SetLocation(Location);
|
||||
SpawnTransform.SetRotation(FQuat(Rotation));
|
||||
SpawnTransform.SetScale3D(Scale);
|
||||
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.Name = *ActorName;
|
||||
|
||||
AActor* NewActor = World->SpawnActor<AActor>(Blueprint->GeneratedClass, SpawnTransform, SpawnParams);
|
||||
if (NewActor)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true);
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to spawn blueprint actor"));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleFocusViewport(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get target actor name if provided
|
||||
FString TargetActorName;
|
||||
bool HasTargetActor = Params->TryGetStringField(TEXT("target"), TargetActorName);
|
||||
|
||||
// Get location if provided
|
||||
FVector Location(0.0f, 0.0f, 0.0f);
|
||||
bool HasLocation = false;
|
||||
if (Params->HasField(TEXT("location")))
|
||||
{
|
||||
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
|
||||
HasLocation = true;
|
||||
}
|
||||
|
||||
// Get distance
|
||||
float Distance = 1000.0f;
|
||||
if (Params->HasField(TEXT("distance")))
|
||||
{
|
||||
Distance = Params->GetNumberField(TEXT("distance"));
|
||||
}
|
||||
|
||||
// Get orientation if provided
|
||||
FRotator Orientation(0.0f, 0.0f, 0.0f);
|
||||
bool HasOrientation = false;
|
||||
if (Params->HasField(TEXT("orientation")))
|
||||
{
|
||||
Orientation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("orientation"));
|
||||
HasOrientation = true;
|
||||
}
|
||||
|
||||
// Get the active viewport
|
||||
FLevelEditorViewportClient* ViewportClient = (FLevelEditorViewportClient*)GEditor->GetActiveViewport()->GetClient();
|
||||
if (!ViewportClient)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get active viewport"));
|
||||
}
|
||||
|
||||
// If we have a target actor, focus on it
|
||||
if (HasTargetActor)
|
||||
{
|
||||
// Find the actor
|
||||
AActor* TargetActor = nullptr;
|
||||
TArray<AActor*> AllActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
|
||||
|
||||
for (AActor* Actor : AllActors)
|
||||
{
|
||||
if (Actor && Actor->GetName() == TargetActorName)
|
||||
{
|
||||
TargetActor = Actor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TargetActor)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *TargetActorName));
|
||||
}
|
||||
|
||||
// Focus on the actor
|
||||
ViewportClient->SetViewLocation(TargetActor->GetActorLocation() - FVector(Distance, 0.0f, 0.0f));
|
||||
}
|
||||
// Otherwise use the provided location
|
||||
else if (HasLocation)
|
||||
{
|
||||
ViewportClient->SetViewLocation(Location - FVector(Distance, 0.0f, 0.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Either 'target' or 'location' must be provided"));
|
||||
}
|
||||
|
||||
// Set orientation if provided
|
||||
if (HasOrientation)
|
||||
{
|
||||
ViewportClient->SetViewRotation(Orientation);
|
||||
}
|
||||
|
||||
// Force viewport to redraw
|
||||
ViewportClient->Invalidate();
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetBoolField(TEXT("success"), true);
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleTakeScreenshot(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get file path parameter
|
||||
FString FilePath;
|
||||
if (!Params->TryGetStringField(TEXT("filepath"), FilePath))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'filepath' parameter"));
|
||||
}
|
||||
|
||||
// Ensure the file path has a proper extension
|
||||
if (!FilePath.EndsWith(TEXT(".png")))
|
||||
{
|
||||
FilePath += TEXT(".png");
|
||||
}
|
||||
|
||||
// Get the active viewport
|
||||
if (GEditor && GEditor->GetActiveViewport())
|
||||
{
|
||||
FViewport* Viewport = GEditor->GetActiveViewport();
|
||||
TArray<FColor> Bitmap;
|
||||
FIntRect ViewportRect(0, 0, Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y);
|
||||
|
||||
if (Viewport->ReadPixels(Bitmap, FReadSurfaceDataFlags(), ViewportRect))
|
||||
{
|
||||
TArray<uint8> CompressedBitmap;
|
||||
FImageUtils::CompressImageArray(Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y, Bitmap, CompressedBitmap);
|
||||
|
||||
if (FFileHelper::SaveArrayToFile(CompressedBitmap, *FilePath))
|
||||
{
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("filepath"), FilePath);
|
||||
return ResultObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot"));
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
#include "Commands/UnrealMCPProjectCommands.h"
|
||||
#include "Commands/UnrealMCPCommonUtils.h"
|
||||
#include "GameFramework/InputSettings.h"
|
||||
|
||||
FUnrealMCPProjectCommands::FUnrealMCPProjectCommands()
|
||||
{
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPProjectCommands::HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
if (CommandType == TEXT("create_input_mapping"))
|
||||
{
|
||||
return HandleCreateInputMapping(Params);
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown project command: %s"), *CommandType));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPProjectCommands::HandleCreateInputMapping(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString ActionName;
|
||||
if (!Params->TryGetStringField(TEXT("action_name"), ActionName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'action_name' parameter"));
|
||||
}
|
||||
|
||||
FString Key;
|
||||
if (!Params->TryGetStringField(TEXT("key"), Key))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'key' parameter"));
|
||||
}
|
||||
|
||||
// Get the input settings
|
||||
UInputSettings* InputSettings = GetMutableDefault<UInputSettings>();
|
||||
if (!InputSettings)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get input settings"));
|
||||
}
|
||||
|
||||
// Create the input action mapping
|
||||
FInputActionKeyMapping ActionMapping;
|
||||
ActionMapping.ActionName = FName(*ActionName);
|
||||
ActionMapping.Key = FKey(*Key);
|
||||
|
||||
// Add modifiers if provided
|
||||
if (Params->HasField(TEXT("shift")))
|
||||
{
|
||||
ActionMapping.bShift = Params->GetBoolField(TEXT("shift"));
|
||||
}
|
||||
if (Params->HasField(TEXT("ctrl")))
|
||||
{
|
||||
ActionMapping.bCtrl = Params->GetBoolField(TEXT("ctrl"));
|
||||
}
|
||||
if (Params->HasField(TEXT("alt")))
|
||||
{
|
||||
ActionMapping.bAlt = Params->GetBoolField(TEXT("alt"));
|
||||
}
|
||||
if (Params->HasField(TEXT("cmd")))
|
||||
{
|
||||
ActionMapping.bCmd = Params->GetBoolField(TEXT("cmd"));
|
||||
}
|
||||
|
||||
// Add the mapping
|
||||
InputSettings->AddActionMapping(ActionMapping);
|
||||
InputSettings->SaveConfig();
|
||||
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("action_name"), ActionName);
|
||||
ResultObj->SetStringField(TEXT("key"), Key);
|
||||
return ResultObj;
|
||||
}
|
||||
@ -0,0 +1,544 @@
|
||||
#include "Commands/UnrealMCPUMGCommands.h"
|
||||
#include "Commands/UnrealMCPCommonUtils.h"
|
||||
#include "Editor.h"
|
||||
#include "EditorAssetLibrary.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#include "WidgetBlueprint.h"
|
||||
// We'll create widgets using regular Factory classes
|
||||
#include "Factories/Factory.h"
|
||||
// Remove problematic includes that don't exist in UE 5.5
|
||||
// #include "UMGEditorSubsystem.h"
|
||||
// #include "WidgetBlueprintFactory.h"
|
||||
#include "WidgetBlueprintEditor.h"
|
||||
#include "Blueprint/WidgetTree.h"
|
||||
#include "Components/CanvasPanel.h"
|
||||
#include "Components/CanvasPanelSlot.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Kismet2/BlueprintEditorUtils.h"
|
||||
#include "Components/Button.h"
|
||||
#include "K2Node_FunctionEntry.h"
|
||||
#include "K2Node_CallFunction.h"
|
||||
#include "K2Node_VariableGet.h"
|
||||
#include "K2Node_VariableSet.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
#include "K2Node_Event.h"
|
||||
|
||||
FUnrealMCPUMGCommands::FUnrealMCPUMGCommands()
|
||||
{
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleCommand(const FString& CommandName, const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
if (CommandName == TEXT("create_umg_widget_blueprint"))
|
||||
{
|
||||
return HandleCreateUMGWidgetBlueprint(Params);
|
||||
}
|
||||
else if (CommandName == TEXT("add_text_block_to_widget"))
|
||||
{
|
||||
return HandleAddTextBlockToWidget(Params);
|
||||
}
|
||||
else if (CommandName == TEXT("add_widget_to_viewport"))
|
||||
{
|
||||
return HandleAddWidgetToViewport(Params);
|
||||
}
|
||||
else if (CommandName == TEXT("add_button_to_widget"))
|
||||
{
|
||||
return HandleAddButtonToWidget(Params);
|
||||
}
|
||||
else if (CommandName == TEXT("bind_widget_event"))
|
||||
{
|
||||
return HandleBindWidgetEvent(Params);
|
||||
}
|
||||
else if (CommandName == TEXT("set_text_block_binding"))
|
||||
{
|
||||
return HandleSetTextBlockBinding(Params);
|
||||
}
|
||||
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown UMG command: %s"), *CommandName));
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleCreateUMGWidgetBlueprint(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
|
||||
}
|
||||
|
||||
// Create the full asset path
|
||||
FString PackagePath = TEXT("/Game/Widgets/");
|
||||
FString AssetName = BlueprintName;
|
||||
FString FullPath = PackagePath + AssetName;
|
||||
|
||||
// Check if asset already exists
|
||||
if (UEditorAssetLibrary::DoesAssetExist(FullPath))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Widget Blueprint '%s' already exists"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Create package
|
||||
UPackage* Package = CreatePackage(*FullPath);
|
||||
if (!Package)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create package"));
|
||||
}
|
||||
|
||||
// Create Widget Blueprint using KismetEditorUtilities
|
||||
UBlueprint* NewBlueprint = FKismetEditorUtilities::CreateBlueprint(
|
||||
UUserWidget::StaticClass(), // Parent class
|
||||
Package, // Outer package
|
||||
FName(*AssetName), // Blueprint name
|
||||
BPTYPE_Normal, // Blueprint type
|
||||
UBlueprint::StaticClass(), // Blueprint class
|
||||
UBlueprintGeneratedClass::StaticClass(), // Generated class
|
||||
FName("CreateUMGWidget") // Creation method name
|
||||
);
|
||||
|
||||
// Make sure the Blueprint was created successfully
|
||||
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(NewBlueprint);
|
||||
if (!WidgetBlueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create Widget Blueprint"));
|
||||
}
|
||||
|
||||
// Add a default Canvas Panel if one doesn't exist
|
||||
if (!WidgetBlueprint->WidgetTree->RootWidget)
|
||||
{
|
||||
UCanvasPanel* RootCanvas = WidgetBlueprint->WidgetTree->ConstructWidget<UCanvasPanel>(UCanvasPanel::StaticClass());
|
||||
WidgetBlueprint->WidgetTree->RootWidget = RootCanvas;
|
||||
}
|
||||
|
||||
// Mark the package dirty and notify asset registry
|
||||
Package->MarkPackageDirty();
|
||||
FAssetRegistryModule::AssetCreated(WidgetBlueprint);
|
||||
|
||||
// Compile the blueprint
|
||||
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
|
||||
|
||||
// Create success response
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("name"), BlueprintName);
|
||||
ResultObj->SetStringField(TEXT("path"), FullPath);
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleAddTextBlockToWidget(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
FString WidgetName;
|
||||
if (!Params->TryGetStringField(TEXT("widget_name"), WidgetName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'widget_name' parameter"));
|
||||
}
|
||||
|
||||
// Find the Widget Blueprint
|
||||
FString FullPath = TEXT("/Game/Widgets/") + BlueprintName;
|
||||
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(FullPath));
|
||||
if (!WidgetBlueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Widget Blueprint '%s' not found"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get optional parameters
|
||||
FString InitialText = TEXT("New Text Block");
|
||||
Params->TryGetStringField(TEXT("text"), InitialText);
|
||||
|
||||
FVector2D Position(0.0f, 0.0f);
|
||||
if (Params->HasField(TEXT("position")))
|
||||
{
|
||||
const TArray<TSharedPtr<FJsonValue>>* PosArray;
|
||||
if (Params->TryGetArrayField(TEXT("position"), PosArray) && PosArray->Num() >= 2)
|
||||
{
|
||||
Position.X = (*PosArray)[0]->AsNumber();
|
||||
Position.Y = (*PosArray)[1]->AsNumber();
|
||||
}
|
||||
}
|
||||
|
||||
// Create Text Block widget
|
||||
UTextBlock* TextBlock = WidgetBlueprint->WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), *WidgetName);
|
||||
if (!TextBlock)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create Text Block widget"));
|
||||
}
|
||||
|
||||
// Set initial text
|
||||
TextBlock->SetText(FText::FromString(InitialText));
|
||||
|
||||
// Add to canvas panel
|
||||
UCanvasPanel* RootCanvas = Cast<UCanvasPanel>(WidgetBlueprint->WidgetTree->RootWidget);
|
||||
if (!RootCanvas)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Root Canvas Panel not found"));
|
||||
}
|
||||
|
||||
UCanvasPanelSlot* PanelSlot = RootCanvas->AddChildToCanvas(TextBlock);
|
||||
PanelSlot->SetPosition(Position);
|
||||
|
||||
// Mark the package dirty and compile
|
||||
WidgetBlueprint->MarkPackageDirty();
|
||||
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
|
||||
|
||||
// Create success response
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("widget_name"), WidgetName);
|
||||
ResultObj->SetStringField(TEXT("text"), InitialText);
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleAddWidgetToViewport(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
|
||||
}
|
||||
|
||||
// Find the Widget Blueprint
|
||||
FString FullPath = TEXT("/Game/Widgets/") + BlueprintName;
|
||||
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(FullPath));
|
||||
if (!WidgetBlueprint)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Widget Blueprint '%s' not found"), *BlueprintName));
|
||||
}
|
||||
|
||||
// Get optional Z-order parameter
|
||||
int32 ZOrder = 0;
|
||||
Params->TryGetNumberField(TEXT("z_order"), ZOrder);
|
||||
|
||||
// Create widget instance
|
||||
UClass* WidgetClass = WidgetBlueprint->GeneratedClass;
|
||||
if (!WidgetClass)
|
||||
{
|
||||
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get widget class"));
|
||||
}
|
||||
|
||||
// Note: This creates the widget but doesn't add it to viewport
|
||||
// The actual addition to viewport should be done through Blueprint nodes
|
||||
// as it requires a game context
|
||||
|
||||
// Create success response with instructions
|
||||
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
||||
ResultObj->SetStringField(TEXT("blueprint_name"), BlueprintName);
|
||||
ResultObj->SetStringField(TEXT("class_path"), WidgetClass->GetPathName());
|
||||
ResultObj->SetNumberField(TEXT("z_order"), ZOrder);
|
||||
ResultObj->SetStringField(TEXT("note"), TEXT("Widget class ready. Use CreateWidget and AddToViewport nodes in Blueprint to display in game."));
|
||||
return ResultObj;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleAddButtonToWidget(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
|
||||
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing blueprint_name parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
FString WidgetName;
|
||||
if (!Params->TryGetStringField(TEXT("widget_name"), WidgetName))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing widget_name parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
FString ButtonText;
|
||||
if (!Params->TryGetStringField(TEXT("text"), ButtonText))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing text parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Load the Widget Blueprint
|
||||
const FString BlueprintPath = FString::Printf(TEXT("/Game/Widgets/%s.%s"), *BlueprintName, *BlueprintName);
|
||||
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(BlueprintPath));
|
||||
if (!WidgetBlueprint)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Widget Blueprint: %s"), *BlueprintPath));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Create Button widget
|
||||
UButton* Button = NewObject<UButton>(WidgetBlueprint->GeneratedClass->GetDefaultObject(), UButton::StaticClass(), *WidgetName);
|
||||
if (!Button)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Failed to create Button widget"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Set button text
|
||||
UTextBlock* ButtonTextBlock = NewObject<UTextBlock>(Button, UTextBlock::StaticClass(), *(WidgetName + TEXT("_Text")));
|
||||
if (ButtonTextBlock)
|
||||
{
|
||||
ButtonTextBlock->SetText(FText::FromString(ButtonText));
|
||||
Button->AddChild(ButtonTextBlock);
|
||||
}
|
||||
|
||||
// Get canvas panel and add button
|
||||
UCanvasPanel* RootCanvas = Cast<UCanvasPanel>(WidgetBlueprint->WidgetTree->RootWidget);
|
||||
if (!RootCanvas)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Root widget is not a Canvas Panel"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Add to canvas and set position
|
||||
UCanvasPanelSlot* ButtonSlot = RootCanvas->AddChildToCanvas(Button);
|
||||
if (ButtonSlot)
|
||||
{
|
||||
const TArray<TSharedPtr<FJsonValue>>* Position;
|
||||
if (Params->TryGetArrayField(TEXT("position"), Position) && Position->Num() >= 2)
|
||||
{
|
||||
FVector2D Pos(
|
||||
(*Position)[0]->AsNumber(),
|
||||
(*Position)[1]->AsNumber()
|
||||
);
|
||||
ButtonSlot->SetPosition(Pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the Widget Blueprint
|
||||
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
|
||||
UEditorAssetLibrary::SaveAsset(BlueprintPath, false);
|
||||
|
||||
Response->SetBoolField(TEXT("success"), true);
|
||||
Response->SetStringField(TEXT("widget_name"), WidgetName);
|
||||
return Response;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleBindWidgetEvent(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
|
||||
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing blueprint_name parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
FString WidgetName;
|
||||
if (!Params->TryGetStringField(TEXT("widget_name"), WidgetName))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing widget_name parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
FString EventName;
|
||||
if (!Params->TryGetStringField(TEXT("event_name"), EventName))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing event_name parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Load the Widget Blueprint
|
||||
const FString BlueprintPath = FString::Printf(TEXT("/Game/Widgets/%s.%s"), *BlueprintName, *BlueprintName);
|
||||
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(BlueprintPath));
|
||||
if (!WidgetBlueprint)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Widget Blueprint: %s"), *BlueprintPath));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Create the event graph if it doesn't exist
|
||||
UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(WidgetBlueprint);
|
||||
if (!EventGraph)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Failed to find or create event graph"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Find the widget in the blueprint
|
||||
UWidget* Widget = WidgetBlueprint->WidgetTree->FindWidget(*WidgetName);
|
||||
if (!Widget)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to find widget: %s"), *WidgetName));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Create the event node (e.g., OnClicked for buttons)
|
||||
UK2Node_Event* EventNode = nullptr;
|
||||
|
||||
// Find existing nodes first
|
||||
TArray<UK2Node_Event*> AllEventNodes;
|
||||
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(WidgetBlueprint, AllEventNodes);
|
||||
|
||||
for (UK2Node_Event* Node : AllEventNodes)
|
||||
{
|
||||
if (Node->CustomFunctionName == FName(*EventName) && Node->EventReference.GetMemberParentClass() == Widget->GetClass())
|
||||
{
|
||||
EventNode = Node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no existing node, create a new one
|
||||
if (!EventNode)
|
||||
{
|
||||
// Calculate position - place it below existing nodes
|
||||
float MaxHeight = 0.0f;
|
||||
for (UEdGraphNode* Node : EventGraph->Nodes)
|
||||
{
|
||||
MaxHeight = FMath::Max(MaxHeight, Node->NodePosY);
|
||||
}
|
||||
|
||||
const FVector2D NodePos(200, MaxHeight + 200);
|
||||
|
||||
// Call CreateNewBoundEventForClass, which returns void, so we can't capture the return value directly
|
||||
// We'll need to find the node after creating it
|
||||
FKismetEditorUtilities::CreateNewBoundEventForClass(
|
||||
Widget->GetClass(),
|
||||
FName(*EventName),
|
||||
WidgetBlueprint,
|
||||
nullptr // We don't need a specific property binding
|
||||
);
|
||||
|
||||
// Now find the newly created node
|
||||
TArray<UK2Node_Event*> UpdatedEventNodes;
|
||||
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(WidgetBlueprint, UpdatedEventNodes);
|
||||
|
||||
for (UK2Node_Event* Node : UpdatedEventNodes)
|
||||
{
|
||||
if (Node->CustomFunctionName == FName(*EventName) && Node->EventReference.GetMemberParentClass() == Widget->GetClass())
|
||||
{
|
||||
EventNode = Node;
|
||||
|
||||
// Set position of the node
|
||||
EventNode->NodePosX = NodePos.X;
|
||||
EventNode->NodePosY = NodePos.Y;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!EventNode)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Failed to create event node"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Save the Widget Blueprint
|
||||
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
|
||||
UEditorAssetLibrary::SaveAsset(BlueprintPath, false);
|
||||
|
||||
Response->SetBoolField(TEXT("success"), true);
|
||||
Response->SetStringField(TEXT("event_name"), EventName);
|
||||
return Response;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleSetTextBlockBinding(const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
|
||||
|
||||
// Get required parameters
|
||||
FString BlueprintName;
|
||||
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing blueprint_name parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
FString WidgetName;
|
||||
if (!Params->TryGetStringField(TEXT("widget_name"), WidgetName))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing widget_name parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
FString BindingName;
|
||||
if (!Params->TryGetStringField(TEXT("binding_name"), BindingName))
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), TEXT("Missing binding_name parameter"));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Load the Widget Blueprint
|
||||
const FString BlueprintPath = FString::Printf(TEXT("/Game/Widgets/%s.%s"), *BlueprintName, *BlueprintName);
|
||||
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(BlueprintPath));
|
||||
if (!WidgetBlueprint)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Widget Blueprint: %s"), *BlueprintPath));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Create a variable for binding if it doesn't exist
|
||||
FBlueprintEditorUtils::AddMemberVariable(
|
||||
WidgetBlueprint,
|
||||
FName(*BindingName),
|
||||
FEdGraphPinType(UEdGraphSchema_K2::PC_Text, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType())
|
||||
);
|
||||
|
||||
// Find the TextBlock widget
|
||||
UTextBlock* TextBlock = Cast<UTextBlock>(WidgetBlueprint->WidgetTree->FindWidget(FName(*WidgetName)));
|
||||
if (!TextBlock)
|
||||
{
|
||||
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to find TextBlock widget: %s"), *WidgetName));
|
||||
return Response;
|
||||
}
|
||||
|
||||
// Create binding function
|
||||
const FString FunctionName = FString::Printf(TEXT("Get%s"), *BindingName);
|
||||
UEdGraph* FuncGraph = FBlueprintEditorUtils::CreateNewGraph(
|
||||
WidgetBlueprint,
|
||||
FName(*FunctionName),
|
||||
UEdGraph::StaticClass(),
|
||||
UEdGraphSchema_K2::StaticClass()
|
||||
);
|
||||
|
||||
if (FuncGraph)
|
||||
{
|
||||
// Add the function to the blueprint with proper template parameter
|
||||
// Template requires null for last parameter when not using a signature-source
|
||||
FBlueprintEditorUtils::AddFunctionGraph<UClass>(WidgetBlueprint, FuncGraph, false, nullptr);
|
||||
|
||||
// Create entry node
|
||||
UK2Node_FunctionEntry* EntryNode = nullptr;
|
||||
|
||||
// Create entry node - use the API that exists in UE 5.5
|
||||
EntryNode = NewObject<UK2Node_FunctionEntry>(FuncGraph);
|
||||
FuncGraph->AddNode(EntryNode, false, false);
|
||||
EntryNode->NodePosX = 0;
|
||||
EntryNode->NodePosY = 0;
|
||||
EntryNode->FunctionReference.SetExternalMember(FName(*FunctionName), WidgetBlueprint->GeneratedClass);
|
||||
EntryNode->AllocateDefaultPins();
|
||||
|
||||
// Create get variable node
|
||||
UK2Node_VariableGet* GetVarNode = NewObject<UK2Node_VariableGet>(FuncGraph);
|
||||
GetVarNode->VariableReference.SetSelfMember(FName(*BindingName));
|
||||
FuncGraph->AddNode(GetVarNode, false, false);
|
||||
GetVarNode->NodePosX = 200;
|
||||
GetVarNode->NodePosY = 0;
|
||||
GetVarNode->AllocateDefaultPins();
|
||||
|
||||
// Connect nodes
|
||||
UEdGraphPin* EntryThenPin = EntryNode->FindPin(UEdGraphSchema_K2::PN_Then);
|
||||
UEdGraphPin* GetVarOutPin = GetVarNode->FindPin(UEdGraphSchema_K2::PN_ReturnValue);
|
||||
if (EntryThenPin && GetVarOutPin)
|
||||
{
|
||||
EntryThenPin->MakeLinkTo(GetVarOutPin);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the Widget Blueprint
|
||||
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
|
||||
UEditorAssetLibrary::SaveAsset(BlueprintPath, false);
|
||||
|
||||
Response->SetBoolField(TEXT("success"), true);
|
||||
Response->SetStringField(TEXT("binding_name"), BindingName);
|
||||
return Response;
|
||||
}
|
||||
@ -0,0 +1,321 @@
|
||||
#include "MCPServerRunnable.h"
|
||||
#include "UnrealMCPBridge.h"
|
||||
#include "Sockets.h"
|
||||
#include "SocketSubsystem.h"
|
||||
#include "Interfaces/IPv4/IPv4Address.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Dom/JsonValue.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonReader.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Misc/ScopeLock.h"
|
||||
#include "HAL/PlatformTime.h"
|
||||
|
||||
// Buffer size for receiving data
|
||||
const int32 BufferSize = 8192;
|
||||
|
||||
FMCPServerRunnable::FMCPServerRunnable(UUnrealMCPBridge* InBridge, TSharedPtr<FSocket> InListenerSocket)
|
||||
: Bridge(InBridge)
|
||||
, ListenerSocket(InListenerSocket)
|
||||
, bRunning(true)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Created server runnable"));
|
||||
}
|
||||
|
||||
FMCPServerRunnable::~FMCPServerRunnable()
|
||||
{
|
||||
// Note: We don't delete the sockets here as they're owned by the bridge
|
||||
}
|
||||
|
||||
bool FMCPServerRunnable::Init()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 FMCPServerRunnable::Run()
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Server thread starting..."));
|
||||
|
||||
while (bRunning)
|
||||
{
|
||||
// UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Waiting for client connection..."));
|
||||
|
||||
bool bPending = false;
|
||||
if (ListenerSocket->HasPendingConnection(bPending) && bPending)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client connection pending, accepting..."));
|
||||
|
||||
ClientSocket = MakeShareable(ListenerSocket->Accept(TEXT("MCPClient")));
|
||||
if (ClientSocket.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client connection accepted"));
|
||||
|
||||
// Set socket options to improve connection stability
|
||||
ClientSocket->SetNoDelay(true);
|
||||
int32 SocketBufferSize = 65536; // 64KB buffer
|
||||
ClientSocket->SetSendBufferSize(SocketBufferSize, SocketBufferSize);
|
||||
ClientSocket->SetReceiveBufferSize(SocketBufferSize, SocketBufferSize);
|
||||
|
||||
uint8 Buffer[8192];
|
||||
while (bRunning)
|
||||
{
|
||||
int32 BytesRead = 0;
|
||||
if (ClientSocket->Recv(Buffer, sizeof(Buffer), BytesRead))
|
||||
{
|
||||
if (BytesRead == 0)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client disconnected (zero bytes)"));
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert received data to string
|
||||
Buffer[BytesRead] = '\0';
|
||||
FString ReceivedText = UTF8_TO_TCHAR(Buffer);
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Received: %s"), *ReceivedText);
|
||||
|
||||
// Parse JSON
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ReceivedText);
|
||||
|
||||
if (FJsonSerializer::Deserialize(Reader, JsonObject))
|
||||
{
|
||||
// Get command type
|
||||
FString CommandType;
|
||||
if (JsonObject->TryGetStringField(TEXT("type"), CommandType))
|
||||
{
|
||||
// Execute command
|
||||
FString Response = Bridge->ExecuteCommand(CommandType, JsonObject->GetObjectField(TEXT("params")));
|
||||
|
||||
// Log response for debugging
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Sending response: %s"), *Response);
|
||||
|
||||
// Send response
|
||||
int32 BytesSent = 0;
|
||||
if (!ClientSocket->Send((uint8*)TCHAR_TO_UTF8(*Response), Response.Len(), BytesSent))
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to send response"));
|
||||
}
|
||||
else {
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Response sent successfully, bytes: %d"), BytesSent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Missing 'type' field in command"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to parse JSON from: %s"), *ReceivedText);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int32 LastError = (int32)ISocketSubsystem::Get()->GetLastErrorCode();
|
||||
// Don't break the connection for WouldBlock error, which is normal for non-blocking sockets
|
||||
bool bShouldBreak = true;
|
||||
|
||||
// Check for "would block" error which isn't a real error for non-blocking sockets
|
||||
if (LastError == SE_EWOULDBLOCK)
|
||||
{
|
||||
UE_LOG(LogTemp, Verbose, TEXT("MCPServerRunnable: Socket would block, continuing..."));
|
||||
bShouldBreak = false;
|
||||
// Small sleep to prevent tight loop when no data
|
||||
FPlatformProcess::Sleep(0.01f);
|
||||
}
|
||||
// Check for other transient errors we might want to tolerate
|
||||
else if (LastError == SE_EINTR) // Interrupted system call
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Socket read interrupted, continuing..."));
|
||||
bShouldBreak = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Client disconnected or error. Last error code: %d"), LastError);
|
||||
}
|
||||
|
||||
if (bShouldBreak)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to accept client connection"));
|
||||
}
|
||||
}
|
||||
|
||||
// Small sleep to prevent tight loop
|
||||
FPlatformProcess::Sleep(0.1f);
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Server thread stopping"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FMCPServerRunnable::Stop()
|
||||
{
|
||||
bRunning = false;
|
||||
}
|
||||
|
||||
void FMCPServerRunnable::Exit()
|
||||
{
|
||||
}
|
||||
|
||||
void FMCPServerRunnable::HandleClientConnection(TSharedPtr<FSocket> InClientSocket)
|
||||
{
|
||||
if (!InClientSocket.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("MCPServerRunnable: Invalid client socket passed to HandleClientConnection"));
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Starting to handle client connection"));
|
||||
|
||||
// Set socket options for better connection stability
|
||||
InClientSocket->SetNonBlocking(false);
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Set socket to blocking mode"));
|
||||
|
||||
// Properly read full message with timeout
|
||||
const int32 MaxBufferSize = 4096;
|
||||
uint8 Buffer[MaxBufferSize];
|
||||
FString MessageBuffer;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Starting message receive loop"));
|
||||
|
||||
while (bRunning && InClientSocket.IsValid())
|
||||
{
|
||||
// Log socket state
|
||||
bool bIsConnected = InClientSocket->GetConnectionState() == SCS_Connected;
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Socket state - Connected: %s"),
|
||||
bIsConnected ? TEXT("true") : TEXT("false"));
|
||||
|
||||
// Log pending data status before receive
|
||||
uint32 PendingDataSize = 0;
|
||||
bool HasPendingData = InClientSocket->HasPendingData(PendingDataSize);
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Before Recv - HasPendingData=%s, Size=%d"),
|
||||
HasPendingData ? TEXT("true") : TEXT("false"), PendingDataSize);
|
||||
|
||||
// Try to receive data with timeout
|
||||
int32 BytesRead = 0;
|
||||
bool bReadSuccess = false;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Attempting to receive data..."));
|
||||
bReadSuccess = InClientSocket->Recv(Buffer, MaxBufferSize, BytesRead, ESocketReceiveFlags::None);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Recv attempt complete - Success=%s, BytesRead=%d"),
|
||||
bReadSuccess ? TEXT("true") : TEXT("false"), BytesRead);
|
||||
|
||||
if (BytesRead > 0)
|
||||
{
|
||||
// Log raw data for debugging
|
||||
FString HexData;
|
||||
for (int32 i = 0; i < FMath::Min(BytesRead, 50); ++i)
|
||||
{
|
||||
HexData += FString::Printf(TEXT("%02X "), Buffer[i]);
|
||||
}
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Raw data (first 50 bytes hex): %s%s"),
|
||||
*HexData, BytesRead > 50 ? TEXT("...") : TEXT(""));
|
||||
|
||||
// Convert and log received data
|
||||
Buffer[BytesRead] = 0; // Null terminate
|
||||
FString ReceivedData = UTF8_TO_TCHAR(Buffer);
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Received data as string: '%s'"), *ReceivedData);
|
||||
|
||||
// Append to message buffer
|
||||
MessageBuffer.Append(ReceivedData);
|
||||
|
||||
// Process complete messages (messages are terminated with newline)
|
||||
if (MessageBuffer.Contains(TEXT("\n")))
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Newline detected in buffer, processing messages"));
|
||||
|
||||
TArray<FString> Messages;
|
||||
MessageBuffer.ParseIntoArray(Messages, TEXT("\n"), true);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Found %d message(s) in buffer"), Messages.Num());
|
||||
|
||||
// Process all complete messages
|
||||
for (int32 i = 0; i < Messages.Num() - 1; ++i)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message %d: '%s'"),
|
||||
i + 1, *Messages[i]);
|
||||
ProcessMessage(InClientSocket, Messages[i]);
|
||||
}
|
||||
|
||||
// Keep any incomplete message in the buffer
|
||||
MessageBuffer = Messages.Last();
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Remaining buffer after processing: %s"),
|
||||
*MessageBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: No complete message yet (no newline detected)"));
|
||||
}
|
||||
}
|
||||
else if (!bReadSuccess)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Connection closed or error occurred - Last error: %d"),
|
||||
(int32)ISocketSubsystem::Get()->GetLastErrorCode());
|
||||
break;
|
||||
}
|
||||
|
||||
// Small sleep to prevent tight loop
|
||||
FPlatformProcess::Sleep(0.01f);
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Exited message receive loop"));
|
||||
}
|
||||
|
||||
void FMCPServerRunnable::ProcessMessage(TSharedPtr<FSocket> Client, const FString& Message)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message: %s"), *Message);
|
||||
|
||||
// Parse message as JSON
|
||||
TSharedPtr<FJsonObject> JsonMessage;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
|
||||
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonMessage) || !JsonMessage.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to parse message as JSON"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract command type and parameters using MCP protocol format
|
||||
FString CommandType;
|
||||
TSharedPtr<FJsonObject> Params = MakeShareable(new FJsonObject());
|
||||
|
||||
if (!JsonMessage->TryGetStringField(TEXT("command"), CommandType))
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Message missing 'command' field"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parameters are optional in MCP protocol
|
||||
if (JsonMessage->HasField(TEXT("params")))
|
||||
{
|
||||
TSharedPtr<FJsonValue> ParamsValue = JsonMessage->TryGetField(TEXT("params"));
|
||||
if (ParamsValue.IsValid() && ParamsValue->Type == EJson::Object)
|
||||
{
|
||||
Params = ParamsValue->AsObject();
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Executing command: %s"), *CommandType);
|
||||
|
||||
// Execute command
|
||||
FString Response = Bridge->ExecuteCommand(CommandType, Params);
|
||||
|
||||
// Send response with newline terminator
|
||||
Response += TEXT("\n");
|
||||
int32 BytesSent = 0;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Sending response: %s"), *Response);
|
||||
|
||||
if (!Client->Send((uint8*)TCHAR_TO_UTF8(*Response), Response.Len(), BytesSent))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("MCPServerRunnable: Failed to send response"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,332 @@
|
||||
#include "UnrealMCPBridge.h"
|
||||
#include "MCPServerRunnable.h"
|
||||
#include "Sockets.h"
|
||||
#include "SocketSubsystem.h"
|
||||
#include "HAL/RunnableThread.h"
|
||||
#include "Interfaces/IPv4/IPv4Address.h"
|
||||
#include "Interfaces/IPv4/IPv4Endpoint.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Dom/JsonValue.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonReader.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "Engine/StaticMeshActor.h"
|
||||
#include "Engine/DirectionalLight.h"
|
||||
#include "Engine/PointLight.h"
|
||||
#include "Engine/SpotLight.h"
|
||||
#include "Camera/CameraActor.h"
|
||||
#include "EditorAssetLibrary.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Engine/Selection.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Async/Async.h"
|
||||
// Add Blueprint related includes
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "Factories/BlueprintFactory.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "K2Node_Event.h"
|
||||
#include "K2Node_VariableGet.h"
|
||||
#include "K2Node_VariableSet.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Components/BoxComponent.h"
|
||||
#include "Components/SphereComponent.h"
|
||||
#include "Kismet2/BlueprintEditorUtils.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
// UE5.5 correct includes
|
||||
#include "Engine/SimpleConstructionScript.h"
|
||||
#include "Engine/SCS_Node.h"
|
||||
#include "UObject/Field.h"
|
||||
#include "UObject/FieldPath.h"
|
||||
// Blueprint Graph specific includes
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "K2Node_CallFunction.h"
|
||||
#include "K2Node_InputAction.h"
|
||||
#include "K2Node_Self.h"
|
||||
#include "GameFramework/InputSettings.h"
|
||||
#include "EditorSubsystem.h"
|
||||
#include "Subsystems/EditorActorSubsystem.h"
|
||||
// Include our new command handler classes
|
||||
#include "Commands/UnrealMCPEditorCommands.h"
|
||||
#include "Commands/UnrealMCPBlueprintCommands.h"
|
||||
#include "Commands/UnrealMCPBlueprintNodeCommands.h"
|
||||
#include "Commands/UnrealMCPProjectCommands.h"
|
||||
#include "Commands/UnrealMCPCommonUtils.h"
|
||||
#include "Commands/UnrealMCPUMGCommands.h"
|
||||
|
||||
// Default settings
|
||||
#define MCP_SERVER_HOST "127.0.0.1"
|
||||
#define MCP_SERVER_PORT 55557
|
||||
|
||||
UUnrealMCPBridge::UUnrealMCPBridge()
|
||||
{
|
||||
EditorCommands = MakeShared<FUnrealMCPEditorCommands>();
|
||||
BlueprintCommands = MakeShared<FUnrealMCPBlueprintCommands>();
|
||||
BlueprintNodeCommands = MakeShared<FUnrealMCPBlueprintNodeCommands>();
|
||||
ProjectCommands = MakeShared<FUnrealMCPProjectCommands>();
|
||||
UMGCommands = MakeShared<FUnrealMCPUMGCommands>();
|
||||
}
|
||||
|
||||
UUnrealMCPBridge::~UUnrealMCPBridge()
|
||||
{
|
||||
EditorCommands.Reset();
|
||||
BlueprintCommands.Reset();
|
||||
BlueprintNodeCommands.Reset();
|
||||
ProjectCommands.Reset();
|
||||
UMGCommands.Reset();
|
||||
}
|
||||
|
||||
// Initialize subsystem
|
||||
void UUnrealMCPBridge::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Initializing"));
|
||||
|
||||
bIsRunning = false;
|
||||
ListenerSocket = nullptr;
|
||||
ConnectionSocket = nullptr;
|
||||
ServerThread = nullptr;
|
||||
Port = MCP_SERVER_PORT;
|
||||
FIPv4Address::Parse(MCP_SERVER_HOST, ServerAddress);
|
||||
|
||||
// Start the server automatically
|
||||
StartServer();
|
||||
}
|
||||
|
||||
// Clean up resources when subsystem is destroyed
|
||||
void UUnrealMCPBridge::Deinitialize()
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Shutting down"));
|
||||
StopServer();
|
||||
}
|
||||
|
||||
// Start the MCP server
|
||||
void UUnrealMCPBridge::StartServer()
|
||||
{
|
||||
if (bIsRunning)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("UnrealMCPBridge: Server is already running"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create socket subsystem
|
||||
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
|
||||
if (!SocketSubsystem)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to get socket subsystem"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create listener socket
|
||||
TSharedPtr<FSocket> NewListenerSocket = MakeShareable(SocketSubsystem->CreateSocket(NAME_Stream, TEXT("UnrealMCPListener"), false));
|
||||
if (!NewListenerSocket.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to create listener socket"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow address reuse for quick restarts
|
||||
NewListenerSocket->SetReuseAddr(true);
|
||||
NewListenerSocket->SetNonBlocking(true);
|
||||
|
||||
// Bind to address
|
||||
FIPv4Endpoint Endpoint(ServerAddress, Port);
|
||||
if (!NewListenerSocket->Bind(*Endpoint.ToInternetAddr()))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to bind listener socket to %s:%d"), *ServerAddress.ToString(), Port);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start listening
|
||||
if (!NewListenerSocket->Listen(5))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to start listening"));
|
||||
return;
|
||||
}
|
||||
|
||||
ListenerSocket = NewListenerSocket;
|
||||
bIsRunning = true;
|
||||
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Server started on %s:%d"), *ServerAddress.ToString(), Port);
|
||||
|
||||
// Start server thread
|
||||
ServerThread = FRunnableThread::Create(
|
||||
new FMCPServerRunnable(this, ListenerSocket),
|
||||
TEXT("UnrealMCPServerThread"),
|
||||
0, TPri_Normal
|
||||
);
|
||||
|
||||
if (!ServerThread)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to create server thread"));
|
||||
StopServer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the MCP server
|
||||
void UUnrealMCPBridge::StopServer()
|
||||
{
|
||||
if (!bIsRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bIsRunning = false;
|
||||
|
||||
// Clean up thread
|
||||
if (ServerThread)
|
||||
{
|
||||
ServerThread->Kill(true);
|
||||
delete ServerThread;
|
||||
ServerThread = nullptr;
|
||||
}
|
||||
|
||||
// Close sockets
|
||||
if (ConnectionSocket.IsValid())
|
||||
{
|
||||
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ConnectionSocket.Get());
|
||||
ConnectionSocket.Reset();
|
||||
}
|
||||
|
||||
if (ListenerSocket.IsValid())
|
||||
{
|
||||
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenerSocket.Get());
|
||||
ListenerSocket.Reset();
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Server stopped"));
|
||||
}
|
||||
|
||||
// Execute a command received from a client
|
||||
FString UUnrealMCPBridge::ExecuteCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Executing command: %s"), *CommandType);
|
||||
|
||||
// Create a promise to wait for the result
|
||||
TPromise<FString> Promise;
|
||||
TFuture<FString> Future = Promise.GetFuture();
|
||||
|
||||
// Queue execution on Game Thread
|
||||
AsyncTask(ENamedThreads::GameThread, [this, CommandType, Params, Promise = MoveTemp(Promise)]() mutable
|
||||
{
|
||||
TSharedPtr<FJsonObject> ResponseJson = MakeShareable(new FJsonObject);
|
||||
|
||||
try
|
||||
{
|
||||
TSharedPtr<FJsonObject> ResultJson;
|
||||
|
||||
if (CommandType == TEXT("ping"))
|
||||
{
|
||||
ResultJson = MakeShareable(new FJsonObject);
|
||||
ResultJson->SetStringField(TEXT("message"), TEXT("pong"));
|
||||
}
|
||||
// Editor Commands (including actor manipulation)
|
||||
else if (CommandType == TEXT("get_actors_in_level") ||
|
||||
CommandType == TEXT("find_actors_by_name") ||
|
||||
CommandType == TEXT("spawn_actor") ||
|
||||
CommandType == TEXT("create_actor") ||
|
||||
CommandType == TEXT("delete_actor") ||
|
||||
CommandType == TEXT("set_actor_transform") ||
|
||||
CommandType == TEXT("get_actor_properties") ||
|
||||
CommandType == TEXT("set_actor_property") ||
|
||||
CommandType == TEXT("spawn_blueprint_actor") ||
|
||||
CommandType == TEXT("focus_viewport") ||
|
||||
CommandType == TEXT("take_screenshot"))
|
||||
{
|
||||
ResultJson = EditorCommands->HandleCommand(CommandType, Params);
|
||||
}
|
||||
// Blueprint Commands
|
||||
else if (CommandType == TEXT("create_blueprint") ||
|
||||
CommandType == TEXT("add_component_to_blueprint") ||
|
||||
CommandType == TEXT("set_component_property") ||
|
||||
CommandType == TEXT("set_physics_properties") ||
|
||||
CommandType == TEXT("compile_blueprint") ||
|
||||
CommandType == TEXT("set_blueprint_property") ||
|
||||
CommandType == TEXT("set_static_mesh_properties") ||
|
||||
CommandType == TEXT("set_pawn_properties"))
|
||||
{
|
||||
ResultJson = BlueprintCommands->HandleCommand(CommandType, Params);
|
||||
}
|
||||
// Blueprint Node Commands
|
||||
else if (CommandType == TEXT("connect_blueprint_nodes") ||
|
||||
CommandType == TEXT("add_blueprint_get_self_component_reference") ||
|
||||
CommandType == TEXT("add_blueprint_self_reference") ||
|
||||
CommandType == TEXT("find_blueprint_nodes") ||
|
||||
CommandType == TEXT("add_blueprint_event_node") ||
|
||||
CommandType == TEXT("add_blueprint_input_action_node") ||
|
||||
CommandType == TEXT("add_blueprint_function_node") ||
|
||||
CommandType == TEXT("add_blueprint_get_component_node") ||
|
||||
CommandType == TEXT("add_blueprint_variable"))
|
||||
{
|
||||
ResultJson = BlueprintNodeCommands->HandleCommand(CommandType, Params);
|
||||
}
|
||||
// Project Commands
|
||||
else if (CommandType == TEXT("create_input_mapping"))
|
||||
{
|
||||
ResultJson = ProjectCommands->HandleCommand(CommandType, Params);
|
||||
}
|
||||
// UMG Commands
|
||||
else if (CommandType == TEXT("create_umg_widget_blueprint") ||
|
||||
CommandType == TEXT("add_text_block_to_widget") ||
|
||||
CommandType == TEXT("add_button_to_widget") ||
|
||||
CommandType == TEXT("bind_widget_event") ||
|
||||
CommandType == TEXT("set_text_block_binding") ||
|
||||
CommandType == TEXT("add_widget_to_viewport"))
|
||||
{
|
||||
ResultJson = UMGCommands->HandleCommand(CommandType, Params);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
|
||||
ResponseJson->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown command: %s"), *CommandType));
|
||||
|
||||
FString ResultString;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
|
||||
FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
|
||||
Promise.SetValue(ResultString);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the result contains an error
|
||||
bool bSuccess = true;
|
||||
FString ErrorMessage;
|
||||
|
||||
if (ResultJson->HasField(TEXT("success")))
|
||||
{
|
||||
bSuccess = ResultJson->GetBoolField(TEXT("success"));
|
||||
if (!bSuccess && ResultJson->HasField(TEXT("error")))
|
||||
{
|
||||
ErrorMessage = ResultJson->GetStringField(TEXT("error"));
|
||||
}
|
||||
}
|
||||
|
||||
if (bSuccess)
|
||||
{
|
||||
// Set success status and include the result
|
||||
ResponseJson->SetStringField(TEXT("status"), TEXT("success"));
|
||||
ResponseJson->SetObjectField(TEXT("result"), ResultJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set error status and include the error message
|
||||
ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
|
||||
ResponseJson->SetStringField(TEXT("error"), ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
|
||||
ResponseJson->SetStringField(TEXT("error"), UTF8_TO_TCHAR(e.what()));
|
||||
}
|
||||
|
||||
FString ResultString;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
|
||||
FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
|
||||
Promise.SetValue(ResultString);
|
||||
});
|
||||
|
||||
return Future.Get();
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#include "UnrealMCPModule.h"
|
||||
#include "UnrealMCPBridge.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "EditorSubsystem.h"
|
||||
#include "Editor.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FUnrealMCPModule"
|
||||
|
||||
void FUnrealMCPModule::StartupModule()
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Unreal MCP Module has started"));
|
||||
}
|
||||
|
||||
void FUnrealMCPModule::ShutdownModule()
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Unreal MCP Module has shut down"));
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FUnrealMCPModule, UnrealMCP)
|
||||
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Json.h"
|
||||
|
||||
/**
|
||||
* Handler class for Blueprint-related MCP commands
|
||||
*/
|
||||
class UNREALMCP_API FUnrealMCPBlueprintCommands
|
||||
{
|
||||
public:
|
||||
FUnrealMCPBlueprintCommands();
|
||||
|
||||
// Handle blueprint commands
|
||||
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
private:
|
||||
// Specific blueprint command handlers
|
||||
TSharedPtr<FJsonObject> HandleCreateBlueprint(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleAddComponentToBlueprint(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSetComponentProperty(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSetPhysicsProperties(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleCompileBlueprint(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSpawnBlueprintActor(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSetBlueprintProperty(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSetStaticMeshProperties(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSetPawnProperties(const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
// Helper functions
|
||||
TSharedPtr<FJsonObject> AddComponentToBlueprint(const FString& BlueprintName, const FString& ComponentType,
|
||||
const FString& ComponentName, const FString& MeshType,
|
||||
const TArray<float>& Location, const TArray<float>& Rotation,
|
||||
const TArray<float>& Scale, const TSharedPtr<FJsonObject>& ComponentProperties);
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Json.h"
|
||||
|
||||
/**
|
||||
* Handler class for Blueprint Node-related MCP commands
|
||||
*/
|
||||
class UNREALMCP_API FUnrealMCPBlueprintNodeCommands
|
||||
{
|
||||
public:
|
||||
FUnrealMCPBlueprintNodeCommands();
|
||||
|
||||
// Handle blueprint node commands
|
||||
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
private:
|
||||
// Specific blueprint node command handlers
|
||||
TSharedPtr<FJsonObject> HandleConnectBlueprintNodes(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleAddBlueprintGetSelfComponentReference(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleAddBlueprintEvent(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleAddBlueprintFunctionCall(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleAddBlueprintVariable(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleAddBlueprintInputActionNode(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleAddBlueprintSelfReference(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleFindBlueprintNodes(const TSharedPtr<FJsonObject>& Params);
|
||||
};
|
||||
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Json.h"
|
||||
|
||||
// Forward declarations
|
||||
class AActor;
|
||||
class UBlueprint;
|
||||
class UEdGraph;
|
||||
class UEdGraphNode;
|
||||
class UEdGraphPin;
|
||||
class UK2Node_Event;
|
||||
class UK2Node_CallFunction;
|
||||
class UK2Node_VariableGet;
|
||||
class UK2Node_VariableSet;
|
||||
class UK2Node_InputAction;
|
||||
class UK2Node_Self;
|
||||
class UFunction;
|
||||
|
||||
/**
|
||||
* Common utilities for UnrealMCP commands
|
||||
*/
|
||||
class UNREALMCP_API FUnrealMCPCommonUtils
|
||||
{
|
||||
public:
|
||||
// JSON utilities
|
||||
static TSharedPtr<FJsonObject> CreateErrorResponse(const FString& Message);
|
||||
static TSharedPtr<FJsonObject> CreateSuccessResponse(const TSharedPtr<FJsonObject>& Data = nullptr);
|
||||
static void GetIntArrayFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName, TArray<int32>& OutArray);
|
||||
static void GetFloatArrayFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName, TArray<float>& OutArray);
|
||||
static FVector2D GetVector2DFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName);
|
||||
static FVector GetVectorFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName);
|
||||
static FRotator GetRotatorFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName);
|
||||
|
||||
// Actor utilities
|
||||
static TSharedPtr<FJsonValue> ActorToJson(AActor* Actor);
|
||||
static TSharedPtr<FJsonObject> ActorToJsonObject(AActor* Actor, bool bDetailed = false);
|
||||
|
||||
// Blueprint utilities
|
||||
static UBlueprint* FindBlueprint(const FString& BlueprintName);
|
||||
static UBlueprint* FindBlueprintByName(const FString& BlueprintName);
|
||||
static UEdGraph* FindOrCreateEventGraph(UBlueprint* Blueprint);
|
||||
|
||||
// Blueprint node utilities
|
||||
static UK2Node_Event* CreateEventNode(UEdGraph* Graph, const FString& EventName, const FVector2D& Position);
|
||||
static UK2Node_CallFunction* CreateFunctionCallNode(UEdGraph* Graph, UFunction* Function, const FVector2D& Position);
|
||||
static UK2Node_VariableGet* CreateVariableGetNode(UEdGraph* Graph, UBlueprint* Blueprint, const FString& VariableName, const FVector2D& Position);
|
||||
static UK2Node_VariableSet* CreateVariableSetNode(UEdGraph* Graph, UBlueprint* Blueprint, const FString& VariableName, const FVector2D& Position);
|
||||
static UK2Node_InputAction* CreateInputActionNode(UEdGraph* Graph, const FString& ActionName, const FVector2D& Position);
|
||||
static UK2Node_Self* CreateSelfReferenceNode(UEdGraph* Graph, const FVector2D& Position);
|
||||
static bool ConnectGraphNodes(UEdGraph* Graph, UEdGraphNode* SourceNode, const FString& SourcePinName,
|
||||
UEdGraphNode* TargetNode, const FString& TargetPinName);
|
||||
static UEdGraphPin* FindPin(UEdGraphNode* Node, const FString& PinName, EEdGraphPinDirection Direction = EGPD_MAX);
|
||||
static UK2Node_Event* FindExistingEventNode(UEdGraph* Graph, const FString& EventName);
|
||||
|
||||
// Property utilities
|
||||
static bool SetObjectProperty(UObject* Object, const FString& PropertyName,
|
||||
const TSharedPtr<FJsonValue>& Value, FString& OutErrorMessage);
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Json.h"
|
||||
|
||||
/**
|
||||
* Handler class for Editor-related MCP commands
|
||||
* Handles viewport control, actor manipulation, and level management
|
||||
*/
|
||||
class UNREALMCP_API FUnrealMCPEditorCommands
|
||||
{
|
||||
public:
|
||||
FUnrealMCPEditorCommands();
|
||||
|
||||
// Handle editor commands
|
||||
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
private:
|
||||
// Actor manipulation commands
|
||||
TSharedPtr<FJsonObject> HandleGetActorsInLevel(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleFindActorsByName(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSpawnActor(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleDeleteActor(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSetActorTransform(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleGetActorProperties(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleSetActorProperty(const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
// Blueprint actor spawning
|
||||
TSharedPtr<FJsonObject> HandleSpawnBlueprintActor(const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
// Editor viewport commands
|
||||
TSharedPtr<FJsonObject> HandleFocusViewport(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> HandleTakeScreenshot(const TSharedPtr<FJsonObject>& Params);
|
||||
};
|
||||
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Json.h"
|
||||
|
||||
/**
|
||||
* Handler class for Project-wide MCP commands
|
||||
*/
|
||||
class UNREALMCP_API FUnrealMCPProjectCommands
|
||||
{
|
||||
public:
|
||||
FUnrealMCPProjectCommands();
|
||||
|
||||
// Handle project commands
|
||||
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
private:
|
||||
// Specific project command handlers
|
||||
TSharedPtr<FJsonObject> HandleCreateInputMapping(const TSharedPtr<FJsonObject>& Params);
|
||||
};
|
||||
@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Json.h"
|
||||
|
||||
/**
|
||||
* Handles UMG (Widget Blueprint) related MCP commands
|
||||
* Responsible for creating and modifying UMG Widget Blueprints,
|
||||
* adding widget components, and managing widget instances in the viewport.
|
||||
*/
|
||||
class UNREALMCP_API FUnrealMCPUMGCommands
|
||||
{
|
||||
public:
|
||||
FUnrealMCPUMGCommands();
|
||||
|
||||
/**
|
||||
* Handle UMG-related commands
|
||||
* @param CommandType - The type of command to handle
|
||||
* @param Params - JSON parameters for the command
|
||||
* @return JSON response with results or error
|
||||
*/
|
||||
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Create a new UMG Widget Blueprint
|
||||
* @param Params - Must include "name" for the blueprint name
|
||||
* @return JSON response with the created blueprint details
|
||||
*/
|
||||
TSharedPtr<FJsonObject> HandleCreateUMGWidgetBlueprint(const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
/**
|
||||
* Add a Text Block widget to a UMG Widget Blueprint
|
||||
* @param Params - Must include:
|
||||
* "blueprint_name" - Name of the target Widget Blueprint
|
||||
* "widget_name" - Name for the new Text Block
|
||||
* "text" - Initial text content (optional)
|
||||
* "position" - [X, Y] position in the canvas (optional)
|
||||
* @return JSON response with the added widget details
|
||||
*/
|
||||
TSharedPtr<FJsonObject> HandleAddTextBlockToWidget(const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
/**
|
||||
* Add a widget instance to the game viewport
|
||||
* @param Params - Must include:
|
||||
* "blueprint_name" - Name of the Widget Blueprint to instantiate
|
||||
* "z_order" - Z-order for widget display (optional)
|
||||
* @return JSON response with the widget instance details
|
||||
*/
|
||||
TSharedPtr<FJsonObject> HandleAddWidgetToViewport(const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
/**
|
||||
* Add a Button widget to a UMG Widget Blueprint
|
||||
* @param Params - Must include:
|
||||
* "blueprint_name" - Name of the target Widget Blueprint
|
||||
* "widget_name" - Name for the new Button
|
||||
* "text" - Button text
|
||||
* "position" - [X, Y] position in the canvas
|
||||
* @return JSON response with the added widget details
|
||||
*/
|
||||
TSharedPtr<FJsonObject> HandleAddButtonToWidget(const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
/**
|
||||
* Bind an event to a widget (e.g. button click)
|
||||
* @param Params - Must include:
|
||||
* "blueprint_name" - Name of the target Widget Blueprint
|
||||
* "widget_name" - Name of the widget to bind
|
||||
* "event_name" - Name of the event to bind
|
||||
* @return JSON response with the binding details
|
||||
*/
|
||||
TSharedPtr<FJsonObject> HandleBindWidgetEvent(const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
/**
|
||||
* Set up text block binding for dynamic updates
|
||||
* @param Params - Must include:
|
||||
* "blueprint_name" - Name of the target Widget Blueprint
|
||||
* "widget_name" - Name of the widget to bind
|
||||
* "binding_name" - Name of the binding to set up
|
||||
* @return JSON response with the binding details
|
||||
*/
|
||||
TSharedPtr<FJsonObject> HandleSetTextBlockBinding(const TSharedPtr<FJsonObject>& Params);
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "HAL/Runnable.h"
|
||||
#include "Sockets.h"
|
||||
#include "Interfaces/IPv4/IPv4Address.h"
|
||||
|
||||
class UUnrealMCPBridge;
|
||||
|
||||
/**
|
||||
* Runnable class for the MCP server thread
|
||||
*/
|
||||
class FMCPServerRunnable : public FRunnable
|
||||
{
|
||||
public:
|
||||
FMCPServerRunnable(UUnrealMCPBridge* InBridge, TSharedPtr<FSocket> InListenerSocket);
|
||||
virtual ~FMCPServerRunnable();
|
||||
|
||||
// FRunnable interface
|
||||
virtual bool Init() override;
|
||||
virtual uint32 Run() override;
|
||||
virtual void Stop() override;
|
||||
virtual void Exit() override;
|
||||
|
||||
protected:
|
||||
void HandleClientConnection(TSharedPtr<FSocket> ClientSocket);
|
||||
void ProcessMessage(TSharedPtr<FSocket> Client, const FString& Message);
|
||||
|
||||
private:
|
||||
UUnrealMCPBridge* Bridge;
|
||||
TSharedPtr<FSocket> ListenerSocket;
|
||||
TSharedPtr<FSocket> ClientSocket;
|
||||
bool bRunning;
|
||||
};
|
||||
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "EditorSubsystem.h"
|
||||
#include "Sockets.h"
|
||||
#include "SocketSubsystem.h"
|
||||
#include "Http.h"
|
||||
#include "Json.h"
|
||||
#include "Interfaces/IPv4/IPv4Address.h"
|
||||
#include "Interfaces/IPv4/IPv4Endpoint.h"
|
||||
#include "Commands/UnrealMCPEditorCommands.h"
|
||||
#include "Commands/UnrealMCPBlueprintCommands.h"
|
||||
#include "Commands/UnrealMCPBlueprintNodeCommands.h"
|
||||
#include "Commands/UnrealMCPProjectCommands.h"
|
||||
#include "Commands/UnrealMCPUMGCommands.h"
|
||||
#include "UnrealMCPBridge.generated.h"
|
||||
|
||||
class FMCPServerRunnable;
|
||||
|
||||
/**
|
||||
* Editor subsystem for MCP Bridge
|
||||
* Handles communication between external tools and the Unreal Editor
|
||||
* through a TCP socket connection. Commands are received as JSON and
|
||||
* routed to appropriate command handlers.
|
||||
*/
|
||||
UCLASS()
|
||||
class UNREALMCP_API UUnrealMCPBridge : public UEditorSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UUnrealMCPBridge();
|
||||
virtual ~UUnrealMCPBridge();
|
||||
|
||||
// UEditorSubsystem implementation
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
// Server functions
|
||||
void StartServer();
|
||||
void StopServer();
|
||||
bool IsRunning() const { return bIsRunning; }
|
||||
|
||||
// Command execution
|
||||
FString ExecuteCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
private:
|
||||
// Server state
|
||||
bool bIsRunning;
|
||||
TSharedPtr<FSocket> ListenerSocket;
|
||||
TSharedPtr<FSocket> ConnectionSocket;
|
||||
FRunnableThread* ServerThread;
|
||||
|
||||
// Server configuration
|
||||
FIPv4Address ServerAddress;
|
||||
uint16 Port;
|
||||
|
||||
// Command handler instances
|
||||
TSharedPtr<FUnrealMCPEditorCommands> EditorCommands;
|
||||
TSharedPtr<FUnrealMCPBlueprintCommands> BlueprintCommands;
|
||||
TSharedPtr<FUnrealMCPBlueprintNodeCommands> BlueprintNodeCommands;
|
||||
TSharedPtr<FUnrealMCPProjectCommands> ProjectCommands;
|
||||
TSharedPtr<FUnrealMCPUMGCommands> UMGCommands;
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FUnrealMCPModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
static inline FUnrealMCPModule& Get()
|
||||
{
|
||||
return FModuleManager::LoadModuleChecked<FUnrealMCPModule>("UnrealMCP");
|
||||
}
|
||||
|
||||
static inline bool IsAvailable()
|
||||
{
|
||||
return FModuleManager::Get().IsModuleLoaded("UnrealMCP");
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,78 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class UnrealMCP : ModuleRules
|
||||
{
|
||||
public UnrealMCP(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
// Use IWYUSupport instead of the deprecated bEnforceIWYU in UE5.5
|
||||
IWYUSupport = IWYUSupport.Full;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"InputCore",
|
||||
"Networking",
|
||||
"Sockets",
|
||||
"HTTP",
|
||||
"Json",
|
||||
"JsonUtilities",
|
||||
"DeveloperSettings"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"UnrealEd",
|
||||
"EditorScriptingUtilities",
|
||||
"EditorSubsystem",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"UMG",
|
||||
"Kismet",
|
||||
"KismetCompiler",
|
||||
"BlueprintGraph",
|
||||
"Projects",
|
||||
"AssetRegistry"
|
||||
}
|
||||
);
|
||||
|
||||
if (Target.bBuildEditor == true)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"PropertyEditor", // For widget property editing
|
||||
"ToolMenus", // For editor UI
|
||||
"BlueprintEditorLibrary", // For Blueprint utilities
|
||||
"UMGEditor" // For WidgetBlueprint.h and other UMG editor functionality
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
35
games/unreal/magisterka/Plugins/UnrealMCP/UnrealMCP.uplugin
Normal file
35
games/unreal/magisterka/Plugins/UnrealMCP/UnrealMCP.uplugin
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "UnrealMCP",
|
||||
"Description": "Model Context Protocol implementation for Unreal Engine",
|
||||
"Category": "Editor",
|
||||
"CreatedBy": "Your Name",
|
||||
"CreatedByURL": "",
|
||||
"DocsURL": "",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "",
|
||||
"CanContainContent": true,
|
||||
"IsBetaVersion": false,
|
||||
"IsExperimentalVersion": false,
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "UnrealMCP",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default",
|
||||
"WhitelistPlatforms": [
|
||||
"Win64",
|
||||
"Mac",
|
||||
"Linux"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "EditorScriptingUtilities",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
[CrashReportClient]
|
||||
bHideLogFilesOption=false
|
||||
bIsAllowedToCloseWithoutSending=true
|
||||
CrashConfigPurgeDays=2
|
||||
Stall.RecordDump=false
|
||||
Ensure.RecordDump=true
|
||||
bAgreeToCrashUpload=false
|
||||
|
||||
@ -1349,7 +1349,7 @@ SlateFileDialogs.TimeStamp=2025.02.05-21.36.32
|
||||
SlateFileDialogs.LastCompileMethod=Unknown
|
||||
|
||||
[AssetEditorSubsystem]
|
||||
CleanShutdown=True
|
||||
CleanShutdown=False
|
||||
DebuggerAttached=False
|
||||
|
||||
[PluginBrowser]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user