flockbay 0.10.19 → 0.10.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,8 @@
27
27
  #include "Components/StaticMeshComponent.h"
28
28
  #include "Materials/MaterialInterface.h"
29
29
  #include "Sound/SoundWave.h"
30
+ #include "IImageWrapper.h"
31
+ #include "IImageWrapperModule.h"
30
32
  #include "EditorSubsystem.h"
31
33
  #include "Subsystems/EditorActorSubsystem.h"
32
34
  #include "Engine/Blueprint.h"
@@ -49,6 +51,35 @@
49
51
  #include "Engine/EngineTypes.h"
50
52
  #include "GameFramework/PlayerController.h"
51
53
  #include "Camera/PlayerCameraManager.h"
54
+ #include "Engine/LocalPlayer.h"
55
+ #include "Engine/SceneCapture2D.h"
56
+ #include "Components/SceneCaptureComponent2D.h"
57
+ #include "Engine/TextureRenderTarget2D.h"
58
+
59
+ static bool SavePngToFile(const FString& FilePath, int32 Width, int32 Height, const TArray<FColor>& Bitmap)
60
+ {
61
+ IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
62
+ TSharedPtr<IImageWrapper> Wrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
63
+ if (!Wrapper.IsValid())
64
+ {
65
+ return false;
66
+ }
67
+
68
+ if (!Wrapper->SetRaw(Bitmap.GetData(), Bitmap.Num() * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8))
69
+ {
70
+ return false;
71
+ }
72
+
73
+ const TArray64<uint8>& Compressed = Wrapper->GetCompressed(100);
74
+ if (Compressed.Num() <= 0 || Compressed.Num() > MAX_int32)
75
+ {
76
+ return false;
77
+ }
78
+
79
+ TArray<uint8> Out;
80
+ Out.Append(Compressed.GetData(), static_cast<int32>(Compressed.Num()));
81
+ return FFileHelper::SaveArrayToFile(Out, *FilePath);
82
+ }
52
83
 
53
84
  FUnrealMCPEditorCommands::FUnrealMCPEditorCommands()
54
85
  {
@@ -215,7 +246,7 @@ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSaveAll(const TSharedPtr
215
246
  }
216
247
 
217
248
  TArray<UPackage*> DirtyBefore;
218
- FEditorFileUtils::GetDirtyPackages(DirtyBefore, /*bIncludeMapPackages=*/true, /*bIncludeContentPackages=*/true);
249
+ FEditorFileUtils::GetDirtyPackages(DirtyBefore);
219
250
 
220
251
  TSet<FString> DirtyBeforeNames;
221
252
  for (UPackage* Pkg : DirtyBefore)
@@ -235,7 +266,7 @@ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSaveAll(const TSharedPtr
235
266
  );
236
267
 
237
268
  TArray<UPackage*> DirtyAfter;
238
- FEditorFileUtils::GetDirtyPackages(DirtyAfter, /*bIncludeMapPackages=*/true, /*bIncludeContentPackages=*/true);
269
+ FEditorFileUtils::GetDirtyPackages(DirtyAfter);
239
270
 
240
271
  TSet<FString> DirtyAfterNames;
241
272
  for (UPackage* Pkg : DirtyAfter)
@@ -289,8 +320,7 @@ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSaveAll(const TSharedPtr
289
320
  Details->SetArrayField(TEXT("stillDirtyPackages"), StillDirty);
290
321
 
291
322
  TSharedPtr<FJsonObject> ErrObj = FUnrealMCPCommonUtils::CreateErrorResponse(
292
- TEXT("Some packages are still dirty after save_all. They may require manual Save As, source control checkout, or have save errors. Resolve in-editor and retry save_all."),
293
- );
323
+ TEXT("Some packages are still dirty after save_all. They may require manual Save As, source control checkout, or have save errors. Resolve in-editor and retry save_all."));
294
324
  ErrObj->SetObjectField(TEXT("details"), Details);
295
325
  return ErrObj;
296
326
  }
@@ -2295,26 +2325,188 @@ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleTakeScreenshot(const TSh
2295
2325
  FilePath += TEXT(".png");
2296
2326
  }
2297
2327
 
2298
- // Get the active viewport
2328
+ FString Source = TEXT("auto");
2329
+ if (Params->TryGetStringField(TEXT("source"), Source))
2330
+ {
2331
+ Source = Source.TrimStartAndEnd().ToLower();
2332
+ if (Source.IsEmpty())
2333
+ {
2334
+ Source = TEXT("auto");
2335
+ }
2336
+ }
2337
+
2338
+ double WidthNum = 0.0;
2339
+ double HeightNum = 0.0;
2340
+ int32 RequestedWidth = 0;
2341
+ int32 RequestedHeight = 0;
2342
+ if (Params->TryGetNumberField(TEXT("width"), WidthNum))
2343
+ {
2344
+ RequestedWidth = static_cast<int32>(WidthNum);
2345
+ }
2346
+ else if (Params->TryGetNumberField(TEXT("resX"), WidthNum))
2347
+ {
2348
+ RequestedWidth = static_cast<int32>(WidthNum);
2349
+ }
2350
+
2351
+ if (Params->TryGetNumberField(TEXT("height"), HeightNum))
2352
+ {
2353
+ RequestedHeight = static_cast<int32>(HeightNum);
2354
+ }
2355
+ else if (Params->TryGetNumberField(TEXT("resY"), HeightNum))
2356
+ {
2357
+ RequestedHeight = static_cast<int32>(HeightNum);
2358
+ }
2359
+
2360
+ const bool bPieRunning = (GEditor && GEditor->PlayWorld);
2361
+ const bool bWantsPie = (Source == TEXT("pie"));
2362
+ const bool bWantsEditor = (Source == TEXT("editor"));
2363
+ const bool bAuto = (Source == TEXT("auto"));
2364
+ if (!bWantsPie && !bWantsEditor && !bAuto)
2365
+ {
2366
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Invalid screenshot source: %s (expected: auto|pie|editor)"), *Source));
2367
+ }
2368
+
2369
+ const bool bUsePieCapture = bWantsPie || (bAuto && bPieRunning);
2370
+ if (bUsePieCapture)
2371
+ {
2372
+ if (!bPieRunning)
2373
+ {
2374
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("PIE is not running (PlayWorld is null)."));
2375
+ }
2376
+
2377
+ UWorld* PlayWorld = GEditor->PlayWorld;
2378
+ APlayerController* PC = PlayWorld ? PlayWorld->GetFirstPlayerController() : nullptr;
2379
+ if (!PC)
2380
+ {
2381
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to find PlayerController in PlayWorld."));
2382
+ }
2383
+
2384
+ APlayerCameraManager* PCM = PC->PlayerCameraManager;
2385
+ if (!PCM)
2386
+ {
2387
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to find PlayerCameraManager (PlayerController.PlayerCameraManager is null)."));
2388
+ }
2389
+
2390
+ int32 Width = RequestedWidth;
2391
+ int32 Height = RequestedHeight;
2392
+ if (Width <= 0 || Height <= 0)
2393
+ {
2394
+ if (ULocalPlayer* LocalPlayer = PC->GetLocalPlayer())
2395
+ {
2396
+ if (LocalPlayer->ViewportClient && LocalPlayer->ViewportClient->Viewport)
2397
+ {
2398
+ const FIntPoint ViewSize = LocalPlayer->ViewportClient->Viewport->GetSizeXY();
2399
+ if (ViewSize.X > 0 && ViewSize.Y > 0)
2400
+ {
2401
+ if (Width <= 0)
2402
+ {
2403
+ Width = ViewSize.X;
2404
+ }
2405
+ if (Height <= 0)
2406
+ {
2407
+ Height = ViewSize.Y;
2408
+ }
2409
+ }
2410
+ }
2411
+ }
2412
+ }
2413
+
2414
+ if (Width <= 0 || Height <= 0)
2415
+ {
2416
+ Width = 1280;
2417
+ Height = 720;
2418
+ }
2419
+
2420
+ const FVector CamLocation = PCM->GetCameraLocation();
2421
+ const FRotator CamRotation = PCM->GetCameraRotation();
2422
+
2423
+ FActorSpawnParameters SpawnParams;
2424
+ SpawnParams.ObjectFlags |= RF_Transient;
2425
+ SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
2426
+
2427
+ ASceneCapture2D* CaptureActor = PlayWorld->SpawnActor<ASceneCapture2D>(ASceneCapture2D::StaticClass(), CamLocation, CamRotation, SpawnParams);
2428
+ if (!CaptureActor)
2429
+ {
2430
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to spawn ASceneCapture2D for PIE screenshot capture."));
2431
+ }
2432
+
2433
+ USceneCaptureComponent2D* CaptureComp = CaptureActor->GetCaptureComponent2D();
2434
+ if (!CaptureComp)
2435
+ {
2436
+ CaptureActor->Destroy();
2437
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get SceneCaptureComponent2D from ASceneCapture2D."));
2438
+ }
2439
+
2440
+ UTextureRenderTarget2D* RenderTarget = NewObject<UTextureRenderTarget2D>(CaptureComp);
2441
+ if (!RenderTarget)
2442
+ {
2443
+ CaptureActor->Destroy();
2444
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create UTextureRenderTarget2D."));
2445
+ }
2446
+ RenderTarget->InitCustomFormat(Width, Height, PF_B8G8R8A8, /*bForceLinearGamma*/ false);
2447
+ RenderTarget->ClearColor = FLinearColor::Black;
2448
+ RenderTarget->UpdateResourceImmediate(true);
2449
+
2450
+ CaptureComp->TextureTarget = RenderTarget;
2451
+ CaptureComp->bCaptureEveryFrame = false;
2452
+ CaptureComp->bCaptureOnMovement = false;
2453
+ CaptureComp->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
2454
+ CaptureComp->FOVAngle = PCM->GetFOVAngle();
2455
+
2456
+ CaptureComp->CaptureScene();
2457
+
2458
+ FTextureRenderTargetResource* RenderResource = RenderTarget->GameThread_GetRenderTargetResource();
2459
+ if (!RenderResource)
2460
+ {
2461
+ CaptureActor->Destroy();
2462
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get render target resource for PIE screenshot capture."));
2463
+ }
2464
+
2465
+ TArray<FColor> Bitmap;
2466
+ if (!RenderResource->ReadPixels(Bitmap))
2467
+ {
2468
+ CaptureActor->Destroy();
2469
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to read pixels from PIE render target."));
2470
+ }
2471
+
2472
+ CaptureActor->Destroy();
2473
+
2474
+ if (!SavePngToFile(FilePath, Width, Height, Bitmap))
2475
+ {
2476
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to save screenshot PNG to disk."));
2477
+ }
2478
+
2479
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
2480
+ ResultObj->SetStringField(TEXT("filepath"), FilePath);
2481
+ ResultObj->SetStringField(TEXT("source"), TEXT("pie"));
2482
+ ResultObj->SetNumberField(TEXT("width"), Width);
2483
+ ResultObj->SetNumberField(TEXT("height"), Height);
2484
+ return ResultObj;
2485
+ }
2486
+
2487
+ // Editor viewport screenshot (can be stale if the OS window is not focused; prefer PIE capture when playing).
2299
2488
  if (GEditor && GEditor->GetActiveViewport())
2300
2489
  {
2490
+ GEditor->RedrawAllViewports(/*bInvalidateHitProxies*/ true);
2491
+
2301
2492
  FViewport* Viewport = GEditor->GetActiveViewport();
2493
+ const FIntPoint ViewSize = Viewport->GetSizeXY();
2302
2494
  TArray<FColor> Bitmap;
2303
- FIntRect ViewportRect(0, 0, Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y);
2304
-
2495
+ FIntRect ViewportRect(0, 0, ViewSize.X, ViewSize.Y);
2496
+
2305
2497
  if (Viewport->ReadPixels(Bitmap, FReadSurfaceDataFlags(), ViewportRect))
2306
2498
  {
2307
- TArray<uint8> CompressedBitmap;
2308
- FImageUtils::CompressImageArray(Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y, Bitmap, CompressedBitmap);
2309
-
2310
- if (FFileHelper::SaveArrayToFile(CompressedBitmap, *FilePath))
2499
+ if (SavePngToFile(FilePath, ViewSize.X, ViewSize.Y, Bitmap))
2311
2500
  {
2312
2501
  TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
2313
2502
  ResultObj->SetStringField(TEXT("filepath"), FilePath);
2503
+ ResultObj->SetStringField(TEXT("source"), TEXT("editor"));
2504
+ ResultObj->SetNumberField(TEXT("width"), ViewSize.X);
2505
+ ResultObj->SetNumberField(TEXT("height"), ViewSize.Y);
2314
2506
  return ResultObj;
2315
2507
  }
2316
2508
  }
2317
2509
  }
2318
-
2319
- return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot"));
2510
+
2511
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot."));
2320
2512
  }
@@ -45,6 +45,7 @@ public class UnrealMCP : ModuleRules
45
45
  "EditorScriptingUtilities",
46
46
  "EditorSubsystem",
47
47
  "Landscape",
48
+ "ImageWrapper",
48
49
  "Slate",
49
50
  "SlateCore",
50
51
  "MessageLog",