// Copyright Ralpha Team. All Rights Reserved. #include "RalphaMCPServer.h" #include "RalphaParameterBridge.h" #include "RalphaScreenCapture.h" #include "Dom/JsonObject.h" #include "Dom/JsonValue.h" #include "Serialization/JsonSerializer.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonWriter.h" #include "Misc/Base64.h" #include "Engine/World.h" #include "Engine/Engine.h" #include "Kismet/GameplayStatics.h" DEFINE_LOG_CATEGORY(LogRalphaMCP); URalphaMCPServer* URalphaMCPServer::Instance = nullptr; URalphaMCPServer::URalphaMCPServer() : ListenerSocket(nullptr) , ServerPort(30010) , bIsRunning(false) , ParameterBridge(nullptr) , ScreenCapture(nullptr) { } URalphaMCPServer::~URalphaMCPServer() { Stop(); } URalphaMCPServer* URalphaMCPServer::Get() { if (!Instance) { Instance = NewObject(); Instance->AddToRoot(); // Prevent garbage collection } return Instance; } bool URalphaMCPServer::Start(int32 Port) { if (bIsRunning) { UE_LOG(LogRalphaMCP, Warning, TEXT("Server already running on port %d"), ServerPort); return true; } ServerPort = Port; // Create socket ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); if (!SocketSubsystem) { UE_LOG(LogRalphaMCP, Error, TEXT("Failed to get socket subsystem")); return false; } ListenerSocket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("RalphaMCPServer"), false); if (!ListenerSocket) { UE_LOG(LogRalphaMCP, Error, TEXT("Failed to create socket")); return false; } // Configure socket ListenerSocket->SetReuseAddr(true); ListenerSocket->SetNonBlocking(true); // Bind to port TSharedRef Addr = SocketSubsystem->CreateInternetAddr(); Addr->SetAnyAddress(); Addr->SetPort(ServerPort); if (!ListenerSocket->Bind(*Addr)) { UE_LOG(LogRalphaMCP, Error, TEXT("Failed to bind to port %d"), ServerPort); SocketSubsystem->DestroySocket(ListenerSocket); ListenerSocket = nullptr; return false; } // Start listening if (!ListenerSocket->Listen(8)) { UE_LOG(LogRalphaMCP, Error, TEXT("Failed to listen on port %d"), ServerPort); SocketSubsystem->DestroySocket(ListenerSocket); ListenerSocket = nullptr; return false; } // Create parameter bridge and screen capture ParameterBridge = NewObject(this); ScreenCapture = NewObject(this); // Register tick TickDelegateHandle = FTSTicker::GetCoreTicker().AddTicker( FTickerDelegate::CreateUObject(this, &URalphaMCPServer::Tick), 0.0f); bIsRunning = true; UE_LOG(LogRalphaMCP, Log, TEXT("Ralpha MCP Server started on port %d"), ServerPort); return true; } void URalphaMCPServer::Stop() { if (!bIsRunning) { return; } // Unregister tick FTSTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle); // Close all client connections ClientConnections.Empty(); // Close listener socket if (ListenerSocket) { ListenerSocket->Close(); ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenerSocket); ListenerSocket = nullptr; } bIsRunning = false; UE_LOG(LogRalphaMCP, Log, TEXT("Ralpha MCP Server stopped")); } bool URalphaMCPServer::IsRunning() const { return bIsRunning; } bool URalphaMCPServer::Tick(float DeltaTime) { if (!bIsRunning || !ListenerSocket) { return true; } // Check for new connections bool bHasPendingConnection = false; if (ListenerSocket->HasPendingConnection(bHasPendingConnection) && bHasPendingConnection) { TSharedRef RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); FSocket* ClientSocket = ListenerSocket->Accept(*RemoteAddress, TEXT("RalphaClient")); if (ClientSocket) { ClientSocket->SetNonBlocking(true); TSharedPtr NewConnection = MakeShared(ClientSocket, this); ClientConnections.Add(NewConnection); UE_LOG(LogRalphaMCP, Log, TEXT("New client connected from %s"), *RemoteAddress->ToString(true)); } } // Process existing connections for (int32 i = ClientConnections.Num() - 1; i >= 0; --i) { if (ClientConnections[i]->IsValid()) { ClientConnections[i]->Tick(); } else { ClientConnections.RemoveAt(i); } } return true; } FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand) { TSharedPtr JsonObject; TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonCommand); if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid()) { return TEXT("{\"success\": false, \"error\": \"Invalid JSON\"}"); } FString CommandType; if (!JsonObject->TryGetStringField(TEXT("type"), CommandType)) { return TEXT("{\"success\": false, \"error\": \"Missing command type\"}"); } TSharedPtr ResponseObject = MakeShared(); // Route commands if (CommandType == TEXT("capture_screenshot")) { int32 Width = JsonObject->HasField(TEXT("width")) ? JsonObject->GetIntegerField(TEXT("width")) : 1920; int32 Height = JsonObject->HasField(TEXT("height")) ? JsonObject->GetIntegerField(TEXT("height")) : 1080; bool bUseCamera = JsonObject->HasField(TEXT("use_camera")) ? JsonObject->GetBoolField(TEXT("use_camera")) : true; FString Base64Image; FString FilePath; bool bSuccess = false; if (bUseCamera) { // Capture from CineCamera using SceneCapture2D bSuccess = ScreenCapture->CaptureFromCamera(Width, Height, Base64Image, FilePath); } // Fallback to viewport capture if camera capture failed or not requested if (!bSuccess) { bSuccess = ScreenCapture->CaptureScreenshot(Width, Height, Base64Image, FilePath); } ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("base64"), Base64Image); ResponseObject->SetStringField(TEXT("path"), FilePath); } } else if (CommandType == TEXT("get_all_parameters")) { TSharedPtr ParamsObject = ParameterBridge->GetAllParameters(); ResponseObject->SetBoolField(TEXT("success"), true); ResponseObject->SetObjectField(TEXT("data"), ParamsObject); } else if (CommandType == TEXT("set_post_process")) { const TSharedPtr* Params; if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) { bool bSuccess = ParameterBridge->SetPostProcessParameters(*Params); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); } } else if (CommandType == TEXT("set_directional_light")) { const TSharedPtr* Params; if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) { bool bSuccess = ParameterBridge->SetDirectionalLightParameters(*Params); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); } } else if (CommandType == TEXT("set_sky_light")) { const TSharedPtr* Params; if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) { bool bSuccess = ParameterBridge->SetSkyLightParameters(*Params); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); } } else if (CommandType == TEXT("set_exponential_height_fog")) { const TSharedPtr* Params; if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) { bool bSuccess = ParameterBridge->SetFogParameters(*Params); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); } } else if (CommandType == TEXT("set_camera")) { const TSharedPtr* Params; if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) { bool bSuccess = ParameterBridge->SetCameraParameters(*Params); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); } } else if (CommandType == TEXT("search_assets")) { FString Query = JsonObject->GetStringField(TEXT("query")); FString AssetType = JsonObject->HasField(TEXT("asset_type")) ? JsonObject->GetStringField(TEXT("asset_type")) : TEXT("All"); int32 MaxResults = JsonObject->HasField(TEXT("max_results")) ? JsonObject->GetIntegerField(TEXT("max_results")) : 20; TArray> Results = ParameterBridge->SearchAssets(Query, AssetType, MaxResults); ResponseObject->SetBoolField(TEXT("success"), true); ResponseObject->SetArrayField(TEXT("results"), Results); } else if (CommandType == TEXT("spawn_actor")) { FString AssetPath = JsonObject->GetStringField(TEXT("asset_path")); const TSharedPtr* LocationObj; FVector Location = FVector::ZeroVector; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->GetNumberField(TEXT("x")); Location.Y = (*LocationObj)->GetNumberField(TEXT("y")); Location.Z = (*LocationObj)->GetNumberField(TEXT("z")); } FRotator Rotation = FRotator::ZeroRotator; const TSharedPtr* RotationObj; if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) { Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; } float Scale = JsonObject->HasField(TEXT("scale")) ? JsonObject->GetNumberField(TEXT("scale")) : 1.0f; FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); FString ActorId; bool bSuccess = ParameterBridge->SpawnActor(AssetPath, Location, Rotation, Scale, ActorLabel, ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("actor_id"), ActorId); } } else if (CommandType == TEXT("delete_actor")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); bool bSuccess = ParameterBridge->DeleteActor(ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("spawn_ocean")) { FVector Location = FVector::ZeroVector; const TSharedPtr* LocationObj; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; } float Size = JsonObject->HasField(TEXT("size")) ? JsonObject->GetNumberField(TEXT("size")) : 100000.0f; FString ActorId; bool bSuccess = ParameterBridge->SpawnOcean(Location, Size, ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("actor_id"), ActorId); } } else if (CommandType == TEXT("set_ocean_waves")) { float WaveAmplitude = JsonObject->HasField(TEXT("amplitude")) ? JsonObject->GetNumberField(TEXT("amplitude")) : 100.0f; float WaveLength = JsonObject->HasField(TEXT("wavelength")) ? JsonObject->GetNumberField(TEXT("wavelength")) : 1000.0f; float Steepness = JsonObject->HasField(TEXT("steepness")) ? JsonObject->GetNumberField(TEXT("steepness")) : 0.5f; int32 NumWaves = JsonObject->HasField(TEXT("num_waves")) ? JsonObject->GetIntegerField(TEXT("num_waves")) : 4; bool bSuccess = ParameterBridge->SetOceanWaves(WaveAmplitude, WaveLength, Steepness, NumWaves); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("create_landscape")) { float Size = JsonObject->HasField(TEXT("size")) ? JsonObject->GetNumberField(TEXT("size")) : 10000.0f; float HeightOffset = JsonObject->HasField(TEXT("height_offset")) ? JsonObject->GetNumberField(TEXT("height_offset")) : -500.0f; bool bSuccess = ParameterBridge->CreateLandscape(Size, HeightOffset); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("set_actor_material")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); FString MaterialPath = JsonObject->GetStringField(TEXT("material_path")); int32 MaterialIndex = JsonObject->HasField(TEXT("material_index")) ? JsonObject->GetIntegerField(TEXT("material_index")) : 0; bool bSuccess = ParameterBridge->SetActorMaterial(ActorId, MaterialPath, MaterialIndex); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("set_actor_simple_material")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); // Parse color - supports hex string or RGB object FLinearColor BaseColor = FLinearColor(0.1f, 0.2f, 0.4f); // Default dark blue if (JsonObject->HasField(TEXT("color"))) { const TSharedPtr* ColorObj; if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) { BaseColor.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 0.1f; BaseColor.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 0.2f; BaseColor.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 0.4f; BaseColor.A = (*ColorObj)->HasField(TEXT("a")) ? (*ColorObj)->GetNumberField(TEXT("a")) : 1.0f; } } float Metallic = JsonObject->HasField(TEXT("metallic")) ? JsonObject->GetNumberField(TEXT("metallic")) : 0.0f; float Roughness = JsonObject->HasField(TEXT("roughness")) ? JsonObject->GetNumberField(TEXT("roughness")) : 0.5f; float Opacity = JsonObject->HasField(TEXT("opacity")) ? JsonObject->GetNumberField(TEXT("opacity")) : 1.0f; bool bSuccess = ParameterBridge->SetActorSimpleMaterial(ActorId, BaseColor, Metallic, Roughness, Opacity); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("set_actor_emissive_material")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); // Parse emissive color - supports RGB object FLinearColor EmissiveColor = FLinearColor(1.0f, 0.8f, 0.3f); // Default warm yellow if (JsonObject->HasField(TEXT("color"))) { const TSharedPtr* ColorObj; if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) { EmissiveColor.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f; EmissiveColor.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 0.8f; EmissiveColor.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 0.3f; } } float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 10.0f; bool bSuccess = ParameterBridge->SetActorEmissiveMaterial(ActorId, EmissiveColor, Intensity); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("list_actors")) { FString ClassFilter = JsonObject->HasField(TEXT("class_filter")) ? JsonObject->GetStringField(TEXT("class_filter")) : TEXT(""); FString NameFilter = JsonObject->HasField(TEXT("name_filter")) ? JsonObject->GetStringField(TEXT("name_filter")) : TEXT(""); bool bIncludeTransforms = JsonObject->HasField(TEXT("include_transforms")) ? JsonObject->GetBoolField(TEXT("include_transforms")) : true; TArray> Actors = ParameterBridge->ListActors(ClassFilter, NameFilter, bIncludeTransforms); ResponseObject->SetBoolField(TEXT("success"), true); ResponseObject->SetArrayField(TEXT("actors"), Actors); } else if (CommandType == TEXT("set_render_quality")) { const TSharedPtr* Settings; if (JsonObject->TryGetObjectField(TEXT("settings"), Settings)) { bool bSuccess = ParameterBridge->SetRenderQuality(*Settings); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing settings")); } } else if (CommandType == TEXT("setup_scene")) { TSharedPtr SetupResult = ParameterBridge->SetupScene(); ResponseObject = SetupResult; } else if (CommandType == TEXT("exec_command")) { FString Command = JsonObject->GetStringField(TEXT("command")); if (!Command.IsEmpty()) { UWorld* World = GEngine->GetWorldContexts()[0].World(); if (World) { GEngine->Exec(World, *Command); ResponseObject->SetBoolField(TEXT("success"), true); ResponseObject->SetStringField(TEXT("executed"), Command); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("No world available")); } } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing command")); } } else if (CommandType == TEXT("new_level")) { FString Template = JsonObject->HasField(TEXT("template")) ? JsonObject->GetStringField(TEXT("template")) : TEXT("Basic"); #if WITH_EDITOR if (GEditor) { // Create new level from template GEditor->Exec(nullptr, TEXT("MAP NEW")); ResponseObject->SetBoolField(TEXT("success"), true); ResponseObject->SetStringField(TEXT("message"), TEXT("New level created")); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Editor not available")); } #else ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Only available in editor")); #endif } else if (CommandType == TEXT("spawn_point_light")) { FVector Location = FVector::ZeroVector; const TSharedPtr* LocationObj; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; } float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f; float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f; FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); FLinearColor Color = FLinearColor::White; if (JsonObject->HasField(TEXT("color"))) { const TSharedPtr* ColorObj; if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) { Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f; Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f; Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f; } } FString ActorId; bool bSuccess = ParameterBridge->SpawnPointLight(Location, Intensity, Color, AttenuationRadius, ActorLabel, ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("actor_id"), ActorId); } } else if (CommandType == TEXT("set_point_light")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); const TSharedPtr* Params; if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) { bool bSuccess = ParameterBridge->SetPointLightParameters(ActorId, *Params); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); } } else if (CommandType == TEXT("spawn_spot_light")) { FVector Location = FVector::ZeroVector; const TSharedPtr* LocationObj; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; } FRotator Rotation = FRotator::ZeroRotator; const TSharedPtr* RotationObj; if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) { Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; } float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f; float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f; float InnerConeAngle = JsonObject->HasField(TEXT("inner_cone_angle")) ? JsonObject->GetNumberField(TEXT("inner_cone_angle")) : 20.0f; float OuterConeAngle = JsonObject->HasField(TEXT("outer_cone_angle")) ? JsonObject->GetNumberField(TEXT("outer_cone_angle")) : 40.0f; FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); FLinearColor Color = FLinearColor::White; if (JsonObject->HasField(TEXT("color"))) { const TSharedPtr* ColorObj; if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) { Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f; Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f; Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f; } } FString ActorId; bool bSuccess = ParameterBridge->SpawnSpotLight(Location, Rotation, Intensity, Color, AttenuationRadius, InnerConeAngle, OuterConeAngle, ActorLabel, ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("actor_id"), ActorId); } } else if (CommandType == TEXT("set_spot_light")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); const TSharedPtr* Params; if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) { bool bSuccess = ParameterBridge->SetSpotLightParameters(ActorId, *Params); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); } } else if (CommandType == TEXT("spawn_rect_light")) { FVector Location = FVector::ZeroVector; const TSharedPtr* LocationObj; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; } FRotator Rotation = FRotator::ZeroRotator; const TSharedPtr* RotationObj; if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) { Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; } float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f; float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f; float SourceWidth = JsonObject->HasField(TEXT("source_width")) ? JsonObject->GetNumberField(TEXT("source_width")) : 100.0f; float SourceHeight = JsonObject->HasField(TEXT("source_height")) ? JsonObject->GetNumberField(TEXT("source_height")) : 100.0f; FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); FLinearColor Color = FLinearColor::White; if (JsonObject->HasField(TEXT("color"))) { const TSharedPtr* ColorObj; if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj)) { Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f; Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f; Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f; } } FString ActorId; bool bSuccess = ParameterBridge->SpawnRectLight(Location, Rotation, Intensity, Color, SourceWidth, SourceHeight, AttenuationRadius, ActorLabel, ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("actor_id"), ActorId); } } else if (CommandType == TEXT("set_rect_light")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); const TSharedPtr* Params; if (JsonObject->TryGetObjectField(TEXT("parameters"), Params)) { bool bSuccess = ParameterBridge->SetRectLightParameters(ActorId, *Params); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters")); } } else if (CommandType == TEXT("spawn_skeletal_mesh")) { FString SkeletalMeshPath = JsonObject->GetStringField(TEXT("skeletal_mesh_path")); FVector Location = FVector::ZeroVector; const TSharedPtr* LocationObj; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; } FRotator Rotation = FRotator::ZeroRotator; const TSharedPtr* RotationObj; if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) { Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; } float Scale = JsonObject->HasField(TEXT("scale")) ? JsonObject->GetNumberField(TEXT("scale")) : 1.0f; FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); FString ActorId; bool bSuccess = ParameterBridge->SpawnSkeletalMesh(SkeletalMeshPath, Location, Rotation, Scale, ActorLabel, ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("actor_id"), ActorId); } } else if (CommandType == TEXT("play_animation")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); FString AnimationPath = JsonObject->GetStringField(TEXT("animation_path")); bool bLooping = JsonObject->HasField(TEXT("looping")) ? JsonObject->GetBoolField(TEXT("looping")) : true; float PlayRate = JsonObject->HasField(TEXT("play_rate")) ? JsonObject->GetNumberField(TEXT("play_rate")) : 1.0f; bool bSuccess = ParameterBridge->PlayAnimation(ActorId, AnimationPath, bLooping, PlayRate); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("stop_animation")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); bool bSuccess = ParameterBridge->StopAnimation(ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("set_animation_pose")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); FString AnimationPath = JsonObject->GetStringField(TEXT("animation_path")); float Time = JsonObject->HasField(TEXT("time")) ? JsonObject->GetNumberField(TEXT("time")) : 0.0f; bool bSuccess = ParameterBridge->SetAnimationPose(ActorId, AnimationPath, Time); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("spawn_sphere_reflection_capture")) { FVector Location = FVector::ZeroVector; const TSharedPtr* LocationObj; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; } float InfluenceRadius = JsonObject->HasField(TEXT("influence_radius")) ? JsonObject->GetNumberField(TEXT("influence_radius")) : 3000.0f; FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); FString ActorId; bool bSuccess = ParameterBridge->SpawnSphereReflectionCapture(Location, InfluenceRadius, ActorLabel, ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("actor_id"), ActorId); } } else if (CommandType == TEXT("spawn_box_reflection_capture")) { FVector Location = FVector::ZeroVector; const TSharedPtr* LocationObj; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; } FVector BoxExtent = FVector(1000.0f, 1000.0f, 400.0f); const TSharedPtr* ExtentObj; if (JsonObject->TryGetObjectField(TEXT("box_extent"), ExtentObj)) { BoxExtent.X = (*ExtentObj)->HasField(TEXT("x")) ? (*ExtentObj)->GetNumberField(TEXT("x")) : 1000.0f; BoxExtent.Y = (*ExtentObj)->HasField(TEXT("y")) ? (*ExtentObj)->GetNumberField(TEXT("y")) : 1000.0f; BoxExtent.Z = (*ExtentObj)->HasField(TEXT("z")) ? (*ExtentObj)->GetNumberField(TEXT("z")) : 400.0f; } FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT(""); FString ActorId; bool bSuccess = ParameterBridge->SpawnBoxReflectionCapture(Location, BoxExtent, ActorLabel, ActorId); ResponseObject->SetBoolField(TEXT("success"), bSuccess); if (bSuccess) { ResponseObject->SetStringField(TEXT("actor_id"), ActorId); } } else if (CommandType == TEXT("update_reflection_captures")) { bool bSuccess = ParameterBridge->UpdateReflectionCaptures(); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("set_light_ies_profile")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); FString IESProfilePath = JsonObject->GetStringField(TEXT("ies_profile_path")); bool bSuccess = ParameterBridge->SetLightIESProfile(ActorId, IESProfilePath); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("list_assets")) { FString FolderPath = JsonObject->HasField(TEXT("folder_path")) ? JsonObject->GetStringField(TEXT("folder_path")) : TEXT("/Game"); FString AssetType = JsonObject->HasField(TEXT("asset_type")) ? JsonObject->GetStringField(TEXT("asset_type")) : TEXT("All"); bool bRecursive = JsonObject->HasField(TEXT("recursive")) ? JsonObject->GetBoolField(TEXT("recursive")) : true; TArray> Results = ParameterBridge->ListAssets(FolderPath, AssetType, bRecursive); ResponseObject->SetBoolField(TEXT("success"), true); ResponseObject->SetArrayField(TEXT("assets"), Results); } else if (CommandType == TEXT("set_actor_transform")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); bool bRelative = JsonObject->HasField(TEXT("relative")) ? JsonObject->GetBoolField(TEXT("relative")) : false; const FVector* LocationPtr = nullptr; FVector Location; const TSharedPtr* LocationObj; if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj)) { Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f; Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f; Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f; LocationPtr = &Location; } const FRotator* RotationPtr = nullptr; FRotator Rotation; const TSharedPtr* RotationObj; if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj)) { Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f; Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f; Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f; RotationPtr = &Rotation; } const float* ScalePtr = nullptr; float Scale; if (JsonObject->HasField(TEXT("scale"))) { Scale = JsonObject->GetNumberField(TEXT("scale")); ScalePtr = &Scale; } bool bSuccess = ParameterBridge->SetActorTransform(ActorId, LocationPtr, RotationPtr, ScalePtr, bRelative); ResponseObject->SetBoolField(TEXT("success"), bSuccess); } else if (CommandType == TEXT("get_actor_details")) { FString ActorId = JsonObject->GetStringField(TEXT("actor_id")); bool bIncludeComponents = JsonObject->HasField(TEXT("include_components")) ? JsonObject->GetBoolField(TEXT("include_components")) : true; bool bIncludeMaterials = JsonObject->HasField(TEXT("include_materials")) ? JsonObject->GetBoolField(TEXT("include_materials")) : true; TSharedPtr Details = ParameterBridge->GetActorDetails(ActorId, bIncludeComponents, bIncludeMaterials); if (Details.IsValid()) { ResponseObject->SetBoolField(TEXT("success"), true); ResponseObject->SetObjectField(TEXT("actor"), Details); } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), TEXT("Actor not found")); } } else { ResponseObject->SetBoolField(TEXT("success"), false); ResponseObject->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown command type: %s"), *CommandType)); } // Serialize response FString ResponseString; TSharedRef> Writer = TJsonWriterFactory<>::Create(&ResponseString); FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer); return ResponseString; } // FRalphaClientConnection implementation FRalphaClientConnection::FRalphaClientConnection(FSocket* InSocket, URalphaMCPServer* InServer) : Socket(InSocket) , Server(InServer) { } FRalphaClientConnection::~FRalphaClientConnection() { if (Socket) { Socket->Close(); ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket); Socket = nullptr; } } bool FRalphaClientConnection::IsValid() const { return Socket != nullptr && Socket->GetConnectionState() == SCS_Connected; } void FRalphaClientConnection::Tick() { if (!IsValid()) { return; } // Receive data uint32 PendingDataSize = 0; while (Socket->HasPendingData(PendingDataSize) && PendingDataSize > 0) { TArray ReceivedData; ReceivedData.SetNumUninitialized(FMath::Min(PendingDataSize, 65536u)); int32 BytesRead = 0; if (Socket->Recv(ReceivedData.GetData(), ReceivedData.Num(), BytesRead)) { ReceiveBuffer += FString(UTF8_TO_TCHAR(reinterpret_cast(ReceivedData.GetData()))); } } // Process complete messages (newline-delimited JSON) int32 NewlineIndex; while (ReceiveBuffer.FindChar(TEXT('\n'), NewlineIndex)) { FString Message = ReceiveBuffer.Left(NewlineIndex); ReceiveBuffer = ReceiveBuffer.Mid(NewlineIndex + 1); if (!Message.IsEmpty()) { FString Response = Server->ProcessCommand(Message); SendResponse(Response); } } } void FRalphaClientConnection::SendResponse(const FString& Response) { if (!IsValid()) { return; } FString ResponseWithNewline = Response + TEXT("\n"); FTCHARToUTF8 Converter(*ResponseWithNewline); int32 BytesSent = 0; Socket->Send(reinterpret_cast(Converter.Get()), Converter.Length(), BytesSent); }