PaintGun.cs
using System;
using Sandbox;

public sealed class PaintGun : Component
{
	[RequireComponent] PlayerController Player { get; set; }

	[Property, Category( "Paint" )] public GameObject DecalPrefab { get; set; }
	[Property, Category( "Paint" )] public Color PaintColor { get; set; } = new Color( 1f, 0.4f, 0f ); 
	[Property, Category( "Weapon" )] public float FireRate { get; set; } = 0.12f;
	[Property, Category( "Weapon" )] public float ProjectileSpeed { get; set; } = 1500f;
	[Property, Category( "Weapon" )] public float ArcStrength { get; set; } = 80f;

	[Property, Category( "Ammo" ), Sync] public int AmmoInMagazine { get; set; } = 30;
	[Property, Category( "Ammo" ), Sync] public int AmmoReserve { get; set; } = 120;
	[Property, Category( "Ammo" )] public int MagazineSize { get; set; } = 30;
	[Property, Category( "Ammo" )] public int MaxReserve { get; set; } = 120;
	[Property, Category( "Ammo" )] public float ReloadTime { get; set; } = 1.5f;

	[Property, Category( "Audio" )] public SoundEvent ReloadSound { get; set; }
	[Property, Category( "Audio" )] public SoundEvent HitSound { get; set; }

	public bool IsReloading { get; private set; }
	TimeSince timeSinceReloadStart;

	TimeSince timeSinceLastShot;

	protected override void OnUpdate()
	{
		if ( IsProxy ) return;

		// Забороняємо стріляти, якщо персонаж у слизі, або якщо з моменту виходу пройшло менше 2 секунд
		var swimming = Player.Components.Get<PaintSwimming>();
		if ( swimming.IsValid() )
		{
			if ( swimming.IsDiving ) return;
			if ( swimming.TimeSinceStoppedDiving < 2f ) return;
		}

		if ( IsReloading )
		{
			if ( timeSinceReloadStart >= ReloadTime )
			{
				FinishReload();
			}
			return;
		}

		if ( Input.Pressed( "Reload" ) && AmmoInMagazine < MagazineSize && AmmoReserve > 0 )
		{
			StartReload();
			return;
		}

		if ( Input.Down( "Attack1" ) && timeSinceLastShot >= FireRate )
		{
			if ( AmmoInMagazine > 0 )
			{
				Shoot();
				AmmoInMagazine--;
				timeSinceLastShot = 0;
			}
			else if ( AmmoReserve > 0 )
			{
				StartReload();
			}
		}
	}

	void StartReload()
	{
		IsReloading = true;
		timeSinceReloadStart = 0;
		
		if ( ReloadSound != null )
		{
			Sound.Play( ReloadSound, WorldPosition );
		}
		
		Log.Info( "Reloading..." );
	}

	void FinishReload()
	{
		IsReloading = false;
		int needed = MagazineSize - AmmoInMagazine;
		int toReload = Math.Min( needed, AmmoReserve );
		
		AmmoInMagazine += toReload;
		AmmoReserve -= toReload;
		Log.Info( $"Reload finished. Ammo: {AmmoInMagazine}/{AmmoReserve}" );
	}

	void Shoot()
	{
		Log.Info("НОВИЙ КОД ПРАЦЮЄ!");
       
		var eyePos = Player.EyePosition;
		var eyeRot = Player.EyeAngles.ToRotation();
		var eyeDir = eyeRot.Forward;

		var go = new GameObject();
		go.Name = "PaintProjectile";
       
		// Актуальне API: Використовуємо WorldPosition, WorldRotation та LocalScale
		go.WorldPosition = eyePos + eyeDir * 40f; 
		go.WorldRotation = eyeRot;
		go.LocalScale = Vector3.One * 0.12f;

		var renderer = go.Components.Create<ModelRenderer>();
		renderer.Model = Model.Load( "models/dev/sphere.vmdl" );
		renderer.Tint = PaintColor;

		// Додаємо скрипт снаряда
		var paint = go.Components.Create<PaintProjectile>();
		paint.DecalPrefab = this.DecalPrefab;
		paint.PaintColor = PaintColor;
		paint.HitSound = this.HitSound;
		
		// Читаємо команду гравця та передаємо в снаряд
		var playerTeam = Player.GameObject.Components.GetAll<PlayerTeam>( FindMode.EverythingInSelfAndDescendants ).FirstOrDefault();
		paint.TeamId = playerTeam.IsValid() ? playerTeam.TeamId : 0;
		paint.Shooter = Player.GameObject;

		paint.Velocity = eyeDir * ProjectileSpeed + Vector3.Up * ArcStrength;
	}
}