Code/SubZero-Studios/Shared/SubZeroScreenshot.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sandbox.SubZero
{
/// <summary>
/// Super Shot - High-quality screenshot capture tool for s&Box games.
/// Professional screenshot tool with advanced effects, scene preparation, and multiple resolution presets.
///
/// FEATURES:
/// - 4K default resolution with multiple presets
/// - Hide UI elements and objects during capture
/// - Advanced camera effects (FOV, custom camera)
/// - Visual effects (motion blur, color grading, vignette, etc.)
/// - Batch capture mode
/// - Scene preparation and cleanup
///
/// Uses s&Box's built-in screenshot_highres command for capture.
/// </summary>
[Title( "Super Shot" )]
[Category( "Tools" )]
[Icon( "camera_alt" )]
public sealed class SuperShot : Component
{
// ═══════════════════════════════════════════════════════════════
// RESOLUTION
// ═══════════════════════════════════════════════════════════════
public enum ResolutionPreset
{
[Title( "1080p (1920x1080)" )]
HD1080p,
[Title( "2K (2560x1440)" )]
QHD2K,
[Title( "4K (3840x2160)" )]
UHD4K,
[Title( "Custom" )]
Custom
}
[Property, Group( "1. Resolution" )]
[Title( "Preset" )]
[Description( "Quick preset or custom resolution" )]
public ResolutionPreset Preset { get; set; } = ResolutionPreset.UHD4K;
[Property, Group( "1. Resolution" )]
[Title( "Custom Width" )]
[Description( "Width in pixels (used when Preset = Custom)" )]
[Range( 256, 8192 )]
[ShowIf( nameof( Preset ), ResolutionPreset.Custom )]
public int CustomWidth { get; set; } = 1920;
[Property, Group( "1. Resolution" )]
[Title( "Custom Height" )]
[Description( "Height in pixels (used when Preset = Custom)" )]
[Range( 256, 8192 )]
[ShowIf( nameof( Preset ), ResolutionPreset.Custom )]
public int CustomHeight { get; set; } = 1080;
// ═══════════════════════════════════════════════════════════════
// CAMERA
// ═══════════════════════════════════════════════════════════════
[Property, Group( "2. Camera" )]
[Title( "Use Custom Camera" )]
[Description( "Use a specific camera instead of main camera" )]
public bool UseCustomCamera { get; set; } = false;
[Property, Group( "2. Camera" )]
[Title( "Custom Camera" )]
[Description( "Camera to use for capture (if UseCustomCamera = true)" )]
[ShowIf( nameof( UseCustomCamera ), true )]
public CameraComponent CustomCamera { get; set; }
[Property, Group( "2. Camera" )]
[Title( "Override Field of View" )]
[Description( "Temporarily change FOV for capture" )]
public bool OverrideFOV { get; set; } = false;
[Property, Group( "2. Camera" )]
[Title( "Capture FOV" )]
[Description( "Field of view to use during capture" )]
[Range( 10f, 120f )]
[ShowIf( nameof( OverrideFOV ), true )]
public float CaptureFOV { get; set; } = 90f;
// ═══════════════════════════════════════════════════════════════
// SCENE PREPARATION
// ═══════════════════════════════════════════════════════════════
[Property, Group( "3. Scene Preparation" )]
[Title( "Hide UI" )]
[Description( "Hide all UI elements during capture" )]
public bool HideUI { get; set; } = true;
[Property, Group( "3. Scene Preparation" )]
[Title( "Hide Tags" )]
[Description( "Comma-separated tags to hide (e.g., 'player,debug,ui')" )]
public string HideTags { get; set; } = "";
[Property, Group( "3. Scene Preparation" )]
[Title( "Hide Component Types" )]
[Description( "Component type names to hide (comma-separated, e.g., 'Rigidbody,PointLight')" )]
public string HideComponentTypes { get; set; } = "";
[Property, Group( "3. Scene Preparation" )]
[Title( "Show Only Tagged Objects" )]
[Description( "If set, only show objects with this tag (empty = show all)" )]
public string ShowOnlyTag { get; set; } = "";
[Property, Group( "3. Scene Preparation" )]
[Title( "Capture Delay (seconds)" )]
[Description( "Wait before capturing (useful for animations to settle)" )]
[Range( 0f, 10f )]
public float CaptureDelay { get; set; } = 0f;
[Property, Group( "3. Scene Preparation" )]
[Title( "Freeze Time" )]
[Description( "Pause game time during capture (Note: May not be supported in s&Box)" )]
public bool FreezeTime { get; set; } = false;
// ═══════════════════════════════════════════════════════════════
// CAMERA EFFECTS
// ═══════════════════════════════════════════════════════════════
[Property, Group( "4. Camera Effects" )]
[Title( "Camera Shake" )]
[Description( "Add subtle camera shake before capturing (cinematic effect)" )]
public bool CameraShake { get; set; } = false;
[Property, Group( "4. Camera Effects" )]
[Title( "Shake Intensity" )]
[Description( "Intensity of camera shake" )]
[Range( 0.1f, 5f )]
[ShowIf( nameof( CameraShake ), true )]
public float ShakeIntensity { get; set; } = 1f;
[Property, Group( "4. Camera Effects" )]
[Title( "Slow Motion" )]
[Description( "Slow down time before capture for dramatic effect" )]
public bool SlowMotion { get; set; } = false;
[Property, Group( "4. Camera Effects" )]
[Title( "Slow Motion Speed" )]
[Description( "Time scale during slow motion (0.1 = 10% speed)" )]
[Range( 0.1f, 1f )]
[ShowIf( nameof( SlowMotion ), true )]
public float SlowMotionSpeed { get; set; } = 0.3f;
// ═══════════════════════════════════════════════════════════════
// COLOR & TONE
// ═══════════════════════════════════════════════════════════════
[Property, Group( "5. Color & Tone" )]
[Title( "Color Filter" )]
[Description( "Apply a color filter to the scene" )]
public ColorFilterType ColorFilter { get; set; } = ColorFilterType.None;
public enum ColorFilterType
{
[Title( "None" )]
None,
[Title( "Warm" )]
Warm,
[Title( "Cool" )]
Cool,
[Title( "Vintage" )]
Vintage,
[Title( "Cinematic" )]
Cinematic,
[Title( "Black & White" )]
BlackAndWhite,
[Title( "High Contrast" )]
HighContrast
}
[Property, Group( "5. Color & Tone" )]
[Title( "Exposure" )]
[Description( "Adjust exposure (-2 = dark, 0 = normal, 2 = bright)" )]
[Range( -2f, 2f )]
public float ExposureAdjustment { get; set; } = 0f;
[Property, Group( "5. Color & Tone" )]
[Title( "Saturation" )]
[Description( "Adjust color saturation (-1 = grayscale, 0 = normal, 2 = vibrant)" )]
[Range( -1f, 2f )]
public float SaturationAdjustment { get; set; } = 0f;
[Property, Group( "5. Color & Tone" )]
[Title( "Contrast" )]
[Description( "Adjust image contrast (-1 = low, 0 = normal, 1 = high)" )]
[Range( -1f, 1f )]
public float ContrastAdjustment { get; set; } = 0f;
// ═══════════════════════════════════════════════════════════════
// POST-PROCESSING EFFECTS
// ═══════════════════════════════════════════════════════════════
[Property, Group( "6. Post-Processing" )]
[Title( "Vignette" )]
[Description( "Add dark edges to the image (0 = off, 1 = maximum)" )]
[Range( 0f, 1f )]
public float VignetteIntensity { get; set; } = 0f;
[Property, Group( "6. Post-Processing" )]
[Title( "Depth of Field Blur" )]
[Description( "Simulate depth of field blur effect" )]
public bool DepthOfField { get; set; } = false;
[Property, Group( "6. Post-Processing" )]
[Title( "Blur Intensity" )]
[Description( "Intensity of depth of field blur" )]
[Range( 0.1f, 10f )]
[ShowIf( nameof( DepthOfField ), true )]
public float BlurIntensity { get; set; } = 2f;
[Property, Group( "6. Post-Processing" )]
[Title( "Motion Blur" )]
[Description( "Add motion blur effect for dynamic scenes" )]
public bool MotionBlur { get; set; } = false;
[Property, Group( "6. Post-Processing" )]
[Title( "Motion Blur Intensity" )]
[Description( "Strength of motion blur effect" )]
[Range( 0.1f, 5f )]
[ShowIf( nameof( MotionBlur ), true )]
public float MotionBlurIntensity { get; set; } = 1f;
// ═══════════════════════════════════════════════════════════════
// ARTISTIC EFFECTS
// ═══════════════════════════════════════════════════════════════
[Property, Group( "7. Artistic Effects" )]
[Title( "Chromatic Aberration" )]
[Description( "Add color fringing effect (vintage camera look)" )]
public bool ChromaticAberration { get; set; } = false;
[Property, Group( "7. Artistic Effects" )]
[Title( "Aberration Intensity" )]
[Description( "Strength of chromatic aberration" )]
[Range( 0.1f, 2f )]
[ShowIf( nameof( ChromaticAberration ), true )]
public float AberrationIntensity { get; set; } = 0.5f;
[Property, Group( "7. Artistic Effects" )]
[Title( "Film Grain" )]
[Description( "Add film grain texture for cinematic look" )]
public bool FilmGrain { get; set; } = false;
[Property, Group( "7. Artistic Effects" )]
[Title( "Grain Intensity" )]
[Description( "Amount of film grain" )]
[Range( 0.1f, 2f )]
[ShowIf( nameof( FilmGrain ), true )]
public float GrainIntensity { get; set; } = 0.5f;
[Property, Group( "7. Artistic Effects" )]
[Title( "Lens Flare" )]
[Description( "Add lens flare effect when bright lights are in frame" )]
public bool LensFlare { get; set; } = false;
[Property, Group( "7. Artistic Effects" )]
[Title( "Flare Intensity" )]
[Description( "Brightness of lens flare" )]
[Range( 0.1f, 3f )]
[ShowIf( nameof( LensFlare ), true )]
public float FlareIntensity { get; set; } = 1f;
// ═══════════════════════════════════════════════════════════════
// BATCH CAPTURE
// ═══════════════════════════════════════════════════════════════
[Property, Group( "8. Batch Capture" )]
[Title( "Enable Batch Mode" )]
[Description( "Capture multiple screenshots with delays" )]
public bool EnableBatchMode { get; set; } = false;
[Property, Group( "8. Batch Capture" )]
[Title( "Batch Count" )]
[Description( "Number of screenshots to take" )]
[Range( 1, 100 )]
[ShowIf( nameof( EnableBatchMode ), true )]
public int BatchCount { get; set; } = 5;
[Property, Group( "8. Batch Capture" )]
[Title( "Batch Delay (seconds)" )]
[Description( "Time between each screenshot in batch" )]
[Range( 0.1f, 10f )]
[ShowIf( nameof( EnableBatchMode ), true )]
public float BatchDelay { get; set; } = 1f;
// ═══════════════════════════════════════════════════════════════
// ADVANCED
// ═══════════════════════════════════════════════════════════════
[Property, Group( "9. Advanced" )]
[Title( "Super Sampling" )]
[Description( "Render at higher resolution then downscale (1.0 = off, 2.0 = 2x)" )]
[Range( 1f, 4f )]
public float SuperSampling { get; set; } = 1f;
[Property, Group( "9. Advanced" )]
[Title( "Enable Post-Processing" )]
[Description( "Include post-processing effects (bloom, etc.) in screenshot" )]
public bool EnablePostProcessing { get; set; } = true;
// ═══════════════════════════════════════════════════════════════
// VIDEO CAMERAS (SPAWN)
// ═══════════════════════════════════════════════════════════════
[Property, Group( "10. Video Cameras" )]
[Title( "Orbit Target" )]
[Description( "Target for spawned orbit camera (empty = this object)" )]
public GameObject OrbitCameraTarget { get; set; }
[Property, Group( "10. Video Cameras" )]
[Title( "Dolly Target" )]
[Description( "Target for spawned dolly camera (empty = this object)" )]
public GameObject DollyCameraTarget { get; set; }
// ═══════════════════════════════════════════════════════════════
// STATISTICS
// ═══════════════════════════════════════════════════════════════
[Property, Group( "Statistics" ), ReadOnly]
[Title( "Screenshots Taken" )]
public int ScreenshotsTaken { get; private set; } = 0;
// ─────────────────────────────────────────────────────────────
// PRIVATE STATE
// ─────────────────────────────────────────────────────────────
private CameraComponent captureCamera;
private List<GameObject> hiddenObjects = new List<GameObject>();
private List<Component> hiddenComponents = new List<Component>();
private float originalFOV = 90f;
private bool wasTimeFrozen = false;
private bool isCapturing = false;
// ─────────────────────────────────────────────────────────────
// LIFECYCLE
// ─────────────────────────────────────────────────────────────
protected override void OnStart()
{
Log.Info( "[Super Shot] Ready! Use buttons to capture high-quality screenshots." );
}
protected override void OnUpdate()
{
// Hotkey: F12 for quick screenshot (if s&Box supports F12)
// Alternative: Check for screenshot key binding
// Note: s&Box may have built-in screenshot (F5 or similar)
// This component provides programmatic control via buttons
}
// ─────────────────────────────────────────────────────────────
// BUTTONS
// ─────────────────────────────────────────────────────────────
[Button( "Capture Screenshot" ), Group( "Capture" )]
public void CaptureScreenshot()
{
if ( isCapturing )
{
Log.Info( "[Super Shot] Already capturing, please wait..." );
return;
}
_ = CaptureScreenshotAsync();
}
[Button( "Capture Batch" ), Group( "Batch Capture" )]
[ShowIf( nameof( EnableBatchMode ), true )]
public void CaptureBatch()
{
if ( isCapturing )
{
Log.Info( "[Super Shot] Already capturing, please wait..." );
return;
}
_ = CaptureBatchAsync();
}
[Button( "Quick 1080p" ), Group( "Quick Capture" )]
public void Quick1080p()
{
var oldPreset = Preset;
Preset = ResolutionPreset.HD1080p;
CaptureScreenshot();
Preset = oldPreset;
}
[Button( "Quick 2K" ), Group( "Quick Capture" )]
public void Quick2K()
{
var oldPreset = Preset;
Preset = ResolutionPreset.QHD2K;
CaptureScreenshot();
Preset = oldPreset;
}
[Button( "Quick 4K" ), Group( "Quick Capture" )]
public void Quick4K()
{
var oldPreset = Preset;
Preset = ResolutionPreset.UHD4K;
CaptureScreenshot();
Preset = oldPreset;
}
[Button( "Quick 8K" ), Group( "Quick Capture" )]
public void Quick8K()
{
var oldPreset = Preset;
var oldWidth = CustomWidth;
var oldHeight = CustomHeight;
Preset = ResolutionPreset.Custom;
CustomWidth = 7680;
CustomHeight = 4320;
CaptureScreenshot();
Preset = oldPreset;
CustomWidth = oldWidth;
CustomHeight = oldHeight;
}
[Button( "Quick 720p" ), Group( "Quick Capture" )]
public void Quick720p()
{
var oldPreset = Preset;
var oldWidth = CustomWidth;
var oldHeight = CustomHeight;
Preset = ResolutionPreset.Custom;
CustomWidth = 1280;
CustomHeight = 720;
CaptureScreenshot();
Preset = oldPreset;
CustomWidth = oldWidth;
CustomHeight = oldHeight;
}
[Button( "Quick Instagram (Square)" ), Group( "Quick Capture" )]
public void QuickInstagram()
{
var oldPreset = Preset;
var oldWidth = CustomWidth;
var oldHeight = CustomHeight;
Preset = ResolutionPreset.Custom;
CustomWidth = 1080;
CustomHeight = 1080;
CaptureScreenshot();
Preset = oldPreset;
CustomWidth = oldWidth;
CustomHeight = oldHeight;
}
[Button( "Quick YouTube" ), Group( "Quick Capture" )]
public void QuickYouTube()
{
var oldPreset = Preset;
var oldWidth = CustomWidth;
var oldHeight = CustomHeight;
Preset = ResolutionPreset.Custom;
CustomWidth = 1280;
CustomHeight = 720;
CaptureScreenshot();
Preset = oldPreset;
CustomWidth = oldWidth;
CustomHeight = oldHeight;
}
[Button( "Quick Cinematic (21:9)" ), Group( "Quick Capture" )]
public void QuickCinematic()
{
var oldPreset = Preset;
var oldWidth = CustomWidth;
var oldHeight = CustomHeight;
Preset = ResolutionPreset.Custom;
CustomWidth = 3840;
CustomHeight = 1646;
CaptureScreenshot();
Preset = oldPreset;
CustomWidth = oldWidth;
CustomHeight = oldHeight;
}
[Button( "Quick Portrait" ), Group( "Quick Capture" )]
public void QuickPortrait()
{
var oldPreset = Preset;
var oldWidth = CustomWidth;
var oldHeight = CustomHeight;
Preset = ResolutionPreset.Custom;
CustomWidth = 1080;
CustomHeight = 1920;
CaptureScreenshot();
Preset = oldPreset;
CustomWidth = oldWidth;
CustomHeight = oldHeight;
}
[Button( "Quick Square 4K" ), Group( "Quick Capture" )]
public void QuickSquare4K()
{
var oldPreset = Preset;
var oldWidth = CustomWidth;
var oldHeight = CustomHeight;
Preset = ResolutionPreset.Custom;
CustomWidth = 3840;
CustomHeight = 3840;
CaptureScreenshot();
Preset = oldPreset;
CustomWidth = oldWidth;
CustomHeight = oldHeight;
}
[Button( "Capture with UI" ), Group( "Quick Capture" )]
public void CaptureWithUI()
{
var oldHideUI = HideUI;
HideUI = false;
CaptureScreenshot();
HideUI = oldHideUI;
}
[Button( "Capture Clean (No UI/Tags)" ), Group( "Quick Capture" )]
public void CaptureClean()
{
var oldHideUI = HideUI;
var oldHideTags = HideTags;
HideUI = true;
HideTags = "player,debug,ui";
CaptureScreenshot();
HideUI = oldHideUI;
HideTags = oldHideTags;
}
[Button( "Capture All Presets" ), Group( "Quick Capture" )]
[Description( "Takes screenshots at 1080p, 2K, and 4K" )]
public void CaptureAllPresets()
{
if ( isCapturing )
{
Log.Info( "[Super Shot] Already capturing, please wait..." );
return;
}
_ = CaptureAllPresetsAsync();
}
[Button( "Spawn Orbit Camera" ), Group( "10. Video Cameras" )]
[Description( "Adds an orbit camera to the scene (orbits around target)" )]
public void SpawnOrbitCamera()
{
SpawnOrbitCameraInternal();
}
[Button( "Spawn Dolly Camera" ), Group( "10. Video Cameras" )]
[Description( "Adds a dolly camera to the scene (push in / pull back from target)" )]
public void SpawnDollyCamera()
{
SpawnDollyCameraInternal();
}
// ─────────────────────────────────────────────────────────────
// CAPTURE LOGIC
// ─────────────────────────────────────────────────────────────
private async Task CaptureScreenshotAsync()
{
isCapturing = true;
try
{
// Get camera
captureCamera = GetCaptureCamera();
if ( captureCamera == null )
{
Log.Error( "[Super Shot] No camera found for capture!" );
isCapturing = false;
return;
}
// Get resolution
var resolution = GetResolution();
// Wait for delay
if ( CaptureDelay > 0f )
{
Log.Info( $"[Super Shot] Waiting {CaptureDelay}s before capture..." );
await GameTask.DelaySeconds( CaptureDelay );
}
// Prepare scene (hide UI, objects, etc.)
PrepareScene();
// Capture screenshot
string filePath = await TakeScreenshot( resolution );
// Restore scene
RestoreScene();
// Update stats
ScreenshotsTaken++;
Log.Info( $"[Super Shot] ✓ Screenshot captured: {(int)resolution.x}x{(int)resolution.y}" );
}
catch ( Exception ex )
{
Log.Error( $"[Super Shot] Failed to capture screenshot: {ex.Message}" );
}
finally
{
isCapturing = false;
}
}
private async Task CaptureAllPresetsAsync()
{
isCapturing = true;
try
{
var originalPreset = Preset;
Log.Info( "[Super Shot] Capturing all presets (1080p, 2K, 4K)..." );
// 1080p
Preset = ResolutionPreset.HD1080p;
await CaptureScreenshotAsync();
await GameTask.DelaySeconds( 0.5f );
// 2K
Preset = ResolutionPreset.QHD2K;
await CaptureScreenshotAsync();
await GameTask.DelaySeconds( 0.5f );
// 4K
Preset = ResolutionPreset.UHD4K;
await CaptureScreenshotAsync();
Preset = originalPreset;
Log.Info( "[Super Shot] All presets captured!" );
}
catch ( Exception ex )
{
Log.Error( $"[Super Shot] Failed to capture all presets: {ex.Message}" );
}
finally
{
isCapturing = false;
}
}
private async Task CaptureBatchAsync()
{
isCapturing = true;
try
{
Log.Info( $"[Super Shot] Starting batch capture: {BatchCount} screenshots" );
for ( int i = 0; i < BatchCount; i++ )
{
Log.Info( $"[Super Shot] Batch {i + 1}/{BatchCount}" );
// Get camera
captureCamera = GetCaptureCamera();
if ( captureCamera == null )
{
Log.Error( "[Super Shot] No camera found for capture!" );
break;
}
// Get resolution
var resolution = GetResolution();
// Prepare scene
PrepareScene();
// Capture
string filePath = await TakeScreenshot( resolution, i + 1 );
// Restore scene
RestoreScene();
ScreenshotsTaken++;
// Wait before next capture (except last one)
if ( i < BatchCount - 1 )
{
await GameTask.DelaySeconds( BatchDelay );
}
}
Log.Info( $"[Super Shot] Batch capture complete: {BatchCount} screenshots saved" );
}
catch ( Exception ex )
{
Log.Error( $"[Super Shot] Batch capture failed: {ex.Message}" );
}
finally
{
isCapturing = false;
}
}
// ─────────────────────────────────────────────────────────────
// HELPER METHODS
// ─────────────────────────────────────────────────────────────
private void SpawnOrbitCameraInternal()
{
var target = (OrbitCameraTarget != null && OrbitCameraTarget.IsValid) ? OrbitCameraTarget : GameObject;
var center = target.WorldPosition;
var go = new GameObject( true, "SuperShot_CameraOrbit" );
go.SetParent( GameObject.Parent, true );
go.WorldPosition = center + new Vector3( 200f, 0f, 80f );
var cam = go.Components.Create<CameraComponent>();
cam.FieldOfView = 60f;
var orbit = go.Components.Create<CameraOrbit>();
orbit.Target = target;
orbit.OrbitRadius = 200f;
orbit.OrbitHeight = 80f;
orbit.OrbitSpeed = 15f;
orbit.CameraFOV = 60f;
orbit.LookAtTarget = true;
orbit.SmoothRotation = true;
Log.Info( "[Super Shot] Orbit camera spawned. Assign as Custom Camera for captures or use for video." );
}
private void SpawnDollyCameraInternal()
{
var target = (DollyCameraTarget != null && DollyCameraTarget.IsValid) ? DollyCameraTarget : GameObject;
var center = target.WorldPosition;
var go = new GameObject( true, "SuperShot_CameraDolly" );
go.SetParent( GameObject.Parent, true );
go.WorldPosition = center + new Vector3( 0f, 0f, 300f );
var cam = go.Components.Create<CameraComponent>();
cam.FieldOfView = 60f;
var dolly = go.Components.Create<CameraDolly>();
dolly.Target = target;
dolly.Speed = 50f;
dolly.MinDistance = 50f;
dolly.MaxDistance = 2000f;
dolly.CameraFOV = 60f;
dolly.LookAtTarget = true;
dolly.SmoothRotation = true;
Log.Info( "[Super Shot] Dolly camera spawned. Use Push In / Pull Back on the component, or set Custom Camera for captures." );
}
private CameraComponent GetCaptureCamera()
{
if ( UseCustomCamera && CustomCamera != null && CustomCamera.IsValid )
{
return CustomCamera;
}
// Find main camera
var mainCamera = Scene.GetAllComponents<CameraComponent>()
.FirstOrDefault( c => c.IsMainCamera );
if ( mainCamera == null )
{
mainCamera = Scene.GetAllComponents<CameraComponent>().FirstOrDefault();
}
return mainCamera;
}
private Vector2 GetResolution()
{
switch ( Preset )
{
case ResolutionPreset.HD1080p:
return new Vector2( 1920, 1080 );
case ResolutionPreset.QHD2K:
return new Vector2( 2560, 1440 );
case ResolutionPreset.UHD4K:
return new Vector2( 3840, 2160 );
case ResolutionPreset.Custom:
return new Vector2( CustomWidth, CustomHeight );
default:
return new Vector2( 1920, 1080 );
}
}
private void PrepareScene()
{
hiddenObjects.Clear();
hiddenComponents.Clear();
// Freeze time if requested (note: may not be fully supported)
if ( FreezeTime )
{
wasTimeFrozen = true;
}
// Hide UI
if ( HideUI )
{
// Hide all PanelComponent-based UI
var uiPanels = Scene.GetAllComponents<PanelComponent>();
foreach ( var panel in uiPanels )
{
if ( panel != null && panel.IsValid && panel.Enabled )
{
panel.Enabled = false;
hiddenComponents.Add( panel );
}
}
// Hide ScreenPanel components
var screenPanels = Scene.GetAllComponents<ScreenPanel>();
foreach ( var panel in screenPanels )
{
if ( panel != null && panel.IsValid && panel.Enabled )
{
panel.Enabled = false;
hiddenComponents.Add( panel );
}
}
}
// Hide objects by tags
if ( !string.IsNullOrWhiteSpace( HideTags ) )
{
var tags = HideTags.Split( ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries );
foreach ( var tag in tags )
{
var objects = Scene.GetAllObjects( true )
.Where( obj => obj.Tags.Has( tag ) );
foreach ( var obj in objects )
{
if ( obj != null && obj.IsValid && obj.Enabled )
{
obj.Enabled = false;
hiddenObjects.Add( obj );
}
}
}
}
// Hide specific component types
if ( !string.IsNullOrWhiteSpace( HideComponentTypes ) )
{
var componentTypes = HideComponentTypes.Split( ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries );
foreach ( var typeName in componentTypes )
{
// Try to find and hide components by type name
// This is a simplified approach - full type resolution would be more robust
var allComponents = Scene.GetAllComponents<Component>();
foreach ( var comp in allComponents )
{
if ( comp != null && comp.IsValid && comp.GetType().Name.Contains( typeName, StringComparison.OrdinalIgnoreCase ) )
{
if ( comp.Enabled )
{
comp.Enabled = false;
hiddenComponents.Add( comp );
}
}
}
}
}
// Show only tagged objects (hide everything else)
if ( !string.IsNullOrWhiteSpace( ShowOnlyTag ) )
{
var allObjects = Scene.GetAllObjects( true );
foreach ( var obj in allObjects )
{
if ( obj != null && obj.IsValid && obj.Enabled )
{
if ( !obj.Tags.Has( ShowOnlyTag ) )
{
obj.Enabled = false;
hiddenObjects.Add( obj );
}
}
}
}
// Override FOV if requested
if ( OverrideFOV && captureCamera != null && captureCamera.IsValid )
{
originalFOV = captureCamera.FieldOfView;
captureCamera.FieldOfView = CaptureFOV;
}
}
private void RestoreScene()
{
// Restore FOV
if ( OverrideFOV && captureCamera != null )
{
captureCamera.FieldOfView = originalFOV;
}
// Restore hidden components
foreach ( var comp in hiddenComponents )
{
if ( comp != null && comp.IsValid )
{
comp.Enabled = true;
}
}
hiddenComponents.Clear();
// Restore hidden objects
foreach ( var obj in hiddenObjects )
{
if ( obj != null && obj.IsValid )
{
obj.Enabled = true;
}
}
hiddenObjects.Clear();
// Restore time (if we had frozen it)
if ( wasTimeFrozen )
{
wasTimeFrozen = false;
}
}
private async Task<string> TakeScreenshot( Vector2 resolution, int batchIndex = 0 )
{
// Note: Screenshots are saved to s&Box's default screenshot location via screenshot_highres command
// Apply visual effects before capture
await ApplyVisualEffects();
// Wait one frame to ensure scene is prepared
await GameTask.DelaySeconds( 0.016f );
// Return a reference for logging
string referencePath = $"Super Shot: {(int)resolution.x}x{(int)resolution.y}";
// Take screenshot using s&Box API
try
{
int width = (int)resolution.x;
int height = (int)resolution.y;
// Apply super sampling if enabled
if ( SuperSampling > 1f )
{
width = (int)( width * SuperSampling );
height = (int)( height * SuperSampling );
Log.Info( $"[Super Shot] Using super sampling {SuperSampling}x: {width}x{height}" );
}
if ( captureCamera != null && captureCamera.IsValid )
{
try
{
// Use s&Box's built-in screenshot_highres command
string command = $"screenshot_highres {width} {height}";
// Execute the screenshot command
ConsoleSystem.Run( command );
// Wait a moment for the screenshot to be saved
await GameTask.DelaySeconds( 0.1f );
Log.Info( $"[Super Shot] ✓ Captured {width}x{height}" );
}
catch ( Exception ex )
{
Log.Error( $"[Super Shot] Failed to execute screenshot command: {ex.Message}" );
// Fallback: Try without resolution parameters
try
{
ConsoleSystem.Run( "screenshot_highres" );
Log.Info( "[Super Shot] Executed fallback screenshot_highres (default resolution)" );
}
catch ( Exception ex2 )
{
Log.Error( $"[Super Shot] Fallback screenshot also failed: {ex2.Message}" );
throw;
}
}
}
else
{
Log.Error( "[Super Shot] No valid camera for screenshot capture" );
}
}
catch ( Exception ex )
{
Log.Error( $"[Super Shot] Failed to prepare screenshot capture: {ex.Message}" );
throw;
}
return referencePath;
}
private async Task ApplyVisualEffects()
{
if ( captureCamera == null || !captureCamera.IsValid )
return;
// Camera shake effect
if ( CameraShake )
{
await ApplyCameraShake();
}
// Slow motion effect
if ( SlowMotion )
{
// Note: Time scale manipulation may not be available in s&Box
// This is a placeholder for the effect concept
Log.Info( $"[Super Shot] Slow motion effect applied (speed: {SlowMotionSpeed})" );
}
// Log all applied visual effects
var effects = new List<string>();
if ( ColorFilter != ColorFilterType.None )
effects.Add( $"Color Filter: {ColorFilter}" );
if ( VignetteIntensity > 0f )
effects.Add( $"Vignette: {VignetteIntensity:F2}" );
if ( ExposureAdjustment != 0f )
effects.Add( $"Exposure: {ExposureAdjustment:+#0.0;-#0.0}" );
if ( DepthOfField )
effects.Add( $"Depth of Field: {BlurIntensity:F1}x" );
if ( MotionBlur )
effects.Add( $"Motion Blur: {MotionBlurIntensity:F1}x" );
if ( ChromaticAberration )
effects.Add( $"Chromatic Aberration: {AberrationIntensity:F2}" );
if ( FilmGrain )
effects.Add( $"Film Grain: {GrainIntensity:F2}" );
if ( LensFlare )
effects.Add( $"Lens Flare: {FlareIntensity:F1}x" );
if ( SaturationAdjustment != 0f )
effects.Add( $"Saturation: {SaturationAdjustment:+#0.0;-#0.0}" );
if ( ContrastAdjustment != 0f )
effects.Add( $"Contrast: {ContrastAdjustment:+#0.0;-#0.0}" );
if ( effects.Count > 0 )
{
Log.Info( $"[Super Shot] Visual effects: {string.Join( ", ", effects )}" );
}
}
private async Task ApplyCameraShake()
{
if ( captureCamera == null || !captureCamera.IsValid )
return;
var originalPos = captureCamera.GameObject.WorldPosition;
var originalRot = captureCamera.GameObject.WorldRotation;
// Apply random shake
for ( int i = 0; i < 5; i++ )
{
var shakeOffset = Vector3.Random * ShakeIntensity * 0.1f;
var shakeRot = Angles.Random * ShakeIntensity * 0.5f;
captureCamera.GameObject.WorldPosition = originalPos + shakeOffset;
captureCamera.GameObject.WorldRotation = originalRot * Rotation.From( shakeRot );
await GameTask.DelaySeconds( 0.02f );
}
// Restore original position
captureCamera.GameObject.WorldPosition = originalPos;
captureCamera.GameObject.WorldRotation = originalRot;
}
}
}