diff --git a/games/unreal/magisterka/Intermediate/CachedAssetRegistry_0.bin b/games/unreal/magisterka/Intermediate/CachedAssetRegistry_0.bin index 8423566..4ce084c 100644 Binary files a/games/unreal/magisterka/Intermediate/CachedAssetRegistry_0.bin and b/games/unreal/magisterka/Intermediate/CachedAssetRegistry_0.bin differ diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp new file mode 100644 index 0000000..de50bb0 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp @@ -0,0 +1,1160 @@ +#include "Commands/UnrealMCPBlueprintCommands.h" +#include "Commands/UnrealMCPCommonUtils.h" +#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" +#include "Engine/SimpleConstructionScript.h" +#include "Engine/SCS_Node.h" +#include "UObject/Field.h" +#include "UObject/FieldPath.h" +#include "EditorAssetLibrary.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "GameFramework/Actor.h" +#include "GameFramework/Pawn.h" + +FUnrealMCPBlueprintCommands::FUnrealMCPBlueprintCommands() +{ +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleCommand(const FString& CommandType, const TSharedPtr& Params) +{ + if (CommandType == TEXT("create_blueprint")) + { + return HandleCreateBlueprint(Params); + } + else if (CommandType == TEXT("add_component_to_blueprint")) + { + return HandleAddComponentToBlueprint(Params); + } + else if (CommandType == TEXT("set_component_property")) + { + return HandleSetComponentProperty(Params); + } + else if (CommandType == TEXT("set_physics_properties")) + { + return HandleSetPhysicsProperties(Params); + } + else if (CommandType == TEXT("compile_blueprint")) + { + return HandleCompileBlueprint(Params); + } + else if (CommandType == TEXT("spawn_blueprint_actor")) + { + return HandleSpawnBlueprintActor(Params); + } + else if (CommandType == TEXT("set_blueprint_property")) + { + return HandleSetBlueprintProperty(Params); + } + else if (CommandType == TEXT("set_static_mesh_properties")) + { + return HandleSetStaticMeshProperties(Params); + } + else if (CommandType == TEXT("set_pawn_properties")) + { + return HandleSetPawnProperties(Params); + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown blueprint command: %s"), *CommandType)); +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleCreateBlueprint(const TSharedPtr& Params) +{ + // Get required parameters + FString BlueprintName; + if (!Params->TryGetStringField(TEXT("name"), BlueprintName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter")); + } + + // Check if blueprint already exists + FString PackagePath = TEXT("/Game/Blueprints/"); + FString AssetName = BlueprintName; + if (UEditorAssetLibrary::DoesAssetExist(PackagePath + AssetName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint already exists: %s"), *BlueprintName)); + } + + // Create the blueprint factory + UBlueprintFactory* Factory = NewObject(); + + // Handle parent class + FString ParentClass; + Params->TryGetStringField(TEXT("parent_class"), ParentClass); + + // Default to Actor if no parent class specified + UClass* SelectedParentClass = AActor::StaticClass(); + + // Try to find the specified parent class + if (!ParentClass.IsEmpty()) + { + FString ClassName = ParentClass; + if (!ClassName.StartsWith(TEXT("A"))) + { + ClassName = TEXT("A") + ClassName; + } + + // First try direct StaticClass lookup for common classes + UClass* FoundClass = nullptr; + if (ClassName == TEXT("APawn")) + { + FoundClass = APawn::StaticClass(); + } + else if (ClassName == TEXT("AActor")) + { + FoundClass = AActor::StaticClass(); + } + else + { + // Try loading the class using LoadClass which is more reliable than FindObject + const FString ClassPath = FString::Printf(TEXT("/Script/Engine.%s"), *ClassName); + FoundClass = LoadClass(nullptr, *ClassPath); + + if (!FoundClass) + { + // Try alternate paths if not found + const FString GameClassPath = FString::Printf(TEXT("/Script/Game.%s"), *ClassName); + FoundClass = LoadClass(nullptr, *GameClassPath); + } + } + + if (FoundClass) + { + SelectedParentClass = FoundClass; + UE_LOG(LogTemp, Log, TEXT("Successfully set parent class to '%s'"), *ClassName); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Could not find specified parent class '%s' at paths: /Script/Engine.%s or /Script/Game.%s, defaulting to AActor"), + *ClassName, *ClassName, *ClassName); + } + } + + Factory->ParentClass = SelectedParentClass; + + // Create the blueprint + UPackage* Package = CreatePackage(*(PackagePath + AssetName)); + UBlueprint* NewBlueprint = Cast(Factory->FactoryCreateNew(UBlueprint::StaticClass(), Package, *AssetName, RF_Standalone | RF_Public, nullptr, GWarn)); + + if (NewBlueprint) + { + // Notify the asset registry + FAssetRegistryModule::AssetCreated(NewBlueprint); + + // Mark the package dirty + Package->MarkPackageDirty(); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("name"), AssetName); + ResultObj->SetStringField(TEXT("path"), PackagePath + AssetName); + return ResultObj; + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create blueprint")); +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleAddComponentToBlueprint(const TSharedPtr& Params) +{ + // Get required parameters + FString BlueprintName; + if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter")); + } + + FString ComponentType; + if (!Params->TryGetStringField(TEXT("component_type"), ComponentType)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'type' parameter")); + } + + FString ComponentName; + if (!Params->TryGetStringField(TEXT("component_name"), ComponentName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter")); + } + + // Find the blueprint + UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName); + if (!Blueprint) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName)); + } + + // Create the component - dynamically find the component class by name + UClass* ComponentClass = nullptr; + + // Try to find the class with exact name first + ComponentClass = FindObject(ANY_PACKAGE, *ComponentType); + + // If not found, try with "Component" suffix + if (!ComponentClass && !ComponentType.EndsWith(TEXT("Component"))) + { + FString ComponentTypeWithSuffix = ComponentType + TEXT("Component"); + ComponentClass = FindObject(ANY_PACKAGE, *ComponentTypeWithSuffix); + } + + // If still not found, try with "U" prefix + if (!ComponentClass && !ComponentType.StartsWith(TEXT("U"))) + { + FString ComponentTypeWithPrefix = TEXT("U") + ComponentType; + ComponentClass = FindObject(ANY_PACKAGE, *ComponentTypeWithPrefix); + + // Try with both prefix and suffix + if (!ComponentClass && !ComponentType.EndsWith(TEXT("Component"))) + { + FString ComponentTypeWithBoth = TEXT("U") + ComponentType + TEXT("Component"); + ComponentClass = FindObject(ANY_PACKAGE, *ComponentTypeWithBoth); + } + } + + // Verify that the class is a valid component type + if (!ComponentClass || !ComponentClass->IsChildOf(UActorComponent::StaticClass())) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown component type: %s"), *ComponentType)); + } + + // Add the component to the blueprint + USCS_Node* NewNode = Blueprint->SimpleConstructionScript->CreateNode(ComponentClass, *ComponentName); + if (NewNode) + { + // Set transform if provided + USceneComponent* SceneComponent = Cast(NewNode->ComponentTemplate); + if (SceneComponent) + { + if (Params->HasField(TEXT("location"))) + { + SceneComponent->SetRelativeLocation(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"))); + } + if (Params->HasField(TEXT("rotation"))) + { + SceneComponent->SetRelativeRotation(FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"))); + } + if (Params->HasField(TEXT("scale"))) + { + SceneComponent->SetRelativeScale3D(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"))); + } + } + + // Add to root if no parent specified + Blueprint->SimpleConstructionScript->AddNode(NewNode); + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("component_name"), ComponentName); + ResultObj->SetStringField(TEXT("component_type"), ComponentType); + return ResultObj; + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to add component to blueprint")); +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleSetComponentProperty(const TSharedPtr& 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")); + } + + FString PropertyName; + if (!Params->TryGetStringField(TEXT("property_name"), PropertyName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_name' parameter")); + } + + // Log all input parameters for debugging + UE_LOG(LogTemp, Warning, TEXT("SetComponentProperty - Blueprint: %s, Component: %s, Property: %s"), + *BlueprintName, *ComponentName, *PropertyName); + + // Log property_value if available + if (Params->HasField(TEXT("property_value"))) + { + TSharedPtr JsonValue = Params->Values.FindRef(TEXT("property_value")); + FString ValueType; + + switch(JsonValue->Type) + { + case EJson::Boolean: ValueType = FString::Printf(TEXT("Boolean: %s"), JsonValue->AsBool() ? TEXT("true") : TEXT("false")); break; + case EJson::Number: ValueType = FString::Printf(TEXT("Number: %f"), JsonValue->AsNumber()); break; + case EJson::String: ValueType = FString::Printf(TEXT("String: %s"), *JsonValue->AsString()); break; + case EJson::Array: ValueType = TEXT("Array"); break; + case EJson::Object: ValueType = TEXT("Object"); break; + default: ValueType = TEXT("Unknown"); break; + } + + UE_LOG(LogTemp, Warning, TEXT("SetComponentProperty - Value Type: %s"), *ValueType); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("SetComponentProperty - No property_value provided")); + } + + // Find the blueprint + UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName); + if (!Blueprint) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Blueprint not found: %s"), *BlueprintName); + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName)); + } + else + { + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Blueprint found: %s (Class: %s)"), + *BlueprintName, + Blueprint->GeneratedClass ? *Blueprint->GeneratedClass->GetName() : TEXT("NULL")); + } + + // Find the component + USCS_Node* ComponentNode = nullptr; + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Searching for component %s in blueprint nodes"), *ComponentName); + + if (!Blueprint->SimpleConstructionScript) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - SimpleConstructionScript is NULL for blueprint %s"), *BlueprintName); + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Invalid blueprint construction script")); + } + + for (USCS_Node* Node : Blueprint->SimpleConstructionScript->GetAllNodes()) + { + if (Node) + { + UE_LOG(LogTemp, Verbose, TEXT("SetComponentProperty - Found node: %s"), *Node->GetVariableName().ToString()); + if (Node->GetVariableName().ToString() == ComponentName) + { + ComponentNode = Node; + break; + } + } + else + { + UE_LOG(LogTemp, Warning, TEXT("SetComponentProperty - Found NULL node in blueprint")); + } + } + + if (!ComponentNode) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Component not found: %s"), *ComponentName); + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Component not found: %s"), *ComponentName)); + } + else + { + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Component found: %s (Class: %s)"), + *ComponentName, + ComponentNode->ComponentTemplate ? *ComponentNode->ComponentTemplate->GetClass()->GetName() : TEXT("NULL")); + } + + // Get the component template + UObject* ComponentTemplate = ComponentNode->ComponentTemplate; + if (!ComponentTemplate) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Component template is NULL for %s"), *ComponentName); + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Invalid component template")); + } + + // Check if this is a Spring Arm component and log special debug info + if (ComponentTemplate->GetClass()->GetName().Contains(TEXT("SpringArm"))) + { + UE_LOG(LogTemp, Warning, TEXT("SetComponentProperty - SpringArm component detected! Class: %s"), + *ComponentTemplate->GetClass()->GetPathName()); + + // Log all properties of the SpringArm component class + UE_LOG(LogTemp, Warning, TEXT("SetComponentProperty - SpringArm properties:")); + for (TFieldIterator PropIt(ComponentTemplate->GetClass()); PropIt; ++PropIt) + { + FProperty* Prop = *PropIt; + UE_LOG(LogTemp, Warning, TEXT(" - %s (%s)"), *Prop->GetName(), *Prop->GetCPPType()); + } + + // Special handling for Spring Arm properties + if (Params->HasField(TEXT("property_value"))) + { + TSharedPtr JsonValue = Params->Values.FindRef(TEXT("property_value")); + + // Get the property using the new FField system + FProperty* Property = FindFProperty(ComponentTemplate->GetClass(), *PropertyName); + if (!Property) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Property %s not found on SpringArm component"), *PropertyName); + return FUnrealMCPCommonUtils::CreateErrorResponse( + FString::Printf(TEXT("Property %s not found on SpringArm component"), *PropertyName)); + } + + // Create a scope guard to ensure property cleanup + struct FScopeGuard + { + UObject* Object; + FScopeGuard(UObject* InObject) : Object(InObject) + { + if (Object) + { + Object->Modify(); + } + } + ~FScopeGuard() + { + if (Object) + { + Object->PostEditChange(); + } + } + } ScopeGuard(ComponentTemplate); + + bool bSuccess = false; + FString ErrorMessage; + + // Handle specific Spring Arm property types + if (FFloatProperty* FloatProp = CastField(Property)) + { + if (JsonValue->Type == EJson::Number) + { + const float Value = JsonValue->AsNumber(); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Setting float property %s to %f"), *PropertyName, Value); + FloatProp->SetPropertyValue_InContainer(ComponentTemplate, Value); + bSuccess = true; + } + } + else if (FBoolProperty* BoolProp = CastField(Property)) + { + if (JsonValue->Type == EJson::Boolean) + { + const bool Value = JsonValue->AsBool(); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Setting bool property %s to %d"), *PropertyName, Value); + BoolProp->SetPropertyValue_InContainer(ComponentTemplate, Value); + bSuccess = true; + } + } + else if (FStructProperty* StructProp = CastField(Property)) + { + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Handling struct property %s of type %s"), + *PropertyName, *StructProp->Struct->GetName()); + + // Special handling for common Spring Arm struct properties + if (StructProp->Struct == TBaseStructure::Get()) + { + if (JsonValue->Type == EJson::Array) + { + const TArray>& Arr = JsonValue->AsArray(); + if (Arr.Num() == 3) + { + FVector Vec( + Arr[0]->AsNumber(), + Arr[1]->AsNumber(), + Arr[2]->AsNumber() + ); + void* PropertyAddr = StructProp->ContainerPtrToValuePtr(ComponentTemplate); + StructProp->CopySingleValue(PropertyAddr, &Vec); + bSuccess = true; + } + } + } + else if (StructProp->Struct == TBaseStructure::Get()) + { + if (JsonValue->Type == EJson::Array) + { + const TArray>& Arr = JsonValue->AsArray(); + if (Arr.Num() == 3) + { + FRotator Rot( + Arr[0]->AsNumber(), + Arr[1]->AsNumber(), + Arr[2]->AsNumber() + ); + void* PropertyAddr = StructProp->ContainerPtrToValuePtr(ComponentTemplate); + StructProp->CopySingleValue(PropertyAddr, &Rot); + bSuccess = true; + } + } + } + } + + if (bSuccess) + { + // Mark the blueprint as modified + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Successfully set SpringArm property %s"), *PropertyName); + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("component"), ComponentName); + ResultObj->SetStringField(TEXT("property"), PropertyName); + ResultObj->SetBoolField(TEXT("success"), true); + return ResultObj; + } + else + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Failed to set SpringArm property %s"), *PropertyName); + return FUnrealMCPCommonUtils::CreateErrorResponse( + FString::Printf(TEXT("Failed to set SpringArm property %s"), *PropertyName)); + } + } + } + + // Regular property handling for non-Spring Arm components continues... + + // Set the property value + if (Params->HasField(TEXT("property_value"))) + { + TSharedPtr JsonValue = Params->Values.FindRef(TEXT("property_value")); + + // Get the property + FProperty* Property = FindFProperty(ComponentTemplate->GetClass(), *PropertyName); + if (!Property) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Property %s not found on component %s"), + *PropertyName, *ComponentName); + + // List all available properties for this component + UE_LOG(LogTemp, Warning, TEXT("SetComponentProperty - Available properties for %s:"), *ComponentName); + for (TFieldIterator PropIt(ComponentTemplate->GetClass()); PropIt; ++PropIt) + { + FProperty* Prop = *PropIt; + UE_LOG(LogTemp, Warning, TEXT(" - %s (%s)"), *Prop->GetName(), *Prop->GetCPPType()); + } + + return FUnrealMCPCommonUtils::CreateErrorResponse( + FString::Printf(TEXT("Property %s not found on component %s"), *PropertyName, *ComponentName)); + } + else + { + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Property found: %s (Type: %s)"), + *PropertyName, *Property->GetCPPType()); + } + + bool bSuccess = false; + FString ErrorMessage; + + // Handle different property types + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Attempting to set property %s"), *PropertyName); + + // Add try-catch block to catch and log any crashes + try + { + if (FStructProperty* StructProp = CastField(Property)) + { + // Handle vector properties + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Property is a struct: %s"), + StructProp->Struct ? *StructProp->Struct->GetName() : TEXT("NULL")); + + if (StructProp->Struct == TBaseStructure::Get()) + { + if (JsonValue->Type == EJson::Array) + { + // Handle array input [x, y, z] + const TArray>& Arr = JsonValue->AsArray(); + if (Arr.Num() == 3) + { + FVector Vec( + Arr[0]->AsNumber(), + Arr[1]->AsNumber(), + Arr[2]->AsNumber() + ); + void* PropertyAddr = StructProp->ContainerPtrToValuePtr(ComponentTemplate); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Setting Vector(%f, %f, %f)"), + Vec.X, Vec.Y, Vec.Z); + StructProp->CopySingleValue(PropertyAddr, &Vec); + bSuccess = true; + } + else + { + ErrorMessage = FString::Printf(TEXT("Vector property requires 3 values, got %d"), Arr.Num()); + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - %s"), *ErrorMessage); + } + } + else if (JsonValue->Type == EJson::Number) + { + // Handle scalar input (sets all components to same value) + float Value = JsonValue->AsNumber(); + FVector Vec(Value, Value, Value); + void* PropertyAddr = StructProp->ContainerPtrToValuePtr(ComponentTemplate); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Setting Vector(%f, %f, %f) from scalar"), + Vec.X, Vec.Y, Vec.Z); + StructProp->CopySingleValue(PropertyAddr, &Vec); + bSuccess = true; + } + else + { + ErrorMessage = TEXT("Vector property requires either a single number or array of 3 numbers"); + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - %s"), *ErrorMessage); + } + } + else + { + // Handle other struct properties using default handler + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Using generic struct handler for %s"), + *PropertyName); + bSuccess = FUnrealMCPCommonUtils::SetObjectProperty(ComponentTemplate, PropertyName, JsonValue, ErrorMessage); + if (!bSuccess) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Failed to set struct property: %s"), *ErrorMessage); + } + } + } + else if (FEnumProperty* EnumProp = CastField(Property)) + { + // Handle enum properties + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Property is an enum")); + if (JsonValue->Type == EJson::String) + { + FString EnumValueName = JsonValue->AsString(); + UEnum* Enum = EnumProp->GetEnum(); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Setting enum from string: %s"), *EnumValueName); + + if (Enum) + { + int64 EnumValue = Enum->GetValueByNameString(EnumValueName); + + if (EnumValue != INDEX_NONE) + { + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Found enum value: %lld"), EnumValue); + EnumProp->GetUnderlyingProperty()->SetIntPropertyValue( + ComponentTemplate, + EnumValue + ); + bSuccess = true; + } + else + { + // List all possible enum values + UE_LOG(LogTemp, Warning, TEXT("SetComponentProperty - Available enum values for %s:"), + *Enum->GetName()); + for (int32 i = 0; i < Enum->NumEnums(); i++) + { + UE_LOG(LogTemp, Warning, TEXT(" - %s (%lld)"), + *Enum->GetNameStringByIndex(i), + Enum->GetValueByIndex(i)); + } + + ErrorMessage = FString::Printf(TEXT("Invalid enum value '%s' for property %s"), + *EnumValueName, *PropertyName); + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - %s"), *ErrorMessage); + } + } + else + { + ErrorMessage = TEXT("Enum object is NULL"); + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - %s"), *ErrorMessage); + } + } + else if (JsonValue->Type == EJson::Number) + { + // Allow setting enum by integer value + int64 EnumValue = JsonValue->AsNumber(); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Setting enum from number: %lld"), EnumValue); + EnumProp->GetUnderlyingProperty()->SetIntPropertyValue( + ComponentTemplate, + EnumValue + ); + bSuccess = true; + } + else + { + ErrorMessage = TEXT("Enum property requires either a string name or integer value"); + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - %s"), *ErrorMessage); + } + } + else if (FNumericProperty* NumericProp = CastField(Property)) + { + // Handle numeric properties + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Property is numeric: IsInteger=%d, IsFloat=%d"), + NumericProp->IsInteger(), NumericProp->IsFloatingPoint()); + + if (JsonValue->Type == EJson::Number) + { + double Value = JsonValue->AsNumber(); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Setting numeric value: %f"), Value); + + if (NumericProp->IsInteger()) + { + NumericProp->SetIntPropertyValue(ComponentTemplate, (int64)Value); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Set integer value: %lld"), (int64)Value); + bSuccess = true; + } + else if (NumericProp->IsFloatingPoint()) + { + NumericProp->SetFloatingPointPropertyValue(ComponentTemplate, Value); + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Set float value: %f"), Value); + bSuccess = true; + } + } + else + { + ErrorMessage = TEXT("Numeric property requires a number value"); + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - %s"), *ErrorMessage); + } + } + else + { + // Handle all other property types using default handler + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Using generic property handler for %s (Type: %s)"), + *PropertyName, *Property->GetCPPType()); + bSuccess = FUnrealMCPCommonUtils::SetObjectProperty(ComponentTemplate, PropertyName, JsonValue, ErrorMessage); + if (!bSuccess) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Failed to set property: %s"), *ErrorMessage); + } + } + } + catch (const std::exception& Ex) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - EXCEPTION: %s"), ANSI_TO_TCHAR(Ex.what())); + return FUnrealMCPCommonUtils::CreateErrorResponse( + FString::Printf(TEXT("Exception while setting property %s: %s"), *PropertyName, ANSI_TO_TCHAR(Ex.what()))); + } + catch (...) + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - UNKNOWN EXCEPTION occurred while setting property %s"), *PropertyName); + return FUnrealMCPCommonUtils::CreateErrorResponse( + FString::Printf(TEXT("Unknown exception while setting property %s"), *PropertyName)); + } + + if (bSuccess) + { + // Mark the blueprint as modified + UE_LOG(LogTemp, Log, TEXT("SetComponentProperty - Successfully set property %s on component %s"), + *PropertyName, *ComponentName); + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("component"), ComponentName); + ResultObj->SetStringField(TEXT("property"), PropertyName); + ResultObj->SetBoolField(TEXT("success"), true); + return ResultObj; + } + else + { + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Failed to set property %s: %s"), + *PropertyName, *ErrorMessage); + return FUnrealMCPCommonUtils::CreateErrorResponse(ErrorMessage); + } + } + + UE_LOG(LogTemp, Error, TEXT("SetComponentProperty - Missing 'property_value' parameter")); + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_value' parameter")); +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleSetPhysicsProperties(const TSharedPtr& 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")); + } + + // Find the blueprint + UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName); + if (!Blueprint) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName)); + } + + // Find the component + USCS_Node* ComponentNode = nullptr; + for (USCS_Node* Node : Blueprint->SimpleConstructionScript->GetAllNodes()) + { + if (Node && Node->GetVariableName().ToString() == ComponentName) + { + ComponentNode = Node; + break; + } + } + + if (!ComponentNode) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Component not found: %s"), *ComponentName)); + } + + UPrimitiveComponent* PrimComponent = Cast(ComponentNode->ComponentTemplate); + if (!PrimComponent) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Component is not a primitive component")); + } + + // Set physics properties + if (Params->HasField(TEXT("simulate_physics"))) + { + PrimComponent->SetSimulatePhysics(Params->GetBoolField(TEXT("simulate_physics"))); + } + + if (Params->HasField(TEXT("mass"))) + { + float Mass = Params->GetNumberField(TEXT("mass")); + // In UE5.5, use proper overrideMass instead of just scaling + PrimComponent->SetMassOverrideInKg(NAME_None, Mass); + UE_LOG(LogTemp, Display, TEXT("Set mass for component %s to %f kg"), *ComponentName, Mass); + } + + if (Params->HasField(TEXT("linear_damping"))) + { + PrimComponent->SetLinearDamping(Params->GetNumberField(TEXT("linear_damping"))); + } + + if (Params->HasField(TEXT("angular_damping"))) + { + PrimComponent->SetAngularDamping(Params->GetNumberField(TEXT("angular_damping"))); + } + + // Mark the blueprint as modified + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("component"), ComponentName); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleCompileBlueprint(const TSharedPtr& Params) +{ + // Get required parameters + FString BlueprintName; + if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter")); + } + + // Find the blueprint + UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName); + if (!Blueprint) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName)); + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("name"), BlueprintName); + ResultObj->SetBoolField(TEXT("compiled"), true); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleSpawnBlueprintActor(const TSharedPtr& 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 + UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName); + 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); + + if (Params->HasField(TEXT("location"))) + { + Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location")); + } + if (Params->HasField(TEXT("rotation"))) + { + Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation")); + } + + // 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)); + + AActor* NewActor = World->SpawnActor(Blueprint->GeneratedClass, SpawnTransform); + if (NewActor) + { + NewActor->SetActorLabel(*ActorName); + return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true); + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to spawn blueprint actor")); +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleSetBlueprintProperty(const TSharedPtr& Params) +{ + // Get required parameters + FString BlueprintName; + if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter")); + } + + FString PropertyName; + if (!Params->TryGetStringField(TEXT("property_name"), PropertyName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_name' parameter")); + } + + // Find the blueprint + UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName); + if (!Blueprint) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName)); + } + + // Get the default object + UObject* DefaultObject = Blueprint->GeneratedClass->GetDefaultObject(); + if (!DefaultObject) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get default object")); + } + + // Set the property value + if (Params->HasField(TEXT("property_value"))) + { + TSharedPtr JsonValue = Params->Values.FindRef(TEXT("property_value")); + + FString ErrorMessage; + if (FUnrealMCPCommonUtils::SetObjectProperty(DefaultObject, PropertyName, JsonValue, ErrorMessage)) + { + // Mark the blueprint as modified + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("property"), PropertyName); + ResultObj->SetBoolField(TEXT("success"), true); + return ResultObj; + } + else + { + return FUnrealMCPCommonUtils::CreateErrorResponse(ErrorMessage); + } + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_value' parameter")); +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleSetStaticMeshProperties(const TSharedPtr& 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")); + } + + // Find the blueprint + UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName); + if (!Blueprint) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName)); + } + + // Find the component + USCS_Node* ComponentNode = nullptr; + for (USCS_Node* Node : Blueprint->SimpleConstructionScript->GetAllNodes()) + { + if (Node && Node->GetVariableName().ToString() == ComponentName) + { + ComponentNode = Node; + break; + } + } + + if (!ComponentNode) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Component not found: %s"), *ComponentName)); + } + + UStaticMeshComponent* MeshComponent = Cast(ComponentNode->ComponentTemplate); + if (!MeshComponent) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Component is not a static mesh component")); + } + + // Set static mesh properties + if (Params->HasField(TEXT("static_mesh"))) + { + FString MeshPath = Params->GetStringField(TEXT("static_mesh")); + UStaticMesh* Mesh = Cast(UEditorAssetLibrary::LoadAsset(MeshPath)); + if (Mesh) + { + MeshComponent->SetStaticMesh(Mesh); + } + } + + if (Params->HasField(TEXT("material"))) + { + FString MaterialPath = Params->GetStringField(TEXT("material")); + UMaterialInterface* Material = Cast(UEditorAssetLibrary::LoadAsset(MaterialPath)); + if (Material) + { + MeshComponent->SetMaterial(0, Material); + } + } + + // Mark the blueprint as modified + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("component"), ComponentName); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintCommands::HandleSetPawnProperties(const TSharedPtr& Params) +{ + // Get required parameters + FString BlueprintName; + if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter")); + } + + // Find the blueprint + UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName); + if (!Blueprint) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName)); + } + + // Get the default object + UObject* DefaultObject = Blueprint->GeneratedClass->GetDefaultObject(); + if (!DefaultObject) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get default object")); + } + + // Track if any properties were set successfully + bool bAnyPropertiesSet = false; + TSharedPtr ResultsObj = MakeShared(); + + // Set auto possess player if specified + if (Params->HasField(TEXT("auto_possess_player"))) + { + TSharedPtr AutoPossessValue = Params->Values.FindRef(TEXT("auto_possess_player")); + + FString ErrorMessage; + if (FUnrealMCPCommonUtils::SetObjectProperty(DefaultObject, TEXT("AutoPossessPlayer"), AutoPossessValue, ErrorMessage)) + { + bAnyPropertiesSet = true; + TSharedPtr PropResultObj = MakeShared(); + PropResultObj->SetBoolField(TEXT("success"), true); + ResultsObj->SetObjectField(TEXT("AutoPossessPlayer"), PropResultObj); + } + else + { + TSharedPtr PropResultObj = MakeShared(); + PropResultObj->SetBoolField(TEXT("success"), false); + PropResultObj->SetStringField(TEXT("error"), ErrorMessage); + ResultsObj->SetObjectField(TEXT("AutoPossessPlayer"), PropResultObj); + } + } + + // Set controller rotation properties + const TCHAR* RotationProps[] = { + TEXT("bUseControllerRotationYaw"), + TEXT("bUseControllerRotationPitch"), + TEXT("bUseControllerRotationRoll") + }; + + const TCHAR* ParamNames[] = { + TEXT("use_controller_rotation_yaw"), + TEXT("use_controller_rotation_pitch"), + TEXT("use_controller_rotation_roll") + }; + + for (int32 i = 0; i < 3; i++) + { + if (Params->HasField(ParamNames[i])) + { + TSharedPtr Value = Params->Values.FindRef(ParamNames[i]); + + FString ErrorMessage; + if (FUnrealMCPCommonUtils::SetObjectProperty(DefaultObject, RotationProps[i], Value, ErrorMessage)) + { + bAnyPropertiesSet = true; + TSharedPtr PropResultObj = MakeShared(); + PropResultObj->SetBoolField(TEXT("success"), true); + ResultsObj->SetObjectField(RotationProps[i], PropResultObj); + } + else + { + TSharedPtr PropResultObj = MakeShared(); + PropResultObj->SetBoolField(TEXT("success"), false); + PropResultObj->SetStringField(TEXT("error"), ErrorMessage); + ResultsObj->SetObjectField(RotationProps[i], PropResultObj); + } + } + } + + // Set can be damaged property + if (Params->HasField(TEXT("can_be_damaged"))) + { + TSharedPtr Value = Params->Values.FindRef(TEXT("can_be_damaged")); + + FString ErrorMessage; + if (FUnrealMCPCommonUtils::SetObjectProperty(DefaultObject, TEXT("bCanBeDamaged"), Value, ErrorMessage)) + { + bAnyPropertiesSet = true; + TSharedPtr PropResultObj = MakeShared(); + PropResultObj->SetBoolField(TEXT("success"), true); + ResultsObj->SetObjectField(TEXT("bCanBeDamaged"), PropResultObj); + } + else + { + TSharedPtr PropResultObj = MakeShared(); + PropResultObj->SetBoolField(TEXT("success"), false); + PropResultObj->SetStringField(TEXT("error"), ErrorMessage); + ResultsObj->SetObjectField(TEXT("bCanBeDamaged"), PropResultObj); + } + } + + // Mark the blueprint as modified if any properties were set + if (bAnyPropertiesSet) + { + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + } + else if (ResultsObj->Values.Num() == 0) + { + // No properties were specified + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("No properties specified to set")); + } + + TSharedPtr ResponseObj = MakeShared(); + ResponseObj->SetStringField(TEXT("blueprint"), BlueprintName); + ResponseObj->SetBoolField(TEXT("success"), bAnyPropertiesSet); + ResponseObj->SetObjectField(TEXT("results"), ResultsObj); + return ResponseObj; +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp new file mode 100644 index 0000000..c0be1f4 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp @@ -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 FUnrealMCPBlueprintNodeCommands::HandleCommand(const FString& CommandType, const TSharedPtr& 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 FUnrealMCPBlueprintNodeCommands::HandleConnectBlueprintNodes(const TSharedPtr& 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 ResultObj = MakeShared(); + 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 FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintGetSelfComponentReference(const TSharedPtr& 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(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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("node_id"), GetComponentNode->NodeGuid.ToString()); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintEvent(const TSharedPtr& 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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("node_id"), EventNode->NodeGuid.ToString()); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintFunctionCall(const TSharedPtr& 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(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(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 PossibleClassNames; + PossibleClassNames.Add(FString(TEXT("U")) + Target + TEXT("Component")); + PossibleClassNames.Add(Target + TEXT("Component")); + + for (const FString& ClassName : PossibleClassNames) + { + TargetClass = FindObject(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(ANY_PACKAGE, TEXT("UGameplayStatics")); + if (!TargetClass) + { + // Try loading it from its known package + TargetClass = LoadObject(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 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(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* ParamsObj; + if (Params->TryGetObjectField(TEXT("params"), ParamsObj)) + { + // Process parameters + for (const TPair>& Param : (*ParamsObj)->Values) + { + const FString& ParamName = Param.Key; + const TSharedPtr& 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(ANY_PACKAGE, *ClassName); + + if (!Class) + { + Class = LoadObject(nullptr, *ClassName); + UE_LOG(LogUnrealMCP, Display, TEXT("FindObject 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(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(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::Get()) + { + // Handle array parameters - like Vector parameters + const TArray>* 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>* 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::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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("node_id"), FunctionNode->NodeGuid.ToString()); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintVariable(const TSharedPtr& 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::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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("variable_name"), VariableName); + ResultObj->SetStringField(TEXT("variable_type"), VariableType); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintInputActionNode(const TSharedPtr& 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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("node_id"), InputActionNode->NodeGuid.ToString()); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintSelfReference(const TSharedPtr& 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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("node_id"), SelfNode->NodeGuid.ToString()); + return ResultObj; +} + +TSharedPtr FUnrealMCPBlueprintNodeCommands::HandleFindBlueprintNodes(const TSharedPtr& 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> 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(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(EventNode->NodeGuid.ToString())); + } + } + } + // Add other node types as needed (InputAction, etc.) + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetArrayField(TEXT("node_guids"), NodeGuidArray); + + return ResultObj; +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp new file mode 100644 index 0000000..e5e2c00 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp @@ -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 FUnrealMCPCommonUtils::CreateErrorResponse(const FString& Message) +{ + TSharedPtr ResponseObject = MakeShared(); + ResponseObject->SetBoolField(TEXT("success"), false); + ResponseObject->SetStringField(TEXT("error"), Message); + return ResponseObject; +} + +TSharedPtr FUnrealMCPCommonUtils::CreateSuccessResponse(const TSharedPtr& Data) +{ + TSharedPtr ResponseObject = MakeShared(); + ResponseObject->SetBoolField(TEXT("success"), true); + + if (Data.IsValid()) + { + ResponseObject->SetObjectField(TEXT("data"), Data); + } + + return ResponseObject; +} + +void FUnrealMCPCommonUtils::GetIntArrayFromJson(const TSharedPtr& JsonObject, const FString& FieldName, TArray& OutArray) +{ + OutArray.Reset(); + + if (!JsonObject->HasField(FieldName)) + { + return; + } + + const TArray>* JsonArray; + if (JsonObject->TryGetArrayField(FieldName, JsonArray)) + { + for (const TSharedPtr& Value : *JsonArray) + { + OutArray.Add((int32)Value->AsNumber()); + } + } +} + +void FUnrealMCPCommonUtils::GetFloatArrayFromJson(const TSharedPtr& JsonObject, const FString& FieldName, TArray& OutArray) +{ + OutArray.Reset(); + + if (!JsonObject->HasField(FieldName)) + { + return; + } + + const TArray>* JsonArray; + if (JsonObject->TryGetArrayField(FieldName, JsonArray)) + { + for (const TSharedPtr& Value : *JsonArray) + { + OutArray.Add((float)Value->AsNumber()); + } + } +} + +FVector2D FUnrealMCPCommonUtils::GetVector2DFromJson(const TSharedPtr& JsonObject, const FString& FieldName) +{ + FVector2D Result(0.0f, 0.0f); + + if (!JsonObject->HasField(FieldName)) + { + return Result; + } + + const TArray>* 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& JsonObject, const FString& FieldName) +{ + FVector Result(0.0f, 0.0f, 0.0f); + + if (!JsonObject->HasField(FieldName)) + { + return Result; + } + + const TArray>* 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& JsonObject, const FString& FieldName) +{ + FRotator Result(0.0f, 0.0f, 0.0f); + + if (!JsonObject->HasField(FieldName)) + { + return Result; + } + + const TArray>* 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(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(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(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(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(Graph); + + FName VarName(*VariableName); + FProperty* Property = FindFProperty(Blueprint->GeneratedClass, VarName); + + if (Property) + { + VariableGetNode->VariableReference.SetFromField(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(Graph); + + FName VarName(*VariableName); + FProperty* Property = FindFProperty(Blueprint->GeneratedClass, VarName); + + if (Property) + { + VariableSetNode->VariableReference.SetFromField(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(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(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(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 FUnrealMCPCommonUtils::ActorToJson(AActor* Actor) +{ + if (!Actor) + { + return MakeShared(); + } + + TSharedPtr ActorObject = MakeShared(); + ActorObject->SetStringField(TEXT("name"), Actor->GetName()); + ActorObject->SetStringField(TEXT("class"), Actor->GetClass()->GetName()); + + FVector Location = Actor->GetActorLocation(); + TArray> LocationArray; + LocationArray.Add(MakeShared(Location.X)); + LocationArray.Add(MakeShared(Location.Y)); + LocationArray.Add(MakeShared(Location.Z)); + ActorObject->SetArrayField(TEXT("location"), LocationArray); + + FRotator Rotation = Actor->GetActorRotation(); + TArray> RotationArray; + RotationArray.Add(MakeShared(Rotation.Pitch)); + RotationArray.Add(MakeShared(Rotation.Yaw)); + RotationArray.Add(MakeShared(Rotation.Roll)); + ActorObject->SetArrayField(TEXT("rotation"), RotationArray); + + FVector Scale = Actor->GetActorScale3D(); + TArray> ScaleArray; + ScaleArray.Add(MakeShared(Scale.X)); + ScaleArray.Add(MakeShared(Scale.Y)); + ScaleArray.Add(MakeShared(Scale.Z)); + ActorObject->SetArrayField(TEXT("scale"), ScaleArray); + + return MakeShared(ActorObject); +} + +TSharedPtr FUnrealMCPCommonUtils::ActorToJsonObject(AActor* Actor, bool bDetailed) +{ + if (!Actor) + { + return nullptr; + } + + TSharedPtr ActorObject = MakeShared(); + ActorObject->SetStringField(TEXT("name"), Actor->GetName()); + ActorObject->SetStringField(TEXT("class"), Actor->GetClass()->GetName()); + + FVector Location = Actor->GetActorLocation(); + TArray> LocationArray; + LocationArray.Add(MakeShared(Location.X)); + LocationArray.Add(MakeShared(Location.Y)); + LocationArray.Add(MakeShared(Location.Z)); + ActorObject->SetArrayField(TEXT("location"), LocationArray); + + FRotator Rotation = Actor->GetActorRotation(); + TArray> RotationArray; + RotationArray.Add(MakeShared(Rotation.Pitch)); + RotationArray.Add(MakeShared(Rotation.Yaw)); + RotationArray.Add(MakeShared(Rotation.Roll)); + ActorObject->SetArrayField(TEXT("rotation"), RotationArray); + + FVector Scale = Actor->GetActorScale3D(); + TArray> ScaleArray; + ScaleArray.Add(MakeShared(Scale.X)); + ScaleArray.Add(MakeShared(Scale.Y)); + ScaleArray.Add(MakeShared(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(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& 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(Object); + + // Handle different property types + if (Property->IsA()) + { + ((FBoolProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsBool()); + return true; + } + else if (Property->IsA()) + { + int32 IntValue = static_cast(Value->AsNumber()); + FIntProperty* IntProperty = CastField(Property); + if (IntProperty) + { + IntProperty->SetPropertyValue_InContainer(Object, IntValue); + return true; + } + } + else if (Property->IsA()) + { + ((FFloatProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsNumber()); + return true; + } + else if (Property->IsA()) + { + ((FStrProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsString()); + return true; + } + else if (Property->IsA()) + { + FByteProperty* ByteProp = CastField(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(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(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(Value->AsNumber()); + ByteProp->SetPropertyValue(PropertyAddr, ByteValue); + return true; + } + } + else if (Property->IsA()) + { + FEnumProperty* EnumProp = CastField(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(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; +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp new file mode 100644 index 0000000..235e7f1 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp @@ -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 FUnrealMCPEditorCommands::HandleCommand(const FString& CommandType, const TSharedPtr& 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 FUnrealMCPEditorCommands::HandleGetActorsInLevel(const TSharedPtr& Params) +{ + TArray AllActors; + UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors); + + TArray> ActorArray; + for (AActor* Actor : AllActors) + { + if (Actor) + { + ActorArray.Add(FUnrealMCPCommonUtils::ActorToJson(Actor)); + } + } + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetArrayField(TEXT("actors"), ActorArray); + + return ResultObj; +} + +TSharedPtr FUnrealMCPEditorCommands::HandleFindActorsByName(const TSharedPtr& Params) +{ + FString Pattern; + if (!Params->TryGetStringField(TEXT("pattern"), Pattern)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'pattern' parameter")); + } + + TArray AllActors; + UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors); + + TArray> MatchingActors; + for (AActor* Actor : AllActors) + { + if (Actor && Actor->GetName().Contains(Pattern)) + { + MatchingActors.Add(FUnrealMCPCommonUtils::ActorToJson(Actor)); + } + } + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetArrayField(TEXT("actors"), MatchingActors); + + return ResultObj; +} + +TSharedPtr FUnrealMCPEditorCommands::HandleSpawnActor(const TSharedPtr& 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 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::StaticClass(), Location, Rotation, SpawnParams); + } + else if (ActorType == TEXT("PointLight")) + { + NewActor = World->SpawnActor(APointLight::StaticClass(), Location, Rotation, SpawnParams); + } + else if (ActorType == TEXT("SpotLight")) + { + NewActor = World->SpawnActor(ASpotLight::StaticClass(), Location, Rotation, SpawnParams); + } + else if (ActorType == TEXT("DirectionalLight")) + { + NewActor = World->SpawnActor(ADirectionalLight::StaticClass(), Location, Rotation, SpawnParams); + } + else if (ActorType == TEXT("CameraActor")) + { + NewActor = World->SpawnActor(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 FUnrealMCPEditorCommands::HandleDeleteActor(const TSharedPtr& Params) +{ + FString ActorName; + if (!Params->TryGetStringField(TEXT("name"), ActorName)) + { + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter")); + } + + TArray 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 ActorInfo = FUnrealMCPCommonUtils::ActorToJsonObject(Actor); + + // Delete the actor + Actor->Destroy(); + + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetObjectField(TEXT("deleted_actor"), ActorInfo); + return ResultObj; + } + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName)); +} + +TSharedPtr FUnrealMCPEditorCommands::HandleSetActorTransform(const TSharedPtr& 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 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 FUnrealMCPEditorCommands::HandleGetActorProperties(const TSharedPtr& 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 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 FUnrealMCPEditorCommands::HandleSetActorProperty(const TSharedPtr& 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 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 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 ResultObj = MakeShared(); + 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 FUnrealMCPEditorCommands::HandleSpawnBlueprintActor(const TSharedPtr& 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(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(Blueprint->GeneratedClass, SpawnTransform, SpawnParams); + if (NewActor) + { + return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true); + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to spawn blueprint actor")); +} + +TSharedPtr FUnrealMCPEditorCommands::HandleFocusViewport(const TSharedPtr& 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 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 ResultObj = MakeShared(); + ResultObj->SetBoolField(TEXT("success"), true); + return ResultObj; +} + +TSharedPtr FUnrealMCPEditorCommands::HandleTakeScreenshot(const TSharedPtr& 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 Bitmap; + FIntRect ViewportRect(0, 0, Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y); + + if (Viewport->ReadPixels(Bitmap, FReadSurfaceDataFlags(), ViewportRect)) + { + TArray CompressedBitmap; + FImageUtils::CompressImageArray(Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y, Bitmap, CompressedBitmap); + + if (FFileHelper::SaveArrayToFile(CompressedBitmap, *FilePath)) + { + TSharedPtr ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("filepath"), FilePath); + return ResultObj; + } + } + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot")); +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPProjectCommands.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPProjectCommands.cpp new file mode 100644 index 0000000..bbf85af --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPProjectCommands.cpp @@ -0,0 +1,72 @@ +#include "Commands/UnrealMCPProjectCommands.h" +#include "Commands/UnrealMCPCommonUtils.h" +#include "GameFramework/InputSettings.h" + +FUnrealMCPProjectCommands::FUnrealMCPProjectCommands() +{ +} + +TSharedPtr FUnrealMCPProjectCommands::HandleCommand(const FString& CommandType, const TSharedPtr& Params) +{ + if (CommandType == TEXT("create_input_mapping")) + { + return HandleCreateInputMapping(Params); + } + + return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown project command: %s"), *CommandType)); +} + +TSharedPtr FUnrealMCPProjectCommands::HandleCreateInputMapping(const TSharedPtr& 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(); + 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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("action_name"), ActionName); + ResultObj->SetStringField(TEXT("key"), Key); + return ResultObj; +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPUMGCommands.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPUMGCommands.cpp new file mode 100644 index 0000000..ceb4193 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPUMGCommands.cpp @@ -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 FUnrealMCPUMGCommands::HandleCommand(const FString& CommandName, const TSharedPtr& 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 FUnrealMCPUMGCommands::HandleCreateUMGWidgetBlueprint(const TSharedPtr& 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(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::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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("name"), BlueprintName); + ResultObj->SetStringField(TEXT("path"), FullPath); + return ResultObj; +} + +TSharedPtr FUnrealMCPUMGCommands::HandleAddTextBlockToWidget(const TSharedPtr& 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(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>* 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::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(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 ResultObj = MakeShared(); + ResultObj->SetStringField(TEXT("widget_name"), WidgetName); + ResultObj->SetStringField(TEXT("text"), InitialText); + return ResultObj; +} + +TSharedPtr FUnrealMCPUMGCommands::HandleAddWidgetToViewport(const TSharedPtr& 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(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 ResultObj = MakeShared(); + 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 FUnrealMCPUMGCommands::HandleAddButtonToWidget(const TSharedPtr& Params) +{ + TSharedPtr Response = MakeShared(); + + // 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(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(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(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(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>* 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 FUnrealMCPUMGCommands::HandleBindWidgetEvent(const TSharedPtr& Params) +{ + TSharedPtr Response = MakeShared(); + + // 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(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 AllEventNodes; + FBlueprintEditorUtils::GetAllNodesOfClass(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 UpdatedEventNodes; + FBlueprintEditorUtils::GetAllNodesOfClass(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 FUnrealMCPUMGCommands::HandleSetTextBlockBinding(const TSharedPtr& Params) +{ + TSharedPtr Response = MakeShared(); + + // 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(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(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(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(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(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; +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp new file mode 100644 index 0000000..2fed46f --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp @@ -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 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 JsonObject; + TSharedRef> 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 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 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 Client, const FString& Message) +{ + UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message: %s"), *Message); + + // Parse message as JSON + TSharedPtr JsonMessage; + TSharedRef> 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 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 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")); + } +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp new file mode 100644 index 0000000..e76c47b --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp @@ -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(); + BlueprintCommands = MakeShared(); + BlueprintNodeCommands = MakeShared(); + ProjectCommands = MakeShared(); + UMGCommands = MakeShared(); +} + +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 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& Params) +{ + UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Executing command: %s"), *CommandType); + + // Create a promise to wait for the result + TPromise Promise; + TFuture Future = Promise.GetFuture(); + + // Queue execution on Game Thread + AsyncTask(ENamedThreads::GameThread, [this, CommandType, Params, Promise = MoveTemp(Promise)]() mutable + { + TSharedPtr ResponseJson = MakeShareable(new FJsonObject); + + try + { + TSharedPtr 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> 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> Writer = TJsonWriterFactory<>::Create(&ResultString); + FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer); + Promise.SetValue(ResultString); + }); + + return Future.Get(); +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPModule.cpp b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPModule.cpp new file mode 100644 index 0000000..f53c55f --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPModule.cpp @@ -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) \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h new file mode 100644 index 0000000..9c2c22a --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h @@ -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 HandleCommand(const FString& CommandType, const TSharedPtr& Params); + +private: + // Specific blueprint command handlers + TSharedPtr HandleCreateBlueprint(const TSharedPtr& Params); + TSharedPtr HandleAddComponentToBlueprint(const TSharedPtr& Params); + TSharedPtr HandleSetComponentProperty(const TSharedPtr& Params); + TSharedPtr HandleSetPhysicsProperties(const TSharedPtr& Params); + TSharedPtr HandleCompileBlueprint(const TSharedPtr& Params); + TSharedPtr HandleSpawnBlueprintActor(const TSharedPtr& Params); + TSharedPtr HandleSetBlueprintProperty(const TSharedPtr& Params); + TSharedPtr HandleSetStaticMeshProperties(const TSharedPtr& Params); + TSharedPtr HandleSetPawnProperties(const TSharedPtr& Params); + + // Helper functions + TSharedPtr AddComponentToBlueprint(const FString& BlueprintName, const FString& ComponentType, + const FString& ComponentName, const FString& MeshType, + const TArray& Location, const TArray& Rotation, + const TArray& Scale, const TSharedPtr& ComponentProperties); +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h new file mode 100644 index 0000000..9146253 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h @@ -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 HandleCommand(const FString& CommandType, const TSharedPtr& Params); + +private: + // Specific blueprint node command handlers + TSharedPtr HandleConnectBlueprintNodes(const TSharedPtr& Params); + TSharedPtr HandleAddBlueprintGetSelfComponentReference(const TSharedPtr& Params); + TSharedPtr HandleAddBlueprintEvent(const TSharedPtr& Params); + TSharedPtr HandleAddBlueprintFunctionCall(const TSharedPtr& Params); + TSharedPtr HandleAddBlueprintVariable(const TSharedPtr& Params); + TSharedPtr HandleAddBlueprintInputActionNode(const TSharedPtr& Params); + TSharedPtr HandleAddBlueprintSelfReference(const TSharedPtr& Params); + TSharedPtr HandleFindBlueprintNodes(const TSharedPtr& Params); +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommonUtils.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommonUtils.h new file mode 100644 index 0000000..bf528e3 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommonUtils.h @@ -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 CreateErrorResponse(const FString& Message); + static TSharedPtr CreateSuccessResponse(const TSharedPtr& Data = nullptr); + static void GetIntArrayFromJson(const TSharedPtr& JsonObject, const FString& FieldName, TArray& OutArray); + static void GetFloatArrayFromJson(const TSharedPtr& JsonObject, const FString& FieldName, TArray& OutArray); + static FVector2D GetVector2DFromJson(const TSharedPtr& JsonObject, const FString& FieldName); + static FVector GetVectorFromJson(const TSharedPtr& JsonObject, const FString& FieldName); + static FRotator GetRotatorFromJson(const TSharedPtr& JsonObject, const FString& FieldName); + + // Actor utilities + static TSharedPtr ActorToJson(AActor* Actor); + static TSharedPtr 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& Value, FString& OutErrorMessage); +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h new file mode 100644 index 0000000..ecb8530 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h @@ -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 HandleCommand(const FString& CommandType, const TSharedPtr& Params); + +private: + // Actor manipulation commands + TSharedPtr HandleGetActorsInLevel(const TSharedPtr& Params); + TSharedPtr HandleFindActorsByName(const TSharedPtr& Params); + TSharedPtr HandleSpawnActor(const TSharedPtr& Params); + TSharedPtr HandleDeleteActor(const TSharedPtr& Params); + TSharedPtr HandleSetActorTransform(const TSharedPtr& Params); + TSharedPtr HandleGetActorProperties(const TSharedPtr& Params); + TSharedPtr HandleSetActorProperty(const TSharedPtr& Params); + + // Blueprint actor spawning + TSharedPtr HandleSpawnBlueprintActor(const TSharedPtr& Params); + + // Editor viewport commands + TSharedPtr HandleFocusViewport(const TSharedPtr& Params); + TSharedPtr HandleTakeScreenshot(const TSharedPtr& Params); +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPProjectCommands.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPProjectCommands.h new file mode 100644 index 0000000..58ea715 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPProjectCommands.h @@ -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 HandleCommand(const FString& CommandType, const TSharedPtr& Params); + +private: + // Specific project command handlers + TSharedPtr HandleCreateInputMapping(const TSharedPtr& Params); +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPUMGCommands.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPUMGCommands.h new file mode 100644 index 0000000..fb204ba --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPUMGCommands.h @@ -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 HandleCommand(const FString& CommandType, const TSharedPtr& 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 HandleCreateUMGWidgetBlueprint(const TSharedPtr& 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 HandleAddTextBlockToWidget(const TSharedPtr& 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 HandleAddWidgetToViewport(const TSharedPtr& 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 HandleAddButtonToWidget(const TSharedPtr& 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 HandleBindWidgetEvent(const TSharedPtr& 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 HandleSetTextBlockBinding(const TSharedPtr& Params); +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/MCPServerRunnable.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/MCPServerRunnable.h new file mode 100644 index 0000000..c0c7c70 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/MCPServerRunnable.h @@ -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 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 ClientSocket); + void ProcessMessage(TSharedPtr Client, const FString& Message); + +private: + UUnrealMCPBridge* Bridge; + TSharedPtr ListenerSocket; + TSharedPtr ClientSocket; + bool bRunning; +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPBridge.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPBridge.h new file mode 100644 index 0000000..9176d29 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPBridge.h @@ -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& Params); + +private: + // Server state + bool bIsRunning; + TSharedPtr ListenerSocket; + TSharedPtr ConnectionSocket; + FRunnableThread* ServerThread; + + // Server configuration + FIPv4Address ServerAddress; + uint16 Port; + + // Command handler instances + TSharedPtr EditorCommands; + TSharedPtr BlueprintCommands; + TSharedPtr BlueprintNodeCommands; + TSharedPtr ProjectCommands; + TSharedPtr UMGCommands; +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPModule.h b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPModule.h new file mode 100644 index 0000000..23af14c --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPModule.h @@ -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("UnrealMCP"); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("UnrealMCP"); + } +}; \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs new file mode 100644 index 0000000..2e1653d --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs @@ -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 ... + } + ); + } +} \ No newline at end of file diff --git a/games/unreal/magisterka/Plugins/UnrealMCP/UnrealMCP.uplugin b/games/unreal/magisterka/Plugins/UnrealMCP/UnrealMCP.uplugin new file mode 100644 index 0000000..b2fcff9 --- /dev/null +++ b/games/unreal/magisterka/Plugins/UnrealMCP/UnrealMCP.uplugin @@ -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 + } + ] +} \ No newline at end of file diff --git a/games/unreal/magisterka/Saved/Config/CrashReportClient/UECC-Linux-F30551E0B2E64120917325867FB1610D/CrashReportClient.ini b/games/unreal/magisterka/Saved/Config/CrashReportClient/UECC-Linux-F30551E0B2E64120917325867FB1610D/CrashReportClient.ini new file mode 100644 index 0000000..fb34d79 --- /dev/null +++ b/games/unreal/magisterka/Saved/Config/CrashReportClient/UECC-Linux-F30551E0B2E64120917325867FB1610D/CrashReportClient.ini @@ -0,0 +1,8 @@ +[CrashReportClient] +bHideLogFilesOption=false +bIsAllowedToCloseWithoutSending=true +CrashConfigPurgeDays=2 +Stall.RecordDump=false +Ensure.RecordDump=true +bAgreeToCrashUpload=false + diff --git a/games/unreal/magisterka/Saved/Config/LinuxEditor/EditorPerProjectUserSettings.ini b/games/unreal/magisterka/Saved/Config/LinuxEditor/EditorPerProjectUserSettings.ini index 9245633..3fa75dc 100644 --- a/games/unreal/magisterka/Saved/Config/LinuxEditor/EditorPerProjectUserSettings.ini +++ b/games/unreal/magisterka/Saved/Config/LinuxEditor/EditorPerProjectUserSettings.ini @@ -1349,7 +1349,7 @@ SlateFileDialogs.TimeStamp=2025.02.05-21.36.32 SlateFileDialogs.LastCompileMethod=Unknown [AssetEditorSubsystem] -CleanShutdown=True +CleanShutdown=False DebuggerAttached=False [PluginBrowser]