ExampleComponents/TurretComponent.cs
using Sandbox;
using Sandbox.Diagnostics;
using Sandbox.Services;
using System;
using System.Threading;

public sealed class TurretComponent : Component
{
	[Property] GameObject Gun { get; set; }
	[Property] GameObject Bullet { get; set; }
	[Property] GameObject SecondaryBullet { get; set; }
	[Property] GameObject Muzzle { get; set; }

	[Property] ModelRenderer GunModel { get; set; }
	[Property] Gradient GunColorGradient { get; set; }
	[Property] Curve GunSizeCurve { get; set; }

	float turretYaw;
	float turretPitch;

	TimeSince timeSinceLastSecondary;

	TimeSince timeSincePrimary = 10;

	void FlashGunModel()
	{
		if ( !GunModel.IsValid() ) return;
		if ( timeSincePrimary < 0 ) return;

		GunModel.Tint = GunColorGradient.Evaluate( timeSincePrimary * 2.0f );
		GunModel.LocalScale = GunSizeCurve.Evaluate( timeSincePrimary * 2.0f );
	}

	protected override void OnUpdate()
	{
		FlashGunModel();

		// rotate gun using mouse input
		turretYaw -= Input.MouseDelta.x * 0.1f;
		turretPitch += Input.MouseDelta.y * 0.1f;
		turretPitch = turretPitch.Clamp( -30, 30 );
		Gun.WorldRotation = Rotation.From( turretPitch, turretYaw, 0 );

		var bbox = BBox.FromPositionAndSize( 0, 5 );

		// drive tank
		Vector3 movement = 0;
		if ( Input.Down( "Forward" ) ) movement += WorldTransform.Forward;
		if ( Input.Down( "backward" ) ) movement += WorldTransform.Backward;

		var rot = GameObject.WorldRotation;
		var pos = GameObject.WorldPosition + movement * Time.Delta * 100.0f;

		if ( Input.Down( "Left" ) )
		{
			rot *= Rotation.From( 0, Time.Delta * 90.0f, 0 );
		}

		if ( Input.Down( "Right" ) )
		{
			rot *= Rotation.From( 0, Time.Delta * -90.0f, 0 );
		}

		LocalTransform = new Transform( pos, rot, 1 );

		if ( Input.Pressed( "Attack1" ) )
		{
			Assert.NotNull( Bullet );

			var obj = Bullet.Clone( Muzzle.WorldPosition, Muzzle.WorldRotation );
			var physics = obj.Components.Get<Rigidbody>( FindMode.EnabledInSelfAndDescendants );
			if ( physics.IsValid() )
			{
				physics.Velocity = Muzzle.WorldRotation.Forward * 2000.0f;
			}

			Stats.Increment( "balls_fired", 1 );

			// Testing sound
			Sound.Play( "sounds/kenney/ui/ui.downvote.sound", WorldPosition );
			timeSincePrimary = 0;
		}

		var tr = Scene.Trace
			.Ray( Muzzle.WorldPosition + Muzzle.WorldRotation.Forward * 50.0f, Muzzle.WorldPosition + Muzzle.WorldRotation.Forward * 4000 )
			.Size( bbox )
			//.Radius( 40 )
			.Run();
		/*
		Gizmo.Transform = global::Transform.Zero;
		Gizmo.Draw.Color = Color.White;
		Gizmo.Draw.LineThickness = 1;
		Gizmo.Draw.Line( tr.StartPosition, tr.EndPosition );
		Gizmo.Draw.Line( tr.HitPosition, tr.HitPosition + tr.Normal * 30.0f );

		Gizmo.Draw.LineSphere( new Sphere( tr.HitPosition, 10.0f ) );

		if ( tr.Body is not null )
		{
			var closestPos = tr.Body.FindClosestPoint( tr.HitPosition );
			Gizmo.Draw.LineSphere( new Sphere( closestPos, 10.0f ) );
		}

		var box = bbox;
		box.Mins += tr.EndPosition;
		box.Maxs += tr.EndPosition;

		Gizmo.Draw.LineBBox( box );
				*/
		//	Gizmo.Draw.Text( $"{tr.EndPosition}", Muzzle.WorldTransform );
		//	Gizmo.Draw.Text( $"{tr.HitPosition}\n{tr.Fraction}\n{tr.Direction}", new Transform( tr.HitPosition ) );

		using ( Gizmo.Scope( "circle", new Transform( tr.HitPosition, Rotation.LookAt( tr.Normal ) ) ) )
		{
		//	Gizmo.Draw.LineCircle( 0, 30 );
		}

		if ( Input.Pressed( "Attack2" ) && timeSinceLastSecondary > 0.02f && tr.Hit )
		{
			Stats.Increment( "cubes_fired", 1 );

			timeSinceLastSecondary = 0;

			int i = 0;

			var r = Muzzle.WorldRotation;

			for ( float f = 0; f < tr.Distance; f += 10.0f )
			{
				if ( i++ > 200 )
					break;

				var off = MathF.Sin( i * 0.4f ) * r.Right * 20.0f;
				off += MathF.Cos( i * 0.4f ) * r.Up * 20.0f;

				var obj = SecondaryBullet.Clone( tr.StartPosition + tr.Direction * f + off * 0.1f, r );

				//r *= Rotation.From( 2, 4, 2 );

				var physics = obj.Components.Get<Rigidbody>( FindMode.EnabledInSelfAndDescendants );
				if ( physics.IsValid() )
				{
					physics.Velocity = off * 2.0f;// Muzzle.WorldTransform.Rotation.Forward * 300.0f;
				}
			}
	
		}
	}
}