Weapons/CameraWeapon.cs
using Sandbox.Rendering;

public sealed class CameraWeapon : BaseWeapon
{
	float fov = 50;
	float roll = 0;

	DepthOfField dof;
	bool focusing;
	Vector3 focusPoint;

	[Property] SoundEvent CameraShoot { get; set; }

	/// <summary>
	/// The RT camera's resolution 
	/// </summary>
	private static int _cameraResolution = 512;

	/// <summary>
	/// The render target texture produced by this camera. Read by <see cref="TVEntity"/>.
	/// </summary>
	public Texture RenderTexture => _renderTexture;

	private Texture _renderTexture;
	private CameraComponent _rtCamera;

	public override bool WantsHideHud => true;

	protected override void OnEnabled()
	{
		base.OnEnabled();

		EnsureRTCamera();
		EnsureRenderTexture();
	}

	protected override void OnDisabled()
	{
		base.OnDisabled();

		DestroyDepthOfField();
		CleanupRenderTexture();
		_rtCamera = null;
	}

	protected override void OnDestroy()
	{
		DestroyDepthOfField();
		CleanupRenderTexture();
		_rtCamera = null;
	}

	protected override void OnPreRender()
	{
		if ( !_rtCamera.IsValid() ) return;

		EnsureRenderTexture();

		if ( HasOwner && Scene.Camera.IsValid() )
		{
			// When held, mirror the player's camera so the TV shows their POV.
			// TODO: network some props to the TV so they show up in the RT camera when held by a player other than the host.
			_rtCamera.WorldPosition = Scene.Camera.WorldPosition;
			_rtCamera.WorldRotation = Scene.Camera.WorldRotation;
			_rtCamera.FieldOfView = Scene.Camera.FieldOfView;

			if ( !_rtCamera.RenderExcludeTags.Has( "viewer" ) )
				_rtCamera.RenderExcludeTags.Add( "viewer" );
		}
		else
		{
			_rtCamera.RenderExcludeTags.Remove( "viewer" );
			_rtCamera.FieldOfView = 40f;
		}
	}

	/// <summary>
	/// We want to control the camera fov when held by a player.
	/// </summary>
	public override void OnCameraSetup( Player player, Sandbox.CameraComponent camera )
	{
		if ( !player.Network.IsOwner || !Network.IsOwner ) return;

		camera.FieldOfView = fov;
		camera.WorldRotation = camera.WorldRotation * new Angles( 0, 0, roll );
	}

	public override void OnCameraMove( Player player, ref Angles angles )
	{
		if ( Input.Down( "attack2" ) )
		{
			angles = default;
		}

		float sensitivity = fov.Remap( 1, 70, 0.01f, 1 );
		angles *= sensitivity;
	}

	public override void OnControl( Player player )
	{
		base.OnControl( player );

		if ( Input.Pressed( "reload" ) )
		{
			fov = 50;
			roll = 0;
		}

		if ( Input.Down( "attack2" ) )
		{
			fov += Input.AnalogLook.pitch;
			fov = fov.Clamp( 1, 150 );
			roll -= Input.AnalogLook.yaw;
		}

		EnsureDepthOfField();

		if ( dof.IsValid() )
		{
			UpdateDepthOfField( dof );
		}

		if ( focusing && Input.Released( "attack1" ) )
		{
			Game.TakeScreenshot();
			Sandbox.Services.Stats.Increment( "photos", 1 );

			GameObject?.PlaySound( CameraShoot );
		}

		focusing = Input.Down( "attack1" );
	}

	private void EnsureDepthOfField()
	{
		if ( dof.IsValid() ) return;

		dof = Scene.Camera.GetOrAddComponent<DepthOfField>();
		dof.Flags |= ComponentFlags.NotNetworked;
		focusing = false;
	}

	private void DestroyDepthOfField()
	{
		dof?.Destroy();
		dof = default;
	}

	private void UpdateDepthOfField( DepthOfField dof )
	{
		if ( !focusing )
		{
			dof.BlurSize = MathF.Pow( Scene.Camera.FieldOfView.Remap( 1, 55, 1, 0 ), 4 ) * 16;
			dof.FocusRange = 512;
			dof.FrontBlur = false;

			var tr = Scene.Trace.Ray( Scene.Camera.Transform.World.ForwardRay, 5000 )
								.Radius( 4 )
								.IgnoreGameObjectHierarchy( GameObject.Root )
								.Run();

			focusPoint = tr.EndPosition;
		}

		var target = Scene.Camera.WorldPosition.Distance( focusPoint ) + 64;

		dof.FocalDistance = dof.FocalDistance.LerpTo( target, Time.Delta * 2.0f );
	}

	private void EnsureRTCamera()
	{
		_rtCamera = GetComponentInChildren<CameraComponent>( true );

		if ( _rtCamera is null )
		{
			var go = new GameObject( GameObject, true, "rt_camera" );
			_rtCamera = go.AddComponent<CameraComponent>();
		}

		_rtCamera.IsMainCamera = false;
		_rtCamera.BackgroundColor = Color.Black;
		_rtCamera.ClearFlags = ClearFlags.Color | ClearFlags.Depth | ClearFlags.Stencil;
		_rtCamera.FieldOfView = fov;
		_rtCamera.RenderExcludeTags.Add( "viewmodel" );
	}

	private void EnsureRenderTexture()
	{
		if ( _renderTexture.IsValid() && _renderTexture.Width == _cameraResolution && _renderTexture.Height == _cameraResolution )
			return;

		CleanupRenderTexture();

		_renderTexture = Texture.CreateRenderTarget()
			.WithSize( _cameraResolution, _cameraResolution )
			.Create();

		if ( _rtCamera.IsValid() )
		{
			_rtCamera.RenderTarget = _renderTexture;
		}
	}

	private void CleanupRenderTexture()
	{
		if ( _rtCamera.IsValid() )
		{
			_rtCamera.RenderTarget = null;
		}

		_renderTexture?.Dispose();
		_renderTexture = null;
	}

	public override void DrawHud( HudPainter painter, Vector2 crosshair )
	{
		// nothing!
	}
}