Ball.cs
using Sandbox;
using Sandbox.Network;
using Sandbox.Services;
using System;
using System.Numerics;

public sealed class Ball : Component, Component.ICollisionListener
{
	GameObject gameManager;

	[Property, Sync] public int Speed { get; set; }
	[Property] public bool CollisionSound { get; set; } = true;
	[Property] float pushDelay = 1.5f;
	bool pushTimerEnabled = false;
	[Sync] public Vector3 Direction { get; set; }
	float paddleTouchCount = 0;
	float phantomTouchCount = 0;
	[Sync] public Color Color { get; set; } = Color.White;

	protected override void OnEnabled()
	{
		base.OnEnabled();
		gameManager = Scene.FindAllWithTag( "GameManager" ).FirstOrDefault();

		Start();
	}

	protected override void OnUpdate()
    {		
		if ( !GameSettings.Acceleration )
		{
			Speed = GameSettings.Speed;
		}

		GetComponent<ModelRenderer>().Tint = Color;
		LocalRotation = Rotation.Identity;

		if ( pushTimerEnabled )
		{
			pushDelay -= Time.Delta;
			if ( pushDelay <= 0 )
			{
				pushTimerEnabled = false;
				pushDelay = 1.5f;
				PushBall();
			}
		}

		//DebugOverlay.Line(0, Direction * 100);
		//DebugOverlay.Text( new Vector3(0, -10), $"{MathF.Atan2( Direction.y, Direction.x )}" );
	}

	public void Start()
	{
		GetComponent<Rigidbody>().Velocity = Vector3.Zero;
		Speed = GameSettings.Speed;
		Color = Color.White;

		var random = new Random();
		WorldPosition = new Vector3( 0, random.Next( -120, 120 ), 7 );
		
		paddleTouchCount = 0;
		phantomTouchCount = 0;
		pushTimerEnabled = true;
	}

	void PushBall()
	{
		if ( pushTimerEnabled ) return;
		for (int i = 0; i < 10;)
		{
			Direction = Vector3.Random.WithZ(0);
			if ( MathF.Atan2( Direction.Abs().y, Direction.Abs().x ) > 1.2 && MathF.Atan2( Direction.Abs().y, Direction.Abs().x ) < 1.9 )
			{
				i++;
			}
			else
			{
				break;
			}
		}

		GetComponent<Rigidbody>().Velocity = (Direction.Normal * Speed).WithZ( 0 );
	}

	public void OnCollisionStart(Collision collision)
	{
		collision.Self.Body.EnableCollisionSounds = CollisionSound;

		Direction = Vector3.Reflect( Direction, collision.Contact.Normal ).Normal.WithZ( 0 );

		collision.Self.Body.Velocity = Direction.Normal * Speed;
		collision.Self.Body.AngularVelocity = 0;

		if ( collision.Other.GameObject.Tags.Has( "paddle" ) && GameSettings.Acceleration )
		{
			paddleTouchCount++;
			phantomTouchCount = 0;
			Speed += Convert.ToInt32( paddleTouchCount / 50f * GameSettings.Speed );
			Color = collision.Other.GameObject.GetComponent<ModelRenderer>().Tint;
		}
		else if ( collision.Other.GameObject.Tags.Has( "top" ) || collision.Other.GameObject.Tags.Has( "bottom" ) )
		{
			phantomTouchCount++;
		}

		if ( phantomTouchCount >= 8 )
		{
			Start();
		}
	}

	public void OnCollisionStop(CollisionStop collision)
	{
		collision.Self.Body.Velocity = Direction.Normal * Speed;
		collision.Self.Body.AngularVelocity = 0;
	}
}